{"id":39584747,"url":"https://github.com/AugustNagro/magnum","last_synced_at":"2026-01-26T15:01:18.435Z","repository":{"id":93835374,"uuid":"598831579","full_name":"AugustNagro/magnum","owner":"AugustNagro","description":"A 'new look' for database access in Scala","archived":false,"fork":false,"pushed_at":"2025-10-18T21:01:24.000Z","size":1517,"stargazers_count":268,"open_issues_count":21,"forks_count":24,"subscribers_count":8,"default_branch":"master","last_synced_at":"2026-01-14T04:11:13.470Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Scala","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/AugustNagro.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2023-02-07T22:22:26.000Z","updated_at":"2025-12-20T04:44:56.000Z","dependencies_parsed_at":null,"dependency_job_id":"ffefe0ac-dd70-4c4e-8ef7-2330454b2c13","html_url":"https://github.com/AugustNagro/magnum","commit_stats":null,"previous_names":[],"tags_count":10,"template":false,"template_full_name":null,"purl":"pkg:github/AugustNagro/magnum","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AugustNagro%2Fmagnum","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AugustNagro%2Fmagnum/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AugustNagro%2Fmagnum/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AugustNagro%2Fmagnum/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/AugustNagro","download_url":"https://codeload.github.com/AugustNagro/magnum/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AugustNagro%2Fmagnum/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28781308,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-26T13:55:28.044Z","status":"ssl_error","status_checked_at":"2026-01-26T13:55:26.068Z","response_time":59,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-01-18T07:35:27.100Z","updated_at":"2026-01-26T15:01:18.430Z","avatar_url":"https://github.com/AugustNagro.png","language":"Scala","funding_links":[],"categories":["\u003ca name=\"Scala\"\u003e\u003c/a\u003eScala"],"sub_categories":[],"readme":"## Magnum\n\n[![Latest version](https://index.scala-lang.org/augustnagro/magnum/magnum/latest.svg?color=orange)](https://index.scala-lang.org/augustnagro/magnum/magnum)\n\nYet another database client for Scala. No dependencies, high productivity.\n\n* [Installing](#installing)\n* [ScalaDoc](#scaladoc)\n* [Documentation](#documentation)\n  * [`connect` creates a database connection](#connect-creates-a-database-connection)\n  * [`transact` creates a database transaction](#transact-creates-a-database-transaction)\n  * [Type-safe Transaction \u0026 Connection Management](#type-safe-transaction--connection-management)\n  * [Customizing Transactions](#customizing-transactions)\n  * [Sql Interpolator, Frag, Query, Update, Returning](#sql-interpolator-frag-query-and-update)\n  * [Batch Updates](#batch-updates)\n  * [Immutable Repositories](#immutable-repositories)\n  * [Repositories](#repositories)\n  * [Database generated columns](#database-generated-columns)\n  * [Specifications](#specifications)\n  * [Scala 3 Enum \u0026 NewType Support](#scala-3-enum--newtype-support)\n  * [`DbCodec`: Typeclass for JDBC reading \u0026 writing](#dbcodec-typeclass-for-jdbc-reading--writing)\n  * [Future-Proof Queries](#future-proof-queries)\n  * [Splicing Literal Values into Frags](#splicing-literal-values-into-frags)\n  * [Postgres Module](#postgres-module)\n  * [Logging](#logging-sql-queries)\n* [Integrations](#integrations)\n  * [ZIO](#zio) \n* [Motivation](#motivation)\n* [Feature List And Database Support](#feature-list)\n* [Talks and Blogs](#talks-and-blogs)\n* [Frequently Asked Questions](#frequently-asked-questions)\n\n## Installing\n\n```\n\"com.augustnagro\" %% \"magnum\" % \"1.3.0\"\n```\n\nMagnum requires Scala \u003e= 3.3.0\n\nYou must also install the JDBC driver for your database, for example:\n\n```\n\"org.postgresql\" % \"postgresql\" % \"\u003cversion\u003e\"\n```\n\nAnd for performance, a JDBC connection pool like [HikariCP](https://github.com/brettwooldridge/HikariCP)\n\n## ScalaDoc\n\nhttps://javadoc.io/doc/com.augustnagro/magnum_3\n\n## Documentation\n\n### `connect` creates a database connection.\n\n`connect` takes two parameters; the database Transactor,\nand a context function with a given `DbCon` connection.\nFor example:\n\n```scala\nimport com.augustnagro.magnum.*\n\nval dataSource: javax.sql.DataSource = ???\nval xa = Transactor(dataSource)\n\nval users: Vector[User] = connect(xa):\n  sql\"SELECT * FROM user\".query[User].run()\n```\n\n### `transact` creates a database transaction.\n\nLike `connect`, `transact` accepts a Transactor and context function.\nThe context function provides a `DbTx` instance.\nIf the function throws, the transaction will be rolled back.\n\n```scala\n// update is rolled back\ntransact(xa):\n  sql\"UPDATE user SET first_name = $firstName WHERE id = $id\".update.run()\n  thisMethodThrows()\n```\n\n### Type-safe Transaction \u0026 Connection Management\n\nAnnotate transactional methods with `using DbTx`, and connections with `using DbCon`.\n\nSince `DbTx \u003c: DbCon`, it's impossible to call a method with the wrong context.\n\nFor example, this compiles:\n\n```scala\ndef runUpdateAndGetUsers()(using DbTx): Vector[User] =\n  userRepo.deleteById(1L)\n  getUsers\n\ndef getUsers(using DbCon): Vector[User] =\n  sql\"SELECT * FROM user\".query.run()\n```\n\nBut not this:\n\n```scala\ndef runSomeQueries(using DbCon): Vector[User] =\n  runUpdateAndGetUsers()\n```\n\n### Customizing transactions\n\n`Transactor` lets you customize the transaction (or connection) behavior.\n\n```scala\nval xa = Transactor(\n  dataSource = ???,\n  sqlLogger = SqlLogger.logSlowQueries(500.milliseconds),\n  connectionConfig = con =\u003e\n    con.setTransactionIsolation(Connection.TRANSACTION_REPEATABLE_READ)\n)\n\ntransact(xa):\n  sql\"SELECT id from myUser\".query[Long].run()\n```\n\n### Sql Interpolator, Frag, Query, and Update\n\nThe `sql` interpolator can express any SQL expression, returning a `Frag` sql fragment. You can interpolate values without the risk of SQL-injection attacks.\n\n```scala\nval firstNameOpt = Some(\"John\")\nval twoDaysAgo = OffsetDateTime.now.minusDays(2)\n\nval frag: Frag =\n  sql\"\"\"\n    SELECT id, last_name FROM user\n    WHERE first_name = $firstNameOpt\n    AND created \u003c= $twoDaysAgo\n    \"\"\"\n```\n\nFrags can be turned into queries with the `query[T](using DbCodec[T])` method:\n\n```scala\nval query = frag.query[(Long, String)] // Query[(Long, String)]\n```\n\nOr updates via `update`\n\n```scala\nval update: Update =\n  sql\"UPDATE user SET first_name = 'Buddha' WHERE id = 3\".update\n```\n\nOr an update with a `RETURNING` clause via `returning`:\n\n```scala\nval updateReturning: Returning =\n  sql\"\"\"\n     UPDATE user SET first_name = 'Buddha'\n     WHERE last_name = 'Harper'\n     RETURNING id\n     \"\"\".returning[Long]\n```\n\nAll are executed via `run()(using DbCon)`:\n\n```scala\ntransact(xa):\n  val tuples: Vector[(Long, String)] = query.run()\n  val updatedRows: Int = update.run()\n  val updatedIds: Vector[Long] = updateReturning.run()\n```\n\n### Batch Updates\n\nBatch updates are supported via `batchUpdate` method in package `com.augustnagro.magnum`.\n\n```scala\nconnect(xa):\n  val users: Iterable[User] = ???\n  val updateResult: BatchUpdateResult =\n    batchUpdate(users): user =\u003e\n      sql\"...\".update\n```\n\n`batchUpdate` returns a `BatchUpdateResult` enum, which is `Success(numRowsUpdated)` or `SuccessNoInfo` otherwise.\n\n### Immutable Repositories\n\nThe `ImmutableRepo` class auto-generates the following methods at compile-time:\n\n```scala\n  def count(using DbCon): Long\n  def existsById(id: ID)(using DbCon): Boolean\n  def findAll(using DbCon): Vector[E]\n  def findAll(spec: Spec[E])(using DbCon): Vector[E]\n  def findById(id: ID)(using DbCon): Option[E]\n  def findAllById(ids: Iterable[ID])(using DbCon): Vector[E]\n```\n\nHere's an example:\n\n```scala\n@Table(PostgresDbType, SqlNameMapper.CamelToSnakeCase)\ncase class User(\n  @Id id: Long,\n  firstName: Option[String],\n  lastName: String,\n  created: OffsetDateTime\n) derives DbCodec\n\nval userRepo = ImmutableRepo[User, Long]\n\ntransact(xa):\n  val cnt = userRepo.count\n  val userOpt = userRepo.findById(2L)\n```\n\nImportantly, class User is annotated with `@Table`, which defines the table's database type. The annotation optionally specifies the name-mapping between scala fields and column names. You can also use the `@SqlName` annotation on individual fields. Finally, The table must `derive DbCodec`, or otherwise provide an implicit DbCodec instance.\n\nThe optional `@Id` annotation denotes the table's primary key. Not setting `@Id` will default to using the first field. If there is no logical id, then remove the annotation and use Null in the ID type parameter of Repositories (see next).\n\nIt is a best practice to extend ImmutableRepo to encapsulate your SQL in repositories. This way, it's easier to maintain since they're grouped together.\n\n```scala\nclass UserRepo extends ImmutableRepo[User, Long]:\n  def firstNamesForLast(lastName: String)(using DbCon): Vector[String] =\n    sql\"\"\"\n      SELECT DISTINCT first_name\n      FROM user\n      WHERE last_name = $lastName\n      \"\"\".query[String].run()\n        \n  // other User-related queries here\n```\n\n### Repositories\n\nThe `Repo` class auto-generates the following methods at compile-time:\n\n```scala\n  def count(using DbCon): Long\n  def existsById(id: ID)(using DbCon): Boolean\n  def findAll(using DbCon): Vector[E]\n  def findAll(spec: Spec[E])(using DbCon): Vector[E]\n  def findById(id: ID)(using DbCon): Option[E]\n  def findAllById(ids: Iterable[ID])(using DbCon): Vector[E]\n  \n  def delete(entity: E)(using DbCon): Unit\n  def deleteById(id: ID)(using DbCon): Unit\n  def truncate()(using DbCon): Unit\n  def deleteAll(entities: Iterable[E])(using DbCon): BatchUpdateResult\n  def deleteAllById(ids: Iterable[ID])(using DbCon): BatchUpdateResult\n  def insert(entityCreator: EC)(using DbCon): Unit\n  def insertAll(entityCreators: Iterable[EC])(using DbCon): Unit\n  def insertReturning(entityCreator: EC)(using DbCon): E\n  def insertAllReturning(entityCreators: Iterable[EC])(using DbCon): Vector[E]\n  def update(entity: E)(using DbCon): Unit\n  def updateAll(entities: Iterable[E])(using DbCon): BatchUpdateResult\n```\n\nHere's an example:\n\n```scala\n@Table(PostgresDbType, SqlNameMapper.CamelToSnakeCase)\ncase class User(\n  @Id id: Long,\n  firstName: Option[String],\n  lastName: String,\n  created: OffsetDateTime\n) derives DbCodec\n\nval userRepo = Repo[User, User, Long]\n\nval countAfterUpdate = transact(xa):\n  userRepo.deleteById(2L)\n  userRepo.count\n```\n\nIt is a best practice to encapsulate your SQL in repositories.\n\n```scala\nclass UserRepo extends Repo[User, User, Long]\n```\n\nAlso note that Repo extends ImmutableRepo. Some databases cannot support every method, and will throw UnsupportedOperationException.\n\n### Database generated columns\n\nIt is often the case that database columns are auto-generated, for example, primary key IDs. This is why the Repo class has 3 type parameters. \n\nThe first defines the Entity-Creator, which should omit any fields that are auto-generated. The entity-creator class must be an 'effective' subclass of the entity class, but it does not have to subclass the entity. This is verified at compile time.\n\nThe second type parameter is the Entity class, and the final is for the ID. If the Entity does not have a logical ID, use Null.\n\n```scala\ncase class UserCreator(\n  firstName: Option[String],\n  lastName: String,\n) derives DbCodec\n\n@Table(PostgresDbType, SqlNameMapper.CamelToSnakeCase)\ncase class User(\n  @Id id: Long,\n  firstName: Option[String],\n  lastName: String,\n  created: OffsetDateTime\n) derives DbCodec\n\nval userRepo = Repo[UserCreator, User, Long]\n\nval newUser: User = transact(xa):\n  userRepo.insertReturning(\n    UserCreator(Some(\"Adam\"), \"Smith\")\n  )\n```\n\n### Specifications\n\nSpecifications help you write safe, dynamic queries.\nAn example use-case would be a search results page that allows users to sort and filter the paginated data.\n\n1. If you need to perform joins to get the data needed, first create a database view.\n2. Next, create an entity class that derives DbCodec.\n3. Finally, use the Spec class to create a specification.\n\nHere's an example:\n\n```scala\nval partialName = \"Ja%\"\nval lastNameOpt = Option(\"Brown\")\nval searchDate = OffsetDateTime.now.minusDays(2)\nval idPosition = 42L\n\nval spec = Spec[User]\n  .where(sql\"first_name ILIKE $partialName\")\n  .where(lastNameOpt.map(ln =\u003e sql\"last_name = $ln\").getOrElse(sql\"\"))\n  .where(sql\"created \u003e= $searchDate\")\n  .seek(\"id\", SeekDir.Gt, idPosition, SortOrder.Asc)\n  .limit(10)\n\nval users: Vector[User] = userRepo.findAll(spec)\n```\n\nNote that both [seek pagination](https://blog.jooq.org/faster-sql-paging-with-jooq-using-the-seek-method/) and offset pagination is supported.\n\n### Scala 3 Enum \u0026 NewType Support\n\nMagnum supports Scala 3 enums (non-adt) fully, by default writing \u0026 reading them as Strings. For example,\n\n```scala\n@Table(PostgresDbType, SqlNameMapper.CamelToUpperSnakeCase)\nenum Color derives DbCodec:\n  case Red, Green, Blue\n\n@Table(PostgresDbType, SqlNameMapper.CamelToSnakeCase)\ncase class User(\n  @Id id: Long,\n  firstName: Option[String],\n  lastName: String,\n  created: OffsetDateTime,\n  favoriteColor: Color\n) derives DbCodec\n```\n\nNewTypes and Opaque Type Alias can cause issues with derivation since given DbCodecs are not available. A simple way to provide them is using DbCodec.bimap:\n\n```scala\nopaque type MyId = Long\n\nobject MyId:\n  def apply(id: Long): MyId =\n    require(id \u003e= 0)\n    id\n\n  extension (myId: MyId)\n    def underlying: Long = myId\n\n  given DbCodec[MyId] =\n    DbCodec[Long].biMap(MyId.apply, _.underlying)\n\ntransact(xa):\n  val id = MyId(123L)\n  sql\"UPDATE my_table SET x = true WHERE id = $id\".update.run()\n```\n\n### `DbCodec`: Typeclass for JDBC reading \u0026 writing\n\nDbCodec is a Typeclass for JDBC reading \u0026 writing.\n\nBuilt-in DbCodecs are provided for many types, including primitives, dates, Options, and Tuples. You can derive DbCodecs by adding `derives DbCodec` to your case class or enum.\n\n```scala\nval rs: ResultSet = ???\nval ints: Vector[Int] = DbCodec[Int].read(rs)\n\nval ps: PreparedStatement = ???\nDbCodec[Int].writeSingle(22, ps)\n```\n\n### Defining your own DbCodecs\n\nTo modify the JDBC mappings, implement a given DbCodec instance as you would for any Typeclass.\n\n### Future-Proof Queries\n\nA common problem when writing SQL queries is that they're difficult to refactor. When a column or table name changes you have to do a global find \u0026 replace. And if you miss a query, it's discovered at runtime.\n\nThere's also lots of repetition when writing SQL. Magnum's repositories help scrap the boilerplate, but writing `SELECT a, b, c, d, ...` for a large table quickly gets tiring.\n\nTo help with this, Magnum offers a `TableInfo` class to enable 'future-proof' queries. An important caveat is that these queries are harder to copy/paste into SQL editors like PgAdmin or DbBeaver.\n\nHere's some examples:\n\n```scala\nimport com.augustnagro.magnum.*\n\ncase class UserCreator(firstName: String, age: Int) derives DbCodec\n\n@Table(PostgresDbType, SqlNameMapper.CamelToSnakeCase)\ncase class User(id: Long, firstName: String, age: Int) derives DbCodec\n\nobject User:\n  val Table = TableInfo[UserCreator, User, Long]\n\ndef allUsers(using DbCon): Vector[User] =\n  val u = User.Table\n  // equiv to \n  // SELECT id, first_name, age FROM user\n  sql\"SELECT ${u.all} FROM $u\".query[User].run()\n\ndef firstNamesForLast(lastName: String)(using DbCon): Vector[String] =\n  val u = User.Table\n  // equiv to\n  // SELECT DISTINCT first_name FROM user WHERE last_name = ?\n  sql\"\"\"\n    SELECT DISTINCT ${u.firstName} FROM $u\n    WHERE ${u.lastName} = $lastName\n  \"\"\".query[String].run()\n\ndef insertOrIgnore(creator: UserCreator)(using DbCon): Unit =\n  val u = User.Table\n  // equiv to\n  // INSERT OR IGNORE INTO user (first_name, age) VALUES (?, ?)\n  sql\"INSERT OR IGNORE INTO $u ${u.insertCols} VALUES ($creator)\".update.run()\n```\n\nIt's important that `val Table = TableInfo[X, Y, Z]` is not explicitly typed, otherwise its structural typing will be destroyed.\n\nIn the case of multiple joins, you can use `TableInfo.alias(String)` to prevent name conflicts:\n\n```scala\nval c = TableInfo[Car].alias(\"c\")\nval p = TableInfo[Person].alias(\"p\")\n\nsql\"\"\"\n   SELECT ${c.all}, ${p.firstName}\n   FROM $c\n   JOIN $p ON ${p.id} = ${c.personId}\n   \"\"\".query.run()\n```\n\n### Splicing Literal Values into Frags\n\nTo splice Strings directly into `sql` statements, you can interpolate `SqlLiteral` values. For example,\n\n```scala\nval table = SqlLiteral(\"beans\")\n  \nsql\"select * from $table\"\n```\n\nThis feature should be used sparingly and never with untrusted input. \n\n### Postgres Module\n\nThe Postgres Module adds support for [Geometric Types](https://www.postgresql.org/docs/current/datatype-geometric.html) and [Arrays](https://www.postgresql.org/docs/current/arrays.html). Postgres Arrays can be decoded into Scala List/Vector/IArray, etc; multi-dimensionality is also supported.\n\n```\n\"com.augustnagro\" %% \"magnumpg\" % \"1.3.0\"\n```\n\nExample: Insert into a table with a `point[]` type column.\n\nWith table:\n\n```sql\ncreate table my_geo (\n  id bigint primary key,\n  pnts point[] not null\n);\n```\n\n```scala\nimport org.postgresql.geometric.*\nimport com.augustnagro.magnum.*\nimport com.augustnagro.magnum.pg.PgCodec.given\n\n@Table(PostgresDbType)\ncase class MyGeo(@Id id: Long, pnts: IArray[PGpoint]) derives DbCodec\n\nval dataSource: javax.sql.DataSource = ???\nval xa = Transactor(dataSource)\n\nval myGeoRepo = Repo[MyGeo, MyGeo, Long]\n\ntransact(xa):\n  myGeoRepo.insert(MyGeo(1L, IArray(PGpoint(1, 1), PGPoint(2, 2))))\n```\n\nThe import of `PgCodec.given` is required to bring Geo/Array DbCodecs into scope.\n\n#### Arrays of Enums\n\nThe `pg` module supports arrays of simple (non-ADT) enums.\n\nIf you want to map an array of [Postgres enums](https://www.postgresql.org/docs/current/datatype-enum.html) to a sequence of Scala enums, use the following import when deriving the DbCodec:\n\n```scala\nimport com.augustnagro.magnum.pg.PgCodec.given\nimport com.augustnagro.magnum.pg.enums.PgEnumToScalaEnumSqlArrayCodec\n\n// in postgres: `create type Color as enum ('Red', 'Green', 'Blue');`\nenum Color derives DbCodec:\n  case Red, Green, Blue\n\n@Table(PostgresDbType)\ncase class Car(@Id id: Long, colors: Vector[Color]) derives DbCodec\n```\n\nIf instead your Postgres type is an array of varchar or text, use the following import:\n\n```scala\nimport com.augustnagro.magnum.pg.enums.PgStringToScalaEnumSqlArrayCodec\n```\n\n### Logging SQL queries\n\nIf you set the java.util Logging level to DEBUG, all SQL queries will be logged.\nSetting to TRACE will log SQL queries and their parameters.\n\n#### Logging Slow Queries\n\nYou can log slow queries by using the `Transactor` class in conjunction with `SqlLogger.logSlowQueries(FiniteDuration)`. See [Customizing Transactions](#customizing-transactions) for an example. You can also implement your own SqlLogger subclass as desired.\n\n## Integrations\n\n### ZIO\n\nMagnum provides a fine layer of integration with ZIO.    \nThe `magnum-zio` module provides an implementation of the `connect` and `transact` utils that return a ZIO effect.\n\nTo use the ZIO integration, add the following dependency:\n```scala\n\"com.augustnagro\" %% \"magnumzio\" % \"x.x.x\"\n```\n\nand import these utils in your code with:\n```scala\nimport com.augustnagro.magnum.magzio.*\n```\n\n## Motivation\n\nHistorically, database clients on the JVM fall into three categories.\n\n* Object Oriented Repositories (Spring-Data, Hibernate)\n* Functional DSLs (JOOQ, Slick, quill, zio-sql)\n* SQL String interpolators (Anorm, doobie, plain jdbc)\n\nMagnum is a Scala 3 library combining aspects of all three,\nproviding a typesafe and refactorable SQL interface,\nwhich can express all SQL expressions, on all JDBC-supported databases.\n\nLike in Zoolander (the movie), Magnum represents a 'new look' for Database access in Scala.\n\n## Feature List\n\n* Supports any database with a JDBC driver,\n  including Postgres, MySql, Oracle, ClickHouse, H2, and Sqlite\n* Efficient `sql\" \"` interpolator\n* Purely-functional API\n* Common queries (like insert, update, delete) generated at compile time\n* Difficult to hit [N+1 query problem](https://stackoverflow.com/questions/97197/what-is-the-n1-selects-problem-in-orm-object-relational-mapping)\n* Type-safe Transactions\n* Supports database-generated columns\n* Easy to use, Loom-ready API (no Futures or Effect Systems)\n* Easy to define entities. Easy to implement DB support \u0026 codecs for custom types.\n* Scales to complex SQL queries\n* Specifications for building dynamic queries, such as table filters with pagination\n* Supports high-performance [Seek pagination](https://blog.jooq.org/faster-sql-paging-with-jooq-using-the-seek-method/)\n* Performant batch-queries\n\n## Developing\nThe tests are written using TestContainers, which requires Docker be installed.\n\n## Talks and Blogs\n\n* Scala Days 2023: [slides](/Magnum-Slides-to-Share.pdf), [talk](https://www.youtube.com/watch?v=iKNRS5b1zAY)\n\n## Frequently Asked Questions\n\n#### Does Magnum support nested entities like:\n\n```scala\n@Table(H2DbType, SqlNameMapper.CamelToSnakeCase)\ncase class Company(\n  name: String,\n  address: Address,\n  ) derives DbCodec\n\ncase class Address(\n  street: String,\n  city: String,\n  zipCode: String,\n  country: String\n) derives DbCodec\n```\n\nNO; Magnum only supports deriving flat entity class structures. This keeps things simple and makes it obvious how the Scala entity class maps to the SQL table.\n\nWe may add support for SQL UDTs (user defined types) in the future; however at the time of writing, UDTs are not well-supported by JDBC drivers.\n\nYou could also express the above example using a foreign key to an Address table, like so:\n\n```scala\n@Table(H2DbType, SqlNameMapper.CamelToSnakeCase)\ncase class Company(\n  name: String,\n  addressId: AddressId,\n) derives DbCodec\n\nopaque type AddressId = Long\nobject AddressId:\n  def apply(id: Long): AddressId = id\n  extension (id: AddressId)\n    def underlying: Long = id\n  given DbCodec[AddressId] =\n    DbCodec[Long].biMap(AddressId.apply, _.underlying)\n\n@Table(H2DbType, SqlNameMapper.CamelToSnakeCase)\ncase class Address(\n  @Id id: AddressId,\n  street: String,\n  city: String,\n  zipCode: String,\n  country: String\n) derives DbCodec\n```\n\n#### UUID DbCodec doesn't work for my database\n\nSome databases directly support the UUID type; these include Postgres, Clickhouse, and H2. When using the built-in `DbCodec[UUID]`, defined in `DbCodec.scala`, serialization and deserialization of `java.util.UUID` will work as expected.\n\nOther databases like MySql, Oracle, and Sqlite, however, do not natively support UUID columns. Users have to choose an alternate datatype to store the UUID: most commonly `varchar(36)` or `binary(16)`. The JDBC drivers for these databases do not support direct serialization and deserialization of `java.util.UUID`, therefore the default `DbCodec[UUID]` will not be sufficient. Instead, import the appropriate codec from `com.augustnagro.magnum.UUIDCodec`. For example,\n\n```scala\nimport com.augustnagro.magnum.*\nimport com.augustnagro.magnum.UUIDCodec.VarCharUUIDCodec\nimport java.util.UUID\n\n@Table(MySqlDbType)\ncase class Person(@Id id: Long, name: String, tracking_id: Option[UUID]) derives DbCodec\n```\n\n## Todo\n* JSON / XML support\n* Support MSSql\n* Cats Effect \u0026 ZIO modules\n* Explicit Nulls support\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FAugustNagro%2Fmagnum","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FAugustNagro%2Fmagnum","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FAugustNagro%2Fmagnum/lists"}