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/07/28 20:56:25 UTC

[1/7] git commit: Add methods and tests for BitSets, and re-organize tests.

Repository: incubator-optiq
Updated Branches:
  refs/heads/master 2d6d78c39 -> d861a0887


Add methods and tests for BitSets, and re-organize tests.


Project: http://git-wip-us.apache.org/repos/asf/incubator-optiq/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-optiq/commit/4a46cdb5
Tree: http://git-wip-us.apache.org/repos/asf/incubator-optiq/tree/4a46cdb5
Diff: http://git-wip-us.apache.org/repos/asf/incubator-optiq/diff/4a46cdb5

Branch: refs/heads/master
Commit: 4a46cdb5bd4f80232fb6e673fc5ae5827bfd80ce
Parents: 2d6d78c
Author: Julian Hyde <jh...@apache.org>
Authored: Mon Jul 21 14:30:30 2014 -0700
Committer: Julian Hyde <jh...@apache.org>
Committed: Mon Jul 28 11:12:21 2014 -0700

----------------------------------------------------------------------
 .../java/net/hydromatic/optiq/util/BitSets.java |  31 +++-
 .../net/hydromatic/optiq/test/OptiqSuite.java   |   2 +
 .../net/hydromatic/optiq/util/BitSetsTest.java  | 170 +++++++++++++++++++
 .../test/java/org/eigenbase/util/UtilTest.java  |  90 +---------
 4 files changed, 199 insertions(+), 94 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/4a46cdb5/core/src/main/java/net/hydromatic/optiq/util/BitSets.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/net/hydromatic/optiq/util/BitSets.java b/core/src/main/java/net/hydromatic/optiq/util/BitSets.java
index b7c9424..f0dbd4f 100644
--- a/core/src/main/java/net/hydromatic/optiq/util/BitSets.java
+++ b/core/src/main/java/net/hydromatic/optiq/util/BitSets.java
@@ -104,14 +104,18 @@ public final class BitSets {
   }
 
   /**
-   * Converts a bitset to an array.
+   * Converts a BitSet to an array.
    *
    * @param bitSet Bit set
-   * @return List of set bits
+   * @return Array of set bits
    */
-  public static Integer[] toArray(final BitSet bitSet) {
-    final List<Integer> list = toList(bitSet);
-    return list.toArray(new Integer[list.size()]);
+  public static int[] toArray(final BitSet bitSet) {
+    final int[] integers = new int[bitSet.cardinality()];
+    int j = 0;
+    for (int i = bitSet.nextSetBit(0); i >= 0; i = bitSet.nextSetBit(i + 1)) {
+      integers[j++] = i;
+    }
+    return integers;
   }
 
   /**
@@ -189,6 +193,23 @@ public final class BitSets {
   public static BitSet range(int toIndex) {
     return range(0, toIndex);
   }
+
+  /** Sets all bits in a given BitSet corresponding to integers from a list. */
+  public static void setAll(BitSet bitSet, Iterable<? extends Number> list) {
+    for (Number number : list) {
+      bitSet.set(number.intValue());
+    }
+  }
+
+  /** Returns a BitSet that is the union of the given BitSets. Does not modify
+   * any of the inputs. */
+  public static BitSet union(BitSet set0, BitSet... sets) {
+    final BitSet s = (BitSet) set0.clone();
+    for (BitSet set : sets) {
+      s.or(set);
+    }
+    return s;
+  }
 }
 
 // End BitSets.java

http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/4a46cdb5/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 a147af6..b2a2f3f 100644
--- a/core/src/test/java/net/hydromatic/optiq/test/OptiqSuite.java
+++ b/core/src/test/java/net/hydromatic/optiq/test/OptiqSuite.java
@@ -22,6 +22,7 @@ import net.hydromatic.optiq.runtime.BinarySearchTest;
 import net.hydromatic.optiq.tools.FrameworksTest;
 import net.hydromatic.optiq.tools.PlannerTest;
 import net.hydromatic.optiq.tools.SqlRunTest;
+import net.hydromatic.optiq.util.BitSetsTest;
 import net.hydromatic.optiq.util.PartiallyOrderedSetTest;
 import net.hydromatic.optiq.util.graph.DirectedGraphTest;
 
@@ -52,6 +53,7 @@ import org.junit.runners.Suite;
 @Suite.SuiteClasses({
     // very fast tests (under 0.1s)
     ArrayTableTest.class,
+    BitSetsTest.class,
     DirectedGraphTest.class,
     ReflectVisitorTest.class,
     RelOptUtilTest.class,

http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/4a46cdb5/core/src/test/java/net/hydromatic/optiq/util/BitSetsTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/net/hydromatic/optiq/util/BitSetsTest.java b/core/src/test/java/net/hydromatic/optiq/util/BitSetsTest.java
new file mode 100644
index 0000000..3c7076d
--- /dev/null
+++ b/core/src/test/java/net/hydromatic/optiq/util/BitSetsTest.java
@@ -0,0 +1,170 @@
+/*
+// Licensed to Julian Hyde under one or more contributor license
+// agreements. See the NOTICE file distributed with this work for
+// additional information regarding copyright ownership.
+//
+// Julian Hyde 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.util;
+
+import org.eigenbase.util.ImmutableIntList;
+
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.BitSet;
+import java.util.Collections;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Unit test for {@link net.hydromatic.optiq.util.BitSets}.
+ */
+public class BitSetsTest {
+  /**
+   * Tests the method
+   * {@link net.hydromatic.optiq.util.BitSets#toIter(java.util.BitSet)}.
+   */
+  @Test public void testToIterBitSet() {
+    BitSet bitSet = new BitSet();
+
+    assertToIterBitSet("", bitSet);
+    bitSet.set(0);
+    assertToIterBitSet("0", bitSet);
+    bitSet.set(1);
+    assertToIterBitSet("0, 1", bitSet);
+    bitSet.clear();
+    bitSet.set(10);
+    assertToIterBitSet("10", bitSet);
+  }
+
+  /**
+   * Tests that iterating over a BitSet yields the expected string.
+   *
+   * @param expected Expected string
+   * @param bitSet   Bit set
+   */
+  private void assertToIterBitSet(final String expected, BitSet bitSet) {
+    StringBuilder buf = new StringBuilder();
+    for (int i : BitSets.toIter(bitSet)) {
+      if (buf.length() > 0) {
+        buf.append(", ");
+      }
+      buf.append(Integer.toString(i));
+    }
+    assertEquals(expected, buf.toString());
+  }
+
+  /**
+   * Tests the method
+   * {@link net.hydromatic.optiq.util.BitSets#toList(java.util.BitSet)}.
+   */
+  @Test public void testToListBitSet() {
+    BitSet bitSet = new BitSet(10);
+    assertEquals(BitSets.toList(bitSet), Collections.<Integer>emptyList());
+    bitSet.set(5);
+    assertEquals(BitSets.toList(bitSet), Arrays.asList(5));
+    bitSet.set(3);
+    assertEquals(BitSets.toList(bitSet), Arrays.asList(3, 5));
+  }
+
+  /**
+   * Tests the method {@link net.hydromatic.optiq.util.BitSets#of(int...)}.
+   */
+  @Test public void testBitSetOf() {
+    assertEquals(
+        BitSets.toList(BitSets.of(0, 4, 2)),
+        Arrays.asList(0, 2, 4));
+    assertEquals(
+        BitSets.toList(BitSets.of()),
+        Collections.<Integer>emptyList());
+  }
+
+  /**
+   * Tests the method {@link net.hydromatic.optiq.util.BitSets#range(int, int)}.
+   */
+  @Test public void testBitSetsRange() {
+    assertEquals(
+        BitSets.toList(BitSets.range(0, 4)),
+        Arrays.asList(0, 1, 2, 3));
+    assertEquals(
+        BitSets.toList(BitSets.range(1, 4)),
+        Arrays.asList(1, 2, 3));
+    assertEquals(
+        BitSets.toList(BitSets.range(2, 2)),
+        Collections.<Integer>emptyList());
+  }
+
+  /**
+   * Tests the method
+   * {@link net.hydromatic.optiq.util.BitSets#toArray(java.util.BitSet)}.
+   */
+  @Test public void testBitSetsToArray() {
+    int[][] arrays = {{}, {0}, {0, 2}, {1, 65}, {100}};
+    for (int[] array : arrays) {
+      assertThat(BitSets.toArray(BitSets.of(array)), equalTo(array));
+    }
+  }
+
+  /**
+   * Tests the method
+   * {@link net.hydromatic.optiq.util.BitSets#union(java.util.BitSet, java.util.BitSet...)}.
+   */
+  @Test public void testBitSetsUnion() {
+    assertThat(BitSets.union(BitSets.of(1), BitSets.of(3)).toString(),
+        equalTo("{1, 3}"));
+    assertThat(BitSets.union(BitSets.of(1), BitSets.of(3, 100)).toString(),
+        equalTo("{1, 3, 100}"));
+    assertThat(
+        BitSets.union(BitSets.of(1), BitSets.of(2), BitSets.of(), BitSets.of(3))
+            .toString(),
+        equalTo("{1, 2, 3}"));
+  }
+
+  /**
+   * Tests the method
+   * {@link net.hydromatic.optiq.util.BitSets#contains(java.util.BitSet, java.util.BitSet)}.
+   */
+  @Test public void testBitSetsContains() {
+    assertTrue(BitSets.contains(BitSets.range(0, 5), BitSets.range(2, 4)));
+    assertTrue(BitSets.contains(BitSets.range(0, 5), BitSets.of(4)));
+    assertFalse(BitSets.contains(BitSets.range(0, 5), BitSets.of(14)));
+    assertFalse(BitSets.contains(BitSets.range(20, 25), BitSets.of(14)));
+    final BitSet empty = BitSets.of();
+    assertTrue(BitSets.contains(BitSets.range(20, 25), empty));
+    assertTrue(BitSets.contains(empty, empty));
+    assertFalse(BitSets.contains(empty, BitSets.of(0)));
+    assertFalse(BitSets.contains(empty, BitSets.of(1)));
+    assertFalse(BitSets.contains(empty, BitSets.of(1000)));
+    assertTrue(BitSets.contains(BitSets.of(1, 4, 7), BitSets.of(1, 4, 7)));
+  }
+
+  /**
+   * Tests the method
+   * {@link net.hydromatic.optiq.util.BitSets#of(org.eigenbase.util.ImmutableIntList)}.
+   */
+  @Test public void testBitSetOfImmutableIntList() {
+    ImmutableIntList list = ImmutableIntList.of();
+    assertThat(BitSets.of(list), equalTo(new BitSet()));
+
+    list = ImmutableIntList.of(2, 70, 5, 0);
+    assertThat(BitSets.of(list), equalTo(BitSets.of(0, 2, 5, 70)));
+  }
+}
+
+// End BitSetsTest.java
+

http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/4a46cdb5/core/src/test/java/org/eigenbase/util/UtilTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/eigenbase/util/UtilTest.java b/core/src/test/java/org/eigenbase/util/UtilTest.java
index bb2bdca..8df1a1f 100644
--- a/core/src/test/java/org/eigenbase/util/UtilTest.java
+++ b/core/src/test/java/org/eigenbase/util/UtilTest.java
@@ -33,7 +33,6 @@ import net.hydromatic.linq4j.function.Function1;
 
 import net.hydromatic.optiq.runtime.FlatLists;
 import net.hydromatic.optiq.runtime.Spaces;
-import net.hydromatic.optiq.util.BitSets;
 import net.hydromatic.optiq.util.CompositeMap;
 
 import com.google.common.collect.ImmutableList;
@@ -487,79 +486,6 @@ public class UtilTest {
   }
 
   /**
-   * Tests the method {@link net.hydromatic.optiq.util.BitSets#toIter(java.util.BitSet)}.
-   */
-  @Test public void testToIterBitSet() {
-    BitSet bitSet = new BitSet();
-
-    assertToIterBitSet("", bitSet);
-    bitSet.set(0);
-    assertToIterBitSet("0", bitSet);
-    bitSet.set(1);
-    assertToIterBitSet("0, 1", bitSet);
-    bitSet.clear();
-    bitSet.set(10);
-    assertToIterBitSet("10", bitSet);
-  }
-
-  /**
-   * Tests that iterating over a BitSet yields the expected string.
-   *
-   * @param expected Expected string
-   * @param bitSet   Bit set
-   */
-  private void assertToIterBitSet(
-      final String expected, BitSet bitSet) {
-    StringBuilder buf = new StringBuilder();
-    for (int i : BitSets.toIter(bitSet)) {
-      if (buf.length() > 0) {
-        buf.append(", ");
-      }
-      buf.append(Integer.toString(i));
-    }
-    assertEquals(expected, buf.toString());
-  }
-
-  /**
-   * Tests the method {@link net.hydromatic.optiq.util.BitSets#toList(java.util.BitSet)}.
-   */
-  @Test public void testToListBitSet() {
-    BitSet bitSet = new BitSet(10);
-    assertEquals(BitSets.toList(bitSet), Collections.<Integer>emptyList());
-    bitSet.set(5);
-    assertEquals(BitSets.toList(bitSet), Arrays.asList(5));
-    bitSet.set(3);
-    assertEquals(BitSets.toList(bitSet), Arrays.asList(3, 5));
-  }
-
-  /**
-   * Tests the method {@link net.hydromatic.optiq.util.BitSets#of(int...)}.
-   */
-  @Test public void testBitSetOf() {
-    assertEquals(
-        BitSets.toList(BitSets.of(0, 4, 2)),
-        Arrays.asList(0, 2, 4));
-    assertEquals(
-        BitSets.toList(BitSets.of()),
-        Collections.<Integer>emptyList());
-  }
-
-  /**
-   * Tests the method {@link net.hydromatic.optiq.util.BitSets#range(int, int)}.
-   */
-  @Test public void testBitSetBetween() {
-    assertEquals(
-        BitSets.toList(BitSets.range(0, 4)),
-        Arrays.asList(0, 1, 2, 3));
-    assertEquals(
-        BitSets.toList(BitSets.range(1, 4)),
-        Arrays.asList(1, 2, 3));
-    assertEquals(
-        BitSets.toList(BitSets.range(2, 2)),
-        Collections.<Integer>emptyList());
-  }
-
-  /**
    * Tests SQL builders.
    */
   @Test public void testSqlBuilder() {
@@ -921,6 +847,7 @@ public class UtilTest {
     ImmutableIntList list = ImmutableIntList.of();
     assertEquals(0, list.size());
     assertEquals(list, Collections.<Integer>emptyList());
+    assertThat(list.toString(), equalTo("[]"));
 
     list = ImmutableIntList.of(1, 3, 5);
     assertEquals(3, list.size());
@@ -934,21 +861,6 @@ public class UtilTest {
   }
 
   /**
-   * Unit test for
-   * {@link net.hydromatic.optiq.util.BitSets#contains(java.util.BitSet, java.util.BitSet)}.
-   */
-  @Test public void testContains() {
-    assertTrue(BitSets.contains(BitSets.range(0, 5), BitSets.range(2, 4)));
-    assertTrue(BitSets.contains(BitSets.range(0, 5), BitSets.of(4)));
-    assertFalse(BitSets.contains(BitSets.range(0, 5), BitSets.of(14)));
-    assertFalse(BitSets.contains(BitSets.range(20, 25), BitSets.of(14)));
-    final BitSet empty = BitSets.of();
-    assertTrue(BitSets.contains(BitSets.range(20, 25), empty));
-    assertTrue(BitSets.contains(empty, empty));
-    assertTrue(BitSets.contains(BitSets.of(1, 4, 7), BitSets.of(1, 4, 7)));
-  }
-
-  /**
    * Unit test for {@link IntegerIntervalSet}.
    */
   @Test public void testIntegerIntervalSet() {


[4/7] git commit: Throttle down PartiallyOrderedSetTest.

Posted by jh...@apache.org.
Throttle down PartiallyOrderedSetTest.


Project: http://git-wip-us.apache.org/repos/asf/incubator-optiq/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-optiq/commit/a4615398
Tree: http://git-wip-us.apache.org/repos/asf/incubator-optiq/tree/a4615398
Diff: http://git-wip-us.apache.org/repos/asf/incubator-optiq/diff/a4615398

Branch: refs/heads/master
Commit: a46153985300404213343e31486444695253f702
Parents: 4a46cdb
Author: Julian Hyde <jh...@apache.org>
Authored: Wed Jul 23 00:51:47 2014 -0700
Committer: Julian Hyde <jh...@apache.org>
Committed: Mon Jul 28 11:12:52 2014 -0700

----------------------------------------------------------------------
 .../net/hydromatic/optiq/util/PartiallyOrderedSetTest.java  | 9 ++++++++-
 1 file changed, 8 insertions(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/a4615398/core/src/test/java/net/hydromatic/optiq/util/PartiallyOrderedSetTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/net/hydromatic/optiq/util/PartiallyOrderedSetTest.java b/core/src/test/java/net/hydromatic/optiq/util/PartiallyOrderedSetTest.java
index ec18ae4..4727a4d 100644
--- a/core/src/test/java/net/hydromatic/optiq/util/PartiallyOrderedSetTest.java
+++ b/core/src/test/java/net/hydromatic/optiq/util/PartiallyOrderedSetTest.java
@@ -17,6 +17,8 @@
 */
 package net.hydromatic.optiq.util;
 
+import net.hydromatic.optiq.test.OptiqAssert;
+
 import org.eigenbase.util.TestUtil;
 
 import org.junit.Test;
@@ -30,7 +32,9 @@ import static org.junit.Assert.*;
  */
 public class PartiallyOrderedSetTest {
   private static final boolean DEBUG = false;
-  private static final int SCALE = 100; // 100, 250, 1000, 3000 are reasonable
+
+  // 100, 250, 1000, 3000 are reasonable
+  private static final int SCALE = OptiqAssert.ENABLE_SLOW ? 250 : 50;
 
   final long seed = new Random().nextLong();
   final Random random = new Random(seed);
@@ -215,6 +219,9 @@ public class PartiallyOrderedSetTest {
   }
 
   @Test public void testDivisorPoset() {
+    if (!OptiqAssert.ENABLE_SLOW) {
+      return;
+    }
     PartiallyOrderedSet<Integer> integers =
         new PartiallyOrderedSet<Integer>(IS_DIVISOR, range(1, 1000));
     assertEquals(


[6/7] git commit: [OPTIQ-349] Add heuristic join-optimizer that can generate bushy joins

Posted by jh...@apache.org.
[OPTIQ-349] Add heuristic join-optimizer that can generate bushy joins

Add Util.human(), to format numbers like 'du -h' does.

Try harder to estimate row-count from abstract RelNodes.

Implement Metadata.toString() in reflective metadata provider.

Add row-count estimates to TPC-DS schema, and run heuristic bushy join algorithm on TPC-DS query 17.


Project: http://git-wip-us.apache.org/repos/asf/incubator-optiq/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-optiq/commit/b0aff0df
Tree: http://git-wip-us.apache.org/repos/asf/incubator-optiq/tree/b0aff0df
Diff: http://git-wip-us.apache.org/repos/asf/incubator-optiq/diff/b0aff0df

Branch: refs/heads/master
Commit: b0aff0df60b4c1f2a3ba70a4e17b86142103b93f
Parents: 1b12738
Author: Julian Hyde <jh...@apache.org>
Authored: Tue Jul 22 11:33:08 2014 -0700
Committer: Julian Hyde <jh...@apache.org>
Committed: Mon Jul 28 11:16:19 2014 -0700

----------------------------------------------------------------------
 .../net/hydromatic/optiq/prepare/Prepare.java   |  81 ++---
 .../java/net/hydromatic/optiq/runtime/Hook.java |   3 +
 .../net/hydromatic/optiq/tools/Programs.java    |  89 ++++-
 .../java/net/hydromatic/optiq/util/BitSets.java |  25 +-
 .../metadata/ReflectiveRelMetadataProvider.java |   9 +-
 .../org/eigenbase/rel/rules/LoptJoinTree.java   |   7 +
 .../org/eigenbase/rel/rules/LoptMultiJoin.java  | 151 +++++----
 .../rel/rules/LoptOptimizeJoinRule.java         |  40 +--
 .../rel/rules/OptimizeBushyJoinRule.java        | 328 ++++++++++++++++++-
 .../org/eigenbase/rel/rules/SemiJoinRel.java    |   4 +-
 .../org/eigenbase/relopt/volcano/RelSubset.java |   6 +-
 .../eigenbase/sql2rel/SqlToRelConverter.java    |  14 +-
 .../org/eigenbase/util/ImmutableIntList.java    |  32 +-
 core/src/main/java/org/eigenbase/util/Util.java |  47 +++
 .../org/eigenbase/util/mapping/Mappings.java    |  91 ++++-
 .../net/hydromatic/optiq/tools/PlannerTest.java | 126 ++++---
 .../test/java/org/eigenbase/util/UtilTest.java  |  43 +++
 .../optiq/impl/tpcds/TpcdsSchema.java           |  42 ++-
 .../hydromatic/optiq/impl/tpcds/TpcdsTest.java  |  51 ++-
 19 files changed, 941 insertions(+), 248 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/b0aff0df/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 c2d5dba..0fb3f14 100644
--- a/core/src/main/java/net/hydromatic/optiq/prepare/Prepare.java
+++ b/core/src/main/java/net/hydromatic/optiq/prepare/Prepare.java
@@ -21,7 +21,6 @@ import net.hydromatic.optiq.DataContext;
 import net.hydromatic.optiq.impl.StarTable;
 import net.hydromatic.optiq.jdbc.OptiqPrepare;
 import net.hydromatic.optiq.jdbc.OptiqSchema;
-import net.hydromatic.optiq.rules.java.JavaRules;
 import net.hydromatic.optiq.runtime.Bindable;
 import net.hydromatic.optiq.runtime.Hook;
 import net.hydromatic.optiq.runtime.Typed;
@@ -29,8 +28,6 @@ import net.hydromatic.optiq.tools.Program;
 import net.hydromatic.optiq.tools.Programs;
 
 import org.eigenbase.rel.*;
-import org.eigenbase.rel.metadata.*;
-import org.eigenbase.rel.rules.*;
 import org.eigenbase.relopt.*;
 import org.eigenbase.reltype.RelDataType;
 import org.eigenbase.rex.RexBuilder;
@@ -40,6 +37,7 @@ import org.eigenbase.sql.validate.*;
 import org.eigenbase.sql2rel.SqlToRelConverter;
 import org.eigenbase.trace.EigenbaseTimingTracer;
 import org.eigenbase.trace.EigenbaseTrace;
+import org.eigenbase.util.Pair;
 
 import com.google.common.collect.ImmutableList;
 
@@ -55,24 +53,6 @@ import java.util.logging.Logger;
 public abstract class Prepare {
   protected static final Logger LOGGER = EigenbaseTrace.getStatementTracer();
 
-  private static final ImmutableList<RelOptRule> CALC_RULES =
-      ImmutableList.of(
-          JavaRules.ENUMERABLE_CALC_RULE,
-          JavaRules.ENUMERABLE_FILTER_TO_CALC_RULE,
-          JavaRules.ENUMERABLE_PROJECT_TO_CALC_RULE,
-          MergeCalcRule.INSTANCE,
-          MergeFilterOntoCalcRule.INSTANCE,
-          MergeProjectOntoCalcRule.INSTANCE,
-          FilterToCalcRule.INSTANCE,
-          ProjectToCalcRule.INSTANCE,
-          MergeCalcRule.INSTANCE,
-
-          // REVIEW jvs 9-Apr-2006: Do we still need these two?  Doesn't the
-          // combination of MergeCalcRule, FilterToCalcRule, and
-          // ProjectToCalcRule have the same effect?
-          MergeFilterOntoCalcRule.INSTANCE,
-          MergeProjectOntoCalcRule.INSTANCE);
-
   protected final OptiqPrepare.Context context;
   protected final CatalogReader catalogReader;
   protected String queryString = null;
@@ -124,39 +104,19 @@ public abstract class Prepare {
     planner.setRoot(rootRel);
 
     final RelTraitSet desiredTraits = getDesiredRootTraitSet(rootRel);
-    final Program program1 =
-        new Program() {
-          public RelNode run(RelOptPlanner planner, RelNode rel,
-              RelTraitSet requiredOutputTraits) {
-            final DataContext dataContext = context.getDataContext();
-            planner.setExecutor(new RexExecutorImpl(dataContext));
-
-            for (Materialization materialization : materializations) {
-              planner.addMaterialization(
-                  new RelOptMaterialization(materialization.tableRel,
-                      materialization.queryRel,
-                      materialization.starRelOptTable));
-            }
-
-            final RelNode rootRel2 =
-                planner.changeTraits(rel, requiredOutputTraits);
-            assert rootRel2 != null;
-
-            planner.setRoot(rootRel2);
-            final RelOptPlanner planner2 = planner.chooseDelegate();
-            final RelNode rootRel3 = planner2.findBestExp();
-            assert rootRel3 != null : "could not implement exp";
-            return rootRel3;
-          }
-        };
-
-    final RelNode rootRel3 = program1.run(planner, rootRel, desiredTraits);
-
-    // Second planner pass to do physical "tweaks". This the first time that
-    // EnumerableCalcRel is introduced.
-    final Program program2 =
-        Programs.hep(CALC_RULES, true, new DefaultRelMetadataProvider());
-    final RelNode rootRel4 = program2.run(null, rootRel3, null);
+    final Program program = getProgram();
+
+    final DataContext dataContext = context.getDataContext();
+    planner.setExecutor(new RexExecutorImpl(dataContext));
+
+    for (Materialization materialization : materializations) {
+      planner.addMaterialization(
+          new RelOptMaterialization(materialization.tableRel,
+              materialization.queryRel,
+              materialization.starRelOptTable));
+    }
+
+    final RelNode rootRel4 = program.run(planner, rootRel, desiredTraits);
     if (LOGGER.isLoggable(Level.FINE)) {
       LOGGER.fine(
           "Plan after physical tweaks: "
@@ -166,6 +126,19 @@ public abstract class Prepare {
     return rootRel4;
   }
 
+  private Program getProgram() {
+    // Allow a test to override the planner.
+    final List<Materialization> materializations = ImmutableList.of();
+    final Pair<List<Materialization>, Program[]> pair =
+        Pair.of(materializations, new Program[1]);
+    Hook.PROGRAM.run(pair);
+    if (pair.right[0] != null) {
+      return pair.right[0];
+    }
+
+    return Programs.standard();
+  }
+
   protected RelTraitSet getDesiredRootTraitSet(RelNode rootRel) {
     // Make sure non-CallingConvention traits, if any, are preserved
     return rootRel.getTraitSet()

http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/b0aff0df/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 442e4b5..653ad23 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 {
   /** Called when a constant expression is being reduced. */
   EXPRESSION_REDUCER,
 
+  /** Called to create a Program to optimize the statement. */
+  PROGRAM,
+
   /** Called with a query that has been generated to send to a back-end system.
    * The query might be a SQL string (for the JDBC adapter), a list of Mongo
    * pipeline expressions (for the MongoDB adapter), et cetera. */

http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/b0aff0df/core/src/main/java/net/hydromatic/optiq/tools/Programs.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/net/hydromatic/optiq/tools/Programs.java b/core/src/main/java/net/hydromatic/optiq/tools/Programs.java
index 83c73cd..3e97c2f 100644
--- a/core/src/main/java/net/hydromatic/optiq/tools/Programs.java
+++ b/core/src/main/java/net/hydromatic/optiq/tools/Programs.java
@@ -17,8 +17,12 @@
 */
 package net.hydromatic.optiq.tools;
 
+import net.hydromatic.optiq.prepare.OptiqPrepareImpl;
+import net.hydromatic.optiq.rules.java.JavaRules;
+
 import org.eigenbase.rel.RelNode;
 import org.eigenbase.rel.metadata.ChainedRelMetadataProvider;
+import org.eigenbase.rel.metadata.DefaultRelMetadataProvider;
 import org.eigenbase.rel.metadata.RelMetadataProvider;
 import org.eigenbase.rel.rules.*;
 import org.eigenbase.relopt.*;
@@ -26,6 +30,7 @@ import org.eigenbase.relopt.hep.*;
 
 import com.google.common.base.Function;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Lists;
 
 import java.util.*;
@@ -41,6 +46,53 @@ public class Programs {
         }
       };
 
+  public static final ImmutableList<RelOptRule> CALC_RULES =
+      ImmutableList.of(
+          JavaRules.ENUMERABLE_CALC_RULE,
+          JavaRules.ENUMERABLE_FILTER_TO_CALC_RULE,
+          JavaRules.ENUMERABLE_PROJECT_TO_CALC_RULE,
+          MergeCalcRule.INSTANCE,
+          MergeFilterOntoCalcRule.INSTANCE,
+          MergeProjectOntoCalcRule.INSTANCE,
+          FilterToCalcRule.INSTANCE,
+          ProjectToCalcRule.INSTANCE,
+          MergeCalcRule.INSTANCE,
+
+          // REVIEW jvs 9-Apr-2006: Do we still need these two?  Doesn't the
+          // combination of MergeCalcRule, FilterToCalcRule, and
+          // ProjectToCalcRule have the same effect?
+          MergeFilterOntoCalcRule.INSTANCE,
+          MergeProjectOntoCalcRule.INSTANCE);
+
+  public static final ImmutableSet<RelOptRule> RULE_SET =
+      ImmutableSet.of(
+          JavaRules.ENUMERABLE_JOIN_RULE,
+          JavaRules.ENUMERABLE_PROJECT_RULE,
+          JavaRules.ENUMERABLE_FILTER_RULE,
+          JavaRules.ENUMERABLE_AGGREGATE_RULE,
+          JavaRules.ENUMERABLE_SORT_RULE,
+          JavaRules.ENUMERABLE_LIMIT_RULE,
+          JavaRules.ENUMERABLE_UNION_RULE,
+          JavaRules.ENUMERABLE_INTERSECT_RULE,
+          JavaRules.ENUMERABLE_MINUS_RULE,
+          JavaRules.ENUMERABLE_TABLE_MODIFICATION_RULE,
+          JavaRules.ENUMERABLE_VALUES_RULE,
+          JavaRules.ENUMERABLE_WINDOW_RULE,
+          JavaRules.ENUMERABLE_ONE_ROW_RULE,
+          JavaRules.ENUMERABLE_EMPTY_RULE,
+          TableAccessRule.INSTANCE,
+          OptiqPrepareImpl.COMMUTE
+              ? CommutativeJoinRule.INSTANCE
+              : MergeProjectRule.INSTANCE,
+          PushFilterPastProjectRule.INSTANCE,
+          PushFilterPastJoinRule.FILTER_ON_JOIN,
+          RemoveDistinctAggregateRule.INSTANCE,
+          ReduceAggregatesRule.INSTANCE,
+          SwapJoinRule.INSTANCE,
+          PushJoinThroughJoinRule.RIGHT,
+          PushJoinThroughJoinRule.LEFT,
+          PushSortPastProjectRule.INSTANCE);
+
   // private constructor for utility class
   private Programs() {}
 
@@ -120,7 +172,7 @@ public class Programs {
           RelTraitSet requiredOutputTraits) {
         final int joinCount = RelOptUtil.countJoins(rel);
         final Program program;
-        if (joinCount < 6) {
+        if (joinCount < (bushy ? 2 : 6)) {
           program = ofRules(rules);
         } else {
           // Create a program that gathers together joins as a MultiJoinRel.
@@ -153,6 +205,41 @@ public class Programs {
     };
   }
 
+  public static Program getProgram() {
+    return new Program() {
+      public RelNode run(RelOptPlanner planner, RelNode rel,
+          RelTraitSet requiredOutputTraits) {
+        return null;
+      }
+    };
+  }
+
+  /** Returns the standard program used by Prepare. */
+  public static Program standard() {
+    final Program program1 =
+        new Program() {
+          public RelNode run(RelOptPlanner planner, RelNode rel,
+              RelTraitSet requiredOutputTraits) {
+            final RelNode rootRel2 =
+                planner.changeTraits(rel, requiredOutputTraits);
+            assert rootRel2 != null;
+
+            planner.setRoot(rootRel2);
+            final RelOptPlanner planner2 = planner.chooseDelegate();
+            final RelNode rootRel3 = planner2.findBestExp();
+            assert rootRel3 != null : "could not implement exp";
+            return rootRel3;
+          }
+        };
+
+    // Second planner pass to do physical "tweaks". This the first time that
+    // EnumerableCalcRel is introduced.
+    final Program program2 =
+        hep(CALC_RULES, true, new DefaultRelMetadataProvider());
+
+    return sequence(program1, program2);
+  }
+
   /** Program backed by a {@link RuleSet}. */
   static class RuleSetProgram implements Program {
     final RuleSet ruleSet;

http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/b0aff0df/core/src/main/java/net/hydromatic/optiq/util/BitSets.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/net/hydromatic/optiq/util/BitSets.java b/core/src/main/java/net/hydromatic/optiq/util/BitSets.java
index f0dbd4f..78394db 100644
--- a/core/src/main/java/net/hydromatic/optiq/util/BitSets.java
+++ b/core/src/main/java/net/hydromatic/optiq/util/BitSets.java
@@ -17,6 +17,7 @@
 */
 package net.hydromatic.optiq.util;
 
+import org.eigenbase.util.ImmutableIntList;
 import org.eigenbase.util.IntList;
 
 import java.util.*;
@@ -136,7 +137,7 @@ public final class BitSets {
   }
 
   /**
-   * Creates a bitset with given bits set.
+   * Creates a BitSet with given bits set.
    *
    * <p>For example, {@code of(new Integer[] {0, 3})} returns a bit set
    * with bits {0, 3} set.
@@ -153,7 +154,7 @@ public final class BitSets {
   }
 
   /**
-   * Creates a bitset with given bits set.
+   * Creates a BitSet with given bits set.
    *
    * <p>For example, {@code of(Arrays.asList(0, 3)) } returns a bit set
    * with bits {0, 3} set.
@@ -161,7 +162,7 @@ public final class BitSets {
    * @param bits Collection of bits to set
    * @return Bit set
    */
-  public static BitSet of(Collection<? extends Number> bits) {
+  public static BitSet of(Iterable<? extends Number> bits) {
     final BitSet bitSet = new BitSet();
     for (Number bit : bits) {
       bitSet.set(bit.intValue());
@@ -170,6 +171,23 @@ public final class BitSets {
   }
 
   /**
+   * Creates a BitSet with given bits set.
+   *
+   * <p>For example, {@code of(ImmutableIntList.of(0, 3))} returns a bit set
+   * with bits {0, 3} set.
+   *
+   * @param bits Collection of bits to set
+   * @return Bit set
+   */
+  public static BitSet of(ImmutableIntList bits) {
+    final BitSet bitSet = new BitSet();
+    for (int i = 0, n = bits.size(); i < n; i++) {
+      bitSet.set(bits.getInt(i));
+    }
+    return bitSet;
+  }
+
+  /**
    * Creates a bitset with bits from {@code fromIndex} (inclusive) to
    * specified {@code toIndex} (exclusive) set to {@code true}.
    *
@@ -190,6 +208,7 @@ public final class BitSets {
     return bitSet;
   }
 
+  /** Creates a BitSet with bits between 0 and {@code toIndex} set. */
   public static BitSet range(int toIndex) {
     return range(0, toIndex);
   }

http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/b0aff0df/core/src/main/java/org/eigenbase/rel/metadata/ReflectiveRelMetadataProvider.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/eigenbase/rel/metadata/ReflectiveRelMetadataProvider.java b/core/src/main/java/org/eigenbase/rel/metadata/ReflectiveRelMetadataProvider.java
index a0c25c7..20e6ec2 100644
--- a/core/src/main/java/org/eigenbase/rel/metadata/ReflectiveRelMetadataProvider.java
+++ b/core/src/main/java/org/eigenbase/rel/metadata/ReflectiveRelMetadataProvider.java
@@ -120,10 +120,15 @@ public class ReflectiveRelMetadataProvider
                           //   Selectivity.selectivity(rex)
                           // by calling method
                           //   new SelectivityImpl().selectivity(filter, rex)
-                          if (method.equals(BuiltinMethod.METADATA_REL.method))
-                          {
+                          if (method.equals(
+                              BuiltinMethod.METADATA_REL.method)) {
                             return rel;
                           }
+                          if (method.equals(
+                              BuiltinMethod.OBJECT_TO_STRING.method)) {
+                            return metadataClass0.getSimpleName() + "(" + rel
+                                + ")";
+                          }
                           final Object[] args1;
                           if (args == null) {
                             args1 = new Object[]{rel};

http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/b0aff0df/core/src/main/java/org/eigenbase/rel/rules/LoptJoinTree.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/eigenbase/rel/rules/LoptJoinTree.java b/core/src/main/java/org/eigenbase/rel/rules/LoptJoinTree.java
index 0e32b0e..5f277a9 100644
--- a/core/src/main/java/org/eigenbase/rel/rules/LoptJoinTree.java
+++ b/core/src/main/java/org/eigenbase/rel/rules/LoptJoinTree.java
@@ -22,6 +22,7 @@ import java.util.*;
 import org.eigenbase.rel.*;
 
 import com.google.common.base.Preconditions;
+import com.google.common.collect.Lists;
 
 /**
  * Utility class used to store a {@link JoinRelBase} tree and the factors that
@@ -132,6 +133,12 @@ public class LoptJoinTree {
     return factorTree;
   }
 
+  public List<Integer> getTreeOrder() {
+    List<Integer> treeOrder = Lists.newArrayList();
+    getTreeOrder(treeOrder);
+    return treeOrder;
+  }
+
   public void getTreeOrder(List<Integer> treeOrder) {
     factorTree.getTreeOrder(treeOrder);
   }

http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/b0aff0df/core/src/main/java/org/eigenbase/rel/rules/LoptMultiJoin.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/eigenbase/rel/rules/LoptMultiJoin.java b/core/src/main/java/org/eigenbase/rel/rules/LoptMultiJoin.java
index 4a79da0..6579674 100644
--- a/core/src/main/java/org/eigenbase/rel/rules/LoptMultiJoin.java
+++ b/core/src/main/java/org/eigenbase/rel/rules/LoptMultiJoin.java
@@ -29,6 +29,9 @@ import org.eigenbase.util.IntList;
 
 import net.hydromatic.optiq.util.BitSets;
 
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+
 /**
  * Utility class that keeps track of the join factors that
  * make up a {@link MultiJoinRel}.
@@ -56,7 +59,7 @@ public class LoptMultiJoin {
   /**
    * Number of factors into the MultiJoinRel
    */
-  private int nJoinFactors;
+  private final int nJoinFactors;
 
   /**
    * Total number of fields in the MultiJoinRel
@@ -66,21 +69,21 @@ public class LoptMultiJoin {
   /**
    * Original inputs into the MultiJoinRel
    */
-  private List<RelNode> joinFactors;
+  private final ImmutableList<RelNode> joinFactors;
 
   /**
    * If a join factor is null generating in a left or right outer join,
    * joinTypes indicates the join type corresponding to the factor. Otherwise,
    * it is set to INNER.
    */
-  private List<JoinRelType> joinTypes;
+  private final ImmutableList<JoinRelType> joinTypes;
 
   /**
    * If a join factor is null generating in a left or right outer join, the
    * bitmap contains the non-null generating factors that the null generating
    * factor is dependent upon
    */
-  private BitSet [] outerJoinFactors;
+  private final BitSet [] outerJoinFactors;
 
   /**
    * Bitmap corresponding to the fields projected from each join factor, after
@@ -134,7 +137,7 @@ public class LoptMultiJoin {
   /**
    * Type factory
    */
-  RelDataTypeFactory factory;
+  final RelDataTypeFactory factory;
 
   /**
    * Indicates for each factor whether its join can be removed because it is
@@ -168,22 +171,18 @@ public class LoptMultiJoin {
 
   public LoptMultiJoin(MultiJoinRel multiJoin) {
     this.multiJoin = multiJoin;
-    joinFactors = multiJoin.getInputs();
+    joinFactors = ImmutableList.copyOf(multiJoin.getInputs());
     nJoinFactors = joinFactors.size();
     projFields = multiJoin.getProjFields();
     joinFieldRefCountsMap = multiJoin.getCopyJoinFieldRefCountsMap();
 
-    joinFilters = new ArrayList<RexNode>();
-    RelOptUtil.decomposeConjunction(
-        multiJoin.getJoinFilter(),
-        joinFilters);
+    joinFilters =
+        Lists.newArrayList(RelOptUtil.conjunctions(multiJoin.getJoinFilter()));
 
     allJoinFilters = new ArrayList<RexNode>(joinFilters);
     List<RexNode> outerJoinFilters = multiJoin.getOuterJoinConditions();
     for (int i = 0; i < nJoinFactors; i++) {
-      List<RexNode> ojFilters = new ArrayList<RexNode>();
-      RelOptUtil.decomposeConjunction(outerJoinFilters.get(i), ojFilters);
-      allJoinFilters.addAll(ojFilters);
+      allJoinFilters.addAll(RelOptUtil.conjunctions(outerJoinFilters.get(i)));
     }
 
     int start = 0;
@@ -197,7 +196,23 @@ public class LoptMultiJoin {
       start += nFieldsInJoinFactor[i];
     }
 
-    setOuterJoinInfo();
+    // Extract outer join information from the join factors, including the type
+    // of outer join and the factors that a null-generating factor is dependent
+    // upon.
+    joinTypes = ImmutableList.copyOf(multiJoin.getJoinTypes());
+    List<RexNode> outerJoinConds = this.multiJoin.getOuterJoinConditions();
+    outerJoinFactors = new BitSet[nJoinFactors];
+    for (int i = 0; i < nJoinFactors; i++) {
+      if (outerJoinConds.get(i) != null) {
+        // set a bitmap containing the factors referenced in the
+        // ON condition of the outer join; mask off the factor
+        // corresponding to the factor itself
+        BitSet dependentFactors =
+            getJoinFilterFactorBitmap(outerJoinConds.get(i), false);
+        dependentFactors.clear(i);
+        outerJoinFactors[i] = dependentFactors;
+      }
+    }
 
     // determine which join factors each join filter references
     setJoinFilterRefs();
@@ -404,28 +419,6 @@ public class LoptMultiJoin {
   }
 
   /**
-   * Extracts outer join information from the join factors, including the type
-   * of outer join and the factors that a null-generating factor is dependent
-   * upon.
-   */
-  private void setOuterJoinInfo() {
-    joinTypes = multiJoin.getJoinTypes();
-    List<RexNode> outerJoinConds = multiJoin.getOuterJoinConditions();
-    outerJoinFactors = new BitSet[nJoinFactors];
-    for (int i = 0; i < nJoinFactors; i++) {
-      if (outerJoinConds.get(i) != null) {
-        // set a bitmap containing the factors referenced in the
-        // ON condition of the outer join; mask off the factor
-        // corresponding to the factor itself
-        BitSet dependentFactors =
-            getJoinFilterFactorBitmap(outerJoinConds.get(i), false);
-        dependentFactors.clear(i);
-        outerJoinFactors[i] = dependentFactors;
-      }
-    }
-  }
-
-  /**
    * Returns a bitmap representing the factors referenced in a join filter
    *
    * @param joinFilter the join filter
@@ -434,18 +427,21 @@ public class LoptMultiJoin {
    *
    * @return the bitmap containing the factor references
    */
-  private BitSet getJoinFilterFactorBitmap(
+  BitSet getJoinFilterFactorBitmap(
       RexNode joinFilter,
       boolean setFields) {
-    BitSet fieldRefBitmap = new BitSet(nTotalFields);
-    joinFilter.accept(new RelOptUtil.InputFinder(fieldRefBitmap));
+    BitSet fieldRefBitmap = fieldBitmap(joinFilter);
     if (setFields) {
       fieldsRefByJoinFilter.put(joinFilter, fieldRefBitmap);
     }
 
-    BitSet factorRefBitmap = new BitSet(nJoinFactors);
-    setFactorBitmap(factorRefBitmap, fieldRefBitmap);
-    return factorRefBitmap;
+    return factorBitmap(fieldRefBitmap);
+  }
+
+  private BitSet fieldBitmap(RexNode joinFilter) {
+    BitSet fieldRefBitmap = new BitSet(nTotalFields);
+    joinFilter.accept(new RelOptUtil.InputFinder(fieldRefBitmap));
+    return fieldRefBitmap;
   }
 
   /**
@@ -474,17 +470,17 @@ public class LoptMultiJoin {
    * Sets the bitmap indicating which factors a filter references based on
    * which fields it references
    *
-   * @param factorRefBitmap bitmap representing factors referenced that will
-   * be set by this method
    * @param fieldRefBitmap bitmap representing fields referenced
+   * @return bitmap representing factors referenced that will
+   * be set by this method
    */
-  private void setFactorBitmap(
-      BitSet factorRefBitmap,
-      BitSet fieldRefBitmap) {
+  private BitSet factorBitmap(BitSet fieldRefBitmap) {
+    BitSet factorRefBitmap = new BitSet(nJoinFactors);
     for (int field : BitSets.toIter(fieldRefBitmap)) {
       int factor = findRef(field);
       factorRefBitmap.set(factor);
     }
+    return factorRefBitmap;
   }
 
   /**
@@ -540,11 +536,9 @@ public class LoptMultiJoin {
         int leftFactor = factorRefs.nextSetBit(0);
         int rightFactor = factorRefs.nextSetBit(leftFactor + 1);
 
-        BitSet leftFields = new BitSet(nTotalFields);
-        List<RexNode> operands = ((RexCall) joinFilter).getOperands();
-        operands.get(0).accept(new RelOptUtil.InputFinder(leftFields));
-        BitSet leftBitmap = new BitSet(nJoinFactors);
-        setFactorBitmap(leftBitmap, leftFields);
+        final RexCall call = (RexCall) joinFilter;
+        BitSet leftFields = fieldBitmap(call.getOperands().get(0));
+        BitSet leftBitmap = factorBitmap(leftFields);
 
         // filter contains only two factor references, one on each
         // side of the operator
@@ -604,29 +598,7 @@ public class LoptMultiJoin {
   public boolean hasAllFactors(
       LoptJoinTree joinTree,
       BitSet factorsNeeded) {
-    BitSet childFactors = new BitSet(nJoinFactors);
-    getChildFactors(joinTree, childFactors);
-    return BitSets.contains(childFactors, factorsNeeded);
-  }
-
-  /**
-   * Sets a bitmap representing all fields corresponding to a RelNode
-   *
-   * @param rel Relational expression for which fields will be set
-   * @param fields bitmap containing set bits for each field in a RelNode
-   */
-  public void setFieldBitmap(LoptJoinTree rel, BitSet fields) {
-    // iterate through all factors within the RelNode
-    BitSet factors = new BitSet(nJoinFactors);
-    getChildFactors(rel, factors);
-    for (int factor = factors.nextSetBit(0);
-        factor >= 0;
-        factor = factors.nextSetBit(factor + 1)) {
-      // set a bit for each field
-      for (int i = 0; i < nFieldsInJoinFactor[factor]; i++) {
-        fields.set(joinStart[factor] + i);
-      }
-    }
+    return BitSets.contains(BitSets.of(joinTree.getTreeOrder()), factorsNeeded);
   }
 
   /**
@@ -636,9 +608,7 @@ public class LoptMultiJoin {
    * @param childFactors bitmap to be set
    */
   public void getChildFactors(LoptJoinTree joinTree, BitSet childFactors) {
-    List<Integer> children = new ArrayList<Integer>();
-    joinTree.getTreeOrder(children);
-    for (int child : children) {
+    for (int child : joinTree.getTreeOrder()) {
       childFactors.set(child);
     }
   }
@@ -809,6 +779,31 @@ public class LoptMultiJoin {
     return selfJoin.getColumnMapping().get(rightOffset);
   }
 
+  public Edge createEdge(RexNode condition) {
+    BitSet fieldRefBitmap = fieldBitmap(condition);
+    BitSet factorRefBitmap = factorBitmap(fieldRefBitmap);
+    return new Edge(condition, factorRefBitmap, fieldRefBitmap);
+  }
+
+  /** Information about a join-condition. */
+  static class Edge {
+    final BitSet factors;
+    final BitSet columns;
+    final RexNode condition;
+
+    Edge(RexNode condition, BitSet factors, BitSet columns) {
+      this.condition = condition;
+      this.factors = factors;
+      this.columns = columns;
+    }
+
+    @Override public String toString() {
+      return "Edge(condition: " + condition
+          + ", factors: " + factors
+          + ", columns: " + columns + ")";
+    }
+  }
+
   //~ Inner Classes ----------------------------------------------------------
 
   /**

http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/b0aff0df/core/src/main/java/org/eigenbase/rel/rules/LoptOptimizeJoinRule.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/eigenbase/rel/rules/LoptOptimizeJoinRule.java b/core/src/main/java/org/eigenbase/rel/rules/LoptOptimizeJoinRule.java
index e758ee1..6e13a25 100644
--- a/core/src/main/java/org/eigenbase/rel/rules/LoptOptimizeJoinRule.java
+++ b/core/src/main/java/org/eigenbase/rel/rules/LoptOptimizeJoinRule.java
@@ -25,6 +25,7 @@ import org.eigenbase.relopt.*;
 import org.eigenbase.reltype.*;
 import org.eigenbase.rex.*;
 import org.eigenbase.sql.fun.*;
+import org.eigenbase.util.ImmutableIntList;
 import org.eigenbase.util.Pair;
 import org.eigenbase.util.mapping.IntPair;
 
@@ -445,7 +446,7 @@ public class LoptOptimizeJoinRule extends RelOptRule {
    *
    * @param multiJoin join factors being optimized
    * @param joinTree selected join ordering
-   * @param fieldNames fieldnames corresponding to the projection expressions
+   * @param fieldNames field names corresponding to the projection expressions
    *
    * @return created projection
    */
@@ -459,8 +460,7 @@ public class LoptOptimizeJoinRule extends RelOptRule {
 
     // create a projection on top of the joins, matching the original
     // join order
-    List<Integer> newJoinOrder = new ArrayList<Integer>();
-    joinTree.getTreeOrder(newJoinOrder);
+    final List<Integer> newJoinOrder = joinTree.getTreeOrder();
     int nJoinFactors = multiJoin.getNumJoinFactors();
     List<RelDataTypeField> fields = multiJoin.getMultiJoinFields();
 
@@ -536,9 +536,7 @@ public class LoptOptimizeJoinRule extends RelOptRule {
       LoptJoinTree joinTree,
       List<RexNode> filters,
       int factor) {
-    int nJoinFactors = multiJoin.getNumJoinFactors();
-    BitSet childFactors = new BitSet(nJoinFactors);
-    multiJoin.getChildFactors(joinTree, childFactors);
+    BitSet childFactors = BitSets.of(joinTree.getTreeOrder());
     childFactors.set(factor);
 
     int factorStart = multiJoin.getJoinStart(factor);
@@ -558,12 +556,9 @@ public class LoptOptimizeJoinRule extends RelOptRule {
 
     // then loop through the outer join filters where the factor being
     // added is the null generating factor in the outer join
-    RexNode outerJoinCond = multiJoin.getOuterJoinCond(factor);
-    List<RexNode> outerJoinFilters = new ArrayList<RexNode>();
-    RelOptUtil.decomposeConjunction(outerJoinCond, outerJoinFilters);
     setFactorJoinKeys(
         multiJoin,
-        outerJoinFilters,
+        RelOptUtil.conjunctions(multiJoin.getOuterJoinCond(factor)),
         childFactors,
         factorStart,
         nFields,
@@ -845,7 +840,7 @@ public class LoptOptimizeJoinRule extends RelOptRule {
           joinTree,
           -1,
           factorToAdd,
-          new ArrayList<Integer>(),
+          ImmutableIntList.of(),
           null,
           filtersToAdd);
     }
@@ -1038,8 +1033,7 @@ public class LoptOptimizeJoinRule extends RelOptRule {
     // remember the original join order before the pushdown so we can
     // appropriately adjust any filters already attached to the join
     // node
-    List<Integer> origJoinOrder = new ArrayList<Integer>();
-    joinTree.getTreeOrder(origJoinOrder);
+    final List<Integer> origJoinOrder = joinTree.getTreeOrder();
 
     // recursively pushdown the factor
     LoptJoinTree subTree = (childNo == 0) ? left : right;
@@ -1212,19 +1206,18 @@ public class LoptOptimizeJoinRule extends RelOptRule {
       boolean adjust) {
     // loop through the remaining filters to be added and pick out the
     // ones that reference only the factors in the new join tree
-    RexNode condition = null;
-    ListIterator<RexNode> filterIter = filtersToAdd.listIterator();
-    int nJoinFactors = multiJoin.getNumJoinFactors();
-    RexBuilder rexBuilder =
+    final RexBuilder rexBuilder =
         multiJoin.getMultiJoinRel().getCluster().getRexBuilder();
-    BitSet childFactors = new BitSet(nJoinFactors);
+    final BitSet childFactors = BitSets.of(rightTree.getTreeOrder());
     if (leftIdx >= 0) {
       childFactors.set(leftIdx);
     } else {
-      multiJoin.getChildFactors(leftTree, childFactors);
+      BitSets.setAll(childFactors, leftTree.getTreeOrder());
     }
     multiJoin.getChildFactors(rightTree, childFactors);
 
+    RexNode condition = null;
+    final ListIterator<RexNode> filterIter = filtersToAdd.listIterator();
     while (filterIter.hasNext()) {
       RexNode joinFilter = filterIter.next();
       BitSet filterBitmap =
@@ -1491,8 +1484,7 @@ public class LoptOptimizeJoinRule extends RelOptRule {
     }
 
     int factIdx = multiJoin.getJoinRemovalFactor(dimIdx);
-    List<Integer> joinOrder = new ArrayList<Integer>();
-    factTree.getTreeOrder(joinOrder);
+    final List<Integer> joinOrder = factTree.getTreeOrder();
     assert joinOrder.contains(factIdx);
 
     // figure out the position of the fact table in the current jointree
@@ -1511,8 +1503,8 @@ public class LoptOptimizeJoinRule extends RelOptRule {
     int nDimFields = dimFields.size();
     Integer [] replacementKeys = new Integer[nDimFields];
     SemiJoinRel semiJoin = multiJoin.getJoinRemovalSemiJoin(dimIdx);
-    List<Integer> dimKeys = semiJoin.getRightKeys();
-    List<Integer> factKeys = semiJoin.getLeftKeys();
+    ImmutableIntList dimKeys = semiJoin.getRightKeys();
+    ImmutableIntList factKeys = semiJoin.getLeftKeys();
     for (int i = 0; i < dimKeys.size(); i++) {
       replacementKeys[dimKeys.get(i)] = factKeys.get(i) + adjustment;
     }
@@ -1554,7 +1546,7 @@ public class LoptOptimizeJoinRule extends RelOptRule {
       LoptJoinTree currJoinTree,
       int leftIdx,
       int factorToAdd,
-      List<Integer> newKeys,
+      ImmutableIntList newKeys,
       Integer [] replacementKeys,
       List<RexNode> filtersToAdd) {
     // create a projection, projecting the fields from the join tree

http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/b0aff0df/core/src/main/java/org/eigenbase/rel/rules/OptimizeBushyJoinRule.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/eigenbase/rel/rules/OptimizeBushyJoinRule.java b/core/src/main/java/org/eigenbase/rel/rules/OptimizeBushyJoinRule.java
index 297ce9c..1c6c4fa 100644
--- a/core/src/main/java/org/eigenbase/rel/rules/OptimizeBushyJoinRule.java
+++ b/core/src/main/java/org/eigenbase/rel/rules/OptimizeBushyJoinRule.java
@@ -17,12 +17,28 @@
 */
 package org.eigenbase.rel.rules;
 
-import java.util.*;
+import java.io.PrintWriter;
+import java.util.BitSet;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.List;
 
 import org.eigenbase.rel.*;
+import org.eigenbase.rel.metadata.RelMdUtil;
+import org.eigenbase.rel.metadata.RelMetadataQuery;
 import org.eigenbase.relopt.*;
 import org.eigenbase.rex.*;
+import org.eigenbase.util.Pair;
 import org.eigenbase.util.Util;
+import org.eigenbase.util.mapping.Mappings;
+
+import net.hydromatic.optiq.prepare.OptiqPrepareImpl;
+import net.hydromatic.optiq.util.BitSets;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Lists;
 
 /**
  * Planner rule that finds an approximately optimal ordering for join operators
@@ -33,25 +49,327 @@ import org.eigenbase.util.Util;
  * <p>It is similar to {@link org.eigenbase.rel.rules.LoptOptimizeJoinRule}.
  * {@code LoptOptimizeJoinRule} is only capable of producing left-deep joins;
  * this rule is capable of producing bushy joins.
+ *
+ * <p>TODO:
+ * <ol>
+ *   <li>Join conditions that touch 1 factor.
+ *   <li>Join conditions that touch 3 factors.
+ *   <li>More than 1 join conditions that touch the same pair of factors,
+ *       e.g. {@code t0.c1 = t1.c1 and t1.c2 = t0.c3}
+ * </ol>
  */
 public class OptimizeBushyJoinRule extends RelOptRule {
   public static final OptimizeBushyJoinRule INSTANCE =
-      new OptimizeBushyJoinRule(RelFactories.DEFAULT_JOIN_FACTORY);
+      new OptimizeBushyJoinRule(RelFactories.DEFAULT_JOIN_FACTORY,
+          RelFactories.DEFAULT_PROJECT_FACTORY);
 
   private final RelFactories.JoinFactory joinFactory;
+  private final RelFactories.ProjectFactory projectFactory;
+  private final PrintWriter pw =
+      OptiqPrepareImpl.DEBUG ? new PrintWriter(System.out, true) : null;
 
   /** Creates an OptimizeBushyJoinRule. */
-  public OptimizeBushyJoinRule(RelFactories.JoinFactory joinFactory) {
+  public OptimizeBushyJoinRule(RelFactories.JoinFactory joinFactory,
+      RelFactories.ProjectFactory projectFactory) {
     super(operand(MultiJoinRel.class, any()));
     this.joinFactory = joinFactory;
+    this.projectFactory = projectFactory;
   }
 
   @Override public void onMatch(RelOptRuleCall call) {
     final MultiJoinRel multiJoinRel = call.rel(0);
+    final RexBuilder rexBuilder = multiJoinRel.getCluster().getRexBuilder();
+
     final LoptMultiJoin multiJoin = new LoptMultiJoin(multiJoinRel);
 
-    final RexBuilder rexBuilder = multiJoinRel.getCluster().getRexBuilder();
-    Util.discard(multiJoin);
+    final List<Vertex> vertexes = Lists.newArrayList();
+    int x = 0;
+    for (int i = 0; i < multiJoin.getNumJoinFactors(); i++) {
+      final RelNode rel = multiJoin.getJoinFactor(i);
+      double cost = RelMetadataQuery.getRowCount(rel);
+      vertexes.add(new LeafVertex(i, rel, cost, x));
+      x += rel.getRowType().getFieldCount();
+    }
+    assert x == multiJoin.getNumTotalFields();
+
+    final List<LoptMultiJoin.Edge> unusedEdges = Lists.newArrayList();
+    for (RexNode node : multiJoin.getJoinFilters()) {
+      unusedEdges.add(multiJoin.createEdge(node));
+    }
+
+    // Comparator that chooses the best edge. A "good edge" is one that has
+    // a large difference in the number of rows on LHS and RHS.
+    final Comparator<LoptMultiJoin.Edge> edgeComparator =
+        new Comparator<LoptMultiJoin.Edge>() {
+          public int compare(LoptMultiJoin.Edge e0, LoptMultiJoin.Edge e1) {
+            return Double.compare(rowCountDiff(e0), rowCountDiff(e1));
+          }
+
+          private double rowCountDiff(LoptMultiJoin.Edge edge) {
+            assert edge.factors.cardinality() == 2 : edge.factors;
+            final int factor0 = edge.factors.nextSetBit(0);
+            final int factor1 = edge.factors.nextSetBit(factor0 + 1);
+            return Math.abs(vertexes.get(factor0).cost
+                - vertexes.get(factor1).cost);
+          }
+        };
+
+    final List<LoptMultiJoin.Edge> usedEdges = Lists.newArrayList();
+    while (!unusedEdges.isEmpty()) {
+      final int edgeOrdinal = chooseBestEdge(unusedEdges, edgeComparator);
+      if (pw != null) {
+        trace(vertexes, unusedEdges, usedEdges, edgeOrdinal, pw);
+      }
+      final LoptMultiJoin.Edge bestEdge = remove(unusedEdges, edgeOrdinal);
+      usedEdges.add(bestEdge);
+
+      // For now, assume that the edge is between precisely two factors.
+      // 1-factor conditions have probably been pushed down,
+      // and 3-or-more-factor conditions are advanced. (TODO:)
+      // Therefore, for now, the factors that are merged are exactly the factors
+      // on this edge.
+      BitSet merged = bestEdge.factors;
+      assert merged.cardinality() == 2;
+      final int[] factors = BitSets.toArray(merged);
+
+      // Determine which factor is to be on the LHS of the join.
+      final int majorFactor;
+      final int minorFactor;
+      if (vertexes.get(factors[0]).cost <= vertexes.get(factors[1]).cost) {
+        majorFactor = factors[0];
+        minorFactor = factors[1];
+      } else {
+        majorFactor = factors[1];
+        minorFactor = factors[0];
+      }
+      final Vertex majorVertex = vertexes.get(majorFactor);
+      final Vertex minorVertex = vertexes.get(minorFactor);
+
+      // Find the join conditions. All conditions whose factors are now all in
+      // the join can now be used.
+      final BitSet newFactors =
+          BitSets.union(majorVertex.factors, minorVertex.factors);
+      final List<RexNode> conditions = Lists.newArrayList(bestEdge.condition);
+      final Iterator<LoptMultiJoin.Edge> edgeIterator = unusedEdges.iterator();
+      while (edgeIterator.hasNext()) {
+        LoptMultiJoin.Edge edge = edgeIterator.next();
+        if (BitSets.contains(newFactors, edge.factors)) {
+          conditions.add(edge.condition);
+          edgeIterator.remove();
+          usedEdges.add(edge);
+        }
+      }
+
+      final int v = vertexes.size();
+      double cost =
+          majorVertex.cost
+          * minorVertex.cost
+          * RelMdUtil.guessSelectivity(
+              RexUtil.composeConjunction(rexBuilder, conditions, false));
+      newFactors.set(v);
+      final Vertex newVertex =
+          new JoinVertex(v, majorFactor, minorFactor, newFactors,
+              cost, ImmutableList.copyOf(conditions));
+      vertexes.add(newVertex);
+
+      // Re-compute selectivity of edges above the one just chosen.
+      // Suppose that we just chose the edge between "product" (10k rows) and
+      // "product_class" (10 rows).
+      // Both of those vertices are now replaced by a new vertex "P-PC".
+      // This vertex has fewer rows (1k rows) -- a fact that is critical to
+      // decisions made later. (Hence "greedy" algorithm not "simple".)
+      // The adjacent edges are modified.
+      for (int i = 0; i < unusedEdges.size(); i++) {
+        final LoptMultiJoin.Edge edge = unusedEdges.get(i);
+        if (edge.factors.intersects(merged)) {
+          BitSet newEdgeFactors = (BitSet) edge.factors.clone();
+          newEdgeFactors.andNot(newFactors);
+          newEdgeFactors.set(v);
+          assert newEdgeFactors.cardinality() == 2;
+          final LoptMultiJoin.Edge newEdge =
+              new LoptMultiJoin.Edge(edge.condition, newEdgeFactors,
+                  edge.columns);
+          unusedEdges.set(i, newEdge);
+        }
+      }
+    }
+
+    // We have a winner!
+    List<Pair<RelNode, Mappings.TargetMapping>> relNodes = Lists.newArrayList();
+    for (Vertex vertex : vertexes) {
+      if (vertex instanceof LeafVertex) {
+        LeafVertex leafVertex = (LeafVertex) vertex;
+        final Mappings.TargetMapping mapping =
+            Mappings.offsetSource(
+                Mappings.createIdentity(
+                    leafVertex.rel.getRowType().getFieldCount()),
+                leafVertex.fieldOffset,
+                multiJoin.getNumTotalFields());
+        relNodes.add(Pair.of(leafVertex.rel, mapping));
+      } else {
+        JoinVertex joinVertex = (JoinVertex) vertex;
+        final Pair<RelNode, Mappings.TargetMapping> leftPair =
+            relNodes.get(joinVertex.leftFactor);
+        RelNode left = leftPair.left;
+        final Mappings.TargetMapping leftMapping = leftPair.right;
+        final Pair<RelNode, Mappings.TargetMapping> rightPair =
+            relNodes.get(joinVertex.rightFactor);
+        RelNode right = rightPair.left;
+        final Mappings.TargetMapping rightMapping = rightPair.right;
+        final Mappings.TargetMapping mapping =
+            Mappings.merge(leftMapping,
+                Mappings.offsetTarget(rightMapping,
+                    left.getRowType().getFieldCount()));
+        if (pw != null) {
+          pw.println("left: " + leftMapping);
+          pw.println("right: " + rightMapping);
+          pw.println("combined: " + mapping);
+          pw.println();
+        }
+        final RexVisitor<RexNode> shuttle =
+            new RexPermuteInputsShuttle(mapping, left, right);
+        final RexNode condition =
+            RexUtil.composeConjunction(rexBuilder, joinVertex.conditions,
+                false);
+        relNodes.add(
+            Pair.of(
+                joinFactory.createJoin(left, right, condition.accept(shuttle),
+                    JoinRelType.INNER, ImmutableSet.<String>of(), false),
+                mapping));
+      }
+      if (pw != null) {
+        pw.println(Util.last(relNodes));
+      }
+    }
+
+    final Pair<RelNode, Mappings.TargetMapping> top = Util.last(relNodes);
+    final RelNode project =
+        RelFactories.createProject(projectFactory, top.left,
+            Mappings.asList(top.right));
+
+    call.transformTo(project);
+  }
+
+  private void trace(List<Vertex> vertexes,
+      List<LoptMultiJoin.Edge> unusedEdges, List<LoptMultiJoin.Edge> usedEdges,
+      int edgeOrdinal, PrintWriter pw) {
+    pw.println("bestEdge: " + edgeOrdinal);
+    pw.println("vertexes:");
+    for (Vertex vertex : vertexes) {
+      pw.println(vertex);
+    }
+    pw.println("unused edges:");
+    for (LoptMultiJoin.Edge edge : unusedEdges) {
+      pw.println(edge);
+    }
+    pw.println("edges:");
+    for (LoptMultiJoin.Edge edge : usedEdges) {
+      pw.println(edge);
+    }
+    pw.println();
+    pw.flush();
+  }
+
+  /** Removes the element of a list at a given ordinal, moving the last element
+   * into its place. This is an efficient means of removing an element from an
+   * array list if you do not mind the order of the list changing. */
+  private static <E> E remove(List<E> list, int ordinal) {
+    final int lastOrdinal = list.size() - 1;
+    final E last = list.remove(lastOrdinal);
+    if (ordinal == lastOrdinal) {
+      return last;
+    }
+    final E e = list.get(ordinal);
+    list.set(ordinal, last);
+    return e;
+  }
+
+  int chooseBestEdge(List<LoptMultiJoin.Edge> edges,
+      Comparator<LoptMultiJoin.Edge> comparator) {
+    return minPos(edges, comparator);
+  }
+
+  /** Returns the index within a list at which compares least according to a
+   * comparator.
+   *
+   * <p>In the case of a tie, returns the earliest such element.</p>
+   *
+   * <p>If the list is empty, returns -1.</p>
+   */
+  static <E> int minPos(List<E> list, Comparator<E> fn) {
+    if (list.isEmpty()) {
+      return -1;
+    }
+    E eBest = list.get(0);
+    int iBest = 0;
+    for (int i = 1; i < list.size(); i++) {
+      E e = list.get(i);
+      if (fn.compare(e, eBest) < 0) {
+        eBest = e;
+        iBest = i;
+      }
+    }
+    return iBest;
+  }
+
+  /** Participant in a join (relation or join). */
+  abstract static class Vertex {
+    final int id;
+
+    protected final BitSet factors;
+    final double cost;
+
+    Vertex(int id, BitSet factors, double cost) {
+      this.id = id;
+      this.factors = factors;
+      this.cost = cost;
+    }
+  }
+
+  /** Relation participating in a join. */
+  static class LeafVertex extends Vertex {
+    private final RelNode rel;
+    final int fieldOffset;
+
+    LeafVertex(int id, RelNode rel, double cost, int fieldOffset) {
+      super(id, BitSets.of(id), cost);
+      this.rel = rel;
+      this.fieldOffset = fieldOffset;
+    }
+
+    @Override public String toString() {
+      return "LeafVertex(id: " + id
+          + ", cost: " + Util.human(cost)
+          + ", factors: " + factors
+          + ", fieldOffset: " + fieldOffset
+          + ")";
+    }
+  }
+
+  /** Participant in a join which is itself a join. */
+  static class JoinVertex extends Vertex {
+    private final int leftFactor;
+    private final int rightFactor;
+    /** Zero or more join conditions. All are in terms of the original input
+     * columns (not in terms of the outputs of left and right input factors). */
+    final ImmutableList<RexNode> conditions;
+
+    JoinVertex(int id, int leftFactor, int rightFactor,
+        BitSet factors, double cost, ImmutableList<RexNode> conditions) {
+      super(id, factors, cost);
+      this.leftFactor = leftFactor;
+      this.rightFactor = rightFactor;
+      this.conditions = Preconditions.checkNotNull(conditions);
+    }
+
+    @Override public String toString() {
+      return "JoinVertex(id: " + id
+          + ", cost: " + Util.human(cost)
+          + ", factors: " + factors
+          + ", leftFactor: " + leftFactor
+          + ", rightFactor: " + rightFactor
+          + ")";
+    }
   }
 }
 

http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/b0aff0df/core/src/main/java/org/eigenbase/rel/rules/SemiJoinRel.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/eigenbase/rel/rules/SemiJoinRel.java b/core/src/main/java/org/eigenbase/rel/rules/SemiJoinRel.java
index 8dfa4fa..e9a9c6d 100644
--- a/core/src/main/java/org/eigenbase/rel/rules/SemiJoinRel.java
+++ b/core/src/main/java/org/eigenbase/rel/rules/SemiJoinRel.java
@@ -109,11 +109,11 @@ public final class SemiJoinRel extends JoinRelBase {
         Collections.<RelDataTypeField>emptyList());
   }
 
-  public List<Integer> getLeftKeys() {
+  public ImmutableIntList getLeftKeys() {
     return leftKeys;
   }
 
-  public List<Integer> getRightKeys() {
+  public ImmutableIntList getRightKeys() {
     return rightKeys;
   }
 }

http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/b0aff0df/core/src/main/java/org/eigenbase/relopt/volcano/RelSubset.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/eigenbase/relopt/volcano/RelSubset.java b/core/src/main/java/org/eigenbase/relopt/volcano/RelSubset.java
index dd83915..add6c4a 100644
--- a/core/src/main/java/org/eigenbase/relopt/volcano/RelSubset.java
+++ b/core/src/main/java/org/eigenbase/relopt/volcano/RelSubset.java
@@ -129,10 +129,10 @@ public class RelSubset extends AbstractRelNode {
   }
 
   public double getRows() {
-    if (best == null) {
-      return Double.POSITIVE_INFINITY;
-    } else {
+    if (best != null) {
       return RelMetadataQuery.getRowCount(best);
+    } else {
+      return RelMetadataQuery.getRowCount(set.rel);
     }
   }
 

http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/b0aff0df/core/src/main/java/org/eigenbase/sql2rel/SqlToRelConverter.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/eigenbase/sql2rel/SqlToRelConverter.java b/core/src/main/java/org/eigenbase/sql2rel/SqlToRelConverter.java
index cbd52f1..ec8fcf4 100644
--- a/core/src/main/java/org/eigenbase/sql2rel/SqlToRelConverter.java
+++ b/core/src/main/java/org/eigenbase/sql2rel/SqlToRelConverter.java
@@ -571,11 +571,9 @@ public class SqlToRelConverter {
         return;
       }
 
-      Map<Integer, Integer> squished = new HashMap<Integer, Integer>();
-      final List<RelDataTypeField> fields =
-          rel.getRowType().getFieldList();
-      List<Pair<RexNode, String>> newProjects =
-          new ArrayList<Pair<RexNode, String>>();
+      final Map<Integer, Integer> squished = Maps.newHashMap();
+      final List<RelDataTypeField> fields = rel.getRowType().getFieldList();
+      final List<Pair<RexNode, String>> newProjects = Lists.newArrayList();
       for (int i = 0; i < fields.size(); i++) {
         if (origins.get(i) == i) {
           squished.put(i, newProjects.size());
@@ -596,8 +594,7 @@ public class SqlToRelConverter {
 
       // Create the expressions to reverse the mapping.
       // Project($0, $1, $0, $2).
-      List<Pair<RexNode, String>> undoProjects =
-          new ArrayList<Pair<RexNode, String>>();
+      final List<Pair<RexNode, String>> undoProjects = Lists.newArrayList();
       for (int i = 0; i < fields.size(); i++) {
         final int origin = origins.get(i);
         RelDataTypeField field = fields.get(i);
@@ -625,12 +622,11 @@ public class SqlToRelConverter {
 
     // Usual case: all of the expressions in the SELECT clause are
     // different.
-    List<AggregateCall> aggCalls = Collections.emptyList();
     rel =
         createAggregate(
             bb,
             BitSets.range(rel.getRowType().getFieldCount()),
-            aggCalls);
+            ImmutableList.<AggregateCall>of());
 
     bb.setRoot(
         rel,

http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/b0aff0df/core/src/main/java/org/eigenbase/util/ImmutableIntList.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/eigenbase/util/ImmutableIntList.java b/core/src/main/java/org/eigenbase/util/ImmutableIntList.java
index 18dd530..aa1a35e 100644
--- a/core/src/main/java/org/eigenbase/util/ImmutableIntList.java
+++ b/core/src/main/java/org/eigenbase/util/ImmutableIntList.java
@@ -144,13 +144,21 @@ public class ImmutableIntList extends FlatLists.AbstractFlatList<Integer> {
     return ints[index];
   }
 
+  public int getInt(int index) {
+    return ints[index];
+  }
+
   public int indexOf(Object o) {
     if (o instanceof Integer) {
-      final int seek = (Integer) o;
-      for (int i = 0; i < ints.length; i++) {
-        if (ints[i] == seek) {
-          return i;
-        }
+      return indexOf((int) (Integer) o);
+    }
+    return -1;
+  }
+
+  public int indexOf(int seek) {
+    for (int i = 0; i < ints.length; i++) {
+      if (ints[i] == seek) {
+        return i;
       }
     }
     return -1;
@@ -158,11 +166,15 @@ public class ImmutableIntList extends FlatLists.AbstractFlatList<Integer> {
 
   public int lastIndexOf(Object o) {
     if (o instanceof Integer) {
-      final int seek = (Integer) o;
-      for (int i = ints.length - 1; i >= 0; --i) {
-        if (ints[i] == seek) {
-          return i;
-        }
+      return lastIndexOf((int) (Integer) o);
+    }
+    return -1;
+  }
+
+  public int lastIndexOf(int seek) {
+    for (int i = ints.length - 1; i >= 0; --i) {
+      if (ints[i] == seek) {
+        return i;
       }
     }
     return -1;

http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/b0aff0df/core/src/main/java/org/eigenbase/util/Util.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/eigenbase/util/Util.java b/core/src/main/java/org/eigenbase/util/Util.java
index fd85b78..9672c0c 100644
--- a/core/src/main/java/org/eigenbase/util/Util.java
+++ b/core/src/main/java/org/eigenbase/util/Util.java
@@ -2056,6 +2056,53 @@ public class Util {
         && list0.subList(0, list1.size()).equals(list1);
   }
 
+  /** Converts a number into human-readable form, with 3 digits and a "K", "M"
+   * or "G" multiplier for thousands, millions or billions.
+   *
+   * <p>Examples: -2, 0, 1, 999, 1.00K, 1.99K, 3.45M, 4.56B.</p>
+   */
+  public static String human(double d) {
+    if (d == 0d) {
+      return "0";
+    }
+    if (d < 0d) {
+      return "-" + human(-d);
+    }
+    final int digitCount = (int) Math.floor(Math.log10(d));
+    switch (digitCount) {
+    case 0:
+    case 1:
+    case 2:
+      return Integer.toString((int) d);
+    case 3:
+    case 4:
+    case 5:
+      return digits3(Math.round(d / 10D), digitCount % 3) + "K";
+    case 6:
+    case 7:
+    case 8:
+      return digits3(Math.round(d / 10000D), digitCount % 3) + "M";
+    case 9:
+    case 10:
+    case 11:
+      return digits3(Math.round(d / 10000000D), digitCount % 3) + "G";
+    default:
+      return Double.toString(d);
+    }
+  }
+
+  private static String digits3(long x, int z) {
+    final String s = Long.toString(x);
+    switch (z) {
+    case 0:
+      return s.charAt(0) + "." + s.substring(1, 3);
+    case 1:
+      return s.substring(0, 2) + "." + s.substring(2, 3);
+    default:
+      return s.substring(0, 3);
+    }
+  }
+
   //~ Inner Classes ----------------------------------------------------------
 
   /**

http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/b0aff0df/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 a727231..3e9a657 100644
--- a/core/src/main/java/org/eigenbase/util/mapping/Mappings.java
+++ b/core/src/main/java/org/eigenbase/util/mapping/Mappings.java
@@ -396,6 +396,13 @@ public abstract class Mappings {
 
   /**
    * Creates a mapping by appending two mappings.
+   *
+   * <p>Sources and targets of the second mapping are shifted to the right.</p>
+   *
+   * <p>For example, <pre>append({0:0, 1:1}, {0:0, 1:1, 2:2})</pre> yields
+   * <pre>{0:0, 1:1, 2:2, 3:3, 4:4}</pre>.
+   *
+   * @see #merge
    */
   public static TargetMapping append(
       TargetMapping mapping0,
@@ -423,18 +430,27 @@ public abstract class Mappings {
 
   /**
    * Creates a mapping by merging two mappings. There must be no clashes.
+   *
+   * <p>Unlike {@link #append}, sources and targets are not shifted.
+   *
+   * <p>For example, <code>merge({0:0, 1:1}, {2:2, 3:3, 4:4})</code> yields
+   * <code>{0:0, 1:1, 2:2, 3:3, 4:4}</code>.
+   * <code>merge({0:0, 1:1}, {1:2, 2:3})</code> throws, because there are
+   * two entries with source=1.
    */
   public static TargetMapping merge(
       TargetMapping mapping0,
       TargetMapping mapping1) {
     final int s0 = mapping0.getSourceCount();
     final int s1 = mapping1.getSourceCount();
-    assert s0 == s1;
+    final int sMin = Math.min(s0, s1);
+    final int sMax = Math.max(s0, s1);
     final int t0 = mapping0.getTargetCount();
     final int t1 = mapping1.getTargetCount();
+    final int tMax = Math.max(t0, t1);
     final TargetMapping mapping =
-        create(MappingType.INVERSE_SURJECTION, s0, Math.max(t0, t1));
-    for (int s = 0; s < s0; s++) {
+        create(MappingType.INVERSE_SURJECTION, sMax, tMax);
+    for (int s = 0; s < sMin; s++) {
       int t = mapping0.getTargetOpt(s);
       if (t >= 0) {
         mapping.set(s, t);
@@ -446,11 +462,36 @@ public abstract class Mappings {
         }
       }
     }
+    for (int s = sMin; s < sMax; s++) {
+      int t = s < s0 ? mapping0.getTargetOpt(s) : -1;
+      if (t >= 0) {
+        mapping.set(s, t);
+        assert mapping1.getTargetOpt(s) < 0;
+      } else {
+        t = s < s1 ? mapping1.getTargetOpt(s) : -1;
+        if (t >= 0) {
+          mapping.set(s, t);
+        }
+      }
+    }
     return mapping;
   }
 
   /**
    * Returns a mapping that shifts a given mapping's source by a given
+   * offset, incrementing the number of sources by the minimum possible.
+   *
+   * @param mapping     Input mapping
+   * @param offset      Offset to be applied to each source
+   * @return Shifted mapping
+   */
+  public static TargetMapping offsetSource(
+      final TargetMapping mapping, final int offset) {
+    return offsetSource(mapping, offset, mapping.getSourceCount() + offset);
+  }
+
+  /**
+   * Returns a mapping that shifts a given mapping's source by a given
    * offset.
    *
    * <p>For example, given {@code mapping} with sourceCount=2, targetCount=8,
@@ -483,6 +524,50 @@ public abstract class Mappings {
   }
 
   /**
+   * Returns a mapping that shifts a given mapping's target by a given
+   * offset, incrementing the number of targets by the minimum possible.
+   *
+   * @param mapping     Input mapping
+   * @param offset      Offset to be applied to each target
+   * @return Shifted mapping
+   */
+  public static TargetMapping offsetTarget(
+      final TargetMapping mapping, final int offset) {
+    return offsetTarget(mapping, offset, mapping.getTargetCount() + offset);
+  }
+
+  /**
+   * Returns a mapping that shifts a given mapping's target by a given
+   * offset.
+   *
+   * <p>For example, given {@code mapping} with sourceCount=2, targetCount=8,
+   * and (source, target) entries {[0: 5], [1: 7]}, offsetTarget(mapping, 3)
+   * returns a mapping with sourceCount=2, targetCount=11,
+   * and (source, target) entries {[0: 8], [1: 10]}.
+   *
+   * @param mapping     Input mapping
+   * @param offset      Offset to be applied to each target
+   * @param targetCount New target count; must be at least {@code mapping}'s
+   *                    target count plus {@code offset}
+   * @return Shifted mapping
+   */
+  public static TargetMapping offsetTarget(
+      final TargetMapping mapping, final int offset, final int targetCount) {
+    if (targetCount < mapping.getTargetCount() + offset) {
+      throw new IllegalArgumentException("new target count too low");
+    }
+    return target(
+        new Function1<Integer, Integer>() {
+          public Integer apply(Integer source) {
+            int target = mapping.getTargetOpt(source);
+            return target < 0 ? null : target + offset;
+          }
+        },
+        mapping.getSourceCount(),
+        targetCount);
+  }
+
+  /**
    * Returns a mapping that shifts a given mapping's source and target by a
    * given offset.
    *

http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/b0aff0df/core/src/test/java/net/hydromatic/optiq/tools/PlannerTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/net/hydromatic/optiq/tools/PlannerTest.java b/core/src/test/java/net/hydromatic/optiq/tools/PlannerTest.java
index 77e734e..d544858 100644
--- a/core/src/test/java/net/hydromatic/optiq/tools/PlannerTest.java
+++ b/core/src/test/java/net/hydromatic/optiq/tools/PlannerTest.java
@@ -22,7 +22,6 @@ import net.hydromatic.optiq.config.Lex;
 import net.hydromatic.optiq.impl.java.ReflectiveSchema;
 import net.hydromatic.optiq.impl.jdbc.*;
 import net.hydromatic.optiq.impl.jdbc.JdbcRules.JdbcProjectRel;
-import net.hydromatic.optiq.prepare.OptiqPrepareImpl;
 import net.hydromatic.optiq.rules.java.EnumerableConvention;
 import net.hydromatic.optiq.rules.java.JavaRules;
 import net.hydromatic.optiq.rules.java.JavaRules.EnumerableProjectRel;
@@ -45,7 +44,6 @@ import org.eigenbase.sql.validate.SqlValidatorScope;
 import org.eigenbase.util.Util;
 
 import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableSet;
 
 import org.junit.Test;
 
@@ -444,7 +442,7 @@ public class PlannerTest {
           .append(i - 1).append(".\"deptno\"");
     }
     Planner planner =
-        getPlanner(null, Programs.heuristicJoinOrder(RULE_SET, false));
+        getPlanner(null, Programs.heuristicJoinOrder(Programs.RULE_SET, false));
     SqlNode parse = planner.parse(buf.toString());
 
     SqlNode validate = planner.validate(parse);
@@ -456,23 +454,81 @@ public class PlannerTest {
         "EnumerableJoinRel(condition=[=($3, $0)], joinType=[inner])"));
   }
 
-  /** Plans a 4-table join query on the FoodMart schema. The ideal plan is not
+  /** Plans a 3-table join query on the FoodMart schema. The ideal plan is not
    * bushy, but nevertheless exercises the bushy-join heuristic optimizer. */
   @Test public void testAlmostBushy() throws Exception {
-    final String sql = "select *\n"
+    checkBushy("select *\n"
         + "from \"sales_fact_1997\" as s\n"
         + "  join \"customer\" as c using (\"customer_id\")\n"
         + "  join \"product\" as p using (\"product_id\")\n"
         + "where c.\"city\" = 'San Francisco'\n"
-        + "and p.\"brand_name\" = 'Washington'";
-    final String expected = ""
-        + "EnumerableJoinRel(condition=[=($0, $38)], joinType=[inner])\n"
-        + "  EnumerableJoinRel(condition=[=($2, $8)], joinType=[inner])\n"
-        + "    EnumerableTableAccessRel(table=[[foodmart2, sales_fact_1997]])\n"
-        + "    EnumerableFilterRel(condition=[=($9, 'San Francisco')])\n"
-        + "      EnumerableTableAccessRel(table=[[foodmart2, customer]])\n"
-        + "  EnumerableFilterRel(condition=[=($2, 'Washington')])\n"
-        + "    EnumerableTableAccessRel(table=[[foodmart2, product]])\n";
+        + "and p.\"brand_name\" = 'Washington'",
+        "EnumerableProjectRel(product_id=[$0], time_id=[$1], customer_id=[$2], promotion_id=[$3], store_id=[$4], store_sales=[$5], store_cost=[$6], unit_sales=[$7], customer_id0=[$8], account_num=[$9], lname=[$10], fname=[$11], mi=[$12], address1=[$13], address2=[$14], address3=[$15], address4=[$16], city=[$17], state_province=[$18], postal_code=[$19], country=[$20], customer_region_id=[$21], phone1=[$22], phone2=[$23], birthdate=[$24], marital_status=[$25], yearly_income=[$26], gender=[$27], total_children=[$28], num_children_at_home=[$29], education=[$30], date_accnt_opened=[$31], member_card=[$32], occupation=[$33], houseowner=[$34], num_cars_owned=[$35], fullname=[$36], product_class_id=[$37], product_id0=[$38], brand_name=[$39], product_name=[$40], SKU=[$41], SRP=[$42], gross_weight=[$43], net_weight=[$44], recyclable_package=[$45], low_fat=[$46], units_per_case=[$47], cases_per_pallet=[$48], shelf_width=[$49], shelf_height=[$50], shelf_depth=[$51])\n"
+        + "  EnumerableProjectRel($f0=[$44], $f1=[$45], $f2=[$46], $f3=[$47], $f4=[$48], $f5=[$49], $f6=[$50], $f7=[$51], $f8=[$15], $f9=[$16], $f10=[$17], $f11=[$18], $f12=[$19], $f13=[$20], $f14=[$21], $f15=[$22], $f16=[$23], $f17=[$24], $f18=[$25], $f19=[$26], $f20=[$27], $f21=[$28], $f22=[$29], $f23=[$30], $f24=[$31], $f25=[$32], $f26=[$33], $f27=[$34], $f28=[$35], $f29=[$36], $f30=[$37], $f31=[$38], $f32=[$39], $f33=[$40], $f34=[$41], $f35=[$42], $f36=[$43], $f37=[$0], $f38=[$1], $f39=[$2], $f40=[$3], $f41=[$4], $f42=[$5], $f43=[$6], $f44=[$7], $f45=[$8], $f46=[$9], $f47=[$10], $f48=[$11], $f49=[$12], $f50=[$13], $f51=[$14])\n"
+        + "    EnumerableJoinRel(condition=[=($44, $1)], joinType=[inner])\n"
+        + "      EnumerableFilterRel(condition=[=($2, 'Washington')])\n"
+        + "        EnumerableTableAccessRel(table=[[foodmart2, product]])\n"
+        + "      EnumerableJoinRel(condition=[=($31, $0)], joinType=[inner])\n"
+        + "        EnumerableFilterRel(condition=[=($9, 'San Francisco')])\n"
+        + "          EnumerableTableAccessRel(table=[[foodmart2, customer]])\n"
+        + "        EnumerableTableAccessRel(table=[[foodmart2, sales_fact_1997]])\n");
+  }
+
+  /** Plans a 4-table join query on the FoodMart schema.
+   *
+   * <p>The ideal plan is bushy:
+   *   customer x (product_class x  product x sales)
+   * which would be written
+   *   (customer x ((product_class x product) x sales))
+   * if you don't assume 'x' is left-associative. */
+  @Test public void testBushy() throws Exception {
+    checkBushy("select *\n"
+        + "from \"sales_fact_1997\" as s\n"
+        + "  join \"customer\" as c using (\"customer_id\")\n"
+        + "  join \"product\" as p using (\"product_id\")\n"
+        + "  join \"product_class\" as pc using (\"product_class_id\")\n"
+        + "where c.\"city\" = 'San Francisco'\n"
+        + "and p.\"brand_name\" = 'Washington'",
+        "EnumerableProjectRel(product_id=[$0], time_id=[$1], customer_id=[$2], promotion_id=[$3], store_id=[$4], store_sales=[$5], store_cost=[$6], unit_sales=[$7], customer_id0=[$8], account_num=[$9], lname=[$10], fname=[$11], mi=[$12], address1=[$13], address2=[$14], address3=[$15], address4=[$16], city=[$17], state_province=[$18], postal_code=[$19], country=[$20], customer_region_id=[$21], phone1=[$22], phone2=[$23], birthdate=[$24], marital_status=[$25], yearly_income=[$26], gender=[$27], total_children=[$28], num_children_at_home=[$29], education=[$30], date_accnt_opened=[$31], member_card=[$32], occupation=[$33], houseowner=[$34], num_cars_owned=[$35], fullname=[$36], product_class_id=[$37], product_id0=[$38], brand_name=[$39], product_name=[$40], SKU=[$41], SRP=[$42], gross_weight=[$43], net_weight=[$44], recyclable_package=[$45], low_fat=[$46], units_per_case=[$47], cases_per_pallet=[$48], shelf_width=[$49], shelf_height=[$50], shelf_depth=[$51], product_class_id0=[$52], pro
 duct_subcategory=[$53], product_category=[$54], product_department=[$55], product_family=[$56])\n"
+        + "  EnumerableProjectRel($f0=[$49], $f1=[$50], $f2=[$51], $f3=[$52], $f4=[$53], $f5=[$54], $f6=[$55], $f7=[$56], $f8=[$0], $f9=[$1], $f10=[$2], $f11=[$3], $f12=[$4], $f13=[$5], $f14=[$6], $f15=[$7], $f16=[$8], $f17=[$9], $f18=[$10], $f19=[$11], $f20=[$12], $f21=[$13], $f22=[$14], $f23=[$15], $f24=[$16], $f25=[$17], $f26=[$18], $f27=[$19], $f28=[$20], $f29=[$21], $f30=[$22], $f31=[$23], $f32=[$24], $f33=[$25], $f34=[$26], $f35=[$27], $f36=[$28], $f37=[$34], $f38=[$35], $f39=[$36], $f40=[$37], $f41=[$38], $f42=[$39], $f43=[$40], $f44=[$41], $f45=[$42], $f46=[$43], $f47=[$44], $f48=[$45], $f49=[$46], $f50=[$47], $f51=[$48], $f52=[$29], $f53=[$30], $f54=[$31], $f55=[$32], $f56=[$33])\n"
+        + "    EnumerableJoinRel(condition=[=($51, $0)], joinType=[inner])\n"
+        + "      EnumerableFilterRel(condition=[=($9, 'San Francisco')])\n"
+        + "        EnumerableTableAccessRel(table=[[foodmart2, customer]])\n"
+        + "      EnumerableJoinRel(condition=[=($20, $6)], joinType=[inner])\n"
+        + "        EnumerableJoinRel(condition=[=($5, $0)], joinType=[inner])\n"
+        + "          EnumerableTableAccessRel(table=[[foodmart2, product_class]])\n"
+        + "          EnumerableFilterRel(condition=[=($2, 'Washington')])\n"
+        + "            EnumerableTableAccessRel(table=[[foodmart2, product]])\n"
+        + "        EnumerableTableAccessRel(table=[[foodmart2, sales_fact_1997]])\n");
+  }
+
+  /** Plans a 5-table join query on the FoodMart schema. The ideal plan is
+   * bushy: store x (customer x (product_class x product x sales)). */
+  @Test public void testBushy5() throws Exception {
+    checkBushy("select *\n"
+        + "from \"sales_fact_1997\" as s\n"
+        + "  join \"customer\" as c using (\"customer_id\")\n"
+        + "  join \"product\" as p using (\"product_id\")\n"
+        + "  join \"product_class\" as pc using (\"product_class_id\")\n"
+        + "  join \"store\" as st using (\"store_id\")\n"
+        + "where c.\"city\" = 'San Francisco'\n",
+        "EnumerableProjectRel(product_id=[$0], time_id=[$1], customer_id=[$2], promotion_id=[$3], store_id=[$4], store_sales=[$5], store_cost=[$6], unit_sales=[$7], customer_id0=[$8], account_num=[$9], lname=[$10], fname=[$11], mi=[$12], address1=[$13], address2=[$14], address3=[$15], address4=[$16], city=[$17], state_province=[$18], postal_code=[$19], country=[$20], customer_region_id=[$21], phone1=[$22], phone2=[$23], birthdate=[$24], marital_status=[$25], yearly_income=[$26], gender=[$27], total_children=[$28], num_children_at_home=[$29], education=[$30], date_accnt_opened=[$31], member_card=[$32], occupation=[$33], houseowner=[$34], num_cars_owned=[$35], fullname=[$36], product_class_id=[$37], product_id0=[$38], brand_name=[$39], product_name=[$40], SKU=[$41], SRP=[$42], gross_weight=[$43], net_weight=[$44], recyclable_package=[$45], low_fat=[$46], units_per_case=[$47], cases_per_pallet=[$48], shelf_width=[$49], shelf_height=[$50], shelf_depth=[$51], product_class_id0=[$52], pro
 duct_subcategory=[$53], product_category=[$54], product_department=[$55], product_family=[$56], store_id0=[$57], store_type=[$58], region_id=[$59], store_name=[$60], store_number=[$61], store_street_address=[$62], store_city=[$63], store_state=[$64], store_postal_code=[$65], store_country=[$66], store_manager=[$67], store_phone=[$68], store_fax=[$69], first_opened_date=[$70], last_remodel_date=[$71], store_sqft=[$72], grocery_sqft=[$73], frozen_sqft=[$74], meat_sqft=[$75], coffee_bar=[$76], video_store=[$77], salad_bar=[$78], prepared_food=[$79], florist=[$80])\n"
+        + "  EnumerableProjectRel($f0=[$73], $f1=[$74], $f2=[$75], $f3=[$76], $f4=[$77], $f5=[$78], $f6=[$79], $f7=[$80], $f8=[$24], $f9=[$25], $f10=[$26], $f11=[$27], $f12=[$28], $f13=[$29], $f14=[$30], $f15=[$31], $f16=[$32], $f17=[$33], $f18=[$34], $f19=[$35], $f20=[$36], $f21=[$37], $f22=[$38], $f23=[$39], $f24=[$40], $f25=[$41], $f26=[$42], $f27=[$43], $f28=[$44], $f29=[$45], $f30=[$46], $f31=[$47], $f32=[$48], $f33=[$49], $f34=[$50], $f35=[$51], $f36=[$52], $f37=[$58], $f38=[$59], $f39=[$60], $f40=[$61], $f41=[$62], $f42=[$63], $f43=[$64], $f44=[$65], $f45=[$66], $f46=[$67], $f47=[$68], $f48=[$69], $f49=[$70], $f50=[$71], $f51=[$72], $f52=[$53], $f53=[$54], $f54=[$55], $f55=[$56], $f56=[$57], $f57=[$0], $f58=[$1], $f59=[$2], $f60=[$3], $f61=[$4], $f62=[$5], $f63=[$6], $f64=[$7], $f65=[$8], $f66=[$9], $f67=[$10], $f68=[$11], $f69=[$12], $f70=[$13], $f71=[$14], $f72=[$15], $f73=[$16], $f74=[$17], $f75=[$18], $f76=[$19], $f77=[$20], $f78=[$21], $f79=[$22], $f80=[$23])\n"
+        + "    EnumerableJoinRel(condition=[=($77, $0)], joinType=[inner])\n"
+        + "      EnumerableTableAccessRel(table=[[foodmart2, store]])\n"
+        + "      EnumerableJoinRel(condition=[=($51, $0)], joinType=[inner])\n"
+        + "        EnumerableFilterRel(condition=[=($9, 'San Francisco')])\n"
+        + "          EnumerableTableAccessRel(table=[[foodmart2, customer]])\n"
+        + "        EnumerableJoinRel(condition=[=($20, $6)], joinType=[inner])\n"
+        + "          EnumerableJoinRel(condition=[=($5, $0)], joinType=[inner])\n"
+        + "            EnumerableTableAccessRel(table=[[foodmart2, product_class]])\n"
+        + "            EnumerableTableAccessRel(table=[[foodmart2, product]])\n"
+        + "          EnumerableTableAccessRel(table=[[foodmart2, sales_fact_1997]])\n");
+  }
+
+  /** Checks that a query returns a particular plan, using a planner with
+   * OptimizeBushyJoinRule enabled. */
+  private void checkBushy(String sql, String expected) throws Exception {
     final SchemaPlus rootSchema = Frameworks.createRootSchema(true);
     final FrameworkConfig config = Frameworks.newConfigBuilder()
         .lex(Lex.ORACLE)
@@ -480,7 +536,7 @@ public class PlannerTest {
             OptiqAssert.addSchema(rootSchema,
                 OptiqAssert.SchemaSpec.CLONE_FOODMART))
         .traitDefs((List<RelTraitDef>) null)
-        .programs(Programs.heuristicJoinOrder(RULE_SET, true))
+        .programs(Programs.heuristicJoinOrder(Programs.RULE_SET, true))
         .build();
     Planner planner = Frameworks.getPlanner(config);
     SqlNode parse = planner.parse(sql);
@@ -565,35 +621,6 @@ public class PlannerTest {
     }
   }
 
-  private static final ImmutableSet<RelOptRule> RULE_SET =
-      ImmutableSet.of(
-          JavaRules.ENUMERABLE_JOIN_RULE,
-          JavaRules.ENUMERABLE_PROJECT_RULE,
-          JavaRules.ENUMERABLE_FILTER_RULE,
-          JavaRules.ENUMERABLE_AGGREGATE_RULE,
-          JavaRules.ENUMERABLE_SORT_RULE,
-          JavaRules.ENUMERABLE_LIMIT_RULE,
-          JavaRules.ENUMERABLE_UNION_RULE,
-          JavaRules.ENUMERABLE_INTERSECT_RULE,
-          JavaRules.ENUMERABLE_MINUS_RULE,
-          JavaRules.ENUMERABLE_TABLE_MODIFICATION_RULE,
-          JavaRules.ENUMERABLE_VALUES_RULE,
-          JavaRules.ENUMERABLE_WINDOW_RULE,
-          JavaRules.ENUMERABLE_ONE_ROW_RULE,
-          JavaRules.ENUMERABLE_EMPTY_RULE,
-          TableAccessRule.INSTANCE,
-          OptiqPrepareImpl.COMMUTE
-              ? CommutativeJoinRule.INSTANCE
-              : MergeProjectRule.INSTANCE,
-          PushFilterPastProjectRule.INSTANCE,
-          PushFilterPastJoinRule.FILTER_ON_JOIN,
-          RemoveDistinctAggregateRule.INSTANCE,
-          ReduceAggregatesRule.INSTANCE,
-          SwapJoinRule.INSTANCE,
-          PushJoinThroughJoinRule.RIGHT,
-          PushJoinThroughJoinRule.LEFT,
-          PushSortPastProjectRule.INSTANCE);
-
   /**
    * Test to determine whether de-correlation correctly removes CorrelatorRel.
    */
@@ -621,11 +648,12 @@ public class PlannerTest {
         Frameworks.createRootSchema(true).add("tpch",
             new ReflectiveSchema(new TpchSchema()));
 
-    Planner p = Frameworks.getPlanner(Frameworks.newConfigBuilder() //
-        .lex(Lex.MYSQL) //
-        .defaultSchema(schema) //
-        .programs(Programs.ofRules(RULE_SET)) //
-        .build());
+    final FrameworkConfig config = Frameworks.newConfigBuilder()
+        .lex(Lex.MYSQL)
+        .defaultSchema(schema)
+        .programs(Programs.ofRules(Programs.RULE_SET))
+        .build();
+    Planner p = Frameworks.getPlanner(config);
     SqlNode n = p.parse(tpchTestQuery);
     n = p.validate(n);
     RelNode r = p.convert(n);

http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/b0aff0df/core/src/test/java/org/eigenbase/util/UtilTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/eigenbase/util/UtilTest.java b/core/src/test/java/org/eigenbase/util/UtilTest.java
index 8df1a1f..c621791 100644
--- a/core/src/test/java/org/eigenbase/util/UtilTest.java
+++ b/core/src/test/java/org/eigenbase/util/UtilTest.java
@@ -33,6 +33,7 @@ import net.hydromatic.linq4j.function.Function1;
 
 import net.hydromatic.optiq.runtime.FlatLists;
 import net.hydromatic.optiq.runtime.Spaces;
+import net.hydromatic.optiq.util.BitSets;
 import net.hydromatic.optiq.util.CompositeMap;
 
 import com.google.common.collect.ImmutableList;
@@ -848,6 +849,7 @@ public class UtilTest {
     assertEquals(0, list.size());
     assertEquals(list, Collections.<Integer>emptyList());
     assertThat(list.toString(), equalTo("[]"));
+    assertThat(BitSets.of(list), equalTo(new BitSet()));
 
     list = ImmutableIntList.of(1, 3, 5);
     assertEquals(3, list.size());
@@ -1260,6 +1262,47 @@ public class UtilTest {
       // ok
     }
   }
+
+  @Test public void testHuman() {
+    assertThat(Util.human(0D), equalTo("0"));
+    assertThat(Util.human(1D), equalTo("1"));
+    assertThat(Util.human(19D), equalTo("19"));
+    assertThat(Util.human(198D), equalTo("198"));
+    assertThat(Util.human(1000D), equalTo("1.00K"));
+    assertThat(Util.human(1002D), equalTo("1.00K"));
+    assertThat(Util.human(1009D), equalTo("1.01K"));
+    assertThat(Util.human(1234D), equalTo("1.23K"));
+    assertThat(Util.human(1987D), equalTo("1.99K"));
+    assertThat(Util.human(1999D), equalTo("2.00K"));
+    assertThat(Util.human(86837.2D), equalTo("86.8K"));
+    assertThat(Util.human(868372.8D), equalTo("868K"));
+    assertThat(Util.human(1009000D), equalTo("1.01M"));
+    assertThat(Util.human(1999999D), equalTo("2.00M"));
+    assertThat(Util.human(1009000000D), equalTo("1.01G"));
+    assertThat(Util.human(1999999000D), equalTo("2.00G"));
+
+    assertThat(Util.human(-1D), equalTo("-1"));
+    assertThat(Util.human(-19D), equalTo("-19"));
+    assertThat(Util.human(-198D), equalTo("-198"));
+    assertThat(Util.human(-1999999000D), equalTo("-2.00G"));
+
+    // not ideal - should use m (milli) and u (micro)
+    assertThat(Util.human(0.18D), equalTo("0.18"));
+    assertThat(Util.human(0.018D), equalTo("0.018"));
+    assertThat(Util.human(0.0018D), equalTo("0.0018"));
+    assertThat(Util.human(0.00018D), equalTo("1.8E-4"));
+    assertThat(Util.human(0.000018D), equalTo("1.8E-5"));
+    assertThat(Util.human(0.0000018D), equalTo("1.8E-6"));
+
+    // bad - should round to 3 digits
+    assertThat(Util.human(0.181111D), equalTo("0.181111"));
+    assertThat(Util.human(0.0181111D), equalTo("0.0181111"));
+    assertThat(Util.human(0.00181111D), equalTo("0.00181111"));
+    assertThat(Util.human(0.000181111D), equalTo("1.81111E-4"));
+    assertThat(Util.human(0.0000181111D), equalTo("1.81111E-5"));
+    assertThat(Util.human(0.00000181111D), equalTo("1.81111E-6"));
+
+  }
 }
 
 // End UtilTest.java

http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/b0aff0df/plus/src/main/java/net/hydromatic/optiq/impl/tpcds/TpcdsSchema.java
----------------------------------------------------------------------
diff --git a/plus/src/main/java/net/hydromatic/optiq/impl/tpcds/TpcdsSchema.java b/plus/src/main/java/net/hydromatic/optiq/impl/tpcds/TpcdsSchema.java
index ec3d0f7..ccfb7d3 100644
--- a/plus/src/main/java/net/hydromatic/optiq/impl/tpcds/TpcdsSchema.java
+++ b/plus/src/main/java/net/hydromatic/optiq/impl/tpcds/TpcdsSchema.java
@@ -23,6 +23,8 @@ import net.hydromatic.linq4j.QueryProvider;
 import net.hydromatic.linq4j.Queryable;
 
 import net.hydromatic.optiq.SchemaPlus;
+import net.hydromatic.optiq.Statistic;
+import net.hydromatic.optiq.Statistics;
 import net.hydromatic.optiq.Table;
 import net.hydromatic.optiq.impl.AbstractSchema;
 import net.hydromatic.optiq.impl.AbstractTableQueryable;
@@ -30,10 +32,13 @@ import net.hydromatic.optiq.impl.java.AbstractQueryableTable;
 
 import org.eigenbase.reltype.RelDataType;
 import org.eigenbase.reltype.RelDataTypeFactory;
+import org.eigenbase.util.Bug;
 
 import com.google.common.collect.ImmutableMap;
 
 import java.sql.Date;
+import java.util.BitSet;
+import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 
@@ -41,7 +46,7 @@ import net.hydromatic.tpcds.TpcdsColumn;
 import net.hydromatic.tpcds.TpcdsEntity;
 import net.hydromatic.tpcds.TpcdsTable;
 
-/** Schema that provides TPC-H tables, populated according to a
+/** Schema that provides TPC-DS tables, populated according to a
  * particular scale factor. */
 public class TpcdsSchema extends AbstractSchema {
   private final double scaleFactor;
@@ -49,6 +54,34 @@ public class TpcdsSchema extends AbstractSchema {
   private final int partCount;
   private final ImmutableMap<String, Table> tableMap;
 
+  // From TPC-DS spec, table 3-2 "Database Row Counts", for 1G sizing.
+  private static final ImmutableMap<String, Integer> TABLE_ROW_COUNTS =
+      ImmutableMap.<String, Integer>builder()
+          .put("call_center", 8)
+          .put("catalog_page", 11718)
+          .put("catalog_returns", 144067)
+          .put("catalog_sales", 1441548)
+          .put("customer", 50000)
+          .put("demographics", 1920800)
+          .put("date_dim", 73049)
+          .put("household_demographics", 7200)
+          .put("income_band", 20)
+          .put("inventory", 11745000)
+          .put("item", 18000)
+          .put("promotions", 300)
+          .put("reason", 35)
+          .put("ship_mode", 20)
+          .put("store", 12)
+          .put("store_returns", 287514)
+          .put("store_sales", 2880404)
+          .put("time_dim", 86400)
+          .put("warehouse", 5)
+          .put("web_page", 60)
+          .put("web_returns", 71763)
+          .put("web_sales", 719384)
+          .put("web_site", 1)
+          .build();
+
   public TpcdsSchema(double scaleFactor, int part, int partCount) {
     this.scaleFactor = scaleFactor;
     this.part = part;
@@ -56,6 +89,7 @@ public class TpcdsSchema extends AbstractSchema {
 
     final ImmutableMap.Builder<String, Table> builder = ImmutableMap.builder();
     for (TpcdsTable<?> tpcdsTable : TpcdsTable.getTables()) {
+      //noinspection unchecked
       builder.put(tpcdsTable.getTableName().toUpperCase(),
           new TpcdsQueryableTable(tpcdsTable));
     }
@@ -77,6 +111,12 @@ public class TpcdsSchema extends AbstractSchema {
       this.tpcdsTable = tpcdsTable;
     }
 
+    @Override public Statistic getStatistic() {
+      Bug.upgrade("add row count estimate to TpcdsTable, and use it");
+      double rowCount = TABLE_ROW_COUNTS.get(tpcdsTable.name);
+      return Statistics.of(rowCount, Collections.<BitSet>emptyList());
+    }
+
     public <T> Queryable<T> asQueryable(final QueryProvider queryProvider,
         final SchemaPlus schema, final String tableName) {
       //noinspection unchecked


[3/7] git commit: Refactor test infrastructure to allow testing against heuristic bushy-join optimizer.

Posted by jh...@apache.org.
Refactor test infrastructure to allow testing against heuristic bushy-join optimizer.

Add enum OptiqAssert.SchemaSpec, to allow more uniform use of various schemas in the test suite.


Project: http://git-wip-us.apache.org/repos/asf/incubator-optiq/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-optiq/commit/929f5310
Tree: http://git-wip-us.apache.org/repos/asf/incubator-optiq/tree/929f5310
Diff: http://git-wip-us.apache.org/repos/asf/incubator-optiq/diff/929f5310

Branch: refs/heads/master
Commit: 929f5310ad53b69a8917105501ad651656e09187
Parents: a461539
Author: Julian Hyde <jh...@apache.org>
Authored: Mon Jul 21 16:14:16 2014 -0700
Committer: Julian Hyde <jh...@apache.org>
Committed: Mon Jul 28 11:12:52 2014 -0700

----------------------------------------------------------------------
 .../net/hydromatic/optiq/tools/Programs.java    |   7 +-
 .../org/eigenbase/rel/rules/LoptJoinTree.java   | 104 +++++++++++--------
 .../rel/rules/LoptOptimizeJoinRule.java         |  24 ++---
 .../rel/rules/OptimizeBushyJoinRule.java        |  58 +++++++++++
 .../net/hydromatic/optiq/test/OptiqAssert.java  | 104 ++++++++++++-------
 .../net/hydromatic/optiq/tools/PlannerTest.java |  72 ++++++++++---
 6 files changed, 253 insertions(+), 116 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/929f5310/core/src/main/java/net/hydromatic/optiq/tools/Programs.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/net/hydromatic/optiq/tools/Programs.java b/core/src/main/java/net/hydromatic/optiq/tools/Programs.java
index 53a6ac6..83c73cd 100644
--- a/core/src/main/java/net/hydromatic/optiq/tools/Programs.java
+++ b/core/src/main/java/net/hydromatic/optiq/tools/Programs.java
@@ -113,7 +113,8 @@ public class Programs {
    * {@link org.eigenbase.rel.rules.MultiJoinRel} and
    * {@link org.eigenbase.rel.rules.LoptOptimizeJoinRule})
    * if there are 6 or more joins (7 or more relations). */
-  public static Program heuristicJoinOrder(final Collection<RelOptRule> rules) {
+  public static Program heuristicJoinOrder(final Collection<RelOptRule> rules,
+      final boolean bushy) {
     return new Program() {
       public RelNode run(RelOptPlanner planner, RelNode rel,
           RelTraitSet requiredOutputTraits) {
@@ -140,7 +141,9 @@ public class Programs {
                   CommutativeJoinRule.INSTANCE,
                   PushJoinThroughJoinRule.LEFT,
                   PushJoinThroughJoinRule.RIGHT));
-          list.add(LoptOptimizeJoinRule.INSTANCE);
+          list.add(bushy
+              ? OptimizeBushyJoinRule.INSTANCE
+              : LoptOptimizeJoinRule.INSTANCE);
           final Program program2 = ofRules(list);
 
           program = sequence(program1, program2);

http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/929f5310/core/src/main/java/org/eigenbase/rel/rules/LoptJoinTree.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/eigenbase/rel/rules/LoptJoinTree.java b/core/src/main/java/org/eigenbase/rel/rules/LoptJoinTree.java
index a274ab9..0e32b0e 100644
--- a/core/src/main/java/org/eigenbase/rel/rules/LoptJoinTree.java
+++ b/core/src/main/java/org/eigenbase/rel/rules/LoptJoinTree.java
@@ -21,6 +21,8 @@ import java.util.*;
 
 import org.eigenbase.rel.*;
 
+import com.google.common.base.Preconditions;
+
 /**
  * Utility class used to store a {@link JoinRelBase} tree and the factors that
  * make up the tree.
@@ -34,26 +36,26 @@ import org.eigenbase.rel.*;
 public class LoptJoinTree {
   //~ Instance fields --------------------------------------------------------
 
-  private BinaryTree factorTree;
-  private RelNode joinTree;
-  private boolean removableSelfJoin;
+  private final BinaryTree factorTree;
+  private final RelNode joinTree;
+  private final boolean removableSelfJoin;
 
   //~ Constructors -----------------------------------------------------------
 
   /**
-   * Creates a jointree consisting of a single node
+   * Creates a join-tree consisting of a single node.
    *
    * @param joinTree RelNode corresponding to the single node
    * @param factorId factor id of the node
    */
   public LoptJoinTree(RelNode joinTree, int factorId) {
     this.joinTree = joinTree;
-    factorTree = new BinaryTree(factorId, this);
+    this.factorTree = new Leaf(factorId, this);
     this.removableSelfJoin = false;
   }
 
   /**
-   * Associates the factor ids with a jointree
+   * Associates the factor ids with a join-tree.
    *
    * @param joinTree RelNodes corresponding to the join tree
    * @param factorTree tree of the factor ids
@@ -70,8 +72,8 @@ public class LoptJoinTree {
   }
 
   /**
-   * Associates the factor ids with a jointree given the factors corresponding
-   * to the left and right subtrees of the join
+   * Associates the factor ids with a join-tree given the factors corresponding
+   * to the left and right subtrees of the join.
    *
    * @param joinTree RelNodes corresponding to the join tree
    * @param leftFactorTree tree of the factor ids for left subtree
@@ -85,7 +87,7 @@ public class LoptJoinTree {
   }
 
   /**
-   * Associates the factor ids with a jointree given the factors corresponding
+   * Associates the factor ids with a join-tree given the factors corresponding
    * to the left and right subtrees of the join. Also indicates whether the
    * join is a removable self-join.
    *
@@ -99,7 +101,7 @@ public class LoptJoinTree {
       BinaryTree leftFactorTree,
       BinaryTree rightFactorTree,
       boolean removableSelfJoin) {
-    factorTree = new BinaryTree(leftFactorTree, rightFactorTree, this);
+    factorTree = new Node(leftFactorTree, rightFactorTree, this);
     this.joinTree = joinTree;
     this.removableSelfJoin = removableSelfJoin;
   }
@@ -111,17 +113,19 @@ public class LoptJoinTree {
   }
 
   public LoptJoinTree getLeft() {
+    final Node node = (Node) factorTree;
     return new LoptJoinTree(
         ((JoinRelBase) joinTree).getLeft(),
-        factorTree.getLeft(),
-        factorTree.getLeft().getParent().isRemovableSelfJoin());
+        node.getLeft(),
+        node.getLeft().getParent().isRemovableSelfJoin());
   }
 
   public LoptJoinTree getRight() {
+    final Node node = (Node) factorTree;
     return new LoptJoinTree(
         ((JoinRelBase) joinTree).getRight(),
-        factorTree.getRight(),
-        factorTree.getRight().getParent().isRemovableSelfJoin());
+        node.getRight(),
+        node.getRight().getParent().isRemovableSelfJoin());
   }
 
   public BinaryTree getFactorTree() {
@@ -142,55 +146,63 @@ public class LoptJoinTree {
    * Simple binary tree class that stores an id in the leaf nodes and keeps
    * track of the parent LoptJoinTree object associated with the binary tree.
    */
-  protected class BinaryTree {
-    private int id;
-    private BinaryTree left;
-    private BinaryTree right;
-    private LoptJoinTree parent;
+  protected abstract static class BinaryTree {
+    private final LoptJoinTree parent;
 
-    public BinaryTree(int rootId, LoptJoinTree parent) {
-      this.id = rootId;
-      this.left = null;
-      this.right = null;
-      this.parent = parent;
+    protected BinaryTree(LoptJoinTree parent) {
+      this.parent = Preconditions.checkNotNull(parent);
     }
 
-    public BinaryTree(
-        BinaryTree left,
-        BinaryTree right,
-        LoptJoinTree parent) {
-      this.left = left;
-      this.right = right;
-      this.parent = parent;
+    public LoptJoinTree getParent() {
+      return parent;
     }
 
-    public BinaryTree getLeft() {
-      return left;
-    }
+    public abstract void getTreeOrder(List<Integer> treeOrder);
+  }
 
-    public BinaryTree getRight() {
-      return right;
-    }
+  /** Binary tree node that has no children. */
+  protected static class Leaf extends BinaryTree {
+    private final int id;
 
-    public LoptJoinTree getParent() {
-      return parent;
+    public Leaf(int rootId, LoptJoinTree parent) {
+      super(parent);
+      this.id = rootId;
     }
 
     /**
      * @return the id associated with a leaf node in a binary tree
      */
     public int getId() {
-      assert left == null && right == null;
       return id;
     }
 
     public void getTreeOrder(List<Integer> treeOrder) {
-      if ((left == null) || (right == null)) {
-        treeOrder.add(id);
-      } else {
-        left.getTreeOrder(treeOrder);
-        right.getTreeOrder(treeOrder);
-      }
+      treeOrder.add(id);
+    }
+  }
+
+  /** Binary tree node that has two children. */
+  protected static class Node extends BinaryTree {
+    private BinaryTree left;
+    private BinaryTree right;
+
+    public Node(BinaryTree left, BinaryTree right, LoptJoinTree parent) {
+      super(parent);
+      this.left = Preconditions.checkNotNull(left);
+      this.right = Preconditions.checkNotNull(right);
+    }
+
+    public BinaryTree getLeft() {
+      return left;
+    }
+
+    public BinaryTree getRight() {
+      return right;
+    }
+
+    public void getTreeOrder(List<Integer> treeOrder) {
+      left.getTreeOrder(treeOrder);
+      right.getTreeOrder(treeOrder);
     }
   }
 }

http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/929f5310/core/src/main/java/org/eigenbase/rel/rules/LoptOptimizeJoinRule.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/eigenbase/rel/rules/LoptOptimizeJoinRule.java b/core/src/main/java/org/eigenbase/rel/rules/LoptOptimizeJoinRule.java
index 9e3d122..e758ee1 100644
--- a/core/src/main/java/org/eigenbase/rel/rules/LoptOptimizeJoinRule.java
+++ b/core/src/main/java/org/eigenbase/rel/rules/LoptOptimizeJoinRule.java
@@ -55,13 +55,13 @@ public class LoptOptimizeJoinRule extends RelOptRule {
 
   // implement RelOptRule
   public void onMatch(RelOptRuleCall call) {
-    MultiJoinRel multiJoinRel = (MultiJoinRel) call.rels[0];
-    LoptMultiJoin multiJoin = new LoptMultiJoin(multiJoinRel);
+    final MultiJoinRel multiJoinRel = call.rel(0);
+    final LoptMultiJoin multiJoin = new LoptMultiJoin(multiJoinRel);
 
     findRemovableOuterJoins(multiJoin);
 
-    RexBuilder rexBuilder = multiJoinRel.getCluster().getRexBuilder();
-    LoptSemiJoinOptimizer semiJoinOpt =
+    final RexBuilder rexBuilder = multiJoinRel.getCluster().getRexBuilder();
+    final LoptSemiJoinOptimizer semiJoinOpt =
         new LoptSemiJoinOptimizer(multiJoin, rexBuilder);
 
     // determine all possible semijoins
@@ -745,13 +745,10 @@ public class LoptOptimizeJoinRule extends RelOptRule {
 
       // can't add a null-generating factor if its dependent,
       // non-null generating factors haven't been added yet
-      if (multiJoin.isNullGenerating(factor)) {
-        BitSet tmp =
-            (BitSet) multiJoin.getOuterJoinFactors(factor).clone();
-        tmp.andNot(factorsAdded);
-        if (tmp.cardinality() != 0) {
-          continue;
-        }
+      if (multiJoin.isNullGenerating(factor)
+          && !BitSets.contains(factorsAdded,
+              multiJoin.getOuterJoinFactors(factor))) {
+        continue;
       }
 
       // determine the best weight between the current factor
@@ -1838,12 +1835,11 @@ public class LoptOptimizeJoinRule extends RelOptRule {
 
     if (selfJoin) {
       return !multiJoin.isLeftFactorInRemovableSelfJoin(
-          left.getFactorTree().getId());
+          ((LoptJoinTree.Leaf) left.getFactorTree()).getId());
     }
 
     Double leftRowCount = RelMetadataQuery.getRowCount(left.getJoinTree());
-    Double rightRowCount =
-        RelMetadataQuery.getRowCount(right.getJoinTree());
+    Double rightRowCount = RelMetadataQuery.getRowCount(right.getJoinTree());
 
     // The left side is smaller than the right if it has fewer rows,
     // or if it has the same number of rows as the right (excluding

http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/929f5310/core/src/main/java/org/eigenbase/rel/rules/OptimizeBushyJoinRule.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/eigenbase/rel/rules/OptimizeBushyJoinRule.java b/core/src/main/java/org/eigenbase/rel/rules/OptimizeBushyJoinRule.java
new file mode 100644
index 0000000..297ce9c
--- /dev/null
+++ b/core/src/main/java/org/eigenbase/rel/rules/OptimizeBushyJoinRule.java
@@ -0,0 +1,58 @@
+/*
+// Licensed to Julian Hyde under one or more contributor license
+// agreements. See the NOTICE file distributed with this work for
+// additional information regarding copyright ownership.
+//
+// Julian Hyde 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.*;
+
+import org.eigenbase.rel.*;
+import org.eigenbase.relopt.*;
+import org.eigenbase.rex.*;
+import org.eigenbase.util.Util;
+
+/**
+ * Planner rule that finds an approximately optimal ordering for join operators
+ * using a heuristic algorithm.
+ *
+ * <p>It is triggered by the pattern {@link ProjectRel} ({@link MultiJoinRel}).
+ *
+ * <p>It is similar to {@link org.eigenbase.rel.rules.LoptOptimizeJoinRule}.
+ * {@code LoptOptimizeJoinRule} is only capable of producing left-deep joins;
+ * this rule is capable of producing bushy joins.
+ */
+public class OptimizeBushyJoinRule extends RelOptRule {
+  public static final OptimizeBushyJoinRule INSTANCE =
+      new OptimizeBushyJoinRule(RelFactories.DEFAULT_JOIN_FACTORY);
+
+  private final RelFactories.JoinFactory joinFactory;
+
+  /** Creates an OptimizeBushyJoinRule. */
+  public OptimizeBushyJoinRule(RelFactories.JoinFactory joinFactory) {
+    super(operand(MultiJoinRel.class, any()));
+    this.joinFactory = joinFactory;
+  }
+
+  @Override public void onMatch(RelOptRuleCall call) {
+    final MultiJoinRel multiJoinRel = call.rel(0);
+    final LoptMultiJoin multiJoin = new LoptMultiJoin(multiJoinRel);
+
+    final RexBuilder rexBuilder = multiJoinRel.getCluster().getRexBuilder();
+    Util.discard(multiJoin);
+  }
+}
+
+// End OptimizeBushyJoinRule.java

http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/929f5310/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 0a713c1..e23c6b5 100644
--- a/core/src/test/java/net/hydromatic/optiq/test/OptiqAssert.java
+++ b/core/src/test/java/net/hydromatic/optiq/test/OptiqAssert.java
@@ -518,32 +518,37 @@ public class OptiqAssert {
     throw new AssertionError("method " + methodName + " not found");
   }
 
-  static OptiqConnection getConnection(String... schema)
-    throws ClassNotFoundException, SQLException {
-    final List<String> schemaList = Arrays.asList(schema);
-    Class.forName("net.hydromatic.optiq.jdbc.Driver");
-    String suffix = schemaList.contains("spark") ? "spark=true" : "";
-    Connection connection =
-        DriverManager.getConnection("jdbc:optiq:" + suffix);
-    OptiqConnection optiqConnection =
-        connection.unwrap(OptiqConnection.class);
-    SchemaPlus rootSchema = optiqConnection.getRootSchema();
-    if (schemaList.contains("hr")) {
-      rootSchema.add("hr", new ReflectiveSchema(new JdbcTest.HrSchema()));
-    }
-    if (schemaList.contains("foodmart")) {
-      rootSchema.add("foodmart",
+  public static SchemaPlus addSchema(SchemaPlus rootSchema, SchemaSpec schema) {
+    switch (schema) {
+    case REFLECTIVE_FOODMART:
+      return rootSchema.add("foodmart",
           new ReflectiveSchema(new JdbcTest.FoodmartSchema()));
-    }
-    if (schemaList.contains("lingual")) {
-      rootSchema.add("SALES",
+    case JDBC_FOODMART:
+      final DataSource dataSource =
+          JdbcSchema.dataSource(
+              CONNECTION_SPEC.url,
+              CONNECTION_SPEC.driver,
+              CONNECTION_SPEC.username,
+              CONNECTION_SPEC.password);
+      return rootSchema.add("foodmart",
+          JdbcSchema.create(rootSchema, "foodmart", dataSource, null,
+              "foodmart"));
+    case CLONE_FOODMART:
+      SchemaPlus foodmart = rootSchema.getSubSchema("foodmart");
+      if (foodmart == null) {
+        foodmart = OptiqAssert.addSchema(rootSchema, SchemaSpec.JDBC_FOODMART);
+      }
+      return rootSchema.add("foodmart2", new CloneSchema(foodmart));
+    case HR:
+      return rootSchema.add("hr",
+          new ReflectiveSchema(new JdbcTest.HrSchema()));
+    case LINGUAL:
+      return rootSchema.add("SALES",
           new ReflectiveSchema(new JdbcTest.LingualSchema()));
-    }
-    if (schemaList.contains("post")) {
+    case POST:
       final SchemaPlus post = rootSchema.add("POST", new AbstractSchema());
       post.add("EMP",
-          ViewTable.viewMacro(
-              post,
+          ViewTable.viewMacro(post,
               "select * from (values\n"
               + "    ('Jane', 10, 'F'),\n"
               + "    ('Bob', 10, 'M'),\n"
@@ -557,8 +562,7 @@ public class OptiqAssert {
               + "  as t(ename, deptno, gender)",
               ImmutableList.<String>of()));
       post.add("DEPT",
-          ViewTable.viewMacro(
-              post,
+          ViewTable.viewMacro(post,
               "select * from (values\n"
               + "    (10, 'Sales'),\n"
               + "    (20, 'Marketing'),\n"
@@ -566,8 +570,7 @@ public class OptiqAssert {
               + "    (40, 'Empty')) as t(deptno, dname)",
               ImmutableList.<String>of()));
       post.add("EMPS",
-          ViewTable.viewMacro(
-              post,
+          ViewTable.viewMacro(post,
               "select * from (values\n"
               + "    (100, 'Fred',  10, CAST(NULL AS CHAR(1)), CAST(NULL AS VARCHAR(20)), 40,               25, TRUE,    FALSE, DATE '1996-08-03'),\n"
               + "    (110, 'Eric',  20, 'M',                   'San Francisco',           3,                80, UNKNOWN, FALSE, DATE '2001-01-01'),\n"
@@ -576,6 +579,33 @@ public class OptiqAssert {
               + "    (130, 'Alice', 40, 'F',                   'Vancouver',               2, CAST(NULL AS INT), FALSE,   TRUE,  DATE '2007-01-01'))\n"
               + " as t(empno, name, deptno, gender, city, empid, age, slacker, manager, joinedat)",
               ImmutableList.<String>of()));
+      return post;
+    default:
+      throw new AssertionError("unknown schema " + schema);
+    }
+  }
+
+  static OptiqConnection getConnection(String... schema)
+    throws ClassNotFoundException, SQLException {
+    final List<String> schemaList = Arrays.asList(schema);
+    Class.forName("net.hydromatic.optiq.jdbc.Driver");
+    String suffix = schemaList.contains("spark") ? "spark=true" : "";
+    Connection connection =
+        DriverManager.getConnection("jdbc:optiq:" + suffix);
+    OptiqConnection optiqConnection =
+        connection.unwrap(OptiqConnection.class);
+    SchemaPlus rootSchema = optiqConnection.getRootSchema();
+    if (schemaList.contains("hr")) {
+      addSchema(rootSchema, SchemaSpec.HR);
+    }
+    if (schemaList.contains("foodmart")) {
+      addSchema(rootSchema, SchemaSpec.REFLECTIVE_FOODMART);
+    }
+    if (schemaList.contains("lingual")) {
+      addSchema(rootSchema, SchemaSpec.LINGUAL);
+    }
+    if (schemaList.contains("post")) {
+      addSchema(rootSchema, SchemaSpec.POST);
     }
     if (schemaList.contains("metadata")) {
       // always present
@@ -602,18 +632,9 @@ public class OptiqAssert {
     OptiqConnection optiqConnection =
         connection.unwrap(OptiqConnection.class);
     final SchemaPlus rootSchema = optiqConnection.getRootSchema();
-    final DataSource dataSource =
-        JdbcSchema.dataSource(
-            CONNECTION_SPEC.url,
-            CONNECTION_SPEC.driver,
-            CONNECTION_SPEC.username,
-            CONNECTION_SPEC.password);
-    final SchemaPlus foodmart =
-        rootSchema.add("foodmart",
-            JdbcSchema.create(rootSchema, "foodmart", dataSource, null,
-                "foodmart"));
+    addSchema(rootSchema, SchemaSpec.JDBC_FOODMART);
     if (withClone) {
-      rootSchema.add("foodmart2", new CloneSchema(foodmart));
+      addSchema(rootSchema, SchemaSpec.CLONE_FOODMART);
     }
     optiqConnection.setSchema("foodmart2");
     return optiqConnection;
@@ -1177,6 +1198,15 @@ public class OptiqAssert {
       this.driver = driver;
     }
   }
+
+  public enum SchemaSpec {
+    REFLECTIVE_FOODMART,
+    JDBC_FOODMART,
+    CLONE_FOODMART,
+    HR,
+    LINGUAL,
+    POST
+  }
 }
 
 // End OptiqAssert.java

http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/929f5310/core/src/test/java/net/hydromatic/optiq/tools/PlannerTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/net/hydromatic/optiq/tools/PlannerTest.java b/core/src/test/java/net/hydromatic/optiq/tools/PlannerTest.java
index 75282c7..77e734e 100644
--- a/core/src/test/java/net/hydromatic/optiq/tools/PlannerTest.java
+++ b/core/src/test/java/net/hydromatic/optiq/tools/PlannerTest.java
@@ -26,7 +26,6 @@ import net.hydromatic.optiq.prepare.OptiqPrepareImpl;
 import net.hydromatic.optiq.rules.java.EnumerableConvention;
 import net.hydromatic.optiq.rules.java.JavaRules;
 import net.hydromatic.optiq.rules.java.JavaRules.EnumerableProjectRel;
-import net.hydromatic.optiq.test.JdbcTest;
 import net.hydromatic.optiq.test.OptiqAssert;
 
 import org.eigenbase.rel.*;
@@ -143,10 +142,13 @@ public class PlannerTest {
         ImmutableList.of(stdOpTab,
             new ListSqlOperatorTable(
                 ImmutableList.<SqlOperator>of(new MyCountAggFunction()))));
-    Planner planner = Frameworks.getPlanner(Frameworks.newConfigBuilder() //
-        .defaultSchema(createHrSchema()) //
-        .operatorTable(opTab) //
-        .build());
+    final SchemaPlus rootSchema = Frameworks.createRootSchema(true);
+    final FrameworkConfig config = Frameworks.newConfigBuilder()
+        .defaultSchema(
+            OptiqAssert.addSchema(rootSchema, OptiqAssert.SchemaSpec.HR))
+        .operatorTable(opTab)
+        .build();
+    final Planner planner = Frameworks.getPlanner(config);
     SqlNode parse =
         planner.parse("select \"deptno\", my_count(\"empid\") from \"emps\"\n"
             + "group by \"deptno\"");
@@ -175,18 +177,16 @@ public class PlannerTest {
     }
   }
 
-  private SchemaPlus createHrSchema() {
-    return Frameworks.createRootSchema(true).add("hr",
-        new ReflectiveSchema(new JdbcTest.HrSchema()));
-  }
-
   private Planner getPlanner(List<RelTraitDef> traitDefs, Program... programs) {
-    return Frameworks.getPlanner(Frameworks.newConfigBuilder() //
-        .lex(Lex.ORACLE) //
-        .defaultSchema(createHrSchema()) //
-        .traitDefs(traitDefs) //
-        .programs(programs) //
-        .build());
+    final SchemaPlus rootSchema = Frameworks.createRootSchema(true);
+    final FrameworkConfig config = Frameworks.newConfigBuilder()
+        .lex(Lex.ORACLE)
+        .defaultSchema(
+            OptiqAssert.addSchema(rootSchema, OptiqAssert.SchemaSpec.HR))
+        .traitDefs(traitDefs)
+        .programs(programs)
+        .build();
+    return Frameworks.getPlanner(config);
   }
 
   /** Tests that planner throws an error if you pass to
@@ -443,7 +443,8 @@ public class PlannerTest {
           .append(i).append(".\"deptno\" = d")
           .append(i - 1).append(".\"deptno\"");
     }
-    Planner planner = getPlanner(null, Programs.heuristicJoinOrder(RULE_SET));
+    Planner planner =
+        getPlanner(null, Programs.heuristicJoinOrder(RULE_SET, false));
     SqlNode parse = planner.parse(buf.toString());
 
     SqlNode validate = planner.validate(parse);
@@ -455,6 +456,43 @@ public class PlannerTest {
         "EnumerableJoinRel(condition=[=($3, $0)], joinType=[inner])"));
   }
 
+  /** Plans a 4-table join query on the FoodMart schema. The ideal plan is not
+   * bushy, but nevertheless exercises the bushy-join heuristic optimizer. */
+  @Test public void testAlmostBushy() throws Exception {
+    final String sql = "select *\n"
+        + "from \"sales_fact_1997\" as s\n"
+        + "  join \"customer\" as c using (\"customer_id\")\n"
+        + "  join \"product\" as p using (\"product_id\")\n"
+        + "where c.\"city\" = 'San Francisco'\n"
+        + "and p.\"brand_name\" = 'Washington'";
+    final String expected = ""
+        + "EnumerableJoinRel(condition=[=($0, $38)], joinType=[inner])\n"
+        + "  EnumerableJoinRel(condition=[=($2, $8)], joinType=[inner])\n"
+        + "    EnumerableTableAccessRel(table=[[foodmart2, sales_fact_1997]])\n"
+        + "    EnumerableFilterRel(condition=[=($9, 'San Francisco')])\n"
+        + "      EnumerableTableAccessRel(table=[[foodmart2, customer]])\n"
+        + "  EnumerableFilterRel(condition=[=($2, 'Washington')])\n"
+        + "    EnumerableTableAccessRel(table=[[foodmart2, product]])\n";
+    final SchemaPlus rootSchema = Frameworks.createRootSchema(true);
+    final FrameworkConfig config = Frameworks.newConfigBuilder()
+        .lex(Lex.ORACLE)
+        .defaultSchema(
+            OptiqAssert.addSchema(rootSchema,
+                OptiqAssert.SchemaSpec.CLONE_FOODMART))
+        .traitDefs((List<RelTraitDef>) null)
+        .programs(Programs.heuristicJoinOrder(RULE_SET, true))
+        .build();
+    Planner planner = Frameworks.getPlanner(config);
+    SqlNode parse = planner.parse(sql);
+
+    SqlNode validate = planner.validate(parse);
+    RelNode convert = planner.convert(validate);
+    RelTraitSet traitSet = planner.getEmptyTraitSet()
+        .replace(EnumerableConvention.INSTANCE);
+    RelNode transform = planner.transform(0, traitSet, convert);
+    assertThat(toString(transform), containsString(expected));
+  }
+
   /**
    * Rule to convert a {@link EnumerableProjectRel} to an
    * {@link JdbcProjectRel}.


[2/7] git commit: Convert Hook to use Guava Function (was linq4j Function1).

Posted by jh...@apache.org.
Convert Hook to use Guava Function (was linq4j Function1).


Project: http://git-wip-us.apache.org/repos/asf/incubator-optiq/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-optiq/commit/1b12738b
Tree: http://git-wip-us.apache.org/repos/asf/incubator-optiq/tree/1b12738b
Diff: http://git-wip-us.apache.org/repos/asf/incubator-optiq/diff/1b12738b

Branch: refs/heads/master
Commit: 1b12738b7d443c4e648dc9477c2ccf11a5a844bb
Parents: 929f531
Author: Julian Hyde <jh...@apache.org>
Authored: Sat Jul 26 15:12:10 2014 -0700
Committer: Julian Hyde <jh...@apache.org>
Committed: Mon Jul 28 11:12:52 2014 -0700

----------------------------------------------------------------------
 .../java/net/hydromatic/optiq/runtime/Hook.java | 32 +++++++++++---------
 .../net/hydromatic/optiq/test/JdbcTest.java     | 11 +++----
 .../net/hydromatic/optiq/test/OptiqAssert.java  | 16 +++++-----
 .../test/SqlToRelConverterExtendedTest.java     |  7 ++---
 4 files changed, 33 insertions(+), 33 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/1b12738b/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 d63c09a..442e4b5 100644
--- a/core/src/main/java/net/hydromatic/optiq/runtime/Hook.java
+++ b/core/src/main/java/net/hydromatic/optiq/runtime/Hook.java
@@ -17,7 +17,7 @@
 */
 package net.hydromatic.optiq.runtime;
 
-import net.hydromatic.linq4j.function.Function1;
+import com.google.common.base.Function;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -52,13 +52,13 @@ public enum Hook {
    * pipeline expressions (for the MongoDB adapter), et cetera. */
   QUERY_PLAN;
 
-  private final List<Function1<Object, Object>> handlers =
-      new CopyOnWriteArrayList<Function1<Object, Object>>();
+  private final List<Function<Object, Object>> handlers =
+      new CopyOnWriteArrayList<Function<Object, Object>>();
 
-  private final ThreadLocal<List<Function1<Object, Object>>> threadHandlers =
-      new ThreadLocal<List<Function1<Object, Object>>>() {
-        protected List<Function1<Object, Object>> initialValue() {
-          return new ArrayList<Function1<Object, Object>>();
+  private final ThreadLocal<List<Function<Object, Object>>> threadHandlers =
+      new ThreadLocal<List<Function<Object, Object>>>() {
+        protected List<Function<Object, Object>> initialValue() {
+          return new ArrayList<Function<Object, Object>>();
         }
       };
 
@@ -76,8 +76,9 @@ public enum Hook {
    *     }</pre>
    * </blockquote>
    */
-  public Closeable add(final Function1<Object, Object> handler) {
-    handlers.add(handler);
+  public <T, R> Closeable add(final Function<T, R> handler) {
+    //noinspection unchecked
+    handlers.add((Function<Object, Object>) handler);
     return new Closeable() {
       public void close() {
         remove(handler);
@@ -86,13 +87,14 @@ public enum Hook {
   }
 
   /** Removes a handler from this Hook. */
-  private boolean remove(Function1 handler) {
+  private boolean remove(Function handler) {
     return handlers.remove(handler);
   }
 
   /** Adds a handler for this thread. */
-  public Closeable addThread(final Function1<Object, Object> handler) {
-    threadHandlers.get().add(handler);
+  public <T, R> Closeable addThread(final Function<T, R> handler) {
+    //noinspection unchecked
+    threadHandlers.get().add((Function<Object, Object>) handler);
     return new Closeable() {
       public void close() {
         removeThread(handler);
@@ -101,16 +103,16 @@ public enum Hook {
   }
 
   /** Removes a thread handler from this Hook. */
-  private boolean removeThread(Function1 handler) {
+  private boolean removeThread(Function handler) {
     return threadHandlers.get().remove(handler);
   }
 
   /** Runs all handlers registered for this Hook, with the given argument. */
   public void run(Object arg) {
-    for (Function1<Object, Object> handler : handlers) {
+    for (Function<Object, Object> handler : handlers) {
       handler.apply(arg);
     }
-    for (Function1<Object, Object> handler : threadHandlers.get()) {
+    for (Function<Object, Object> handler : threadHandlers.get()) {
       handler.apply(arg);
     }
   }

http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/1b12738b/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 3b0e6b9..4ae47d5 100644
--- a/core/src/test/java/net/hydromatic/optiq/test/JdbcTest.java
+++ b/core/src/test/java/net/hydromatic/optiq/test/JdbcTest.java
@@ -55,6 +55,7 @@ import org.eigenbase.util.Bug;
 import org.eigenbase.util.Pair;
 import org.eigenbase.util.Util;
 
+import com.google.common.base.Function;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableMultimap;
@@ -5630,9 +5631,8 @@ public class JdbcTest {
   @Test public void testHook() {
     final int[] callCount = {0};
     final Hook.Closeable hook = Hook.PARSE_TREE.addThread(
-        new Function1<Object, Object>() {
-          public Object apply(Object a0) {
-            Object[] args = (Object[]) a0;
+        new Function<Object[], Object>() {
+          public Void apply(Object[] args) {
             assertThat(args.length, equalTo(2));
             assertThat(args[0], instanceOf(String.class));
             assertThat((String) args[0], equalTo(
@@ -5661,9 +5661,8 @@ public class JdbcTest {
   @Test public void testDialect() {
     final String[] sqls = {null};
     final Hook.Closeable hook = Hook.QUERY_PLAN.addThread(
-        new Function1<Object, Object>() {
-          public Object apply(Object a0) {
-            String sql = (String) a0;
+        new Function<String, Void>() {
+          public Void apply(String sql) {
             sqls[0] = sql;
             return null;
           }

http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/1b12738b/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 e23c6b5..336ce56 100644
--- a/core/src/test/java/net/hydromatic/optiq/test/OptiqAssert.java
+++ b/core/src/test/java/net/hydromatic/optiq/test/OptiqAssert.java
@@ -33,6 +33,7 @@ import org.eigenbase.rel.RelNode;
 import org.eigenbase.relopt.RelOptUtil;
 import org.eigenbase.util.*;
 
+import com.google.common.base.Function;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMultiset;
 
@@ -400,9 +401,8 @@ public class OptiqAssert {
     final String message =
         "With materializationsEnabled=" + materializationsEnabled;
     Hook.Closeable closeable = Hook.TRIMMED.addThread(
-        new Function1<Object, Object>() {
-          public Object apply(Object a0) {
-            RelNode rel = (RelNode) a0;
+        new Function<RelNode, Object>() {
+          public Void apply(RelNode rel) {
             convertChecker.apply(rel);
             return null;
           }
@@ -1021,9 +1021,9 @@ public class OptiqAssert {
         return;
       }
       final Hook.Closeable hook = Hook.JAVA_PLAN.addThread(
-          new Function1<Object, Object>() {
-            public Object apply(Object a0) {
-              plan = (String) a0;
+          new Function<String, Void>() {
+            public Void apply(String a0) {
+              plan = a0;
               return null;
             }
           });
@@ -1046,8 +1046,8 @@ public class OptiqAssert {
     public AssertQuery queryContains(Function1<List, Void> predicate1) {
       final List<Object> list = new ArrayList<Object>();
       final Hook.Closeable hook = Hook.QUERY_PLAN.addThread(
-          new Function1<Object, Object>() {
-            public Object apply(Object a0) {
+          new Function<Object, Void>() {
+            public Void apply(Object a0) {
               list.add(a0);
               return null;
             }

http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/1b12738b/core/src/test/java/net/hydromatic/optiq/test/SqlToRelConverterExtendedTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/net/hydromatic/optiq/test/SqlToRelConverterExtendedTest.java b/core/src/test/java/net/hydromatic/optiq/test/SqlToRelConverterExtendedTest.java
index c07445b..4067338 100644
--- a/core/src/test/java/net/hydromatic/optiq/test/SqlToRelConverterExtendedTest.java
+++ b/core/src/test/java/net/hydromatic/optiq/test/SqlToRelConverterExtendedTest.java
@@ -17,8 +17,6 @@
 */
 package net.hydromatic.optiq.test;
 
-import net.hydromatic.linq4j.function.Function1;
-
 import net.hydromatic.optiq.SchemaPlus;
 import net.hydromatic.optiq.runtime.Hook;
 import net.hydromatic.optiq.tools.Frameworks;
@@ -28,6 +26,8 @@ import org.eigenbase.relopt.RelOptCluster;
 import org.eigenbase.relopt.RelOptSchema;
 import org.eigenbase.test.SqlToRelConverterTest;
 
+import com.google.common.base.Function;
+
 import org.junit.After;
 import org.junit.Before;
 
@@ -40,9 +40,8 @@ public class SqlToRelConverterExtendedTest extends SqlToRelConverterTest {
   Hook.Closeable closeable;
 
   @Before public void before() {
-    //noinspection unchecked
     this.closeable = Hook.CONVERTED.addThread(
-        (Function1) new Function1<RelNode, Void>() {
+        new Function<RelNode, Void>() {
           public Void apply(RelNode a0) {
             foo(a0);
             return null;


[5/7] [OPTIQ-349] Add heuristic join-optimizer that can generate bushy joins

Posted by jh...@apache.org.
http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/b0aff0df/plus/src/test/java/net/hydromatic/optiq/impl/tpcds/TpcdsTest.java
----------------------------------------------------------------------
diff --git a/plus/src/test/java/net/hydromatic/optiq/impl/tpcds/TpcdsTest.java b/plus/src/test/java/net/hydromatic/optiq/impl/tpcds/TpcdsTest.java
index 713a1fe..27862e0 100644
--- a/plus/src/test/java/net/hydromatic/optiq/impl/tpcds/TpcdsTest.java
+++ b/plus/src/test/java/net/hydromatic/optiq/impl/tpcds/TpcdsTest.java
@@ -17,18 +17,25 @@
 */
 package net.hydromatic.optiq.impl.tpcds;
 
+import net.hydromatic.optiq.prepare.Prepare;
+import net.hydromatic.optiq.runtime.Hook;
 import net.hydromatic.optiq.test.OptiqAssert;
+import net.hydromatic.optiq.tools.Program;
+import net.hydromatic.optiq.tools.Programs;
 
 import org.eigenbase.util.Bug;
+import org.eigenbase.util.Pair;
+
+import com.google.common.base.Function;
 
 import org.junit.Ignore;
 import org.junit.Test;
 
+import java.util.List;
 import java.util.Random;
 
 import net.hydromatic.tpcds.query.Query;
 
-
 /** Unit test for {@link net.hydromatic.optiq.impl.tpcds.TpcdsSchema}.
  *
  * <p>Only runs if {@code -Doptiq.test.slow=true} is specified on the
@@ -89,12 +96,48 @@ public class TpcdsTest {
     checkQuery(1).runs();
   }
 
-  @Test public void testQuery17() {
-    checkQuery(17).explainContains("xx");
+  @Test public void testQuery17Plan() {
+    //noinspection unchecked
+    Hook.Closeable closeable = Hook.PROGRAM.addThread(
+        new Function<Pair<List<Prepare.Materialization>, Program[]>, Void>() {
+          public Void apply(Pair<List<Prepare.Materialization>, Program[]> a0) {
+            a0.right[0] = Programs.heuristicJoinOrder(Programs.RULE_SET, true);
+            return null;
+          }
+        });
+    try {
+      checkQuery(17).explainMatches("including all attributes ",
+          OptiqAssert.checkMaskedResultContains(""
+              + "EnumerableProjectRel(I_ITEM_ID=[$0], I_ITEM_DESC=[$1], S_STATE=[$2], STORE_SALES_QUANTITYCOUNT=[$3], STORE_SALES_QUANTITYAVE=[$4], STORE_SALES_QUANTITYSTDEV=[$5], STORE_SALES_QUANTITYCOV=[/($5, $4)], AS_STORE_RETURNS_QUANTITYCOUNT=[$6], AS_STORE_RETURNS_QUANTITYAVE=[$7], AS_STORE_RETURNS_QUANTITYSTDEV=[$8], STORE_RETURNS_QUANTITYCOV=[/($8, $7)], CATALOG_SALES_QUANTITYCOUNT=[$9], CATALOG_SALES_QUANTITYAVE=[$10], CATALOG_SALES_QUANTITYSTDEV=[/($11, $10)], CATALOG_SALES_QUANTITYCOV=[/($11, $10)]): rowcount = 5.434029018852197E26, cumulative cost = {1.618185849567114E30 rows, 6.412154242245593E28 cpu, 0.0 io}\n"
+              + "  EnumerableSortRel(sort0=[$0], sort1=[$1], sort2=[$2], dir0=[ASC], dir1=[ASC], dir2=[ASC]): rowcount = 5.434029018852197E26, cumulative cost = {1.6176424466652288E30 rows, 5.597049889417763E28 cpu, 0.0 io}\n"
+              + "    EnumerableProjectRel(I_ITEM_ID=[$0], I_ITEM_DESC=[$1], S_STATE=[$2], STORE_SALES_QUANTITYCOUNT=[$3], STORE_SALES_QUANTITYAVE=[CAST(/($4, $5)):JavaType(class java.lang.Integer)], STORE_SALES_QUANTITYSTDEV=[CAST(POWER(/(-($6, /(*($4, $4), $5)), CASE(=($5, 1), null, -($5, 1))), 0.5)):JavaType(class java.lang.Integer)], AS_STORE_RETURNS_QUANTITYCOUNT=[$7], AS_STORE_RETURNS_QUANTITYAVE=[CAST(/($8, $7)):JavaType(class java.lang.Integer)], AS_STORE_RETURNS_QUANTITYSTDEV=[CAST(POWER(/(-($9, /(*($8, $8), $7)), CASE(=($7, 1), null, -($7, 1))), 0.5)):JavaType(class java.lang.Integer)], CATALOG_SALES_QUANTITYCOUNT=[$10], CATALOG_SALES_QUANTITYAVE=[CAST(/($11, $10)):JavaType(class java.lang.Integer)], $f11=[CAST(POWER(/(-($12, /(*($11, $11), $10)), CASE(=($10, 1), null, -($10, 1))), 0.5)):JavaType(class java.lang.Integer)]): rowcount = 5.434029018852197E26, cumulative cost = {1.1954863841615548E28 rows, 5.5427095992292415E28 cpu, 0.0 io}\n"
+              + "      EnumerableAggregateRel(group=[{0, 1, 2}], STORE_SALES_QUANTITYCOUNT=[COUNT()], agg#1=[SUM($3)], agg#2=[COUNT($3)], agg#3=[SUM($6)], AS_STORE_RETURNS_QUANTITYCOUNT=[COUNT($4)], agg#5=[SUM($4)], agg#6=[SUM($7)], CATALOG_SALES_QUANTITYCOUNT=[COUNT($5)], agg#8=[SUM($5)], agg#9=[SUM($8)]): rowcount = 5.434029018852197E26, cumulative cost = {1.1411460939730328E28 rows, 4.890626116966977E28 cpu, 0.0 io}\n"
+              + "        EnumerableProjectRel(I_ITEM_ID=[$58], I_ITEM_DESC=[$61], S_STATE=[$24], SS_QUANTITY=[$89], SR_RETURN_QUANTITY=[$140], CS_QUANTITY=[$196], $f6=[*($89, $89)], $f7=[*($140, $140)], $f8=[*($196, $196)]): rowcount = 5.434029018852197E27, cumulative cost = {1.0868058037845108E28 rows, 4.890626116966977E28 cpu, 0.0 io}\n"
+              + "          EnumerableJoinRel(condition=[AND(=($82, $133), =($81, $132), =($88, $139))], joinType=[inner]): rowcount = 5.434029018852197E27, cumulative cost = {5.434029018992911E27 rows, 5065780.0 cpu, 0.0 io}\n"
+              + "            EnumerableJoinRel(condition=[=($0, $86)], joinType=[inner]): rowcount = 2.3008402586892598E13, cumulative cost = {4.8588854672852766E13 rows, 3044518.0 cpu, 0.0 io}\n"
+              + "              EnumerableTableAccessRel(table=[[TPCDS, STORE]]): rowcount = 12.0, cumulative cost = {12.0 rows, 13.0 cpu, 0.0 io}\n"
+              + "              EnumerableJoinRel(condition=[=($0, $50)], joinType=[inner]): rowcount = 1.2782445881607E13, cumulative cost = {1.279800620431134E13 rows, 3044505.0 cpu, 0.0 io}\n"
+              + "                EnumerableFilterRel(condition=[=(CAST($15):VARCHAR(6) CHARACTER SET \"ISO-8859-1\" COLLATE \"ISO-8859-1$en_US$primary\", '1998Q1')]): rowcount = 10957.35, cumulative cost = {84006.35 rows, 146099.0 cpu, 0.0 io}\n"
+              + "                  EnumerableTableAccessRel(table=[[TPCDS, DATE_DIM]]): rowcount = 73049.0, cumulative cost = {73049.0 rows, 73050.0 cpu, 0.0 io}\n"
+              + "                EnumerableJoinRel(condition=[=($0, $24)], joinType=[inner]): rowcount = 7.7770908E9, cumulative cost = {7.783045975286664E9 rows, 2898406.0 cpu, 0.0 io}\n"
+              + "                  EnumerableTableAccessRel(table=[[TPCDS, ITEM]]): rowcount = 18000.0, cumulative cost = {18000.0 rows, 18001.0 cpu, 0.0 io}\n"
+              + "                  EnumerableTableAccessRel(table=[[TPCDS, STORE_SALES]]): rowcount = 2880404.0, cumulative cost = {2880404.0 rows, 2880405.0 cpu, 0.0 io}\n"
+              + "            EnumerableJoinRel(condition=[AND(=($31, $79), =($30, $91))], joinType=[inner]): rowcount = 6.9978029381741304E16, cumulative cost = {6.9978054204658736E16 rows, 2021262.0 cpu, 0.0 io}\n"
+              + "              EnumerableJoinRel(condition=[=($28, $0)], joinType=[inner]): rowcount = 7.87597881975E8, cumulative cost = {7.884434222216867E8 rows, 433614.0 cpu, 0.0 io}\n"
+              + "                EnumerableFilterRel(condition=[OR(=($15, '1998Q1'), =($15, '1998Q2'), =($15, '1998Q3'))]): rowcount = 18262.25, cumulative cost = {91311.25 rows, 146099.0 cpu, 0.0 io}\n"
+              + "                  EnumerableTableAccessRel(table=[[TPCDS, DATE_DIM]]): rowcount = 73049.0, cumulative cost = {73049.0 rows, 73050.0 cpu, 0.0 io}\n"
+              + "                EnumerableTableAccessRel(table=[[TPCDS, STORE_RETURNS]]): rowcount = 287514.0, cumulative cost = {287514.0 rows, 287515.0 cpu, 0.0 io}\n"
+              + "              EnumerableJoinRel(condition=[=($28, $0)], joinType=[inner]): rowcount = 3.94888649445E9, cumulative cost = {3.9520401026966867E9 rows, 1587648.0 cpu, 0.0 io}\n"
+              + "                EnumerableFilterRel(condition=[OR(=($15, '1998Q1'), =($15, '1998Q2'), =($15, '1998Q3'))]): rowcount = 18262.25, cumulative cost = {91311.25 rows, 146099.0 cpu, 0.0 io}\n"
+              + "                  EnumerableTableAccessRel(table=[[TPCDS, DATE_DIM]]): rowcount = 73049.0, cumulative cost = {73049.0 rows, 73050.0 cpu, 0.0 io}\n"
+              + "                EnumerableTableAccessRel(table=[[TPCDS, CATALOG_SALES]]): rowcount = 1441548.0, cumulative cost = {1441548.0 rows, 1441549.0 cpu, 0.0 io}\n"));
+    } finally {
+      closeable.close();
+    }
   }
 
   @Test public void testQuery58() {
-    checkQuery(58).explainContains("xx").runs();
+    checkQuery(58).explainContains("PLAN").runs();
   }
 
   @Ignore("takes too long to optimize")


[7/7] git commit: Add test case for [DRILL-1199] Order by nested inside a where clause fails

Posted by jh...@apache.org.
Add test case for [DRILL-1199] Order by nested inside a where clause fails


Project: http://git-wip-us.apache.org/repos/asf/incubator-optiq/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-optiq/commit/d861a088
Tree: http://git-wip-us.apache.org/repos/asf/incubator-optiq/tree/d861a088
Diff: http://git-wip-us.apache.org/repos/asf/incubator-optiq/diff/d861a088

Branch: refs/heads/master
Commit: d861a0887f92caf70b045c4a29cc9960589838e5
Parents: b0aff0d
Author: Julian Hyde <jh...@apache.org>
Authored: Mon Jul 28 11:44:57 2014 -0700
Committer: Julian Hyde <jh...@apache.org>
Committed: Mon Jul 28 11:44:57 2014 -0700

----------------------------------------------------------------------
 core/src/test/resources/sql/misc.oq | 11 +++++++++++
 1 file changed, 11 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-optiq/blob/d861a088/core/src/test/resources/sql/misc.oq
----------------------------------------------------------------------
diff --git a/core/src/test/resources/sql/misc.oq b/core/src/test/resources/sql/misc.oq
index 21711f6..aaa7f83 100644
--- a/core/src/test/resources/sql/misc.oq
+++ b/core/src/test/resources/sql/misc.oq
@@ -76,6 +76,17 @@ from "hr"."emps";
 
 !ok
 
+# [DRILL-1199] Order by nested inside a where clause fails
+# (Not that it's right, but Tableau does it.)
+select * from (select * from "hr"."emps" order by "empid") where (0=1);
++-------+--------+------+--------+------------+
+| empid | deptno | name | salary | commission |
++-------+--------+------+--------+------------+
++-------+--------+------+--------+------------+
+(0 rows)
+
+!ok
+
 # [OPTIQ-340] SqlToRelConverter fails with complex join condition
 select e."deptno", d."deptno"
 from "hr"."emps" as e