From c47c559f1a4edd26e57553b4da418f7a17c75882 Mon Sep 17 00:00:00 2001 From: Alexander Ioffe Date: Wed, 3 Jul 2024 18:59:58 -0400 Subject: [PATCH] continue --- .../io/getquill/sql/idiom/SqlIdiom.scala | 6 +- .../io/getquill/sql/norm/ExpandDistinct.scala | 15 +++-- .../getquill/sql/norm/RemoveExtraAlias.scala | 67 ++++++++++++++++++- .../io/getquill/sql/norm/SqlNormalize.scala | 4 +- 4 files changed, 80 insertions(+), 12 deletions(-) diff --git a/quill-engine/src/main/scala/io/getquill/sql/idiom/SqlIdiom.scala b/quill-engine/src/main/scala/io/getquill/sql/idiom/SqlIdiom.scala index da8afd94c6..9eb9c13290 100644 --- a/quill-engine/src/main/scala/io/getquill/sql/idiom/SqlIdiom.scala +++ b/quill-engine/src/main/scala/io/getquill/sql/idiom/SqlIdiom.scala @@ -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} @@ -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() diff --git a/quill-engine/src/main/scala/io/getquill/sql/norm/ExpandDistinct.scala b/quill-engine/src/main/scala/io/getquill/sql/norm/ExpandDistinct.scala index 818c11bfd0..24b4b83402 100644 --- a/quill-engine/src/main/scala/io/getquill/sql/norm/ExpandDistinct.scala +++ b/quill-engine/src/main/scala/io/getquill/sql/norm/ExpandDistinct.scala @@ -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")) } } } diff --git a/quill-engine/src/main/scala/io/getquill/sql/norm/RemoveExtraAlias.scala b/quill-engine/src/main/scala/io/getquill/sql/norm/RemoveExtraAlias.scala index 95d4f4cfd0..28b086dd43 100644 --- a/quill-engine/src/main/scala/io/getquill/sql/norm/RemoveExtraAlias.scala +++ b/quill-engine/src/main/scala/io/getquill/sql/norm/RemoveExtraAlias.scala @@ -1,8 +1,71 @@ 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" -> 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 fromContextAliases = 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 => fromContextAliases.contains(id)) + + // if it is a leaf add leaf.value + val select = + q.select.map { + // TODO need to do this kind of replacement in Join-by clauses the aggregations etc... + case sv: SelectValue if containsAlias(sv.ast) => + val reductions = CollectAst.byType[Ident](sv.ast).map(id => id -> valueize(id)) + val newAst = BetaReduction(sv.ast, TypeBehavior.ReplaceWithReduction, reductions: _*) + val newSelectValue = SelectValue(newAst, sv.alias, sv.concat) + newSelectValue match { + case sv @ SelectValue(LeafQuat(ast), _, _) => sv.copy(alias = Some("value")) + case _ => newSelectValue + } + case sv @ SelectValue(LeafQuat(ast), _, _) => + sv.copy(alias = Some("value")) // TODO check if there is no alias already? Probably don't need to since aliasing only really happens in ExpandNestedQueries + case sv => sv + + } + q.copy(select = select, from = from)(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 + } + + 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 diff --git a/quill-engine/src/main/scala/io/getquill/sql/norm/SqlNormalize.scala b/quill-engine/src/main/scala/io/getquill/sql/norm/SqlNormalize.scala index 9d1622f686..529f14b715 100644 --- a/quill-engine/src/main/scala/io/getquill/sql/norm/SqlNormalize.scala +++ b/quill-engine/src/main/scala/io/getquill/sql/norm/SqlNormalize.scala @@ -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]+.