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.
- make the infix behave as the aggregation
- 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