zio / zio-quill

Compile-time Language Integrated Queries for Scala

Home Page:https://zio.dev/zio-quill

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

custom aggregations in correlated subqueries

shagoon opened this issue · comments

My issue initiated when trying to perform some json-aggregation through correlated subqueries. I wrote a couple of messages about that to the discord channel.

To track down the problem, I decided to try to write an infix-query, that does the same as the built-in size/count aggregation. The latest scastie that shows that and the initial problem I had, is available at https://scastie.scala-lang.org/tKSoFbB6QBqYqdaHVz7BSg.

However, I further investigated the problem. In the scastie above, I used two nested infix-queries to get the parentheses around correlated subqueries. I think, this should not have been required at all. I.e., instead of having the nested version

implicit class MyQueryOps[T](q: Query[T]) {
  def mySize = quote(infix"(${q.map(t => infix"count($t)".pure.as[Long])})".pure.as[Long])
}

the plain version

implicit class MyQueryOps[T](q: Query[T]) {
  def mySize = quote(q.map(x => infix"COUNT($x.*)".pure.as[Long]).value.getOrNull)
}

should behave the same as the built-in size/count.

However, it doesn't. https://scastie.scala-lang.org/ERJrGZlZRZCPHmumPARr5Q shows that. While size automagically becomes a correlated subquery, mySize doesn't. I think, it's not obvious, that the two queries behave that different, especially given, that they share the same signature.

I think there are two possible solutions to that.

  1. make the infix behave as the aggregation
  2. provide a way to lift an infix into an aggregation

As it was easier for me to implement 2.), I added def aggregate[R](f: T => R): R in trait Query[+T] in phenetic/zio-quill@7b8765d. This allows to rewrite the correlated subquery like this:

object Showcase2 extends App {

  val ctx = new SqlMirrorContext(PostgresDialect, SnakeCase)

  import ctx._

  case class Person(id: Long, name: String)
  case class Follower(fellow: Long, follower: Long)

  val q = ctx.run {
    for {
      p <- query[Person]
    } yield (p, query[Follower].aggregate(p => unquote(p.mySize)))
  }
  println(q.string)

  implicit class MyQueryOps[T](t: T) {
    def mySize = quote(infix"COUNT($t.*)".pure.as[Long])
  }

}

This also solves my initial problems (io.getquill.quotation.QuatException: The post-rename field 'xxx' does not exist in an SQL-level type when using joins in more than one correlated subquery).

However, I'm not quite happy with it. Signature probably should be def aggregate[R](f: Query[T] => R): R instead. AggregationOperator.`custom` feels clunky, especially case AggregationOperator.`custom` => stmt"". And I'm wondering, whether the new method is required at all, or the existing .value could do just that?

What do you think?

@getquill/maintainers