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/08/29 20:04:08 UTC
git commit: [OPTIQ-344] Lattice data structure
Repository: incubator-optiq
Updated Branches:
refs/heads/master 793e5c4d2 -> acee9632f
[OPTIQ-344] Lattice data structure
Project: http://git-wip-us.apache.org/repos/asf/incubator-optiq/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-optiq/commit/acee9632
Tree: http://git-wip-us.apache.org/repos/asf/incubator-optiq/tree/acee9632
Diff: http://git-wip-us.apache.org/repos/asf/incubator-optiq/diff/acee9632
Branch: refs/heads/master
Commit: acee9632f113d0523928add6032d3c4c41944624
Parents: 793e5c4
Author: Julian Hyde <jh...@apache.org>
Authored: Mon Jul 14 01:31:33 2014 -0700
Committer: Julian Hyde <jh...@apache.org>
Committed: Fri Aug 29 02:15:14 2014 -0700
----------------------------------------------------------------------
.../java/net/hydromatic/optiq/SchemaPlus.java | 5 +
.../main/java/net/hydromatic/optiq/Schemas.java | 72 ++++++
.../net/hydromatic/optiq/impl/StarTable.java | 25 +-
.../net/hydromatic/optiq/jdbc/OptiqPrepare.java | 17 +-
.../net/hydromatic/optiq/jdbc/OptiqSchema.java | 62 +++++
.../hydromatic/optiq/materialize/Lattice.java | 226 +++++++++++++++++++
.../net/hydromatic/optiq/model/JsonLattice.java | 39 ++++
.../net/hydromatic/optiq/model/JsonSchema.java | 12 +-
.../hydromatic/optiq/model/ModelHandler.java | 18 ++
.../optiq/prepare/OptiqMaterializer.java | 71 +++---
.../optiq/prepare/OptiqPrepareImpl.java | 37 ++-
.../net/hydromatic/optiq/prepare/Prepare.java | 29 ++-
.../java/net/hydromatic/optiq/runtime/Hook.java | 9 +
.../net/hydromatic/optiq/util/Compatible.java | 9 +
.../optiq/util/graph/DefaultDirectedGraph.java | 7 +-
.../org/eigenbase/relopt/RelOptLattice.java | 57 +++++
.../org/eigenbase/relopt/RelOptPlanner.java | 8 +
.../org/eigenbase/relopt/hep/HepPlanner.java | 4 +
.../relopt/volcano/VolcanoPlanner.java | 44 +++-
.../net/hydromatic/optiq/test/LatticeTest.java | 186 +++++++++++++++
.../net/hydromatic/optiq/test/ModelTest.java | 47 ++++
.../net/hydromatic/optiq/test/OptiqAssert.java | 88 ++++++--
.../net/hydromatic/optiq/test/OptiqSuite.java | 1 +
.../org/eigenbase/test/MockRelOptPlanner.java | 4 +
.../test/java/org/eigenbase/util/TestUtil.java | 36 +++
25 files changed, 1039 insertions(+), 74 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/acee9632/core/src/main/java/net/hydromatic/optiq/SchemaPlus.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/net/hydromatic/optiq/SchemaPlus.java b/core/src/main/java/net/hydromatic/optiq/SchemaPlus.java
index 6a476df..91ca5ca 100644
--- a/core/src/main/java/net/hydromatic/optiq/SchemaPlus.java
+++ b/core/src/main/java/net/hydromatic/optiq/SchemaPlus.java
@@ -16,6 +16,8 @@
*/
package net.hydromatic.optiq;
+import net.hydromatic.optiq.materialize.Lattice;
+
import com.google.common.collect.ImmutableList;
/**
@@ -57,6 +59,9 @@ public interface SchemaPlus extends Schema {
/** Adds a function to this schema. */
void add(String name, Function function);
+ /** Adds a lattice to this schema. */
+ void add(String name, Lattice lattice);
+
boolean isMutable();
/** Returns an underlying object. */
http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/acee9632/core/src/main/java/net/hydromatic/optiq/Schemas.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/net/hydromatic/optiq/Schemas.java b/core/src/main/java/net/hydromatic/optiq/Schemas.java
index 45f32eb..3f279c5 100644
--- a/core/src/main/java/net/hydromatic/optiq/Schemas.java
+++ b/core/src/main/java/net/hydromatic/optiq/Schemas.java
@@ -23,6 +23,7 @@ import net.hydromatic.linq4j.expressions.*;
import net.hydromatic.optiq.config.OptiqConnectionConfig;
import net.hydromatic.optiq.impl.java.JavaTypeFactory;
import net.hydromatic.optiq.jdbc.*;
+import net.hydromatic.optiq.materialize.Lattice;
import org.eigenbase.reltype.RelDataType;
import org.eigenbase.reltype.RelDataTypeFactory;
@@ -30,6 +31,7 @@ import org.eigenbase.reltype.RelProtoDataType;
import org.eigenbase.sql.type.SqlTypeUtil;
import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Lists;
import java.lang.reflect.*;
import java.sql.Connection;
@@ -39,6 +41,26 @@ import java.util.*;
* Utility functions for schemas.
*/
public final class Schemas {
+ private static final com.google.common.base.Function<OptiqSchema.LatticeEntry,
+ OptiqSchema.TableEntry> TO_TABLE_ENTRY =
+ new com.google.common.base.Function<OptiqSchema.LatticeEntry,
+ OptiqSchema.TableEntry>() {
+ public OptiqSchema.TableEntry apply(OptiqSchema.LatticeEntry entry) {
+ final OptiqSchema.TableEntry starTable = entry.getStarTable();
+ assert starTable.getTable().getJdbcTableType()
+ == Schema.TableType.STAR;
+ return entry.getStarTable();
+ }
+ };
+
+ private static final com.google.common.base.Function<OptiqSchema.LatticeEntry,
+ Lattice> TO_LATTICE =
+ new com.google.common.base.Function<OptiqSchema.LatticeEntry, Lattice>() {
+ public Lattice apply(OptiqSchema.LatticeEntry entry) {
+ return entry.getLattice();
+ }
+ };
+
private Schemas() {
throw new AssertionError("no instances!");
}
@@ -178,6 +200,22 @@ public final class Schemas {
}
}
+ /** Parses and validates a SQL query and converts to relational algebra. For
+ * use within Optiq only. */
+ public static OptiqPrepare.ConvertResult convert(
+ final OptiqConnection connection, final OptiqSchema schema,
+ final List<String> schemaPath, final String sql) {
+ final OptiqPrepare prepare = OptiqPrepare.DEFAULT_FACTORY.apply();
+ final OptiqPrepare.Context context =
+ makeContext(connection, schema, schemaPath);
+ OptiqPrepare.Dummy.push(context);
+ try {
+ return prepare.convert(context, sql);
+ } finally {
+ OptiqPrepare.Dummy.pop(context);
+ }
+ }
+
/** Prepares a SQL query for execution. For use within Optiq only. */
public static OptiqPrepare.PrepareResult<Object> prepare(
final OptiqConnection connection, final OptiqSchema schema,
@@ -262,6 +300,40 @@ public final class Schemas {
};
}
+ /** Returns the star tables defined in a schema.
+ *
+ * @param schema Schema */
+ public static List<OptiqSchema.TableEntry> getStarTables(OptiqSchema schema) {
+ final List<OptiqSchema.LatticeEntry> list = getLatticeEntries(schema);
+ return Lists.transform(list, TO_TABLE_ENTRY);
+ }
+
+ /** Returns the lattices defined in a schema.
+ *
+ * @param schema Schema */
+ public static List<Lattice> getLattices(OptiqSchema schema) {
+ final List<OptiqSchema.LatticeEntry> list = getLatticeEntries(schema);
+ return Lists.transform(list, TO_LATTICE);
+ }
+
+ /** Returns the lattices defined in a schema.
+ *
+ * @param schema Schema */
+ public static List<OptiqSchema.LatticeEntry> getLatticeEntries(
+ OptiqSchema schema) {
+ final List<OptiqSchema.LatticeEntry> list = Lists.newArrayList();
+ gatherLattices(schema, list);
+ return list;
+ }
+
+ private static void gatherLattices(OptiqSchema schema,
+ List<OptiqSchema.LatticeEntry> list) {
+ list.addAll(schema.getLatticeMap().values());
+ for (OptiqSchema subSchema : schema.getSubSchemaMap().values()) {
+ gatherLattices(subSchema, list);
+ }
+ }
+
/** Dummy data context that has no variables. */
private static class DummyDataContext implements DataContext {
private final OptiqConnection connection;
http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/acee9632/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 33ee4d3..1e8b2a3 100644
--- a/core/src/main/java/net/hydromatic/optiq/impl/StarTable.java
+++ b/core/src/main/java/net/hydromatic/optiq/impl/StarTable.java
@@ -17,8 +17,10 @@
package net.hydromatic.optiq.impl;
import net.hydromatic.optiq.Table;
+import net.hydromatic.optiq.TranslatableTable;
-import org.eigenbase.relopt.RelOptUtil;
+import org.eigenbase.rel.*;
+import org.eigenbase.relopt.*;
import org.eigenbase.reltype.RelDataType;
import org.eigenbase.reltype.RelDataTypeFactory;
import org.eigenbase.sql.validate.SqlValidatorUtil;
@@ -41,7 +43,7 @@ import java.util.List;
* to a query on top of a star table. Queries that are candidates to map onto
* the materialization are mapped onto the same star table.</p>
*/
-public class StarTable extends AbstractTable {
+public class StarTable extends AbstractTable implements TranslatableTable {
// TODO: we'll also need a list of join conditions between tables. For now
// we assume that join conditions match
public final ImmutableList<Table> tables;
@@ -79,6 +81,11 @@ public class StarTable extends AbstractTable {
SqlValidatorUtil.uniquify(nameList));
}
+ public RelNode toRel(RelOptTable.ToRelContext context, RelOptTable table) {
+ // Create a table scan of infinite cost.
+ return new StarTableScan(context.getCluster(), table);
+ }
+
public StarTable add(Table table) {
final List<Table> tables1 = new ArrayList<Table>(tables);
tables1.add(table);
@@ -103,6 +110,20 @@ public class StarTable extends AbstractTable {
throw new IllegalArgumentException("star table " + this
+ " does not contain table " + table);
}
+
+ /** Relational expression that scans a {@link StarTable}.
+ *
+ * <p>It has infinite cost.
+ */
+ private static class StarTableScan extends TableAccessRelBase {
+ public StarTableScan(RelOptCluster cluster, RelOptTable relOptTable) {
+ super(cluster, cluster.traitSetOf(Convention.NONE), relOptTable);
+ }
+
+ @Override public RelOptCost computeSelfCost(RelOptPlanner planner) {
+ return planner.getCostFactory().makeInfiniteCost();
+ }
+ }
}
// End StarTable.java
http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/acee9632/core/src/main/java/net/hydromatic/optiq/jdbc/OptiqPrepare.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/net/hydromatic/optiq/jdbc/OptiqPrepare.java b/core/src/main/java/net/hydromatic/optiq/jdbc/OptiqPrepare.java
index 4663ca9..f9f062c 100644
--- a/core/src/main/java/net/hydromatic/optiq/jdbc/OptiqPrepare.java
+++ b/core/src/main/java/net/hydromatic/optiq/jdbc/OptiqPrepare.java
@@ -60,8 +60,9 @@ public interface OptiqPrepare {
}
};
- ParseResult parse(
- Context context, String sql);
+ ParseResult parse(Context context, String sql);
+
+ ConvertResult convert(Context context, String sql);
<T> PrepareResult<T> prepareSql(
Context context,
@@ -202,6 +203,18 @@ public interface OptiqPrepare {
}
}
+ /** The result of parsing and validating a SQL query and converting it to
+ * relational algebra. */
+ public static class ConvertResult extends ParseResult {
+ public final RelNode relNode;
+
+ public ConvertResult(OptiqPrepareImpl prepare, SqlValidator validator,
+ String sql, SqlNode sqlNode, RelDataType rowType, RelNode relNode) {
+ super(prepare, validator, sql, sqlNode, rowType);
+ this.relNode = relNode;
+ }
+ }
+
/** The result of preparing a query. It gives the Avatica driver framework
* the information it needs to create a prepared statement, or to execute a
* statement directly, without an explicit prepare step. */
http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/acee9632/core/src/main/java/net/hydromatic/optiq/jdbc/OptiqSchema.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/net/hydromatic/optiq/jdbc/OptiqSchema.java b/core/src/main/java/net/hydromatic/optiq/jdbc/OptiqSchema.java
index 9d7fb80..b97b06b 100644
--- a/core/src/main/java/net/hydromatic/optiq/jdbc/OptiqSchema.java
+++ b/core/src/main/java/net/hydromatic/optiq/jdbc/OptiqSchema.java
@@ -22,6 +22,8 @@ import net.hydromatic.linq4j.expressions.Expression;
import net.hydromatic.optiq.*;
import net.hydromatic.optiq.Table;
import net.hydromatic.optiq.impl.MaterializedViewTable;
+import net.hydromatic.optiq.impl.StarTable;
+import net.hydromatic.optiq.materialize.Lattice;
import net.hydromatic.optiq.util.Compatible;
import org.eigenbase.util.Pair;
@@ -62,6 +64,8 @@ public class OptiqSchema {
new TreeMap<String, TableEntry>(COMPARATOR);
private final Multimap<String, FunctionEntry> functionMap =
LinkedListMultimap.create();
+ private final NavigableMap<String, LatticeEntry> latticeMap =
+ new TreeMap<String, LatticeEntry>(COMPARATOR);
private final NavigableSet<String> functionNames =
new TreeSet<String>(COMPARATOR);
private final NavigableMap<String, FunctionEntry> nullaryFunctionMap =
@@ -137,6 +141,15 @@ public class OptiqSchema {
return entry;
}
+ private LatticeEntry add(String name, Lattice lattice) {
+ if (latticeMap.containsKey(name)) {
+ throw new RuntimeException("Duplicate lattice '" + name + "'");
+ }
+ final LatticeEntryImpl entry = new LatticeEntryImpl(this, name, lattice);
+ latticeMap.put(name, entry);
+ return entry;
+ }
+
public OptiqRootSchema root() {
for (OptiqSchema schema = this;;) {
if (schema.parent == null) {
@@ -301,6 +314,13 @@ public class OptiqSchema {
return Compatible.INSTANCE.navigableMap(builder.build());
}
+ /** Returns a collection of lattices.
+ *
+ * <p>All are explicit (defined using {@link #add(String, Lattice)}). */
+ public NavigableMap<String, LatticeEntry> getLatticeMap() {
+ return Compatible.INSTANCE.immutableNavigableMap(latticeMap);
+ }
+
/** Returns the set of all table names. Includes implicit and explicit tables
* and functions with zero parameters. */
public NavigableSet<String> getTableNames() {
@@ -502,6 +522,17 @@ public class OptiqSchema {
public abstract boolean isMaterialization();
}
+ /** Membership of a lattice in a schema. */
+ public abstract static class LatticeEntry extends Entry {
+ public LatticeEntry(OptiqSchema schema, String name) {
+ super(schema, name);
+ }
+
+ public abstract Lattice getLattice();
+
+ public abstract TableEntry getStarTable();
+ }
+
/** Implementation of {@link SchemaPlus} based on an {@code OptiqSchema}. */
private class SchemaPlusImpl implements SchemaPlus {
public OptiqSchema optiqSchema() {
@@ -591,6 +622,10 @@ public class OptiqSchema {
public void add(String name, net.hydromatic.optiq.Function function) {
OptiqSchema.this.add(name, function);
}
+
+ public void add(String name, Lattice lattice) {
+ OptiqSchema.this.add(name, lattice);
+ }
}
/**
@@ -636,6 +671,33 @@ public class OptiqSchema {
}
}
+ /**
+ * Implementation of {@link LatticeEntry}
+ * where all properties are held in fields.
+ */
+ public static class LatticeEntryImpl extends LatticeEntry {
+ private final Lattice lattice;
+ private final OptiqSchema.TableEntry starTableEntry;
+
+ /** Creates a LatticeEntryImpl. */
+ public LatticeEntryImpl(OptiqSchema schema, String name, Lattice lattice) {
+ super(schema, name);
+ this.lattice = lattice;
+
+ // Star table has same name as lattice and is in same schema.
+ final StarTable starTable = lattice.createStarTable();
+ starTableEntry = schema.add(name, starTable);
+ }
+
+ public Lattice getLattice() {
+ return lattice;
+ }
+
+ public TableEntry getStarTable() {
+ return starTableEntry;
+ }
+ }
+
/** Strategy for caching the value of an object and re-creating it if its
* value is out of date as of a given timestamp.
*
http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/acee9632/core/src/main/java/net/hydromatic/optiq/materialize/Lattice.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/net/hydromatic/optiq/materialize/Lattice.java b/core/src/main/java/net/hydromatic/optiq/materialize/Lattice.java
new file mode 100644
index 0000000..984b157
--- /dev/null
+++ b/core/src/main/java/net/hydromatic/optiq/materialize/Lattice.java
@@ -0,0 +1,226 @@
+/*
+ * 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 net.hydromatic.optiq.materialize;
+
+import net.hydromatic.optiq.Schemas;
+import net.hydromatic.optiq.Table;
+import net.hydromatic.optiq.impl.MaterializedViewTable;
+import net.hydromatic.optiq.impl.StarTable;
+import net.hydromatic.optiq.jdbc.OptiqPrepare;
+import net.hydromatic.optiq.jdbc.OptiqSchema;
+import net.hydromatic.optiq.util.graph.*;
+
+import org.eigenbase.rel.*;
+import org.eigenbase.relopt.RelOptUtil;
+import org.eigenbase.rex.*;
+import org.eigenbase.util.mapping.IntPair;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.*;
+
+import java.util.*;
+
+/**
+ * Structure that allows materialized views based upon a star schema to be
+ * recognized and recommended.
+ */
+public class Lattice {
+ public final ImmutableList<Node> nodes;
+
+ private Lattice(List<Node> nodes) {
+ this.nodes = ImmutableList.copyOf(nodes);
+
+ // Validate that nodes form a tree; each node except the first references
+ // a predecessor.
+ for (int i = 0; i < nodes.size(); i++) {
+ Node node = nodes.get(i);
+ if (i == 0) {
+ assert node.parent == null;
+ } else {
+ assert nodes.subList(0, i).contains(node.parent);
+ }
+ }
+ }
+
+ /** Creates a Lattice. */
+ public static Lattice create(OptiqSchema schema, String sql) {
+ OptiqPrepare.ConvertResult parsed =
+ Schemas.convert(MaterializedViewTable.MATERIALIZATION_CONNECTION,
+ schema, schema.path(null), sql);
+
+ // Walk the join tree.
+ List<RelNode> relNodes = Lists.newArrayList();
+ List<int[][]> tempLinks = Lists.newArrayList();
+ populate(relNodes, tempLinks, parsed.relNode);
+
+ // Build a graph.
+ final DirectedGraph<RelNode, Edge> graph =
+ DefaultDirectedGraph.create(Edge.FACTORY);
+ for (RelNode node : relNodes) {
+ graph.addVertex(node);
+ }
+ for (int[][] tempLink : tempLinks) {
+ final RelNode source = relNodes.get(tempLink[0][0]);
+ final RelNode target = relNodes.get(tempLink[1][0]);
+ Edge edge = graph.getEdge(source, target);
+ if (edge == null) {
+ edge = graph.addEdge(source, target);
+ }
+ edge.pairs.add(IntPair.of(tempLink[0][1], tempLink[1][1]));
+ }
+
+ // Convert the graph into a tree of nodes, each connected to a parent and
+ // with a join condition to that parent.
+ List<Node> nodes = Lists.newArrayList();
+ Node previous = null;
+ final Map<RelNode, Node> map = Maps.newIdentityHashMap();
+ for (RelNode relNode : TopologicalOrderIterator.of(graph)) {
+ final List<Edge> edges = graph.getInwardEdges(relNode);
+ Node node;
+ if (previous == null) {
+ if (!edges.isEmpty()) {
+ throw new RuntimeException("root node must not have relationships: "
+ + relNode);
+ }
+ node = new Node((TableAccessRelBase) relNode, null, null);
+ } else {
+ if (edges.size() != 1) {
+ throw new RuntimeException(
+ "child node must have precisely one parent: " + relNode);
+ }
+ final Edge edge = edges.get(0);
+ node =
+ new Node((TableAccessRelBase) relNode, map.get(edge.getSource()),
+ edge.pairs);
+ }
+ nodes.add(node);
+ map.put(relNode, node);
+ previous = node;
+ }
+ return new Lattice(nodes);
+ }
+
+ private static boolean populate(List<RelNode> nodes, List<int[][]> tempLinks,
+ RelNode rel) {
+ if (nodes.isEmpty() && rel instanceof ProjectRel) {
+ return populate(nodes, tempLinks, ((ProjectRel) rel).getChild());
+ }
+ if (rel instanceof TableAccessRelBase) {
+ nodes.add(rel);
+ return true;
+ }
+ if (rel instanceof JoinRel) {
+ JoinRel join = (JoinRel) rel;
+ if (join.getJoinType() != JoinRelType.INNER) {
+ throw new RuntimeException("only inner join allowed, but got "
+ + join.getJoinType());
+ }
+ populate(nodes, tempLinks, join.getLeft());
+ populate(nodes, tempLinks, join.getRight());
+ for (RexNode rex : RelOptUtil.conjunctions(join.getCondition())) {
+ tempLinks.add(grab(nodes, rex));
+ }
+ return true;
+ }
+ throw new RuntimeException("Invalid node type "
+ + rel.getClass().getSimpleName() + " in lattice query");
+ }
+
+ /** Converts an "t1.c1 = t2.c2" expression into two (input, field) pairs. */
+ private static int[][] grab(List<RelNode> leaves, RexNode rex) {
+ switch (rex.getKind()) {
+ case EQUALS:
+ break;
+ default:
+ throw new AssertionError("only equi-join allowed");
+ }
+ final List<RexNode> operands = ((RexCall) rex).getOperands();
+ return new int[][] {
+ inputField(leaves, operands.get(0)),
+ inputField(leaves, operands.get(1))};
+ }
+
+ /** Converts an expression into an (input, field) pair. */
+ private static int[] inputField(List<RelNode> leaves, RexNode rex) {
+ if (!(rex instanceof RexInputRef)) {
+ throw new RuntimeException("only equi-join of columns allowed: " + rex);
+ }
+ RexInputRef ref = (RexInputRef) rex;
+ int start = 0;
+ for (int i = 0; i < leaves.size(); i++) {
+ final RelNode leaf = leaves.get(i);
+ final int end = start + leaf.getRowType().getFieldCount();
+ if (ref.getIndex() < end) {
+ return new int[] {i, ref.getIndex() - start};
+ }
+ start = end;
+ }
+ throw new AssertionError("input not found");
+ }
+
+ public StarTable createStarTable() {
+ final List<Table> tables = Lists.newArrayList();
+ for (Node node : nodes) {
+ tables.add(node.scan.getTable().unwrap(Table.class));
+ }
+ return new StarTable(tables);
+ }
+
+ /** Source relation of a lattice.
+ *
+ * <p>Relations form a tree; all relations except the root relation
+ * (the fact table) have precisely one parent and an equi-join
+ * condition on one or more pairs of columns linking to it. */
+ public static class Node {
+ public final TableAccessRelBase scan;
+ public final Node parent;
+ public final ImmutableList<IntPair> link;
+
+ public Node(TableAccessRelBase scan, Node parent, List<IntPair> link) {
+ this.scan = Preconditions.checkNotNull(scan);
+ this.parent = parent;
+ this.link = link == null ? null : ImmutableList.copyOf(link);
+ assert (parent == null) == (link == null);
+ }
+ }
+
+ /** Edge in the temporary graph. */
+ private static class Edge extends DefaultEdge {
+ public static final DirectedGraph.EdgeFactory<RelNode, Edge> FACTORY =
+ new DirectedGraph.EdgeFactory<RelNode, Edge>() {
+ public Edge createEdge(RelNode source, RelNode target) {
+ return new Edge(source, target);
+ }
+ };
+
+ final List<IntPair> pairs = Lists.newArrayList();
+
+ public Edge(RelNode source, RelNode target) {
+ super(source, target);
+ }
+
+ public RelNode getTarget() {
+ return (RelNode) target;
+ }
+
+ public RelNode getSource() {
+ return (RelNode) source;
+ }
+ }
+}
+
+// End Lattice.java
http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/acee9632/core/src/main/java/net/hydromatic/optiq/model/JsonLattice.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/net/hydromatic/optiq/model/JsonLattice.java b/core/src/main/java/net/hydromatic/optiq/model/JsonLattice.java
new file mode 100644
index 0000000..60abce0
--- /dev/null
+++ b/core/src/main/java/net/hydromatic/optiq/model/JsonLattice.java
@@ -0,0 +1,39 @@
+/*
+ * 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 net.hydromatic.optiq.model;
+
+/**
+ * Element that describes a star schema and provides a framework for defining,
+ * recognizing, and recommending materialized views at various levels of
+ * aggregation.
+ *
+ * @see JsonRoot Description of schema elements
+ */
+public class JsonLattice {
+ public String name;
+ public String sql;
+
+ public void accept(ModelHandler handler) {
+ handler.visit(this);
+ }
+
+ @Override public String toString() {
+ return "JsonLattice(name=" + name + ", sql=" + sql + ")";
+ }
+}
+
+// End JsonLattice.java
http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/acee9632/core/src/main/java/net/hydromatic/optiq/model/JsonSchema.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/net/hydromatic/optiq/model/JsonSchema.java b/core/src/main/java/net/hydromatic/optiq/model/JsonSchema.java
index e1e7cc4..f0921dd 100644
--- a/core/src/main/java/net/hydromatic/optiq/model/JsonSchema.java
+++ b/core/src/main/java/net/hydromatic/optiq/model/JsonSchema.java
@@ -19,7 +19,8 @@ package net.hydromatic.optiq.model;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
-import java.util.ArrayList;
+import com.google.common.collect.Lists;
+
import java.util.List;
/**
@@ -42,8 +43,10 @@ public abstract class JsonSchema {
* string-list. */
public List<Object> path;
- public List<JsonMaterialization> materializations =
- new ArrayList<JsonMaterialization>();
+ public final List<JsonMaterialization> materializations =
+ Lists.newArrayList();
+
+ public final List<JsonLattice> lattices = Lists.newArrayList();
/** Whether to cache metadata (tables, functions and sub-schemas) generated
* by this schema. Default value is {@code true}.
@@ -71,6 +74,9 @@ public abstract class JsonSchema {
public abstract void accept(ModelHandler handler);
public void visitChildren(ModelHandler modelHandler) {
+ for (JsonLattice jsonLattice : lattices) {
+ jsonLattice.accept(modelHandler);
+ }
for (JsonMaterialization jsonMaterialization : materializations) {
jsonMaterialization.accept(modelHandler);
}
http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/acee9632/core/src/main/java/net/hydromatic/optiq/model/ModelHandler.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/net/hydromatic/optiq/model/ModelHandler.java b/core/src/main/java/net/hydromatic/optiq/model/ModelHandler.java
index 624e914..91cd1bf 100644
--- a/core/src/main/java/net/hydromatic/optiq/model/ModelHandler.java
+++ b/core/src/main/java/net/hydromatic/optiq/model/ModelHandler.java
@@ -21,6 +21,7 @@ import net.hydromatic.optiq.impl.*;
import net.hydromatic.optiq.impl.jdbc.JdbcSchema;
import net.hydromatic.optiq.jdbc.OptiqConnection;
import net.hydromatic.optiq.jdbc.OptiqSchema;
+import net.hydromatic.optiq.materialize.Lattice;
import org.eigenbase.util.Pair;
import org.eigenbase.util.Util;
@@ -242,6 +243,23 @@ public class ModelHandler {
}
}
+ public void visit(JsonLattice jsonLattice) {
+ try {
+ final SchemaPlus schema = currentSchema();
+ if (!schema.isMutable()) {
+ throw new RuntimeException(
+ "Cannot define lattice; parent schema '"
+ + currentSchemaName()
+ + "' is not a SemiMutableSchema");
+ }
+ OptiqSchema optiqSchema = OptiqSchema.from(schema);
+ schema.add(jsonLattice.name,
+ Lattice.create(optiqSchema, jsonLattice.sql));
+ } catch (Exception e) {
+ throw new RuntimeException("Error instantiating " + jsonLattice, e);
+ }
+ }
+
public void visit(JsonCustomTable jsonTable) {
try {
final SchemaPlus schema = currentMutableSchema("table");
http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/acee9632/core/src/main/java/net/hydromatic/optiq/prepare/OptiqMaterializer.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/net/hydromatic/optiq/prepare/OptiqMaterializer.java b/core/src/main/java/net/hydromatic/optiq/prepare/OptiqMaterializer.java
index cb35bcd..ba2ae80 100644
--- a/core/src/main/java/net/hydromatic/optiq/prepare/OptiqMaterializer.java
+++ b/core/src/main/java/net/hydromatic/optiq/prepare/OptiqMaterializer.java
@@ -30,11 +30,14 @@ import org.eigenbase.sql.parser.SqlParseException;
import org.eigenbase.sql.parser.SqlParser;
import org.eigenbase.sql2rel.SqlToRelConverter;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+
import java.util.*;
/**
-* Context for populating a {@link Materialization}.
-*/
+ * Context for populating a {@link Materialization}.
+ */
class OptiqMaterializer extends OptiqPrepareImpl.OptiqPreparingStmt {
public OptiqMaterializer(OptiqPrepare.Context context,
CatalogReader catalogReader, OptiqSchema schema,
@@ -76,16 +79,32 @@ class OptiqMaterializer extends OptiqPrepareImpl.OptiqPreparingStmt {
}
/** Converts a relational expression to use a
- * {@link net.hydromatic.optiq.impl.StarTable} defined in {@code schema}.
+ * {@link StarTable} defined in {@code schema}.
* Uses the first star table that fits. */
private void useStar(OptiqSchema schema, Materialization materialization) {
- List<OptiqSchema.TableEntry> starTables = getStarTables(schema);
+ for (Callback x : useStar(schema, materialization.queryRel)) {
+ // Success -- we found a star table that matches.
+ materialization.materialize(x.rel, x.starRelOptTable);
+ System.out.println("Materialization "
+ + materialization.materializedTable + " matched star table "
+ + x.starTable + "; query after re-write: "
+ + RelOptUtil.toString(materialization.queryRel));
+ }
+ }
+
+ /** Converts a relational expression to use a
+ * {@link net.hydromatic.optiq.impl.StarTable} defined in {@code schema}.
+ * Uses the first star table that fits. */
+ private Iterable<Callback> useStar(OptiqSchema schema, RelNode queryRel) {
+ List<OptiqSchema.TableEntry> starTables =
+ Schemas.getStarTables(schema.root());
if (starTables.isEmpty()) {
// Don't waste effort converting to leaf-join form.
- return;
+ return ImmutableList.of();
}
+ final List<Callback> list = Lists.newArrayList();
final RelNode rel2 =
- RelOptMaterialization.toLeafJoinForm(materialization.queryRel);
+ RelOptMaterialization.toLeafJoinForm(queryRel);
for (OptiqSchema.TableEntry starTable : starTables) {
final Table table = starTable.getTable();
assert table instanceof StarTable;
@@ -95,31 +114,7 @@ class OptiqMaterializer extends OptiqPrepareImpl.OptiqPreparingStmt {
final RelNode rel3 =
RelOptMaterialization.tryUseStar(rel2, starRelOptTable);
if (rel3 != null) {
- // Success -- we found a star table that matches.
- materialization.materialize(rel3, starRelOptTable);
- System.out.println("Materialization "
- + materialization.materializedTable + " matched star table "
- + starTable + "; query after re-write: "
- + RelOptUtil.toString(materialization.queryRel));
- return;
- }
- }
- }
-
- /** Returns the star tables defined in a schema.
- * @param schema Schema */
- private List<OptiqSchema.TableEntry> getStarTables(OptiqSchema schema) {
- final List<OptiqSchema.TableEntry> list =
- new ArrayList<OptiqSchema.TableEntry>();
- // TODO: Assumes that star tables are all defined in a schema called
- // "mat". Instead, we should look for star tables that use a given set of
- // tables, regardless of schema.
- final OptiqSchema matSchema = schema.root().getSubSchema("mat", true);
- if (matSchema != null) {
- for (OptiqSchema.TableEntry tis : matSchema.tableMap.values()) {
- if (tis.getTable().getJdbcTableType() == Schema.TableType.STAR) {
- list.add(tis);
- }
+ list.add(new Callback(rel3, starTable, starRelOptTable));
}
}
return list;
@@ -169,6 +164,20 @@ class OptiqMaterializer extends OptiqPrepareImpl.OptiqPreparingStmt {
}
}
+ /** Called when we discover a star table that matches. */
+ static class Callback {
+ public final RelNode rel;
+ public final OptiqSchema.TableEntry starTable;
+ public final RelOptTableImpl starRelOptTable;
+
+ Callback(RelNode rel,
+ OptiqSchema.TableEntry starTable,
+ RelOptTableImpl starRelOptTable) {
+ this.rel = rel;
+ this.starTable = starTable;
+ this.starRelOptTable = starRelOptTable;
+ }
+ }
}
// End OptiqMaterializer.java
http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/acee9632/core/src/main/java/net/hydromatic/optiq/prepare/OptiqPrepareImpl.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/net/hydromatic/optiq/prepare/OptiqPrepareImpl.java b/core/src/main/java/net/hydromatic/optiq/prepare/OptiqPrepareImpl.java
index b894962..4c19268 100644
--- a/core/src/main/java/net/hydromatic/optiq/prepare/OptiqPrepareImpl.java
+++ b/core/src/main/java/net/hydromatic/optiq/prepare/OptiqPrepareImpl.java
@@ -38,6 +38,7 @@ import net.hydromatic.optiq.tools.Frameworks;
import org.eigenbase.rel.*;
import org.eigenbase.rel.rules.*;
import org.eigenbase.relopt.*;
+import org.eigenbase.relopt.hep.*;
import org.eigenbase.relopt.volcano.VolcanoPlanner;
import org.eigenbase.reltype.*;
import org.eigenbase.rex.RexBuilder;
@@ -149,6 +150,15 @@ public class OptiqPrepareImpl implements OptiqPrepare {
public ParseResult parse(
Context context, String sql) {
+ return parse_(context, sql, false);
+ }
+
+ public ConvertResult convert(Context context, String sql) {
+ return (ConvertResult) parse_(context, sql, true);
+ }
+
+ /** Shared implementation for {@link #parse} and {@link #convert}. */
+ private ParseResult parse_(Context context, String sql, boolean convert) {
final JavaTypeFactory typeFactory = context.getTypeFactory();
OptiqCatalogReader catalogReader =
new OptiqCatalogReader(
@@ -167,8 +177,24 @@ public class OptiqPrepareImpl implements OptiqPrepare {
new OptiqSqlValidator(
SqlStdOperatorTable.instance(), catalogReader, typeFactory);
SqlNode sqlNode1 = validator.validate(sqlNode);
- return new ParseResult(this, validator, sql, sqlNode1,
- validator.getValidatedNodeType(sqlNode1));
+ if (!convert) {
+ return new ParseResult(this, validator, sql, sqlNode1,
+ validator.getValidatedNodeType(sqlNode1));
+ }
+ final OptiqPreparingStmt preparingStmt =
+ new OptiqPreparingStmt(
+ context,
+ catalogReader,
+ typeFactory,
+ context.getRootSchema(),
+ null,
+ new HepPlanner(new HepProgramBuilder().build()),
+ EnumerableConvention.INSTANCE);
+ final SqlToRelConverter converter =
+ preparingStmt.getSqlToRelConverter(validator, catalogReader);
+ final RelNode relNode = converter.convertQuery(sqlNode1, false, true);
+ return new ConvertResult(this, validator, sql, sqlNode1,
+ validator.getValidatedNodeType(sqlNode1), relNode);
}
/** Creates a collection of planner factories.
@@ -374,8 +400,10 @@ public class OptiqPrepareImpl implements OptiqPrepare {
for (Prepare.Materialization materialization : materializations) {
populateMaterializations(context, planner, materialization);
}
+ final List<OptiqSchema.LatticeEntry> lattices =
+ Schemas.getLatticeEntries(rootSchema);
preparedResult = preparingStmt.prepareSql(
- sqlNode, Object.class, validator, true, materializations);
+ sqlNode, Object.class, validator, true, materializations, lattices);
switch (sqlNode.getKind()) {
case INSERT:
case EXPLAIN:
@@ -643,7 +671,8 @@ public class OptiqPrepareImpl implements OptiqPrepare {
rootRel = trimUnusedFields(rootRel);
final List<Materialization> materializations = ImmutableList.of();
- rootRel = optimize(resultType, rootRel, materializations);
+ final List<OptiqSchema.LatticeEntry> lattices = ImmutableList.of();
+ rootRel = optimize(resultType, rootRel, materializations, lattices);
if (timingTracer != null) {
timingTracer.traceTime("end optimization");
http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/acee9632/core/src/main/java/net/hydromatic/optiq/prepare/Prepare.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/net/hydromatic/optiq/prepare/Prepare.java b/core/src/main/java/net/hydromatic/optiq/prepare/Prepare.java
index 87903ed..8f17eb7 100644
--- a/core/src/main/java/net/hydromatic/optiq/prepare/Prepare.java
+++ b/core/src/main/java/net/hydromatic/optiq/prepare/Prepare.java
@@ -18,6 +18,7 @@ package net.hydromatic.optiq.prepare;
import net.hydromatic.optiq.DataContext;
import net.hydromatic.optiq.impl.StarTable;
+import net.hydromatic.optiq.impl.java.JavaTypeFactory;
import net.hydromatic.optiq.jdbc.OptiqPrepare;
import net.hydromatic.optiq.jdbc.OptiqSchema;
import net.hydromatic.optiq.runtime.Bindable;
@@ -95,10 +96,12 @@ public abstract class Prepare {
* @param rootRel root of a relational expression
*
* @param materializations Tables known to be populated with a given query
+ * @param lattices Lattices
* @return an equivalent optimized relational expression
*/
protected RelNode optimize(RelDataType logicalRowType, final RelNode rootRel,
- final List<Materialization> materializations) {
+ final List<Materialization> materializations,
+ final List<OptiqSchema.LatticeEntry> lattices) {
final RelOptPlanner planner = rootRel.getCluster().getPlanner();
planner.setRoot(rootRel);
@@ -116,6 +119,16 @@ public abstract class Prepare {
materialization.starRelOptTable));
}
+ for (OptiqSchema.LatticeEntry lattice : lattices) {
+ final OptiqSchema.TableEntry starTable = lattice.getStarTable();
+ final JavaTypeFactory typeFactory = context.getTypeFactory();
+ final RelOptTableImpl starRelOptTable =
+ RelOptTableImpl.create(catalogReader,
+ starTable.getTable().getRowType(typeFactory), starTable);
+ planner.addLattice(
+ new RelOptLattice(lattice.getLattice(), starRelOptTable));
+ }
+
final RelNode rootRel4 = program.run(planner, rootRel, desiredTraits);
if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.fine(
@@ -160,14 +173,16 @@ public abstract class Prepare {
Class runtimeContextClass,
SqlValidator validator,
boolean needsValidation,
- List<Materialization> materializations) {
+ List<Materialization> materializations,
+ List<OptiqSchema.LatticeEntry> lattices) {
return prepareSql(
sqlQuery,
sqlQuery,
runtimeContextClass,
validator,
needsValidation,
- materializations);
+ materializations,
+ lattices);
}
public PreparedResult prepareSql(
@@ -176,7 +191,8 @@ public abstract class Prepare {
Class runtimeContextClass,
SqlValidator validator,
boolean needsValidation,
- List<Materialization> materializations) {
+ List<Materialization> materializations,
+ List<OptiqSchema.LatticeEntry> lattices) {
queryString = sqlQuery.toString();
init(runtimeContextClass);
@@ -243,13 +259,14 @@ public abstract class Prepare {
switch (explainDepth) {
case PHYSICAL:
default:
- rootRel = optimize(rootRel.getRowType(), rootRel, materializations);
+ rootRel = optimize(rootRel.getRowType(), rootRel, materializations,
+ lattices);
return createPreparedExplanation(
null, parameterRowType, rootRel, explainAsXml, detailLevel);
}
}
- rootRel = optimize(resultType, rootRel, materializations);
+ rootRel = optimize(resultType, rootRel, materializations, lattices);
if (timingTracer != null) {
timingTracer.traceTime("end optimization");
http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/acee9632/core/src/main/java/net/hydromatic/optiq/runtime/Hook.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/net/hydromatic/optiq/runtime/Hook.java b/core/src/main/java/net/hydromatic/optiq/runtime/Hook.java
index 8944578..c8c7a80 100644
--- a/core/src/main/java/net/hydromatic/optiq/runtime/Hook.java
+++ b/core/src/main/java/net/hydromatic/optiq/runtime/Hook.java
@@ -47,6 +47,9 @@ public enum Hook {
* optimization. */
TRIMMED,
+ /** Called by the planner after substituting a materialization. */
+ SUB,
+
/** Called when a constant expression is being reduced. */
EXPRESSION_REDUCER,
@@ -130,6 +133,12 @@ public enum Hook {
* JDK 1.6.</p>
*/
public interface Closeable /*extends AutoCloseable*/ {
+ /** Closeable that does nothing. */
+ Closeable EMPTY =
+ new Closeable() {
+ public void close() {}
+ };
+
void close(); // override, removing "throws"
}
}
http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/acee9632/core/src/main/java/net/hydromatic/optiq/util/Compatible.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/net/hydromatic/optiq/util/Compatible.java b/core/src/main/java/net/hydromatic/optiq/util/Compatible.java
index b410fc1..0c8d09d 100644
--- a/core/src/main/java/net/hydromatic/optiq/util/Compatible.java
+++ b/core/src/main/java/net/hydromatic/optiq/util/Compatible.java
@@ -41,6 +41,10 @@ public interface Compatible {
* {@link java.util.NavigableMap}. */
<K, V> NavigableMap<K, V> navigableMap(ImmutableSortedMap<K, V> map);
+ /** Converts a {@link Map} to a {@link java.util.NavigableMap} that is
+ * immutable. */
+ <K, V> NavigableMap<K, V> immutableNavigableMap(NavigableMap<K, V> map);
+
/** Creates the implementation of Compatible suitable for the
* current environment. */
class Factory {
@@ -71,6 +75,11 @@ public interface Compatible {
ImmutableSortedMap map = (ImmutableSortedMap) args[0];
return CompatibleGuava11.navigableMap(map);
}
+ if (method.getName().equals("immutableNavigableMap")) {
+ Map map = (Map) args[0];
+ ImmutableSortedMap sortedMap = ImmutableSortedMap.copyOf(map);
+ return CompatibleGuava11.navigableMap(sortedMap);
+ }
return null;
}
});
http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/acee9632/core/src/main/java/net/hydromatic/optiq/util/graph/DefaultDirectedGraph.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/net/hydromatic/optiq/util/graph/DefaultDirectedGraph.java b/core/src/main/java/net/hydromatic/optiq/util/graph/DefaultDirectedGraph.java
index 857a922..3bafdf6 100644
--- a/core/src/main/java/net/hydromatic/optiq/util/graph/DefaultDirectedGraph.java
+++ b/core/src/main/java/net/hydromatic/optiq/util/graph/DefaultDirectedGraph.java
@@ -37,7 +37,12 @@ public class DefaultDirectedGraph<V, E extends DefaultEdge>
}
public static <V> DefaultDirectedGraph<V, DefaultEdge> create() {
- return new DefaultDirectedGraph<V, DefaultEdge>(DefaultEdge.<V>factory());
+ return create(DefaultEdge.<V>factory());
+ }
+
+ public static <V, E extends DefaultEdge> DefaultDirectedGraph<V, E> create(
+ EdgeFactory<V, E> edgeFactory) {
+ return new DefaultDirectedGraph<V, E>(edgeFactory);
}
@Override
http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/acee9632/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
new file mode 100644
index 0000000..b85234e
--- /dev/null
+++ b/core/src/main/java/org/eigenbase/relopt/RelOptLattice.java
@@ -0,0 +1,57 @@
+/*
+ * 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.relopt;
+
+import java.util.List;
+
+import org.eigenbase.rel.RelNode;
+
+import net.hydromatic.optiq.materialize.Lattice;
+
+import com.google.common.collect.Lists;
+
+/**
+ * Use of a lattice by the query optimizer.
+ */
+public class RelOptLattice {
+ private final Lattice lattice;
+ private final RelOptTable starRelOptTable;
+ private final List<RelOptMaterialization> materializations =
+ Lists.newArrayList();
+
+ public RelOptLattice(Lattice lattice, RelOptTable starRelOptTable) {
+ this.lattice = lattice;
+ this.starRelOptTable = starRelOptTable;
+ }
+
+ public RelOptTable rootTable() {
+ return lattice.nodes.get(0).scan.getTable();
+ }
+
+ /** Rewrites a relational expression to use a lattice.
+ *
+ * <p>Returns null if a rewrite is not possible.
+ *
+ * @param node Relational expression
+ * @return Rewritten query
+ */
+ public RelNode rewrite(RelNode node) {
+ return RelOptMaterialization.tryUseStar(node, starRelOptTable);
+ }
+}
+
+// End RelOptLattice.java
http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/acee9632/core/src/main/java/org/eigenbase/relopt/RelOptPlanner.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/eigenbase/relopt/RelOptPlanner.java b/core/src/main/java/org/eigenbase/relopt/RelOptPlanner.java
index e7185d1..4c9f3c8 100644
--- a/core/src/main/java/org/eigenbase/relopt/RelOptPlanner.java
+++ b/core/src/main/java/org/eigenbase/relopt/RelOptPlanner.java
@@ -156,6 +156,14 @@ public interface RelOptPlanner {
void addMaterialization(RelOptMaterialization materialization);
/**
+ * Defines a lattice.
+ *
+ * <p>The lattice may have materializations; it is not necessary to call
+ * {@link #addMaterialization} for these; they are registered implicitly.
+ */
+ void addLattice(RelOptLattice lattice);
+
+ /**
* Finds the most efficient expression to implement this query.
*
* @throws CannotPlanException if cannot find a plan
http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/acee9632/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 d0835dd..05b09c2 100644
--- a/core/src/main/java/org/eigenbase/relopt/hep/HepPlanner.java
+++ b/core/src/main/java/org/eigenbase/relopt/hep/HepPlanner.java
@@ -132,6 +132,10 @@ public class HepPlanner extends AbstractRelOptPlanner {
// ignore - this planner does not support materializations
}
+ public void addLattice(RelOptLattice lattice) {
+ // ignore - this planner does not support lattices
+ }
+
// implement RelOptPlanner
public boolean addRule(RelOptRule rule) {
boolean added = allRules.add(rule);
http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/acee9632/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 8de7415..e8b740b 100644
--- a/core/src/main/java/org/eigenbase/relopt/volcano/VolcanoPlanner.java
+++ b/core/src/main/java/org/eigenbase/relopt/volcano/VolcanoPlanner.java
@@ -34,9 +34,11 @@ import org.eigenbase.util.*;
import net.hydromatic.linq4j.expressions.Expressions;
import net.hydromatic.optiq.prepare.OptiqPrepareImpl;
+import net.hydromatic.optiq.runtime.Hook;
import net.hydromatic.optiq.runtime.Spaces;
import net.hydromatic.optiq.util.graph.*;
+import com.google.common.base.Function;
import com.google.common.collect.*;
import static org.eigenbase.util.Stacks.*;
@@ -50,6 +52,13 @@ public class VolcanoPlanner extends AbstractRelOptPlanner {
protected static final double COST_IMPROVEMENT = .5;
+ private static final Function<RelOptTable, List<String>> GET_QUALIFIED_NAME =
+ new Function<RelOptTable, List<String>>() {
+ public List<String> apply(RelOptTable relOptTable) {
+ return relOptTable.getQualifiedName();
+ }
+ };
+
//~ Instance fields --------------------------------------------------------
protected RelSubset root;
@@ -179,7 +188,9 @@ public class VolcanoPlanner extends AbstractRelOptPlanner {
private boolean locked;
private final List<RelOptMaterialization> materializations =
- new ArrayList<RelOptMaterialization>();
+ Lists.newArrayList();
+
+ private final List<RelOptLattice> lattices = Lists.newArrayList();
final Map<RelNode, Provenance> provenanceMap =
new HashMap<RelNode, Provenance>();
@@ -261,6 +272,15 @@ public class VolcanoPlanner extends AbstractRelOptPlanner {
materializations.add(materialization);
}
+ public void addLattice(RelOptLattice lattice) {
+ lattices.add(lattice);
+ }
+
+ private void useLattice(RelOptLattice lattice, RelNode rel) {
+ Hook.SUB.run(rel);
+ registerImpl(rel, root.set);
+ }
+
private void useMaterialization(RelOptMaterialization materialization) {
// Try to rewrite the original root query in terms of the materialized
// query. If that is possible, register the remnant query as equivalent
@@ -271,6 +291,7 @@ public class VolcanoPlanner extends AbstractRelOptPlanner {
// TODO: try to substitute other materializations in the remnant.
// Useful for big queries, e.g.
// (t1 group by c1) join (t2 group by c2).
+ Hook.SUB.run(sub);
registerImpl(sub, root.set);
return;
}
@@ -349,6 +370,23 @@ public class VolcanoPlanner extends AbstractRelOptPlanner {
}
}
}
+
+ // Use a lattice if the query uses at least the central (fact) table of the
+ // lattice.
+ final List<Pair<RelOptLattice, RelNode>> latticeUses = Lists.newArrayList();
+ final Set<List<String>> queryTableNames =
+ Sets.newHashSet(Iterables.transform(queryTables, GET_QUALIFIED_NAME));
+ for (RelOptLattice lattice : lattices) {
+ if (queryTableNames.contains(lattice.rootTable().getQualifiedName())) {
+ RelNode rel2 = lattice.rewrite(originalRoot);
+ if (rel2 != null) {
+ latticeUses.add(Pair.of(lattice, rel2));
+ }
+ }
+ }
+ if (!latticeUses.isEmpty()) {
+ useLattice(latticeUses.get(0).left, latticeUses.get(0).right);
+ }
}
/**
@@ -587,8 +625,8 @@ public class VolcanoPlanner extends AbstractRelOptPlanner {
* <p>Furthermore, after every 10 iterations without an implementable plan,
* RelSubSets that contain only logical RelNodes are given an importance
* boost via {@link #injectImportanceBoost()}. Once an implementable plan is
- * found, the artificially raised importances are cleared ({@link
- * #clearImportanceBoost()}).
+ * found, the artificially raised importance values are cleared (see
+ * {@link #clearImportanceBoost()}).
*
* @return the most efficient RelNode tree found for implementing the given
* query
http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/acee9632/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
new file mode 100644
index 0000000..80b4cde
--- /dev/null
+++ b/core/src/test/java/net/hydromatic/optiq/test/LatticeTest.java
@@ -0,0 +1,186 @@
+/*
+ * 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 net.hydromatic.optiq.test;
+
+import org.eigenbase.util.TestUtil;
+import org.eigenbase.util.Util;
+
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.junit.Assert.assertThat;
+
+/**
+ * Unit test for lattices.
+ */
+public class LatticeTest {
+ private OptiqAssert.AssertThat modelWithLattice(String name, String sql) {
+ return modelWithLattices(
+ "{ name: '" + name + "', sql: " + TestUtil.escapeString(sql) + "}");
+ }
+
+ private OptiqAssert.AssertThat modelWithLattices(String... lattices) {
+ final Class<JdbcTest.EmpDeptTableFactory> clazz =
+ JdbcTest.EmpDeptTableFactory.class;
+ return OptiqAssert.that().withModel(""
+ + "{\n"
+ + " version: '1.0',\n"
+ + " schemas: [\n"
+ + JdbcTest.FOODMART_SCHEMA
+ + ",\n"
+ + " {\n"
+ + " name: 'adhoc',\n"
+ + " tables: [\n"
+ + " {\n"
+ + " name: 'EMPLOYEES',\n"
+ + " type: 'custom',\n"
+ + " factory: '"
+ + clazz.getName()
+ + "',\n"
+ + " operand: {'foo': true, 'bar': 345}\n"
+ + " }\n"
+ + " ],\n"
+ + " lattices: "
+ + Arrays.toString(lattices)
+ + " }\n"
+ + " ]\n"
+ + "}").withSchema("adhoc");
+ }
+
+ /** Tests that it's OK for a lattice to have the same name as a table in the
+ * schema. */
+ @Test public void testLatticeWithSameNameAsTable() {
+ modelWithLattice("EMPLOYEES", "select * from \"foodmart\".\"days\"")
+ .query("select count(*) from EMPLOYEES")
+ .returnsValue("4");
+ }
+
+ /** Tests that it's an error to have two lattices with the same name in a
+ * schema. */
+ @Test public void testTwoLatticesWithSameNameFails() {
+ modelWithLattices(
+ "{name: 'Lattice1', sql: 'select * from \"foodmart\".\"days\"'}",
+ "{name: 'Lattice1', sql: 'select * from \"foodmart\".\"time_by_day\"'}")
+ .connectThrows("Duplicate lattice 'Lattice1'");
+ }
+
+ /** Tests a lattice whose SQL is invalid. */
+ @Test public void testLatticeInvalidSqlFails() {
+ modelWithLattice("star", "select foo from nonexistent")
+ .connectThrows("Error instantiating JsonLattice(name=star, ")
+ .connectThrows("Table 'NONEXISTENT' not found");
+ }
+
+ /** Tests a lattice whose SQL is invalid because it contains a GROUP BY. */
+ @Test public void testLatticeSqlWithGroupByFails() {
+ modelWithLattice("star",
+ "select 1 from \"foodmart\".\"sales_fact_1997\" as s group by \"product_id\"")
+ .connectThrows("Invalid node type AggregateRel in lattice query");
+ }
+
+ /** Tests a lattice whose SQL is invalid because it contains a ORDER BY. */
+ @Test public void testLatticeSqlWithOrderByFails() {
+ modelWithLattice("star",
+ "select 1 from \"foodmart\".\"sales_fact_1997\" as s order by \"product_id\"")
+ .connectThrows("Invalid node type SortRel in lattice query");
+ }
+
+ /** Tests a lattice whose SQL is invalid because it contains a UNION ALL. */
+ @Test public void testLatticeSqlWithUnionFails() {
+ modelWithLattice("star",
+ "select 1 from \"foodmart\".\"sales_fact_1997\" as s\n"
+ + "union all\n"
+ + "select 1 from \"foodmart\".\"sales_fact_1997\" as s")
+ .connectThrows("Invalid node type UnionRel in lattice query");
+ }
+
+ /** Tests a lattice with valid join SQL. */
+ @Test public void testLatticeSqlWithJoin() {
+ foodmartModel()
+ .query("values 1")
+ .returnsValue("1");
+ }
+
+ /** Tests a lattice with invalid SQL (for a lattice). */
+ @Test public void testLatticeInvalidSql() {
+ modelWithLattice("star",
+ "select 1 from \"foodmart\".\"sales_fact_1997\" as s\n"
+ + "join \"foodmart\".\"product\" as p using (\"product_id\")\n"
+ + "join \"foodmart\".\"time_by_day\" as t on s.\"product_id\" = 100")
+ .connectThrows("only equi-join of columns allowed: 100");
+ }
+
+ /** Left join is invalid in a lattice. */
+ @Test public void testLatticeInvalidSql2() {
+ modelWithLattice("star",
+ "select 1 from \"foodmart\".\"sales_fact_1997\" as s\n"
+ + "join \"foodmart\".\"product\" as p using (\"product_id\")\n"
+ + "left join \"foodmart\".\"time_by_day\" as t on s.\"product_id\" = p.\"product_id\"")
+ .connectThrows("only inner join allowed, but got LEFT");
+ }
+
+ /** When a lattice is registered, there is a table with the same name.
+ * It can be used for explain, but not for queries. */
+ @Test public void testLatticeStarTable() {
+ try {
+ foodmartModel()
+ .query("select count(*) from \"adhoc\".\"star\"")
+ .convertContains(
+ "AggregateRel(group=[{}], EXPR$0=[COUNT()])\n"
+ + " ProjectRel(DUMMY=[0])\n"
+ + " ProjectRel\n"
+ + " StarTableScan(table=[[adhoc, star]])\n");
+ } catch (RuntimeException e) {
+ assertThat(Util.getStackTrace(e), containsString("CannotPlanException"));
+ }
+ }
+
+ /** Tests that a 2-way join query can be mapped 4-way join lattice. */
+ @Test public void testLatticeRecognizeJoin() {
+ final AtomicInteger counter = new AtomicInteger();
+ foodmartModel()
+ .query(
+ "select s.\"unit_sales\", p.\"brand_name\"\n"
+ + "from \"foodmart\".\"sales_fact_1997\" as s\n"
+ + "join \"foodmart\".\"product\" as p using (\"product_id\")\n")
+ .substitutionMatches(
+ OptiqAssert.checkRel(
+ "ProjectRel(unit_sales=[$1], brand_name=[$3])\n"
+ + " JoinRel(condition=[=($0, $2)], joinType=[inner])\n"
+ + " ProjectRel(product_id=[$0], unit_sales=[$7])\n"
+ + " ProjectRel($f0=[$0], $f1=[$1], $f2=[$2], $f3=[$3], $f4=[$4], $f5=[$5], $f6=[$6], $f7=[$7])\n"
+ + " TableAccessRel(table=[[adhoc, star]])\n"
+ + " ProjectRel(product_id=[$1], brand_name=[$2])\n"
+ + " JdbcTableScan(table=[[foodmart, product]])\n",
+ counter));
+ assertThat(counter.intValue(), equalTo(1));
+ }
+
+ private OptiqAssert.AssertThat foodmartModel() {
+ return modelWithLattice("star",
+ "select 1 from \"foodmart\".\"sales_fact_1997\" as s\n"
+ + "join \"foodmart\".\"product\" as p using (\"product_id\")\n"
+ + "join \"foodmart\".\"time_by_day\" as t using (\"time_id\")\n"
+ + "join \"foodmart\".\"product_class\" as pc on p.\"product_class_id\" = pc.\"product_class_id\"");
+ }
+}
+
+// End LatticeTest.java
http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/acee9632/core/src/test/java/net/hydromatic/optiq/test/ModelTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/net/hydromatic/optiq/test/ModelTest.java b/core/src/test/java/net/hydromatic/optiq/test/ModelTest.java
index 25043d4..ec3aea4 100644
--- a/core/src/test/java/net/hydromatic/optiq/test/ModelTest.java
+++ b/core/src/test/java/net/hydromatic/optiq/test/ModelTest.java
@@ -175,6 +175,53 @@ public class ModelTest {
"Cannot define materialization; parent schema 'adhoc' is not a "
+ "SemiMutableSchema");
}
+
+ /** Tests a model containing a lattice. */
+ @Test public void testReadLattice() throws IOException {
+ final ObjectMapper mapper = mapper();
+ JsonRoot root = mapper.readValue(
+ "{\n"
+ + " version: '1.0',\n"
+ + " schemas: [\n"
+ + " {\n"
+ + " name: 'FoodMart',\n"
+ + " tables: [\n"
+ + " {\n"
+ + " name: 'time_by_day',\n"
+ + " columns: [\n"
+ + " {\n"
+ + " name: 'time_id'\n"
+ + " }\n"
+ + " ]\n"
+ + " },\n"
+ + " {\n"
+ + " name: 'sales_fact_1997',\n"
+ + " columns: [\n"
+ + " {\n"
+ + " name: 'time_id'\n"
+ + " }\n"
+ + " ]\n"
+ + " }\n"
+ + " ],\n"
+ + " lattices: [\n"
+ + " {\n"
+ + " name: 'SalesStar',\n"
+ + " sql: 'select * from sales_fact_1997'\n"
+ + " }\n"
+ + " ]\n"
+ + " }\n"
+ + " ]\n"
+ + "}",
+ JsonRoot.class);
+ assertEquals("1.0", root.version);
+ assertEquals(1, root.schemas.size());
+ final JsonMapSchema schema = (JsonMapSchema) root.schemas.get(0);
+ assertEquals("FoodMart", schema.name);
+ assertEquals(1, schema.lattices.size());
+ final JsonLattice lattice0 = schema.lattices.get(0);
+ assertEquals("SalesStar", lattice0.name);
+ assertEquals("select * from sales_fact_1997", lattice0.sql);
+ }
}
// End ModelTest.java
http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/acee9632/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 66b179e..b2fe555 100644
--- a/core/src/test/java/net/hydromatic/optiq/test/OptiqAssert.java
+++ b/core/src/test/java/net/hydromatic/optiq/test/OptiqAssert.java
@@ -19,6 +19,7 @@ package net.hydromatic.optiq.test;
import net.hydromatic.linq4j.function.Function1;
import net.hydromatic.optiq.*;
+import net.hydromatic.optiq.config.OptiqConnectionProperty;
import net.hydromatic.optiq.impl.AbstractSchema;
import net.hydromatic.optiq.impl.ViewTable;
import net.hydromatic.optiq.impl.clone.CloneSchema;
@@ -44,6 +45,7 @@ import java.sql.*;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.*;
+import java.util.concurrent.atomic.AtomicInteger;
import javax.sql.DataSource;
import static org.hamcrest.CoreMatchers.*;
@@ -147,6 +149,20 @@ public class OptiqAssert {
return new AssertThat(Config.REGULAR);
}
+ static Function1<RelNode, Void> checkRel(final String expected,
+ final AtomicInteger counter) {
+ return new Function1<RelNode, Void>() {
+ public Void apply(RelNode relNode) {
+ if (counter != null) {
+ counter.incrementAndGet();
+ }
+ String s = RelOptUtil.toString(relNode);
+ assertThat(s, containsString(expected));
+ return null;
+ }
+ };
+ }
+
static Function1<Throwable, Void> checkException(
final String expected) {
return new Function1<Throwable, Void>() {
@@ -356,7 +372,8 @@ public class OptiqAssert {
final List<Hook.Closeable> closeableList = Lists.newArrayList();
try {
((OptiqConnection) connection).getProperties().setProperty(
- "materializationsEnabled", Boolean.toString(materializationsEnabled));
+ OptiqConnectionProperty.MATERIALIZATIONS_ENABLED.camelName(),
+ Boolean.toString(materializationsEnabled));
for (Pair<Hook, Function> hook : hooks) {
closeableList.add(hook.left.addThread(hook.right));
}
@@ -401,19 +418,34 @@ public class OptiqAssert {
Connection connection,
String sql,
boolean materializationsEnabled,
- final Function1<RelNode, Void> convertChecker) throws Exception {
+ final Function1<RelNode, Void> convertChecker,
+ final Function1<RelNode, Void> substitutionChecker) throws Exception {
final String message =
"With materializationsEnabled=" + materializationsEnabled;
- Hook.Closeable closeable = Hook.TRIMMED.addThread(
- new Function<RelNode, Object>() {
- public Void apply(RelNode rel) {
- convertChecker.apply(rel);
- return null;
- }
- });
+ final Hook.Closeable closeable =
+ convertChecker == null
+ ? Hook.Closeable.EMPTY
+ : Hook.TRIMMED.addThread(
+ new Function<RelNode, Void>() {
+ public Void apply(RelNode rel) {
+ convertChecker.apply(rel);
+ return null;
+ }
+ });
+ final Hook.Closeable closeable2 =
+ substitutionChecker == null
+ ? Hook.Closeable.EMPTY
+ : Hook.SUB.addThread(
+ new Function<RelNode, Void>() {
+ public Void apply(RelNode rel) {
+ substitutionChecker.apply(rel);
+ return null;
+ }
+ });
try {
((OptiqConnection) connection).getProperties().setProperty(
- "materializationsEnabled", Boolean.toString(materializationsEnabled));
+ OptiqConnectionProperty.MATERIALIZATIONS_ENABLED.camelName(),
+ Boolean.toString(materializationsEnabled));
PreparedStatement statement = connection.prepareStatement(sql);
statement.close();
connection.close();
@@ -421,6 +453,7 @@ public class OptiqAssert {
throw new RuntimeException(message, e);
} finally {
closeable.close();
+ closeable2.close();
}
}
@@ -972,24 +1005,30 @@ public class OptiqAssert {
/** Checks that when the query (which was set using
* {@link AssertThat#query(String)}) is converted to a relational algebra
* expression matching the given string. */
- public AssertQuery convertContains(final String expected) {
- return convertMatches(
- new Function1<RelNode, Void>() {
- public Void apply(RelNode relNode) {
- String s = RelOptUtil.toString(relNode);
- assertThat(s, containsString(expected));
- return null;
- }
- });
+ public final AssertQuery convertContains(final String expected) {
+ return convertMatches(checkRel(expected, null));
}
public AssertQuery convertMatches(final Function1<RelNode, Void> checker) {
try {
- assertPrepare(createConnection(), sql, false, checker);
+ assertPrepare(createConnection(), sql, this.materializationsEnabled,
+ checker, null);
return this;
} catch (Exception e) {
- throw new RuntimeException(
- "exception while preparing [" + sql + "]", e);
+ throw new RuntimeException("exception while preparing [" + sql + "]",
+ e);
+ }
+ }
+
+ public AssertQuery substitutionMatches(
+ final Function1<RelNode, Void> checker) {
+ try {
+ assertPrepare(createConnection(), sql, materializationsEnabled,
+ null, checker);
+ return this;
+ } catch (Exception e) {
+ throw new RuntimeException("exception while preparing [" + sql + "]",
+ e);
}
}
@@ -1174,6 +1213,11 @@ public class OptiqAssert {
return this;
}
+ @Override public AssertQuery substitutionMatches(
+ Function1<RelNode, Void> checker) {
+ return this;
+ }
+
@Override
public AssertQuery planContains(String expected) {
return this;
http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/acee9632/core/src/test/java/net/hydromatic/optiq/test/OptiqSuite.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/net/hydromatic/optiq/test/OptiqSuite.java b/core/src/test/java/net/hydromatic/optiq/test/OptiqSuite.java
index 6f9ac93..1e58866 100644
--- a/core/src/test/java/net/hydromatic/optiq/test/OptiqSuite.java
+++ b/core/src/test/java/net/hydromatic/optiq/test/OptiqSuite.java
@@ -85,6 +85,7 @@ import org.junit.runners.Suite;
RelOptRulesTest.class,
RexExecutorTest.class,
MaterializationTest.class,
+ LatticeTest.class,
SqlLimitsTest.class,
LinqFrontJdbcBackTest.class,
JdbcFrontLinqBackTest.class,
http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/acee9632/core/src/test/java/org/eigenbase/test/MockRelOptPlanner.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/eigenbase/test/MockRelOptPlanner.java b/core/src/test/java/org/eigenbase/test/MockRelOptPlanner.java
index fd8d965..1d46a40 100644
--- a/core/src/test/java/org/eigenbase/test/MockRelOptPlanner.java
+++ b/core/src/test/java/org/eigenbase/test/MockRelOptPlanner.java
@@ -62,6 +62,10 @@ public class MockRelOptPlanner extends AbstractRelOptPlanner {
// ignore - this planner does not support materializations
}
+ public void addLattice(RelOptLattice lattice) {
+ // ignore - this planner does not support lattices
+ }
+
@Override public void clear() {
super.clear();
this.rule = null;
http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/acee9632/core/src/test/java/org/eigenbase/util/TestUtil.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/eigenbase/util/TestUtil.java b/core/src/test/java/org/eigenbase/util/TestUtil.java
index 34c6b4c..34dfd6a 100644
--- a/core/src/test/java/org/eigenbase/util/TestUtil.java
+++ b/core/src/test/java/org/eigenbase/util/TestUtil.java
@@ -134,6 +134,42 @@ public abstract class TestUtil {
return buf.toString();
}
+ /** Quotes a string for Java or JSON. */
+ public static String escapeString(String s) {
+ return escapeString(new StringBuilder(), s).toString();
+ }
+
+ /** Quotes a string for Java or JSON, into a builder. */
+ public static StringBuilder escapeString(StringBuilder buf, String s) {
+ buf.append('"');
+ int n = s.length();
+ char lastChar = 0;
+ for (int i = 0; i < n; ++i) {
+ char c = s.charAt(i);
+ switch (c) {
+ case '\\':
+ buf.append("\\\\");
+ break;
+ case '"':
+ buf.append("\\\"");
+ break;
+ case '\n':
+ buf.append("\\n");
+ break;
+ case '\r':
+ if (lastChar != '\n') {
+ buf.append("\\r");
+ }
+ break;
+ default:
+ buf.append(c);
+ break;
+ }
+ lastChar = c;
+ }
+ return buf.append('"');
+ }
+
/**
* Quotes a pattern.
*/