Skip to content

Commit

Permalink
Replace SheathLeafClauses with simpler SqlQuery-oriented transforms
Browse files Browse the repository at this point in the history
  • Loading branch information
deusaquilus committed Jul 11, 2024
1 parent 575d3d8 commit abb7e08
Show file tree
Hide file tree
Showing 5 changed files with 128 additions and 14 deletions.
37 changes: 35 additions & 2 deletions quill-engine/src/main/scala/io/getquill/sql/SqlQuery.scala
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,27 @@ import io.getquill.sql.Common.ContainsImpurities

final case class OrderByCriteria(ast: Ast, ordering: PropertyOrdering)

sealed trait FromContext { def quat: Quat }
sealed trait FromContext {
def quat: Quat
def mapAst(f: Ast => Ast): FromContext = this match {
case c: TableContext => c
case QueryContext(query, alias) => QueryContext(query.mapAst(f), alias)
case c: InfixContext => c.mapAsts(f)
case JoinContext(t, a, b, on) => JoinContext(t, a.mapAst(f), b.mapAst(f), f(on))
case FlatJoinContext(t, a, on) => FlatJoinContext(t, a.mapAst(f), f(on))
}
}
final case class TableContext(entity: Entity, alias: Ident) extends FromContext {
override def quat: Quat = entity.quat
}
final case class QueryContext(query: SqlQuery, alias: Ident) extends FromContext {
override def quat: Quat = query.quat
}
final case class InfixContext(infix: Infix, alias: Ident) extends FromContext { override def quat: Quat = infix.quat }
final case class InfixContext(infix: Infix, alias: Ident) extends FromContext {
override def quat: Quat = infix.quat
def mapAsts(f: Ast => Ast): InfixContext =
copy(infix = infix.copy(params = infix.params.map(f)))
}
final case class JoinContext(t: JoinType, a: FromContext, b: FromContext, on: Ast) extends FromContext {
override def quat: Quat = Quat.Tuple(a.quat, b.quat)
}
Expand All @@ -29,6 +42,16 @@ final case class FlatJoinContext(t: JoinType, a: FromContext, on: Ast) extends F
sealed trait SqlQuery {
def quat: Quat

def mapAst(f: Ast => Ast): SqlQuery =
this match {
case flatten: FlattenSqlQuery =>
flatten.mapAsts(f)
case SetOperationSqlQuery(a, op, b) =>
SetOperationSqlQuery(a.mapAst(f), op, b.mapAst(f))(quat)
case UnaryOperationSqlQuery(op, q) =>
UnaryOperationSqlQuery(op, q.mapAst(f))(quat)
}

override def toString: String = {
import io.getquill.MirrorSqlDialect._
import io.getquill.idiom.StatementInterpolator._
Expand Down Expand Up @@ -83,6 +106,16 @@ final case class FlattenSqlQuery(
)(quatType: Quat)
extends SqlQuery {
override def quat: Quat = quatType

def mapAsts(f: Ast => Ast): FlattenSqlQuery =
copy(
where = where.map(f),
groupBy = groupBy.map(f),
orderBy = orderBy.map(o => o.copy(ast = f(o.ast))),
limit = limit.map(f),
offset = offset.map(f),
select = select.map(s => s.copy(ast = f(s.ast)))
)(quatType)
}

object TakeDropFlatten {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import io.getquill.norm.ConcatBehavior.AnsiConcat
import io.getquill.norm.EqualityBehavior.AnsiEquality
import io.getquill.norm.{ConcatBehavior, EqualityBehavior, ExpandReturning, NormalizeCaching, ProductAggregationToken}
import io.getquill.quat.Quat
import io.getquill.sql.norm.{HideTopLevelFilterAlias, NormalizeFilteredActionAliases, RemoveExtraAlias, RemoveUnusedSelects}
import io.getquill.sql.norm.{HideTopLevelFilterAlias, NormalizeFilteredActionAliases, RemoveExtraAlias, RemoveUnusedSelects, ValueizeSingleLeafSelects}
import io.getquill.util.{Interleave, Interpolator, Messages, TraceConfig}
import io.getquill.util.Messages.{TraceType, fail, trace}

Expand Down Expand Up @@ -82,7 +82,9 @@ trait SqlIdiom extends Idiom {
val sql = querifyAst(q, idiomContext.traceConfig)
trace"SQL: ${sql}".andLog()
VerifySqlQuery(sql).map(fail)
val expanded = ExpandNestedQueries(sql, topLevelQuat)
val valueized = ValueizeSingleLeafSelects(naming)(sql, topLevelQuat)
trace"Valueized SQL: ${valueized}".andLog()
val expanded = ExpandNestedQueries(valueized, topLevelQuat)
trace"Expanded SQL: ${expanded}".andLog()
val refined = if (Messages.pruneColumns) RemoveUnusedSelects(expanded) else expanded
trace"Filtered SQL (only used selects): ${refined}".andLog()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,12 +77,15 @@ class ExpandDistinct(traceConfig: TraceConfig) {
// Problems with distinct were first discovered in #1032. Basically, unless
// the distinct is "expanded" adding an outer map, Ident's representing a Table will end up in invalid places
// such as "ORDER BY tableIdent" etc...
case Distinct(Map(q, x, p)) =>
val newMap = Map(q, x, Tuple(List(p)))
val newQuat = Quat.Tuple(valueQuat(p.quat)) // force quat recomputation for perf purposes
val newIdent = Ident(x.name, newQuat)
trace"ExpandDistinct Distinct(Map(other))" andReturn
Map(Distinct(newMap), newIdent, Property(newIdent, "_1"))

// TODO EXPERIMENTING WITH THIS CLAUSE, TRY TO DISABLE``

// case Distinct(Map(q, x, p)) =>
// val newMap = Map(q, x, Tuple(List(p)))
// val newQuat = Quat.Tuple(valueQuat(p.quat)) // force quat recomputation for perf purposes
// val newIdent = Ident(x.name, newQuat)
// trace"ExpandDistinct Distinct(Map(other))" andReturn
// Map(Distinct(newMap), newIdent, Property(newIdent, "_1"))
}
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,84 @@
package io.getquill.sql.norm

import io.getquill.NamingStrategy
import io.getquill.ast.{Property, Renameable}
import io.getquill.context.sql.{FlattenSqlQuery, SelectValue}
import io.getquill.ast.Ast.LeafQuat
import io.getquill.ast.{Ast, CollectAst, Ident, Property, Renameable}
import io.getquill.context.sql.{FlatJoinContext, FlattenSqlQuery, FromContext, InfixContext, JoinContext, QueryContext, SelectValue, TableContext}
import io.getquill.norm.{BetaReduction, TypeBehavior}
import io.getquill.quat.Quat

// If we run this right after SqlQuery we know that in every place with a single select-value it is a leaf clause e.g. `SELECT x FROM (SELECT p.name from Person p)) AS x`
// in that case we know that SelectValue(x) is a leaf clause that we should expand into a `x.value`.
// MAKE SURE THIS RUNS BEFORE ExpandNestedQueries otherwise it will be incorrect, it should only run for single-selects from atomic values,
// if the ExpandNestedQueries ran it could be a single field that is coming from a case class e.g. case class MySingleValue(stuff: Int) that is being selected from
case class ValueizeSingleLeafSelects(strategy: NamingStrategy) extends StatelessQueryTransformer {
protected def productize(ast: Ident) =
Ident(ast.name, Quat.Product("<Value>", "value" -> Quat.Value))

protected def valueize(ast: Ident) =
Property(productize(ast), "value")

// Turn every `SELECT primitive-x` into a `SELECT case-class-x.primitive-value`
override protected def expandNested(q: FlattenSqlQuery, level: QueryLevel): FlattenSqlQuery = {
// get the alises before we transform (i.e. Valueize) the contexts inside turning the leaf-quat alises into product-quat alises
val leafValuedFroms = collectAliases(q.from).filter(!_.quat.isProduct)
// now transform the inner clauses
val from = q.from.map(expandContext(_))

def containsAlias(ast: Ast): Boolean =
CollectAst.byType[Ident](ast).exists(id => leafValuedFroms.contains(id))

// If there is one single select clause that has a primitive (i.e. Leaf) quat then we can alias it to "value"
// This is the case of `SELECT primitive FROM (SELECT p.age from Person p) AS primitive`
// where we turn it into `SELECT p.name AS value FROM Person p`
def aliasSelects(selectValues: List[SelectValue]) =
selectValues match {
case List(sv @ SelectValue(LeafQuat(ast), _, _)) => List(sv.copy(alias = Some("value")))
case other => other
}

val valuizedQuery =
q.copy(from = from)(q.quat).mapAsts { ast =>
if (containsAlias(ast)) {
val reductions = CollectAst.byType[Ident](ast).filter(id => leafValuedFroms.contains(id)).map(id => id -> valueize(id))
BetaReduction(ast, TypeBehavior.ReplaceWithReduction, reductions: _*)
} else {
ast
}
}

valuizedQuery.copy(select = aliasSelects(valuizedQuery.select))(q.quat)
}

// Turn every `FROM primitive-x` into a `FROM case-class(x.primitive)`
override protected def expandContext(s: FromContext): FromContext =
super.expandContext(s) match {
case QueryContext(query, LeafQuat(id: Ident)) =>
QueryContext(query, productize(id))
case other =>
other
}

// protected def expandContext(s: FromContext): FromContext =
// s match {
// case QueryContext(q, alias) =>
// QueryContext(apply(q, QueryLevel.Inner), alias)
// case JoinContext(t, a, b, on) =>
// JoinContext(t, expandContext(a), expandContext(b), on)
// case FlatJoinContext(t, a, on) =>
// FlatJoinContext(t, expandContext(a), on)
// case _: TableContext | _: InfixContext => s
// }

private def collectAliases(contexts: List[FromContext]): List[Ident] =
contexts.flatMap {
case c: TableContext => List(c.alias)
case c: QueryContext => List(c.alias)
case c: InfixContext => List(c.alias)
case JoinContext(_, a, b, _) => collectAliases(List(a)) ++ collectAliases(List(b))
case FlatJoinContext(_, from, _) => collectAliases(List(from))
}
}

/**
* Remove aliases at the top level of the AST since they are not needed (quill
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,8 @@ class SqlNormalize(
.andThen(demarcate("ExpandJoin"))
.andThen(ExpandMappedInfix.apply _)
.andThen(demarcate("ExpandMappedInfix"))
.andThen(SheathLeafClausesPhase.apply _)
.andThen(demarcate("SheathLeaves"))
// .andThen(SheathLeafClausesPhase.apply _)
// .andThen(demarcate("SheathLeaves"))
.andThen { ast =>
// In the final stage of normalization, change all temporary aliases into
// shorter ones of the form x[0-9]+.
Expand Down

0 comments on commit abb7e08

Please sign in to comment.