Skip to content

Commit 8f76007

Browse files
committed
Combine multiple “from” relations into a single CrossRelation.
This eliminates some duplication where we define an operation on a CrossRelation and then effectively fold the same operation over the top-level list. It also simplifies some cases where we “knew” the list was non-empty and can now show that in the types.
1 parent ca59aa0 commit 8f76007

6 files changed

Lines changed: 124 additions & 126 deletions

File tree

src/main/scala/slamdata/engine/compiler.scala

Lines changed: 70 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -172,21 +172,18 @@ trait Compiler[F[_]] {
172172
}
173173
}
174174

175-
def buildJoinDirectionMap(relations: List[SqlRelation]): Map[String, List[JoinTraverse.Dir]] = {
175+
def buildJoinDirectionMap(relations: SqlRelation): Map[String, List[JoinTraverse.Dir]] = {
176176
def loop(rel: SqlRelation): JoinTraverse = rel match {
177177
case t @ TableRelationAST(name, aliasOpt) => JoinTraverse.Leaf(t.aliasName)
178178
case t @ SubqueryRelationAST(subquery, alias) => JoinTraverse.Leaf(t.aliasName) // Leaf????
179179
case JoinRelation(left, right, tpe, clause) => JoinTraverse.Join(loop(left), loop(right))
180180
case CrossRelation(left, right) => JoinTraverse.Join(loop(left), loop(right))
181181
}
182182

183-
(relations.map(loop _).foldLeft[Option[JoinTraverse]](None) {
184-
case (None, traverse) => Some(traverse)
185-
case (Some(acc), traverse) => Some(JoinTraverse.Join(acc, traverse))
186-
}).map(_.directionMap).getOrElse(Map())
183+
loop(relations).directionMap
187184
}
188185

189-
def compileTableRefs(joined: Term[LogicalPlan], relations: List[SqlRelation]): Map[String, Term[LogicalPlan]] = {
186+
def compileTableRefs(joined: Term[LogicalPlan], relations: SqlRelation): Map[String, Term[LogicalPlan]] = {
190187
buildJoinDirectionMap(relations).map {
191188
case (name, dirs) =>
192189
name -> dirs.foldLeft(joined) {
@@ -198,11 +195,10 @@ trait Compiler[F[_]] {
198195
}
199196
}
200197

201-
def tableContext(joined: Term[LogicalPlan], relations: List[SqlRelation]): TableContext = {
198+
def tableContext(joined: Term[LogicalPlan], relations: SqlRelation): TableContext =
202199
TableContext(Some(joined), compileTableRefs(joined, relations))
203-
}
204200

205-
def step(relations: List[SqlRelation]): (Option[CompilerM[Term[LogicalPlan]]] => CompilerM[Term[LogicalPlan]] => CompilerM[Term[LogicalPlan]]) = {
201+
def step(relations: SqlRelation): (Option[CompilerM[Term[LogicalPlan]]] => CompilerM[Term[LogicalPlan]] => CompilerM[Term[LogicalPlan]]) = {
206202
(current: Option[CompilerM[Term[LogicalPlan]]]) => (next: CompilerM[Term[LogicalPlan]]) =>
207203
current.map { current =>
208204
for {
@@ -332,90 +328,85 @@ trait Compiler[F[_]] {
332328

333329
val projs = projections.map(_.expr)
334330

335-
val stepBuilder = step(relations)
336-
337-
if (relations.length == 0) for {
338-
projs <- projs.map(compile0).sequenceU
339-
} yield buildSelectRecord(names, projs)
340-
else for {
341-
joined <- relations.map(compile0).sequenceU
342-
343-
crossed = Foldable[List].foldLeftM[CompilerM, Term[LogicalPlan], Term[LogicalPlan]](
344-
joined.tail, joined.head
345-
)((left, right) => emit[Term[LogicalPlan]](LogicalPlan.invoke(Cross, left :: right :: Nil)))
346-
347-
rez <- stepBuilder(Some(crossed)) {
348-
val filtered = filter map { filter =>
349-
for {
350-
t <- CompilerState.rootTableReq
351-
f <- compile0(filter)
352-
} yield Filter(t, f)
353-
}
354-
355-
stepBuilder(filtered) {
356-
val grouped = groupBy map { groupBy =>
331+
relations match {
332+
case None => for {
333+
projs <- projs.map(compile0).sequenceU
334+
} yield buildSelectRecord(names, projs)
335+
case Some(relations) => {
336+
val stepBuilder = step(relations)
337+
stepBuilder(Some(compile0(relations))) {
338+
val filtered = filter map { filter =>
357339
for {
358340
t <- CompilerState.rootTableReq
359-
g <- compileArray(groupBy.keys)
360-
} yield GroupBy(t, g)
341+
f <- compile0(filter)
342+
} yield Filter(t, f)
361343
}
362344

363-
stepBuilder(grouped) {
364-
val having = groupBy.flatMap(_.having) map { having =>
345+
stepBuilder(filtered) {
346+
val grouped = groupBy map { groupBy =>
365347
for {
366348
t <- CompilerState.rootTableReq
367-
h <- compile0(having)
368-
} yield Filter(t, h)
349+
g <- compileArray(groupBy.keys)
350+
} yield GroupBy(t, g)
369351
}
370352

371-
stepBuilder(having) {
372-
val select = Some {
353+
stepBuilder(grouped) {
354+
val having = groupBy.flatMap(_.having) map { having =>
373355
for {
374-
projs <- projs.map(compile0).sequenceU
375-
} yield buildSelectRecord(names, projs)
356+
t <- CompilerState.rootTableReq
357+
h <- compile0(having)
358+
} yield Filter(t, h)
376359
}
377360

378-
stepBuilder(select) {
379-
val sort = orderBy map { orderBy =>
361+
stepBuilder(having) {
362+
val select = Some {
380363
for {
381-
t <- CompilerState.rootTableReq
382-
s <- compileArray(orderBy.keys.map(_._1)) // TODO: Ascending / descending
383-
} yield OrderBy(t, s)
364+
projs <- projs.map(compile0).sequenceU
365+
} yield buildSelectRecord(names, projs)
384366
}
385367

386-
stepBuilder(sort) {
387-
// Note: inspecting the name is not the most awesome imaginable way to identify
388-
// fields that were introduced to support "oredr by"
389-
val synthetic: Option[String] => Boolean = {
390-
case Some(name) => name.startsWith("__sd__")
391-
case None => false
368+
stepBuilder(select) {
369+
val sort = orderBy map { orderBy =>
370+
for {
371+
t <- CompilerState.rootTableReq
372+
s <- compileArray(orderBy.keys.map(_._1)) // TODO: Ascending / descending
373+
} yield OrderBy(t, s)
392374
}
393-
394-
val pruned = if (names.exists(synthetic))
395-
Some {
396-
for {
397-
t <- CompilerState.rootTableReq
398-
ns = names.collect {
399-
case Some(name) if !synthetic(Some(name)) => name
400-
}
401-
ts = ns.map(name => ObjectProject(t, LogicalPlan.constant(Data.Str(name))))
402-
} yield buildSelectRecord(ns.map(name => Some(name)), ts)
403-
}
404-
else None
405-
406-
stepBuilder(pruned) {
407-
val drop = offset map { offset =>
408-
for {
409-
t <- CompilerState.rootTableReq
410-
} yield Drop(t, LogicalPlan.constant(Data.Int(offset)))
411-
}
412375

413-
stepBuilder(drop) {
414-
(limit map { limit =>
376+
stepBuilder(sort) {
377+
// Note: inspecting the name is not the most awesome imaginable way to identify
378+
// fields that were introduced to support "oredr by"
379+
val synthetic: Option[String] => Boolean = {
380+
case Some(name) => name.startsWith("__sd__")
381+
case None => false
382+
}
383+
384+
val pruned = if (names.exists(synthetic))
385+
Some {
386+
for {
387+
t <- CompilerState.rootTableReq
388+
ns = names.collect {
389+
case Some(name) if !synthetic(Some(name)) => name
390+
}
391+
ts = ns.map(name => ObjectProject(t, LogicalPlan.constant(Data.Str(name))))
392+
} yield buildSelectRecord(ns.map(name => Some(name)), ts)
393+
}
394+
else None
395+
396+
stepBuilder(pruned) {
397+
val drop = offset map { offset =>
415398
for {
416399
t <- CompilerState.rootTableReq
417-
} yield Take(t, LogicalPlan.constant(Data.Int(limit)))
418-
}).getOrElse(CompilerState.rootTableReq)
400+
} yield Drop(t, LogicalPlan.constant(Data.Int(offset)))
401+
}
402+
403+
stepBuilder(drop) {
404+
(limit map { limit =>
405+
for {
406+
t <- CompilerState.rootTableReq
407+
} yield Take(t, LogicalPlan.constant(Data.Int(limit)))
408+
}).getOrElse(CompilerState.rootTableReq)
409+
}
419410
}
420411
}
421412
}
@@ -424,7 +415,7 @@ trait Compiler[F[_]] {
424415
}
425416
}
426417
}
427-
} yield rez
418+
}
428419

429420
case Subselect(select) => compile0(select)
430421

@@ -512,7 +503,7 @@ trait Compiler[F[_]] {
512503

513504
case SubqueryRelationAST(subquery, _) => compile0(subquery)
514505

515-
case JoinRelation(left, right, tpe, clause) =>
506+
case JoinRelation(left, right, tpe, clause) =>
516507
for {
517508
leftName <- CompilerState.freshName("left")
518509
rightName <- CompilerState.freshName("right")
@@ -521,8 +512,8 @@ trait Compiler[F[_]] {
521512
left0 <- compile0(left)
522513
right0 <- compile0(right)
523514
join <- CompilerState.contextual(
524-
tableContext(leftFree, List(left))
525-
++ tableContext(rightFree, List(right))) {
515+
tableContext(leftFree, left) ++ tableContext(rightFree, right)
516+
) {
526517
for {
527518
tuple <- compileJoin(clause)
528519
} yield LogicalPlan.join(leftFree, rightFree,

src/main/scala/slamdata/engine/semantics.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import scalaz._
77
import scalaz.std.map._
88
import scalaz.std.string._
99
import scalaz.std.list._
10+
import scalaz.std.option._
1011
import scalaz.std.set._
1112

1213
import scalaz.syntax.apply._

src/main/scala/slamdata/engine/sql/ast.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ trait NodeInstances {
1818
object Node extends NodeInstances
1919

2020
final case class SelectStmt(projections: List[Proj],
21-
relations: List[SqlRelation],
21+
relations: Option[SqlRelation],
2222
filter: Option[Expr],
2323
groupBy: Option[GroupBy],
2424
orderBy: Option[OrderBy],

src/main/scala/slamdata/engine/sql/parser.scala

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import scala.util.parsing.combinator.token._
1212
import scala.util.parsing.input.CharArrayReader.EofCh
1313

1414
import scalaz._
15+
import scalaz.std.option._
16+
import scalaz.syntax.bind._
1517

1618
case class Query(value: String)
1719

@@ -72,7 +74,7 @@ class SQLParser extends StandardTokenParsers {
7274
keyword("select") ~> projections ~
7375
opt(relations) ~ opt(filter) ~
7476
opt(group_by) ~ opt(order_by) ~ opt(limit) ~ opt(offset) <~ opt(op(";")) ^^ {
75-
case p ~ r ~ f ~ g ~ o ~ l ~ off => SelectStmt(p, r.getOrElse(Nil), f, g, o, l, off)
77+
case p ~ r ~ f ~ g ~ o ~ l ~ off => SelectStmt(p, r.join, f, g, o, l, off)
7678
}
7779

7880
def projections: Parser[List[Proj]] = repsep(projection, op(",")).map(_.toList)
@@ -206,7 +208,11 @@ class SQLParser extends StandardTokenParsers {
206208
stringLit ^^ { case s => StringLiteral(s) } |
207209
keyword("null") ^^^ NullLiteral.apply
208210

209-
def relations: Parser[List[SqlRelation]] = keyword("from") ~> rep1sep(relation, op(",")).map(_.toList)
211+
def relations: Parser[Option[SqlRelation]] =
212+
keyword("from") ~> rep1sep(relation, op(",")).map(_.foldLeft[Option[SqlRelation]](None) {
213+
case (None, traverse) => Some(traverse)
214+
case (Some(acc), traverse) => Some(CrossRelation(acc, traverse))
215+
})
210216

211217
def std_join_relation: Parser[SqlRelation => SqlRelation] =
212218
opt(join_type) ~ keyword("join") ~ simple_relation ~ keyword("on") ~ expr ^^

src/test/scala/slamdata/engine/compiler.scala

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -560,22 +560,22 @@ class CompilerSpec extends Specification with CompilerHelpers {
560560
"compile simple inner equi-join" in {
561561
testLogicalPlanCompile(
562562
"select foo.name, bar.address from foo join bar on foo.id = bar.foo_id",
563-
letOne('tmp2,
564-
let(Map('left0 -> read("foo"),
565-
'right1 -> read("bar")),
566-
join(free('left0), free('right1),
563+
letOne('tmp0,
564+
let(Map('left1 -> read("foo"),
565+
'right2 -> read("bar")),
566+
join(free('left1), free('right2),
567567
JoinType.Inner, JoinRel.Eq,
568-
ObjectProject(free('left0), constant(Data.Str("id"))),
569-
ObjectProject(free('right1), constant(Data.Str("foo_id"))))),
568+
ObjectProject(free('left1), constant(Data.Str("id"))),
569+
ObjectProject(free('right2), constant(Data.Str("foo_id"))))),
570570
letOne('tmp3,
571571
makeObj(
572572
"name" ->
573573
ObjectProject(
574-
ObjectProject(free('tmp2), constant(Data.Str("left"))),
574+
ObjectProject(free('tmp0), constant(Data.Str("left"))),
575575
constant(Data.Str("name"))),
576576
"address" ->
577577
ObjectProject(
578-
ObjectProject(free('tmp2), constant(Data.Str("right"))),
578+
ObjectProject(free('tmp0), constant(Data.Str("right"))),
579579
constant(Data.Str("address")))),
580580
free('tmp3))))
581581
}

0 commit comments

Comments
 (0)