# Quill library for SQL statement generation and validation - Status: draft [ accepted | deprecated | superseded by [xxx](yyyymmdd-xxx.md)] - Deciders: Yurii Shynbuiev, Fabio Pinheiro, Benjamin Voiturier - Date: [2023-01-17] - Tags: DAL, SQL, Postrgresql, Typesafe ## Context and Problem Statement PostgreSQL is essential to the Atala Prism technology stack, where most entities are stored. Backend services: Prism Agent, Prism Mediator, and Prism Node use PostgreSQL. [Doobie](https://tpolecat.github.io/doobie/index.html) llibrary is currently used in Scala code to communicate with Postgresql. Quotes from the website ``` Doobie is a pure functional JDBC layer for Scala and Cats. It is not an ORM, nor is it a relational algebra; it simply provides a functional way to construct programs (and higher-level libraries) that use JDBC doobie is a Typelevel project. This means we embrace pure, typeful, functional programming, and provide a safe and friendly environment for teaching, learning, and contributing as described in the Scala Code of Conduct. ``` Doobie is a good choice for DAL, and this ADR is about something other than replacing it. Writing the SQL statement and mapping the row to the case class is a boilerplate and error-prone activity that the Quill library can optimize. **Writing the code for mapping a table row to a case class and writing the low-level SQL statement is an error-prone and boilerplate thing** **Using the [Quill](https://getquill.io/) library on top of Doobie can optimize and improve these things.** Quote from the website: ``` Quill provides a Quoted Domain Specific Language (QDSL) to express queries in Scala and execute them in a target language. The library’s core is designed to support multiple target languages, currently featuring specializations for Structured Query Language (SQL) and Cassandra Query Language (CQL). 1. Boilerplate-free mapping: The database schema is mapped using simple case classes. 2. Quoted DSL: Queries are defined inside a quote block. Quill parses each quoted block of code (quotation) at compile time and translates them to an internal Abstract Syntax Tree (AST) 3. Compile-time query generation: The ctx.run call reads the quotation’s AST and translates it to the target language at compile time, emitting the query string as a compilation message. As the query string is known at compile time, the runtime overhead is very low and similar to using the database driver directly. 4. Compile-time query validation: If configured, the query is verified against the database at compile time, and the compilation fails if it is not valid. The query validation does not alter the database state. ``` There are [Slick](https://scala-slick.org/) and [ScalikeJDBC](http://scalikejdbc.org/) libraries as well. Comparison of these libraries is not a goal of this ADR, but it's essential to know the differences. There are good references to take a look at in the [Links](#links) section. Overall, all libraries have differences in the following aspects: - Metamodel (how to define the schema and type mapping) - Static SQL statement (how and where does the SQL statement is written/generated) - Dynamic SQL statement (how and where does the dynamic SQL statement written/generated) - Connection Management (thread and connection pooling) - Asynchronous API (the high-level API to execute queries blocking or non-blocking) - Asynchronous IO (is IO operation blocking or asynchronous) - Effect library that is used (free-monad, Future, Task, ZIO) ## Decision Drivers - Generate and validate SQL statement based on the convention-over-configuration approach in compile time (type-safe queries) - Reduce boilerplate and error-prone code - Easy to write the dynamic queries ## Considered Options - Doobie (Quill for the connection pooling, SQL statement execution, and SQL statement writing) - Doobie + Quill (Quill for the connection pooling, SQL statement execution, and SQL statement writing + Quill for the SQL statement generation) - Quill (Quill for the connection pooling, SQL statement execution, and SQL statement writing and generation) ## Decision Outcome Chosen option: "Doobie + Quill" because it's the simplest solution that requires minimal changes to the existing code and brings the benefits of automatic SQL statement generation and validation in compile time (see below). ### Positive Consequences - convention-over-configuration approach for the generation and validation of SQL statements using macros in the compile time - easy work with dynamic queries - backward compatible solution (minimum changes are required for the current code base) ### Negative Consequences - DTO case classes are required for each table to generate the SQL statement based on the convention ## Pros and Cons of the Options ### Doobie Doobies library is used as it is right now without any changes - Good, because it is a solid FP library for Postgresql - Good, because it has good documentation and a large community of developers who contribute to the library - Good, because it is built using Free Monad, which makes it composable and easy to integrate with any popular effects library - Bad, because it has a low-level API for writing the SQL statement (boilerplate and error-prone code) - Bad, because it uses blocking IO at the network level ### Doobie+Quill Doobie library is used as it is right now, and Quill library is used for SQL statement generation and validation in compile time - Good, because it ss a solid FP library for Postgresql - Good, because it has good documentation and a large community of developers who contribute to the library - Good, because it is built using Free Monad, which makes it composable and easy to integrate with any popular effects library - Good, because Quill library is used for SQL statement generation at the compile time - Good, because Quill library extends the current solution, and no changes to the code base are required - Bad, because the DTO case class must be created for each table - Bad, because it uses blocking IO at the network level ### Quill Quill is used instead of Doobie - Good, because it is a solid FP library for Postgresql - Good, because it has good documentation and a large community of developers who contribute to the library - Good, because it is built using Free Monad, which makes it composable and easy to integrate with any widespread effects library - Good, because it is used for SQL statement generation at the compile time instead of using Doobie low-level API - Good, because it can be configured to use non-blocking IO at the network level - Bad, because significant refactoring of all DAL is required - Bad, because the DTO case class must be created for each table ## Examples ### Doobie ``` import doobie._ import doobie.implicits._ import doobie.postgres._ case class Person(id: Int, name: String) val q = sql"SELECT id, name FROM person WHERE id = 1".query[Person] val result: ConnectionIO[List[Person]] = q.to[List].transact(Transactor.fromDriverManager[IO]( "org.postgresql.Driver", "jdbc:postgresql:world", "username", "password" )) ``` ### Quill ``` import io.getquill._ val ctx = new SqlMirrorContext(PostgresDialect, "ctx") case class Person(id: Int, name: String) val q = quote { query[Person].filter(p => p.id == 1) } val result: List[Person] = ctx.run(q) ``` ### Slick ``` import slick.jdbc.PostgresProfile.api._ val db = Database.forConfig("database") case class Person(id: Int, name: String) val q = TableQuery[Person].filter(_.id === 1) val result: Future[Seq[Person]] = db.run(q.result) ``` #### Two more real example of Doobie and Quill usage are in the [Links](#links) section. ## Links - [Comparing Scala relational database access libraries](https://softwaremill.com/comparing-scala-relational-database-access-libraries/) - [Comparison with Alternatives](https://scala-slick.org/docs/compare-alternatives) - [Doobie vs Quill](https://www.libhunt.com/compare-doobie-vs-zio-quill) - [Slick vs Doobie](https://www.libhunt.com/compare-slick--slick-vs-doobie?ref=compare) - [Database access libraries in Scala](https://medium.com/@takezoe/database-access-libraries-in-scala-7aa7590aa3db) - [Typechecking SQL queries with doobie](https://godatadriven.com/blog/typechecking-sql-queries-with-doobie/) - [Typechecking SQL in Slick and doobie](https://underscore.io/blog/posts/2015/05/28/typechecking-sql.html) - [Doobie example in the Pollux library](https://github.com/input-output-hk/atala-prism-building-blocks/blob/pollux-v0.17.0/pollux/lib/sql-doobie/src/main/scala/io/iohk/atala/pollux/sql/repository/JdbcCredentialRepository.scala) - [Quill example in the Pollux library](https://github.com/input-output-hk/atala-prism-building-blocks/blob/pollux-v0.17.0/pollux/lib/sql-doobie/src/main/scala/io/iohk/atala/pollux/sql/model/VerifiableCredentialSchema.scala)