Skip to content

Commit

Permalink
further gymnastics with subqueries
Browse files Browse the repository at this point in the history
This should complete the work around subqueries yielding full tables.
Prior commit (9fb3f83) did not properly
handle outer where/group by/order by clauses.

This commit adds a new `maybeNamed: Option[NamedColumn[_]]` to
`SubqueryColumn`, which is used to match column operations (equality,
aggregation, etc.) to target subquery table/column -- e.g. "where t1.c1 =
t1.c2" instead of erroneous "where t2.id = t3.id" (since table aliases
t2 & t3 don't exist, all subquery columns are referenced under a single
subquery table alias tN).
  • Loading branch information
godenji committed Dec 22, 2022
1 parent d8bd865 commit 37e81ba
Show file tree
Hide file tree
Showing 4 changed files with 33 additions and 16 deletions.
18 changes: 11 additions & 7 deletions src/main/scala/slaq/scalaquery/ql/Subquery.scala
Expand Up @@ -11,8 +11,12 @@ case class Subquery
override def isNamedTable = true
}

case class SubqueryColumn(pos: Int, subquery: Subquery, typeMapper: TypeMapper[_])
extends Node {
case class SubqueryColumn(
pos: Int,
subquery: Subquery,
typeMapper: TypeMapper[_],
maybeNamed: Option[NamedColumn[_]]
) extends Node {

def nodeChildren = subquery :: Nil
override def nodeNamedChildren = (subquery, "subquery") :: Nil
Expand Down Expand Up @@ -58,22 +62,22 @@ object Subquery {
case t: Table[_] =>
val xs = t.*.nodeChildren.collect {
case pn: ProductNode =>
pn.nodeChildren.zipWithIndex.map((n2, i) =>
pn.nodeChildren.zipWithIndex.collect { case (n2: NamedColumn[_], i) =>
if i != 0 then pos += 1
SubqueryColumn(pos, p, mapper(n2))
)
SubqueryColumn(pos, p, mapper(n2), Some(n2))
}
}.flatten
t.mapOp(_ => SubqueryTable(xs))
case _ =>
SubqueryColumn(pos, p, mapper(n))
SubqueryColumn(pos, p, mapper(n), None)
}
}, unpackable.unpack
)
}

private def mapper(n: Node) = n match {
case c: Column[_] => c.typeMapper
case SubqueryColumn(_, _, tm) => tm
case SubqueryColumn(_, _, tm, _) => tm
case x => Fail(s"""
Expected Column, SubqueryColumn, or Table but got $x -- maybe you tried to yield `t.*`?
See UnionTest.scala for detailed example of union queries with table projections.
Expand Down
20 changes: 13 additions & 7 deletions src/main/scala/slaq/scalaquery/ql/core/FromBuilder.scala
Expand Up @@ -34,13 +34,19 @@ trait FromBuilder { self: QueryBuilder with QueryBuilderAction =>
if (isFirst) fromSlot += " FROM "
tr.tableJoin.map(createJoin(_, isFirst, numAliases)(using fromSlot)).
getOrElse {
if (!isFirst) {
tr.table match
case Subquery(_, _, Some(joinType)) =>
fromSlot += s" ${joinType.sqlName} JOIN LATERAL "
case _ => fromSlot += ','
}
tableLabel(tr.table, alias)(using fromSlot)
tr.table match
// no-op if referencing subquery column in outer expression
// i.e. all subquery columns reference a single table `tN`
// (e.g. "select t1.c1, t1.c2, ... from (...) t1")
case NamedColumn(n, _, _) if n.isInstanceOf[SubqueryTable] => ()
case t =>
if (!isFirst) {
t match
case Subquery(_, _, Some(joinType)) =>
fromSlot += s" ${joinType.sqlName} JOIN LATERAL "
case _ => fromSlot += ','
}
tableLabel(t, alias)(using fromSlot)
}
declaredTables += alias
}
Expand Down
9 changes: 8 additions & 1 deletion src/main/scala/slaq/scalaquery/ql/core/QueryBuilder.scala
Expand Up @@ -106,7 +106,7 @@ abstract class QueryBuilder(
show(x, b)
if i != xs.size -1 then b += ','
)
case SubqueryColumn(pos, q, _) => b += s"${quote(tableAlias(q))}.${quote(s"c$pos")}"
case SubqueryColumn(pos, q, _, _) => b += s"${quote(tableAlias(q))}.${quote(s"c$pos")}"
case SimpleLiteral(w) => b += w
case _ =>
Fail(s"Unmatched node `$c` in show block")
Expand Down Expand Up @@ -176,6 +176,13 @@ abstract class QueryBuilder(
* Column show
*/
private final def show[T](c: Column[T], b: SqlBuilder): Unit = c match {
case n @ NamedColumn(t: SubqueryTable, name, _) =>
t.cols
.collect {
case SubqueryColumn(pos, subquery, _, Some(col)) if col.name == name =>
s"${quote(tableAlias(subquery))}.${quote(s"c$pos")}"
}
.foreach(b += _)
case n: NamedColumn[_] =>
// must pass self (not n.table) to tableAlias for Join check
b += s"${quote(tableAlias(n))}.${quote(n.name)}"
Expand Down
2 changes: 1 addition & 1 deletion src/test/scala/slaq/scalaquery/test/SubqueryTest.scala
Expand Up @@ -62,7 +62,7 @@ class SubqueryTest(tdb: TestDB) extends DBTest(tdb) with SubqueryModel {
p2 <- Posts leftJoin (_.id is p.id)
_ <- Query groupBy c.id orderBy p.id
yield (c.id.max, c, p, p2)
).subquery if max =~ ConstColumn(4)
).subquery if max =~ ConstColumn(4) & c.id =~ p.id
yield (max, c, p, p2)
println(sqJoin.pretty)
sqJoin.foreach(x => println(" " + x))
Expand Down

0 comments on commit 37e81ba

Please sign in to comment.