You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@calcite.apache.org by jh...@apache.org on 2014/12/30 16:41:22 UTC
incubator-calcite git commit: [CALCITE-451] Implement theta join,
inner and outer, in enumerable convention
Repository: incubator-calcite
Updated Branches:
refs/heads/master 0caa1c546 -> 167623f40
[CALCITE-451] Implement theta join, inner and outer, in enumerable convention
Project: http://git-wip-us.apache.org/repos/asf/incubator-calcite/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-calcite/commit/167623f4
Tree: http://git-wip-us.apache.org/repos/asf/incubator-calcite/tree/167623f4
Diff: http://git-wip-us.apache.org/repos/asf/incubator-calcite/diff/167623f4
Branch: refs/heads/master
Commit: 167623f40797f051450c2d8dda5b26998317bd94
Parents: 0caa1c5
Author: julianhyde <jh...@apache.org>
Authored: Tue Dec 30 00:24:32 2014 -0800
Committer: julianhyde <jh...@apache.org>
Committed: Tue Dec 30 00:24:32 2014 -0800
----------------------------------------------------------------------
.../adapter/enumerable/EnumerableJoinRule.java | 14 +-
.../adapter/enumerable/EnumerableThetaJoin.java | 178 +++++++++++++++++++
.../org/apache/calcite/runtime/Enumerables.java | 59 +++++-
.../org/apache/calcite/util/BuiltInMethod.java | 2 +
.../apache/calcite/runtime/EnumerablesTest.java | 97 ++++++++--
.../java/org/apache/calcite/test/JdbcTest.java | 21 +++
core/src/test/resources/sql/outer.oq | 41 +++++
.../calcite/linq4j/tree/FunctionExpression.java | 27 ++-
8 files changed, 419 insertions(+), 20 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/167623f4/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableJoinRule.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableJoinRule.java b/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableJoinRule.java
index 915b635..6ffc912 100644
--- a/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableJoinRule.java
+++ b/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableJoinRule.java
@@ -18,6 +18,7 @@ package org.apache.calcite.adapter.enumerable;
import org.apache.calcite.plan.Convention;
import org.apache.calcite.plan.RelOptCluster;
+import org.apache.calcite.plan.RelTraitSet;
import org.apache.calcite.rel.InvalidRelException;
import org.apache.calcite.rel.RelNode;
import org.apache.calcite.rel.convert.ConverterRule;
@@ -53,15 +54,24 @@ class EnumerableJoinRule extends ConverterRule {
}
newInputs.add(input);
}
+ final RelOptCluster cluster = join.getCluster();
+ final RelTraitSet traitSet =
+ join.getTraitSet().replace(EnumerableConvention.INSTANCE);
final RelNode left = newInputs.get(0);
final RelNode right = newInputs.get(1);
final JoinInfo info = JoinInfo.of(left, right, join.getCondition());
if (!info.isEqui() && join.getJoinType() != JoinRelType.INNER) {
// EnumerableJoinRel only supports equi-join. We can put a filter on top
// if it is an inner join.
- return null;
+ try {
+ return new EnumerableThetaJoin(cluster, traitSet, left, right,
+ join.getCondition(), join.getJoinType(),
+ join.getVariablesStopped());
+ } catch (InvalidRelException e) {
+ EnumerableRules.LOGGER.fine(e.toString());
+ return null;
+ }
}
- final RelOptCluster cluster = join.getCluster();
RelNode newRel;
try {
newRel = new EnumerableJoin(
http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/167623f4/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableThetaJoin.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableThetaJoin.java b/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableThetaJoin.java
new file mode 100644
index 0000000..bf4516a
--- /dev/null
+++ b/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableThetaJoin.java
@@ -0,0 +1,178 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF 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 org.apache.calcite.adapter.enumerable;
+
+import org.apache.calcite.linq4j.function.Predicate2;
+import org.apache.calcite.linq4j.tree.BlockBuilder;
+import org.apache.calcite.linq4j.tree.Expression;
+import org.apache.calcite.linq4j.tree.Expressions;
+import org.apache.calcite.linq4j.tree.ParameterExpression;
+import org.apache.calcite.plan.RelOptCluster;
+import org.apache.calcite.plan.RelOptCost;
+import org.apache.calcite.plan.RelOptPlanner;
+import org.apache.calcite.plan.RelTraitSet;
+import org.apache.calcite.rel.InvalidRelException;
+import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.core.Join;
+import org.apache.calcite.rel.core.JoinRelType;
+import org.apache.calcite.rel.metadata.RelMetadataQuery;
+import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.rex.RexProgramBuilder;
+import org.apache.calcite.util.BuiltInMethod;
+import org.apache.calcite.util.Pair;
+
+import com.google.common.collect.ImmutableList;
+
+import java.util.Set;
+
+/** Implementation of {@link org.apache.calcite.rel.core.Join} in
+ * {@link org.apache.calcite.adapter.enumerable.EnumerableConvention enumerable calling convention}
+ * that allows conditions that are not just {@code =} (equals). */
+public class EnumerableThetaJoin extends Join implements EnumerableRel {
+ protected EnumerableThetaJoin(RelOptCluster cluster, RelTraitSet traits,
+ RelNode left, RelNode right, RexNode condition, JoinRelType joinType,
+ Set<String> variablesStopped) throws InvalidRelException {
+ super(cluster, traits, left, right, condition, joinType,
+ variablesStopped);
+ }
+
+ @Override public EnumerableThetaJoin copy(RelTraitSet traitSet,
+ RexNode condition, RelNode left, RelNode right, JoinRelType joinType,
+ boolean semiJoinDone) {
+ try {
+ return new EnumerableThetaJoin(getCluster(), traitSet, left, right,
+ condition, joinType, variablesStopped);
+ } catch (InvalidRelException e) {
+ // Semantic error not possible. Must be a bug. Convert to
+ // internal error.
+ throw new AssertionError(e);
+ }
+ }
+
+ @Override public RelOptCost computeSelfCost(RelOptPlanner planner) {
+ double rowCount = RelMetadataQuery.getRowCount(this);
+
+ // Joins can be flipped, and for many algorithms, both versions are viable
+ // and have the same cost. To make the results stable between versions of
+ // the planner, make one of the versions slightly more expensive.
+ switch (joinType) {
+ case RIGHT:
+ rowCount = addEpsilon(rowCount);
+ break;
+ default:
+ if (left.getId() > right.getId()) {
+ rowCount = addEpsilon(rowCount);
+ }
+ }
+
+ final double rightRowCount = right.getRows();
+ final double leftRowCount = left.getRows();
+ if (Double.isInfinite(leftRowCount)) {
+ rowCount = leftRowCount;
+ }
+ if (Double.isInfinite(rightRowCount)) {
+ rowCount = rightRowCount;
+ }
+ return planner.getCostFactory().makeCost(rowCount, 0, 0);
+ }
+
+ private double addEpsilon(double d) {
+ assert d >= 0d;
+ final double d0 = d;
+ if (d < 10) {
+ // For small d, adding 1 would change the value significantly.
+ d *= 1.001d;
+ if (d != d0) {
+ return d;
+ }
+ }
+ // For medium d, add 1. Keeps integral values integral.
+ ++d;
+ if (d != d0) {
+ return d;
+ }
+ // For large d, adding 1 might not change the value. Add .1%.
+ // If d is NaN, this still will probably not change the value. That's OK.
+ d *= 1.001d;
+ return d;
+ }
+
+ public Result implement(EnumerableRelImplementor implementor, Prefer pref) {
+ final BlockBuilder builder = new BlockBuilder();
+ final Result leftResult =
+ implementor.visitChild(this, 0, (EnumerableRel) left, pref);
+ Expression leftExpression =
+ builder.append("left", leftResult.block);
+ final Result rightResult =
+ implementor.visitChild(this, 1, (EnumerableRel) right, pref);
+ Expression rightExpression =
+ builder.append("right", rightResult.block);
+ final PhysType physType =
+ PhysTypeImpl.of(implementor.getTypeFactory(),
+ getRowType(),
+ pref.preferArray());
+ final BlockBuilder builder2 = new BlockBuilder();
+ return implementor.result(
+ physType,
+ builder.append(
+ Expressions.call(BuiltInMethod.THETA_JOIN.method,
+ leftExpression,
+ rightExpression,
+ predicate(implementor,
+ builder2,
+ leftResult.physType,
+ rightResult.physType,
+ condition),
+ EnumUtils.joinSelector(joinType,
+ physType,
+ ImmutableList.of(leftResult.physType,
+ rightResult.physType)),
+ Expressions.constant(joinType.generatesNullsOnLeft()),
+ Expressions.constant(joinType.generatesNullsOnRight())))
+ .toBlock());
+ }
+
+ Expression predicate(EnumerableRelImplementor implementor,
+ BlockBuilder builder, PhysType leftPhysType, PhysType rightPhysType,
+ RexNode condition) {
+ final ParameterExpression left_ =
+ Expressions.parameter(leftPhysType.getJavaRowType(), "left");
+ final ParameterExpression right_ =
+ Expressions.parameter(rightPhysType.getJavaRowType(), "right");
+ final RexProgramBuilder program =
+ new RexProgramBuilder(
+ implementor.getTypeFactory().builder()
+ .addAll(left.getRowType().getFieldList())
+ .addAll(right.getRowType().getFieldList())
+ .build(),
+ getCluster().getRexBuilder());
+ program.addCondition(condition);
+ builder.add(
+ Expressions.return_(null,
+ RexToLixTranslator.translateCondition(program.getProgram(),
+ implementor.getTypeFactory(),
+ builder,
+ new RexToLixTranslator.InputGetterImpl(
+ ImmutableList.of(Pair.of((Expression) left_, leftPhysType),
+ Pair.of((Expression) right_, rightPhysType))),
+ implementor.allCorrelateVariables)));
+ return Expressions.lambda(Predicate2.class, builder.toBlock(), left_,
+ right_);
+ }
+}
+
+// End EnumerableThetaJoin.java
http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/167623f4/core/src/main/java/org/apache/calcite/runtime/Enumerables.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/runtime/Enumerables.java b/core/src/main/java/org/apache/calcite/runtime/Enumerables.java
index 629bced..c8ebe4d 100644
--- a/core/src/main/java/org/apache/calcite/runtime/Enumerables.java
+++ b/core/src/main/java/org/apache/calcite/runtime/Enumerables.java
@@ -25,12 +25,15 @@ import org.apache.calcite.linq4j.function.EqualityComparer;
import org.apache.calcite.linq4j.function.Function1;
import org.apache.calcite.linq4j.function.Function2;
import org.apache.calcite.linq4j.function.Predicate1;
+import org.apache.calcite.linq4j.function.Predicate2;
import org.apache.calcite.util.Bug;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
import java.util.List;
+import java.util.Set;
/**
* Utilities for processing {@link org.apache.calcite.linq4j.Enumerable}
@@ -104,6 +107,55 @@ public class Enumerables {
}
/**
+ * Correlates the elements of two sequences based on a predicate.
+ */
+ public static <TSource, TInner, TResult> Enumerable<TResult> thetaJoin(
+ final Enumerable<TSource> outer, final Enumerable<TInner> inner,
+ final Predicate2<TSource, TInner> predicate,
+ Function2<TSource, TInner, TResult> resultSelector,
+ final boolean generateNullsOnLeft,
+ final boolean generateNullsOnRight) {
+ // Building the result as a list is easy but hogs memory. We should iterate.
+ final List<TResult> result = Lists.newArrayList();
+ final Enumerator<TSource> lefts = outer.enumerator();
+ final List<TInner> rightList = inner.toList();
+ final Set<TInner> rightUnmatched;
+ if (generateNullsOnLeft) {
+ rightUnmatched = Sets.newIdentityHashSet();
+ rightUnmatched.addAll(rightList);
+ } else {
+ rightUnmatched = null;
+ }
+ while (lefts.moveNext()) {
+ int leftMatchCount = 0;
+ final TSource left = lefts.current();
+ final Enumerator<TInner> rights = Linq4j.iterableEnumerator(rightList);
+ while (rights.moveNext()) {
+ TInner right = rights.current();
+ if (predicate.apply(left, right)) {
+ ++leftMatchCount;
+ if (rightUnmatched != null) {
+ rightUnmatched.remove(right);
+ }
+ result.add(resultSelector.apply(left, right));
+ }
+ }
+ if (generateNullsOnRight && leftMatchCount == 0) {
+ result.add(resultSelector.apply(left, null));
+ }
+ }
+ if (rightUnmatched != null) {
+ final Enumerator<TInner> rights =
+ Linq4j.iterableEnumerator(rightUnmatched);
+ while (rights.moveNext()) {
+ TInner right = rights.current();
+ result.add(resultSelector.apply(null, right));
+ }
+ }
+ return Linq4j.asEnumerable(result);
+ }
+
+ /**
* Filters a sequence of values based on a
* predicate.
*/
@@ -254,8 +306,11 @@ public class Enumerables {
public TResult current() {
final List<Object> list = cartesians.current();
- return resultSelector.apply((TSource) list.get(0),
- (TInner) list.get(1));
+ @SuppressWarnings("unchecked") final TSource left =
+ (TSource) list.get(0);
+ @SuppressWarnings("unchecked") final TInner right =
+ (TInner) list.get(1);
+ return resultSelector.apply(left, right);
}
public boolean moveNext() {
http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/167623f4/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java b/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java
index ad83a92..5ef8a89 100644
--- a/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java
+++ b/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java
@@ -124,6 +124,8 @@ public enum BuiltInMethod {
SLICE0(Enumerables.class, "slice0", Enumerable.class),
SEMI_JOIN(Enumerables.class, "semiJoin", Enumerable.class, Enumerable.class,
Function1.class, Function1.class),
+ THETA_JOIN(Enumerables.class, "thetaJoin", Enumerable.class, Enumerable.class,
+ Predicate2.class, Function2.class, boolean.class, boolean.class),
CORRELATE_JOIN(ExtendedEnumerable.class, "correlateJoin",
CorrelateJoinType.class, Function1.class, Function2.class),
SELECT(ExtendedEnumerable.class, "select", Function1.class),
http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/167623f4/core/src/test/java/org/apache/calcite/runtime/EnumerablesTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/runtime/EnumerablesTest.java b/core/src/test/java/org/apache/calcite/runtime/EnumerablesTest.java
index 6a5fd1b..bfbf339 100644
--- a/core/src/test/java/org/apache/calcite/runtime/EnumerablesTest.java
+++ b/core/src/test/java/org/apache/calcite/runtime/EnumerablesTest.java
@@ -21,6 +21,7 @@ import org.apache.calcite.linq4j.Linq4j;
import org.apache.calcite.linq4j.function.Function1;
import org.apache.calcite.linq4j.function.Function2;
import org.apache.calcite.linq4j.function.Functions;
+import org.apache.calcite.linq4j.function.Predicate2;
import com.google.common.collect.Lists;
@@ -36,19 +37,39 @@ import static org.junit.Assert.assertThat;
* Unit tests for {@link org.apache.calcite.runtime.Enumerables}.
*/
public class EnumerablesTest {
+ private static final Enumerable<Emp> EMPS = Linq4j.asEnumerable(
+ Arrays.asList(
+ new Emp(10, "Fred"),
+ new Emp(20, "Theodore"),
+ new Emp(20, "Sebastian"),
+ new Emp(30, "Joe")));
+
+ private static final Enumerable<Dept> DEPTS = Linq4j.asEnumerable(
+ Arrays.asList(
+ new Dept(20, "Sales"),
+ new Dept(15, "Marketing")));
+
+ private static final Function2<Emp, Dept, String> EMP_DEPT_TO_STRING =
+ new Function2<Emp, Dept, String>() {
+ public String apply(Emp v0, Dept v1) {
+ return "{" + (v0 == null ? null : v0.name)
+ + ", " + (v0 == null ? null : v0.deptno)
+ + ", " + (v1 == null ? null : v1.deptno)
+ + ", " + (v1 == null ? null : v1.name)
+ + "}";
+ }
+ };
+
+ private static final Predicate2<Emp, Dept> EQUAL_DEPTNO =
+ new Predicate2<Emp, Dept>() {
+ public boolean apply(Emp v0, Dept v1) {
+ return v0.deptno == v1.deptno;
+ }
+ };
+
@Test public void testSemiJoin() {
assertThat(
- Enumerables.semiJoin(
- Linq4j.asEnumerable(
- Arrays.asList(
- new Emp(10, "Fred"),
- new Emp(20, "Theodore"),
- new Emp(20, "Sebastian"),
- new Emp(30, "Joe"))),
- Linq4j.asEnumerable(
- Arrays.asList(
- new Dept(20, "Sales"),
- new Dept(15, "Marketing"))),
+ Enumerables.semiJoin(EMPS, DEPTS,
new Function1<Emp, Integer>() {
public Integer apply(Emp a0) {
return a0.deptno;
@@ -162,6 +183,60 @@ public class EnumerablesTest {
}, false, false);
}
+ @Test public void testThetaJoin() {
+ assertThat(
+ Enumerables.thetaJoin(EMPS, DEPTS, EQUAL_DEPTNO, EMP_DEPT_TO_STRING,
+ false, false).toList().toString(),
+ equalTo("[{Theodore, 20, 20, Sales}, {Sebastian, 20, 20, Sales}]"));
+ }
+
+ @Test public void testThetaLeftJoin() {
+ assertThat(
+ Enumerables.thetaJoin(EMPS, DEPTS, EQUAL_DEPTNO, EMP_DEPT_TO_STRING,
+ false, true).toList().toString(),
+ equalTo("[{Fred, 10, null, null}, {Theodore, 20, 20, Sales}, "
+ + "{Sebastian, 20, 20, Sales}, {Joe, 30, null, null}]"));
+ }
+
+ @Test public void testThetaRightJoin() {
+ assertThat(
+ Enumerables.thetaJoin(EMPS, DEPTS, EQUAL_DEPTNO, EMP_DEPT_TO_STRING,
+ true, false).toList().toString(),
+ equalTo("[{Theodore, 20, 20, Sales}, {Sebastian, 20, 20, Sales}, "
+ + "{null, null, 15, Marketing}]"));
+ }
+
+ @Test public void testThetaFullJoin() {
+ assertThat(
+ Enumerables.thetaJoin(EMPS, DEPTS, EQUAL_DEPTNO, EMP_DEPT_TO_STRING,
+ true, true).toList().toString(),
+ equalTo("[{Fred, 10, null, null}, {Theodore, 20, 20, Sales}, "
+ + "{Sebastian, 20, 20, Sales}, {Joe, 30, null, null}, "
+ + "{null, null, 15, Marketing}]"));
+ }
+
+ @Test public void testThetaFullJoinLeftEmpty() {
+ assertThat(
+ Enumerables.thetaJoin(EMPS.take(0), DEPTS, EQUAL_DEPTNO,
+ EMP_DEPT_TO_STRING, true, true).toList().toString(),
+ equalTo("[{null, null, 20, Sales}, {null, null, 15, Marketing}]"));
+ }
+
+ @Test public void testThetaFullJoinRightEmpty() {
+ assertThat(
+ Enumerables.thetaJoin(EMPS, DEPTS.take(0), EQUAL_DEPTNO,
+ EMP_DEPT_TO_STRING, true, true).toList().toString(),
+ equalTo("[{Fred, 10, null, null}, {Theodore, 20, null, null}, "
+ + "{Sebastian, 20, null, null}, {Joe, 30, null, null}]"));
+ }
+
+ @Test public void testThetaFullJoinBothEmpty() {
+ assertThat(
+ Enumerables.thetaJoin(EMPS.take(0), DEPTS.take(0), EQUAL_DEPTNO,
+ EMP_DEPT_TO_STRING, true, true).toList().toString(),
+ equalTo("[]"));
+ }
+
/** Employee record. */
private static class Emp {
final int deptno;
http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/167623f4/core/src/test/java/org/apache/calcite/test/JdbcTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/test/JdbcTest.java b/core/src/test/java/org/apache/calcite/test/JdbcTest.java
index e166631..6a44e01 100644
--- a/core/src/test/java/org/apache/calcite/test/JdbcTest.java
+++ b/core/src/test/java/org/apache/calcite/test/JdbcTest.java
@@ -1587,6 +1587,27 @@ public class JdbcTest {
}
/** Test case for
+ * <a href="https://issues.apache.org/jira/browse/CALCITE-451">[CALCITE-451]
+ * Implement theta join, inner and outer, in enumerable convention</a>. */
+ @Test public void testThetaJoin() {
+ CalciteAssert.that()
+ .with(CalciteAssert.Config.REGULAR)
+ .query(
+ "select e.\"empid\", d.\"name\", e.\"name\"\n"
+ + "from \"hr\".\"emps\" as e\n"
+ + "left join \"hr\".\"depts\" as d\n"
+ + "on e.\"deptno\" < d.\"deptno\"\n")
+ .returnsUnordered("empid=100; name=Marketing; name=Bill",
+ "empid=100; name=HR; name=Bill",
+ "empid=200; name=Marketing; name=Eric",
+ "empid=200; name=HR; name=Eric",
+ "empid=150; name=Marketing; name=Sebastian",
+ "empid=150; name=HR; name=Sebastian",
+ "empid=110; name=Marketing; name=Theodore",
+ "empid=110; name=HR; name=Theodore");
+ }
+
+ /** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-35">CALCITE-35</a>,
* "Support parenthesized sub-clause in JOIN". */
@Ignore
http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/167623f4/core/src/test/resources/sql/outer.oq
----------------------------------------------------------------------
diff --git a/core/src/test/resources/sql/outer.oq b/core/src/test/resources/sql/outer.oq
index e371fe9..92071b1 100644
--- a/core/src/test/resources/sql/outer.oq
+++ b/core/src/test/resources/sql/outer.oq
@@ -228,6 +228,47 @@ select * from (select * from emp where gender ='F') as emp full join dept on emp
!ok
+# same as above, but expressed as a theta-join
+select * from (select * from emp where gender ='F') as emp full join dept on emp.deptno - dept.deptno = 0;
++-------+--------+--------+---------+-------------+
+| ENAME | DEPTNO | GENDER | DEPTNO0 | DNAME |
++-------+--------+--------+---------+-------------+
+| Jane | 10 | F | 10 | Sales |
+| Susan | 30 | F | 30 | Engineering |
+| Alice | 30 | F | 30 | Engineering |
+| Eve | 50 | F | | |
+| Grace | 60 | F | | |
+| Wilma | | F | | |
+| | | | 20 | Marketing |
+| | | | 40 | Empty |
++-------+--------+--------+---------+-------------+
+(8 rows)
+
+!ok
+EnumerableThetaJoin(condition=[=(-($1, $3), 0)], joinType=[full])
+ EnumerableCalc(expr#0..2=[{inputs}], expr#3=['F'], expr#4=[=($t2, $t3)], proj#0..2=[{exprs}], $condition=[$t4])
+ EnumerableUnion(all=[true])
+ EnumerableCalc(expr#0=[{inputs}], expr#1=['Jane'], expr#2=[10], expr#3=['F'], EXPR$0=[$t1], EXPR$1=[$t2], EXPR$2=[$t3])
+ EnumerableValues(tuples=[[{ 0 }]])
+ EnumerableCalc(expr#0=[{inputs}], expr#1=['Bob'], expr#2=[10], expr#3=['M'], EXPR$0=[$t1], EXPR$1=[$t2], EXPR$2=[$t3])
+ EnumerableValues(tuples=[[{ 0 }]])
+ EnumerableCalc(expr#0=[{inputs}], expr#1=['Eric'], expr#2=[20], expr#3=['M'], EXPR$0=[$t1], EXPR$1=[$t2], EXPR$2=[$t3])
+ EnumerableValues(tuples=[[{ 0 }]])
+ EnumerableCalc(expr#0=[{inputs}], expr#1=['Susan'], expr#2=[30], expr#3=['F'], EXPR$0=[$t1], EXPR$1=[$t2], EXPR$2=[$t3])
+ EnumerableValues(tuples=[[{ 0 }]])
+ EnumerableCalc(expr#0=[{inputs}], expr#1=['Alice'], expr#2=[30], expr#3=['F'], EXPR$0=[$t1], EXPR$1=[$t2], EXPR$2=[$t3])
+ EnumerableValues(tuples=[[{ 0 }]])
+ EnumerableCalc(expr#0=[{inputs}], expr#1=['Adam'], expr#2=[50], expr#3=['M'], EXPR$0=[$t1], EXPR$1=[$t2], EXPR$2=[$t3])
+ EnumerableValues(tuples=[[{ 0 }]])
+ EnumerableCalc(expr#0=[{inputs}], expr#1=['Eve'], expr#2=[50], expr#3=['F'], EXPR$0=[$t1], EXPR$1=[$t2], EXPR$2=[$t3])
+ EnumerableValues(tuples=[[{ 0 }]])
+ EnumerableCalc(expr#0=[{inputs}], expr#1=['Grace'], expr#2=[60], expr#3=['F'], EXPR$0=[$t1], EXPR$1=[$t2], EXPR$2=[$t3])
+ EnumerableValues(tuples=[[{ 0 }]])
+ EnumerableCalc(expr#0=[{inputs}], expr#1=['Wilma'], expr#2=[null], expr#3=['F'], EXPR$0=[$t1], EXPR$1=[$t2], EXPR$2=[$t3])
+ EnumerableValues(tuples=[[{ 0 }]])
+ EnumerableCalc(expr#0..1=[{inputs}], proj#0..1=[{exprs}])
+ EnumerableValues(tuples=[[{ 10, 'Sales ' }, { 20, 'Marketing ' }, { 30, 'Engineering' }, { 40, 'Empty ' }]])
+!plan
# End outer.oq
http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/167623f4/linq4j/src/main/java/org/apache/calcite/linq4j/tree/FunctionExpression.java
----------------------------------------------------------------------
diff --git a/linq4j/src/main/java/org/apache/calcite/linq4j/tree/FunctionExpression.java b/linq4j/src/main/java/org/apache/calcite/linq4j/tree/FunctionExpression.java
index 705693c..cc681df 100644
--- a/linq4j/src/main/java/org/apache/calcite/linq4j/tree/FunctionExpression.java
+++ b/linq4j/src/main/java/org/apache/calcite/linq4j/tree/FunctionExpression.java
@@ -172,10 +172,14 @@ public final class FunctionExpression<F extends Function<?>>
// Generate an intermediate bridge method if at least one parameter is
// primitive.
+ final String bridgeResultTypeName =
+ isAbstractMethodPrimitive()
+ ? Types.className(bridgeResultType)
+ : Types.boxClassName(bridgeResultType);
if (!boxBridgeParams.equals(params)) {
writer
.append("public ")
- .append(Types.boxClassName(bridgeResultType))
+ .append(bridgeResultTypeName)
.list(" " + methodName + "(", ", ", ") ", boxBridgeParams)
.begin("{\n")
.list("return " + methodName + "(\n", ",\n", ");\n", boxBridgeArgs)
@@ -190,7 +194,7 @@ public final class FunctionExpression<F extends Function<?>>
if (!bridgeParams.equals(params)) {
writer
.append("public ")
- .append(Types.boxClassName(bridgeResultType))
+ .append(bridgeResultTypeName)
.list(" " + methodName + "(", ", ", ") ", bridgeParams)
.begin("{\n")
.list("return " + methodName + "(\n", ",\n", ");\n", bridgeArgs)
@@ -200,11 +204,24 @@ public final class FunctionExpression<F extends Function<?>>
writer.end("}\n");
}
+ private boolean isAbstractMethodPrimitive() {
+ Method method = getAbstractMethod();
+ return method != null && Primitive.is(method.getReturnType());
+ }
+
private String getAbstractMethodName() {
- if (type.toString().contains("CalciteFlatMapFunction")) {
- return "call"; // FIXME
+ final Method abstractMethod = getAbstractMethod();
+ assert abstractMethod != null;
+ return abstractMethod.getName();
+ }
+
+ private Method getAbstractMethod() {
+ if (type instanceof Class
+ && ((Class) type).isInterface()
+ && ((Class) type).getDeclaredMethods().length == 1) {
+ return ((Class) type).getDeclaredMethods()[0];
}
- return "apply";
+ return null;
}
@Override public boolean equals(Object o) {