Sqala: Compile-Time SQL for Scala 3 with Named Tuples and Semantic Checking

Hey Scala devs! I’m excited to share sqala – a powerful new SQL query library built specifically for Scala 3 that blends SQL expressiveness with Scala’s type system. If you’re tired of ORM quirks or string-based SQL, this might be your new favorite tool.

Killer Features

  1. Projections to Named Tuples:
    you can easily project your fields to named tuples using map or select method:
case class User(@autoInc id: Long, name: String, age: Int)
val q = query:
  from[User].filter(_.name == "Dove").map(u => (name = u.name))
val res = db.fetch(q) // the type of res will be `List[(name: String)]`
  1. Show sql as compiler info when query is an inline function:

  2. SQL semantic checking with meaningful warning:

  3. Support Oracle’s CONNECT BY (query recursive data) feature but generating standard SQL.

case class Department(id: Int, managerId: Int, name: String)

val q = query:
    from[Department]
        .connectBy(d => prior(d.id) == d.managerId)
        .startWith(d => d.managerId == 0)
        .map(d => (id = d.id, managerId = d.managerId, name = d.name))
// will generate query with CTE, use inline def and db.fetch(q) to see generated sql

Getting Started

Docs are in site sqala-docs and you can do a quick experiment using scala-cli with following code:

//> using scala 3.7.1
//> using dep com.h2database:h2:2.3.232
//> using dep com.wz7982::sqala-jdbc:0.3.6

import org.h2.jdbcx.*
import sqala.jdbc.*
import sqala.metadata.*
import sqala.static.dsl.*

object DB:
  val ds = JdbcDataSource()
  ds.setURL("jdbc:h2:mem:default")
  val db = JdbcContext(ds, H2Dialect)

case class User(@autoInc id: Long, name: String, age: Int)

object Main:
  import DB.{db, ds}
  def main(args: Array[String]) =
    // logging with println
    given Logger = Logger(log => println(log))
    val conn = ds.getConnection()
    conn
      .createStatement()
      .executeUpdate:
        """CREATE TABLE `user` (`id` BIGINT AUTO_INCREMENT PRIMARY KEY,`name` TEXT,`age` INT)"""
    conn.commit()
    val user = User(0L, "Dove", 8)
    db.insert(user)
    inline def q = query:
      from[User].filter(_.name == "Dove").map(u => (id = u.id, name = u.name))
    val res = db.fetch(q)
    for re <- res do println(re.name)
10 Likes