Skip to content

Commit

Permalink
Start [MOREL-75] Outer join
Browse files Browse the repository at this point in the history
Add left, right, full keywords;
derive type of join expressions
add method Ast.Id.toPat();
move joinRelType;
full join execution works

Fixes hydromatic#75
  • Loading branch information
julianhyde committed Jan 31, 2024
1 parent dd1533e commit 4599454
Show file tree
Hide file tree
Showing 18 changed files with 575 additions and 83 deletions.
3 changes: 2 additions & 1 deletion docs/reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,8 @@ In Standard ML but not in Morel:
| <i>var<sub>1</sub></i> <b>,</b> ... <b>,</b> <i>var<sub>v</sub></i> <b>suchThat</b> <i>exp</i>
constrained iteration (<i>v</i> &ge; 1)
<i>step</i> &rarr; <b>where</b> <i>exp</i> filter clause
| <b>join</b> <i>scan</i> [ <b>on</b> <i>exp</i> ] join clause
| [ <b>left</b> | <b>right</b> | <b>full</b> ] <b>join</b> <i>scan</i> [ <b>on</b> <i>exp</i> ]
join clause
| <b>group</b> <i>groupKey<sub>1</sub></i> <b>,</b> ... <b>,</b> <i>groupKey<sub>g</sub></i>
[ <b>compute</b> <i>agg<sub>1</sub></i> <b>,</b> ... <b>,</b> <i>agg<sub>a</sub></i> ]
group clause (<i>g</i> &ge; 0, <i>a</i> &ge; 1)
Expand Down
19 changes: 15 additions & 4 deletions src/main/java/net/hydromatic/morel/ast/Ast.java
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,12 @@
import java.util.TreeMap;
import java.util.function.Consumer;
import java.util.function.ObjIntConsumer;
import java.util.stream.Collectors;

import static net.hydromatic.morel.ast.AstBuilder.ast;
import static net.hydromatic.morel.type.RecordType.ORDERING;
import static net.hydromatic.morel.util.Ord.forEachIndexed;
import static net.hydromatic.morel.util.Pair.forEachIndexed;
import static net.hydromatic.morel.util.Static.toImmutableSet;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.collect.Iterables.getLast;
Expand Down Expand Up @@ -774,6 +774,11 @@ public Id accept(Shuttle shuttle) {
AstWriter unparse(AstWriter w, int left, int right) {
return w.id(name);
}

/** Creates a pattern equivalent to this identifier. */
public IdPat toPat() {
return new IdPat(pos, name);
}
}

/** Parse tree node of a record selector. */
Expand Down Expand Up @@ -949,7 +954,7 @@ public DatatypeBind accept(Shuttle shuttle) {
*/
public static class TyCon extends AstNode {
public final Id id;
public final @org.checkerframework.checker.nullness.qual.Nullable Type type;
public final @Nullable Type type;

TyCon(Pos pos, Id id, Type type) {
super(pos, Op.TY_CON);
Expand Down Expand Up @@ -1509,7 +1514,10 @@ && getLast(steps) instanceof Ast.Yield) {
final Set<Id> nextFields = new HashSet<>();
for (FromStep step : steps) {
switch (step.op) {
case FULL_JOIN:
case INNER_JOIN:
case LEFT_JOIN:
case RIGHT_JOIN:
case SCAN:
final Scan scan = (Scan) step;
nextFields.clear();
Expand All @@ -1536,7 +1544,7 @@ && getLast(steps) instanceof Ast.Yield) {
nextFields.addAll(Pair.left(groupExps));
groupExps.forEach((id, exp) -> nextFields.add(id));
aggregates.forEach(aggregate -> nextFields.add(aggregate.id));
fields = nextFields;
fields = ImmutableSet.copyOf(nextFields);
break;

case YIELD:
Expand All @@ -1546,7 +1554,7 @@ && getLast(steps) instanceof Ast.Yield) {
((Record) yield.exp).args.keySet()
.stream()
.map(label -> ast.id(Pos.ZERO, label))
.collect(Collectors.toSet());
.collect(toImmutableSet());
}
break;
}
Expand Down Expand Up @@ -1623,7 +1631,10 @@ public static class Scan extends FromStep {
Scan(Pos pos, Op op, Pat pat, Exp exp, @Nullable Exp condition) {
super(pos, op);
switch (op) {
case FULL_JOIN:
case INNER_JOIN:
case LEFT_JOIN:
case RIGHT_JOIN:
break;
case SCAN:
checkArgument(condition == null);
Expand Down
43 changes: 43 additions & 0 deletions src/main/java/net/hydromatic/morel/ast/AstNodes.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* Licensed to Julian Hyde under one or more contributor license
* agreements. See the NOTICE file distributed with this work
* for additional information regarding copyright ownership.
* Julian Hyde licenses this file to you under the Apache
* License, Version 2.0 (the "License"); you may not use this
* file except in compliance with the License. You may obtain a
* copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
* either express or implied. See the License for the specific
* language governing permissions and limitations under the
* License.
*/
package net.hydromatic.morel.ast;

import org.apache.calcite.rel.core.JoinRelType;

/** Utilities for abstract syntax trees. */
public abstract class AstNodes {
/** Converts an {@link Op} into a Calcite join type, otherwise throws. */
public static JoinRelType joinRelType(Op op) {
switch (op) {
case INNER_JOIN:
case SCAN:
return JoinRelType.INNER;
case LEFT_JOIN:
return JoinRelType.LEFT;
case RIGHT_JOIN:
return JoinRelType.RIGHT;
case FULL_JOIN:
return JoinRelType.FULL;
default:
throw new AssertionError(op);
}
}
}

// End AstNodes.java
5 changes: 4 additions & 1 deletion src/main/java/net/hydromatic/morel/ast/Core.java
Original file line number Diff line number Diff line change
Expand Up @@ -1113,7 +1113,10 @@ public static class Scan extends FromStep {
Exp condition) {
super(op, bindings);
switch (op) {
case FULL_JOIN:
case INNER_JOIN:
case LEFT_JOIN:
case RIGHT_JOIN:
case SUCH_THAT:
break;
default:
Expand Down Expand Up @@ -1146,7 +1149,7 @@ public static class Scan extends FromStep {
.append(infix)
.append(exp, Op.EQ.right, 0);
if (!isLiteralTrue()) {
w.append("on").append(condition, 0, 0);
w.append(" on ").append(condition, 0, 0);
}
return w;
}
Expand Down
11 changes: 8 additions & 3 deletions src/main/java/net/hydromatic/morel/ast/FromBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
import static net.hydromatic.morel.ast.CoreBuilder.core;
import static net.hydromatic.morel.util.Pair.forEach;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.collect.Iterables.getLast;

/** Builds a {@link Core.From}.
Expand Down Expand Up @@ -126,11 +127,15 @@ public FromBuilder suchThat(Core.Pat pat, Core.Exp exp) {
}

public FromBuilder scan(Core.Pat pat, Core.Exp exp) {
return scan(pat, exp, core.boolLiteral(true));
return scan(Op.INNER_JOIN, pat, exp, core.boolLiteral(true));
}

public FromBuilder scan(Core.Pat pat, Core.Exp exp, Core.Exp condition) {
public FromBuilder scan(Op op, Core.Pat pat, Core.Exp exp,
Core.Exp condition) {
checkArgument(op == Op.INNER_JOIN || op == Op.LEFT_JOIN
|| op == Op.RIGHT_JOIN || op == Op.FULL_JOIN);
if (exp.op == Op.FROM
&& op == Op.INNER_JOIN
&& steps.isEmpty()
&& core.boolLiteral(true).equals(condition)
&& (pat instanceof Core.IdPat
Expand Down Expand Up @@ -366,7 +371,7 @@ private class StepHandler extends Visitor {
if (scan.op == Op.SUCH_THAT) {
suchThat(scan.pat, scan.exp);
} else {
scan(scan.pat, scan.exp, scan.condition);
scan(scan.op, scan.pat, scan.exp, scan.condition);
}
}

Expand Down
3 changes: 3 additions & 0 deletions src/main/java/net/hydromatic/morel/ast/Op.java
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,9 @@ public enum Op {
FROM,
SCAN(" "),
INNER_JOIN(" join "),
LEFT_JOIN(" left join "),
RIGHT_JOIN(" right join "),
FULL_JOIN(" full join "),
WHERE,
GROUP,
COMPUTE,
Expand Down
13 changes: 4 additions & 9 deletions src/main/java/net/hydromatic/morel/compile/CalciteCompiler.java
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@
import java.util.function.Predicate;
import javax.annotation.Nonnull;

import static net.hydromatic.morel.ast.AstNodes.joinRelType;
import static net.hydromatic.morel.ast.CoreBuilder.core;
import static net.hydromatic.morel.util.Ord.forEachIndexed;

Expand Down Expand Up @@ -405,7 +406,10 @@ private static void harmonizeRowTypes(RelBuilder relBuilder, int inputCount) {

private RelContext step(RelContext cx, int i, Core.FromStep fromStep) {
switch (fromStep.op) {
case FULL_JOIN:
case INNER_JOIN:
case LEFT_JOIN:
case RIGHT_JOIN:
return join(cx, i, (Core.Scan) fromStep);
case WHERE:
return where(cx, (Core.Where) fromStep);
Expand Down Expand Up @@ -684,15 +688,6 @@ private RelContext join(RelContext cx, int i, Core.Scan scan) {
return cx;
}

private static JoinRelType joinRelType(Op op) {
switch (op) {
case INNER_JOIN:
return JoinRelType.INNER;
default:
throw new AssertionError(op);
}
}

private RelContext where(RelContext cx, Core.Where where) {
cx.relBuilder.filter(cx.varList, translate(cx, where.exp));
return cx;
Expand Down
12 changes: 10 additions & 2 deletions src/main/java/net/hydromatic/morel/compile/Compiler.java
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@
import java.util.function.Supplier;

import static net.hydromatic.morel.ast.Ast.Direction.DESC;
import static net.hydromatic.morel.ast.AstNodes.joinRelType;
import static net.hydromatic.morel.ast.CoreBuilder.core;
import static net.hydromatic.morel.util.Pair.forEach;
import static net.hydromatic.morel.util.Static.toImmutableList;
Expand Down Expand Up @@ -336,11 +337,18 @@ && getOnlyElement(bindings).id.type.equals(elementType)) {
elementType);
switch (firstStep.op) {
case INNER_JOIN:
case LEFT_JOIN:
case RIGHT_JOIN:
case FULL_JOIN:
final Core.Scan scan = (Core.Scan) firstStep;
final Code code = compile(cx, scan.exp);
final Code conditionCode = compile(cx, scan.condition);
final ImmutableList<Binding> outerBindings =
joinRelType(firstStep.op).generatesNullsOnLeft()
? bindings
: ImmutableList.of();
return () -> Codes.scanRowSink(firstStep.op, scan.pat, code,
conditionCode, nextFactory.get());
conditionCode, outerBindings, nextFactory.get());

case SUCH_THAT:
// Given
Expand Down Expand Up @@ -379,7 +387,7 @@ && getOnlyElement(bindings).id.type.equals(elementType)) {
final Code code2 = compile(cx, exp2);
final Code conditionCode2 = compile(cx, scan2.condition);
return () -> Codes.scanRowSink(Op.INNER_JOIN, scan2.pat, code2,
conditionCode2, nextFactory.get());
conditionCode2, ImmutableList.of(), nextFactory.get());

case WHERE:
final Core.Where where = (Core.Where) firstStep;
Expand Down
17 changes: 10 additions & 7 deletions src/main/java/net/hydromatic/morel/compile/Resolver.java
Original file line number Diff line number Diff line change
Expand Up @@ -453,7 +453,7 @@ private Core.Exp flattenLet(List<Ast.Decl> decls, Ast.Exp exp) {
// flattenLet(val x :: xs = [1, 2, 3] and (y, z) = (2, 4), x + y)
// becomes
// let v = ([1, 2, 3], (2, 4)) in case v of (x :: xs, (y, z)) => x + y end
if (decls.size() == 0) {
if (decls.isEmpty()) {
return toCore(exp);
}
final Ast.Decl decl = decls.get(0);
Expand Down Expand Up @@ -798,15 +798,18 @@ Core.Exp run(Ast.From from) {
final Op op = scan.exp.op == Op.SUCH_THAT ? Op.SUCH_THAT
: scan.op == Op.SCAN ? Op.INNER_JOIN
: scan.op;
final List<Binding> bindings2 = new ArrayList<>(fromBuilder.bindings());
Compiles.acceptBinding(typeMap.typeSystem, corePat, bindings2);
Core.Exp coreCondition = scan.condition == null
? core.boolLiteral(true)
: r.withEnv(bindings2).toCore(scan.condition);
Core.Exp coreCondition;
if (scan.condition == null) {
coreCondition = core.boolLiteral(true);
} else {
final List<Binding> bindings2 = new ArrayList<>(fromBuilder.bindings());
Compiles.acceptBinding(typeMap.typeSystem, corePat, bindings2);
coreCondition = r.withEnv(bindings2).toCore(scan.condition);
}
if (op == Op.SUCH_THAT) {
fromBuilder.suchThat(corePat, coreExp);
} else {
fromBuilder.scan(corePat, coreExp, coreCondition);
fromBuilder.scan(op, corePat, coreExp, coreCondition);
}
}

Expand Down
Loading

0 comments on commit 4599454

Please sign in to comment.