Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
fec3fff
feat(sql): scaffold zio-blocks-sql module with build.sbt
987Nabil Mar 14, 2026
927cdb0
feat(sql): add DbValue and SqlDialect core types
987Nabil Mar 14, 2026
e9f65f8
feat(sql): implement Deriver[DbCodec] for Schema-driven codec derivation
987Nabil Mar 14, 2026
99d1632
feat(sql): add SqlNameMapper for column naming with snake_case default
987Nabil Mar 14, 2026
d9154ad
feat(sql): scaffold zio-blocks-sql module with build.sbt
987Nabil Mar 14, 2026
7195151
feat(sql): add JDBC backend (JdbcConnection, ResultSet mapping)
987Nabil Mar 14, 2026
08a343e
feat(sql): add sql"" interpolator with Frag type for parameterized qu…
987Nabil Mar 14, 2026
6d2cf84
feat(sql): add DDL generation (CREATE TABLE, DROP TABLE)
987Nabil Mar 14, 2026
201d377
feat(sql): add Table[A] derivation from Schema metadata
987Nabil Mar 14, 2026
a4dbf1c
feat(sql): add Transactor, context functions, and query execution
987Nabil Mar 14, 2026
b98397f
feat(sql-zio): add TransactorZIO wrapper for ZIO effect integration
987Nabil Mar 14, 2026
9ea3285
style(sql-zio): fix scalafmt indentation
987Nabil Mar 14, 2026
8817dc7
fix(sql): load SQLite JDBC driver explicitly in TransactorSpec
987Nabil Mar 14, 2026
b6a078f
test(sql): add comprehensive type roundtrip tests and DbParam coverage
987Nabil Mar 14, 2026
b4c2113
fix(build): remove sql modules from cross-version test/doc aliases
987Nabil Mar 14, 2026
c544b51
Revert "fix(build): remove sql modules from cross-version test/doc al…
987Nabil Mar 14, 2026
dc64607
fix(build): support Scala 3.3 LTS + 3.7 for sql modules
987Nabil Mar 14, 2026
14bc482
fix(sql): replace UUID.randomUUID() in shared tests for Scala.js comp…
987Nabil Mar 14, 2026
a417f6d
fix(build): remove Scala-3-only sql modules from cross-version test/d…
987Nabil Mar 14, 2026
c4dd7aa
fix(sql): address Copilot review comments — pluralize, Option, sql in…
987Nabil Mar 14, 2026
c62885f
feat(sql): add Repo[E, ID] with auto-generated CRUD, fixed-name Table…
987Nabil Mar 15, 2026
3bbf4a2
feat(sql): add Frag extensions (.query, .queryOne, .queryLimit, .upda…
987Nabil Mar 15, 2026
3fa0f69
fix(sql): Repo returns affected rows, count via SqlOps, transact safe…
987Nabil Mar 15, 2026
d09e916
feat(sql): support Scala 3 enums and simple sealed traits in DbCodec
987Nabil Mar 15, 2026
6513ea1
feat(sql): add insertReturning, insertAll batch, and SqlLogger
987Nabil Mar 15, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,9 @@ lazy val root = project
scope.jvm,
scope.js,
`scope-examples`,
sql.jvm,
sql.js,
`sql-zio`,
schema.jvm,
schema.js,
`schema-avro`,
Expand Down Expand Up @@ -299,6 +302,45 @@ lazy val scope = crossProject(JSPlatform, JVMPlatform)
coverageMinimumBranchTotal := 65
)

lazy val sql = crossProject(JSPlatform, JVMPlatform)
.crossType(CrossType.Full)
.dependsOn(schema, scope)
.settings(stdSettings("zio-blocks-sql", Seq(BuildHelper.Scala3, BuildHelper.Scala33)))
.settings(crossProjectSettings)
.settings(buildInfoSettings("zio.blocks.sql"))
.enablePlugins(BuildInfoPlugin)
.jvmSettings(mimaSettings(failOnProblem = false))
.jsSettings(jsSettings)
.settings(
libraryDependencies ++= Seq(
"dev.zio" %%% "zio-test" % "2.1.24" % Test,
"dev.zio" %%% "zio-test-sbt" % "2.1.24" % Test
),
coverageMinimumStmtTotal := 0,
coverageMinimumBranchTotal := 0
)
.jvmSettings(
libraryDependencies ++= Seq(
"org.xerial" % "sqlite-jdbc" % "3.49.1.0" % Test,
"org.postgresql" % "postgresql" % "42.7.5" % Test
)
)

lazy val `sql-zio` = project
.settings(stdSettings("zio-blocks-sql-zio", Seq(BuildHelper.Scala3, BuildHelper.Scala33)))
.dependsOn(sql.jvm)
.settings(buildInfoSettings("zio.blocks.sql.zio"))
.enablePlugins(BuildInfoPlugin)
.settings(
libraryDependencies ++= Seq(
"dev.zio" %% "zio" % "2.1.24",
"dev.zio" %% "zio-test" % "2.1.24" % Test,
"dev.zio" %% "zio-test-sbt" % "2.1.24" % Test
),
coverageMinimumStmtTotal := 0,
coverageMinimumBranchTotal := 0
)

lazy val `scope-examples` = project
.settings(stdSettings("zio-blocks-scope-examples", Seq(BuildHelper.Scala3)))
.dependsOn(scope.jvm)
Expand Down
36 changes: 36 additions & 0 deletions sql-zio/src/main/scala/zio/blocks/sql/zio/TransactorZIO.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package zio.blocks.sql.zio

import zio._
import zio.blocks.sql._

class TransactorZIO(underlying: Transactor) {

def connect[A](f: DbCon ?=> A): Task[A] =
ZIO.attemptBlocking(underlying.connect(f))

def transact[A](f: DbTx ?=> A): Task[A] =
ZIO.attemptBlocking(underlying.transact(f))
}

object TransactorZIO {
def fromTransactor(transactor: Transactor): TransactorZIO =
new TransactorZIO(transactor)

def fromUrl(url: String, dialect: SqlDialect): TransactorZIO =
new TransactorZIO(JdbcTransactor.fromUrl(url, dialect))

def fromUrl(
url: String,
user: String,
password: String,
dialect: SqlDialect
): TransactorZIO =
new TransactorZIO(JdbcTransactor.fromUrl(url, user, password, dialect))

def fromDataSource(dataSource: javax.sql.DataSource, dialect: SqlDialect): TransactorZIO =
new TransactorZIO(JdbcTransactor.fromDataSource(dataSource, dialect))

// ZLayer for dependency injection
def layer(url: String, dialect: SqlDialect): ZLayer[Any, Nothing, TransactorZIO] =
ZLayer.succeed(fromUrl(url, dialect))
}
Empty file.
Empty file.
46 changes: 46 additions & 0 deletions sql/jvm/src/main/scala/zio/blocks/sql/JdbcConnection.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package zio.blocks.sql

import java.sql.Connection

class JdbcConnection(val underlying: Connection) extends DbConnection {

def prepareStatement(sql: String): DbPreparedStatement =
new JdbcPreparedStatement(underlying.prepareStatement(sql))

def close(): Unit = underlying.close()

def isClosed: Boolean = underlying.isClosed

def setAutoCommit(autoCommit: Boolean): Unit = underlying.setAutoCommit(autoCommit)

def getAutoCommit: Boolean = underlying.getAutoCommit

def commit(): Unit = underlying.commit()

def rollback(): Unit = underlying.rollback()
}

class JdbcPreparedStatement(val underlying: java.sql.PreparedStatement) extends DbPreparedStatement {

def executeQuery(): DbResultSet =
new JdbcResultSet(underlying.executeQuery())

def executeUpdate(): Int = underlying.executeUpdate()

def close(): Unit = underlying.close()

def paramWriter: DbParamWriter = new JdbcParamWriter(underlying)

def addBatch(): Unit = underlying.addBatch()

def executeBatch(): Array[Int] = underlying.executeBatch()
}

class JdbcResultSet(val underlying: java.sql.ResultSet) extends DbResultSet {

def next(): Boolean = underlying.next()

def close(): Unit = underlying.close()

def reader: DbResultReader = new JdbcResultReader(underlying)
}
41 changes: 41 additions & 0 deletions sql/jvm/src/main/scala/zio/blocks/sql/JdbcParamWriter.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package zio.blocks.sql

import java.sql.PreparedStatement

class JdbcParamWriter(val underlying: PreparedStatement) extends DbParamWriter {

def setInt(index: Int, value: Int): Unit = underlying.setInt(index, value)

def setLong(index: Int, value: Long): Unit = underlying.setLong(index, value)

def setDouble(index: Int, value: Double): Unit = underlying.setDouble(index, value)

def setFloat(index: Int, value: Float): Unit = underlying.setFloat(index, value)

def setBoolean(index: Int, value: Boolean): Unit = underlying.setBoolean(index, value)

def setString(index: Int, value: String): Unit = underlying.setString(index, value)

def setBigDecimal(index: Int, value: java.math.BigDecimal): Unit = underlying.setBigDecimal(index, value)

def setBytes(index: Int, value: Array[Byte]): Unit = underlying.setBytes(index, value)

def setShort(index: Int, value: Short): Unit = underlying.setShort(index, value)

def setByte(index: Int, value: Byte): Unit = underlying.setByte(index, value)

def setLocalDate(index: Int, value: java.time.LocalDate): Unit = underlying.setObject(index, value)

def setLocalDateTime(index: Int, value: java.time.LocalDateTime): Unit = underlying.setObject(index, value)

def setLocalTime(index: Int, value: java.time.LocalTime): Unit = underlying.setObject(index, value)

def setInstant(index: Int, value: java.time.Instant): Unit =
underlying.setTimestamp(index, java.sql.Timestamp.from(value))

def setDuration(index: Int, value: java.time.Duration): Unit = underlying.setString(index, value.toString)

def setUUID(index: Int, value: java.util.UUID): Unit = underlying.setObject(index, value)

def setNull(index: Int, sqlType: Int): Unit = underlying.setNull(index, sqlType)
}
51 changes: 51 additions & 0 deletions sql/jvm/src/main/scala/zio/blocks/sql/JdbcResultReader.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package zio.blocks.sql

import java.sql.ResultSet
import java.util.UUID

class JdbcResultReader(val underlying: ResultSet) extends DbResultReader {

def getInt(index: Int): Int = underlying.getInt(index)

def getLong(index: Int): Long = underlying.getLong(index)

def getDouble(index: Int): Double = underlying.getDouble(index)

def getFloat(index: Int): Float = underlying.getFloat(index)

def getBoolean(index: Int): Boolean = underlying.getBoolean(index)

def getString(index: Int): String = underlying.getString(index)

def getBigDecimal(index: Int): java.math.BigDecimal = underlying.getBigDecimal(index)

def getBytes(index: Int): Array[Byte] = underlying.getBytes(index)

def getShort(index: Int): Short = underlying.getShort(index)

def getByte(index: Int): Byte = underlying.getByte(index)

def getLocalDate(index: Int): java.time.LocalDate = underlying.getObject(index, classOf[java.time.LocalDate])

def getLocalDateTime(index: Int): java.time.LocalDateTime =
underlying.getObject(index, classOf[java.time.LocalDateTime])

def getLocalTime(index: Int): java.time.LocalTime = underlying.getObject(index, classOf[java.time.LocalTime])

def getInstant(index: Int): java.time.Instant = {
val ts = underlying.getTimestamp(index)
if (ts == null) null else ts.toInstant
}

def getDuration(index: Int): java.time.Duration = {
val s = underlying.getString(index)
if (s == null) null else java.time.Duration.parse(s)
}

def getUUID(index: Int): UUID = {
val s = underlying.getString(index)
if (s == null) null else UUID.fromString(s)
}

def wasNull: Boolean = underlying.wasNull()
}
62 changes: 62 additions & 0 deletions sql/jvm/src/main/scala/zio/blocks/sql/JdbcTransactor.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package zio.blocks.sql

import java.sql.{Connection, DriverManager}

class JdbcTransactor(
connectionFactory: () => Connection,
val dialect: SqlDialect,
val sqlLogger: SqlLogger = SqlLogger.noop
) extends Transactor {

def connect[A](f: DbCon ?=> A): A = {
val conn = connectionFactory()
val dbConn = new JdbcConnection(conn)
try {
given con: DbCon = new DbCon {
val connection: DbConnection = dbConn
val dialect: SqlDialect = JdbcTransactor.this.dialect
val logger: SqlLogger = JdbcTransactor.this.sqlLogger
}
f
} finally {
try dbConn.close()
catch { case _: Throwable => () }
}
}

def transact[A](f: DbTx ?=> A): A = {
val conn = connectionFactory()
val dbConn = new JdbcConnection(conn)
conn.setAutoCommit(false)
try {
given tx: DbTx = new DbTx {
val connection: DbConnection = dbConn
val dialect: SqlDialect = JdbcTransactor.this.dialect
val logger: SqlLogger = JdbcTransactor.this.sqlLogger
}
val result = f
conn.commit()
result
} catch {
case e: Throwable =>
try conn.rollback()
catch { case rb: Throwable => e.addSuppressed(rb) }
throw e
} finally {
try dbConn.close()
catch { case _: Throwable => () }
}
Comment on lines +27 to +48
}
}

object JdbcTransactor {

def fromUrl(url: String, dialect: SqlDialect): JdbcTransactor =
new JdbcTransactor(() => DriverManager.getConnection(url), dialect)

def fromUrl(url: String, user: String, password: String, dialect: SqlDialect): JdbcTransactor =
new JdbcTransactor(() => DriverManager.getConnection(url, user, password), dialect)

def fromDataSource(dataSource: javax.sql.DataSource, dialect: SqlDialect): JdbcTransactor =
new JdbcTransactor(() => dataSource.getConnection, dialect)
}
Empty file.
Loading
Loading