{"id":15044862,"url":"https://github.com/zio/zio-protoquill","last_synced_at":"2025-04-08T09:07:56.247Z","repository":{"id":37206501,"uuid":"343503075","full_name":"zio/zio-protoquill","owner":"zio","description":"Quill for Scala 3","archived":false,"fork":false,"pushed_at":"2025-03-27T14:16:06.000Z","size":31433,"stargazers_count":216,"open_issues_count":58,"forks_count":51,"subscribers_count":8,"default_branch":"master","last_synced_at":"2025-04-01T07:51:58.798Z","etag":null,"topics":["cassandra","jdbc","language-integrated-query","linq","postgresql","scala","spark","sparksql","sql"],"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/zio.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.txt","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}},"created_at":"2021-03-01T17:37:46.000Z","updated_at":"2025-03-27T14:16:09.000Z","dependencies_parsed_at":"2024-01-19T03:30:59.064Z","dependency_job_id":"20c83e49-018a-4f4a-948f-ca38ee6d4a72","html_url":"https://github.com/zio/zio-protoquill","commit_stats":{"total_commits":991,"total_committers":34,"mean_commits":"29.147058823529413","dds":"0.24419778002018167","last_synced_commit":"806867078dfb0e99ed9a5a43663a55e65b9fbad6"},"previous_names":["getquill/protoquill"],"tags_count":43,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zio%2Fzio-protoquill","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zio%2Fzio-protoquill/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zio%2Fzio-protoquill/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zio%2Fzio-protoquill/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/zio","download_url":"https://codeload.github.com/zio/zio-protoquill/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247809962,"owners_count":20999816,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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":["cassandra","jdbc","language-integrated-query","linq","postgresql","scala","spark","sparksql","sql"],"created_at":"2024-09-24T20:51:09.288Z","updated_at":"2025-04-08T09:07:56.204Z","avatar_url":"https://github.com/zio.png","language":"Scala","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Introduction\n\nProtoQuill is the Scala 3 version of [Quill: Free/Libre Compile-time Language Integrated Queries for Scala](https://zio.dev/zio-quill//). For those migrating, or exploring migration from Scala2-Quill, most Queries written in Scala2-Quill should work readily in ProtoQuill but they will become Dynamic. Change them to `inline def` expressions and they should once-again be compile-time (see the [Rationale](#rationale-for-inline) section for some info on why I chose to do this). Also see the [Migration Notes](#migration-notes) section.\n\nNot all Contexts and not all Functionality is Supported yet. Here is a rough list of both:\n\nCurrently Supported:\n - Basic Quotation, Querying, Lifting, and Types (Compile-Time and Dynamic)\n - Inner/Outer, Left/Right joins\n - Query.map/flatMap/concatMap/filter other [query constructs](https://zio.dev/zio-quill//#quotation-queries).\n - Insert, Update, Delete [Actions](https://zio.dev/zio-quill//#quotation-actions) (Compile-Time and Dynamic)\n - Batch Insert, Batch Update, and Batch Delete Actions, both Compile-Time and Runtime! (Scala2-Quill only supports Compile-Time batch queries)\n - ZIO and Synchronous JDBC contexts.\n - SQL OnConflict Clauses\n - Prepare Query (i.e. `context.prepare(query)`)\n - Translate Query (i.e. `context.translate(query)`)\n - Cassandra Contexts (using V4 drivers!)\n - Dynamic Query API (i.e. [this](https://zio.dev/zio-quill//#quotation-dynamic-queries-dynamic-query-api))\n\nNot Supported:\n - Implicit class based extensions. Please see the [Extensions](https://github.com/zio/zio-protoquill#extensions) section below on how to do this.\n\nPlanned Future Support\n - OrientDB Contexts\n - Spark Context\n\nThere are also quite a few new features that ProtoQuill has:\n - Scala Methods and Typeclasses Transforming ProtoQuill queries (see [Shareable Code](#shareable-code) and [Advanced Example](#advanced-example)).\n - [Custom Parsing](#custom-parsing) - Write parsers for custom user code!\n - [Co-Product Rows](#co-product-rows) (Highly experimental, use with caution!)\n - [Caliban-Integration](#caliban-integration) (Experimental deep integration with Caliban. Trivially filter/exclude any columns you want!)\n - [Dependent Contexts](https://github.com/zio/zio-quill#dependent-contexts) - In Scala2-Quill Dependent Contexts were demonstrated as a typical example of the limitations of working with Quoted blocks. In ProtoQuill these work as expected since there are no path dependant types in the ProtoQuill output. Have a look at this [scastie example](https://scastie.scala-lang.org/TO5dF87jQQegUGqmIQtbew) for more information.\n\nOne other note that this documentation is not yet a fully-fledged reference for ProtoQuill features. Have a look at the original [Quill documentation](https://zio.dev/zio-quill//) for basic information about how Quill constructs (e.g. Queries, Joins, Actions, Batch Actions, etc...) are written in lieu of any documentation missing here.\n\nFor further information, watch:\n - [ProtoQuill Release Party](https://www.youtube.com/watch?v=El9fkkHewp0) - Overview of new Quill features in Scala 3 and discussion about the future of Metaprogramming.\n - [Quill, Dotty, And The Awesome Power of 'Inline'](https://www.youtube.com/watch?v=SmBpGkIsJIU) - Many examples of new things that can be done with this library that cannot be done with standard Quill.\n - [ScQuilL Sessions - Quill, Dotty, and Macros](https://www.youtube.com/watch?v=0PSg__PPjY8\u0026list=PLqky8QybCVQYNZY_MNJpkjFKT-dAdHQDX) - A tutorial on developing Dotty-Quill from scratch (covers quoting, liftables, and liftables).\n - [Generic Derivation is the New Reflection](https://www.youtube.com/watch?v=E9L1-rkYPng) - A tutorial on how Dotty Generic Derivation works covering a Dotty-Quill use-case.\n\n\n# Getting Started\n\nThe simplest way to get started with ProtoQuill is with the standard JDBC contexts.\nThese are sychronous so for a high-throughput system you will ultimately need to switch\nto either the ZIO-based contexts \n\nAdd the following to your SBT file:\n```scala\nlibraryDependencies ++= Seq(\n  // Syncronous JDBC Modules\n  \"io.getquill\" %% \"quill-jdbc\" % \"4.7.3\",\n  // Or ZIO Modules\n  \"io.getquill\" %% \"quill-jdbc-zio\" % \"4.7.3\",\n  // Or Cassandra\n  \"io.getquill\" %% \"quill-cassandra\" % \"4.7.3\",\n  // Or Cassandra + ZIO\n  \"io.getquill\" %% \"quill-cassandra-zio\" % \"4.7.3\",\n  // Add for Caliban Integration\n  \"io.getquill\" %% \"quill-caliban\" % \"4.7.3\"\n)\n```\n\nAssuming we are using Postgres, add the following application.conf.\n```\ntestPostgresDB.dataSourceClassName=org.postgresql.ds.PGSimpleDataSource\ntestPostgresDB.dataSource.databaseName=\u003cmy-database\u003e\ntestPostgresDB.dataSource.url=\u003cmy-jdbc-url\u003e\n```\nHave a look at [this list](https://zio.dev/zio-quill//#contexts-quill-jdbc) to see how to configure other databases.\n\nCreate a context and a case class representing your table.\n```scala\nimport io.getquill._\n\nobject MyApp {\n  case class Person(firstName: String, lastName: String, age: Int)\n\n  // SnakeCase turns firstName -\u003e first_name\n  val ctx = new PostgresJdbcContext(SnakeCase, \"ctx\")\n  import ctx._\n\n  def main(args: Array[String]): Unit = {\n    val named = \"Joe\"\n    inline def somePeople = quote {\n      query[Person].filter(p =\u003e p.firstName == lift(named))\n    }\n    val people: List[Person] = run(somePeople)\n    // TODO Get SQL\n    println(people)\n  }\n}\n```\n\n# Tutorial\n\n## Queries\n\nProtoQuill queries are built using inline quoted expressions.\n```scala\n// With just this import you can use quote, query, insert/update/delete and lazyLift\nimport io.getquill._\n\ninline def people = quote {\n  query[Person]\n}\ninline def joes = quote {\n  people.filter(p =\u003e p.name == \"Joe\")\n}\n\n\u003e You *do not* need to import a context in ProtoQuill to make a quoation, just `io.getquill._`. Contexts are only needed for lifting. See the `Lifting and Lazy Lifting` section for more detail.\n\nrun(joes)\n// TODO Get SQL\n```\n\n### Quotation is (Mostly) Optional\n\nIf all parts of a Query are `inline def`, quotation is not strictly necessary:\n\n```scala\ninline def people = query[Person]\ninline def joes = people.filter(p =\u003e p.name == \"Joe\")\n\nrun(joes)\n// TODO Get SQL\n```\n\nHowever, if parts of the the query are dynamic (i.e. not `inline def`) it is needed:\n```scala\ninline def people = quote {\n  query[Person]\n}\nval joes = quote {\n  people.filter(p =\u003e p.name == \"Joe\")\n}\n\nrun(joes)\n// TODO Warning Dynamic Query\n```\n\n### Quoted Operations\n\nProtoQuill supports Quill `query[T]` constructs including:\n - Outer/Inner, Left/Right Join (both monadic and applicative)\n - Map, FlatMap, ConcatMap\n - Union, Union-All\n - Distinct, Nested\n - querySchema\n\nPlease refer to [quotation-queries](https://zio.dev/zio-quill//#quotation-queries) in the Scala2-Quill documentation for more information.\nKeep in mind that in ProtoQuill for these to generate compile-time queries, they need to be `inline def`.\n\n### Batch Queries\n\nProtoQuill supports Insert/Update/Delete actions as well as their batch variations.\nPlease refer to [quotation-actions](https://zio.dev/zio-quill//#quotation-actions) in the Scala2-Quill documentation for more information.\nKeep in mind that in ProtoQuill for these to generate compile-time queries, they need to be `inline def`.\nThe `onConflict` instructions are not yet supported in ProtoQuill.\n\nIn the latest release, ProtoQuill supports the full range of batch queries that Scala2-Quill supports, including:\n```scala\n// batch queries with different entities\nliftQuery(vips).foreach(v =\u003e query[Person].insertValue(Person(v.first + v.last, v.age)))\n\n// batch queries with scalars\nliftQuery(List(1,2,3)).foreach(i =\u003e query[Person].filter(p =\u003e p.id == i).update(_.age -\u003e 123))\n\n// batch queries with additional lifts\nliftQuery(people).foreach(p =\u003e query[Person].filter(p =\u003e p.age \u003e lift(123)).contains(p.age)).updateValue(p))\n// ...even with additional liftQuery clauses!\nliftQuery(people).foreach(p =\u003e query[Person].filter(p =\u003e p.age \u003e lift(123) \u0026\u0026 liftQuery(List(1,2,3)).contains(p.age)).updateValue(p))\n\n// batch queries with `returning` clauses\nliftQuery(vips).foreach(v =\u003e query[Person].insertValue(Person(v.first + v.last, v.age)).returning(_.id))\n```\n\n### Metas\n\nQueryMeta, SchemaMeta, InsertMeta, and UpdateMeta are supported in ProtoQuill.\nPlease refer to [meta-dsl](https://zio.dev/zio-quill//#extending-quill-meta-dsl) in the Scala2-Quill documentation for more information.\nKeep in mind that in ProtoQuill for these to generate compile-time queries, they need to be `inline def`.\n\nAdditionally, they can be defined using Scala 3 `given` syntax:\n```scala\n// SchemaMeta\ninline given SchemaMeta[Person] = schemaMeta(\"PersonTable\", name -\u003e \"nameRow\")\n\n// Insert Meta\ninline given InsertMeta[Person] = insertMeta(_.id)\n\n// Update Meta\ninline given UpdateMeta[Person] = updateMeta(_.id)\n```\nThis also works with QueryMeta:\n```scala\ninline given QueryMeta[PersonName, String] =\n  queryMeta(\n    quote {\n      (q: Query[PersonName]) =\u003e q.map(p =\u003e p.name)\n    }\n  )((name: String) =\u003e PersonName(name))\n\nval result = ctx.run(people)\n// TODO Get SQL\n```\n\n### Shareable Code\n\nSince quotation of `inline def` code is optional, Quill expressions can share code with regular Scala constructs.\n\n```scala\n// case class Person(name: String, age: Int)\ninline def onlyJoes(p: Person) = p.name == \"Joe\"\n\nrun( query[Person].filter(p =\u003e onlyJoes(p)) )\n// TODO Get SQL\n\nval people: List[Person] = ...\nval joes = people.filter(p =\u003e onlyJoes(p))\n```\n\n### Advanced Example\n\nSince Quill expressions can share code with regular Scala constructs,\nthis can be generalized into higher-level constructs such as typeclasses.\n\n```scala\n// case class Person(name: String, age: Int)\n\ntrait Filterable[F[_]]:\n  extension [A](inline x: F[A])\n    inline def filter(inline f: A =\u003e Boolean): F[A]\n\nextension [F[_]](inline people: F[Person])(using inline filterable: Filterable[F])\n  inline def onlyJoes = people.filter(p =\u003e p.name == \"Joe\")\n\nclass ListFilterable extends Filterable[List]:\n  extension [A](inline xs: List[A])\n    inline def filter(inline f: A =\u003e Boolean): List[A] = xs.filter(f)\n\nclass QueryFilterable extends Filterable[List]:\n  extension [A](inline xs: List[A])\n    inline def filter(inline f: A =\u003e Boolean): List[A] = xs.filter(f)\n\nrun( query[Person].onlyJoes )\n// GET SQL\n\nval people: List[Person] = ...\nval joes = people.onlyJoes\n```\nUsing this technique, a standard functor-hierarchy can be constructed that can be used for compile-time\ngeneration of Quill Queries. Have a look at the [Typeclass-series examples](https://github.com/getquill/protoquill/tree/master/quill-sql-tests/src/test/scala/io/getquill/examples) for more inspiration.\n\n\n## Lifting and Lazy Lifting\n\nSince Quill-Quotations define blocks of compile-time-inspectable code, adding variables whose value is only know during runtime typically requires lifting:\n```scala\n// NOTE: Be sure to import a context first!\n// val ctx = new MirrorSqlContext(PostgresDialect, Literal); import ctx._\n\nval runtimeValue = somethingFromSomewhere() // A value that we only know during runtime\ninline def somePeople = quote {\n  query[Person].filter(p =\u003e p.name == lift(runtimeValue))\n}\nval results: List[Person] = run(somePeople)\n// TODO Get SQL\n```\n\nHowever, since Quotation in ProtoQuill is static, you can use `lazyLift` to lift a value without importing a context. The advantage of this is that you can lift things before what context to use.\n\n```scala\nimport io.getquill._\nval name = ...\ninline def q = quote { query[Person].filter(p =\u003e p.name == lift(name)) }\n\n// Now we can use this quotation in multiple contexts\n{\n  val ctx = new PostgresJdbcContext(Literal, \"ctx\")\n  val results = ctx.run(q)\n  // TODO Get SQL\n}\n{\n  val ctx = new H2JdbcContext(Literal, \"ctx\")\n  val results = ctx.run(q)\n  // TODO Get SQL\n}\n```\nNote however that for lazy-lifts to work, for a query, all of it's parts need to be `inline def`. That is to say, Dynamic Queries do not work with `lazyLift`.\n\n\n### How it Works\n\nInternally, the lift-method will use the mechanisms of the underlying database-layer (e.g. JDBC) to lift the `runtimeValue` ultimately swapping it into the `\"?\"` location using some kind of prepared statement. This is typically handled by the encoders.\n\n```scala\n/* Note that is an approximate example! Not actual Quill code. */\n\ntrait Encoders:\n  implicit val stringEncoder: Encoder[String] =\n    JdbcEncoder(sqlType, (index: Int, value: String, row: PrepareRow) =\u003e {\n      row.setString(index, value)\n      row\n    })\n\nclass MyDatabaseContext(...) with Encoders\n\ndef lift(value: T)(using Encoder[T]) = ...\n```\n\u003e Note that the above is only a conceptual model of how `lift` works. In reality it needs to be implemented using Scala 3 Macros in order to function properly.\n\nThis means that in order to do lifting we must first import a context.\n```scala\n// Must do this:\nval ctx = new MyDatabaseContext()\nimport ctx._\n\n// Before Doing this:\ninline def somePeople = quote {\n  query[Person].filter(p =\u003e p.name == lift(runtimeValue))\n}\n```\n\nUnlike `lift`, `lazyLift` does not require a encoder to be imported at the call-site because it delays summoning the encoder until the `run` function.\n```scala\ninline def somePeople = quote {\n  query[Person].filter(p =\u003e p.name == lazyLift(runtimeValue))\n}\n\n// Need to import a context only for the `run` function.\nval ctx = new MyDatabaseContext()\nimport ctx._\nval result: List[Person] = run(somePeople) // summons Encoder[String] here\n```\nConceptually, `lazyLift` can be thought of like this:\n```scala\n// Return some kind of information that can be evaluated later when an encoder is summoned\ndef lift(value: T) = (encoder: Encoder[T]) =\u003e encoder.encode(t)\n\nclass MyDatabaseContext:\n  def run(q: Quoted[Query[T]]) =\n     val statement = prepareStatement(q)\n     val t = q.lifts(0)\n     statement.prepare(1, summon[Encoder[T]].encode(t)) // Summon the actual encoder at the `run` site.\n```\nAgain, please note that this is not the actual Quill code, this is just a conceptual model of how it works.\n\n## Filtering Tables by Key/Values\nOn typical use-case that ProtoQuill can do (which has been difficult in the past) is to filter a query based on an arbitrary group of column/value pairs. This is typically done with Http-Based systems where URL-parameters `\u0026key=value` are decoded as a map. In ProtoQuill, the `filterByKeys` addresses this use-case.\n```scala\nval values: Map[String, String] = Map(\"firstName\" -\u003e \"Joe\", \"age\" -\u003e \"22\")\n\n// filterByKeys uses lift so you need a context to use it\nval ctx = new MirrorContext(Literal, PostgresDialect)\nimport ctx._\n\ninline def q = quote {\n  query[Person].filterByKeys(values)\n}\nrun(q)\n\n// SELECT p.firstName, p.lastName, p.age\n// FROM Person p\n// WHERE\n//   (p.firstName = ? OR ? IS NULL) AND\n//   (p.lastName = ? OR ? IS NULL) AND\n//   (p.age = ? OR ? IS NULL) AND\n//   true\n```\nThe way that this works is that in each `?` slot, the corresponding column is looked up from the map.\n```\n// SELECT p.firstName, p.lastName, p.age\n// FROM Person p\n// WHERE\n//   (p.firstName = { values(\"firstName\") } OR { values(\"firstName\") } IS NULL) AND\n//   (p.lastName = { values(\"lastName\") } OR { values(\"lastName\") } IS NULL) AND\n//   (p.age = { values(\"age\") } OR { values(\"age\") } IS NULL) AND\n//   true\n```\n\n## Getting SQL of the Last Executed Query (ZIO Only)\n\nIn ZIO contexts, you can get the SQL of the last executed query by using the `getLastExecutedQuery()` method.\n\n```scala\nval people =\n   for {\n     people \u003c- ctx.run(quote { query[Person] })\n     sql \u003c- ctx.getLastExecutedQuery()\n     _ \u003c- ZIO.log(s\"Last Executed Query: ${sql}\")\n   } yield people\n```\n\nFor advanced debugging use-cases, you can even get the syntax tree of the last executed query by using the `getLastExecutedQueryTree()` method.\nSince this may incur additional performance overhead, make sure that your context extends `AstSplicing` in order to enable this behavior.\n\n```scala\nval people =\n   for {\n     // First create a quill-zio context. Be sure that it extends AstSplicing\n     ctx \u003c- Quill.Postgres(Literal, connectionPool) with AstSplicing\n     // Then run a query and invoke getLastExecutionInfo \n     people \u003c- ctx.run(quote { query[Person] })\n     info \u003c- ctx.getLastExecutionInfo()\n     _ \u003c- ZIO.log(s\"Last Executed Ast: ${info.ast}\")\n   } yield people\n```\n\n## Co-Product Rows\n\nCo-Product are supported using Enums and sealed traits. Keep in mind that for now, only static-global enums are supported and any sealed traits that are used must be sealed in a *separate object* in order to work. Otherwise a sum-type mirror of them will not be found. In ORM-terms, Quill uses a \"Table Per Class-Hierarchy\" model of co-product polymorhism in which data for all co-products must be encodeable within a simple row.\n\u003e Note: As a possible avenue of exploration, this approach can be combined with QueryMeta to relax the requirement of having a single table for all coproducts since QueryMeta can be used to produce a set of joins under the facade of being a single table.\n\n\nTo use co-product rows do the following:\n\n1. Create the Coproduct\n   ```scala\n   object StaticEnumExample {\n     enum Shape(val id: Int):\n       case Square(override val id: Int, width: Int, height: Int) extends Shape(id)\n       case Circle(override val id: Int, radius: Int) extends Shape(id)\n   }\n   ```\n   \u003e Note: Currently all Enums used for Co-Product rows need to be in a static namespace (i.e. in an Object).\n     This is due a [Dotty/Scala3 issue](https://github.com/lampepfl/dotty/issues/11174).\n2. Create an object called a row-typer which will take a Database row and figure out how what element of the coproduct to decode into.\n   ```scala\n   given RowTyper[Shape] with\n     def apply(row: Row) =\n       row.apply[String](\"type\") match\n         case \"square\" =\u003e classTag[Shape.Square]\n         case \"circle\" =\u003e classTag[Shape.Circle]\n    ```\n3. Create and run your query:\n   ```scala\n   inline def q = quote { query[Shape].filter(s =\u003e s.id == 18) }\n   val result: List[Shape] = ctx.run(q)\n   ```\n\n## Custom Parsing\n\nThe Parser API has been refined considerably in ProtoQuill Beta2 and it is now in a ready state.\n\n### Reason for This\n\nIn Quill you can define methods (including extension methods) that return quoted sections. This is typically used for user-defined logic:\n```scala\nimport io.getquill._\n\nobject MyBusinessLogic:\n  extension (inline i: Int)\n    inline def **(exponent: Int) = quote { sql\"power($i, $exponent)\" }\n\ndef main(args: Array[String]) =\n  import MyBusinessLogic._\n  run( query[Person].map(p =\u003e p.age ** 2 )\n  // SELECT power(p.age, 2) FROM Person p\n```\nHowever, it is entirely possible that you might also want to use these Business-Logic constructs in regular Scala code:\n```scala\nobject MyBusinessLogicNonQuill: // Need to define a different object that does non-quill logic\n  extension (inline i: Int)\n    inline def **(exponent: Int) = Math.pow(i, exponent)\nval ageSquared = person.age ** 2\n```\nThis is cumbersome because multiple objects need to be defined for the two implementations that this power-method (i.e. `**`) needs to be.\nFor this reason, ProtoQuill supports an easy extension syntax for custom parsing. It works like this:\n\n### Syntax\n\n1. First, define your business logic and methods as usual:\n   ```scala\n   object MyBusinessLogic: // Be sure that Nothing is inline here!\n     extension (i: Int)\n       def **(exponent: Int) = Math.pow(i, exponent)\n   ```\n2. Then define a parser to handle this construct:\n   ```scala\n   import io.getquill.parser._\n   import io.getquill.ast.{ Ast, Infix }\n   import io.getquill.quat.Quat\n\n   case class CustomOperationsParser(root: Parser[Ast] = Parser.empty)(override implicit val qctx: Quotes) extends Parser.Clause[Ast] {\n     import quotes.reflect._\n     import CustomOps._\n     def reparent(newRoot: Parser[Ast]) = this.copy(root = newRoot)\n     def delegate: PartialFunction[Expr[_], Ast] =\n       case '{ ($i: Int)**($j: Int) } =\u003e\n         Infix(\n           List(\"power(\", \" ,\", \")\"),\n           List(astParse(i), astParse(j)), true, Quat.Value)\n   }\n\n   object CustomParser extends ParserLibrary:\n     import Parser._\n     override def operationsParser(using qctx: Quotes) =\n       Series.of(new OperationsParser, new CustomOperationsParser)\n   ```\n   Note that these to steps need to be done in a *separate compilation unit*. That typically means that you\n   need to make a separate SBT project with this logic that is compiled before the rest of your application code.\n3. Now in your application code, you can use the custom parser after defing it as a given (or implicit)\n   ```scala\n   given myParser: CustomParser.type = CustomParser\n   import MyBusinessLogic._\n   case class Person(name: String, age: Int)\n   inline def q = quote { query[Person].map(p =\u003e p.age ** 2) }\n   // SELECT power(p.age ,2) FROM Person p\n   ```\n\n  ## Migration Notes\n\n - Most Scala2-Quill code should either work in ProtoQuill directly or require minimal changes in order to work.\n   However, since ProtoQuill compile-time queries rely on `inline def`, these queries must be changed from this:\n   ```scala\n   case class Person(name: String, age: Int)\n   val people = quote { query[Person] }\n   val joes = quote { people.filter(p =\u003e p.name == \"Joe\") }\n   run(joes) // Dynamic Query Detected\n   ```\n   To this:\n   ```scala\n   case class Person(name: String, age: Int)\n   inline def people = quote { query[Person] }\n   inline def joes = quote { people.filter(p =\u003e p.name == \"Joe\") }\n   run(joes) // SELECT p.name, p.age FROM Person p WHERE p.name = 'Joe'\n   ```\n - If you have not implemented `io.getquill._` or are importing components one by one, you will need to add this import. Unlike in Scala2-Quill where methods such as `query`, `quote`, etc... come from your context (e.g. in `val ctx = new PostgresJdbcContext(...); import ctx._`), in ProtoQuill these methods come from the [Dsl object](https://github.com/getquill/protoquill/blob/master/quill-sql/src/main/scala/io/getquill/Dsl.scala) which is exported to `io.getquill`. If you want to import the minimal amount of components, you will at least need `io.getquill.quote` and `io.getquill.query`.\n - TBD: Warnings will occur when there are encoders for some type T but not decoders for it. This is due to possible Quat issues.\n - TBD: The `Embedded` construct is not strictly required in some cases (review cases, Embedded[T] summon is also supported).\n\n# Extensions\n\nProtoQuill supports standard Dotty extensions. An inline extension will yield a compile-time query.\n\n```scala\ncase class Person(first: String, last: String)\n\nextension (inline p: Person) // make sure this variable is `inline`\n  inline def fullName = p.first + \" \" + p.last\n\nrun( query[Person].map(p =\u003e p.fullName) )\n// SELECT p.name || ' ' || p.age FROM Person p\n```\nIn Scala2-Quill, this kind of extension was supported by a hacky use of implicit classes:\n```\nimplicit class PersonExpt(p: Person)\n  def fullName = p.first + \" \" + p.last\n\nrun( query[Person].map(p =\u003e p.fullName) )\n// SELECT p.name || ' ' || p.age FROM Person p\n```\nThis latter kind of extension mechanism is not yet supported and ProtoQuill but will likely be eventually supported\nfor backwards-compatibility reasons. The Queries in which it is used will inherently become Dynamic.\n\n# Rationale for Inline\n\nFor a basic reasoning of why Inline was chosen (instead of Refined-Types on `val` expressions) have a look at the video: [Quill, Dotty, And The Awesome Power of 'Inline'](https://www.youtube.com/watch?v=SmBpGkIsJIU). A more thorough explination is TBD.\n\n# Caliban Integration\n\nExperimental Caliban integration is provided by the `quill-caliban` module. This makes it relatively easy to setup a Caliban GraphQL endpoint where you can filter by any column returned from a Quill query as well as include/exclude any column, and these exclusions are pushed down to the database (In Spark-speak, these are called filter-pushdown, predicate-pushdown respectively). In order to setup the Caliban integration, do the following:\n\n```scala\n// Import Quill\nimport io.getquill._\n\n// Import the Caliban integration\nimport io.getquill.CalibanIntegration._\n\n// Given some simple schema\ncase class PersonT(id: Int, first: String, last: String, age: Int)\ncase class AddressT(ownerId: Int, street: String)\ncase class PersonAddress(id: Int, first: String, last: String, age: Int, street: Option[String])\n\n// Create a query and add .filterColumns and .filterByKeys to the end\ninline def peopleAndAddresses(inline columns: List[String], inline filters: Map[String, String]) =\n  quote {\n    // Given a query...\n    query[Person].leftJoin(query[Address]).on((p, a) =\u003e p.id == a.ownerId)\n      .map((p, a) =\u003e PersonAddress(p.id, p.first, p.last, p.age, a.map(_.street)))\n      // Add these to the end\n      .filterColumns(columns)\n      .filterByKeys(filters)\n  }\n\n// Create a data-source that will pass along the column include/exclude and filter information\nobject DataService:\n  def personAddress(columns: List[String], filters: Map[String, String]) =\n    run(q(columns, filters)).provide(Has(myDataSource))\n    // Assume this returns:\n    // List(\n    //   PersonAddress(1, \"One\", \"A\", 44, Some(\"123 St\")),\n    //   PersonAddress(2, \"Two\", \"B\", 55, Some(\"123 St\")),\n    //   PersonAddress(3, \"Three\", \"C\", 66, None),\n    // )\n\n// Create your Caliban Endpoint\ncase class Queries(\n  personAddress: Field =\u003e (ProductArgs[PersonAddress] =\u003e Task[List[PersonAddress]])\n)\n\n// Implement the endpoint\nval endpoint =\n   graphQL(\n    RootResolver(\n      Queries(\n        personAddress =\u003e\n          (productArgs =\u003e\n            DataService.personAddress(\n              quillColumns(personAddress) /* From CalibanIntegration module*/,\n              productArgs.keyValues\n            )\n          )\n      )\n    )\n  ).interpreter\n\n// Test-run the endpoint like this! (make sure not use the 'query' variable or it will collide with Quill!):\nval calibanQuery =\n  \"\"\"\n  {\n    # Filter by any field in PersonAddress here including first, last, age, street! (or any combination of filters!)\n    personAddressFlat(first: \"Joe\") {\n      # Include/Exclude any fields from PersonAddress here!\n      id\n      last\n      street\n    }\n  }\"\"\"\n\nval output =\n  zio.Runtime.default.unsafeRun(for {\n      interpreter \u003c- api.interpreter\n      result      \u003c- interpreter.execute(calibanQuery)\n    } yield (result)\n  )\n\n// The following data will be returned:\noutput.data.toString == \"\"\"{\"personAddress\":[{\"id\":1,\"first\":\"One\",\"last\":\"A\",\"street\":\"123 St\"}]}\"\"\"\n```\nYou can also plug in this Caliban endpoint into ZIO-Http\n```scala\nobject CalibanExample extends zio.App:\n  val myApp = for {\n    _ \u003c- Dao.resetDatabase()\n    interpreter \u003c- endpoints\n    _ \u003c- Server.start(\n        port = 8088,\n        http = Http.route { case _ -\u003e Root / \"api\" / \"graphql\" =\u003e\n          ZHttpAdapter.makeHttpService(interpreter)\n        }\n      )\n      .forever\n  } yield ()\n\n  override def run(args: List[String]): ZIO[ZEnv, Nothing, ExitCode] =\n    myApp.exitCode\n\nend CalibanExample\n```\nHave a look at the Quill-Caliban Examples [here](https://github.com/zio/zio-protoquill/tree/master/quill-caliban/src/test/scala/io/getquill/example) and the Quill-Caliban tests [here](https://github.com/zio/zio-protoquill/tree/master/quill-caliban/src/test/scala/io/getquill).\n\n### How it works\n\nWhen the `.filterColumns(columns)` and the `.filterByKeys(filters)` methods are called, the following query (that results from the Person\u003c-\u003eAddress table join):\n```sql\nSELECT\n  p.id, p.first, p.last, p.age, a.street\nFROM\n  Person p\n  LEFT JOIN Address a ON p.id = a.ownerId\n```\n...becomes this:\n```sql\nSELECT\n  CASE WHEN ? THEN p.id ELSE null END,\n  CASE WHEN ? THEN p.first ELSE null END,\n  CASE WHEN ? THEN p.last ELSE null END,\n  CASE WHEN ? THEN p.age ELSE null END,\n  CASE WHEN ? THEN a.street ELSE null END\nFROM\n  Person p\n  LEFT JOIN Address a ON p.id = a.ownerId\nWHERE\n  (cast(CASE WHEN ? THEN p.id ELSE null END as VARCHAR) = ? OR ? IS NULL)\n  AND (CASE WHEN ? THEN p.first ELSE null END = ? OR ? IS NULL)\n  AND (CASE WHEN ? THEN p.last ELSE null END = ? OR ? IS NULL)\n  AND (cast(CASE WHEN ? THEN p.age ELSE null END as VARCHAR) = ? OR ? IS NULL)\n  AND (CASE WHEN ? THEN a.street ELSE null END = ? OR ? IS NULL)\n```\nIn each question mark in the `SELECT` and `WHERE` clauses, the appropriate field is selected from the `columns` list and `filters` map.\nHere is roughly how that looks:\n```sql\nSELECT\n  CASE WHEN ${columns.contains(\"id\")} THEN p.id ELSE null END,\n  CASE WHEN ${columns.contains(\"first\")} THEN p.first ELSE null END,\n  CASE WHEN ${columns.contains(\"last\")} THEN p.last ELSE null END,\n  CASE WHEN ${columns.contains(\"age\")} THEN p.age ELSE null END,\n  CASE WHEN ${columns.contains(\"street\")} THEN a.street ELSE null END\nFROM\n  Person p\n  LEFT JOIN Address a ON p.id = a.ownerId\nWHERE\n      (cast(CASE WHEN ${filters.contains(\"id\")} THEN p.id ELSE null END as VARCHAR) = ${filters(\"id\").orNull} OR ${${filters(\"id\").orNull}} IS NULL)\n  AND      (CASE WHEN ${filters.contains(\"first\")} THEN p.first ELSE null END = ${filters(\"first\").orNull} OR ${${filters(\"first\").orNull}} IS NULL)\n  AND      (CASE WHEN ${filters.contains(\"last\")} THEN p.last ELSE null END = ${filters(\"last\").orNull} OR ${${filters(\"last\").orNull}} IS NULL)\n  AND (cast(CASE WHEN ${filters.contains(\"age\")} THEN p.age ELSE null END as VARCHAR) = ${filters(\"age\").orNull} OR ${${filters(\"age\").orNull}} IS NULL)\n  AND      (CASE WHEN ${filters.contains(\"street\")} THEN a.street ELSE null END = ${filters(\"street\").orNull} OR ${${filters(\"street\").orNull}} IS NULL)\n```\nThe important thing to understand is that the SQL optimizer can see into these `CASE WHEN (condition)` clauses when `condition` is a static variable and then know whether the actual column on the right-hand side of the Query actually needs to be used. In Database-Speak, we would call this SARGable.\n\nDon't take my word for it though, have a look at the examples under quill-caliban [here](https://github.com/zio/zio-protoquill/tree/master/quill-caliban/src/test/scala/io/getquill/example). For example run the CalibanExample.scala and execute the following GraphQL query:\n```\nquery{\n  personAddressPlan(first: \"One\") {\n    plan\n    pa {\n      id\n      street\n      first\n      last\n      # Exclude the street column\n    }\n  }\n}\n```\n\n\nIn the response you will not only the results but the Query plan as well. Note that in this particular case our Query-Planner knows that:\n1. We are using the filter \"One\" on the Person.first column.\n2. Since the only column we care about from the Address table is Address.street and our Query doesn't even want that column (and because in our case Person\u003c-\u003eAddress is a one-to-one relationship, that means that the table Address doesn't even need to be scanned!\n\n![Screenshot from 2021-12-29 23-54-52](https://user-images.githubusercontent.com/1369480/147722872-a4801b6e-e916-427d-b134-e71bb8a0259a.png)\n\nAs the writer of Caliban puts it... \"From the front-end down to the DB, you only pay for what you ask!\"\n\n# Interesting Ideas\n - Implement a `lazyFilterByKeys` method using the same logic as filterByKeys but using lazy lifts.\n   in order to be able to use this kind of functionality with out having to import a context.\n - Implement a `filterByLikes` which is the same as `filterByKeys` but uses `like` instead of `==`.\n   then can also implement a `lazyFilterByLikes`.\n - Write a `query.filter(p =\u003e p.firstName.inSet(\"foo\", lift(bar), \"baz\"))` using the `ListFlicer`.\n   this could either translate into `WHERE firstName == 'foo' OR firstName == ? OR firstName == 'baz'` or\n   `WHERE firstName in ('foo', ?, 'baz')`.\n - Combine MapFlicer and ListFilcer to allow up to N maps to filter each field up to N times\n   this would be very useful with `like` in order to check that a field matches multiple patterns\n   e.g. `... FROM Person p WHERE p.firstName like 'j%' AND p.firstName like '%e' i.e. find\n   all people whose name starts with 'j' and ends with 'e'.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzio%2Fzio-protoquill","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fzio%2Fzio-protoquill","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzio%2Fzio-protoquill/lists"}