Skip to content

Commit

Permalink
some ideas of how to make group by work with aggregate functions
Browse files Browse the repository at this point in the history
  • Loading branch information
sviezypan committed Jan 23, 2022
1 parent c051436 commit 360fe14
Show file tree
Hide file tree
Showing 12 changed files with 275 additions and 54 deletions.
3 changes: 3 additions & 0 deletions core/jvm/src/main/scala/zio/sql/Sql.scala
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ trait Sql extends SelectModule with DeleteModule with UpdateModule with ExprModu
def select[F, A, B <: SelectionSet[A]](selection: Selection[F, A, B]): SelectBuilder[F, A, B] =
SelectBuilder(selection)

def select[F, A, B <: SelectionSet[A]](selection: AggSelection[F, A, B]): AggSelectBuilder[F, A, B] =
AggSelectBuilder(selection)

def subselect[ParentTable]: SubselectPartiallyApplied[ParentTable] = new SubselectPartiallyApplied[ParentTable]

def subselectFrom[ParentTable, F, Source, B <: SelectionSet[Source]](
Expand Down
23 changes: 17 additions & 6 deletions core/jvm/src/main/scala/zio/sql/expr.scala
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,11 @@ trait ExprModule extends NewtypesModule with FeaturesModule with OpsModule {
def isNotTrue[A1 <: A](implicit ev: B <:< Boolean): Expr[F, A1, Boolean] =
Expr.Property(self, PropertyOp.IsNotTrue)

def as[B1 >: B](name: String): Selection[F, A, SelectionSet.Cons[A, B1, SelectionSet.Empty]] =
Selection.computedAs(self, name)
//TODO
def as[B1 >: B](name: String): Expr[F, A, B1] = {
val _ = name
self
}

def ascending: Ordering[Expr[F, A, B]] = Ordering.Asc(self)

Expand All @@ -105,7 +108,14 @@ trait ExprModule extends NewtypesModule with FeaturesModule with OpsModule {
}
}

object Expr {
trait ExprToSelectionLowerPrio {
implicit def expToSelection[F: Features.IsNotAggregated, A, B](
expr: Expr[F, A, B]
): Selection[F, A, SelectionSet.Cons[A, B, SelectionSet.Empty]] =
Selection.computedOption(expr, Expr.exprName(expr))
}

object Expr extends ExprToSelectionLowerPrio {
implicit val subqueryToExpr = self.Read.Subselect.subselectToExpr _

sealed trait InvariantExpr[F, -A, B] extends Expr[F, A, B] {
Expand All @@ -122,10 +132,10 @@ trait ExprModule extends NewtypesModule with FeaturesModule with OpsModule {
case _ => None
}

implicit def expToSelection[F, A, B](
implicit def aggregatedExprToSelection[F: Features.IsFullyAggregated, A, B](
expr: Expr[F, A, B]
): Selection[F, A, SelectionSet.Cons[A, B, SelectionSet.Empty]] =
Selection.computedOption(expr, exprName(expr))
): AggSelection[F, A, SelectionSet.Cons[A, B, SelectionSet.Empty]] =
AggSelection.computedOption(expr, exprName(expr))

sealed case class Subselect[F <: Features.Aggregated[_], Repr, Source, Subsource, Head](
subselect: Read.Subselect[F, Repr, _ <: Source, Subsource, Head, SelectionSet.Empty]
Expand Down Expand Up @@ -273,6 +283,7 @@ trait ExprModule extends NewtypesModule with FeaturesModule with OpsModule {
object AggregationDef {
val Count = AggregationDef[Any, Long](FunctionName("count"))
val Sum = AggregationDef[Double, Double](FunctionName("sum"))
//TODO what is Arbitrary??? it does not exists on postgresql
def Arbitrary[F, A, B: TypeTag](expr: Expr[F, A, B]) = AggregationDef[B, B](FunctionName("arbitrary"))(expr)
val Avg = AggregationDef[Double, Double](FunctionName("avg"))
def Min[F, A, B: TypeTag](expr: Expr[F, A, B]) = AggregationDef[B, B](FunctionName("min"))(expr)
Expand Down
51 changes: 41 additions & 10 deletions core/jvm/src/main/scala/zio/sql/features.scala
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
package zio.sql

import com.github.ghik.silencer.silent

import scala.annotation.implicitNotFound

trait FeaturesModule {

type :||:[A, B] = Features.Union[A, B]

object Features {
object Features extends PartialAggregationLowerPrio {
type Aggregated[_]
type Union[_, _]
type Source[_]
Expand All @@ -17,16 +15,33 @@ trait FeaturesModule {
type Literal
type Function0

sealed trait IsAggregated[A]
sealed trait IsNotAggregated[A]
object IsNotAggregated {
implicit def UnionIsNotAgregated[A: IsNotAggregated, B: IsNotAggregated]: IsNotAggregated[Union[A, B]] =
new IsNotAggregated[Union[A, B]] {}

implicit def SourceIsNotAggregated[A]: IsNotAggregated[Source[A]] =
new IsNotAggregated[Source[A]] {}

implicit val DerivedIsNotAggregated: IsNotAggregated[Derived] =
new IsNotAggregated[Derived] {}

implicit val LiteralIsNotAggregated: IsNotAggregated[Literal] =
new IsNotAggregated[Literal] {}

implicit val Function0IsNotAggregated: IsNotAggregated[Function0] =
new IsNotAggregated[Function0] {}
}

sealed trait IsFullyAggregated[A]

object IsAggregated {
def apply[A](implicit is: IsAggregated[A]): IsAggregated[A] = is
object IsFullyAggregated {
def apply[A](implicit is: IsFullyAggregated[A]): IsFullyAggregated[A] = is

implicit def AggregatedIsAggregated[A]: IsAggregated[Aggregated[A]] = new IsAggregated[Aggregated[A]] {}
implicit def AggregatedIsAggregated[A]: IsFullyAggregated[Aggregated[A]] = new IsFullyAggregated[Aggregated[A]] {}

@silent
implicit def UnionIsAggregated[A: IsAggregated, B: IsAggregated]: IsAggregated[Union[A, B]] =
new IsAggregated[Union[A, B]] {}
implicit def UnionIsAggregated[A: IsFullyAggregated, B: IsFullyAggregated]: IsFullyAggregated[Union[A, B]] =
new IsFullyAggregated[Union[A, B]] {}
}

@implicitNotFound("You can only use this function on a column in the source table")
Expand All @@ -37,4 +52,20 @@ trait FeaturesModule {
}
}

trait PartialAggregationLowerPrio {
sealed trait IsPartiallyAggregated[A]

object IsPartiallyAggregated {

def apply[A](implicit is: IsPartiallyAggregated[A]): IsPartiallyAggregated[A] = is

implicit def AggregatedIsAggregated[A]: IsPartiallyAggregated[Features.Aggregated[A]] = new IsPartiallyAggregated[Features.Aggregated[A]] {}

implicit def UnionIsAggregatedInB[A, B](implicit instB: IsPartiallyAggregated[B]): IsPartiallyAggregated[Features.Union[A, B]] =
new IsPartiallyAggregated[Features.Union[A, B]] {}

implicit def UnionIsAggregatedInA[A, B](implicit instB: IsPartiallyAggregated[A]): IsPartiallyAggregated[Features.Union[A, B]] =
new IsPartiallyAggregated[Features.Union[A, B]] {}
}
}
}
172 changes: 157 additions & 15 deletions core/jvm/src/main/scala/zio/sql/select.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,84 @@ import scala.language.implicitConversions

trait SelectModule { self: ExprModule with TableModule =>

sealed case class AggSelectBuilder[F0, Source, B <: SelectionSet[Source]](selection: AggSelection[F0, Source, B]){

def from[Source0 <: Source](table: Table.Aux[Source0])(implicit
ev: B <:< SelectionSet.Cons[Source0, selection.value.ColumnHead, selection.value.SelectionTail]
): AggSelectBuilderGroupBy[
F0,
selection.value.ResultTypeRepr,
Source0,
selection.value.ColumnHead,
selection.value.SelectionTail
] = {
type B0 = SelectionSet.ConsAux[
selection.value.ResultTypeRepr,
Source0,
selection.value.ColumnHead,
selection.value.SelectionTail
]
val b: B0 = selection.value.asInstanceOf[B0]

AggSelectBuilderGroupBy(Read.Subselect(Selection[F0, Source0, B0](b), Some(table), true, Nil))
}
}

sealed trait AggVerifier[MainF, F1]
object AggVerifier {

implicit def unionOf[MainF, F1, Remainder](
implicit
ev1: MainF <:< Features.Union[F1, Remainder],
ev2: Features.IsFullyAggregated[Remainder]
) : AggVerifier[MainF, F1] = new AggVerifier[MainF, F1] {}

implicit def unionOfB[MainF, F1, Remainder](
implicit
ev1: MainF <:< Features.Union[Remainder, F1],
ev2: Features.IsFullyAggregated[Remainder]
) : AggVerifier[MainF, F1] = new AggVerifier[MainF, F1] {}
}

// Features.Union[Features.Union[Features.Source[String("customer_id")], Features.Source[String("order_date")],]
// Features.Aggregated[Features.Source[String("id")]]]

// select customer_id, order_date, count(id)
// from orders
// group by customer_id


// Features.Source[String("customer_id")]
//Features.Source[String("order_date")]

// we require all the Exprs which are not aggregated from partially aggregated F and any other
// F here is always aggregated
sealed case class AggSelectBuilderGroupBy[F, Repr, Source, Head, Tail <: SelectionSet[Source]](
select: Read.Select[F, Repr, Source, Head, Tail]) {
import Read.ExprSet._

// format: off
def groupBy[F1, B](expr: Expr[F1, Source, B])(
implicit
ev1: Features.IsNotAggregated[F1],
ev2: AggVerifier[F, F1]
): Read.Select[F, Repr, Source, Head, Tail] =
select.copy(groupByExprs2 = NoExpr ++ expr)

def groupBy[F1, F2](expr: Expr[F1, Source, Any], expr2: Expr[F2, Source, Any]): Read.Select[F, Repr, Source, Head, Tail] =
select.copy(groupByExprs2 = NoExpr ++ expr ++ expr2)

def groupBy[F1, F2, F3](expr: Expr[F1, Source, Any], expr2: Expr[F2, Source, Any], expr3: Expr[F3, Source, Any]): Read.Select[F, Repr, Source, Head, Tail] =
select.copy(groupByExprs2 = NoExpr ++ expr ++ expr2 ++ expr3)

def groupBy[F1, F2, F3, F4](expr: Expr[F1, Source, Any], expr2: Expr[F2, Source, Any], expr3: Expr[F3, Source, Any], expr4: Expr[F4, Source, Any]): Read.Select[F, Repr, Source, Head, Tail] =
select.copy(groupByExprs2 = NoExpr ++ expr ++ expr2 ++ expr3 ++ expr4)

def groupBy[F1, F2, F3, F4, F5](expr: Expr[F1, Source, Any], expr2: Expr[F2, Source, Any], expr3: Expr[F3, Source, Any], expr4: Expr[F4, Source, Any], expr5: Expr[F5, Source, Any]): Read.Select[F, Repr, Source, Head, Tail] =
select.copy(groupByExprs2 = NoExpr ++ expr ++ expr2 ++ expr3 ++ expr4 ++ expr5)
// format: on
}

sealed case class SelectBuilder[F0, Source, B <: SelectionSet[Source]](selection: Selection[F0, Source, B]) {

def from[Source0 <: Source](table: Table.Aux[Source0])(implicit
Expand Down Expand Up @@ -58,6 +136,10 @@ trait SelectModule { self: ExprModule with TableModule =>
final class SubselectPartiallyApplied[ParentTable] {
def apply[F, A, B <: SelectionSet[A]](selection: Selection[F, A, B]): SubselectBuilder[F, A, B, ParentTable] =
SubselectBuilder(selection)

//TODO fix
def apply[F, A, B <: SelectionSet[A]](selection: AggSelection[F, A, B]): SubselectBuilder[F, A, B, ParentTable] =
??? //SubselectBuilder(selection)
}

sealed case class SubselectBuilder[F, Source, B <: SelectionSet[Source], ParentTable](
Expand Down Expand Up @@ -318,15 +400,36 @@ trait SelectModule { self: ExprModule with TableModule =>

type Select[F, Repr, Source, Head, Tail <: SelectionSet[Source]] = Subselect[F, Repr, Source, Source, Head, Tail]

sealed trait ExprSet[-Source] {
type Append[F2, Source1 <: Source, B2] <: ExprSet[Source1]
def ++[F2, Source1 <: Source, B2](that: Expr[F2, Source1, B2]): Append[F2, Source1, B2]
}

object ExprSet {
type NoExpr = NoExpr.type
case object NoExpr extends ExprSet[Any] {
override type Append[F2, Source1 <: Any, B2] = ExprCons[F2, Source1, B2, NoExpr]
override def ++[F2, Source1 <: Any, B2](that: Expr[F2, Source1, B2]): Append[F2, Source1, B2] = ExprCons(that, NoExpr)
}

sealed case class ExprCons[F, Source, B, T <: ExprSet[Source]](head: Expr[F, Source, B], tail: T) extends ExprSet[Source] {
override type Append[F2, Source1 <: Source, B2] =
ExprCons[F, Source1, B, tail.Append[F2, Source1, B2]]
override def ++[F2, Source1 <: Source, B2](that: Expr[F2, Source1, B2]): Append[F2, Source1, B2] =
ExprCons(head, tail.++[F2, Source1, B2](that))
}
}

sealed case class Subselect[F, Repr, Source, Subsource, Head, Tail <: SelectionSet[Source]](
selection: Selection[F, Source, SelectionSet.ConsAux[Repr, Source, Head, Tail]],
table: Option[Table.Aux[Subsource]],
whereExpr: Expr[_, Source, Boolean],
groupBy: List[Expr[_, Source, Any]] = Nil,
groupByExprs: List[Expr[_, Source, Any]] = Nil,
havingExpr: Expr[_, Source, Boolean] = true,
orderBy: List[Ordering[Expr[_, Source, Any]]] = Nil,
orderByExprs: List[Ordering[Expr[_, Source, Any]]] = Nil,
offset: Option[Long] = None, //todo don't know how to do this outside of postgres/mysql
limit: Option[Long] = None
limit: Option[Long] = None,
groupByExprs2: ExprSet[Source] = ExprSet.NoExpr
) extends Read[Repr] { self =>

def where(whereExpr2: Expr[_, Source, Boolean]): Subselect[F, Repr, Source, Subsource, Head, Tail] =
Expand All @@ -340,20 +443,27 @@ trait SelectModule { self: ExprModule with TableModule =>
o: Ordering[Expr[_, Source, Any]],
os: Ordering[Expr[_, Source, Any]]*
): Subselect[F, Repr, Source, Subsource, Head, Tail] =
copy(orderBy = self.orderBy ++ (o :: os.toList))

def groupBy(key: Expr[_, Source, Any], keys: Expr[_, Source, Any]*)(implicit
ev: Features.IsAggregated[F]
): Subselect[F, Repr, Source, Subsource, Head, Tail] = {
val _ = ev
copy(groupBy = groupBy ++ (key :: keys.toList))
copy(orderByExprs = self.orderByExprs ++ (o :: os.toList))

def having(havingExpr2: Expr[_, Source, Boolean])
// (implicit
// ev: Features.IsAggregated[F]
// )
: Subselect[F, Repr, Source, Subsource, Head, Tail] = {
//val _ = ev
copy(havingExpr = self.havingExpr && havingExpr2)
}

def having(havingExpr2: Expr[_, Source, Boolean])(implicit
ev: Features.IsAggregated[F]
): Subselect[F, Repr, Source, Subsource, Head, Tail] = {
val _ = ev
copy(havingExpr = self.havingExpr && havingExpr2)
/**
* what about this? xD
* select count(customer_id), count(id), '1' as "Hi"
from orders
group by "Hi"
*/
//TODO we can allow group by but only by Source.Expr, (at this point F is FullyAggregated)
// TODO found a way how to make Features.IsSource implicit for all Fs in varargs
def groupBy[X: Features.IsNotAggregated](key: Expr[X, Source, Any], keys: Expr[_, Source, Any]*): Subselect[F, Repr, Source, Subsource, Head, Tail] = {
copy(groupByExprs = groupByExprs ++ (key :: keys.toList))
}

override def asTable(
Expand Down Expand Up @@ -423,6 +533,29 @@ trait SelectModule { self: ExprModule with TableModule =>
def lit[B: TypeTag](values: B*): Read[(B, Unit)] = Literal(values.toSeq)
}

sealed case class AggSelection[F, -A, +B <: SelectionSet[A]](value: B) { self =>

type ColsRepr = value.ResultTypeRepr

def ++[F2, A1 <: A, C <: SelectionSet[A1]](
that: Selection[F2, A1, C]
): AggSelection[F :||: F2, A1, self.value.Append[A1, C]] =
AggSelection(self.value ++ that.value)

//TODO this is not correct by => age ++ Count(id) ++ Count(id)
def ++[F2, A1 <: A, C <: SelectionSet[A1]](
that: AggSelection[F2, A1, C]
)(implicit ev: Features.IsFullyAggregated[F]): Selection[F :||: F2, A1, self.value.Append[A1, C]] =
Selection(self.value ++ that.value)
}

object AggSelection {
import ColumnSelection._
import SelectionSet.{ Cons, Empty }
def computedOption[F, A, B](expr: Expr[F, A, B], name: Option[ColumnName]): AggSelection[F, A, Cons[A, B, Empty]] =
AggSelection(Cons(Computed(expr, name), Empty))
}

/**
* A columnar selection of `B` from a source `A`, modeled as `A => B`.
*/
Expand All @@ -435,6 +568,15 @@ trait SelectModule { self: ExprModule with TableModule =>
): Selection[F :||: F2, A1, self.value.Append[A1, C]] =
Selection(self.value ++ that.value)

def ++[F2, A1 <: A, C <: SelectionSet[A1]](
that: AggSelection[F2, A1, C]
): AggSelection[F :||: F2, A1, self.value.Append[A1, C]] =
AggSelection(self.value ++ that.value)

// (Arbitrary(age)) ++ (Count(1)) => Selection
// age ++ (Count(1)) => AggSelection
// (Count(1)) ++ age => AggSelection
// age ++ name => Selection
}

object Selection {
Expand Down
12 changes: 12 additions & 0 deletions examples/src/main/scala/Example1.scala
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,14 @@ object Example1 extends Sql {
.offset(1000)
.orderBy(age.descending)

val tt = ((age + 2) as "age")

val joined =
select((age as "age") ++ (age2 as "age2"))
.from(table.join(table2).on(name === name2))

val xx = (Arbitrary(age) as "age") ++ (Count(1) as "count")

val aggregated =
select((Arbitrary(age) as "age") ++ (Count(1) as "count"))
.from(table)
Expand All @@ -48,4 +52,12 @@ object Example1 extends Sql {
.set(age, age + 2)
.set(name, "foo")
.where(age > 100)

val orders = (uuid("id") ++ uuid("customer_id") ++ localDate("order_date")).table("orders")

val orderId :*: fkCustomerId :*: orderDate :*: _ = orders.columns

val query = select(fkCustomerId ++ Count(orderId))
.from(orders)
.groupBy(fkCustomerId)
}
2 changes: 1 addition & 1 deletion jdbc/src/main/scala/zio/sql/ConnectionPool.scala
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import zio.clock._
trait ConnectionPool {

/**
* Retrieves a JDBC [[java.sql.Connection]] as a [[zio.Managed]] resource.
* Retrieves a JDBC java.sql.Connection as a [[zio.Managed]] resource.
* The managed resource will safely acquire and release the connection, and
* may be interrupted or timed out if necessary.
*/
Expand Down
Loading

0 comments on commit 360fe14

Please sign in to comment.