From c1dc664039b8dc841d18ae7d9e537fcb89fb0418 Mon Sep 17 00:00:00 2001 From: Julian Hyde Date: Fri, 19 Jan 2024 16:47:32 -0800 Subject: [PATCH] [MOREL-216] Allow comma-separated scans in `join`, and `on` in the `from` clause The following is now legal: from a in [1, 2], b in [3, 4, 5] on a + b = 6 where b < 5 join c in [6, 7] on b + c = 10, d in [7, 8] An `on` after the first scan, `from a in [1, 2]`, was and remains illegal. Remove Op.INNER_JOIN, because its semantics is identical to Op.SCAN. (We will add operators back if and when we support `left join`.) Fixes #216 --- docs/reference.md | 8 ++- .../java/net/hydromatic/morel/ast/Ast.java | 24 +++----- .../net/hydromatic/morel/ast/AstBuilder.java | 4 +- .../java/net/hydromatic/morel/ast/Core.java | 12 ++-- .../net/hydromatic/morel/ast/CoreBuilder.java | 8 +-- .../net/hydromatic/morel/ast/FromBuilder.java | 4 +- .../java/net/hydromatic/morel/ast/Op.java | 1 - .../net/hydromatic/morel/ast/Shuttle.java | 2 +- .../morel/compile/CalciteCompiler.java | 4 +- .../hydromatic/morel/compile/Compiler.java | 2 +- .../morel/compile/Relationalizer.java | 2 +- .../morel/compile/SuchThatShuttle.java | 1 - .../morel/compile/TypeResolver.java | 1 - .../java/net/hydromatic/morel/eval/Codes.java | 5 +- src/main/javacc/MorelParser.jj | 52 ++++++++++------- .../java/net/hydromatic/morel/MainTest.java | 57 ++++++++++--------- src/test/resources/script/builtIn.smli | 2 +- src/test/resources/script/relational.smli | 6 +- src/test/resources/script/suchThat.smli | 28 +++++++++ 19 files changed, 125 insertions(+), 98 deletions(-) diff --git a/docs/reference.md b/docs/reference.md index f5dd1bb5..aca0d58c 100644 --- a/docs/reference.md +++ b/docs/reference.md @@ -139,7 +139,7 @@ In Standard ML but not in Morel: conditional | case exp of match case analysis | fn match function - | from [ scan1 , ... , scans ] step* + | from [ scan1 , ... , scans ] step* relational expression (s ≥ 0) exprowexprowItem [, exprowItem ]* expression row @@ -147,10 +147,12 @@ In Standard ML but not in Morel: matchmatchItem [ '|' matchItem ]* match matchItempat => exp -scanpat [ in | = ] exp +scanpat in exp [ on exp ] iteration + | pat = exp [ on exp ] single iteration | var unbounded variable stepwhere exp filter clause - | join scan [ on exp ] join clause + | join scan1 [ , ... , scans ] + join clause | group groupKey1 , ... , groupKeyg [ compute agg1 , ... , agga ] group clause (g ≥ 0, a ≥ 1) diff --git a/src/main/java/net/hydromatic/morel/ast/Ast.java b/src/main/java/net/hydromatic/morel/ast/Ast.java index 17a8e9da..4b72d852 100644 --- a/src/main/java/net/hydromatic/morel/ast/Ast.java +++ b/src/main/java/net/hydromatic/morel/ast/Ast.java @@ -1509,7 +1509,6 @@ && getLast(steps) instanceof Ast.Yield) { final Set nextFields = new HashSet<>(); for (FromStep step : steps) { switch (step.op) { - case INNER_JOIN: case SCAN: final Scan scan = (Scan) step; nextFields.clear(); @@ -1578,8 +1577,12 @@ public Exp accept(Shuttle shuttle) { } else { w.append("from"); forEachIndexed(steps, (step, i) -> { - if (step.op == Op.SCAN && i > 0 && steps.get(i - 1).op == Op.SCAN) { - w.append(","); + if (step.op == Op.SCAN && i > 0) { + if (steps.get(i - 1).op == Op.SCAN) { + w.append(","); + } else { + w.append(" join"); + } } step.unparse(w, 0, 0); }); @@ -1620,17 +1623,8 @@ public static class Scan extends FromStep { public final @Nullable Exp exp; public final @Nullable Exp condition; - Scan(Pos pos, Op op, Pat pat, @Nullable Exp exp, @Nullable Exp condition) { - super(pos, op); - switch (op) { - case INNER_JOIN: - break; - case SCAN: - checkArgument(condition == null); - break; - default: - throw new AssertionError("not a join type " + op); - } + Scan(Pos pos, Pat pat, @Nullable Exp exp, @Nullable Exp condition) { + super(pos, Op.SCAN); this.pat = pat; this.exp = exp; this.condition = condition; @@ -1668,7 +1662,7 @@ public Scan copy(Pat pat, @Nullable Exp exp, @Nullable Exp condition) { && Objects.equals(this.exp, exp) && Objects.equals(this.condition, condition) ? this - : new Scan(pos, op, pat, exp, condition); + : new Scan(pos, pat, exp, condition); } } diff --git a/src/main/java/net/hydromatic/morel/ast/AstBuilder.java b/src/main/java/net/hydromatic/morel/ast/AstBuilder.java index e81b481f..6b4cc2a3 100644 --- a/src/main/java/net/hydromatic/morel/ast/AstBuilder.java +++ b/src/main/java/net/hydromatic/morel/ast/AstBuilder.java @@ -457,9 +457,9 @@ public Ast.Exp map(Pos pos, Ast.Exp e1, Ast.Exp e2) { return apply(apply(ref(pos, BuiltIn.LIST_MAP), e1), e2); } - public Ast.Scan scan(Pos pos, Op op, Ast.Pat pat, Ast.Exp exp, + public Ast.Scan scan(Pos pos, Ast.Pat pat, Ast.Exp exp, @Nullable Ast.Exp condition) { - return new Ast.Scan(pos, op, pat, exp, condition); + return new Ast.Scan(pos, pat, exp, condition); } public Ast.Order order(Pos pos, Iterable orderItems) { diff --git a/src/main/java/net/hydromatic/morel/ast/Core.java b/src/main/java/net/hydromatic/morel/ast/Core.java index dce8ad2b..83075d68 100644 --- a/src/main/java/net/hydromatic/morel/ast/Core.java +++ b/src/main/java/net/hydromatic/morel/ast/Core.java @@ -1187,13 +1187,9 @@ public static class Scan extends FromStep { public final Exp exp; public final Exp condition; - Scan(Op op, ImmutableList bindings, Pat pat, Exp exp, + Scan(ImmutableList bindings, Pat pat, Exp exp, Exp condition) { - super(op, bindings); - if (op != Op.INNER_JOIN) { - // SCAN and CROSS_JOIN are valid in ast, not core. - throw new IllegalArgumentException("not a join type " + op); - } + super(Op.SCAN, bindings); this.pat = requireNonNull(pat, "pat"); this.exp = requireNonNull(exp, "exp"); this.condition = requireNonNull(condition, "condition"); @@ -1224,7 +1220,7 @@ private static boolean canAssign(Type fromType, Type toType) { @Override protected AstWriter unparse(AstWriter w, From from, int ordinal, int left, int right) { - w.append(ordinal == 0 ? " " : op.padded) + w.append(ordinal == 0 ? " " : " join ") // for these purposes 'in' has same precedence as '=' .append(pat, 0, Op.EQ.left); if (Extents.isInfinite(exp)) { @@ -1252,7 +1248,7 @@ public Scan copy(List bindings, Pat pat, Exp exp, Exp condition) { && condition == this.condition && bindings.equals(this.bindings) ? this - : core.scan(op, bindings, pat, exp, condition); + : core.scan(bindings, pat, exp, condition); } } diff --git a/src/main/java/net/hydromatic/morel/ast/CoreBuilder.java b/src/main/java/net/hydromatic/morel/ast/CoreBuilder.java index fa335046..5f4ea372 100644 --- a/src/main/java/net/hydromatic/morel/ast/CoreBuilder.java +++ b/src/main/java/net/hydromatic/morel/ast/CoreBuilder.java @@ -415,8 +415,7 @@ public Core.Exp implicitYieldExp(TypeSystem typeSystem, } } - public static List lastBindings( - List steps) { + public List lastBindings(List steps) { return steps.isEmpty() ? ImmutableList.of() : Iterables.getLast(steps).bindings; @@ -492,10 +491,9 @@ public Core.DatatypeDecl datatypeDecl(Iterable dataTypes) { return new Core.DatatypeDecl(ImmutableList.copyOf(dataTypes)); } - public Core.Scan scan(Op op, List bindings, Core.Pat pat, + public Core.Scan scan(List bindings, Core.Pat pat, Core.Exp exp, Core.Exp condition) { - return new Core.Scan(op, ImmutableList.copyOf(bindings), pat, exp, - condition); + return new Core.Scan(ImmutableList.copyOf(bindings), pat, exp, condition); } public Core.Aggregate aggregate(Type type, Core.Exp aggregate, diff --git a/src/main/java/net/hydromatic/morel/ast/FromBuilder.java b/src/main/java/net/hydromatic/morel/ast/FromBuilder.java index 4ac6c382..e18ba466 100644 --- a/src/main/java/net/hydromatic/morel/ast/FromBuilder.java +++ b/src/main/java/net/hydromatic/morel/ast/FromBuilder.java @@ -201,7 +201,7 @@ && getLast(((Core.From) exp).steps).bindings.size() == 1 return yield_(uselessIfLast, bindings, core.record(typeSystem, nameExps)); } Compiles.acceptBinding(typeSystem, pat, bindings); - return addStep(core.scan(Op.INNER_JOIN, bindings, pat, exp, condition)); + return addStep(core.scan(bindings, pat, exp, condition)); } public FromBuilder addAll(Iterable steps) { @@ -367,7 +367,7 @@ private Core.Exp build(boolean simplify) { } if (simplify && steps.size() == 1 - && steps.get(0).op == Op.INNER_JOIN) { + && steps.get(0).op == Op.SCAN) { final Core.Scan scan = (Core.Scan) steps.get(0); if (scan.pat.op == Op.ID_PAT) { return scan.exp; diff --git a/src/main/java/net/hydromatic/morel/ast/Op.java b/src/main/java/net/hydromatic/morel/ast/Op.java index 0dfef84b..5ae30abc 100644 --- a/src/main/java/net/hydromatic/morel/ast/Op.java +++ b/src/main/java/net/hydromatic/morel/ast/Op.java @@ -130,7 +130,6 @@ public enum Op { CASE, FROM, SCAN(" "), - INNER_JOIN(" join "), WHERE, GROUP, COMPUTE, diff --git a/src/main/java/net/hydromatic/morel/ast/Shuttle.java b/src/main/java/net/hydromatic/morel/ast/Shuttle.java index 09333a64..de8287b0 100644 --- a/src/main/java/net/hydromatic/morel/ast/Shuttle.java +++ b/src/main/java/net/hydromatic/morel/ast/Shuttle.java @@ -231,7 +231,7 @@ protected AstNode visit(Ast.OrderItem orderItem) { } protected Ast.Scan visit(Ast.Scan scan) { - return ast.scan(scan.pos, scan.op, scan.pat.accept(this), + return ast.scan(scan.pos, scan.pat.accept(this), scan.exp.accept(this), scan.condition == null ? null : scan.condition.accept(this)); } diff --git a/src/main/java/net/hydromatic/morel/compile/CalciteCompiler.java b/src/main/java/net/hydromatic/morel/compile/CalciteCompiler.java index eee61b12..a0a68dc3 100644 --- a/src/main/java/net/hydromatic/morel/compile/CalciteCompiler.java +++ b/src/main/java/net/hydromatic/morel/compile/CalciteCompiler.java @@ -404,7 +404,7 @@ private static void harmonizeRowTypes(RelBuilder relBuilder, int inputCount) { private RelContext step(RelContext cx, int i, Core.FromStep fromStep) { switch (fromStep.op) { - case INNER_JOIN: + case SCAN: return join(cx, i, (Core.Scan) fromStep); case WHERE: return where(cx, (Core.Where) fromStep); @@ -687,7 +687,7 @@ private RelContext join(RelContext cx, int i, Core.Scan scan) { private static JoinRelType joinRelType(Op op) { switch (op) { - case INNER_JOIN: + case SCAN: return JoinRelType.INNER; default: throw new AssertionError(op); diff --git a/src/main/java/net/hydromatic/morel/compile/Compiler.java b/src/main/java/net/hydromatic/morel/compile/Compiler.java index 9a69786c..35f5f9e1 100644 --- a/src/main/java/net/hydromatic/morel/compile/Compiler.java +++ b/src/main/java/net/hydromatic/morel/compile/Compiler.java @@ -329,7 +329,7 @@ && getOnlyElement(bindings).id.type.equals(elementType)) { createRowSinkFactory(cx, firstStep.bindings, skip(steps), elementType); switch (firstStep.op) { - case INNER_JOIN: + case SCAN: final Core.Scan scan = (Core.Scan) firstStep; final Code code = compile(cx, scan.exp); final Code conditionCode = compile(cx, scan.condition); diff --git a/src/main/java/net/hydromatic/morel/compile/Relationalizer.java b/src/main/java/net/hydromatic/morel/compile/Relationalizer.java index 6d3cdc8d..ebd6cb72 100644 --- a/src/main/java/net/hydromatic/morel/compile/Relationalizer.java +++ b/src/main/java/net/hydromatic/morel/compile/Relationalizer.java @@ -108,7 +108,7 @@ private Core.From toFrom(Core.Exp exp) { final List bindings = new ArrayList<>(); Compiles.acceptBinding(typeSystem, id, bindings); final Core.Scan scan = - core.scan(Op.INNER_JOIN, bindings, id, exp, core.boolLiteral(true)); + core.scan(bindings, id, exp, core.boolLiteral(true)); return core.from(typeSystem, ImmutableList.of(scan)); } } diff --git a/src/main/java/net/hydromatic/morel/compile/SuchThatShuttle.java b/src/main/java/net/hydromatic/morel/compile/SuchThatShuttle.java index 3e9c6e16..82da5002 100644 --- a/src/main/java/net/hydromatic/morel/compile/SuchThatShuttle.java +++ b/src/main/java/net/hydromatic/morel/compile/SuchThatShuttle.java @@ -110,7 +110,6 @@ Core.From visit(Core.From from) { final Core.FromStep step = steps.get(i); switch (step.op) { case SCAN: - case INNER_JOIN: final Core.Scan scan = (Core.Scan) step; if (Extents.isInfinite(scan.exp)) { final int idPatCount = idPats.size(); diff --git a/src/main/java/net/hydromatic/morel/compile/TypeResolver.java b/src/main/java/net/hydromatic/morel/compile/TypeResolver.java index c100d700..0212d14f 100644 --- a/src/main/java/net/hydromatic/morel/compile/TypeResolver.java +++ b/src/main/java/net/hydromatic/morel/compile/TypeResolver.java @@ -480,7 +480,6 @@ private Pair deduceStepType(TypeEnv env, Map fieldVars, List fromSteps) { switch (step.op) { case SCAN: - case INNER_JOIN: final Ast.Scan scan = (Ast.Scan) step; final Ast.Exp scanExp; final boolean eq; diff --git a/src/main/java/net/hydromatic/morel/eval/Codes.java b/src/main/java/net/hydromatic/morel/eval/Codes.java index cf2bfe97..7a4025e9 100644 --- a/src/main/java/net/hydromatic/morel/eval/Codes.java +++ b/src/main/java/net/hydromatic/morel/eval/Codes.java @@ -3092,7 +3092,7 @@ static class ScanRowSink extends BaseRowSink { ScanRowSink(Op op, Core.Pat pat, Code code, Code conditionCode, RowSink rowSink) { super(rowSink); - checkArgument(op == Op.INNER_JOIN); + checkArgument(op == Op.SCAN); this.op = op; this.pat = pat; this.code = code; @@ -3101,8 +3101,7 @@ static class ScanRowSink extends BaseRowSink { @Override public Describer describe(Describer describer) { return describer.start("join", d -> - d.arg("op", op.padded.trim()) - .arg("pat", pat) + d.arg("pat", pat) .arg("exp", code) .argIf("condition", conditionCode, !isConstantTrue(conditionCode)) .arg("sink", rowSink)); diff --git a/src/main/javacc/MorelParser.jj b/src/main/javacc/MorelParser.jj index 8e8c415d..bcac7317 100644 --- a/src/main/javacc/MorelParser.jj +++ b/src/main/javacc/MorelParser.jj @@ -330,8 +330,8 @@ Exp from() : } { { span = Span.of(pos()); } - [ fromFirstStep(steps) - ( fromFirstStep(steps) )* + [ fromFirstScan(steps) + ( fromScan(steps) )* ] ( fromStep(steps) )* { @@ -339,7 +339,7 @@ Exp from() : } } -void fromFirstStep(List steps) : +void fromFirstScan(List steps) : { final Pair patExp; } @@ -349,30 +349,22 @@ void fromFirstStep(List steps) : patExp.right != null ? Span.of(patExp.left, patExp.right) : Span.of(patExp.left); - steps.add(ast.scan(span.pos(), Op.SCAN, patExp.left, patExp.right, null)); + steps.add(ast.scan(span.pos(), patExp.left, patExp.right, null)); } } -void fromStep(List steps) : +void fromScan(List steps) : { - final Span span; - final Op op; final Pair patExp; final Exp condition; - final Exp filterExp; - final Exp skipExp; - final Exp takeExp; - final Exp yieldExp; - final PairList groupExps; - final List aggregates; - final List orderItems; } { - { - span = Span.of(pos()); - op = Op.INNER_JOIN; -} - patExp = fromSource() + patExp = fromSource() { + final Span span = + patExp.right != null + ? Span.of(patExp.left, patExp.right) + : Span.of(patExp.left); + } ( LOOKAHEAD(2) condition = expression() @@ -380,10 +372,26 @@ void fromStep(List steps) : { condition = null; } ) { - steps.add( - ast.scan(span.end(this), op, patExp.left, patExp.right, - condition)); + steps.add(ast.scan(span.end(this), patExp.left, patExp.right, condition)); } +} + +void fromStep(List steps) : +{ + final Span span; + final Op op; + final Exp filterExp; + final Exp skipExp; + final Exp takeExp; + final Exp yieldExp; + final PairList groupExps; + final List aggregates; + final List orderItems; +} +{ + + fromScan(steps) + ( fromScan(steps) )* | { span = Span.of(pos()); } filterExp = expression() { steps.add(ast.where(span.end(this), filterExp)); diff --git a/src/test/java/net/hydromatic/morel/MainTest.java b/src/test/java/net/hydromatic/morel/MainTest.java index 5ba69a28..01ae0a4c 100644 --- a/src/test/java/net/hydromatic/morel/MainTest.java +++ b/src/test/java/net/hydromatic/morel/MainTest.java @@ -1705,11 +1705,18 @@ private static List node(Object... args) { ml("from e in emps, job, d where hasEmp (e, d, job)") .assertParseSame(); ml("from a, b in emps where a > b join c join d in depts where c > d") - .assertParseSame(); - // We ought to allow "join v1, v2", but don't currently. + .assertParse("from a, b in emps where a > b " + + "join c, d in depts where c > d"); ml("from a, b in emps where a > b join c, d in depts where c > d") + .assertParseSame(); + ml("from e in emps, d in depts on e.deptno = d.deptno") + .assertParse("from e in emps, d in depts on #deptno e = #deptno d"); + ml("from e in emps on true, d in depts on e.deptno = d.deptno") .assertParseThrowsParseException( - startsWith("Encountered \" \",\" \", \"\" at line 1, column 37.")); + startsWith("Encountered \" \"on\" \"on \"\" " + + "at line 1, column 16.")); + ml("from e, d in depts on e.deptno = d.deptno") + .assertParse("from e, d in depts on #deptno e = #deptno d"); ml("from , d in depts").assertError("Xx"); ml("from join d in depts on c").assertError("Xx"); ml("from left join d in depts on c").assertError("Xx"); @@ -1717,11 +1724,14 @@ private static List node(Object... args) { ml("from full join d in depts on c").assertError("Xx"); ml("from e in emps join d in depts").assertError("Xx"); ml("from e in emps join d in depts where c").assertError("Xx"); - ml("from e in emps join d in depts on c").assertParseSame(); - ml("from e in emps left join d in depts on c").assertParseSame(); - ml("from e in emps right join d in depts on c").assertParseSame(); - ml("from e in emps full join d in depts on c").assertParseSame(); - ml("from e in (from z in emps) join d in (from y in depts) on c") + ml("from e in emps join d in depts on c") + .assertParse("from e in emps, d in depts on c"); + if ("TODO".isEmpty()) { + ml("from e in emps left join d in depts on c").assertParseSame(); + ml("from e in emps right join d in depts on c").assertParseSame(); + ml("from e in emps full join d in depts on c").assertParseSame(); + } + ml("from e in (from z in emps), d in (from y in depts) on c") .assertParseSame(); ml("from e in emps\n" + " group e.deptno\n" @@ -1770,7 +1780,8 @@ private static List node(Object... args) { .assertParseSame() .assertType("(int -> bool) -> int list"); ml("fn f => from i in [1, 2, 3] join j in [3, 4] on f (i, j) yield i + j") - .assertParseSame() + .assertParse("fn f => from i in [1, 2, 3]," + + " j in [3, 4] on f (i, j) yield i + j") .assertType("(int * int -> bool) -> int list"); // In "from p in exp" and "from p = exp", p can be any pattern @@ -1803,12 +1814,6 @@ private static List node(Object... args) { startsWith("Encountered \"\" at line 1, column 11.")); } - @Test void testFoo() { - ml("fn f => from i in [1, 2, 3] join j in [3, 4] on f (i, j) yield i + j") - .assertParseSame() - .assertType("(int * int -> bool) -> int list"); - } - @Test void testFromYield() { ml("from a in [1], b in [true]") .assertType("{a:int, b:bool} list"); @@ -1901,21 +1906,21 @@ private static List node(Object... args) { + " yield {d, n}\n" + "end"; final String code = "from(sink\n" - + " join(op join, pat v0,\n" + + " join(pat v0,\n" + " exp from(\n" - + " sink join(op join, pat e, exp tuple(\n" + + " sink join(pat e, exp tuple(\n" + " tuple(constant(10), constant(100), constant(Fred)),\n" + " tuple(constant(20), constant(101), constant(Velma)),\n" + " tuple(constant(30), constant(102), constant(Shaggy)),\n" + " tuple(constant(30), constant(103), constant(Scooby))),\n" + " sink collect(tuple(apply(fnValue nth:2, argCode get(name e)), " + "apply(fnValue nth:0, argCode get(name e)))))), " - + "sink join(op join, pat n_1, exp tuple(\n" + + "sink join(pat n_1, exp tuple(\n" + " apply(fnValue nth:0, argCode get(name v0))), " - + "sink join(op join, pat d_1, exp tuple(constant(30)), " + + "sink join(pat d_1, exp tuple(constant(30)), " + "sink where(condition apply2(fnValue elem,\n" + " tuple(get(name n), get(name d)), " - + "from(sink join(op join, pat e, exp tuple(\n" + + "from(sink join(pat e, exp tuple(\n" + " tuple(constant(10), constant(100), constant(Fred)),\n" + " tuple(constant(20), constant(101), constant(Velma)),\n" + " tuple(constant(30), constant(102), constant(Shaggy)),\n" @@ -1948,9 +1953,9 @@ private static List node(Object... args) { + " (d, job) => op elem ((op div (d, 2), job)," + " from e in #emp scott" + " yield (#deptno e, #job e)) yield j"; - final String code = "from(sink join(op join, pat d_1,\n" + final String code = "from(sink join(pat d_1,\n" + " exp apply(fnValue nth:1, argCode get(name scott)),\n" - + " sink join(op join, pat j,\n" + + " sink join(pat j,\n" + " exp apply(\n" + " fnCode apply(fnValue List.filter,\n" + " argCode match(j,\n" @@ -1959,7 +1964,7 @@ private static List node(Object... args) { + " tuple(apply2(fnValue div, get(name d), constant(2)),\n" + " get(name job)),\n" + " from(\n" - + " sink join(op join, pat e,\n" + + " sink join(pat e,\n" + " exp apply(fnValue nth:2, argCode get(name scott)),\n" + " sink collect(\n" + " tuple(apply(fnValue nth:1, argCode get(name e)),\n" @@ -2240,7 +2245,7 @@ private static List node(Object... args) { + " where d_1 = 30" + " yield {d = d_1, n = n_1}"; final String code = "from(sink\n" - + " join(op join, pat (n_1, d_1),\n" + + " join(pat (n_1, d_1),\n" + " exp apply(\n" + " fnCode apply(fnValue List.filter,\n" + " argCode match(v0,\n" @@ -2249,7 +2254,7 @@ private static List node(Object... args) { + " apply2(fnValue elem,\n" + " tuple(get(name n), get(name d)),\n" + " from(sink\n" - + " join(op join, pat e, exp tuple(\n" + + " join(pat e, exp tuple(\n" + " tuple(constant(30), constant(102), constant(Shaggy)),\n" + " tuple(constant(30), constant(103), constant(Scooby))),\n" + " sink collect(tuple(apply(fnValue nth:2, argCode get(name e)),\n" @@ -2595,7 +2600,7 @@ private static List node(Object... args) { + " group a = #a r compute sb = sum of #b r" + " yield {a = a, a2 = a + a, sb = sb}"; final String plan = "from(" - + "sink join(op join, pat r, exp tuple(tuple(constant(2), constant(3))), " + + "sink join(pat r, exp tuple(tuple(constant(2), constant(3))), " + "sink group(key tuple(apply(fnValue nth:0, argCode get(name r))), " + "agg aggregate, " + "sink collect(tuple(get(name a), " diff --git a/src/test/resources/script/builtIn.smli b/src/test/resources/script/builtIn.smli index fc298945..0bfd2021 100644 --- a/src/test/resources/script/builtIn.smli +++ b/src/test/resources/script/builtIn.smli @@ -2563,7 +2563,7 @@ Relational.iterate > : {empno:int, ename:string, mgr:int} list Sys.plan (); > val it = -> "apply(fnCode apply(fnValue Relational.iterate, argCode from(sink join(op join, pat e_1, exp constant([[7839, KING, 0], [7566, JONES, 7839], [7698, BLAKE, 7839], [7782, CLARK, 7839], [7788, SCOTT, 7566], [7902, FORD, 7566], [7499, ALLEN, 7698], [7521, WARD, 7698], [7654, MARTIN, 7698], [7844, TURNER, 7698], [7900, JAMES, 7698], [7934, MILLER, 7782], [7876, ADAMS, 7788], [7369, SMITH, 7902]]), sink where(condition apply2(fnValue =, apply(fnValue nth:2, argCode get(name e)), constant(0)), sink collect(get(name e)))))), argCode match(v0, apply(fnCode match((oldList, newList), from(sink join(op join, pat d, exp get(name newList), sink join(op join, pat e, exp constant([[7839, KING, 0], [7566, JONES, 7839], [7698, BLAKE, 7839], [7782, CLARK, 7839], [7788, SCOTT, 7566], [7902, FORD, 7566], [7499, ALLEN, 7698], [7521, WARD, 7698], [7654, MARTIN, 7698], [7844, TURNER, 7698], [7900, JAMES, 7698], [7934, MILLER, 7782], [7876, ADAMS, 7788], [7369, SMITH, 7902]]), sink where(condition apply2(fnValue =, apply(fnValue nth:2, argCode get(name e)), apply(fnValue nth:0, argCode get(name d))), sink collect(get(name e))))))), argCode get(name v0))))" +> "apply(fnCode apply(fnValue Relational.iterate, argCode from(sink join(pat e_1, exp constant([[7839, KING, 0], [7566, JONES, 7839], [7698, BLAKE, 7839], [7782, CLARK, 7839], [7788, SCOTT, 7566], [7902, FORD, 7566], [7499, ALLEN, 7698], [7521, WARD, 7698], [7654, MARTIN, 7698], [7844, TURNER, 7698], [7900, JAMES, 7698], [7934, MILLER, 7782], [7876, ADAMS, 7788], [7369, SMITH, 7902]]), sink where(condition apply2(fnValue =, apply(fnValue nth:2, argCode get(name e)), constant(0)), sink collect(get(name e)))))), argCode match(v0, apply(fnCode match((oldList, newList), from(sink join(pat d, exp get(name newList), sink join(pat e, exp constant([[7839, KING, 0], [7566, JONES, 7839], [7698, BLAKE, 7839], [7782, CLARK, 7839], [7788, SCOTT, 7566], [7902, FORD, 7566], [7499, ALLEN, 7698], [7521, WARD, 7698], [7654, MARTIN, 7698], [7844, TURNER, 7698], [7900, JAMES, 7698], [7934, MILLER, 7782], [7876, ADAMS, 7788], [7369, SMITH, 7902]]), sink where(condition apply2(fnValue =, apply(fnValue nth:2, argCode get(name e)), apply(fnValue nth:0, argCode get(name d))), sink collect(get(name e))))))), argCode get(name v0))))" > : string Relational.sum [1, 2, 3]; diff --git a/src/test/resources/script/relational.smli b/src/test/resources/script/relational.smli index 7ec5a332..54f4472a 100644 --- a/src/test/resources/script/relational.smli +++ b/src/test/resources/script/relational.smli @@ -1473,7 +1473,7 @@ yield {r.j, r.s}; > {j=8,s="morel"}] : {j:int, s:string} list Sys.plan(); > val it = -> "from(sink join(op join, pat r, exp apply(fnValue List.tabulate, argCode tuple(constant(6), match(i, tuple(get(name i), apply2(fnValue +, get(name i), constant(3)), apply3(fnValue String.substring, constant(morel), constant(0), get(name i)))))), sink collect(tuple(apply(fnValue nth:1, argCode get(name r)), apply(fnValue nth:2, argCode get(name r))))))" +> "from(sink join(pat r, exp apply(fnValue List.tabulate, argCode tuple(constant(6), match(i, tuple(get(name i), apply2(fnValue +, get(name i), constant(3)), apply3(fnValue String.substring, constant(morel), constant(0), get(name i)))))), sink collect(tuple(apply(fnValue nth:1, argCode get(name r)), apply(fnValue nth:2, argCode get(name r))))))" > : string Sys.set ("hybrid", true); @@ -1535,7 +1535,7 @@ end; > val it = ["Shaggy","Scooby"] : string list Sys.plan(); > val it = -> "from(sink join(op join, pat e_1, exp apply(fnCode match((emps, deptno), from(sink join(op join, pat e, exp get(name emps), sink where(condition apply2(fnValue =, apply(fnValue nth:0, argCode get(name e)), get(name deptno)), sink collect(get(name e)))))), argCode tuple(constant([[10, 100, Fred], [20, 101, Velma], [30, 102, Shaggy], [30, 103, Scooby]]), constant(30))), sink collect(apply(fnValue nth:2, argCode get(name e)))))" +> "from(sink join(pat e_1, exp apply(fnCode match((emps, deptno), from(sink join(pat e, exp get(name emps), sink where(condition apply2(fnValue =, apply(fnValue nth:0, argCode get(name e)), get(name deptno)), sink collect(get(name e)))))), argCode tuple(constant([[10, 100, Fred], [20, 101, Velma], [30, 102, Shaggy], [30, 103, Scooby]]), constant(30))), sink collect(apply(fnValue nth:2, argCode get(name e)))))" > : string (*) Same, via a predicate @@ -1550,7 +1550,7 @@ end; > val it = ["Shaggy","Scooby"] : string list Sys.plan(); > val it = -> "from(sink join(op join, pat e, exp constant([[10, 100, Fred], [20, 101, Velma], [30, 102, Shaggy], [30, 103, Scooby]]), sink where(condition apply2(fnValue =, apply(fnValue nth:0, argCode get(name e)), constant(30)), sink yield(codes [get(e)], sink collect(apply(fnValue nth:2, argCode get(name e)))))))" +> "from(sink join(pat e, exp constant([[10, 100, Fred], [20, 101, Velma], [30, 102, Shaggy], [30, 103, Scooby]]), sink where(condition apply2(fnValue =, apply(fnValue nth:0, argCode get(name e)), constant(30)), sink yield(codes [get(e)], sink collect(apply(fnValue nth:2, argCode get(name e)))))))" > : string (*) dummy diff --git a/src/test/resources/script/suchThat.smli b/src/test/resources/script/suchThat.smli index 5490d2d6..f718580f 100644 --- a/src/test/resources/script/suchThat.smli +++ b/src/test/resources/script/suchThat.smli @@ -50,6 +50,34 @@ where b = (i mod 2 = 0); > {b=true,i=6},{b=false,i=7},{b=true,i=8},{b=false,i=9}] > : {b:bool, i:int} list +(*) Unbound variables in declared 'join'; +(*) condition on 'j' occurs after 'join' +from i, j +where i > 0 andalso i < 3 +join k, m +where j > 4 andalso j < 7 + andalso k > 8 andalso k < 11 + andalso m > 12 andalso m < 15; +> val it = +> [{i=1,j=5,k=9,m=13},{i=1,j=5,k=9,m=14},{i=1,j=5,k=10,m=13}, +> {i=1,j=5,k=10,m=14},{i=1,j=6,k=9,m=13},{i=1,j=6,k=9,m=14}, +> {i=1,j=6,k=10,m=13},{i=1,j=6,k=10,m=14},{i=2,j=5,k=9,m=13}, +> {i=2,j=5,k=9,m=14},{i=2,j=5,k=10,m=13},{i=2,j=5,k=10,m=14},...] +> : {i:int, j:int, k:int, m:int} list + +(*) 'on' in 'from' +from i in [1, 2, 3], + j in [2, 3, 4] on j = i; +> val it = [{i=2,j=2},{i=3,j=3}] : {i:int, j:int} list + +from a in [1, 2], + b in [3, 4, 5] on a + b = 6 +where b < 5 +join c in [6, 7] on b + c = 10, + d in [7, 8]; +> val it = [{a=2,b=4,c=6,d=7},{a=2,b=4,c=6,d=8}] +> : {a:int, b:int, c:int, d:int} list + from dno, name, loc where {deptno = dno, dname = name, loc} elem scott.dept andalso dno > 20;