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/09/12 10:06:44 UTC
[2/2] git commit: Now,
a lattice can materialize an aggregate-join and use it in a
subsequent query.
Now, a lattice can materialize an aggregate-join and use it in a subsequent query.
Add AggregateFilterTransposeRule, to help crystallize a query into a form that matches an aggregate table.
Minor fixes and tweaks to BreadthFirstIterator, DepthFirstIterator, ArrayTable, StarTable, Mappings.
Project: http://git-wip-us.apache.org/repos/asf/incubator-optiq/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-optiq/commit/1d2cb011
Tree: http://git-wip-us.apache.org/repos/asf/incubator-optiq/tree/1d2cb011
Diff: http://git-wip-us.apache.org/repos/asf/incubator-optiq/diff/1d2cb011
Branch: refs/heads/master
Commit: 1d2cb011856d5d7c43cd55df6d0ceb71812ff24e
Parents: fbac0d6
Author: Julian Hyde <jh...@apache.org>
Authored: Mon Sep 8 18:05:28 2014 -0700
Committer: Julian Hyde <jh...@apache.org>
Committed: Fri Sep 12 00:33:24 2014 -0700
----------------------------------------------------------------------
core/pom.xml | 2 +-
.../net/hydromatic/optiq/impl/StarTable.java | 5 +
.../hydromatic/optiq/impl/clone/ArrayTable.java | 2 +-
.../optiq/impl/clone/CloneSchema.java | 10 +-
.../hydromatic/optiq/rules/java/JavaRules.java | 4 -
.../optiq/util/graph/BreadthFirstIterator.java | 32 +++-
.../optiq/util/graph/DepthFirstIterator.java | 37 ++--
.../rel/rules/AggregateFilterTransposeRule.java | 139 ++++++++++++++
.../rel/rules/AggregateStarTableRule.java | 1 -
.../org/eigenbase/relopt/RelOptLattice.java | 8 +-
.../eigenbase/relopt/RelOptMaterialization.java | 187 +++++++++++++------
.../java/org/eigenbase/relopt/RelOptUtil.java | 14 ++
.../org/eigenbase/relopt/hep/HepPlanner.java | 8 +-
.../relopt/volcano/VolcanoPlanner.java | 3 +
.../eigenbase/rex/RexPermuteInputsShuttle.java | 31 ++-
.../main/java/org/eigenbase/rex/RexUtil.java | 15 +-
.../sql2rel/RelStructuredTypeFlattener.java | 5 +-
.../org/eigenbase/util/mapping/Mappings.java | 59 ++++--
.../net/hydromatic/optiq/test/FoodmartTest.java | 2 +-
.../net/hydromatic/optiq/test/JdbcTest.java | 28 ++-
.../net/hydromatic/optiq/test/LatticeTest.java | 13 +-
.../optiq/test/LinqFrontJdbcBackTest.java | 2 +-
.../net/hydromatic/optiq/test/OptiqAssert.java | 80 ++++----
.../optiq/util/graph/DirectedGraphTest.java | 21 ++-
.../org/eigenbase/util/mapping/MappingTest.java | 11 +-
25 files changed, 536 insertions(+), 183 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/1d2cb011/core/pom.xml
----------------------------------------------------------------------
diff --git a/core/pom.xml b/core/pom.xml
index 93ca43d..c1957f3 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -55,7 +55,7 @@ limitations under the License.
<threadCount>1</threadCount>
<perCoreThreadCount>true</perCoreThreadCount>
<parallel>both</parallel>
- <argLine>-Xmx1024m -XX:MaxPermSize=256m</argLine>
+ <argLine>-Xmx1536m -XX:MaxPermSize=256m</argLine>
</configuration>
</plugin>
<plugin>
http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/1d2cb011/core/src/main/java/net/hydromatic/optiq/impl/StarTable.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/net/hydromatic/optiq/impl/StarTable.java b/core/src/main/java/net/hydromatic/optiq/impl/StarTable.java
index 37d3dc9..c4284e0 100644
--- a/core/src/main/java/net/hydromatic/optiq/impl/StarTable.java
+++ b/core/src/main/java/net/hydromatic/optiq/impl/StarTable.java
@@ -16,6 +16,7 @@
*/
package net.hydromatic.optiq.impl;
+import net.hydromatic.optiq.Schema;
import net.hydromatic.optiq.Table;
import net.hydromatic.optiq.TranslatableTable;
import net.hydromatic.optiq.materialize.Lattice;
@@ -66,6 +67,10 @@ public class StarTable extends AbstractTable implements TranslatableTable {
return new StarTable(lattice, ImmutableList.copyOf(tables));
}
+ @Override public Schema.TableType getJdbcTableType() {
+ return Schema.TableType.STAR;
+ }
+
public RelDataType getRowType(RelDataTypeFactory typeFactory) {
final List<RelDataType> typeList = new ArrayList<RelDataType>();
final List<String> nameList = new ArrayList<String>();
http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/1d2cb011/core/src/main/java/net/hydromatic/optiq/impl/clone/ArrayTable.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/net/hydromatic/optiq/impl/clone/ArrayTable.java b/core/src/main/java/net/hydromatic/optiq/impl/clone/ArrayTable.java
index 5d445aa..8b56b7b 100644
--- a/core/src/main/java/net/hydromatic/optiq/impl/clone/ArrayTable.java
+++ b/core/src/main/java/net/hydromatic/optiq/impl/clone/ArrayTable.java
@@ -576,7 +576,7 @@ class ArrayTable extends AbstractQueryableTable {
public Object getObject(Object dataSet, int ordinal) {
Pair<Object, Integer> pair = (Pair<Object, Integer>) dataSet;
- return pair.getValue();
+ return pair.left;
}
public int getInt(Object dataSet, int ordinal) {
http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/1d2cb011/core/src/main/java/net/hydromatic/optiq/impl/clone/CloneSchema.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/net/hydromatic/optiq/impl/clone/CloneSchema.java b/core/src/main/java/net/hydromatic/optiq/impl/clone/CloneSchema.java
index 3d534a6..e3fa950 100644
--- a/core/src/main/java/net/hydromatic/optiq/impl/clone/CloneSchema.java
+++ b/core/src/main/java/net/hydromatic/optiq/impl/clone/CloneSchema.java
@@ -62,10 +62,12 @@ public class CloneSchema extends AbstractSchema {
protected Map<String, Table> getTableMap() {
final Map<String, Table> map = new LinkedHashMap<String, Table>();
for (String name : sourceSchema.getTableNames()) {
- final QueryableTable sourceTable =
- (QueryableTable) sourceSchema.getTable(name);
- map.put(name,
- createCloneTable(MATERIALIZATION_CONNECTION, sourceTable, name));
+ final Table table = sourceSchema.getTable(name);
+ if (table instanceof QueryableTable) {
+ final QueryableTable sourceTable = (QueryableTable) table;
+ map.put(name,
+ createCloneTable(MATERIALIZATION_CONNECTION, sourceTable, name));
+ }
}
return map;
}
http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/1d2cb011/core/src/main/java/net/hydromatic/optiq/rules/java/JavaRules.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/net/hydromatic/optiq/rules/java/JavaRules.java b/core/src/main/java/net/hydromatic/optiq/rules/java/JavaRules.java
index 35d3f9f..d9ead6c 100644
--- a/core/src/main/java/net/hydromatic/optiq/rules/java/JavaRules.java
+++ b/core/src/main/java/net/hydromatic/optiq/rules/java/JavaRules.java
@@ -742,8 +742,6 @@ public class JavaRules {
public static class EnumerableCalcRel
extends CalcRelBase
implements EnumerableRel {
- private final RexProgram program;
-
public EnumerableCalcRel(
RelOptCluster cluster,
RelTraitSet traitSet,
@@ -754,8 +752,6 @@ public class JavaRules {
super(cluster, traitSet, child, rowType, program, collationList);
assert getConvention() instanceof EnumerableConvention;
assert !program.containsAggs();
- this.program = program;
- this.rowType = program.getOutputRowType();
}
@Override public EnumerableCalcRel copy(RelTraitSet traitSet, RelNode child,
http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/1d2cb011/core/src/main/java/net/hydromatic/optiq/util/graph/BreadthFirstIterator.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/net/hydromatic/optiq/util/graph/BreadthFirstIterator.java b/core/src/main/java/net/hydromatic/optiq/util/graph/BreadthFirstIterator.java
index 2dcdc58..0f7589b 100644
--- a/core/src/main/java/net/hydromatic/optiq/util/graph/BreadthFirstIterator.java
+++ b/core/src/main/java/net/hydromatic/optiq/util/graph/BreadthFirstIterator.java
@@ -16,11 +16,10 @@
*/
package net.hydromatic.optiq.util.graph;
-import org.eigenbase.util.ChunkList;
-
+import java.util.ArrayDeque;
+import java.util.Deque;
import java.util.HashSet;
import java.util.Iterator;
-import java.util.List;
import java.util.Set;
/**
@@ -32,12 +31,12 @@ import java.util.Set;
public class BreadthFirstIterator<V, E extends DefaultEdge>
implements Iterator<V> {
private final DirectedGraph<V, E> graph;
- private final List<V> list = new ChunkList<V>();
+ private final Deque<V> deque = new ArrayDeque<V>();
private final Set<V> set = new HashSet<V>();
public BreadthFirstIterator(DirectedGraph<V, E> graph, V root) {
this.graph = graph;
- this.list.add(root);
+ this.deque.add(root);
}
public static <V, E extends DefaultEdge> Iterable<V> of(
@@ -49,16 +48,33 @@ public class BreadthFirstIterator<V, E extends DefaultEdge>
};
}
+ /** Populates a set with the nodes reachable from a given node. */
+ public static <V, E extends DefaultEdge> void reachable(Set<V> set,
+ final DirectedGraph<V, E> graph, final V root) {
+ final Deque<V> deque = new ArrayDeque<V>();
+ deque.add(root);
+ set.add(root);
+ while (!deque.isEmpty()) {
+ V v = deque.removeFirst();
+ for (E e : graph.getOutwardEdges(v)) {
+ @SuppressWarnings("unchecked") V target = (V) e.target;
+ if (set.add(target)) {
+ deque.addLast(target);
+ }
+ }
+ }
+ }
+
public boolean hasNext() {
- return !list.isEmpty();
+ return !deque.isEmpty();
}
public V next() {
- V v = list.remove(0);
+ V v = deque.removeFirst();
for (E e : graph.getOutwardEdges(v)) {
@SuppressWarnings("unchecked") V target = (V) e.target;
if (set.add(target)) {
- list.add(target);
+ deque.addLast(target);
}
}
return v;
http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/1d2cb011/core/src/main/java/net/hydromatic/optiq/util/graph/DepthFirstIterator.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/net/hydromatic/optiq/util/graph/DepthFirstIterator.java b/core/src/main/java/net/hydromatic/optiq/util/graph/DepthFirstIterator.java
index 9652e10..c6cd7cf 100644
--- a/core/src/main/java/net/hydromatic/optiq/util/graph/DepthFirstIterator.java
+++ b/core/src/main/java/net/hydromatic/optiq/util/graph/DepthFirstIterator.java
@@ -16,6 +16,9 @@
*/
package net.hydromatic.optiq.util.graph;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+
import java.util.*;
/**
@@ -26,33 +29,37 @@ import java.util.*;
*/
public class DepthFirstIterator<V, E extends DefaultEdge>
implements Iterator<V> {
-
private final Iterator<V> iterator;
public DepthFirstIterator(DirectedGraph<V, E> graph, V start) {
// Dumb implementation that builds the list first.
- List<V> list = buildList(graph, start);
- iterator = list.iterator();
+ iterator = buildList(graph, start).iterator();
+ }
+
+ private static <V, E extends DefaultEdge> List<V> buildList(
+ DirectedGraph<V, E> graph, V start) {
+ final List<V> list = Lists.newArrayList();
+ buildListRecurse(list, Sets.<V>newHashSet(), graph, start);
+ return list;
}
+ /** Creates an iterable over the vertices in the given graph in a depth-first
+ * iteration order. */
public static <V, E extends DefaultEdge> Iterable<V> of(
- final DirectedGraph<V, E> graph, final V start) {
- return new Iterable<V>() {
- public Iterator<V> iterator() {
- return new DepthFirstIterator<V, E>(graph, start);
- }
- };
+ DirectedGraph<V, E> graph, V start) {
+ // Doesn't actually return a DepthFirstIterator, but a list with the same
+ // contents, which is more efficient.
+ return buildList(graph, start);
}
- private List<V> buildList(DirectedGraph<V, E> graph, V start) {
- final List<V> list = new ArrayList<V>();
+ /** Populates a collection with the nodes reachable from a given node. */
+ public static <V, E extends DefaultEdge> void reachable(Collection<V> list,
+ final DirectedGraph<V, E> graph, final V start) {
buildListRecurse(list, new HashSet<V>(), graph, start);
- return list;
}
- private void buildListRecurse(List<V> list,
- Set<V> activeVertices,
- DirectedGraph<V, E> graph,
+ private static <V, E extends DefaultEdge> void buildListRecurse(
+ Collection<V> list, Set<V> activeVertices, DirectedGraph<V, E> graph,
V start) {
if (!activeVertices.add(start)) {
return;
http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/1d2cb011/core/src/main/java/org/eigenbase/rel/rules/AggregateFilterTransposeRule.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/eigenbase/rel/rules/AggregateFilterTransposeRule.java b/core/src/main/java/org/eigenbase/rel/rules/AggregateFilterTransposeRule.java
new file mode 100644
index 0000000..44a5818
--- /dev/null
+++ b/core/src/main/java/org/eigenbase/rel/rules/AggregateFilterTransposeRule.java
@@ -0,0 +1,139 @@
+/*
+ * 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.eigenbase.rel.rules;
+
+import java.util.BitSet;
+import java.util.List;
+
+import org.eigenbase.rel.AggregateCall;
+import org.eigenbase.rel.AggregateRelBase;
+import org.eigenbase.rel.Aggregation;
+import org.eigenbase.rel.FilterRelBase;
+import org.eigenbase.rel.RelNode;
+import org.eigenbase.rel.metadata.RelMetadataQuery;
+import org.eigenbase.relopt.RelOptRule;
+import org.eigenbase.relopt.RelOptRuleCall;
+import org.eigenbase.relopt.RelOptUtil;
+import org.eigenbase.relopt.SubstitutionVisitor;
+import org.eigenbase.rex.RexNode;
+import org.eigenbase.rex.RexUtil;
+import org.eigenbase.util.mapping.Mappings;
+
+import net.hydromatic.optiq.util.BitSets;
+
+import com.google.common.base.Function;
+import com.google.common.collect.Lists;
+
+/**
+ * Planner rule that matches an {@link org.eigenbase.rel.AggregateRelBase}
+ * on a {@link org.eigenbase.rel.FilterRelBase} and transposes them,
+ * pushing the aggregate below the filter.
+ *
+ * <p>In some cases, it is necessary to split the aggregate.
+ *
+ * <p>This rule does not directly improve performance. The aggregate will
+ * have to process more rows, to produce aggregated rows that will be thrown
+ * away. The rule might be beneficial if the predicate is very expensive to
+ * evaluate. The main use of the rule is to match a query that has a filter
+ * under an aggregate to an existing aggregate table.
+ */
+public class AggregateFilterTransposeRule extends RelOptRule {
+ public static final AggregateFilterTransposeRule INSTANCE =
+ new AggregateFilterTransposeRule();
+
+ private AggregateFilterTransposeRule() {
+ super(
+ operand(AggregateRelBase.class,
+ operand(FilterRelBase.class, any())));
+ }
+
+ public void onMatch(RelOptRuleCall call) {
+ final AggregateRelBase aggregate = call.rel(0);
+ final FilterRelBase filter = call.rel(1);
+
+ // Do the columns used by the filter appear in the output of the aggregate?
+ final BitSet filterColumns =
+ RelOptUtil.InputFinder.bits(filter.getCondition());
+ final BitSet newGroupSet =
+ BitSets.union(aggregate.getGroupSet(), filterColumns);
+ final RelNode input = filter.getChild();
+ final Boolean unique =
+ RelMetadataQuery.areColumnsUnique(input, newGroupSet);
+ if (unique != null && unique) {
+ // The input is already unique on the grouping columns, so there's little
+ // advantage of aggregating again. More important, without this check,
+ // the rule fires forever: A-F => A-F-A => A-A-F-A => A-A-A-F-A => ...
+ return;
+ }
+ final AggregateRelBase newAggregate =
+ aggregate.copy(aggregate.getTraitSet(), input, newGroupSet,
+ aggregate.getAggCallList());
+ final Mappings.TargetMapping mapping = Mappings.target(
+ new Function<Integer, Integer>() {
+ public Integer apply(Integer a0) {
+ return BitSets.toList(newGroupSet).indexOf(a0);
+ }
+ },
+ input.getRowType().getFieldCount(),
+ newGroupSet.cardinality());
+ final RexNode newCondition =
+ RexUtil.apply(mapping, filter.getCondition());
+ final FilterRelBase newFilter = filter.copy(filter.getTraitSet(),
+ newAggregate, newCondition);
+ if (BitSets.contains(aggregate.getGroupSet(), filterColumns)) {
+ // Everything needed by the filter is returned by the aggregate.
+ assert newGroupSet.equals(aggregate.getGroupSet());
+ call.transformTo(newFilter);
+ } else {
+ // The filter needs at least one extra column.
+ // Now aggregate it away.
+ final BitSet topGroupSet = new BitSet();
+ for (int c : BitSets.toIter(aggregate.getGroupSet())) {
+ topGroupSet.set(BitSets.toList(newGroupSet).indexOf(c));
+ }
+ final List<AggregateCall> topAggCallList = Lists.newArrayList();
+ final int offset = newGroupSet.cardinality()
+ - aggregate.getGroupSet().cardinality();
+ assert offset > 0;
+ for (AggregateCall aggregateCall : aggregate.getAggCallList()) {
+ final List<Integer> args = Lists.newArrayList();
+ for (int arg : aggregateCall.getArgList()) {
+ args.add(arg + offset);
+ }
+ final Aggregation rollup =
+ SubstitutionVisitor.getRollup(aggregateCall.getAggregation());
+ if (rollup == null) {
+ // This aggregate cannot be rolled up.
+ return;
+ }
+ if (aggregateCall.isDistinct()) {
+ // Cannot roll up distinct.
+ return;
+ }
+ topAggCallList.add(
+ new AggregateCall(rollup, aggregateCall.isDistinct(), args,
+ aggregateCall.type, aggregateCall.name));
+ }
+ final AggregateRelBase topAggregate =
+ aggregate.copy(aggregate.getTraitSet(), newFilter, topGroupSet,
+ topAggCallList);
+ call.transformTo(topAggregate);
+ }
+ }
+}
+
+// End AggregateFilterTransposeRule.java
http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/1d2cb011/core/src/main/java/org/eigenbase/rel/rules/AggregateStarTableRule.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/eigenbase/rel/rules/AggregateStarTableRule.java b/core/src/main/java/org/eigenbase/rel/rules/AggregateStarTableRule.java
index 2edadfe..5f61fd6 100644
--- a/core/src/main/java/org/eigenbase/rel/rules/AggregateStarTableRule.java
+++ b/core/src/main/java/org/eigenbase/rel/rules/AggregateStarTableRule.java
@@ -98,7 +98,6 @@ public class AggregateStarTableRule extends RelOptRule {
if (aggregateTable == null) {
return;
}
- System.out.println(aggregateTable);
final double rowCount = aggregate.getRows();
final RelOptTable aggregateRelOptTable =
RelOptTableImpl.create(table.getRelOptSchema(),
http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/1d2cb011/core/src/main/java/org/eigenbase/relopt/RelOptLattice.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/eigenbase/relopt/RelOptLattice.java b/core/src/main/java/org/eigenbase/relopt/RelOptLattice.java
index a41202b..0606282 100644
--- a/core/src/main/java/org/eigenbase/relopt/RelOptLattice.java
+++ b/core/src/main/java/org/eigenbase/relopt/RelOptLattice.java
@@ -23,6 +23,7 @@ import org.eigenbase.rel.AggregateCall;
import org.eigenbase.rel.RelNode;
import org.eigenbase.reltype.RelDataTypeField;
import org.eigenbase.sql.SqlDialect;
+import org.eigenbase.util.Util;
import org.eigenbase.util.mapping.IntPair;
import net.hydromatic.optiq.config.OptiqConnectionConfig;
@@ -117,7 +118,12 @@ public class RelOptLattice {
if (k++ > 0) {
buf.append(", ");
}
- dialect.quoteIdentifier(buf, field.getName());
+ final List<String> identifiers = lattice.columns.get(i);
+ dialect.quoteIdentifier(buf, identifiers);
+ if (!Util.last(identifiers).equals(field.getName())) {
+ buf.append(" AS ");
+ dialect.quoteIdentifier(buf, field.getName());
+ }
}
buf.append("\nFROM ");
for (Lattice.Node node : usedNodes) {
http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/1d2cb011/core/src/main/java/org/eigenbase/relopt/RelOptMaterialization.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/eigenbase/relopt/RelOptMaterialization.java b/core/src/main/java/org/eigenbase/relopt/RelOptMaterialization.java
index 2cf1792..0b0686d 100644
--- a/core/src/main/java/org/eigenbase/relopt/RelOptMaterialization.java
+++ b/core/src/main/java/org/eigenbase/relopt/RelOptMaterialization.java
@@ -16,10 +16,18 @@
*/
package org.eigenbase.relopt;
+import java.util.List;
+
import org.eigenbase.rel.*;
import org.eigenbase.rel.metadata.DefaultRelMetadataProvider;
+import org.eigenbase.rel.rules.AggregateFilterTransposeRule;
+import org.eigenbase.rel.rules.AggregateProjectMergeRule;
import org.eigenbase.rel.rules.MergeProjectRule;
import org.eigenbase.rel.rules.PullUpProjectsAboveJoinRule;
+import org.eigenbase.rel.rules.PushFilterPastJoinRule;
+import org.eigenbase.rel.rules.PushProjectPastFilterRule;
+import org.eigenbase.rex.RexNode;
+import org.eigenbase.rex.RexUtil;
import org.eigenbase.sql.SqlExplainLevel;
import org.eigenbase.util.Util;
import org.eigenbase.util.mapping.Mappings;
@@ -30,7 +38,9 @@ import net.hydromatic.optiq.prepare.OptiqPrepareImpl;
import net.hydromatic.optiq.tools.Program;
import net.hydromatic.optiq.tools.Programs;
+import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
/**
* Records that a particular query is materialized by a particular table.
@@ -69,7 +79,7 @@ public class RelOptMaterialization {
final RelOptTable starRelOptTable) {
final StarTable starTable = starRelOptTable.unwrap(StarTable.class);
assert starTable != null;
- return rel.accept(
+ RelNode rel2 = rel.accept(
new RelShuttleImpl() {
@Override
public RelNode visit(TableAccessRelBase scan) {
@@ -98,87 +108,147 @@ public class RelOptMaterialization {
return rel;
}
join = (JoinRel) rel;
- final RelNode left = join.getLeft();
- final RelNode right = join.getRight();
- try {
- if (left instanceof TableAccessRelBase
- && right instanceof TableAccessRelBase) {
- match(left, null, right, null, join.getCluster());
- }
- if (isProjectedTable(left)
- && right instanceof TableAccessRelBase) {
- final ProjectRel leftProject = (ProjectRel) left;
- match(leftProject.getChild(), leftProject.getMapping(), right,
- null, join.getCluster());
- }
- if (left instanceof TableAccessRelBase
- && isProjectedTable(right)) {
- final ProjectRel rightProject = (ProjectRel) right;
- match(left, null, rightProject.getChild(),
- rightProject.getMapping(), join.getCluster());
- }
- if (isProjectedTable(left)
- && isProjectedTable(right)) {
- final ProjectRel leftProject = (ProjectRel) left;
- final ProjectRel rightProject = (ProjectRel) right;
- match(leftProject.getChild(), leftProject.getMapping(),
- rightProject.getChild(), rightProject.getMapping(),
- join.getCluster());
+ final ProjectFilterTable left =
+ ProjectFilterTable.of(join.getLeft());
+ if (left != null) {
+ final ProjectFilterTable right =
+ ProjectFilterTable.of(join.getRight());
+ if (right != null) {
+ try {
+ match(left, right, join.getCluster());
+ } catch (Util.FoundOne e) {
+ return (RelNode) e.getNode();
+ }
}
- } catch (Util.FoundOne e) {
- return (RelNode) e.getNode();
}
}
}
- private boolean isProjectedTable(RelNode rel) {
- return rel instanceof ProjectRel
- && ((ProjectRel) rel).isMapping()
- && ((ProjectRel) rel).getChild() instanceof TableAccessRelBase;
- }
-
/** Throws a {@link org.eigenbase.util.Util.FoundOne} containing a
* {@link org.eigenbase.rel.TableAccessRel} on success.
* (Yes, an exception for normal operation.) */
- private void match(RelNode left, Mappings.TargetMapping leftMapping,
- RelNode right, Mappings.TargetMapping rightMapping,
+ private void match(ProjectFilterTable left, ProjectFilterTable right,
RelOptCluster cluster) {
- if (leftMapping == null) {
- leftMapping =
- Mappings.createIdentity(left.getRowType().getFieldCount());
- }
- if (rightMapping == null) {
- rightMapping =
- Mappings.createIdentity(right.getRowType().getFieldCount());
- }
+ final Mappings.TargetMapping leftMapping = left.mapping();
+ final Mappings.TargetMapping rightMapping = right.mapping();
final RelOptTable leftRelOptTable = left.getTable();
final Table leftTable = leftRelOptTable.unwrap(Table.class);
+ final int leftCount = leftRelOptTable.getRowType().getFieldCount();
final RelOptTable rightRelOptTable = right.getTable();
final Table rightTable = rightRelOptTable.unwrap(Table.class);
if (leftTable instanceof StarTable
&& ((StarTable) leftTable).tables.contains(rightTable)) {
+ final int offset =
+ ((StarTable) leftTable).columnOffset(rightTable);
Mappings.TargetMapping mapping =
Mappings.merge(leftMapping,
- Mappings.offset(rightMapping,
- ((StarTable) leftTable).columnOffset(rightTable),
- leftRelOptTable.getRowType().getFieldCount()));
- throw new Util.FoundOne(
- RelOptUtil.createProject(
- new TableAccessRel(cluster, leftRelOptTable),
- Mappings.asList(mapping.inverse())));
+ Mappings.offsetTarget(
+ Mappings.offsetSource(rightMapping, offset),
+ leftMapping.getTargetCount()));
+ final RelNode project = RelOptUtil.createProject(
+ new TableAccessRel(cluster, leftRelOptTable),
+ Mappings.asList(mapping.inverse()));
+ final List<RexNode> conditions = Lists.newArrayList();
+ if (left.condition != null) {
+ conditions.add(RexUtil.apply(mapping, left.condition));
+ }
+ if (right.condition != null) {
+ conditions.add(
+ RexUtil.apply(mapping,
+ RexUtil.shift(right.condition, offset)));
+ }
+ final RelNode filter =
+ RelOptUtil.createFilter(project, conditions);
+ throw new Util.FoundOne(filter);
}
if (rightTable instanceof StarTable
&& ((StarTable) rightTable).tables.contains(leftTable)) {
- assert false; // TODO:
+ final int offset =
+ ((StarTable) rightTable).columnOffset(leftTable);
Mappings.TargetMapping mapping =
- Mappings.append(leftMapping, rightMapping);
- throw new Util.FoundOne(
- RelOptUtil.createProject(
- new TableAccessRel(cluster, rightRelOptTable),
- Mappings.asList(mapping.inverse())));
+ Mappings.merge(
+ Mappings.offsetSource(leftMapping, offset),
+ Mappings.offsetTarget(rightMapping, leftCount));
+ final RelNode project = RelOptUtil.createProject(
+ new TableAccessRel(cluster, rightRelOptTable),
+ Mappings.asList(mapping.inverse()));
+ final List<RexNode> conditions = Lists.newArrayList();
+ if (left.condition != null) {
+ conditions.add(RexUtil.apply(mapping, left.condition));
+ }
+ if (right.condition != null) {
+ conditions.add(
+ RexUtil.apply(mapping,
+ RexUtil.shift(right.condition, offset)));
+ }
+ final RelNode filter =
+ RelOptUtil.createFilter(project, conditions);
+ throw new Util.FoundOne(filter);
}
}
});
+ if (rel2 == rel) {
+ return rel;
+ }
+ final Program program = Programs.hep(
+ ImmutableList.of(PushProjectPastFilterRule.INSTANCE,
+ AggregateProjectMergeRule.INSTANCE,
+ AggregateFilterTransposeRule.INSTANCE),
+ false,
+ new DefaultRelMetadataProvider());
+ return program.run(null, rel2, null);
+ }
+
+ /** A table scan and optional project mapping and filter condition. */
+ private static class ProjectFilterTable {
+ final RexNode condition;
+ final Mappings.TargetMapping mapping;
+ final TableAccessRelBase scan;
+
+ private ProjectFilterTable(RexNode condition,
+ Mappings.TargetMapping mapping, TableAccessRelBase scan) {
+ this.condition = condition;
+ this.mapping = mapping;
+ this.scan = Preconditions.checkNotNull(scan);
+ }
+
+ static ProjectFilterTable of(RelNode node) {
+ if (node instanceof FilterRelBase) {
+ final FilterRelBase filter = (FilterRelBase) node;
+ return of2(filter.getCondition(), filter.getChild());
+ } else {
+ return of2(null, node);
+ }
+ }
+
+ private static ProjectFilterTable of2(RexNode condition, RelNode node) {
+ if (node instanceof ProjectRelBase) {
+ final ProjectRelBase project = (ProjectRelBase) node;
+ return of3(condition, project.getMapping(), project.getChild());
+ } else {
+ return of3(condition, null, node);
+ }
+ }
+
+ private static ProjectFilterTable of3(RexNode condition,
+ Mappings.TargetMapping mapping, RelNode node) {
+ if (node instanceof TableAccessRelBase) {
+ return new ProjectFilterTable(condition, mapping,
+ (TableAccessRelBase) node);
+ } else {
+ return null;
+ }
+ }
+
+ public Mappings.TargetMapping mapping() {
+ return mapping != null
+ ? mapping
+ : Mappings.createIdentity(scan.getRowType().getFieldCount());
+ }
+
+ public RelOptTable getTable() {
+ return scan.getTable();
+ }
}
/**
@@ -191,6 +261,7 @@ public class RelOptMaterialization {
ImmutableList.of(
PullUpProjectsAboveJoinRule.RIGHT_PROJECT,
PullUpProjectsAboveJoinRule.LEFT_PROJECT,
+ PushFilterPastJoinRule.PushFilterIntoJoinRule.FILTER_ON_JOIN,
MergeProjectRule.INSTANCE),
false,
new DefaultRelMetadataProvider());
http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/1d2cb011/core/src/main/java/org/eigenbase/relopt/RelOptUtil.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/eigenbase/relopt/RelOptUtil.java b/core/src/main/java/org/eigenbase/relopt/RelOptUtil.java
index eae8c2f..aee380d 100644
--- a/core/src/main/java/org/eigenbase/relopt/RelOptUtil.java
+++ b/core/src/main/java/org/eigenbase/relopt/RelOptUtil.java
@@ -454,6 +454,20 @@ public abstract class RelOptUtil {
return new FilterRel(child.getCluster(), child, condition);
}
+ /** Creates a filter, or returns the original relational expression if the
+ * condition is trivial. */
+ public static RelNode createFilter(RelNode child,
+ Iterable<? extends RexNode> conditions) {
+ final RelOptCluster cluster = child.getCluster();
+ final RexNode condition =
+ RexUtil.composeConjunction(cluster.getRexBuilder(), conditions, true);
+ if (condition == null) {
+ return child;
+ } else {
+ return createFilter(child, condition);
+ }
+ }
+
/**
* Creates a filter which will remove rows containing NULL values.
*
http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/1d2cb011/core/src/main/java/org/eigenbase/relopt/hep/HepPlanner.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/eigenbase/relopt/hep/HepPlanner.java b/core/src/main/java/org/eigenbase/relopt/hep/HepPlanner.java
index ab94fe7..e06f8c1 100644
--- a/core/src/main/java/org/eigenbase/relopt/hep/HepPlanner.java
+++ b/core/src/main/java/org/eigenbase/relopt/hep/HepPlanner.java
@@ -869,12 +869,8 @@ public class HepPlanner extends AbstractRelOptPlanner {
// Yer basic mark-and-sweep.
Set<HepRelVertex> rootSet = new HashSet<HepRelVertex>();
- Iterator<HepRelVertex> iter =
- new DepthFirstIterator<HepRelVertex, DefaultEdge>(
- graph,
- root);
- while (iter.hasNext()) {
- rootSet.add(iter.next());
+ if (graph.vertexSet().contains(root)) {
+ BreadthFirstIterator.reachable(rootSet, graph, root);
}
if (rootSet.size() == graph.vertexSet().size()) {
http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/1d2cb011/core/src/main/java/org/eigenbase/relopt/volcano/VolcanoPlanner.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/eigenbase/relopt/volcano/VolcanoPlanner.java b/core/src/main/java/org/eigenbase/relopt/volcano/VolcanoPlanner.java
index 23f37c0..03afff5 100644
--- a/core/src/main/java/org/eigenbase/relopt/volcano/VolcanoPlanner.java
+++ b/core/src/main/java/org/eigenbase/relopt/volcano/VolcanoPlanner.java
@@ -403,6 +403,9 @@ public class VolcanoPlanner extends AbstractRelOptPlanner {
if (queryTableNames.contains(lattice.rootTable().getQualifiedName())) {
RelNode rel2 = lattice.rewrite(leafJoinRoot.get());
if (rel2 != null) {
+ if (OptiqPrepareImpl.DEBUG) {
+ System.out.println("use lattice:\n" + RelOptUtil.toString(rel2));
+ }
latticeUses.add(Pair.of(lattice, rel2));
}
}
http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/1d2cb011/core/src/main/java/org/eigenbase/rex/RexPermuteInputsShuttle.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/eigenbase/rex/RexPermuteInputsShuttle.java b/core/src/main/java/org/eigenbase/rex/RexPermuteInputsShuttle.java
index 52c380a..f9d7b1e 100644
--- a/core/src/main/java/org/eigenbase/rex/RexPermuteInputsShuttle.java
+++ b/core/src/main/java/org/eigenbase/rex/RexPermuteInputsShuttle.java
@@ -16,24 +16,25 @@
*/
package org.eigenbase.rex;
-import java.util.ArrayList;
import java.util.List;
import org.eigenbase.rel.RelNode;
import org.eigenbase.reltype.RelDataTypeField;
import org.eigenbase.util.mapping.Mappings;
+import com.google.common.collect.ImmutableList;
+
/**
* Shuttle which applies a permutation to its input fields.
*
* @see RexPermutationShuttle
+ * @see RexUtil#apply(org.eigenbase.util.mapping.Mappings.TargetMapping, RexNode)
*/
public class RexPermuteInputsShuttle extends RexShuttle {
//~ Instance fields --------------------------------------------------------
private final Mappings.TargetMapping mapping;
- private final List<RelDataTypeField> fields =
- new ArrayList<RelDataTypeField>();
+ private final ImmutableList<RelDataTypeField> fields;
//~ Constructors -----------------------------------------------------------
@@ -51,14 +52,34 @@ public class RexPermuteInputsShuttle extends RexShuttle {
public RexPermuteInputsShuttle(
Mappings.TargetMapping mapping,
RelNode... inputs) {
+ this(mapping, fields(inputs));
+ }
+
+ private RexPermuteInputsShuttle(
+ Mappings.TargetMapping mapping,
+ ImmutableList<RelDataTypeField> fields) {
this.mapping = mapping;
+ this.fields = fields;
+ }
+
+ /** Creates a shuttle with an empty field list. It cannot handle GET calls but
+ * otherwise works OK. */
+ public static RexPermuteInputsShuttle of(Mappings.TargetMapping mapping) {
+ return new RexPermuteInputsShuttle(mapping,
+ ImmutableList.<RelDataTypeField>of());
+ }
+
+ //~ Methods ----------------------------------------------------------------
+
+ private static ImmutableList<RelDataTypeField> fields(RelNode[] inputs) {
+ final ImmutableList.Builder<RelDataTypeField> fields =
+ ImmutableList.builder();
for (RelNode input : inputs) {
fields.addAll(input.getRowType().getFieldList());
}
+ return fields.build();
}
- //~ Methods ----------------------------------------------------------------
-
@Override
public RexNode visitInputRef(RexInputRef local) {
final int index = local.getIndex();
http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/1d2cb011/core/src/main/java/org/eigenbase/rex/RexUtil.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/eigenbase/rex/RexUtil.java b/core/src/main/java/org/eigenbase/rex/RexUtil.java
index 5ed708d..af365e1 100644
--- a/core/src/main/java/org/eigenbase/rex/RexUtil.java
+++ b/core/src/main/java/org/eigenbase/rex/RexUtil.java
@@ -607,12 +607,16 @@ public class RexUtil {
}
}
- /** Flattens a list of AND nodes. */
+ /** Flattens a list of AND nodes.
+ *
+ * <p>Treats null nodes as literal TRUE (i.e. ignores them). */
public static ImmutableList<RexNode> flattenAnd(
Iterable<? extends RexNode> nodes) {
final ImmutableList.Builder<RexNode> builder = ImmutableList.builder();
for (RexNode node : nodes) {
- addAnd(builder, node);
+ if (node != null) {
+ addAnd(builder, node);
+ }
}
return builder.build();
}
@@ -784,6 +788,13 @@ public class RexUtil {
}
/**
+ * Applies a mapping to an expression.
+ */
+ public static RexNode apply(Mappings.TargetMapping mapping, RexNode node) {
+ return node.accept(RexPermuteInputsShuttle.of(mapping));
+ }
+
+ /**
* Applies a shuttle to an array of expressions. Creates a copy first.
*
* @param shuttle Shuttle
http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/1d2cb011/core/src/main/java/org/eigenbase/sql2rel/RelStructuredTypeFlattener.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/eigenbase/sql2rel/RelStructuredTypeFlattener.java b/core/src/main/java/org/eigenbase/sql2rel/RelStructuredTypeFlattener.java
index 5fa73a9..eacbbac 100644
--- a/core/src/main/java/org/eigenbase/sql2rel/RelStructuredTypeFlattener.java
+++ b/core/src/main/java/org/eigenbase/sql2rel/RelStructuredTypeFlattener.java
@@ -28,8 +28,7 @@ import org.eigenbase.sql.type.*;
import org.eigenbase.util.*;
import org.eigenbase.util.mapping.Mappings;
-import net.hydromatic.linq4j.function.Function1;
-
+import com.google.common.base.Function;
import com.google.common.collect.*;
// TODO jvs 10-Feb-2005: factor out generic rewrite helper, with the
@@ -256,7 +255,7 @@ public class RelStructuredTypeFlattener implements ReflectiveVisitor {
private Mappings.TargetMapping getNewForOldInputMapping(RelNode oldRel) {
final RelNode newRel = getNewForOldRel(oldRel);
return Mappings.target(
- new Function1<Integer, Integer>() {
+ new Function<Integer, Integer>() {
public Integer apply(Integer oldInput) {
return getNewForOldInput(oldInput);
}
http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/1d2cb011/core/src/main/java/org/eigenbase/util/mapping/Mappings.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/eigenbase/util/mapping/Mappings.java b/core/src/main/java/org/eigenbase/util/mapping/Mappings.java
index 0266e0b..ea5a6fd 100644
--- a/core/src/main/java/org/eigenbase/util/mapping/Mappings.java
+++ b/core/src/main/java/org/eigenbase/util/mapping/Mappings.java
@@ -20,10 +20,10 @@ import java.util.*;
import org.eigenbase.util.*;
-import net.hydromatic.linq4j.function.Function1;
-
import net.hydromatic.optiq.util.BitSets;
+import com.google.common.base.Function;
+
/**
* Utility functions related to mappings.
*
@@ -270,12 +270,11 @@ public abstract class Mappings {
}
public static TargetMapping target(
- Function1<Integer, Integer> function,
+ Function<Integer, Integer> function,
int sourceCount,
int targetCount) {
final PartialFunctionImpl mapping =
- new PartialFunctionImpl(
- sourceCount, targetCount, MappingType.FUNCTION);
+ new PartialFunctionImpl(sourceCount, targetCount, MappingType.FUNCTION);
for (int source = 0; source < sourceCount; source++) {
Integer target = function.apply(source);
if (target != null) {
@@ -285,6 +284,16 @@ public abstract class Mappings {
return mapping;
}
+ public static Mapping target(Iterable<IntPair> pairs, int sourceCount,
+ int targetCount) {
+ final PartialFunctionImpl mapping =
+ new PartialFunctionImpl(sourceCount, targetCount, MappingType.FUNCTION);
+ for (IntPair pair : pairs) {
+ mapping.set(pair.source, pair.target);
+ }
+ return mapping;
+ }
+
public static Mapping source(List<Integer> targets, int targetCount) {
final int sourceCount = targets.size();
final PartialFunctionImpl mapping =
@@ -465,7 +474,7 @@ public abstract class Mappings {
int t = s < s0 ? mapping0.getTargetOpt(s) : -1;
if (t >= 0) {
mapping.set(s, t);
- assert mapping1.getTargetOpt(s) < 0;
+ assert s >= s1 || mapping1.getTargetOpt(s) < 0;
} else {
t = s < s1 ? mapping1.getTargetOpt(s) : -1;
if (t >= 0) {
@@ -510,7 +519,7 @@ public abstract class Mappings {
throw new IllegalArgumentException("new source count too low");
}
return target(
- new Function1<Integer, Integer>() {
+ new Function<Integer, Integer>() {
public Integer apply(Integer source) {
int source2 = source - offset;
return source2 < 0 || source2 >= mapping.getSourceCount()
@@ -556,7 +565,7 @@ public abstract class Mappings {
throw new IllegalArgumentException("new target count too low");
}
return target(
- new Function1<Integer, Integer>() {
+ new Function<Integer, Integer>() {
public Integer apply(Integer source) {
int target = mapping.getTargetOpt(source);
return target < 0 ? null : target + offset;
@@ -587,7 +596,7 @@ public abstract class Mappings {
throw new IllegalArgumentException("new source count too low");
}
return target(
- new Function1<Integer, Integer>() {
+ new Function<Integer, Integer>() {
public Integer apply(Integer source) {
final int source2 = source - offset;
if (source2 < 0 || source2 >= mapping.getSourceCount()) {
@@ -619,6 +628,35 @@ public abstract class Mappings {
return true;
}
+ /** Inverts an {@link java.lang.Iterable} over
+ * {@link org.eigenbase.util.mapping.IntPair}s. */
+ public static Iterable<IntPair> invert(final Iterable<IntPair> pairs) {
+ return new Iterable<IntPair>() {
+ public Iterator<IntPair> iterator() {
+ return invert(pairs.iterator());
+ }
+ };
+ }
+
+ /** Inverts an {@link java.util.Iterator} over
+ * {@link org.eigenbase.util.mapping.IntPair}s. */
+ public static Iterator<IntPair> invert(final Iterator<IntPair> pairs) {
+ return new Iterator<IntPair>() {
+ public boolean hasNext() {
+ return pairs.hasNext();
+ }
+
+ public IntPair next() {
+ final IntPair pair = pairs.next();
+ return IntPair.of(pair.target, pair.source);
+ }
+
+ public void remove() {
+ throw new UnsupportedOperationException("remove");
+ }
+ };
+ }
+
//~ Inner Interfaces -------------------------------------------------------
/**
@@ -1492,8 +1530,7 @@ public abstract class Mappings {
}
public Mapping inverse() {
- // todo: implement
- throw new UnsupportedOperationException();
+ return target(invert(this), targetCount, sourceCount);
}
public void set(int source, int target) {
http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/1d2cb011/core/src/test/java/net/hydromatic/optiq/test/FoodmartTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/net/hydromatic/optiq/test/FoodmartTest.java b/core/src/test/java/net/hydromatic/optiq/test/FoodmartTest.java
index ff8432c..852a106 100644
--- a/core/src/test/java/net/hydromatic/optiq/test/FoodmartTest.java
+++ b/core/src/test/java/net/hydromatic/optiq/test/FoodmartTest.java
@@ -184,7 +184,7 @@ public class FoodmartTest {
/** Returns the singleton instance of the query set. It is backed by a
* soft reference, so it may be freed if memory is short and no one is
* using it. */
- public static FoodMartQuerySet instance() throws IOException {
+ public static synchronized FoodMartQuerySet instance() throws IOException {
final SoftReference<FoodMartQuerySet> refLocal = ref;
if (refLocal != null) {
final FoodMartQuerySet set = refLocal.get();
http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/1d2cb011/core/src/test/java/net/hydromatic/optiq/test/JdbcTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/net/hydromatic/optiq/test/JdbcTest.java b/core/src/test/java/net/hydromatic/optiq/test/JdbcTest.java
index 2efcbaa..6730332 100644
--- a/core/src/test/java/net/hydromatic/optiq/test/JdbcTest.java
+++ b/core/src/test/java/net/hydromatic/optiq/test/JdbcTest.java
@@ -956,7 +956,8 @@ public class JdbcTest {
@Test public void testCloneSchema()
throws ClassNotFoundException, SQLException {
- final OptiqConnection connection = OptiqAssert.getConnection(false);
+ final OptiqConnection connection =
+ OptiqAssert.getConnection(OptiqAssert.SchemaSpec.JDBC_FOODMART);
final SchemaPlus rootSchema = connection.getRootSchema();
final SchemaPlus foodmart = rootSchema.getSubSchema("foodmart");
rootSchema.add("foodmart2", new CloneSchema(foodmart));
@@ -2054,6 +2055,28 @@ public class JdbcTest {
.runs();
}
+ /** Tests that a relatively complex query on the foodmart schema creates
+ * an in-memory aggregate table and then uses it. */
+ @Test public void testFoodmartLattice() throws IOException {
+ // 8: select ... from customer, sales, time ... group by ...
+ final FoodmartTest.FoodmartQuery query =
+ FoodmartTest.FoodMartQuerySet.instance().queries.get(8);
+ OptiqAssert.that()
+ .with(OptiqAssert.Config.JDBC_FOODMART_WITH_LATTICE)
+ .pooled()
+ .withSchema("foodmart")
+ .query(query.sql)
+ .enableMaterializations(true)
+ .explainContains(
+ "EnumerableCalcRel(expr#0..8=[{inputs}], c0=[$t3], c1=[$t2], c2=[$t1], c3=[$t0], c4=[$t8], c5=[$t8], c6=[$t6], c7=[$t4], c8=[$t7], c9=[$t5])\n"
+ + " EnumerableSortRel(sort0=[$3], sort1=[$2], sort2=[$1], sort3=[$8], dir0=[ASC-nulls-last], dir1=[ASC-nulls-last], dir2=[ASC-nulls-last], dir3=[ASC-nulls-last])\n"
+ + " EnumerableCalcRel(expr#0..8=[{inputs}], expr#9=['%Jeanne%'], expr#10=[LIKE($t8, $t9)], proj#0..8=[{exprs}], $condition=[$t10])\n"
+ + " EnumerableAggregateRel(group=[{0, 1, 2, 3, 4, 5, 6, 7, 8}])\n"
+ + " EnumerableCalcRel(expr#0..9=[{inputs}], expr#10=[CAST($t0):INTEGER], expr#11=[1997], expr#12=[=($t10, $t11)], $f0=[$t1], $f1=[$t2], $f2=[$t3], $f3=[$t4], $f4=[$t5], $f5=[$t6], $f6=[$t7], $f7=[$t8], $f8=[$t9], $f9=[$t0], $condition=[$t12])\n"
+ + " EnumerableTableAccessRel(table=[[foodmart, m{12, 18, 27, 28, 30, 35, 36, 37, 40, 46}]])")
+ .runs();
+ }
+
/** Test case for (not yet fixed)
* <a href="https://issues.apache.org/jira/browse/OPTIQ-99">OPTIQ-99</a>,
* "Recognize semi-join that has high selectivity and push it down". */
@@ -5847,7 +5870,8 @@ public class JdbcTest {
}
@Test public void testSchemaCaching() throws Exception {
- final OptiqConnection connection = OptiqAssert.getConnection(false);
+ final OptiqConnection connection =
+ OptiqAssert.getConnection(OptiqAssert.SchemaSpec.JDBC_FOODMART);
final SchemaPlus rootSchema = connection.getRootSchema();
// create schema "/a"
http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/1d2cb011/core/src/test/java/net/hydromatic/optiq/test/LatticeTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/net/hydromatic/optiq/test/LatticeTest.java b/core/src/test/java/net/hydromatic/optiq/test/LatticeTest.java
index c11b574..703ad9b 100644
--- a/core/src/test/java/net/hydromatic/optiq/test/LatticeTest.java
+++ b/core/src/test/java/net/hydromatic/optiq/test/LatticeTest.java
@@ -198,15 +198,12 @@ public class LatticeTest {
assertThat(s,
anyOf(
containsString(
- "AggregateRel(group=[{0, 1}])\n"
- + " ProjectRel(brand_name=[$10], customer_id=[$2])\n"
- + " ProjectRel($f0=[$0], $f1=[$1], $f2=[$2], $f3=[$3], $f4=[$4], $f5=[$5], $f6=[$6], $f7=[$7], $f8=[$8], $f9=[$9], $f10=[$10], $f11=[$11], $f12=[$12], $f13=[$13], $f14=[$14], $f15=[$15], $f16=[$16], $f17=[$17], $f18=[$18], $f19=[$19], $f20=[$20], $f21=[$21], $f22=[$22])\n"
- + " TableAccessRel(table=[[adhoc, star]])\n"),
+ "ProjectRel($f0=[$1], $f1=[$0])\n"
+ + " AggregateRel(group=[{2, 10}])\n"
+ + " TableAccessRel(table=[[adhoc, star]])\n"),
containsString(
- "AggregateRel(group=[{0, 1}])\n"
- + " ProjectRel(customer_id=[$2], brand_name=[$10])\n"
- + " ProjectRel($f0=[$0], $f1=[$1], $f2=[$2], $f3=[$3], $f4=[$4], $f5=[$5], $f6=[$6], $f7=[$7], $f8=[$8], $f9=[$9], $f10=[$10], $f11=[$11], $f12=[$12], $f13=[$13], $f14=[$14], $f15=[$15], $f16=[$16], $f17=[$17], $f18=[$18], $f19=[$19], $f20=[$20], $f21=[$21], $f22=[$22])\n"
- + " TableAccessRel(table=[[adhoc, star]])\n")));
+ "AggregateRel(group=[{2, 10}])\n"
+ + " TableAccessRel(table=[[adhoc, star]])\n")));
return null;
}
});
http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/1d2cb011/core/src/test/java/net/hydromatic/optiq/test/LinqFrontJdbcBackTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/net/hydromatic/optiq/test/LinqFrontJdbcBackTest.java b/core/src/test/java/net/hydromatic/optiq/test/LinqFrontJdbcBackTest.java
index 56146d1..a7218e2 100644
--- a/core/src/test/java/net/hydromatic/optiq/test/LinqFrontJdbcBackTest.java
+++ b/core/src/test/java/net/hydromatic/optiq/test/LinqFrontJdbcBackTest.java
@@ -37,7 +37,7 @@ public class LinqFrontJdbcBackTest {
@Test public void testTableWhere() throws SQLException,
ClassNotFoundException {
final OptiqConnection connection =
- OptiqAssert.getConnection(false);
+ OptiqAssert.getConnection(OptiqAssert.SchemaSpec.JDBC_FOODMART);
final SchemaPlus schema =
connection.getRootSchema().getSubSchema("foodmart");
ParameterExpression c =
http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/1d2cb011/core/src/test/java/net/hydromatic/optiq/test/OptiqAssert.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/net/hydromatic/optiq/test/OptiqAssert.java b/core/src/test/java/net/hydromatic/optiq/test/OptiqAssert.java
index 5a5b9a7..24909e8 100644
--- a/core/src/test/java/net/hydromatic/optiq/test/OptiqAssert.java
+++ b/core/src/test/java/net/hydromatic/optiq/test/OptiqAssert.java
@@ -25,6 +25,8 @@ import net.hydromatic.optiq.impl.java.ReflectiveSchema;
import net.hydromatic.optiq.impl.jdbc.JdbcSchema;
import net.hydromatic.optiq.jdbc.MetaImpl;
import net.hydromatic.optiq.jdbc.OptiqConnection;
+import net.hydromatic.optiq.jdbc.OptiqSchema;
+import net.hydromatic.optiq.materialize.Lattice;
import net.hydromatic.optiq.runtime.Hook;
import org.eigenbase.rel.RelNode;
@@ -566,6 +568,7 @@ public class OptiqAssert {
}
public static SchemaPlus addSchema(SchemaPlus rootSchema, SchemaSpec schema) {
+ SchemaPlus foodmart;
switch (schema) {
case REFLECTIVE_FOODMART:
return rootSchema.add("foodmart",
@@ -580,8 +583,21 @@ public class OptiqAssert {
return rootSchema.add("foodmart",
JdbcSchema.create(rootSchema, "foodmart", dataSource, null,
"foodmart"));
+ case JDBC_FOODMART_WITH_LATTICE:
+ foodmart = rootSchema.getSubSchema("foodmart");
+ if (foodmart == null) {
+ foodmart = OptiqAssert.addSchema(rootSchema, SchemaSpec.JDBC_FOODMART);
+ }
+ foodmart.add("lattice",
+ Lattice.create(foodmart.unwrap(OptiqSchema.class),
+ "select 1 from \"foodmart\".\"sales_fact_1997\" as s\n"
+ + "join \"foodmart\".\"time_by_day\" as t using (\"time_id\")\n"
+ + "join \"foodmart\".\"customer\" as c using (\"customer_id\")\n"
+ + "join \"foodmart\".\"product\" as p using (\"product_id\")\n"
+ + "join \"foodmart\".\"product_class\" as pc on p.\"product_class_id\" = pc.\"product_class_id\""));
+ return foodmart;
case CLONE_FOODMART:
- SchemaPlus foodmart = rootSchema.getSubSchema("foodmart");
+ foodmart = rootSchema.getSubSchema("foodmart");
if (foodmart == null) {
foodmart = OptiqAssert.addSchema(rootSchema, SchemaSpec.JDBC_FOODMART);
}
@@ -666,22 +682,30 @@ public class OptiqAssert {
* uses the connection as its own provider. The connection contains a
* schema called "foodmart" backed by a JDBC connection to MySQL.
*
- * @param withClone Whether to create a "foodmart2" schema as in-memory
- * clone
+ * @param schemaSpec Schema specification; whether to create a "foodmart2"
+ * schema as in-memory clone
* @return Connection
* @throws ClassNotFoundException
* @throws java.sql.SQLException
*/
- static OptiqConnection getConnection(boolean withClone)
+ static OptiqConnection getConnection(SchemaSpec schemaSpec)
throws ClassNotFoundException, SQLException {
Class.forName("net.hydromatic.optiq.jdbc.Driver");
Connection connection = DriverManager.getConnection("jdbc:optiq:");
OptiqConnection optiqConnection =
connection.unwrap(OptiqConnection.class);
final SchemaPlus rootSchema = optiqConnection.getRootSchema();
- addSchema(rootSchema, SchemaSpec.JDBC_FOODMART);
- if (withClone) {
- addSchema(rootSchema, SchemaSpec.CLONE_FOODMART);
+ switch (schemaSpec) {
+ case JDBC_FOODMART:
+ addSchema(rootSchema, schemaSpec);
+ break;
+ case CLONE_FOODMART:
+ case JDBC_FOODMART_WITH_LATTICE:
+ addSchema(rootSchema, SchemaSpec.JDBC_FOODMART);
+ addSchema(rootSchema, schemaSpec);
+ break;
+ default:
+ throw new AssertionError("unknown schema " + schemaSpec);
}
optiqConnection.setSchema("foodmart2");
return optiqConnection;
@@ -884,37 +908,6 @@ public class OptiqAssert {
OptiqConnection createConnection() throws Exception;
}
- private static class MemoizingConnectionFactory implements ConnectionFactory {
- private final Supplier<OptiqConnection> supplier;
-
- public MemoizingConnectionFactory(final ConnectionFactory factory) {
- super();
- this.supplier = Suppliers.memoize(
- new Supplier<OptiqConnection>() {
- public OptiqConnection get() {
- try {
- return factory.createConnection();
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- }
- });
- }
-
- public OptiqConnection createConnection() throws Exception {
- try {
- return supplier.get();
- } catch (RuntimeException e) {
- if (e.getClass() == RuntimeException.class
- && e.getCause() instanceof Exception
- && e.getCause() != e) {
- throw (Exception) e.getCause();
- }
- throw e;
- }
- }
- }
-
private static class PoolingConnectionFactory implements ConnectionFactory {
private final ConnectionFactory factory;
@@ -966,9 +959,11 @@ public class OptiqAssert {
case LINGUAL:
return getConnection("lingual");
case JDBC_FOODMART:
- return getConnection(false);
+ return getConnection(OptiqAssert.SchemaSpec.JDBC_FOODMART);
case FOODMART_CLONE:
- return getConnection(true);
+ return getConnection(SchemaSpec.CLONE_FOODMART);
+ case JDBC_FOODMART_WITH_LATTICE:
+ return getConnection(SchemaSpec.JDBC_FOODMART_WITH_LATTICE);
case SPARK:
return getConnection("spark");
default:
@@ -1261,6 +1256,10 @@ public class OptiqAssert {
* database. */
FOODMART_CLONE,
+ /** Configuration that contains an in-memory clone of the FoodMart
+ * database, plus a lattice to enable on-the-fly materializations. */
+ JDBC_FOODMART_WITH_LATTICE,
+
/** Configuration that includes the metadata schema. */
REGULAR_PLUS_METADATA,
@@ -1351,6 +1350,7 @@ public class OptiqAssert {
REFLECTIVE_FOODMART,
JDBC_FOODMART,
CLONE_FOODMART,
+ JDBC_FOODMART_WITH_LATTICE,
HR,
LINGUAL,
POST
http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/1d2cb011/core/src/test/java/net/hydromatic/optiq/util/graph/DirectedGraphTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/net/hydromatic/optiq/util/graph/DirectedGraphTest.java b/core/src/test/java/net/hydromatic/optiq/util/graph/DirectedGraphTest.java
index 9d5ff6a..6ff7d35 100644
--- a/core/src/test/java/net/hydromatic/optiq/util/graph/DirectedGraphTest.java
+++ b/core/src/test/java/net/hydromatic/optiq/util/graph/DirectedGraphTest.java
@@ -18,6 +18,7 @@ package net.hydromatic.optiq.util.graph;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Lists;
import org.hamcrest.CoreMatchers;
@@ -25,6 +26,7 @@ import org.junit.Test;
import java.util.*;
+import static org.hamcrest.CoreMatchers.equalTo;
import static org.junit.Assert.*;
/**
@@ -117,7 +119,10 @@ public class DirectedGraphTest {
for (String s : DepthFirstIterator.of(graph, "A")) {
list.add(s);
}
- assertEquals("[A, B, C, D, E, C, D, F]", list.toString());
+ assertThat(list.toString(), equalTo("[A, B, C, D, E, C, D, F]"));
+ list.clear();
+ DepthFirstIterator.reachable(list, graph, "A");
+ assertThat(list.toString(), equalTo("[A, B, C, D, E, C, D, F]"));
}
/** Unit test for {@link DepthFirstIterator}. */
@@ -274,9 +279,10 @@ public class DirectedGraphTest {
* {@link net.hydromatic.optiq.util.graph.BreadthFirstIterator}. */
@Test public void testBreadthFirstIterator() {
DefaultDirectedGraph<String, DefaultEdge> graph = createDag();
- assertThat(getA(graph, "A"),
- CoreMatchers.<List<String>>equalTo(
- ImmutableList.of("A", "B", "E", "C", "F", "D")));
+ final List<String> expected =
+ ImmutableList.of("A", "B", "E", "C", "F", "D");
+ assertThat(getA(graph, "A"), equalTo(expected));
+ assertThat(Lists.newArrayList(getB(graph, "A")), equalTo(expected));
}
private List<String> getA(DefaultDirectedGraph<String, DefaultEdge> graph,
@@ -288,6 +294,13 @@ public class DirectedGraphTest {
return list;
}
+ private Set<String> getB(DefaultDirectedGraph<String, DefaultEdge> graph,
+ String root) {
+ final Set<String> list = new LinkedHashSet<String>();
+ BreadthFirstIterator.reachable(list, graph, root);
+ return list;
+ }
+
}
// End DirectedGraphTest.java
http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/1d2cb011/core/src/test/java/org/eigenbase/util/mapping/MappingTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/eigenbase/util/mapping/MappingTest.java b/core/src/test/java/org/eigenbase/util/mapping/MappingTest.java
index ea57ebb..65a9d61 100644
--- a/core/src/test/java/org/eigenbase/util/mapping/MappingTest.java
+++ b/core/src/test/java/org/eigenbase/util/mapping/MappingTest.java
@@ -143,13 +143,10 @@ public class MappingTest {
final List<Integer> integers = Mappings.asList(mapping);
assertThat(integers, equalTo(targets));
- try {
- final Mapping inverse = mapping.inverse();
- fail("expected exception, got " + inverse);
- } catch (UnsupportedOperationException e) {
- // ok... but we'd prefer if that inverse succeeds if the mapping is
- // invertible
- }
+ final Mapping inverse = mapping.inverse();
+ assertThat(inverse.toString(),
+ equalTo(
+ "[size=5, sourceCount=10, targetCount=5, elements=[1:1, 3:0, 4:2, 5:3, 8:4]]"));
}
/** Unit test for {@link Mappings#target(List, int)}. */