Skip to content

Commit

Permalink
[CALCITE-3542] Implement RepeatUnion All=false
Browse files Browse the repository at this point in the history
  • Loading branch information
rubenada committed Dec 5, 2019
1 parent 170bb9b commit 3559b3b
Show file tree
Hide file tree
Showing 6 changed files with 240 additions and 92 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import org.apache.calcite.rel.RelNode;
import org.apache.calcite.rel.core.RepeatUnion;
import org.apache.calcite.util.BuiltInMethod;
import org.apache.calcite.util.Util;

import java.util.List;

Expand Down Expand Up @@ -53,12 +54,8 @@ public class EnumerableRepeatUnion extends RepeatUnion implements EnumerableRel
}

@Override public Result implement(EnumerableRelImplementor implementor, Prefer pref) {
if (!all) {
throw new UnsupportedOperationException(
"Only EnumerableRepeatUnion ALL is supported");
}

// return repeatUnionAll(<seedExp>, <iterativeExp>, iterationLimit);
// return repeatUnion(<seedExp>, <iterativeExp>, iterationLimit, all, <comparer>);

BlockBuilder builder = new BlockBuilder();
RelNode seed = getSeedRel();
Expand All @@ -70,17 +67,20 @@ public class EnumerableRepeatUnion extends RepeatUnion implements EnumerableRel
Expression seedExp = builder.append("seed", seedResult.block);
Expression iterativeExp = builder.append("iteration", iterationResult.block);

PhysType physType = PhysTypeImpl.of(
implementor.getTypeFactory(),
getRowType(),
pref.prefer(seedResult.format));

Expression unionExp = Expressions.call(
BuiltInMethod.REPEAT_UNION_ALL.method,
BuiltInMethod.REPEAT_UNION.method,
seedExp,
iterativeExp,
Expressions.constant(iterationLimit, int.class));
Expressions.constant(iterationLimit, int.class),
Expressions.constant(all, boolean.class),
Util.first(physType.comparer(), Expressions.call(BuiltInMethod.IDENTITY_COMPARER.method)));
builder.add(unionExp);

PhysType physType = PhysTypeImpl.of(
implementor.getTypeFactory(),
getRowType(),
pref.prefer(seedResult.format));
return implementor.result(physType, builder.toBlock());
}

Expand Down
4 changes: 2 additions & 2 deletions core/src/main/java/org/apache/calcite/util/BuiltInMethod.java
Original file line number Diff line number Diff line change
Expand Up @@ -221,8 +221,8 @@ public enum BuiltInMethod {
Comparator.class),
UNION(ExtendedEnumerable.class, "union", Enumerable.class),
CONCAT(ExtendedEnumerable.class, "concat", Enumerable.class),
REPEAT_UNION_ALL(EnumerableDefaults.class, "repeatUnionAll", Enumerable.class,
Enumerable.class, int.class),
REPEAT_UNION(EnumerableDefaults.class, "repeatUnion", Enumerable.class,
Enumerable.class, int.class, boolean.class, EqualityComparer.class),
LAZY_COLLECTION_SPOOL(EnumerableDefaults.class, "lazyCollectionSpool", Collection.class,
Enumerable.class),
INTERSECT(ExtendedEnumerable.class, "intersect", Enumerable.class, boolean.class),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import org.apache.calcite.adapter.java.ReflectiveSchema;
import org.apache.calcite.rel.RelNode;
import org.apache.calcite.rel.core.JoinRelType;
import org.apache.calcite.rex.RexNode;
import org.apache.calcite.schema.Schema;
import org.apache.calcite.test.CalciteAssert;
import org.apache.calcite.test.HierarchySchema;
Expand All @@ -30,7 +31,9 @@
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Function;

/**
Expand All @@ -55,33 +58,65 @@ public class EnumerableRepeatUnionHierarchyTest {
private static final String EMP4 = "empid=4; name=Emp4";
private static final String EMP5 = "empid=5; name=Emp5";

private static final int[] ID1 = new int[]{1};
private static final String ID1_STR = Arrays.toString(ID1);
private static final int[] ID2 = new int[]{2};
private static final String ID2_STR = Arrays.toString(ID2);
private static final int[] ID3 = new int[]{3};
private static final String ID3_STR = Arrays.toString(ID3);
private static final int[] ID4 = new int[]{4};
private static final String ID4_STR = Arrays.toString(ID4);
private static final int[] ID5 = new int[]{5};
private static final String ID5_STR = Arrays.toString(ID5);
private static final int[] ID3_5 = new int[]{3, 5};
private static final String ID3_5_STR = Arrays.toString(ID3_5);
private static final int[] ID1_3 = new int[]{1, 3};
private static final String ID1_3_STR = Arrays.toString(ID1_3);

public static Iterable<Object[]> data() {

return Arrays.asList(new Object[][] {
{ 1, true, -1, new String[]{EMP1} },
{ 2, true, -2, new String[]{EMP2, EMP1} },
{ 3, true, -1, new String[]{EMP3, EMP2, EMP1} },
{ 4, true, -5, new String[]{EMP4, EMP1} },
{ 5, true, -1, new String[]{EMP5, EMP2, EMP1} },
{ 3, true, 0, new String[]{EMP3} },
{ 3, true, 1, new String[]{EMP3, EMP2} },
{ 3, true, 2, new String[]{EMP3, EMP2, EMP1} },
{ 3, true, 10, new String[]{EMP3, EMP2, EMP1} },

{ 1, false, -1, new String[]{EMP1, EMP2, EMP4, EMP3, EMP5} },
{ 2, false, -10, new String[]{EMP2, EMP3, EMP5} },
{ 3, false, -100, new String[]{EMP3} },
{ 4, false, -1, new String[]{EMP4} },
{ 1, false, 0, new String[]{EMP1} },
{ 1, false, 1, new String[]{EMP1, EMP2, EMP4} },
{ 1, false, 2, new String[]{EMP1, EMP2, EMP4, EMP3, EMP5} },
{ 1, false, 20, new String[]{EMP1, EMP2, EMP4, EMP3, EMP5} },
{ true, ID1, ID1_STR, true, -1, new String[]{EMP1} },
{ true, ID2, ID2_STR, true, -2, new String[]{EMP2, EMP1} },
{ true, ID3, ID3_STR, true, -1, new String[]{EMP3, EMP2, EMP1} },
{ true, ID4, ID4_STR, true, -5, new String[]{EMP4, EMP1} },
{ true, ID5, ID5_STR, true, -1, new String[]{EMP5, EMP2, EMP1} },
{ true, ID3, ID3_STR, true, 0, new String[]{EMP3} },
{ true, ID3, ID3_STR, true, 1, new String[]{EMP3, EMP2} },
{ true, ID3, ID3_STR, true, 2, new String[]{EMP3, EMP2, EMP1} },
{ true, ID3, ID3_STR, true, 10, new String[]{EMP3, EMP2, EMP1} },

{ true, ID1, ID1_STR, false, -1, new String[]{EMP1, EMP2, EMP4, EMP3, EMP5} },
{ true, ID2, ID2_STR, false, -10, new String[]{EMP2, EMP3, EMP5} },
{ true, ID3, ID3_STR, false, -100, new String[]{EMP3} },
{ true, ID4, ID4_STR, false, -1, new String[]{EMP4} },
{ true, ID1, ID1_STR, false, 0, new String[]{EMP1} },
{ true, ID1, ID1_STR, false, 1, new String[]{EMP1, EMP2, EMP4} },
{ true, ID1, ID1_STR, false, 2, new String[]{EMP1, EMP2, EMP4, EMP3, EMP5} },
{ true, ID1, ID1_STR, false, 20, new String[]{EMP1, EMP2, EMP4, EMP3, EMP5} },

// tests to verify all=true vs all=false
{ true, ID3_5, ID3_5_STR, true, -1, new String[]{EMP3, EMP5, EMP2, EMP2, EMP1, EMP1} },
{ false, ID3_5, ID3_5_STR, true, -1, new String[]{EMP3, EMP5, EMP2, EMP1} },
{ true, ID3_5, ID3_5_STR, true, 0, new String[]{EMP3, EMP5} },
{ false, ID3_5, ID3_5_STR, true, 0, new String[]{EMP3, EMP5} },
{ true, ID3_5, ID3_5_STR, true, 1, new String[]{EMP3, EMP5, EMP2, EMP2} },
{ false, ID3_5, ID3_5_STR, true, 1, new String[]{EMP3, EMP5, EMP2} },
{ true, ID1_3, ID1_3_STR, false, -1, new String[]{EMP1, EMP3, EMP2, EMP4, EMP3, EMP5} },
{ false, ID1_3, ID1_3_STR, false, -1, new String[]{EMP1, EMP3, EMP2, EMP4, EMP5} },
});
}

@ParameterizedTest
@ParameterizedTest(name = "{index} : hierarchy(startIds:{2}, ascendant:{3}, "
+ "maxDepth:{4}, all:{0})")
@MethodSource("data")
public void testHierarchy(int startId, boolean ascendant,
int maxDepth, String[] expected) {
public void testHierarchy(
boolean all,
int[] startIds,
String startIdsStr,
boolean ascendant,
int maxDepth,
String[] expected) {
final String fromField;
final String toField;
if (ascendant) {
Expand All @@ -96,27 +131,40 @@ public void testHierarchy(int startId, boolean ascendant,
CalciteAssert.that()
.withSchema("s", schema)
.query("?")
.withRel(hierarchy(startId, fromField, toField, maxDepth))
.withRel(buildHierarchy(all, startIds, fromField, toField, maxDepth))
.returnsOrdered(expected);
}

private Function<RelBuilder, RelNode> hierarchy(int startId, String fromField,
String toField, int maxDepth) {
private Function<RelBuilder, RelNode> buildHierarchy(
boolean all,
int[] startIds,
String fromField,
String toField,
int maxDepth) {

// WITH RECURSIVE delta(empid, name) as (
// SELECT empid, name FROM emps WHERE empid = <startId>
// UNION ALL
// SELECT empid, name FROM emps WHERE empid IN (<startIds>)
// UNION [ALL]
// SELECT e.empid, e.name FROM delta d
// JOIN hierarchies h ON d.empid = h.<fromField>
// JOIN emps e ON h.<toField> = e.empid
// )
// SELECT empid, name FROM delta
return builder -> builder
.scan("s", "emps")
.filter(
return builder -> {
builder
.scan("s", "emps");

final List<RexNode> filters = new ArrayList<>();
for (int startId : startIds) {
filters.add(
builder.equals(
builder.field("empid"),
builder.literal(startId)))
builder.literal(startId)));
}

builder
.filter(
builder.or(filters))
.project(
builder.field("emps", "empid"),
builder.field("emps", "name"))
Expand All @@ -137,8 +185,10 @@ private Function<RelBuilder, RelNode> hierarchy(int startId, String fromField,
.project(
builder.field("emps", "empid"),
builder.field("emps", "name"))
.repeatUnion("#DELTA#", true, maxDepth)
.build();
.repeatUnion("#DELTA#", all, maxDepth);

return builder.build();
};
}

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,72 @@ public class EnumerableRepeatUnionTest {
.returnsOrdered("i=1", "i=2", "i=3", "i=4", "i=5", "i=6", "i=7", "i=8", "i=9", "i=10");
}

@Test public void testGenerateNumbers2() {
CalciteAssert.that()
.query("?")
.withRel(
// WITH RECURSIVE aux(i) AS (
// VALUES (0)
// UNION -- (ALL would generate an infinite loop!)
// SELECT (i+1)%10 FROM aux WHERE i < 10
// )
// SELECT * FROM aux
builder -> builder
.values(new String[] { "i" }, 0)
.transientScan("AUX")
.filter(
builder.call(SqlStdOperatorTable.LESS_THAN,
builder.field(0),
builder.literal(10)))
.project(
builder.call(SqlStdOperatorTable.MOD,
builder.call(SqlStdOperatorTable.PLUS,
builder.field(0),
builder.literal(1)),
builder.literal(10)))
.repeatUnion("AUX", false)
.build())
.returnsOrdered("i=0", "i=1", "i=2", "i=3", "i=4", "i=5", "i=6", "i=7", "i=8", "i=9");
}

@Test public void testGenerateNumbers3() {
CalciteAssert.that()
.query("?")
.withRel(
// WITH RECURSIVE aux(i, j) AS (
// VALUES (0, 0)
// UNION -- (ALL would generate an infinite loop!)
// SELECT (i+1)%10, j FROM aux WHERE i < 10
// )
// SELECT * FROM aux
builder -> builder
.values(new String[] { "i", "j" }, 0, 0)
.transientScan("AUX")
.filter(
builder.call(SqlStdOperatorTable.LESS_THAN,
builder.field(0),
builder.literal(10)))
.project(
builder.call(SqlStdOperatorTable.MOD,
builder.call(SqlStdOperatorTable.PLUS,
builder.field(0),
builder.literal(1)),
builder.literal(10)),
builder.field(1))
.repeatUnion("AUX", false)
.build())
.returnsOrdered("i=0; j=0",
"i=1; j=0",
"i=2; j=0",
"i=3; j=0",
"i=4; j=0",
"i=5; j=0",
"i=6; j=0",
"i=7; j=0",
"i=8; j=0",
"i=9; j=0");
}

@Test public void testFactorial() {
CalciteAssert.that()
.query("?")
Expand Down

0 comments on commit 3559b3b

Please sign in to comment.