calcite-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From jh...@apache.org
Subject incubator-calcite git commit: [CALCITE-603] Metadata providers for size, memory, parallelism
Date Thu, 26 Feb 2015 04:51:54 GMT
Repository: incubator-calcite
Updated Branches:
  refs/heads/master 96cac4796 -> dacde8f2e


[CALCITE-603] Metadata providers for size, memory, parallelism

Add ImmutableNullableList.Builder.

Allow a metadata interface to have multiple methods, and a single metadata provider to implement multiple methods.


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

Branch: refs/heads/master
Commit: dacde8f2ee4abb0cc11f102b1373d5d41909d91a
Parents: 96cac47
Author: Julian Hyde <jhyde@apache.org>
Authored: Tue Feb 24 09:48:12 2015 -0800
Committer: Julian Hyde <jhyde@apache.org>
Committed: Wed Feb 25 16:28:13 2015 -0800

----------------------------------------------------------------------
 .../calcite/rel/metadata/BuiltInMetadata.java   |  92 ++++-
 .../metadata/DefaultRelMetadataProvider.java    |   3 +
 .../metadata/ReflectiveRelMetadataProvider.java | 197 ++++++++---
 .../calcite/rel/metadata/RelMdMemory.java       | 100 ++++++
 .../calcite/rel/metadata/RelMdParallelism.java  |  80 +++++
 .../apache/calcite/rel/metadata/RelMdSize.java  | 354 +++++++++++++++++++
 .../calcite/rel/metadata/RelMetadataQuery.java  | 121 +++++++
 .../apache/calcite/sql2rel/RelDecorrelator.java |   2 +-
 .../org/apache/calcite/util/BuiltInMethod.java  |  11 +
 .../calcite/util/ImmutableNullableList.java     |  87 +++++
 .../calcite/test/MultiJdbcSchemaJoinTest.java   |   2 +-
 .../apache/calcite/test/RelMetadataTest.java    | 172 ++++++++-
 .../java/org/apache/calcite/util/UtilTest.java  |  12 +
 13 files changed, 1165 insertions(+), 68 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/dacde8f2/core/src/main/java/org/apache/calcite/rel/metadata/BuiltInMetadata.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/rel/metadata/BuiltInMetadata.java b/core/src/main/java/org/apache/calcite/rel/metadata/BuiltInMetadata.java
index df641ec..16562f8 100644
--- a/core/src/main/java/org/apache/calcite/rel/metadata/BuiltInMetadata.java
+++ b/core/src/main/java/org/apache/calcite/rel/metadata/BuiltInMetadata.java
@@ -27,6 +27,7 @@ import org.apache.calcite.util.ImmutableBitSet;
 
 import com.google.common.collect.ImmutableList;
 
+import java.util.List;
 import java.util.Set;
 
 /**
@@ -174,6 +175,34 @@ public abstract class BuiltInMetadata {
     Double getPopulationSize(ImmutableBitSet groupKey);
   }
 
+  /** Metadata about the size of rows and columns. */
+  public interface Size extends Metadata {
+    /**
+     * Determines the average size (in bytes) of a row from this relational
+     * expression.
+     *
+     * @return average size of a row, in bytes, or null if not known
+     */
+    Double averageRowSize();
+
+    /**
+     * Determines the average size (in bytes) of a value of a column in this
+     * relational expression.
+     *
+     * <p>Null values are included (presumably they occupy close to 0 bytes).
+     *
+     * <p>It is left to the caller to decide whether the size is the compressed
+     * size, the uncompressed size, or memory allocation when the value is
+     * wrapped in an object in the Java heap. The uncompressed size is probably
+     * a good compromise.
+     *
+     * @return an immutable list containing, for each column, the average size
+     * of a column value, in bytes. Each value or the entire list may be null if
+     * the metadata is not available
+     */
+    List<Double> averageColumnSizes();
+  }
+
   /** Metadata about the origins of columns. */
   public interface ColumnOrigin extends Metadata {
     /**
@@ -247,10 +276,71 @@ public abstract class BuiltInMetadata {
     RelOptPredicateList getPredicates();
   }
 
+  /** Metadata about the degree of parallelism of a relational expression, and
+   * how its operators are assigned to processes with independent resource
+   * pools. */
+  public interface Parallelism extends Metadata {
+    /** Returns whether each physical operator implementing this relational
+     * expression belongs to a different process than its inputs.
+     *
+     * <p>A collection of operators processing all of the splits of a particular
+     * stage in the query pipeline is called a "phase". A phase starts with
+     * a leaf node such as a {@link org.apache.calcite.rel.core.TableScan},
+     * or with a phase-change node such as an
+     * {@link org.apache.calcite.rel.core.Exchange}. Hadoop's shuffle operator
+     * (a form of sort-exchange) causes data to be sent across the network. */
+    Boolean isPhaseTransition();
+
+    /** Returns the number of distinct splits of the data.
+     *
+     * <p>Note that splits must be distinct. For broadcast, where each copy is
+     * the same, returns 1.
+     *
+     * <p>Thus the split count is the <em>proportion</em> of the data seen by
+     * each operator instance.
+     */
+    Integer splitCount();
+  }
+
+  /** Metadata about the memory use of an operator. */
+  public interface Memory extends Metadata {
+    /** Returns the expected amount of memory, in bytes, required by a physical
+     * operator implementing this relational expression, across all splits.
+     *
+     * <p>How much memory is used depends very much on the algorithm; for
+     * example, an implementation of
+     * {@link org.apache.calcite.rel.core.Aggregate} that loads all data into a
+     * hash table requires approximately {@code rowCount * averageRowSize}
+     * bytes, whereas an implementation that assumes that the input is sorted
+     * requires only {@code averageRowSize} bytes to maintain a single
+     * accumulator for each aggregate function.
+     */
+    Double memory();
+
+    /** Returns the cumulative amount of memory, in bytes, required by the
+     * physical operator implementing this relational expression, and all other
+     * operators within the same phase, across all splits.
+     *
+     * @see org.apache.calcite.rel.metadata.BuiltInMetadata.Parallelism#splitCount()
+     */
+    Double cumulativeMemoryWithinPhase();
+
+    /** Returns the expected cumulative amount of memory, in bytes, required by
+     * the physical operator implementing this relational expression, and all
+     * operators within the same phase, within each split.
+     *
+     * <p>Basic formula:
+     *
+     * <blockquote>cumulativeMemoryWithinPhaseSplit
+     *     = cumulativeMemoryWithinPhase / Parallelism.splitCount</blockquote>
+     */
+    Double cumulativeMemoryWithinPhaseSplit();
+  }
+
   /** The built-in forms of metadata. */
   interface All extends Selectivity, UniqueKeys, RowCount, DistinctRowCount,
       PercentageOriginalRows, ColumnUniqueness, ColumnOrigin, Predicates,
-      Collation, Distribution {
+      Collation, Distribution, Size, Parallelism, Memory {
   }
 }
 

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/dacde8f2/core/src/main/java/org/apache/calcite/rel/metadata/DefaultRelMetadataProvider.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/rel/metadata/DefaultRelMetadataProvider.java b/core/src/main/java/org/apache/calcite/rel/metadata/DefaultRelMetadataProvider.java
index 8078af2..a844d61 100644
--- a/core/src/main/java/org/apache/calcite/rel/metadata/DefaultRelMetadataProvider.java
+++ b/core/src/main/java/org/apache/calcite/rel/metadata/DefaultRelMetadataProvider.java
@@ -41,6 +41,9 @@ public class DefaultRelMetadataProvider extends ChainedRelMetadataProvider {
             RelMdUniqueKeys.SOURCE,
             RelMdColumnUniqueness.SOURCE,
             RelMdPopulationSize.SOURCE,
+            RelMdSize.SOURCE,
+            RelMdParallelism.SOURCE,
+            RelMdMemory.SOURCE,
             RelMdDistinctRowCount.SOURCE,
             RelMdSelectivity.SOURCE,
             RelMdExplainVisibility.SOURCE,

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/dacde8f2/core/src/main/java/org/apache/calcite/rel/metadata/ReflectiveRelMetadataProvider.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/rel/metadata/ReflectiveRelMetadataProvider.java b/core/src/main/java/org/apache/calcite/rel/metadata/ReflectiveRelMetadataProvider.java
index 8cf2996..5597e6b 100644
--- a/core/src/main/java/org/apache/calcite/rel/metadata/ReflectiveRelMetadataProvider.java
+++ b/core/src/main/java/org/apache/calcite/rel/metadata/ReflectiveRelMetadataProvider.java
@@ -18,12 +18,16 @@ package org.apache.calcite.rel.metadata;
 
 import org.apache.calcite.rel.RelNode;
 import org.apache.calcite.util.BuiltInMethod;
+import org.apache.calcite.util.ImmutableNullableList;
+import org.apache.calcite.util.Pair;
 import org.apache.calcite.util.ReflectiveVisitor;
 import org.apache.calcite.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.Maps;
+import com.google.common.collect.Sets;
 
 import java.lang.reflect.InvocationHandler;
 import java.lang.reflect.Method;
@@ -31,7 +35,10 @@ import java.lang.reflect.Modifier;
 import java.lang.reflect.Proxy;
 import java.util.Arrays;
 import java.util.Comparator;
+import java.util.Iterator;
+import java.util.List;
 import java.util.Map;
+import java.util.TreeSet;
 
 /**
  * Implementation of the {@link RelMetadataProvider} interface that dispatches
@@ -86,73 +93,111 @@ public class ReflectiveRelMetadataProvider
    * <blockquote><pre><code>
    * class RelMdSelectivity {
    *   public Double getSelectivity(Union rel, RexNode predicate) { }
-   *   public Double getSelectivity(LogicalFilter rel, RexNode predicate) { }
+   *   public Double getSelectivity(Filter rel, RexNode predicate) { }
    * </code></pre></blockquote>
    *
    * <p>provides implementations of selectivity for relational expressions
-   * that extend {@link org.apache.calcite.rel.logical.LogicalUnion}
-   * or {@link org.apache.calcite.rel.logical.LogicalFilter}.</p>
+   * that extend {@link org.apache.calcite.rel.core.Union}
+   * or {@link org.apache.calcite.rel.core.Filter}.</p>
    */
   public static RelMetadataProvider reflectiveSource(Method method,
-      final Object target) {
-    final Class<?> metadataClass0 = method.getDeclaringClass();
+      Object target) {
+    return reflectiveSource(target, ImmutableList.of(method));
+  }
+
+  /** Returns a reflective metadata provider that implements several
+   * methods. */
+  public static RelMetadataProvider reflectiveSource(Object target,
+      Method... methods) {
+    return reflectiveSource(target, ImmutableList.copyOf(methods));
+  }
+
+  private static RelMetadataProvider reflectiveSource(final Object target,
+      final ImmutableList<Method> methods) {
+    assert methods.size() > 0;
+    final Method method0 = methods.get(0);
+    final Class<?> metadataClass0 = method0.getDeclaringClass();
     assert Metadata.class.isAssignableFrom(metadataClass0);
-    final Map<Class<RelNode>, Function<RelNode, Metadata>> treeMap =
-        Maps.<Class<RelNode>, Class<RelNode>, Function<RelNode, Metadata>>
-            newTreeMap(SUPERCLASS_COMPARATOR);
-    for (final Method method1 : target.getClass().getMethods()) {
-      if (method1.getName().equals(method.getName())
-          && (method1.getModifiers() & Modifier.STATIC) == 0
-          && (method1.getModifiers() & Modifier.PUBLIC) != 0) {
-        final Class<?>[] parameterTypes1 = method1.getParameterTypes();
-        final Class<?>[] parameterTypes = method.getParameterTypes();
-        if (parameterTypes1.length == parameterTypes.length + 1
-            && RelNode.class.isAssignableFrom(parameterTypes1[0])
-            && Util.skip(Arrays.asList(parameterTypes1))
-                .equals(Arrays.asList(parameterTypes))) {
-          //noinspection unchecked
-          final Class<RelNode> key = (Class) parameterTypes1[0];
-          final Function<RelNode, Metadata> function =
-              new Function<RelNode, Metadata>() {
-                public Metadata apply(final RelNode rel) {
-                  return (Metadata) Proxy.newProxyInstance(
-                      metadataClass0.getClassLoader(),
-                      new Class[]{metadataClass0},
-                      new InvocationHandler() {
-                        public Object invoke(Object proxy, Method method,
-                            Object[] args) throws Throwable {
-                          // Suppose we are an implementation of Selectivity
-                          // that wraps "filter", a LogicalFilter. Then we
-                          // implement
-                          //   Selectivity.selectivity(rex)
-                          // by calling method
-                          //   new SelectivityImpl().selectivity(filter, rex)
-                          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};
-                          } else {
-                            args1 = new Object[args.length + 1];
-                            args1[0] = rel;
-                            System.arraycopy(args, 0, args1, 1, args.length);
-                          }
-                          return method1.invoke(target, args1);
-                        }
-                      });
-                }
-              };
-          treeMap.put(key, function);
+    for (Method method : methods) {
+      assert method.getDeclaringClass() == metadataClass0;
+    }
+
+    // Find the distinct set of RelNode classes handled by this provider,
+    // ordered base-class first.
+    final TreeSet<Class<RelNode>> classes =
+        Sets.newTreeSet(SUPERCLASS_COMPARATOR);
+    final Map<Pair<Class<RelNode>, Method>, Method> handlerMap =
+        Maps.newHashMap();
+    for (final Method handlerMethod : target.getClass().getMethods()) {
+      for (Method method : methods) {
+        if (couldImplement(handlerMethod, method)) {
+          @SuppressWarnings("unchecked") final Class<RelNode> relNodeClass =
+              (Class<RelNode>) handlerMethod.getParameterTypes()[0];
+          classes.add(relNodeClass);
+          handlerMap.put(Pair.of(relNodeClass, method), handlerMethod);
         }
       }
     }
+
+    final Map<Class<RelNode>, Function<RelNode, Metadata>> treeMap =
+        Maps.newTreeMap(SUPERCLASS_COMPARATOR);
+
+    for (Class<RelNode> key : classes) {
+      ImmutableNullableList.Builder<Method> builder =
+          ImmutableNullableList.builder();
+      for (final Method method : methods) {
+        builder.add(find(classes, handlerMap, key, method));
+      }
+      final List<Method> handlerMethods = builder.build();
+      final Function<RelNode, Metadata> function =
+          new Function<RelNode, Metadata>() {
+            public Metadata apply(final RelNode rel) {
+              return (Metadata) Proxy.newProxyInstance(
+                  metadataClass0.getClassLoader(),
+                  new Class[]{metadataClass0},
+                  new InvocationHandler() {
+                    public Object invoke(Object proxy, Method method,
+                        Object[] args) throws Throwable {
+                      // Suppose we are an implementation of Selectivity
+                      // that wraps "filter", a LogicalFilter. Then we
+                      // implement
+                      //   Selectivity.selectivity(rex)
+                      // by calling method
+                      //   new SelectivityImpl().selectivity(filter, rex)
+                      if (method.equals(
+                          BuiltInMethod.METADATA_REL.method)) {
+                        return rel;
+                      }
+                      if (method.equals(
+                          BuiltInMethod.OBJECT_TO_STRING.method)) {
+                        return metadataClass0.getSimpleName() + "(" + rel
+                            + ")";
+                      }
+                      int i = methods.indexOf(method);
+                      if (i < 0) {
+                        throw new AssertionError("not handled: " + method
+                            + " for " + rel);
+                      }
+                      final Object[] args1;
+                      if (args == null) {
+                        args1 = new Object[]{rel};
+                      } else {
+                        args1 = new Object[args.length + 1];
+                        args1[0] = rel;
+                        System.arraycopy(args, 0, args1, 1, args.length);
+                      }
+                      final Method handlerMethod = handlerMethods.get(i);
+                      if (handlerMethod == null) {
+                        throw new AssertionError("not handled: " + method
+                            + " for " + rel);
+                      }
+                      return handlerMethod.invoke(target, args1);
+                    }
+                  });
+            }
+          };
+      treeMap.put(key, function);
+    }
     // Due to the comparator, the TreeMap is sorted such that any derived class
     // will occur before its base class. The immutable map is not a sorted map,
     // but it retains the traversal order, and that is sufficient.
@@ -161,6 +206,42 @@ public class ReflectiveRelMetadataProvider
     return new ReflectiveRelMetadataProvider(map, metadataClass0);
   }
 
+  /** Finds an implementation of a method for {@code relNodeClass} or its
+   * nearest base class. Assumes that base classes have already been added to
+   * {@code map}. */
+  private static Method find(TreeSet<Class<RelNode>> classes,
+      Map<Pair<Class<RelNode>, Method>, Method> handlerMap,
+      Class<RelNode> relNodeClass, Method method) {
+    final Iterator<Class<RelNode>> iterator = classes.descendingIterator();
+    for (;;) {
+      final Method implementingMethod =
+          handlerMap.get(Pair.of(relNodeClass, method));
+      if (implementingMethod != null) {
+        return implementingMethod;
+      }
+      if (!iterator.hasNext()) {
+        return null;
+      }
+      relNodeClass = iterator.next();
+    }
+  }
+
+  private static boolean couldImplement(Method handlerMethod, Method method) {
+    if (!handlerMethod.getName().equals(method.getName())
+        || (handlerMethod.getModifiers() & Modifier.STATIC) != 0
+        || (handlerMethod.getModifiers() & Modifier.PUBLIC) == 0) {
+      return false;
+    }
+    final Class<?>[] parameterTypes1 = handlerMethod.getParameterTypes();
+    final Class<?>[] parameterTypes = method.getParameterTypes();
+    if (parameterTypes1.length != parameterTypes.length + 1
+        || !RelNode.class.isAssignableFrom(parameterTypes1[0])) {
+      return false;
+    }
+    return Util.skip(Arrays.asList(parameterTypes1))
+        .equals(Arrays.asList(parameterTypes));
+  }
+
   //~ Methods ----------------------------------------------------------------
 
   public Function<RelNode, Metadata> apply(

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/dacde8f2/core/src/main/java/org/apache/calcite/rel/metadata/RelMdMemory.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/rel/metadata/RelMdMemory.java b/core/src/main/java/org/apache/calcite/rel/metadata/RelMdMemory.java
new file mode 100644
index 0000000..31bf076
--- /dev/null
+++ b/core/src/main/java/org/apache/calcite/rel/metadata/RelMdMemory.java
@@ -0,0 +1,100 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.calcite.rel.metadata;
+
+import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.util.BuiltInMethod;
+
+/**
+ * Default implementations of the
+ * {@link org.apache.calcite.rel.metadata.BuiltInMetadata.Memory}
+ * metadata provider for the standard logical algebra.
+ *
+ * @see RelMetadataQuery#isPhaseTransition
+ * @see RelMetadataQuery#splitCount
+ */
+public class RelMdMemory {
+  /** Source for
+   * {@link org.apache.calcite.rel.metadata.BuiltInMetadata.Memory}. */
+  public static final RelMetadataProvider SOURCE =
+      ReflectiveRelMetadataProvider.reflectiveSource(new RelMdMemory(),
+          BuiltInMethod.MEMORY.method,
+          BuiltInMethod.CUMULATIVE_MEMORY_WITHIN_PHASE.method,
+          BuiltInMethod.CUMULATIVE_MEMORY_WITHIN_PHASE_SPLIT.method);
+
+  //~ Constructors -----------------------------------------------------------
+
+  private RelMdMemory() {}
+
+  //~ Methods ----------------------------------------------------------------
+
+  /** Catch-all implementation for
+   * {@link org.apache.calcite.rel.metadata.BuiltInMetadata.Memory#memory()},
+   * invoked using reflection.
+   *
+   * @see org.apache.calcite.rel.metadata.RelMetadataQuery#memory
+   */
+  public Double memory(RelNode rel) {
+    return null;
+  }
+
+  /** Catch-all implementation for
+   * {@link org.apache.calcite.rel.metadata.BuiltInMetadata.Memory#cumulativeMemoryWithinPhase()},
+   * invoked using reflection.
+   *
+   * @see org.apache.calcite.rel.metadata.RelMetadataQuery#memory
+   */
+  public Double cumulativeMemoryWithinPhase(RelNode rel) {
+    Double nullable = RelMetadataQuery.memory(rel);
+    if (nullable == null) {
+      return null;
+    }
+    Boolean isPhaseTransition = RelMetadataQuery.isPhaseTransition(rel);
+    if (isPhaseTransition == null) {
+      return null;
+    }
+    double d = nullable;
+    if (!isPhaseTransition) {
+      for (RelNode input : rel.getInputs()) {
+        nullable = RelMetadataQuery.cumulativeMemoryWithinPhase(input);
+        if (nullable == null) {
+          return null;
+        }
+        d += nullable;
+      }
+    }
+    return d;
+  }
+
+  /** Catch-all implementation for
+   * {@link org.apache.calcite.rel.metadata.BuiltInMetadata.Memory#cumulativeMemoryWithinPhaseSplit()},
+   * invoked using reflection.
+   *
+   * @see org.apache.calcite.rel.metadata.RelMetadataQuery#cumulativeMemoryWithinPhaseSplit
+   */
+  public Double cumulativeMemoryWithinPhaseSplit(RelNode rel) {
+    final Double memoryWithinPhase =
+        RelMetadataQuery.cumulativeMemoryWithinPhase(rel);
+    final Integer splitCount = RelMetadataQuery.splitCount(rel);
+    if (memoryWithinPhase == null || splitCount == null) {
+      return null;
+    }
+    return memoryWithinPhase / splitCount;
+  }
+}
+
+// End RelMdMemory.java

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/dacde8f2/core/src/main/java/org/apache/calcite/rel/metadata/RelMdParallelism.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/rel/metadata/RelMdParallelism.java b/core/src/main/java/org/apache/calcite/rel/metadata/RelMdParallelism.java
new file mode 100644
index 0000000..7b18b77
--- /dev/null
+++ b/core/src/main/java/org/apache/calcite/rel/metadata/RelMdParallelism.java
@@ -0,0 +1,80 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.calcite.rel.metadata;
+
+import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.core.Exchange;
+import org.apache.calcite.rel.core.TableScan;
+import org.apache.calcite.rel.core.Values;
+import org.apache.calcite.util.BuiltInMethod;
+
+/**
+ * Default implementations of the
+ * {@link org.apache.calcite.rel.metadata.BuiltInMetadata.Parallelism}
+ * metadata provider for the standard logical algebra.
+ *
+ * @see org.apache.calcite.rel.metadata.RelMetadataQuery#isPhaseTransition
+ * @see org.apache.calcite.rel.metadata.RelMetadataQuery#splitCount
+ */
+public class RelMdParallelism {
+  /** Source for
+   * {@link org.apache.calcite.rel.metadata.BuiltInMetadata.Parallelism}. */
+  public static final RelMetadataProvider SOURCE =
+      ReflectiveRelMetadataProvider.reflectiveSource(new RelMdParallelism(),
+          BuiltInMethod.IS_PHASE_TRANSITION.method,
+          BuiltInMethod.SPLIT_COUNT.method);
+
+  //~ Constructors -----------------------------------------------------------
+
+  private RelMdParallelism() {}
+
+  //~ Methods ----------------------------------------------------------------
+
+  /** Catch-all implementation for
+   * {@link org.apache.calcite.rel.metadata.BuiltInMetadata.Parallelism#isPhaseTransition()},
+   * invoked using reflection.
+   *
+   * @see org.apache.calcite.rel.metadata.RelMetadataQuery#isPhaseTransition
+   */
+  public Boolean isPhaseTransition(RelNode rel) {
+    return false;
+  }
+
+  public Boolean isPhaseTransition(TableScan rel) {
+    return true;
+  }
+
+  public Boolean isPhaseTransition(Values rel) {
+    return true;
+  }
+
+  public Boolean isPhaseTransition(Exchange rel) {
+    return true;
+  }
+
+  /** Catch-all implementation for
+   * {@link org.apache.calcite.rel.metadata.BuiltInMetadata.Parallelism#splitCount()},
+   * invoked using reflection.
+   *
+   * @see org.apache.calcite.rel.metadata.RelMetadataQuery#splitCount
+   */
+  public Integer splitCount(RelNode rel) {
+    return 1;
+  }
+}
+
+// End RelMdParallelism.java

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/dacde8f2/core/src/main/java/org/apache/calcite/rel/metadata/RelMdSize.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/rel/metadata/RelMdSize.java b/core/src/main/java/org/apache/calcite/rel/metadata/RelMdSize.java
new file mode 100644
index 0000000..65ffbbe
--- /dev/null
+++ b/core/src/main/java/org/apache/calcite/rel/metadata/RelMdSize.java
@@ -0,0 +1,354 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.calcite.rel.metadata;
+
+import org.apache.calcite.avatica.util.ByteString;
+import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.core.Aggregate;
+import org.apache.calcite.rel.core.AggregateCall;
+import org.apache.calcite.rel.core.Exchange;
+import org.apache.calcite.rel.core.Filter;
+import org.apache.calcite.rel.core.Intersect;
+import org.apache.calcite.rel.core.Join;
+import org.apache.calcite.rel.core.Minus;
+import org.apache.calcite.rel.core.Project;
+import org.apache.calcite.rel.core.Sort;
+import org.apache.calcite.rel.core.TableScan;
+import org.apache.calcite.rel.core.Union;
+import org.apache.calcite.rel.core.Values;
+import org.apache.calcite.rel.type.RelDataType;
+import org.apache.calcite.rel.type.RelDataTypeField;
+import org.apache.calcite.rex.RexCall;
+import org.apache.calcite.rex.RexInputRef;
+import org.apache.calcite.rex.RexLiteral;
+import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.util.BuiltInMethod;
+import org.apache.calcite.util.ImmutableNullableList;
+import org.apache.calcite.util.NlsString;
+import org.apache.calcite.util.Pair;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+
+import java.util.List;
+
+/**
+ * Default implementations of the
+ * {@link org.apache.calcite.rel.metadata.BuiltInMetadata.Size}
+ * metadata provider for the standard logical algebra.
+ *
+ * @see RelMetadataQuery#getAverageRowSize
+ * @see RelMetadataQuery#getAverageColumnSizes
+ * @see RelMetadataQuery#getAverageColumnSizesNotNull
+ */
+public class RelMdSize {
+  /** Source for
+   * {@link org.apache.calcite.rel.metadata.BuiltInMetadata.Size}. */
+  public static final RelMetadataProvider SOURCE =
+      ReflectiveRelMetadataProvider.reflectiveSource(new RelMdSize(),
+          BuiltInMethod.AVERAGE_COLUMN_SIZES.method,
+          BuiltInMethod.AVERAGE_ROW_SIZE.method);
+
+  /** Bytes per character (2). */
+  public static final int BYTES_PER_CHARACTER = Character.SIZE / Byte.SIZE;
+
+  //~ Constructors -----------------------------------------------------------
+
+  private RelMdSize() {}
+
+  //~ Methods ----------------------------------------------------------------
+
+  /** Catch-all implementation for
+   * {@link org.apache.calcite.rel.metadata.BuiltInMetadata.Size#averageRowSize()},
+   * invoked using reflection.
+   *
+   * @see org.apache.calcite.rel.metadata.RelMetadataQuery#getAverageRowSize
+   */
+  public Double averageRowSize(RelNode rel) {
+    final List<Double> averageColumnSizes =
+        RelMetadataQuery.getAverageColumnSizes(rel);
+    if (averageColumnSizes == null) {
+      return null;
+    }
+    Double d = 0d;
+    final List<RelDataTypeField> fields = rel.getRowType().getFieldList();
+    for (Pair<Double, RelDataTypeField> p
+        : Pair.zip(averageColumnSizes, fields)) {
+      if (p.left == null) {
+        d += averageFieldValueSize(p.right);
+      } else {
+        d += p.left;
+      }
+    }
+    return d;
+  }
+
+  /** Catch-all implementation for
+   * {@link org.apache.calcite.rel.metadata.BuiltInMetadata.Size#averageColumnSizes()},
+   * invoked using reflection.
+   *
+   * @see org.apache.calcite.rel.metadata.RelMetadataQuery#getAverageColumnSizes
+   */
+  public List<Double> averageColumnSizes(RelNode rel) {
+    return null; // absolutely no idea
+  }
+
+  public List<Double> averageColumnSizes(Filter rel) {
+    return RelMetadataQuery.getAverageColumnSizes(rel.getInput());
+  }
+
+  public List<Double> averageColumnSizes(Sort rel) {
+    return RelMetadataQuery.getAverageColumnSizes(rel.getInput());
+  }
+
+  public List<Double> averageColumnSizes(Exchange rel) {
+    return RelMetadataQuery.getAverageColumnSizes(rel.getInput());
+  }
+
+  public List<Double> averageColumnSizes(Project rel) {
+    final List<Double> inputColumnSizes =
+        RelMetadataQuery.getAverageColumnSizesNotNull(rel.getInput());
+    final ImmutableNullableList.Builder<Double> sizes =
+        ImmutableNullableList.builder();
+    for (RexNode project : rel.getProjects()) {
+      sizes.add(averageRexSize(project, inputColumnSizes));
+    }
+    return sizes.build();
+  }
+
+  public List<Double> averageColumnSizes(Values rel) {
+    final List<RelDataTypeField> fields = rel.getRowType().getFieldList();
+    final ImmutableList.Builder<Double> list = ImmutableList.builder();
+    for (int i = 0; i < fields.size(); i++) {
+      RelDataTypeField field = fields.get(i);
+      double d;
+      if (rel.getTuples().isEmpty()) {
+        d = averageTypeValueSize(field.getType());
+      } else {
+        d = 0;
+        for (ImmutableList<RexLiteral> literals : rel.getTuples()) {
+          d += typeValueSize(field.getType(), literals.get(i).getValue());
+        }
+        d /= rel.getTuples().size();
+      }
+      list.add(d);
+    }
+    return list.build();
+  }
+
+  public List<Double> averageColumnSizes(TableScan rel) {
+    final List<RelDataTypeField> fields = rel.getRowType().getFieldList();
+    final ImmutableList.Builder<Double> list = ImmutableList.builder();
+    for (RelDataTypeField field : fields) {
+      list.add(averageTypeValueSize(field.getType()));
+    }
+    return list.build();
+  }
+
+  public List<Double> averageColumnSizes(Aggregate rel) {
+    final List<Double> inputColumnSizes =
+        RelMetadataQuery.getAverageColumnSizesNotNull(rel.getInput());
+    final ImmutableList.Builder<Double> list = ImmutableList.builder();
+    for (int key : rel.getGroupSet()) {
+      list.add(inputColumnSizes.get(key));
+    }
+    for (AggregateCall aggregateCall : rel.getAggCallList()) {
+      list.add(averageTypeValueSize(aggregateCall.type));
+    }
+    return list.build();
+  }
+
+  public List<Double> averageColumnSizes(Join rel) {
+    final RelNode left = rel.getLeft();
+    final RelNode right = rel.getRight();
+    final List<Double> lefts =
+        RelMetadataQuery.getAverageColumnSizes(left);
+    final List<Double> rights =
+        RelMetadataQuery.getAverageColumnSizes(right);
+    if (lefts == null && rights == null) {
+      return null;
+    }
+    final int fieldCount = rel.getRowType().getFieldCount();
+    Double[] sizes = new Double[fieldCount];
+    if (lefts != null) {
+      lefts.toArray(sizes);
+    }
+    if (rights != null) {
+      final int leftCount = left.getRowType().getFieldCount();
+      for (int i = 0; i < rights.size(); i++) {
+        sizes[leftCount + i] = rights.get(i);
+      }
+    }
+    return ImmutableNullableList.copyOf(sizes);
+  }
+
+  public List<Double> averageColumnSizes(Intersect rel) {
+    return RelMetadataQuery.getAverageColumnSizes(rel.getInput(0));
+  }
+
+  public List<Double> averageColumnSizes(Minus rel) {
+    return RelMetadataQuery.getAverageColumnSizes(rel.getInput(0));
+  }
+
+  public List<Double> averageColumnSizes(Union rel) {
+    final int fieldCount = rel.getRowType().getFieldCount();
+    List<List<Double>> inputColumnSizeList = Lists.newArrayList();
+    for (RelNode input : rel.getInputs()) {
+      final List<Double> inputSizes =
+          RelMetadataQuery.getAverageColumnSizes(input);
+      if (inputSizes != null) {
+        inputColumnSizeList.add(inputSizes);
+      }
+    }
+    switch (inputColumnSizeList.size()) {
+    case 0:
+      return null; // all were null
+    case 1:
+      return inputColumnSizeList.get(0); // all but one were null
+    }
+    final ImmutableNullableList.Builder<Double> sizes =
+        ImmutableNullableList.builder();
+    int nn = 0;
+    for (int i = 0; i < fieldCount; i++) {
+      double d = 0d;
+      int n = 0;
+      for (List<Double> inputColumnSizes : inputColumnSizeList) {
+        Double d2 = inputColumnSizes.get(i);
+        if (d2 != null) {
+          d += d2;
+          ++n;
+          ++nn;
+        }
+      }
+      sizes.add(n > 0 ? d / n : null);
+    }
+    if (nn == 0) {
+      return null; // all columns are null
+    }
+    return sizes.build();
+  }
+
+  /** Estimates the average size (in bytes) of a value of a field, knowing
+   * nothing more than its type.
+   *
+   * <p>We assume that the proportion of nulls is negligible, even if the field
+   * is nullable.
+   */
+  protected Double averageFieldValueSize(RelDataTypeField field) {
+    return averageTypeValueSize(field.getType());
+  }
+
+  /** Estimates the average size (in bytes) of a value of a type.
+   *
+   * <p>We assume that the proportion of nulls is negligible, even if the type
+   * is nullable.
+   */
+  public Double averageTypeValueSize(RelDataType type) {
+    switch (type.getSqlTypeName()) {
+    case BOOLEAN:
+    case TINYINT:
+      return 1d;
+    case SMALLINT:
+      return 2d;
+    case INTEGER:
+    case FLOAT:
+    case REAL:
+    case DATE:
+    case TIME:
+      return 4d;
+    case BIGINT:
+    case DOUBLE:
+    case TIMESTAMP:
+    case INTERVAL_DAY_TIME:
+    case INTERVAL_YEAR_MONTH:
+      return 8d;
+    case BINARY:
+      return (double) type.getPrecision();
+    case VARBINARY:
+      return Math.min((double) type.getPrecision(), 100d);
+    case CHAR:
+      return (double) type.getPrecision() * BYTES_PER_CHARACTER;
+    case VARCHAR:
+      // Even in large (say VARCHAR(2000)) columns most strings are small
+      return Math.min((double) type.getPrecision() * BYTES_PER_CHARACTER, 100d);
+    default:
+      return null;
+    }
+  }
+
+  /** Estimates the average size (in bytes) of a value of a type.
+   *
+   * <p>Nulls count as 1 byte.
+   */
+  public double typeValueSize(RelDataType type, Comparable value) {
+    if (value == null) {
+      return 1d;
+    }
+    switch (type.getSqlTypeName()) {
+    case BOOLEAN:
+    case TINYINT:
+      return 1d;
+    case SMALLINT:
+      return 2d;
+    case INTEGER:
+    case FLOAT:
+    case REAL:
+    case DATE:
+    case TIME:
+      return 4d;
+    case BIGINT:
+    case DOUBLE:
+    case TIMESTAMP:
+    case INTERVAL_DAY_TIME:
+    case INTERVAL_YEAR_MONTH:
+      return 8d;
+    case BINARY:
+    case VARBINARY:
+      return ((ByteString) value).length();
+    case CHAR:
+    case VARCHAR:
+      return ((NlsString) value).getValue().length() * BYTES_PER_CHARACTER;
+    default:
+      return 32;
+    }
+  }
+
+  public Double averageRexSize(RexNode node, List<Double> inputColumnSizes) {
+    switch (node.getKind()) {
+    case INPUT_REF:
+      return inputColumnSizes.get(((RexInputRef) node).getIndex());
+    case LITERAL:
+      return typeValueSize(node.getType(), ((RexLiteral) node).getValue());
+    default:
+      if (node instanceof RexCall) {
+        RexCall call = (RexCall) node;
+        for (RexNode operand : call.getOperands()) {
+          // It's a reasonable assumption that a function's result will have
+          // similar size to its argument of a similar type. For example,
+          // UPPER(c) has the same average size as c.
+          if (operand.getType().getSqlTypeName()
+              == node.getType().getSqlTypeName()) {
+            return averageRexSize(operand, inputColumnSizes);
+          }
+        }
+      }
+      return averageTypeValueSize(node.getType());
+    }
+  }
+}
+
+// End RelMdSize.java

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/dacde8f2/core/src/main/java/org/apache/calcite/rel/metadata/RelMetadataQuery.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/rel/metadata/RelMetadataQuery.java b/core/src/main/java/org/apache/calcite/rel/metadata/RelMetadataQuery.java
index ae3d0e8..8ce34e2 100644
--- a/core/src/main/java/org/apache/calcite/rel/metadata/RelMetadataQuery.java
+++ b/core/src/main/java/org/apache/calcite/rel/metadata/RelMetadataQuery.java
@@ -29,6 +29,8 @@ import org.apache.calcite.util.ImmutableBitSet;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
 
+import java.util.Collections;
+import java.util.List;
 import java.util.Set;
 
 /**
@@ -330,6 +332,125 @@ public abstract class RelMetadataQuery {
 
   /**
    * Returns the
+   * {@link org.apache.calcite.rel.metadata.BuiltInMetadata.Size#averageRowSize()}
+   * statistic.
+   *
+   * @param rel      the relational expression
+   * @return average size of a row, in bytes, or null if not known
+     */
+  public static Double getAverageRowSize(RelNode rel) {
+    final BuiltInMetadata.Size metadata =
+        rel.metadata(BuiltInMetadata.Size.class);
+    return metadata.averageRowSize();
+  }
+
+  /**
+   * Returns the
+   * {@link org.apache.calcite.rel.metadata.BuiltInMetadata.Size#averageColumnSizes()}
+   * statistic.
+   *
+   * @param rel      the relational expression
+   * @return a list containing, for each column, the average size of a column
+   * value, in bytes. Each value or the entire list may be null if the
+   * metadata is not available
+   */
+  public static List<Double> getAverageColumnSizes(RelNode rel) {
+    final BuiltInMetadata.Size metadata =
+        rel.metadata(BuiltInMetadata.Size.class);
+    return metadata.averageColumnSizes();
+  }
+
+  /** As {@link #getAverageColumnSizes(org.apache.calcite.rel.RelNode)} but
+   * never returns a null list, only ever a list of nulls. */
+  public static List<Double> getAverageColumnSizesNotNull(RelNode rel) {
+    final BuiltInMetadata.Size metadata =
+        rel.metadata(BuiltInMetadata.Size.class);
+    final List<Double> averageColumnSizes = metadata.averageColumnSizes();
+    return averageColumnSizes == null
+        ? Collections.<Double>nCopies(rel.getRowType().getFieldCount(), null)
+        : averageColumnSizes;
+  }
+
+  /**
+   * Returns the
+   * {@link org.apache.calcite.rel.metadata.BuiltInMetadata.Parallelism#isPhaseTransition()}
+   * statistic.
+   *
+   * @param rel      the relational expression
+   * @return whether each physical operator implementing this relational
+   * expression belongs to a different process than its inputs, or null if not
+   * known
+   */
+  public static Boolean isPhaseTransition(RelNode rel) {
+    final BuiltInMetadata.Parallelism metadata =
+        rel.metadata(BuiltInMetadata.Parallelism.class);
+    return metadata.isPhaseTransition();
+  }
+
+  /**
+   * Returns the
+   * {@link org.apache.calcite.rel.metadata.BuiltInMetadata.Parallelism#splitCount()}
+   * statistic.
+   *
+   * @param rel      the relational expression
+   * @return the number of distinct splits of the data, or null if not known
+   */
+  public static Integer splitCount(RelNode rel) {
+    final BuiltInMetadata.Parallelism metadata =
+        rel.metadata(BuiltInMetadata.Parallelism.class);
+    return metadata.splitCount();
+  }
+
+  /**
+   * Returns the
+   * {@link org.apache.calcite.rel.metadata.BuiltInMetadata.Memory#memory()}
+   * statistic.
+   *
+   * @param rel      the relational expression
+   * @return the expected amount of memory, in bytes, required by a physical
+   * operator implementing this relational expression, across all splits,
+   * or null if not known
+   */
+  public static Double memory(RelNode rel) {
+    final BuiltInMetadata.Memory metadata =
+        rel.metadata(BuiltInMetadata.Memory.class);
+    return metadata.memory();
+  }
+
+  /**
+   * Returns the
+   * {@link org.apache.calcite.rel.metadata.BuiltInMetadata.Memory#cumulativeMemoryWithinPhase()}
+   * statistic.
+   *
+   * @param rel      the relational expression
+   * @return the cumulative amount of memory, in bytes, required by the
+   * physical operator implementing this relational expression, and all other
+   * operators within the same phase, across all splits, or null if not known
+   */
+  public static Double cumulativeMemoryWithinPhase(RelNode rel) {
+    final BuiltInMetadata.Memory metadata =
+        rel.metadata(BuiltInMetadata.Memory.class);
+    return metadata.cumulativeMemoryWithinPhase();
+  }
+
+  /**
+   * Returns the
+   * {@link org.apache.calcite.rel.metadata.BuiltInMetadata.Memory#cumulativeMemoryWithinPhaseSplit()}
+   * statistic.
+   *
+   * @param rel      the relational expression
+   * @return the expected cumulative amount of memory, in bytes, required by
+   * the physical operator implementing this relational expression, and all
+   * operators within the same phase, within each split, or null if not known
+   */
+  public static Double cumulativeMemoryWithinPhaseSplit(RelNode rel) {
+    final BuiltInMetadata.Memory metadata =
+        rel.metadata(BuiltInMetadata.Memory.class);
+    return metadata.cumulativeMemoryWithinPhaseSplit();
+  }
+
+  /**
+   * Returns the
    * {@link BuiltInMetadata.DistinctRowCount#getDistinctRowCount(org.apache.calcite.util.ImmutableBitSet, org.apache.calcite.rex.RexNode)}
    * statistic.
    *

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/dacde8f2/core/src/main/java/org/apache/calcite/sql2rel/RelDecorrelator.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql2rel/RelDecorrelator.java b/core/src/main/java/org/apache/calcite/sql2rel/RelDecorrelator.java
index 7313f5e..34e2f27 100644
--- a/core/src/main/java/org/apache/calcite/sql2rel/RelDecorrelator.java
+++ b/core/src/main/java/org/apache/calcite/sql2rel/RelDecorrelator.java
@@ -112,7 +112,7 @@ import java.util.logging.Logger;
  *   <li>replace {@code CorelMap} constructor parameter with a RelNode
  *   <li>make {@link #currentRel} immutable (would require a fresh
  *      RelDecorrelator for each node being decorrelated)</li>
- *   <li>make fields of {@link CorelMap} immutable</li>
+ *   <li>make fields of {@code CorelMap} immutable</li>
  *   <li>make sub-class rules static, and have them create their own
  *   de-correlator</li>
  * </ul>

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/dacde8f2/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java b/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java
index 6d21b11..ff43113 100644
--- a/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java
+++ b/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java
@@ -43,6 +43,9 @@ import org.apache.calcite.linq4j.function.Predicate2;
 import org.apache.calcite.linq4j.tree.FunctionExpression;
 import org.apache.calcite.linq4j.tree.Primitive;
 import org.apache.calcite.linq4j.tree.Types;
+import org.apache.calcite.rel.metadata.BuiltInMetadata.Memory;
+import org.apache.calcite.rel.metadata.BuiltInMetadata.Parallelism;
+import org.apache.calcite.rel.metadata.BuiltInMetadata.Size;
 import org.apache.calcite.rel.metadata.Metadata;
 import org.apache.calcite.rex.RexNode;
 import org.apache.calcite.runtime.ArrayBindable;
@@ -293,6 +296,14 @@ public enum BuiltInMethod {
   ELEMENT(SqlFunctions.class, "element", List.class),
   SELECTIVITY(Selectivity.class, "getSelectivity", RexNode.class),
   UNIQUE_KEYS(UniqueKeys.class, "getUniqueKeys", boolean.class),
+  AVERAGE_ROW_SIZE(Size.class, "averageRowSize"),
+  AVERAGE_COLUMN_SIZES(Size.class, "averageColumnSizes"),
+  IS_PHASE_TRANSITION(Parallelism.class, "isPhaseTransition"),
+  SPLIT_COUNT(Parallelism.class, "splitCount"),
+  MEMORY(Memory.class, "memory"),
+  CUMULATIVE_MEMORY_WITHIN_PHASE(Memory.class, "cumulativeMemoryWithinPhase"),
+  CUMULATIVE_MEMORY_WITHIN_PHASE_SPLIT(Memory.class,
+      "cumulativeMemoryWithinPhaseSplit"),
   COLUMN_UNIQUENESS(ColumnUniqueness.class, "areColumnsUnique",
       ImmutableBitSet.class, boolean.class),
   COLLATIONS(Collation.class, "collations"),

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/dacde8f2/core/src/main/java/org/apache/calcite/util/ImmutableNullableList.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/util/ImmutableNullableList.java b/core/src/main/java/org/apache/calcite/util/ImmutableNullableList.java
index 1e92f7b..8ba9a6e 100644
--- a/core/src/main/java/org/apache/calcite/util/ImmutableNullableList.java
+++ b/core/src/main/java/org/apache/calcite/util/ImmutableNullableList.java
@@ -17,10 +17,14 @@
 package org.apache.calcite.util;
 
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Iterators;
+import com.google.common.collect.Lists;
 
 import java.util.AbstractList;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.Iterator;
 import java.util.List;
 
 /**
@@ -157,6 +161,89 @@ public class ImmutableNullableList<E> extends AbstractList<E> {
   @Override public int size() {
     return elements.length;
   }
+
+  /**
+   * Returns a new builder. The generated builder is equivalent to the builder
+   * created by the {@link Builder} constructor.
+   */
+  public static <E> Builder<E> builder() {
+    return new Builder<E>();
+  }
+
+  /**
+   * A builder for creating immutable nullable list instances.
+   */
+  public static final class Builder<E> {
+    private final List<E> contents = Lists.newArrayList();
+
+    /**
+     * Creates a new builder. The returned builder is equivalent to the builder
+     * generated by
+     * {@link org.apache.calcite.util.ImmutableNullableList#builder}.
+     */
+    public Builder() {}
+
+    /**
+     * Adds {@code element} to the {@code ImmutableNullableList}.
+     *
+     * @param element the element to add
+     * @return this {@code Builder} object
+     */
+    public Builder<E> add(E element) {
+      contents.add(element);
+      return this;
+    }
+
+    /**
+     * Adds each element of {@code elements} to the
+     * {@code ImmutableNullableList}.
+     *
+     * @param elements the {@code Iterable} to add to the
+     *     {@code ImmutableNullableList}
+     * @return this {@code Builder} object
+     * @throws NullPointerException if {@code elements} is null
+     */
+    public Builder<E> addAll(Iterable<? extends E> elements) {
+      Iterables.addAll(contents, elements);
+      return this;
+    }
+
+    /**
+     * Adds each element of {@code elements} to the
+     * {@code ImmutableNullableList}.
+     *
+     * @param elements the elements to add to the {@code ImmutableNullableList}
+     * @return this {@code Builder} object
+     * @throws NullPointerException if {@code elements} is null
+     */
+    public Builder<E> add(E... elements) {
+      for (E element : elements) {
+        add(element);
+      }
+      return this;
+    }
+
+    /**
+     * Adds each element of {@code elements} to the
+     * {@code ImmutableNullableList}.
+     *
+     * @param elements the elements to add to the {@code ImmutableNullableList}
+     * @return this {@code Builder} object
+     * @throws NullPointerException if {@code elements} is null
+     */
+    public Builder<E> addAll(Iterator<? extends E> elements) {
+      Iterators.addAll(contents, elements);
+      return this;
+    }
+
+    /**
+     * Returns a newly-created {@code ImmutableNullableList} based on the
+     * contents of the {@code Builder}.
+     */
+    public List<E> build() {
+      return copyOf(contents);
+    }
+  }
 }
 
 // End ImmutableNullableList.java

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/dacde8f2/core/src/test/java/org/apache/calcite/test/MultiJdbcSchemaJoinTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/test/MultiJdbcSchemaJoinTest.java b/core/src/test/java/org/apache/calcite/test/MultiJdbcSchemaJoinTest.java
index 426b531..bf2ad40 100644
--- a/core/src/test/java/org/apache/calcite/test/MultiJdbcSchemaJoinTest.java
+++ b/core/src/test/java/org/apache/calcite/test/MultiJdbcSchemaJoinTest.java
@@ -81,7 +81,7 @@ public class MultiJdbcSchemaJoinTest {
   }
 
   /** Makes sure that {@link #test} is re-entrant.
-   * Effectively a test for {@link TempDb}. */
+   * Effectively a test for {@code TempDb}. */
   @Test public void test2() throws SQLException, ClassNotFoundException {
     test();
   }

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/dacde8f2/core/src/test/java/org/apache/calcite/test/RelMetadataTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/test/RelMetadataTest.java b/core/src/test/java/org/apache/calcite/test/RelMetadataTest.java
index 0e60487..b92b549 100644
--- a/core/src/test/java/org/apache/calcite/test/RelMetadataTest.java
+++ b/core/src/test/java/org/apache/calcite/test/RelMetadataTest.java
@@ -28,15 +28,18 @@ import org.apache.calcite.rel.RelCollations;
 import org.apache.calcite.rel.RelFieldCollation;
 import org.apache.calcite.rel.RelNode;
 import org.apache.calcite.rel.core.Aggregate;
+import org.apache.calcite.rel.core.AggregateCall;
 import org.apache.calcite.rel.core.Join;
 import org.apache.calcite.rel.core.JoinRelType;
 import org.apache.calcite.rel.core.Project;
 import org.apache.calcite.rel.core.Sort;
 import org.apache.calcite.rel.logical.LogicalAggregate;
 import org.apache.calcite.rel.logical.LogicalFilter;
+import org.apache.calcite.rel.logical.LogicalJoin;
 import org.apache.calcite.rel.logical.LogicalProject;
 import org.apache.calcite.rel.logical.LogicalSort;
 import org.apache.calcite.rel.logical.LogicalTableScan;
+import org.apache.calcite.rel.logical.LogicalUnion;
 import org.apache.calcite.rel.logical.LogicalValues;
 import org.apache.calcite.rel.metadata.CachingRelMetadataProvider;
 import org.apache.calcite.rel.metadata.ChainedRelMetadataProvider;
@@ -60,21 +63,24 @@ import org.apache.calcite.util.ImmutableIntList;
 
 import com.google.common.base.Throwables;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
 
-import org.hamcrest.CoreMatchers;
 import org.hamcrest.Matcher;
 import org.junit.Ignore;
 import org.junit.Test;
 
 import java.lang.reflect.Method;
 import java.math.BigDecimal;
-import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 import java.util.Set;
 
 import static org.hamcrest.CoreMatchers.equalTo;
 import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.nullValue;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertThat;
 import static org.junit.Assert.assertTrue;
@@ -109,7 +115,7 @@ public class RelMetadataTest extends SqlToRelTestBase {
   //~ Methods ----------------------------------------------------------------
 
   private static Matcher<? super Number> nearTo(Number v, Number epsilon) {
-    return CoreMatchers.equalTo(v); // TODO: use epsilon
+    return equalTo(v); // TODO: use epsilon
   }
 
   // ----------------------------------------------------------------------
@@ -597,7 +603,7 @@ public class RelMetadataTest extends SqlToRelTestBase {
   }
 
   @Test public void testCustomProvider() {
-    final List<String> buf = new ArrayList<String>();
+    final List<String> buf = Lists.newArrayList();
     ColTypeImpl.THREAD_LIST.set(buf);
 
     RelNode rel =
@@ -788,11 +794,164 @@ public class RelMetadataTest extends SqlToRelTestBase {
       RexBuilder rexBuilder, Object... values) {
     ImmutableList.Builder<RexLiteral> b = ImmutableList.builder();
     for (Object value : values) {
-      b.add(rexBuilder.makeExactLiteral(BigDecimal.valueOf((Integer) value)));
+      final RexLiteral literal;
+      if (value == null) {
+        literal = (RexLiteral) rexBuilder.makeNullLiteral(SqlTypeName.VARCHAR);
+      } else if (value instanceof Integer) {
+        literal = rexBuilder.makeExactLiteral(
+            BigDecimal.valueOf((Integer) value));
+      } else {
+        literal = rexBuilder.makeLiteral((String) value);
+      }
+      b.add(literal);
     }
     builder.add(b.build());
   }
 
+  /** Unit test for
+   * {@link org.apache.calcite.rel.metadata.RelMetadataQuery#getAverageColumnSizes(org.apache.calcite.rel.RelNode)},
+   * {@link org.apache.calcite.rel.metadata.RelMetadataQuery#getAverageRowSize(org.apache.calcite.rel.RelNode)}. */
+  @Test public void testAverageRowSize() {
+    final Project rel = (Project) convertSql("select * from emp, dept");
+    final Join join = (Join) rel.getInput();
+    final RelOptTable empTable = join.getInput(0).getTable();
+    final RelOptTable deptTable = join.getInput(1).getTable();
+    Frameworks.withPlanner(
+        new Frameworks.PlannerAction<Void>() {
+          public Void apply(RelOptCluster cluster,
+              RelOptSchema relOptSchema,
+              SchemaPlus rootSchema) {
+            checkAverageRowSize(cluster, empTable, deptTable);
+            return null;
+          }
+        });
+  }
+
+  private void checkAverageRowSize(RelOptCluster cluster, RelOptTable empTable,
+      RelOptTable deptTable) {
+    final RexBuilder rexBuilder = cluster.getRexBuilder();
+    final LogicalTableScan empScan = LogicalTableScan.create(cluster, empTable);
+
+    Double rowSize = RelMetadataQuery.getAverageRowSize(empScan);
+    List<Double> columnSizes = RelMetadataQuery.getAverageColumnSizes(empScan);
+
+    assertThat(columnSizes.size(),
+        equalTo(empScan.getRowType().getFieldCount()));
+    assertThat(columnSizes,
+        equalTo(Arrays.asList(4.0, 40.0, 20.0, 4.0, 8.0, 4.0, 4.0, 4.0, 1.0)));
+    assertThat(rowSize, equalTo(89.0));
+
+    // Empty values
+    final LogicalValues emptyValues =
+        LogicalValues.createEmpty(cluster, empTable.getRowType());
+    rowSize = RelMetadataQuery.getAverageRowSize(emptyValues);
+    columnSizes = RelMetadataQuery.getAverageColumnSizes(emptyValues);
+    assertThat(columnSizes.size(),
+        equalTo(emptyValues.getRowType().getFieldCount()));
+    assertThat(columnSizes,
+        equalTo(Arrays.asList(4.0, 40.0, 20.0, 4.0, 8.0, 4.0, 4.0, 4.0, 1.0)));
+    assertThat(rowSize, equalTo(89.0));
+
+    // Values
+    final RelDataType rowType = cluster.getTypeFactory().builder()
+        .add("a", SqlTypeName.INTEGER)
+        .add("b", SqlTypeName.VARCHAR)
+        .add("c", SqlTypeName.VARCHAR)
+        .build();
+    final ImmutableList.Builder<ImmutableList<RexLiteral>> tuples =
+        ImmutableList.builder();
+    addRow(tuples, rexBuilder, 1, "1234567890", "ABC");
+    addRow(tuples, rexBuilder, 2, "1",          "A");
+    addRow(tuples, rexBuilder, 3, "2",          null);
+    final LogicalValues values =
+        LogicalValues.create(cluster, rowType, tuples.build());
+    rowSize = RelMetadataQuery.getAverageRowSize(values);
+    columnSizes = RelMetadataQuery.getAverageColumnSizes(values);
+    assertThat(columnSizes.size(),
+        equalTo(values.getRowType().getFieldCount()));
+    assertThat(columnSizes, equalTo(Arrays.asList(4.0, 8.0, 3.0)));
+    assertThat(rowSize, equalTo(15.0));
+
+    // Union
+    final LogicalUnion union =
+        LogicalUnion.create(ImmutableList.<RelNode>of(empScan, emptyValues),
+            true);
+    rowSize = RelMetadataQuery.getAverageRowSize(union);
+    columnSizes = RelMetadataQuery.getAverageColumnSizes(union);
+    assertThat(columnSizes.size(), equalTo(9));
+    assertThat(columnSizes,
+        equalTo(Arrays.asList(4.0, 40.0, 20.0, 4.0, 8.0, 4.0, 4.0, 4.0, 1.0)));
+    assertThat(rowSize, equalTo(89.0));
+
+    // Filter
+    final LogicalTableScan deptScan =
+        LogicalTableScan.create(cluster, deptTable);
+    final LogicalFilter filter =
+        LogicalFilter.create(deptScan,
+            rexBuilder.makeCall(SqlStdOperatorTable.LESS_THAN,
+                rexBuilder.makeInputRef(deptScan, 0),
+                rexBuilder.makeExactLiteral(BigDecimal.TEN)));
+    rowSize = RelMetadataQuery.getAverageRowSize(filter);
+    columnSizes = RelMetadataQuery.getAverageColumnSizes(filter);
+    assertThat(columnSizes.size(), equalTo(2));
+    assertThat(columnSizes, equalTo(Arrays.asList(4.0, 20.0)));
+    assertThat(rowSize, equalTo(24.0));
+
+    // Project
+    final LogicalProject deptProject =
+        LogicalProject.create(filter,
+            ImmutableList.of(
+                rexBuilder.makeInputRef(filter, 0),
+                rexBuilder.makeInputRef(filter, 1),
+                rexBuilder.makeCall(SqlStdOperatorTable.PLUS,
+                    rexBuilder.makeInputRef(filter, 0),
+                    rexBuilder.makeExactLiteral(BigDecimal.ONE)),
+                rexBuilder.makeCall(SqlStdOperatorTable.CHAR_LENGTH,
+                    rexBuilder.makeInputRef(filter, 1))),
+            (List<String>) null);
+    rowSize = RelMetadataQuery.getAverageRowSize(deptProject);
+    columnSizes = RelMetadataQuery.getAverageColumnSizes(deptProject);
+    assertThat(columnSizes.size(), equalTo(4));
+    assertThat(columnSizes, equalTo(Arrays.asList(4.0, 20.0, 4.0, 4.0)));
+    assertThat(rowSize, equalTo(32.0));
+
+    // Join
+    final LogicalJoin join =
+        LogicalJoin.create(empScan, deptProject, rexBuilder.makeLiteral(true),
+            JoinRelType.INNER, ImmutableSet.<String>of());
+    rowSize = RelMetadataQuery.getAverageRowSize(join);
+    columnSizes = RelMetadataQuery.getAverageColumnSizes(join);
+    assertThat(columnSizes.size(), equalTo(13));
+    assertThat(columnSizes,
+        equalTo(
+            Arrays.asList(4.0, 40.0, 20.0, 4.0, 8.0, 4.0, 4.0, 4.0, 1.0, 4.0,
+                20.0, 4.0, 4.0)));
+    assertThat(rowSize, equalTo(121.0));
+
+    // Aggregate
+    final LogicalAggregate aggregate =
+        LogicalAggregate.create(join, false, ImmutableBitSet.of(2, 0),
+            ImmutableList.<ImmutableBitSet>of(),
+            ImmutableList.of(
+                AggregateCall.create(
+                    SqlStdOperatorTable.COUNT, false, ImmutableIntList.of(),
+                    2, join, null, null)));
+    rowSize = RelMetadataQuery.getAverageRowSize(aggregate);
+    columnSizes = RelMetadataQuery.getAverageColumnSizes(aggregate);
+    assertThat(columnSizes.size(), equalTo(3));
+    assertThat(columnSizes, equalTo(Arrays.asList(4.0, 20.0, 8.0)));
+    assertThat(rowSize, equalTo(32.0));
+
+    // Smoke test Parallelism and Memory metadata providers
+    assertThat(RelMetadataQuery.memory(aggregate), nullValue());
+    assertThat(RelMetadataQuery.cumulativeMemoryWithinPhase(aggregate),
+        nullValue());
+    assertThat(RelMetadataQuery.cumulativeMemoryWithinPhaseSplit(aggregate),
+        nullValue());
+    assertThat(RelMetadataQuery.isPhaseTransition(aggregate), is(false));
+    assertThat(RelMetadataQuery.splitCount(aggregate), is(1));
+  }
+
   /** Custom metadata interface. */
   public interface ColType extends Metadata {
     String getColType(int column);
@@ -801,8 +960,7 @@ public class RelMetadataTest extends SqlToRelTestBase {
   /** A provider for {@link org.apache.calcite.test.RelMetadataTest.ColType} via
    * reflection. */
   public static class ColTypeImpl {
-    static final ThreadLocal<List<String>> THREAD_LIST =
-        new ThreadLocal<List<String>>();
+    static final ThreadLocal<List<String>> THREAD_LIST = new ThreadLocal<>();
     static final Method METHOD;
     static {
       try {

http://git-wip-us.apache.org/repos/asf/incubator-calcite/blob/dacde8f2/core/src/test/java/org/apache/calcite/util/UtilTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/util/UtilTest.java b/core/src/test/java/org/apache/calcite/util/UtilTest.java
index 4fcdd20..8aca657 100644
--- a/core/src/test/java/org/apache/calcite/util/UtilTest.java
+++ b/core/src/test/java/org/apache/calcite/util/UtilTest.java
@@ -1303,6 +1303,18 @@ public class UtilTest {
     }
   }
 
+  /** Test for {@link org.apache.calcite.util.ImmutableNullableList.Builder}. */
+  @Test public void testImmutableNullableListBuilder() {
+    final ImmutableNullableList.Builder<String> builder =
+        ImmutableNullableList.builder();
+    builder.add("a")
+        .add((String) null)
+        .add("c");
+    final List<String> arrayList = Arrays.asList("a", null, "c");
+    final List<String> list = builder.build();
+    assertThat(arrayList.equals(list), is(true));
+  }
+
   @Test public void testHuman() {
     assertThat(Util.human(0D), equalTo("0"));
     assertThat(Util.human(1D), equalTo("1"));


Mime
View raw message