-
Notifications
You must be signed in to change notification settings - Fork 17
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add ZIO module providing
connect
and transact
mechanism adapted t…
…o ZIO (#47)
- Loading branch information
Showing
5 changed files
with
403 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
128 changes: 128 additions & 0 deletions
128
magnum-zio/src/main/scala/com/augustnagro/magnum/magzio/zio.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
package com.augustnagro.magnum.magzio | ||
|
||
import zio.{Trace, ZIO} | ||
import com.augustnagro.magnum.* | ||
|
||
import java.sql.Connection | ||
import javax.sql.DataSource | ||
import scala.util.control.NonFatal | ||
|
||
/** Executes a given query on a given DataSource | ||
* | ||
* Re-implementation for ZIO of | ||
* [[com.augustnagro.magnum.connect(dataSource: DataSource)]] | ||
* | ||
* Usage: | ||
* {{{ | ||
* import com.augustnagro.magnum.magzio.* | ||
* | ||
* connect(datasource) { cn ?=> repo.findById(id) } | ||
* }}} | ||
*/ | ||
def connect[A](dataSource: DataSource)(q: DbCon ?=> A)(implicit | ||
trace: Trace | ||
): ZIO[Any, Throwable, A] = | ||
connect(Transactor(dataSource))(q) | ||
|
||
/** Executes a given query on a given Transactor | ||
* | ||
* Re-implementation for ZIO of | ||
* [[com.augustnagro.magnum.connect(dataSource: DataSource)]] | ||
* | ||
* Usage: | ||
* {{{ | ||
* import com.augustnagro.magnum.magzio.* | ||
* | ||
* connect(transactor) { cn ?=> repo.findById(id) } | ||
* }}} | ||
*/ | ||
def connect[A]( | ||
transactor: Transactor | ||
)(q: DbCon ?=> A)(implicit trace: Trace): ZIO[Any, Throwable, A] = | ||
ZIO.blocking { | ||
ZIO.acquireReleaseWith( | ||
acquire = ZIO.attempt(transactor.dataSource.getConnection()) | ||
)(release = | ||
conn => if (conn ne null) ZIO.attempt(conn.close()).orDie else ZIO.unit | ||
) { cn => | ||
ZIO.attempt { | ||
transactor.connectionConfig(cn) | ||
q(using DbCon(cn, transactor.sqlLogger)) | ||
} | ||
} | ||
} | ||
|
||
/** Executes a given transaction on a given DataSource | ||
* | ||
* Re-implementation for ZIO of | ||
* [[com.augustnagro.magnum.transact(transactor: Transactor)]] | ||
* | ||
* Usage: | ||
* {{{ | ||
* import com.augustnagro.magnum.magzio.* | ||
* | ||
* transact(dataSource) { tx ?=> repo.insertReturning(creator) } | ||
* }}} | ||
*/ | ||
def transact[A](dataSource: DataSource)(q: DbTx ?=> A)(implicit | ||
trace: Trace | ||
): ZIO[Any, Throwable, A] = | ||
transact(Transactor(dataSource))(q) | ||
|
||
/** Executes a given transaction on a given DataSource | ||
* | ||
* Re-implementation for ZIO of | ||
* [[com.augustnagro.magnum.transact(transactor: Transactor, connectionConfig: Connection => Unit)]] | ||
* | ||
* Usage: | ||
* {{{ | ||
* import com.augustnagro.magnum.magzio.* | ||
* | ||
* transact(dataSource, ...) { tx ?=> repo.insertReturning(creator) } | ||
* }}} | ||
*/ | ||
def transact[A](dataSource: DataSource, connectionConfig: Connection => Unit)( | ||
q: DbTx ?=> A | ||
)(implicit trace: Trace): ZIO[Any, Throwable, A] = | ||
val transactor = | ||
Transactor(dataSource = dataSource, connectionConfig = connectionConfig) | ||
transact(transactor)(q) | ||
|
||
/** Executes a given transaction on a given Transactor | ||
* | ||
* Re-implementation for ZIO of | ||
* [[com.augustnagro.magnum.transact(transactor: Transactor)]] | ||
* | ||
* Usage: | ||
* {{{ | ||
* import com.augustnagro.magnum.magzio.* | ||
* | ||
* transact(transactor) { tx ?=> repo.insertReturning(creator) } | ||
* }}} | ||
*/ | ||
def transact[A]( | ||
transactor: Transactor | ||
)(q: DbTx ?=> A)(implicit trace: Trace): ZIO[Any, Throwable, A] = | ||
ZIO.blocking { | ||
ZIO.acquireReleaseWith( | ||
acquire = ZIO.attempt(transactor.dataSource.getConnection()) | ||
)(release = | ||
conn => if (conn ne null) ZIO.attempt(conn.close()).orDie else ZIO.unit | ||
) { cn => | ||
ZIO.uninterruptible { | ||
ZIO.attempt { | ||
transactor.connectionConfig(cn) | ||
cn.setAutoCommit(false) | ||
try { | ||
val res = q(using DbTx(cn, transactor.sqlLogger)) | ||
cn.commit() | ||
res | ||
} catch { | ||
case NonFatal(t) => | ||
cn.rollback() | ||
throw t | ||
} | ||
} | ||
} | ||
} | ||
} |
196 changes: 196 additions & 0 deletions
196
magnum-zio/src/test/scala/com/augustnagro/magnum/magzio/ImmutableRepoZioTests.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,196 @@ | ||
package com.augustnagro.magnum.magzio | ||
|
||
import com.augustnagro.magnum.* | ||
import munit.FunSuite | ||
import shared.Color | ||
import zio.* | ||
|
||
import java.sql.Connection | ||
import java.time.OffsetDateTime | ||
import scala.util.{Success, Using} | ||
|
||
def immutableRepoZioTests( | ||
suite: FunSuite, | ||
dbType: DbType, | ||
xa: () => Transactor | ||
)(using | ||
munit.Location, | ||
DbCodec[OffsetDateTime] | ||
): Unit = | ||
import suite.* | ||
|
||
val runtime: Runtime[Any] = zio.Runtime.default | ||
|
||
def runIO[A](io: ZIO[Any, Throwable, A]): A = | ||
Unsafe.unsafe { implicit unsafe => | ||
runtime.unsafe.run(io).getOrThrow() | ||
} | ||
|
||
@Table(dbType, SqlNameMapper.CamelToSnakeCase) | ||
case class Car( | ||
model: String, | ||
@Id id: Long, | ||
topSpeed: Int, | ||
@SqlName("vin") vinNumber: Option[Int], | ||
color: Color, | ||
created: OffsetDateTime | ||
) derives DbCodec | ||
|
||
val carRepo = ImmutableRepo[Car, Long] | ||
val car = TableInfo[Car, Car, Long] | ||
|
||
val allCars = Vector( | ||
Car( | ||
model = "McLaren Senna", | ||
id = 1L, | ||
topSpeed = 208, | ||
vinNumber = Some(123), | ||
color = Color.Red, | ||
created = OffsetDateTime.parse("2024-11-24T22:17:30.000000000Z") | ||
), | ||
Car( | ||
model = "Ferrari F8 Tributo", | ||
id = 2L, | ||
topSpeed = 212, | ||
vinNumber = Some(124), | ||
color = Color.Green, | ||
created = OffsetDateTime.parse("2024-11-24T22:17:31.000000000Z") | ||
), | ||
Car( | ||
model = "Aston Martin Superleggera", | ||
id = 3L, | ||
topSpeed = 211, | ||
vinNumber = None, | ||
color = Color.Blue, | ||
created = OffsetDateTime.parse("2024-11-24T22:17:32.000000000Z") | ||
) | ||
) | ||
|
||
test("count"): | ||
val count = | ||
runIO: | ||
magzio.connect(xa()): | ||
carRepo.count | ||
assert(count == 3L) | ||
|
||
test("existsById"): | ||
val (exists3, exists4) = | ||
runIO: | ||
magzio.connect(xa()): | ||
carRepo.existsById(3L) -> carRepo.existsById(4L) | ||
assert(exists3) | ||
assert(!exists4) | ||
|
||
test("findAll"): | ||
val cars = | ||
runIO: | ||
magzio.connect(xa()): | ||
carRepo.findAll | ||
assert(cars == allCars) | ||
|
||
test("findById"): | ||
val (exists3, exists4) = | ||
runIO: | ||
magzio.connect(xa()): | ||
carRepo.findById(3L) -> carRepo.findById(4L) | ||
assert(exists3.get == allCars.last) | ||
assert(exists4 == None) | ||
|
||
test("findAllByIds"): | ||
assume(dbType != ClickhouseDbType) | ||
assume(dbType != MySqlDbType) | ||
assume(dbType != OracleDbType) | ||
assume(dbType != SqliteDbType) | ||
val ids = | ||
runIO: | ||
magzio.connect(xa()): | ||
carRepo.findAllById(Vector(1L, 3L)).map(_.id) | ||
assert(ids == Vector(1L, 3L)) | ||
|
||
test("serializable transaction"): | ||
val count = | ||
runIO: | ||
magzio.transact(xa().copy(connectionConfig = withSerializable)): | ||
carRepo.count | ||
assert(count == 3L) | ||
|
||
def withSerializable(con: Connection): Unit = | ||
con.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE) | ||
|
||
test("select query"): | ||
val minSpeed: Int = 210 | ||
val query = | ||
sql"select ${car.all} from $car where ${car.topSpeed} > $minSpeed" | ||
.query[Car] | ||
val result = | ||
runIO: | ||
magzio.connect(xa()): | ||
query.run() | ||
assertNoDiff( | ||
query.frag.sqlString, | ||
"select model, id, top_speed, vin, color, created from car where top_speed > ?" | ||
) | ||
assert(query.frag.params == Vector(minSpeed)) | ||
assert(result == allCars.tail) | ||
|
||
test("select query with aliasing"): | ||
val minSpeed = 210 | ||
val cAlias = car.alias("c") | ||
val query = | ||
sql"select ${cAlias.all} from $cAlias where ${cAlias.topSpeed} > $minSpeed" | ||
.query[Car] | ||
val result = | ||
runIO: | ||
magzio.connect(xa()): | ||
query.run() | ||
assertNoDiff( | ||
query.frag.sqlString, | ||
"select c.model, c.id, c.top_speed, c.vin, c.color, c.created from car c where c.top_speed > ?" | ||
) | ||
assert(query.frag.params == Vector(minSpeed)) | ||
assert(result == allCars.tail) | ||
|
||
test("select via option"): | ||
val vin = Some(124) | ||
val cars = | ||
runIO: | ||
magzio.connect(xa()): | ||
sql"select * from car where vin = $vin" | ||
.query[Car] | ||
.run() | ||
assert(cars == allCars.filter(_.vinNumber == vin)) | ||
|
||
test("tuple select"): | ||
val tuples = | ||
runIO: | ||
magzio.connect(xa()): | ||
sql"select model, color from car where id = 2" | ||
.query[(String, Color)] | ||
.run() | ||
assert(tuples == Vector(allCars(1).model -> allCars(1).color)) | ||
|
||
test("reads null int as None and not Some(0)"): | ||
val maybeCar = | ||
runIO: | ||
magzio.connect(xa()): | ||
carRepo.findById(3L) | ||
assert(maybeCar.get.vinNumber == None) | ||
|
||
test("created timestamps should match"): | ||
val allCars = | ||
runIO: | ||
magzio.connect(xa()): | ||
carRepo.findAll | ||
assert(allCars.map(_.created) == allCars.map(_.created)) | ||
|
||
test(".query iterator"): | ||
val carsCount = | ||
runIO: | ||
magzio.connect(xa()): | ||
Using.Manager(implicit use => | ||
val it = sql"SELECT * FROM car".query[Car].iterator() | ||
it.map(_.id).size | ||
) | ||
assert(carsCount == Success(3)) | ||
|
||
end immutableRepoZioTests |
Oops, something went wrong.