-
Notifications
You must be signed in to change notification settings - Fork 1
OXY-166: Add scalar aggregate functions (SUM / AVG / MIN / MAX) to oxygen-sql #307
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,57 @@ | ||
| package oxygen.sql.query.dsl | ||
|
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Im ok with multiple classes in the same file in certain cases. This one feels valid enough, fine. If there are multiple classes in the same file, the file should have a lower-case name. The exception to this is Fix this file, and add that sentiment to the claude markdown scaffold. |
||
|
|
||
| /** | ||
| * Type-level widening for the `SUM(_)` aggregate, mirroring Postgres' result types. | ||
| * | ||
| * Postgres widens the result of `SUM`: | ||
| * - `smallint` / `int` -> `bigint` (Scala `Long`) | ||
| * - `bigint` / `numeric` -> `numeric` (Scala `BigDecimal`) | ||
| * - `real` -> `real` (Scala `Float`) | ||
| * - `double precision` -> `double precision` (Scala `Double`) | ||
| * | ||
| * The resulting DSL expression decodes to `Option[Out]` (SQL `NULL` over an empty set -> `None`). | ||
| */ | ||
| sealed trait SumType[A] { | ||
| type Out | ||
| } | ||
| object SumType { | ||
|
|
||
| type Aux[A, B] = SumType[A] { type Out = B } | ||
|
|
||
| private def make[A, B]: SumType.Aux[A, B] = new SumType[A] { override type Out = B } | ||
|
|
||
| given short: SumType.Aux[Short, Long] = make | ||
| given int: SumType.Aux[Int, Long] = make | ||
| given long: SumType.Aux[Long, BigDecimal] = make | ||
| given float: SumType.Aux[Float, Float] = make | ||
| given double: SumType.Aux[Double, Double] = make | ||
| given bigDecimal: SumType.Aux[BigDecimal, BigDecimal] = make | ||
|
|
||
| } | ||
|
|
||
| /** | ||
| * Type-level widening for the `AVG(_)` aggregate, mirroring Postgres' result types. | ||
| * | ||
| * Postgres returns: | ||
| * - `numeric` for `smallint` / `int` / `bigint` / `numeric` inputs (Scala `BigDecimal`) | ||
| * - `double precision` for `real` / `double precision` inputs (Scala `Double`) | ||
| * | ||
| * The resulting DSL expression decodes to `Option[Out]` (SQL `NULL` over an empty set -> `None`). | ||
| */ | ||
| sealed trait AvgType[A] { | ||
| type Out | ||
| } | ||
| object AvgType { | ||
|
|
||
| type Aux[A, B] = AvgType[A] { type Out = B } | ||
|
|
||
| private def make[A, B]: AvgType.Aux[A, B] = new AvgType[A] { override type Out = B } | ||
|
|
||
| given short: AvgType.Aux[Short, BigDecimal] = make | ||
| given int: AvgType.Aux[Int, BigDecimal] = make | ||
| given long: AvgType.Aux[Long, BigDecimal] = make | ||
| given float: AvgType.Aux[Float, Double] = make | ||
| given double: AvgType.Aux[Double, Double] = make | ||
| given bigDecimal: AvgType.Aux[BigDecimal, BigDecimal] = make | ||
|
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -54,6 +54,25 @@ object Q { | |
| def _1: Long = macroOnly | ||
| } | ||
|
|
||
| // scalar aggregates over the whole result (no GROUP BY). | ||
| // all return `Option`: over an empty result set the aggregate is SQL NULL -> `None`. | ||
|
|
||
| object sum { | ||
| def apply[A, B](toSum: A)(using ev: SumType.Aux[A, B]): Option[B] = macroOnly | ||
| } | ||
|
|
||
| object avg { | ||
| def apply[A, B](toAvg: A)(using ev: AvgType.Aux[A, B]): Option[B] = macroOnly | ||
| } | ||
|
Comment on lines
+60
to
+66
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. do these still work if you do it like: def apply[A](toSum: A)(using ev: SumType[A]): Option[ev.Out] = macroOnlythats kinda the whole point of the |
||
|
|
||
| object min { | ||
| def apply[A](toMin: A): Option[A] = macroOnly | ||
| } | ||
|
|
||
| object max { | ||
| def apply[A](toMax: A): Option[A] = macroOnly | ||
| } | ||
|
Comment on lines
+68
to
+74
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. also, all 4 of these dont really feel like they deserve an |
||
|
|
||
| extension [A](self: A) { | ||
| def tablePK(using ev: TableRepr[A]): ev.PrimaryKeyT = ev.pk.get(self) | ||
| def tableNPK(using ev: TableRepr[A]): ev.NonPrimaryKeyT = ev.npk.get(self) | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is the only one Im not OK with:
sum(Long)->BigInt