Skip to content

Commit

Permalink
Single path-element alias in top-level selects
Browse files Browse the repository at this point in the history
  • Loading branch information
deusaquilus committed Jan 21, 2022
1 parent a43964a commit 5d149ba
Show file tree
Hide file tree
Showing 27 changed files with 492 additions and 295 deletions.
4 changes: 2 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 @@ -167,7 +167,7 @@ object SqlQuery {
case Map(GroupBy(q, x @ Ident(alias, _), g), a, p) =>
val b = base(q, alias)
//use ExpandSelection logic to break down OrderBy clause
val flatGroupByAsts = new ExpandSelection(b.from).apply(List(SelectValue(g))).map(_.ast)
val flatGroupByAsts = new ExpandSelection(b.from, false).apply(List(SelectValue(g))).map(_.ast)
val groupByClause =
if (flatGroupByAsts.length > 1) Tuple(flatGroupByAsts)
else flatGroupByAsts.head
Expand Down Expand Up @@ -281,7 +281,7 @@ object SqlQuery {
case (Tuple(properties), ord: PropertyOrdering) => properties.flatMap(orderByCriterias(_, ord, from))
case (Tuple(properties), TupleOrdering(ord)) => properties.zip(ord).flatMap { case (a, o) => orderByCriterias(a, o, from) }
//if its a quat product, use ExpandSelection to break it down into its component fields and apply the ordering to all of them
case (id @ Ident(_, _: Quat.Product), ord) => new ExpandSelection(from).apply(List(SelectValue(ast))).map(_.ast).flatMap(orderByCriterias(_, ord, from))
case (id @ Ident(_, _: Quat.Product), ord) => new ExpandSelection(from, false).apply(List(SelectValue(ast))).map(_.ast).flatMap(orderByCriterias(_, ord, from))
case (a, o: PropertyOrdering) => List(OrderByCriteria(a, o))
case other => fail(s"Invalid order by criteria $ast")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ 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.RemoveExtraAlias.TopLevelRemove
import io.getquill.sql.norm.{ RemoveExtraAlias, RemoveUnusedSelects }
import io.getquill.util.{ Interleave, Messages }
import io.getquill.util.Messages.{ fail, trace }
Expand Down Expand Up @@ -233,10 +232,19 @@ trait SqlIdiom extends Idiom {

def tokenizer(implicit astTokenizer: Tokenizer[Ast]) =
Tokenizer[SelectValue] {

// ExpandNestedQuery should elaborate all Idents with Product quats so the only ones left are value quats
// treat them as a id.* because in most SQL dialects identifiers cannot be spliced in by themselves
case SelectValue(Ident("?", Quat.Value), _, _) => "?".token
case SelectValue(Ident(name, Quat.Value), _, _) => stmt"${strategy.default(name).token}.*"

// Typically these next two will be for Ast Property
case SelectValue(ast, Some(alias), false) => {
stmt"${ast.token} AS ${tokenizeColumnAlias(strategy, alias).token}"
}
case SelectValue(ast, Some(alias), true) => stmt"${concatFunction.token}(${ast.token}) AS ${tokenizeColumnAlias(strategy, alias).token}"

// For situations where this is no alias etc...
case selectValue =>
val value =
selectValue match {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import io.getquill.sql.norm.{ InContext, SelectPropertyProtractor, StatelessQuer
import io.getquill.ast.PropertyOrCore
import io.getquill.norm.PropertyMatroshka

class ExpandSelection(from: List[FromContext]) {
class ExpandSelection(from: List[FromContext], isTopLevel: Boolean) {

def apply(values: List[SelectValue]): List[SelectValue] =
values.flatMap(apply(_))
Expand All @@ -17,6 +17,12 @@ class ExpandSelection(from: List[FromContext]) {
}

private def apply(value: SelectValue): List[SelectValue] = {
def concatOr(concatA: Option[String], concatB: String)(or: Option[String]) =
if (!isTopLevel)
concatA.concatWith(concatB)
else
or

value match {
// Assuming there's no case class or tuple buried inside or a property i.e. if there were,
// the beta reduction would have unrolled them already
Expand All @@ -36,20 +42,20 @@ class ExpandSelection(from: List[FromContext]) {
case (p: Property, path) =>
// Append alias headers (i.e. _1,_2 from tuples and field names foo,bar from case classes) to the
// value of the Quat path
SelectValue(p, alias.concatWith(path.mkString), concat)
SelectValue(p, concatOr(alias, path.mkString)(path.lastOption), concat)
case (other, _) =>
SelectValue(other, alias, concat)
}
case SelectValue(Tuple(values), alias, concat) =>
values.zipWithIndex.flatMap {
case (ast, i) =>
apply(SelectValue(ast, alias.concatWith(s"_${i + 1}"), concat))
val label = s"_${i + 1}"
apply(SelectValue(ast, concatOr(alias, label)(Some(label)), concat))
}
case SelectValue(CaseClass(fields), alias, concat) =>
fields.flatMap {
case (name, ast) =>
val concated = SelectValue(ast, alias.concatWith(name), concat)
apply(SelectValue(ast, alias.concatWith(name), concat))
apply(SelectValue(ast, concatOr(alias, name)(Some(name)), concat))
}
// Direct infix select, etc...
case other => List(other)
Expand All @@ -62,7 +68,7 @@ object ExpandNestedQueries extends StatelessQueryTransformer {
protected override def apply(q: SqlQuery, isTopLevel: Boolean = false): SqlQuery =
q match {
case q: FlattenSqlQuery =>
val selection = new ExpandSelection(q.from)(q.select)
val selection = new ExpandSelection(q.from, isTopLevel)(q.select)
val out = expandNested(q.copy(select = selection)(q.quat), isTopLevel)
out
case other =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,14 @@ 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.sql.norm.RemoveExtraAlias.TopLevelRemove

object RemoveExtraAlias {
sealed trait TopLevelRemove
case object TopLevelRemove {
/**
* Remove all top-level aliases. This is for top-level queries where aliases are not needed at all
* since Quill extractors use the position value instead
*/
case object All extends TopLevelRemove
/** Remove only aliases on the top level that match the column name (same as rest of the query) */
case object OnlyMatching extends TopLevelRemove
}
}

/**
* Remove aliases at the top level of the AST since they are not needed
* (quill uses select row indexes to figure out what data corresponds to what encodeable object)
* as well as entities whose aliases are the same as their selection e.g. "select x.foo as foo"
* since this just adds syntactic noise.
*/
case class RemoveExtraAlias(strategy: NamingStrategy, topLevel: TopLevelRemove = TopLevelRemove.All) extends StatelessQueryTransformer {
case class RemoveExtraAlias(strategy: NamingStrategy) extends StatelessQueryTransformer {
// Remove aliases that are the same as as the select values. Since a strategy may change the name,
// use a heuristic where if the column naming strategy make the property name be different from the
// alias, keep the column property name.
Expand All @@ -51,16 +37,7 @@ case class RemoveExtraAlias(strategy: NamingStrategy, topLevel: TopLevelRemove =

override protected def expandNested(q: FlattenSqlQuery, isTopLevel: Boolean): FlattenSqlQuery = {
val from = q.from.map(expandContext(_))
val select =
q.select
.map(removeUnneededAlias(_))
.map { sv =>
// If we are on the top-level query and instructed to remove all the aliases inside then do that
if (isTopLevel && topLevel == TopLevelRemove.All)
sv.copy(alias = None)
else
sv
}
val select = q.select.map(removeUnneededAlias(_))
q.copy(select = select, from = from)(q.quat)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -255,15 +255,15 @@ class OrientDBIdiomSpec extends Spec {
qr1.map(t => (t.i, t.s))
}
ctx.run(q).string mustEqual
"SELECT i, s FROM TestEntity"
"SELECT i _1, s _2 FROM TestEntity"
}
"caseclass" in {
case class IntString(intProp: Int, stringProp: String)
val q = quote {
qr1.map(t => new IntString(t.i, t.s))
}
ctx.run(q).string mustEqual
"SELECT i, s FROM TestEntity"
"SELECT i intProp, s stringProp FROM TestEntity"
}
"null" in {
val q = quote {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ class OrientDBQuerySpec extends Spec {
qr1.map(t => (t.i, t.s))
}
mirrorContext.run(q).string mustEqual
"SELECT i, s FROM TestEntity"
"SELECT i _1, s _2 FROM TestEntity"
}
"other" in {
val q = quote {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import io.getquill.NamingStrategy
import io.getquill.ast.{ Ast, BinaryOperation, CaseClass, Constant, ExternalIdent, Ident, Operation, Property, Query, StringOperator, Tuple, Value }
import io.getquill.context.spark.norm.EscapeQuestionMarks
import io.getquill.context.sql.{ FlattenSqlQuery, SelectValue, SetOperationSqlQuery, SqlQuery, UnaryOperationSqlQuery }
import io.getquill.context.sql.idiom.SqlIdiom
import io.getquill.context.sql.idiom.{ SqlIdiom }
import io.getquill.context.sql.norm.SqlNormalize
import io.getquill.idiom.StatementInterpolator._
import io.getquill.idiom.Token
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ class EmbeddedSpec extends Spec {
val q = quote {
query[Emb].map(e => Parent(1, e)).distinct
}
ctx.run(q).string mustEqual "SELECT DISTINCT 1, e.a, e.b FROM Emb e"
ctx.run(q).string mustEqual "SELECT DISTINCT 1 AS id, e.a, e.b FROM Emb e"
}

"function property inside of nested distinct queries - tuple" in {
Expand All @@ -23,7 +23,7 @@ class EmbeddedSpec extends Spec {
val q = quote {
query[Emb].map(e => Parent(1, e)).distinct.map(p => (2, p)).distinct
}
ctx.run(q).string mustEqual "SELECT DISTINCT 2, e.id, e.emb1a, e.emb1b FROM (SELECT DISTINCT 1 AS id, e.a AS emb1a, e.b AS emb1b FROM Emb e) AS e"
ctx.run(q).string mustEqual "SELECT DISTINCT 2 AS _1, e.id, e.emb1a AS a, e.emb1b AS b FROM (SELECT DISTINCT 1 AS id, e.a AS emb1a, e.b AS emb1b FROM Emb e) AS e"
}

"function property inside of nested distinct queries through tuples" in {
Expand All @@ -32,7 +32,7 @@ class EmbeddedSpec extends Spec {
val q = quote {
query[Emb].map(e => (1, e)).distinct.map(t => Parent(t._1, t._2)).distinct
}
ctx.run(q).string mustEqual "SELECT DISTINCT e._1, e._2a, e._2b FROM (SELECT DISTINCT 1 AS _1, e.a AS _2a, e.b AS _2b FROM Emb e) AS e"
ctx.run(q).string mustEqual "SELECT DISTINCT e._1 AS id, e._2a AS a, e._2b AS b FROM (SELECT DISTINCT 1 AS _1, e.a AS _2a, e.b AS _2b FROM Emb e) AS e"
}

"function property inside of nested distinct queries - twice" in {
Expand All @@ -42,7 +42,7 @@ class EmbeddedSpec extends Spec {
val q = quote {
query[Emb].map(e => Parent(1, e)).distinct.map(p => Grandparent(2, p)).distinct
}
ctx.run(q).string mustEqual "SELECT DISTINCT 2, e.idP, e.emb1a, e.emb1b FROM (SELECT DISTINCT 1 AS idP, e.a AS emb1a, e.b AS emb1b FROM Emb e) AS e"
ctx.run(q).string mustEqual "SELECT DISTINCT 2 AS idG, e.idP, e.emb1a AS a, e.emb1b AS b FROM (SELECT DISTINCT 1 AS idP, e.a AS emb1a, e.b AS emb1b FROM Emb e) AS e"
}

"function property inside of nested distinct queries - twice - into tuple" in {
Expand All @@ -52,7 +52,7 @@ class EmbeddedSpec extends Spec {
val q = quote {
query[Emb].map(e => Parent(1, e)).distinct.map(p => Grandparent(2, p)).distinct.map(g => (3, g)).distinct
}
ctx.run(q).string mustEqual "SELECT DISTINCT 3, p.idG, p.paridP, p.paremb1a, p.paremb1b FROM (SELECT DISTINCT 2 AS idG, p.idP AS paridP, p.emb1a AS paremb1a, p.emb1b AS paremb1b FROM (SELECT DISTINCT 1 AS idP, e.a AS emb1a, e.b AS emb1b FROM Emb e) AS p) AS p"
ctx.run(q).string mustEqual "SELECT DISTINCT 3 AS _1, p.idG, p.paridP AS idP, p.paremb1a AS a, p.paremb1b AS b FROM (SELECT DISTINCT 2 AS idG, p.idP AS paridP, p.emb1a AS paremb1a, p.emb1b AS paremb1b FROM (SELECT DISTINCT 1 AS idP, e.a AS emb1a, e.b AS emb1b FROM Emb e) AS p) AS p"
}
}

Expand Down
34 changes: 17 additions & 17 deletions quill-sql/src/test/scala/io/getquill/context/sql/GroupBySpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ class GroupBySpec extends Spec {
.map { case (country, citiesInCountry) => (country, citiesInCountry.size) }
)
testContext.run(q.dynamic).string mustEqual
"SELECT x11.name, COUNT(x01.*) FROM City x01 INNER JOIN Country x11 ON x01.countryId = x11.id GROUP BY x11.id, x11.name"
"SELECT x11.name AS _1, COUNT(x01.*) AS _2 FROM City x01 INNER JOIN Country x11 ON x01.countryId = x11.id GROUP BY x11.id, x11.name"
}
"with QuerySchema" in {
implicit val citySchema = schemaMeta[City]("theCity", _.name -> "theCityName")
Expand All @@ -36,7 +36,7 @@ class GroupBySpec extends Spec {
.map { case (country, citiesInCountry) => (country, citiesInCountry.size) }
)
testContext.run(q.dynamic).string mustEqual
"SELECT x12.theCountryName, COUNT(x05.*) FROM theCity x05 INNER JOIN theCountry x12 ON x05.countryId = x12.id GROUP BY x12.id, x12.theCountryName"
"SELECT x12.theCountryName AS _1, COUNT(x05.*) AS _2 FROM theCity x05 INNER JOIN theCountry x12 ON x05.countryId = x12.id GROUP BY x12.id, x12.theCountryName"
}
"nested" in {
val q = quote(
Expand All @@ -48,7 +48,7 @@ class GroupBySpec extends Spec {
.map { case (country, citiesInCountry) => (country, citiesInCountry.size) }
)
testContext.run(q.dynamic).string mustEqual
"SELECT x010._2id, x010._2name, COUNT(x010.*) FROM (SELECT x13.id AS _2id, x13.name AS _2name FROM City x09 INNER JOIN Country x13 ON x09.countryId = x13.id) AS x010 GROUP BY x010._2id, x010._2name"
"SELECT x010._2id AS id, x010._2name AS name, COUNT(x010.*) AS _2 FROM (SELECT x13.id AS _2id, x13.name AS _2name FROM City x09 INNER JOIN Country x13 ON x09.countryId = x13.id) AS x010 GROUP BY x010._2id, x010._2name"
}
"with QuerySchema nested" in {
implicit val citySchema = schemaMeta[City]("theCity", _.name -> "theCityName")
Expand All @@ -62,7 +62,7 @@ class GroupBySpec extends Spec {
.map { case (country, citiesInCountry) => (country, citiesInCountry.size) }
)
testContext.run(q.dynamic).string mustEqual
"SELECT x013._2id, x013._2theCountryName, COUNT(x013.*) FROM (SELECT x14.id AS _2id, x14.theCountryName AS _2theCountryName FROM theCity x012 INNER JOIN theCountry x14 ON x012.countryId = x14.id) AS x013 GROUP BY x013._2id, x013._2theCountryName"
"SELECT x013._2id AS id, x013._2theCountryName AS theCountryName, COUNT(x013.*) AS _2 FROM (SELECT x14.id AS _2id, x14.theCountryName AS _2theCountryName FROM theCity x012 INNER JOIN theCountry x14 ON x012.countryId = x14.id) AS x013 GROUP BY x013._2id, x013._2theCountryName"
}

}
Expand All @@ -84,10 +84,10 @@ class GroupBySpec extends Spec {
testContext.run(q).string.collapseSpace mustEqual
"""
|SELECT
| x15.countryCode,
| x15.countryCode AS _1,
| x15.name,
| x15.dialect,
| COUNT(x015.*)
| COUNT(x015.*) AS _2
|FROM
| City x015
| INNER JOIN Country x15 ON x015.countryCode = x15.countryCode
Expand All @@ -109,10 +109,10 @@ class GroupBySpec extends Spec {
testContext.run(q).string(true).collapseSpace mustEqual
"""
|SELECT
| x020._2countryCode,
| x020._2languagename,
| x020._2languagedialect,
| COUNT(x020.*)
| x020._2countryCode AS countryCode,
| x020._2languagename AS name,
| x020._2languagedialect AS dialect,
| COUNT(x020.*) AS _2
|FROM
| (
| SELECT
Expand Down Expand Up @@ -143,10 +143,10 @@ class GroupBySpec extends Spec {
)
testContext.run(q).string(true).collapseSpace mustEqual
"""|SELECT
| x17.theCountryCode,
| x17.theCountryCode AS _1,
| x17.TheLanguageName,
| x17.dialect,
| COUNT(x022.*)
| COUNT(x022.*) AS _2
|FROM
| City x022
| INNER JOIN theCountry x17 ON x022.countryCode = x17.theCountryCode
Expand All @@ -171,10 +171,10 @@ class GroupBySpec extends Spec {
testContext.run(q).string(true).collapseSpace mustEqual
"""
|SELECT
| x027._2theCountryCode,
| x027._2languageTheLanguageName,
| x027._2languagedialect,
| COUNT(x027.*)
| x027._2theCountryCode AS theCountryCode,
| x027._2languageTheLanguageName AS TheLanguageName,
| x027._2languagedialect AS dialect,
| COUNT(x027.*) AS _2
|FROM
| (
| SELECT
Expand Down Expand Up @@ -205,7 +205,7 @@ class GroupBySpec extends Spec {
.map { case (language, cityLanguages) => (language, cityLanguages.size) }
)
testContext.run(q.dynamic).string mustEqual
"SELECT x19.countryCode, x19.language, COUNT(*) FROM City x029 INNER JOIN CountryLanguage x19 ON x029.countryCode = x19.countryCode GROUP BY x19.countryCode, x19.language"
"SELECT x19.countryCode, x19.language, COUNT(*) AS _2 FROM City x029 INNER JOIN CountryLanguage x19 ON x029.countryCode = x19.countryCode GROUP BY x19.countryCode, x19.language"
}

}
Loading

0 comments on commit 5d149ba

Please sign in to comment.