Skip to content

Commit b1b539b

Browse files
committed
task: Migrate the infrastructure layer (DB.scala and Magnum.scala) to parlance
1 parent 51b3569 commit b1b539b

8 files changed

Lines changed: 27 additions & 29 deletions

File tree

backend/src/main/scala/com/softwaremill/bootzooka/email/EmailModel.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package com.softwaremill.bootzooka.email
22

33
import com.augustnagro.magnum.{DbTx, PostgresDbType, Repo, Spec, SqlNameMapper, Table}
4-
import com.softwaremill.bootzooka.infrastructure.Magnum.given
4+
import com.softwaremill.bootzooka.infrastructure.Codecs.given
55
import com.softwaremill.bootzooka.util.Strings.Id
66
import ox.discard
77

backend/src/main/scala/com/softwaremill/bootzooka/infrastructure/Magnum.scala renamed to backend/src/main/scala/com/softwaremill/bootzooka/infrastructure/Codecs.scala

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,15 @@
11
package com.softwaremill.bootzooka.infrastructure
22

3-
import com.augustnagro.magnum.DbCodec
4-
import com.softwaremill.bootzooka.logging.Logging
3+
import ma.chinespirit.parlance.DbCodec
54
import com.softwaremill.bootzooka.util.Strings.*
65

76
import java.time.{Instant, OffsetDateTime, ZoneOffset}
87

9-
/** Magnum codecs for custom types, useful when writing SQL queries. */
10-
object Magnum extends Logging:
8+
/** parlance [[DbCodec]]s for custom types, useful when writing SQL queries. */
9+
object Codecs:
1110
given DbCodec[Instant] = summon[DbCodec[OffsetDateTime]].biMap(_.toInstant, _.atOffset(ZoneOffset.UTC))
1211

1312
given idCodec[T]: DbCodec[Id[T]] = DbCodec.StringCodec.biMap(_.asId[T], _.toString)
1413
given DbCodec[Hashed] = DbCodec.StringCodec.biMap(_.asHashed, _.toString)
1514
given DbCodec[LowerCased] = DbCodec.StringCodec.biMap(_.toLowerCased, _.toString)
16-
end Magnum
15+
end Codecs

backend/src/main/scala/com/softwaremill/bootzooka/infrastructure/DB.scala

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package com.softwaremill.bootzooka.infrastructure
22

3-
import com.augustnagro.magnum.{DbCodec, DbTx, SqlLogger, Transactor, connect, sql}
3+
import ma.chinespirit.parlance.{DbTx, Postgres, SqlLogger, Transactor, sql}
44
import com.softwaremill.bootzooka.infrastructure.DB.LeftException
55
import com.softwaremill.bootzooka.logging.Logging
66
import com.zaxxer.hikari.{HikariConfig, HikariDataSource}
@@ -15,21 +15,20 @@ import scala.util.NotGiven
1515
import scala.util.control.{NoStackTrace, NonFatal}
1616

1717
class DB(dataSource: DataSource & Closeable) extends Logging with AutoCloseable:
18-
private val transactor = Transactor(
19-
dataSource = dataSource,
20-
sqlLogger = SqlLogger.logSlowQueries(200.millis)
21-
)
18+
// the database type is pinned explicitly so that the `DbTx[Postgres]` context type matches throughout the codebase
19+
// (otherwise `Transactor(Postgres, ...)` would infer the singleton type `Postgres.type`)
20+
private val transactor = Transactor[Postgres](Postgres, dataSource, SqlLogger.logSlowQueries(200.millis))
2221

2322
/** Runs `f` in a transaction. The transaction is commited if the result is a [[Right]], and rolled back otherwise. */
24-
def transactEither[E, T](f: DbTx ?=> Either[E, T]): Either[E, T] =
25-
try com.augustnagro.magnum.transact(transactor)(Right(f.fold(e => throw LeftException(e), identity)))
23+
def transactEither[E, T](f: DbTx[Postgres] ?=> Either[E, T]): Either[E, T] =
24+
try transactor.transact(Right(f.fold(e => throw LeftException(e), identity)))
2625
catch case e: LeftException[E] @unchecked => Left(e.left)
2726

2827
/** Runs `f` in a transaction. The result cannot be an `Either`, as then [[transactEither]] should be used. The transaction is commited if
2928
* no exception is thrown.
3029
*/
31-
def transact[T](f: DbTx ?=> T)(using NotGiven[T <:< Either[?, ?]]): T =
32-
com.augustnagro.magnum.transact(transactor)(f)
30+
def transact[T](f: DbTx[Postgres] ?=> T)(using NotGiven[T <:< Either[?, ?]]): T =
31+
transactor.transact(f)
3332

3433
override def close(): Unit = dataSource.close()
3534
end DB
@@ -56,7 +55,7 @@ object DB extends Logging:
5655
.load()
5756

5857
def migrate(): Unit = if config.migrateOnStart then flyway.migrate().discard
59-
def testConnection(ds: DataSource): Unit = connect(ds)(sql"SELECT 1".query[Int].run()).discard
58+
def testConnection(ds: DataSource): Unit = Transactor[Postgres](Postgres, ds).connect(sql"SELECT 1".query[Int].run()).discard
6059

6160
@tailrec
6261
def connectAndMigrate(ds: DataSource): Unit =

backend/src/main/scala/com/softwaremill/bootzooka/passwordreset/PasswordResetCodeModel.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package com.softwaremill.bootzooka.passwordreset
22

33
import com.augustnagro.magnum.{DbCodec, DbTx, PostgresDbType, Repo, SqlName, SqlNameMapper, Table}
4-
import com.softwaremill.bootzooka.infrastructure.Magnum.given
4+
import com.softwaremill.bootzooka.infrastructure.Codecs.given
55
import com.softwaremill.bootzooka.security.AuthTokenOps
66
import com.softwaremill.bootzooka.user.User
77
import com.softwaremill.bootzooka.util.Strings.Id

backend/src/main/scala/com/softwaremill/bootzooka/security/ApiKeyModel.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package com.softwaremill.bootzooka.security
22

33
import com.augustnagro.magnum.{DbTx, PostgresDbType, Repo, SqlName, SqlNameMapper, Table, TableInfo, sql}
4-
import com.softwaremill.bootzooka.infrastructure.Magnum.given
4+
import com.softwaremill.bootzooka.infrastructure.Codecs.given
55
import com.softwaremill.bootzooka.user.User
66
import com.softwaremill.bootzooka.util.Strings.Id
77
import ox.discard

backend/src/main/scala/com/softwaremill/bootzooka/user/UserModel.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ package com.softwaremill.bootzooka.user
22

33
import com.augustnagro.magnum.{DbCodec, DbTx, Frag, PostgresDbType, Repo, Spec, SqlName, SqlNameMapper, Table, TableInfo, sql}
44
import com.password4j.{Argon2Function, Password}
5-
import com.softwaremill.bootzooka.infrastructure.Magnum.given
5+
import com.softwaremill.bootzooka.infrastructure.Codecs.given
66
import com.softwaremill.bootzooka.user.User.PasswordHashing
77
import com.softwaremill.bootzooka.user.User.PasswordHashing.Argon2Config.*
88
import com.softwaremill.bootzooka.util.PasswordVerificationStatus

docs/devtips.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,11 @@ There are two imports that are useful when developing a new functionality:
4444
If you are defining database queries or running transactions, add the following imports:
4545

4646
```scala
47-
import com.softwaremill.bootzooka.infrastructure.Magnum.given
48-
import com.augustnagro.magnum.{sql, DbTx}
47+
import com.softwaremill.bootzooka.infrastructure.Codecs.given
48+
import ma.chinespirit.parlance.{sql, DbTx, Postgres}
4949
```
5050

51-
This will bring into scope custom [Magnum](https://github.com/AugustNagro/magnum) codecs, the sql query interpolator
51+
This will bring into scope custom [parlance](https://github.com/lbialy/parlance) codecs, the sql query interpolator
5252
as well as the given instance which is required by methods that should run in a transaction.
5353

5454
### HTTP API

parlance-migration-notes.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,13 @@ parlance 0.1.0 is a redesigned, Active-Record-inspired ORM (92 core source files
2424
|---|---|---|
2525
| `import com.augustnagro.magnum.*` | `import ma.chinespirit.parlance.*` | package rename |
2626
| `DbCodec`, `.biMap(to, from)` | same | ✓ identical |
27-
| `DbCodec.StringCodec` | `DbCodec[String]` (i.e. `summon[DbCodec[String]]`) | `StringCodec` given exists; prefer `DbCodec[String]` |
27+
| `DbCodec.StringCodec` | `DbCodec.StringCodec` (also `DbCodec[String]`) | `StringCodec` is a given in `object DbCodec`; `DbCodec.StringCodec.biMap(...)` compiles as-is |
2828
| `summon[DbCodec[OffsetDateTime]]` | same |`OffsetDateTimeCodec` given exists |
29-
| custom `given DbCodec[Instant]` (in `infrastructure/Magnum.scala`) | **remove** | parlance already ships `given DbCodec[Instant]`; keeping the custom one is an ambiguous-given conflict |
30-
| `Transactor(dataSource = ds, sqlLogger = ...)` | `Transactor(Postgres, ds, SqlLogger.logSlowQueries(200.millis))` | **DB type is now a required first arg**; `Transactor[D <: DatabaseType]` |
29+
| custom `given DbCodec[Instant]` (in `infrastructure/Codecs.scala`) | **keep** | parlance ships `given InstantCodec: DbCodec[Instant]` in `object DbCodec`, but an *imported* given (via `import Codecs.given`) takes precedence over implicit/companion scope — **verified: no ambiguity**. (Corrects the earlier note that said to remove it.) |
30+
| `Transactor(dataSource = ds, sqlLogger = ...)` | `Transactor[Postgres](Postgres, ds, SqlLogger.logSlowQueries(200.millis))` | **DB type is a required first arg** AND must be **pinned** as `[Postgres]``Transactor(Postgres, ...)` infers the singleton `Postgres.type`, which then mismatches `DbTx[Postgres]` everywhere. |
3131
| `SqlLogger.logSlowQueries(200.millis)` | same ||
32-
| top-level `transact(transactor)(f)` | **`transactor.transact(f)`** (instance method) | `def transact[T](f: DbTx[D] ?=> T): T` |
33-
| top-level `connect(ds)(f)` | **`transactor.connect(f)`** (instance method) | `connect` takes no DataSource — needs a `Transactor`; `def connect[T](f: DbCon[D] ?=> T): T`. DB.scala `testConnection(ds)` must build/reuse a Transactor instead of passing a raw `DataSource`. |
32+
| top-level `transact(transactor)(f)` | **`transactor.transact(f)`** (instance method) | `def transact[T](f: DbTx[D] ?=> T): T`. Rolls back only on a thrown exception (then rethrows) — so the `LeftException` rollback-on-`Left` trick still works unchanged. |
33+
| top-level `connect(ds)(f)` | **`transactor.connect(f)`** (instance method) | `connect` takes no DataSource — needs a `Transactor`; `def connect[T](f: DbCon[D] ?=> T): T`. DB.scala `testConnection(ds)` builds a throwaway `Transactor[Postgres](Postgres, ds)` to run the test query. |
3434
| `(using DbTx)`, `DbTx ?=> T` | **`(using DbTx[Postgres])`, `DbTx[Postgres] ?=> T`** | `DbCon`/`DbTx` are parameterized by DB type. Touches *every* model/service/api file with a `DbTx` param (UserModel, UserService, ApiKeyModel, ApiKeyService, Auth, PasswordResetCodeModel, PasswordResetService, EmailModel, EmailService, the `*Api` files, `AuthTokenOps`). Read-only sites may use `DbCon[Postgres]`. |
3535
| `sql"..."` interpolator | same | ✓ available via wildcard import; if selectively imported, confirm the `sql` name is importable |
3636
| `.query[T].run()`, `.update.run()` | same | ✓ require a `DbCon`/`DbTx` in scope |
@@ -52,6 +52,6 @@ parlance 0.1.0 is a redesigned, Active-Record-inspired ORM (92 core source files
5252
2. `TableInfo` column-reference + `sql"$u"` table interpolation: confirm the API matches Magnum's usage in `UserModel`/`ApiKeyModel`.
5353
3. Add `derives EntityMeta` to all entities; drop the DB-type arg from every `@Table`.
5454
4. Parameterize all `DbTx`/`DbCon` usages with `[Postgres]`.
55-
5. Rework `DB.scala`: `Transactor(Postgres, ds, ...)`, instance `transact`/`connect`, and `testConnection` (no raw-DataSource `connect`).
56-
6. Remove the custom `given DbCodec[Instant]`; switch `DbCodec.StringCodec` `DbCodec[String]`.
55+
5. ~~Rework `DB.scala`~~ **DONE** (and `infrastructure/Magnum.scala``infrastructure/Codecs.scala`, with the four import sites updated).
56+
6. ~~Remove the custom `given DbCodec[Instant]`~~ — keep it; `DbCodec.StringCodec` works as-is (see table).
5757
7. `insert``rawInsert`; add `()` to `Repo[...]` constructions.

0 commit comments

Comments
 (0)