[CALCITE-604] Tune metadata by generating a dispatcher at runtime

Currently we dispatch to metadata providers using reflection. With
this change, we generate a dispatcher class and compile using Janino.

Cache results within a metadata call. This should yield a performance
improvement when, say, a Join requires many kinds of metadata
(rowCount, averageRowSize, selectivity) and they all rely on the
rowCount of the input, and that rowCount is expensive to compute.


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

Branch: refs/heads/master
Commit: d14040c52f8b0bc351443cff1584e8b587378698
Parents: 8cba7fc
Author: Julian Hyde <[email protected]>
Authored: Mon Jan 18 16:22:06 2016 -0800
Committer: Julian Hyde <[email protected]>
Committed: Wed Feb 24 13:37:49 2016 -0800

----------------------------------------------------------------------
 .../org/apache/calcite/plan/RelOptCluster.java  |   2 +-
 .../calcite/plan/RelOptMaterialization.java     |   4 +-
 .../plan/hep/HepRelMetadataProvider.java        |  20 +
 .../calcite/plan/volcano/VolcanoPlanner.java    |  13 +
 .../volcano/VolcanoRelMetadataProvider.java     |  20 +
 .../calcite/rel/metadata/BuiltInMetadata.java   | 163 ++++++
 .../metadata/CachingRelMetadataProvider.java    |   5 +
 .../metadata/ChainedRelMetadataProvider.java    |  23 +
 .../metadata/DefaultRelMetadataProvider.java    |   8 +-
 .../rel/metadata/JaninoRelMetadataProvider.java | 522 +++++++++++++++++++
 .../calcite/rel/metadata/MetadataDef.java       |  67 +++
 .../calcite/rel/metadata/MetadataHandler.java   |  28 +
 .../calcite/rel/metadata/NullSentinel.java      |  14 +-
 .../metadata/ReflectiveRelMetadataProvider.java | 185 ++++---
 .../calcite/rel/metadata/RelMdCollation.java    |   7 +-
 .../rel/metadata/RelMdColumnOrigins.java        |   7 +-
 .../rel/metadata/RelMdColumnUniqueness.java     |   7 +-
 .../rel/metadata/RelMdDistinctRowCount.java     |   7 +-
 .../calcite/rel/metadata/RelMdDistribution.java |   7 +-
 .../rel/metadata/RelMdExplainVisibility.java    |   7 +-
 .../calcite/rel/metadata/RelMdMaxRowCount.java  |   7 +-
 .../calcite/rel/metadata/RelMdMemory.java       |   6 +-
 .../calcite/rel/metadata/RelMdParallelism.java  |   7 +-
 .../metadata/RelMdPercentageOriginalRows.java   |   7 +-
 .../rel/metadata/RelMdPopulationSize.java       |   7 +-
 .../calcite/rel/metadata/RelMdPredicates.java   |   7 +-
 .../calcite/rel/metadata/RelMdRowCount.java     |   7 +-
 .../calcite/rel/metadata/RelMdSelectivity.java  |   7 +-
 .../apache/calcite/rel/metadata/RelMdSize.java  |   6 +-
 .../calcite/rel/metadata/RelMdUniqueKeys.java   |   7 +-
 .../rel/metadata/RelMetadataProvider.java       |  10 +
 .../calcite/rel/metadata/RelMetadataQuery.java  | 391 ++++++++++----
 .../calcite/sql2rel/SqlToRelConverter.java      |   4 +
 .../java/org/apache/calcite/tools/Programs.java |   8 +-
 .../apache/calcite/test/RelMetadataTest.java    |  11 +-
 .../apache/calcite/test/RelOptRulesTest.java    |   4 +-
 .../org/apache/calcite/test/RelOptTestBase.java |   4 +-
 37 files changed, 1418 insertions(+), 198 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/calcite/blob/d14040c5/core/src/main/java/org/apache/calcite/plan/RelOptCluster.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/plan/RelOptCluster.java 
b/core/src/main/java/org/apache/calcite/plan/RelOptCluster.java
index 241f4e0..f88e232 100644
--- a/core/src/main/java/org/apache/calcite/plan/RelOptCluster.java
+++ b/core/src/main/java/org/apache/calcite/plan/RelOptCluster.java
@@ -81,7 +81,7 @@ public class RelOptCluster {
 
     // set up a default rel metadata provider,
     // giving the planner first crack at everything
-    setMetadataProvider(new DefaultRelMetadataProvider());
+    setMetadataProvider(DefaultRelMetadataProvider.INSTANCE);
     this.emptyTraitSet = planner.emptyTraitSet();
     assert emptyTraitSet.size() == planner.getRelTraitDefs().size();
   }

http://git-wip-us.apache.org/repos/asf/calcite/blob/d14040c5/core/src/main/java/org/apache/calcite/plan/RelOptMaterialization.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/calcite/plan/RelOptMaterialization.java 
b/core/src/main/java/org/apache/calcite/plan/RelOptMaterialization.java
index 4108cff..1da831d 100644
--- a/core/src/main/java/org/apache/calcite/plan/RelOptMaterialization.java
+++ b/core/src/main/java/org/apache/calcite/plan/RelOptMaterialization.java
@@ -204,7 +204,7 @@ public class RelOptMaterialization {
             AggregateProjectMergeRule.INSTANCE,
             AggregateFilterTransposeRule.INSTANCE),
         false,
-        new DefaultRelMetadataProvider());
+        DefaultRelMetadataProvider.INSTANCE);
     return program.run(null, rel2, null);
   }
 
@@ -273,7 +273,7 @@ public class RelOptMaterialization {
             FilterJoinRule.FilterIntoJoinRule.FILTER_ON_JOIN,
             ProjectMergeRule.INSTANCE),
         false,
-        new DefaultRelMetadataProvider());
+        DefaultRelMetadataProvider.INSTANCE);
     if (CalcitePrepareImpl.DEBUG) {
       System.out.println(
           RelOptUtil.dumpPlan(

http://git-wip-us.apache.org/repos/asf/calcite/blob/d14040c5/core/src/main/java/org/apache/calcite/plan/hep/HepRelMetadataProvider.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/calcite/plan/hep/HepRelMetadataProvider.java 
b/core/src/main/java/org/apache/calcite/plan/hep/HepRelMetadataProvider.java
index 3f1170a..317fe94 100644
--- a/core/src/main/java/org/apache/calcite/plan/hep/HepRelMetadataProvider.java
+++ b/core/src/main/java/org/apache/calcite/plan/hep/HepRelMetadataProvider.java
@@ -18,10 +18,17 @@ package org.apache.calcite.plan.hep;
 
 import org.apache.calcite.rel.RelNode;
 import org.apache.calcite.rel.metadata.Metadata;
+import org.apache.calcite.rel.metadata.MetadataDef;
+import org.apache.calcite.rel.metadata.MetadataHandler;
 import org.apache.calcite.rel.metadata.RelMetadataProvider;
 import org.apache.calcite.rel.metadata.RelMetadataQuery;
 import org.apache.calcite.rel.metadata.UnboundMetadata;
 
+import com.google.common.collect.ImmutableMap;
+
+import java.lang.reflect.Method;
+import java.util.Map;
+
 /**
  * HepRelMetadataProvider implements the {@link RelMetadataProvider} interface
  * by combining metadata from the rels inside of a {@link HepRelVertex}.
@@ -29,6 +36,14 @@ import org.apache.calcite.rel.metadata.UnboundMetadata;
 class HepRelMetadataProvider implements RelMetadataProvider {
   //~ Methods ----------------------------------------------------------------
 
+  @Override public boolean equals(Object obj) {
+    return obj instanceof HepRelMetadataProvider;
+  }
+
+  @Override public int hashCode() {
+    return 107;
+  }
+
   public <M extends Metadata> UnboundMetadata<M>
   apply(Class<? extends RelNode> relClass,
       final Class<? extends M> metadataClass) {
@@ -46,6 +61,11 @@ class HepRelMetadataProvider implements RelMetadataProvider {
       }
     };
   }
+
+  public <M extends Metadata> Map<Method, MetadataHandler<M>>
+  handlers(MetadataDef<M> def) {
+    return ImmutableMap.of();
+  }
 }
 
 // End HepRelMetadataProvider.java

http://git-wip-us.apache.org/repos/asf/calcite/blob/d14040c5/core/src/main/java/org/apache/calcite/plan/volcano/VolcanoPlanner.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/calcite/plan/volcano/VolcanoPlanner.java 
b/core/src/main/java/org/apache/calcite/plan/volcano/VolcanoPlanner.java
index 2565ca7..8cb91b0 100644
--- a/core/src/main/java/org/apache/calcite/plan/volcano/VolcanoPlanner.java
+++ b/core/src/main/java/org/apache/calcite/plan/volcano/VolcanoPlanner.java
@@ -48,6 +48,7 @@ import org.apache.calcite.rel.RelVisitor;
 import org.apache.calcite.rel.convert.Converter;
 import org.apache.calcite.rel.convert.ConverterRule;
 import org.apache.calcite.rel.core.TableScan;
+import org.apache.calcite.rel.metadata.JaninoRelMetadataProvider;
 import org.apache.calcite.rel.metadata.RelMetadataProvider;
 import org.apache.calcite.rel.metadata.RelMetadataQuery;
 import org.apache.calcite.rel.rules.AggregateJoinTransposeRule;
@@ -322,6 +323,11 @@ public class VolcanoPlanner extends AbstractRelOptPlanner {
   }
 
   public void setRoot(RelNode rel) {
+    // We're registered all the rules, and therefore RelNode classes,
+    // we're interested in, and have not yet started calling metadata 
providers.
+    // So now is a good time to tell the metadata layer what to expect.
+    registerMetadataRels();
+
     this.root = registerImpl(rel, null);
     if (this.originalRoot == null) {
       this.originalRoot = rel;
@@ -842,6 +848,13 @@ public class VolcanoPlanner extends AbstractRelOptPlanner {
     return cheapest;
   }
 
+  /** Informs {@link JaninoRelMetadataProvider} about the different kinds of
+   * {@link RelNode} that we will be dealing with. It will reduce the number
+   * of times that we need to re-generate the provider. */
+  private void registerMetadataRels() {
+    JaninoRelMetadataProvider.DEFAULT.register(classOperands.keySet());
+  }
+
   /** Ensures that the subset that is the root relational expression contains
    * converters to all other subsets in its equivalence set.
    *

http://git-wip-us.apache.org/repos/asf/calcite/blob/d14040c5/core/src/main/java/org/apache/calcite/plan/volcano/VolcanoRelMetadataProvider.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/calcite/plan/volcano/VolcanoRelMetadataProvider.java
 
b/core/src/main/java/org/apache/calcite/plan/volcano/VolcanoRelMetadataProvider.java
index 201670f..c32d7d3 100644
--- 
a/core/src/main/java/org/apache/calcite/plan/volcano/VolcanoRelMetadataProvider.java
+++ 
b/core/src/main/java/org/apache/calcite/plan/volcano/VolcanoRelMetadataProvider.java
@@ -18,10 +18,17 @@ package org.apache.calcite.plan.volcano;
 
 import org.apache.calcite.rel.RelNode;
 import org.apache.calcite.rel.metadata.Metadata;
+import org.apache.calcite.rel.metadata.MetadataDef;
+import org.apache.calcite.rel.metadata.MetadataHandler;
 import org.apache.calcite.rel.metadata.RelMetadataProvider;
 import org.apache.calcite.rel.metadata.RelMetadataQuery;
 import org.apache.calcite.rel.metadata.UnboundMetadata;
 
+import com.google.common.collect.ImmutableMap;
+
+import java.lang.reflect.Method;
+import java.util.Map;
+
 /**
  * VolcanoRelMetadataProvider implements the {@link RelMetadataProvider}
  * interface by combining metadata from the rels making up an equivalence 
class.
@@ -29,6 +36,14 @@ import org.apache.calcite.rel.metadata.UnboundMetadata;
 public class VolcanoRelMetadataProvider implements RelMetadataProvider {
   //~ Methods ----------------------------------------------------------------
 
+  @Override public boolean equals(Object obj) {
+    return obj instanceof VolcanoRelMetadataProvider;
+  }
+
+  @Override public int hashCode() {
+    return 103;
+  }
+
   public <M extends Metadata> UnboundMetadata<M>
   apply(Class<? extends RelNode> relClass,
       final Class<? extends M> metadataClass) {
@@ -99,6 +114,11 @@ public class VolcanoRelMetadataProvider implements 
RelMetadataProvider {
       }
     };
   }
+
+  public <M extends Metadata> Map<Method, MetadataHandler<M>>
+  handlers(MetadataDef<M> def) {
+    return ImmutableMap.of();
+  }
 }
 
 // End VolcanoRelMetadataProvider.java

http://git-wip-us.apache.org/repos/asf/calcite/blob/d14040c5/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 87fbbf4..0ff0515 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
@@ -23,6 +23,7 @@ import org.apache.calcite.rel.RelDistribution;
 import org.apache.calcite.rel.RelNode;
 import org.apache.calcite.rex.RexNode;
 import org.apache.calcite.sql.SqlExplainLevel;
+import org.apache.calcite.util.BuiltInMethod;
 import org.apache.calcite.util.ImmutableBitSet;
 
 import com.google.common.collect.ImmutableList;
@@ -37,6 +38,9 @@ public abstract class BuiltInMetadata {
 
   /** Metadata about the selectivity of a predicate. */
   public interface Selectivity extends Metadata {
+    MetadataDef<Selectivity> DEF = MetadataDef.of(Selectivity.class,
+        Selectivity.Handler.class, BuiltInMethod.SELECTIVITY.method);
+
     /**
      * Estimates the percentage of an expression's output rows which satisfy a
      * given predicate. Returns null to indicate that no reliable estimate can
@@ -48,10 +52,18 @@ public abstract class BuiltInMetadata {
      * reliable estimate can be determined
      */
     Double getSelectivity(RexNode predicate);
+
+    /** Handler API. */
+    interface Handler extends MetadataHandler<Selectivity> {
+      Double getSelectivity(RelNode r, RelMetadataQuery mq, RexNode predicate);
+    }
   }
 
   /** Metadata about which combinations of columns are unique identifiers. */
   public interface UniqueKeys extends Metadata {
+    MetadataDef<UniqueKeys> DEF = MetadataDef.of(UniqueKeys.class,
+        UniqueKeys.Handler.class, BuiltInMethod.UNIQUE_KEYS.method);
+
     /**
      * Determines the set of unique minimal keys for this expression. A key is
      * represented as an {@link org.apache.calcite.util.ImmutableBitSet}, where
@@ -66,10 +78,19 @@ public abstract class BuiltInMetadata {
      * (whereas empty set indicates definitely no keys at all)
      */
     Set<ImmutableBitSet> getUniqueKeys(boolean ignoreNulls);
+
+    /** Handler API. */
+    interface Handler extends MetadataHandler<UniqueKeys> {
+      Set<ImmutableBitSet> getUniqueKeys(RelNode r, RelMetadataQuery mq,
+          boolean ignoreNulls);
+    }
   }
 
   /** Metadata about whether a set of columns uniquely identifies a row. */
   public interface ColumnUniqueness extends Metadata {
+    MetadataDef<ColumnUniqueness> DEF = MetadataDef.of(ColumnUniqueness.class,
+        ColumnUniqueness.Handler.class, 
BuiltInMethod.COLUMN_UNIQUENESS.method);
+
     /**
      * Determines whether a specified set of columns from a specified 
relational
      * expression are unique.
@@ -93,12 +114,26 @@ public abstract class BuiltInMetadata {
      * null if not enough information is available to make that determination
      */
     Boolean areColumnsUnique(ImmutableBitSet columns, boolean ignoreNulls);
+
+    /** Handler API. */
+    interface Handler extends MetadataHandler<ColumnUniqueness> {
+      Boolean areColumnsUnique(RelNode r, RelMetadataQuery mq,
+          ImmutableBitSet columns, boolean ignoreNulls);
+    }
   }
 
   /** Metadata about which columns are sorted. */
   public interface Collation extends Metadata {
+    MetadataDef<Collation> DEF = MetadataDef.of(Collation.class,
+        Collation.Handler.class, BuiltInMethod.COLLATIONS.method);
+
     /** Determines which columns are sorted. */
     ImmutableList<RelCollation> collations();
+
+    /** Handler API. */
+    interface Handler extends MetadataHandler<Collation> {
+      ImmutableList<RelCollation> collations(RelNode r, RelMetadataQuery mq);
+    }
   }
 
   /** Metadata about how a relational expression is distributed.
@@ -113,12 +148,23 @@ public abstract class BuiltInMetadata {
    * among nodes, but it may be partitioned among threads running on the same
    * node. */
   public interface Distribution extends Metadata {
+    MetadataDef<Distribution> DEF = MetadataDef.of(Distribution.class,
+        Distribution.Handler.class, BuiltInMethod.DISTRIBUTION.method);
+
     /** Determines how the rows are distributed. */
     RelDistribution distribution();
+
+    /** Handler API. */
+    interface Handler extends MetadataHandler<Distribution> {
+      RelDistribution distribution(RelNode r, RelMetadataQuery mq);
+    }
   }
 
   /** Metadata about the number of rows returned by a relational expression. */
   public interface RowCount extends Metadata {
+    MetadataDef<RowCount> DEF = MetadataDef.of(RowCount.class,
+        RowCount.Handler.class, BuiltInMethod.ROW_COUNT.method);
+
     /**
      * Estimates the number of rows which will be returned by a relational
      * expression. The default implementation for this query asks the rel 
itself
@@ -129,11 +175,19 @@ public abstract class BuiltInMetadata {
      * determined
      */
     Double getRowCount();
+
+    /** Handler API. */
+    interface Handler extends MetadataHandler<RowCount> {
+      Double getRowCount(RelNode r, RelMetadataQuery mq);
+    }
   }
 
   /** Metadata about the maximum number of rows returned by a relational
    * expression. */
   public interface MaxRowCount extends Metadata {
+    MetadataDef<MaxRowCount> DEF = MetadataDef.of(MaxRowCount.class,
+        MaxRowCount.Handler.class, BuiltInMethod.MAX_ROW_COUNT.method);
+
     /**
      * Estimates the max number of rows which will be returned by a relational
      * expression.
@@ -145,11 +199,19 @@ public abstract class BuiltInMetadata {
      * @return upper bound on the number of rows returned
      */
     Double getMaxRowCount();
+
+    /** Handler API. */
+    interface Handler extends MetadataHandler<MaxRowCount> {
+      Double getMaxRowCount(RelNode r, RelMetadataQuery mq);
+    }
   }
 
   /** Metadata about the number of distinct rows returned by a set of columns
    * in a relational expression. */
   public interface DistinctRowCount extends Metadata {
+    MetadataDef<DistinctRowCount> DEF = MetadataDef.of(DistinctRowCount.class,
+        DistinctRowCount.Handler.class, 
BuiltInMethod.DISTINCT_ROW_COUNT.method);
+
     /**
      * Estimates the number of rows which would be produced by a GROUP BY on 
the
      * set of columns indicated by groupKey, where the input to the GROUP BY 
has
@@ -163,11 +225,22 @@ public abstract class BuiltInMetadata {
      * if no reliable estimate can be determined
      */
     Double getDistinctRowCount(ImmutableBitSet groupKey, RexNode predicate);
+
+    /** Handler API. */
+    interface Handler extends MetadataHandler<DistinctRowCount> {
+      Double getDistinctRowCount(RelNode r, RelMetadataQuery mq,
+          ImmutableBitSet groupKey, RexNode predicate);
+    }
   }
 
   /** Metadata about the proportion of original rows that remain in a 
relational
    * expression. */
   public interface PercentageOriginalRows extends Metadata {
+    MetadataDef<PercentageOriginalRows> DEF =
+        MetadataDef.of(PercentageOriginalRows.class,
+            PercentageOriginalRows.Handler.class,
+            BuiltInMethod.PERCENTAGE_ORIGINAL_ROWS.method);
+
     /**
      * Estimates the percentage of the number of rows actually produced by a
      * relational expression out of the number of rows it would produce if all
@@ -177,11 +250,19 @@ public abstract class BuiltInMetadata {
      * reliable estimate can be determined
      */
     Double getPercentageOriginalRows();
+
+    /** Handler API. */
+    interface Handler extends MetadataHandler<PercentageOriginalRows> {
+      Double getPercentageOriginalRows(RelNode r, RelMetadataQuery mq);
+    }
   }
 
   /** Metadata about the number of distinct values in the original source of a
    * column or set of columns. */
   public interface PopulationSize extends Metadata {
+    MetadataDef<PopulationSize> DEF = MetadataDef.of(PopulationSize.class,
+        PopulationSize.Handler.class, BuiltInMethod.POPULATION_SIZE.method);
+
     /**
      * Estimates the distinct row count in the original source for the given
      * {@code groupKey}, ignoring any filtering being applied by the 
expression.
@@ -194,10 +275,20 @@ public abstract class BuiltInMetadata {
      * estimate can be determined
      */
     Double getPopulationSize(ImmutableBitSet groupKey);
+
+    /** Handler API. */
+    interface Handler extends MetadataHandler<PopulationSize> {
+      Double getPopulationSize(RelNode r, RelMetadataQuery mq,
+          ImmutableBitSet groupKey);
+    }
   }
 
   /** Metadata about the size of rows and columns. */
   public interface Size extends Metadata {
+    MetadataDef<Size> DEF = MetadataDef.of(Size.class, Size.Handler.class,
+        BuiltInMethod.AVERAGE_ROW_SIZE.method,
+        BuiltInMethod.AVERAGE_COLUMN_SIZES.method);
+
     /**
      * Determines the average size (in bytes) of a row from this relational
      * expression.
@@ -222,10 +313,19 @@ public abstract class BuiltInMetadata {
      * the metadata is not available
      */
     List<Double> averageColumnSizes();
+
+    /** Handler API. */
+    interface Handler extends MetadataHandler<Size> {
+      Double averageRowSize(RelNode r, RelMetadataQuery mq);
+      List<Double> averageColumnSizes(RelNode r, RelMetadataQuery mq);
+    }
   }
 
   /** Metadata about the origins of columns. */
   public interface ColumnOrigin extends Metadata {
+    MetadataDef<ColumnOrigin> DEF = MetadataDef.of(ColumnOrigin.class,
+        ColumnOrigin.Handler.class, BuiltInMethod.COLUMN_ORIGIN.method);
+
     /**
      * For a given output column of an expression, determines all columns of
      * underlying tables which contribute to result values. An output column 
may
@@ -239,11 +339,20 @@ public abstract class BuiltInMetadata {
      * all)
      */
     Set<RelColumnOrigin> getColumnOrigins(int outputColumn);
+
+    /** Handler API. */
+    interface Handler extends MetadataHandler<ColumnOrigin> {
+      Set<RelColumnOrigin> getColumnOrigins(RelNode r, RelMetadataQuery mq,
+          int outputColumn);
+    }
   }
 
   /** Metadata about the cost of evaluating a relational expression, including
    * all of its inputs. */
   public interface CumulativeCost extends Metadata {
+    MetadataDef<CumulativeCost> DEF = MetadataDef.of(CumulativeCost.class,
+        CumulativeCost.Handler.class, BuiltInMethod.CUMULATIVE_COST.method);
+
     /**
      * Estimates the cost of executing a relational expression, including the
      * cost of its inputs. The default implementation for this query adds
@@ -255,11 +364,20 @@ public abstract class BuiltInMetadata {
      * determined
      */
     RelOptCost getCumulativeCost();
+
+    /** Handler API. */
+    interface Handler extends MetadataHandler<CumulativeCost> {
+      RelOptCost getCumulativeCost(RelNode r, RelMetadataQuery mq);
+    }
   }
 
   /** Metadata about the cost of evaluating a relational expression, not
    * including its inputs. */
   public interface NonCumulativeCost extends Metadata {
+    MetadataDef<NonCumulativeCost> DEF = 
MetadataDef.of(NonCumulativeCost.class,
+        NonCumulativeCost.Handler.class,
+        BuiltInMethod.NON_CUMULATIVE_COST.method);
+
     /**
      * Estimates the cost of executing a relational expression, not counting 
the
      * cost of its inputs. (However, the non-cumulative cost is still usually
@@ -271,10 +389,19 @@ public abstract class BuiltInMetadata {
      * determined
      */
     RelOptCost getNonCumulativeCost();
+
+    /** Handler API. */
+    interface Handler extends MetadataHandler<NonCumulativeCost> {
+      RelOptCost getNonCumulativeCost(RelNode r, RelMetadataQuery mq);
+    }
   }
 
   /** Metadata about whether a relational expression should appear in a plan. 
*/
   public interface ExplainVisibility extends Metadata {
+    MetadataDef<ExplainVisibility> DEF = 
MetadataDef.of(ExplainVisibility.class,
+        ExplainVisibility.Handler.class,
+        BuiltInMethod.EXPLAIN_VISIBILITY.method);
+
     /**
      * Determines whether a relational expression should be visible in EXPLAIN
      * PLAN output at a particular level of detail.
@@ -283,11 +410,20 @@ public abstract class BuiltInMetadata {
      * @return true for visible, false for invisible
      */
     Boolean isVisibleInExplain(SqlExplainLevel explainLevel);
+
+    /** Handler API. */
+    interface Handler extends MetadataHandler<ExplainVisibility> {
+      Boolean isVisibleInExplain(RelNode r, RelMetadataQuery mq,
+          SqlExplainLevel explainLevel);
+    }
   }
 
   /** Metadata about the predicates that hold in the rows emitted from a
    * relational expression. */
   public interface Predicates extends Metadata {
+    MetadataDef<Predicates> DEF = MetadataDef.of(Predicates.class,
+        Predicates.Handler.class, BuiltInMethod.PREDICATES.method);
+
     /**
      * Derives the predicates that hold on rows emitted from a relational
      * expression.
@@ -295,12 +431,21 @@ public abstract class BuiltInMetadata {
      * @return Predicate list
      */
     RelOptPredicateList getPredicates();
+
+    /** Handler API. */
+    interface Handler extends MetadataHandler<Predicates> {
+      RelOptPredicateList getPredicates(RelNode r, RelMetadataQuery mq);
+    }
   }
 
   /** 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 {
+    MetadataDef<Parallelism> DEF = MetadataDef.of(Parallelism.class,
+        Parallelism.Handler.class, BuiltInMethod.IS_PHASE_TRANSITION.method,
+        BuiltInMethod.SPLIT_COUNT.method);
+
     /** Returns whether each physical operator implementing this relational
      * expression belongs to a different process than its inputs.
      *
@@ -321,10 +466,21 @@ public abstract class BuiltInMetadata {
      * each operator instance.
      */
     Integer splitCount();
+
+    /** Handler API. */
+    interface Handler extends MetadataHandler<Parallelism> {
+      Boolean isPhaseTransition(RelNode r, RelMetadataQuery mq);
+      Integer splitCount(RelNode r, RelMetadataQuery mq);
+    }
   }
 
   /** Metadata about the memory use of an operator. */
   public interface Memory extends Metadata {
+    MetadataDef<Memory> DEF = MetadataDef.of(Memory.class,
+        Memory.Handler.class, BuiltInMethod.MEMORY.method,
+        BuiltInMethod.CUMULATIVE_MEMORY_WITHIN_PHASE.method,
+        BuiltInMethod.CUMULATIVE_MEMORY_WITHIN_PHASE_SPLIT.method);
+
     /** Returns the expected amount of memory, in bytes, required by a physical
      * operator implementing this relational expression, across all splits.
      *
@@ -356,6 +512,13 @@ public abstract class BuiltInMetadata {
      *     = cumulativeMemoryWithinPhase / Parallelism.splitCount</blockquote>
      */
     Double cumulativeMemoryWithinPhaseSplit();
+
+    /** Handler API. */
+    interface Handler extends MetadataHandler<Memory> {
+      Double memory(RelNode r, RelMetadataQuery mq);
+      Double cumulativeMemoryWithinPhase(RelNode r, RelMetadataQuery mq);
+      Double cumulativeMemoryWithinPhaseSplit(RelNode r, RelMetadataQuery mq);
+    }
   }
 
   /** The built-in forms of metadata. */

http://git-wip-us.apache.org/repos/asf/calcite/blob/d14040c5/core/src/main/java/org/apache/calcite/rel/metadata/CachingRelMetadataProvider.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/calcite/rel/metadata/CachingRelMetadataProvider.java
 
b/core/src/main/java/org/apache/calcite/rel/metadata/CachingRelMetadataProvider.java
index 8438a8d..202bf4c 100644
--- 
a/core/src/main/java/org/apache/calcite/rel/metadata/CachingRelMetadataProvider.java
+++ 
b/core/src/main/java/org/apache/calcite/rel/metadata/CachingRelMetadataProvider.java
@@ -77,6 +77,11 @@ public class CachingRelMetadataProvider implements 
RelMetadataProvider {
     };
   }
 
+  public <M extends Metadata> Map<Method, MetadataHandler<M>>
+  handlers(MetadataDef<M> def) {
+    return underlyingProvider.handlers(def);
+  }
+
   //~ Inner Classes ----------------------------------------------------------
 
   /** An entry in the cache. Consists of the cached object and the timestamp

http://git-wip-us.apache.org/repos/asf/calcite/blob/d14040c5/core/src/main/java/org/apache/calcite/rel/metadata/ChainedRelMetadataProvider.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/calcite/rel/metadata/ChainedRelMetadataProvider.java
 
b/core/src/main/java/org/apache/calcite/rel/metadata/ChainedRelMetadataProvider.java
index eb8aec5..698bc9c 100644
--- 
a/core/src/main/java/org/apache/calcite/rel/metadata/ChainedRelMetadataProvider.java
+++ 
b/core/src/main/java/org/apache/calcite/rel/metadata/ChainedRelMetadataProvider.java
@@ -20,6 +20,7 @@ import org.apache.calcite.rel.RelNode;
 
 import com.google.common.base.Throwables;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Lists;
 
 import java.lang.reflect.InvocationHandler;
@@ -28,6 +29,7 @@ import java.lang.reflect.Method;
 import java.lang.reflect.Proxy;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Map;
 
 /**
  * Implementation of the {@link RelMetadataProvider}
@@ -51,10 +53,21 @@ public class ChainedRelMetadataProvider implements 
RelMetadataProvider {
   protected ChainedRelMetadataProvider(
       ImmutableList<RelMetadataProvider> providers) {
     this.providers = providers;
+    assert !providers.contains(this);
   }
 
   //~ Methods ----------------------------------------------------------------
 
+  @Override public boolean equals(Object obj) {
+    return obj == this
+        || obj instanceof ChainedRelMetadataProvider
+        && providers.equals(((ChainedRelMetadataProvider) obj).providers);
+  }
+
+  @Override public int hashCode() {
+    return providers.hashCode();
+  }
+
   public <M extends Metadata> UnboundMetadata<M>
   apply(Class<? extends RelNode> relClass,
       final Class<? extends M> metadataClass) {
@@ -91,6 +104,16 @@ public class ChainedRelMetadataProvider implements 
RelMetadataProvider {
     }
   }
 
+  public <M extends Metadata> Map<Method, MetadataHandler<M>>
+  handlers(MetadataDef<M> def) {
+    final ImmutableMap.Builder<Method, MetadataHandler<M>> builder =
+        ImmutableMap.builder();
+    for (RelMetadataProvider provider : providers.reverse()) {
+      builder.putAll(provider.handlers(def));
+    }
+    return builder.build();
+  }
+
   /** Creates a chain. */
   public static RelMetadataProvider of(List<RelMetadataProvider> list) {
     return new ChainedRelMetadataProvider(ImmutableList.copyOf(list));

http://git-wip-us.apache.org/repos/asf/calcite/blob/d14040c5/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 e40f1d4..2abd5b5 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
@@ -25,14 +25,20 @@ import com.google.common.collect.ImmutableList;
  * the methods declared in {@link RelMetadataQuery}.
  */
 public class DefaultRelMetadataProvider extends ChainedRelMetadataProvider {
+  public static final DefaultRelMetadataProvider INSTANCE =
+      new DefaultRelMetadataProvider();
+
   //~ Constructors -----------------------------------------------------------
 
   /**
    * Creates a new default provider. This provider defines "catch-all"
    * handlers for generic RelNodes, so it should always be given lowest
    * priority when chaining.
+   *
+   * <p>Use this constructor only from a sub-class. Otherwise use the singleton
+   * instance, {@link #INSTANCE}.
    */
-  public DefaultRelMetadataProvider() {
+  protected DefaultRelMetadataProvider() {
     super(
         ImmutableList.of(
             RelMdPercentageOriginalRows.SOURCE,

http://git-wip-us.apache.org/repos/asf/calcite/blob/d14040c5/core/src/main/java/org/apache/calcite/rel/metadata/JaninoRelMetadataProvider.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/calcite/rel/metadata/JaninoRelMetadataProvider.java
 
b/core/src/main/java/org/apache/calcite/rel/metadata/JaninoRelMetadataProvider.java
new file mode 100644
index 0000000..c861106
--- /dev/null
+++ 
b/core/src/main/java/org/apache/calcite/rel/metadata/JaninoRelMetadataProvider.java
@@ -0,0 +1,522 @@
+/*
+ * 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.adapter.enumerable.EnumerableAggregate;
+import org.apache.calcite.adapter.enumerable.EnumerableFilter;
+import org.apache.calcite.adapter.enumerable.EnumerableJoin;
+import org.apache.calcite.adapter.enumerable.EnumerableProject;
+import org.apache.calcite.adapter.enumerable.EnumerableTableScan;
+import org.apache.calcite.interpreter.JaninoRexCompiler;
+import org.apache.calcite.linq4j.Ord;
+import org.apache.calcite.linq4j.tree.ClassDeclaration;
+import org.apache.calcite.linq4j.tree.MemberDeclaration;
+import org.apache.calcite.linq4j.tree.Primitive;
+import org.apache.calcite.plan.hep.HepRelVertex;
+import org.apache.calcite.plan.volcano.AbstractConverter;
+import org.apache.calcite.plan.volcano.RelSubset;
+import org.apache.calcite.prepare.CalcitePrepareImpl;
+import org.apache.calcite.rel.AbstractRelNode;
+import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.convert.ConverterImpl;
+import org.apache.calcite.rel.logical.LogicalAggregate;
+import org.apache.calcite.rel.logical.LogicalCalc;
+import org.apache.calcite.rel.logical.LogicalCorrelate;
+import org.apache.calcite.rel.logical.LogicalExchange;
+import org.apache.calcite.rel.logical.LogicalFilter;
+import org.apache.calcite.rel.logical.LogicalIntersect;
+import org.apache.calcite.rel.logical.LogicalJoin;
+import org.apache.calcite.rel.logical.LogicalMinus;
+import org.apache.calcite.rel.logical.LogicalProject;
+import org.apache.calcite.rel.logical.LogicalSort;
+import org.apache.calcite.rel.logical.LogicalTableFunctionScan;
+import org.apache.calcite.rel.logical.LogicalTableModify;
+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.logical.LogicalWindow;
+import org.apache.calcite.rel.stream.LogicalChi;
+import org.apache.calcite.rel.stream.LogicalDelta;
+import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.util.ControlFlowException;
+import org.apache.calcite.util.Pair;
+
+import com.google.common.base.Throwables;
+import com.google.common.cache.CacheBuilder;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.LinkedHashMultimap;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Multimap;
+
+import org.codehaus.commons.compiler.CompileException;
+import org.codehaus.commons.compiler.CompilerFactoryFactory;
+import org.codehaus.commons.compiler.IClassBodyEvaluator;
+import org.codehaus.commons.compiler.ICompilerFactory;
+
+import java.io.IOException;
+import java.io.StringReader;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.CopyOnWriteArraySet;
+import java.util.concurrent.ExecutionException;
+import javax.annotation.Nonnull;
+
+/**
+ * Implementation of the {@link RelMetadataProvider} interface that generates
+ * a class that dispatches to the underlying providers.
+ */
+public class JaninoRelMetadataProvider implements RelMetadataProvider {
+  private final RelMetadataProvider provider;
+
+  // Constants and static fields
+
+  public static final JaninoRelMetadataProvider DEFAULT =
+      JaninoRelMetadataProvider.of(DefaultRelMetadataProvider.INSTANCE);
+
+  private static final Set<Class<? extends RelNode>> ALL_RELS =
+      new CopyOnWriteArraySet<>();
+
+  /** Cache of pre-generated handlers by provider and kind of metadata.
+   * For the cache to be effective, providers should implement identity
+   * correctly. */
+  private static final LoadingCache<Key, MetadataHandler> HANDLERS =
+      CacheBuilder.newBuilder().build(
+          new CacheLoader<Key, MetadataHandler>() {
+            public MetadataHandler load(@Nonnull Key key) {
+              //noinspection unchecked
+              return load3(key.def, key.provider.handlers(key.def),
+                  key.relClasses);
+            }
+          });
+
+  // Pre-register the most common relational operators, to reduce the number of
+  // times we re-generate.
+  static {
+    DEFAULT.register(
+        Arrays.asList(RelNode.class,
+            AbstractRelNode.class,
+            RelSubset.class,
+            HepRelVertex.class,
+            ConverterImpl.class,
+            AbstractConverter.class,
+
+            LogicalAggregate.class,
+            LogicalCalc.class,
+            LogicalCorrelate.class,
+            LogicalExchange.class,
+            LogicalFilter.class,
+            LogicalIntersect.class,
+            LogicalJoin.class,
+            LogicalMinus.class,
+            LogicalProject.class,
+            LogicalSort.class,
+            LogicalTableFunctionScan.class,
+            LogicalTableModify.class,
+            LogicalTableScan.class,
+            LogicalUnion.class,
+            LogicalValues.class,
+            LogicalWindow.class,
+            LogicalChi.class,
+            LogicalDelta.class,
+
+            EnumerableAggregate.class,
+            EnumerableFilter.class,
+            EnumerableProject.class,
+            EnumerableJoin.class,
+            EnumerableTableScan.class));
+  }
+
+  /** Private constructor; use {@link #of}. */
+  private JaninoRelMetadataProvider(RelMetadataProvider provider) {
+    this.provider = provider;
+  }
+
+  /** Creates a JaninoRelMetadataProvider.
+   *
+   * @param provider Underlying provider
+   */
+  public static JaninoRelMetadataProvider of(RelMetadataProvider provider) {
+    if (provider instanceof JaninoRelMetadataProvider) {
+      return (JaninoRelMetadataProvider) provider;
+    }
+    return new JaninoRelMetadataProvider(provider);
+  }
+
+  @Override public boolean equals(Object obj) {
+    return obj == this
+        || obj instanceof JaninoRelMetadataProvider
+        && ((JaninoRelMetadataProvider) obj).provider.equals(provider);
+  }
+
+  @Override public int hashCode() {
+    return 109 + provider.hashCode();
+  }
+
+  public <M extends Metadata> UnboundMetadata<M> apply(
+      Class<? extends RelNode> relClass, Class<? extends M> metadataClass) {
+    throw new UnsupportedOperationException();
+  }
+
+  public <M extends Metadata> Map<Method, MetadataHandler<M>>
+  handlers(MetadataDef<M> def) {
+    return provider.handlers(def);
+  }
+
+  private static <M extends Metadata>
+  MetadataHandler<M> load3(MetadataDef<M> def,
+      Map<Method, MetadataHandler<M>> map,
+      ImmutableList<Class<? extends RelNode>> relClasses) {
+    final StringBuilder buff = new StringBuilder();
+    final String name =
+        "GeneratedMetadataHandler_" + def.metadataClass.getSimpleName();
+    final Set<MetadataHandler> providerSet = new HashSet<>();
+    final List<Pair<String, MetadataHandler>> providerList = new ArrayList<>();
+    //noinspection unchecked
+    final ReflectiveRelMetadataProvider.Space space =
+        new ReflectiveRelMetadataProvider.Space((Map) map);
+    for (MetadataHandler provider : space.providerMap.values()) {
+      if (providerSet.add(provider)) {
+        providerList.add(Pair.of("provider" + (providerSet.size() - 1),
+            provider));
+      }
+    }
+
+    buff.append("  private final java.util.List relClasses;\n");
+    for (Pair<String, MetadataHandler> pair : providerList) {
+      buff.append("  public final ").append(pair.right.getClass().getName())
+          .append(' ').append(pair.left).append(";\n");
+    }
+    buff.append("  public ").append(name).append("(java.util.List relClasses");
+    for (Pair<String, MetadataHandler> pair : providerList) {
+      buff.append(",\n")
+          .append("      ")
+          .append(pair.right.getClass().getName())
+          .append(' ')
+          .append(pair.left);
+    }
+    buff.append(") {\n")
+        .append("    this.relClasses = relClasses;\n");
+
+    for (Pair<String, MetadataHandler> pair : providerList) {
+      buff.append("    this.").append(pair.left).append(" = 
").append(pair.left)
+          .append(";\n");
+    }
+    buff.append("  }\n")
+        .append("  public org.apache.calcite.rel.metadata.MetadataDef getDef() 
{\n")
+        .append("    return ")
+        .append(def.metadataClass.getName())
+        .append(".DEF;\n")
+        .append("  }\n");
+    for (Ord<Method> method : Ord.zip(def.methods)) {
+      buff.append("  public ")
+          .append(method.e.getReturnType().getName())
+          .append(" ")
+          .append(method.e.getName())
+          .append("(\n")
+          .append("      org.apache.calcite.rel.RelNode r,\n")
+          .append("      org.apache.calcite.rel.metadata.RelMetadataQuery mq");
+      paramList(buff, method.e)
+          .append(") {\n");
+      buff.append("    final java.util.List key = ")
+          .append(
+              (method.e.getParameterTypes().length < 4
+              ? org.apache.calcite.runtime.FlatLists.class
+              : ImmutableList.class).getName())
+          .append(".of(")
+          .append(def.metadataClass.getName());
+      if (method.i == 0) {
+        buff.append(".DEF");
+      } else {
+        buff.append(".DEF.methods.get(")
+            .append(method.i)
+            .append(")");
+      }
+      buff.append(", r");
+      safeArgList(buff, method.e)
+          .append(");\n")
+          .append("    final Object v = mq.map.get(key);\n")
+          .append("    if (v != null) {\n")
+          .append("      if (v == ")
+          .append(NullSentinel.class.getName())
+          .append(".ACTIVE) {\n")
+          .append("        throw ")
+          .append(CyclicMetadataException.class.getName())
+          .append(".INSTANCE;\n")
+          .append("      }\n")
+          .append("      return (")
+          .append(method.e.getReturnType().getName())
+          .append(") v;\n")
+          .append("    }\n")
+          .append("    mq.map.put(key,")
+          .append(NullSentinel.class.getName())
+          .append(".ACTIVE);\n")
+          .append("    try {\n")
+          .append("      final ")
+          .append(method.e.getReturnType().getName())
+          .append(" x = ")
+          .append(method.e.getName())
+          .append("_(r, mq");
+      argList(buff, method.e)
+          .append(");\n")
+          .append("      mq.map.put(key, x);\n")
+          .append("      return x;\n")
+          .append("    } catch (")
+          .append(NoHandler.class.getName())
+          .append(" e) {\n")
+          .append("      mq.map.remove(key);\n")
+          .append("      throw e;\n")
+          .append("    }\n")
+          .append("  }\n")
+          .append("\n")
+          .append("  private ")
+          .append(method.e.getReturnType().getName())
+          .append(" ")
+          .append(method.e.getName())
+          .append("_(\n")
+          .append("      org.apache.calcite.rel.RelNode r,\n")
+          .append("      org.apache.calcite.rel.metadata.RelMetadataQuery mq");
+      paramList(buff, method.e)
+          .append(") {\n");
+      buff.append("    switch (relClasses.indexOf(r.getClass())) {\n");
+
+      // Build a list of clauses, grouping clauses that have the same action.
+      final Multimap<String, Integer> clauses = LinkedHashMultimap.create();
+      final StringBuilder buf2 = new StringBuilder();
+      for (Ord<Class<? extends RelNode>> relClass : Ord.zip(relClasses)) {
+        if (relClass.e == HepRelVertex.class) {
+          buf2.append("      return ")
+              .append(method.e.getName())
+              .append("(((")
+              .append(relClass.e.getName())
+              .append(") r).getCurrentRel(), mq");
+          argList(buf2, method.e)
+              .append(");\n");
+        } else {
+          final Method handler = space.find(relClass.e, method.e);
+          final String v = findProvider(providerList, 
handler.getDeclaringClass());
+          buf2.append("      return ")
+              .append(v)
+              .append(".")
+              .append(method.e.getName())
+              .append("((")
+              .append(handler.getParameterTypes()[0].getName())
+              .append(") r, mq");
+          argList(buf2, method.e)
+              .append(");\n");
+        }
+        clauses.put(buf2.toString(), relClass.i);
+        buf2.setLength(0);
+      }
+      buf2.append("      throw new ")
+          .append(NoHandler.class.getName())
+          .append("(r.getClass());\n")
+          .append("    }\n")
+          .append("  }\n");
+      clauses.put(buf2.toString(), -1);
+      for (Map.Entry<String, Collection<Integer>> pair : 
clauses.asMap().entrySet()) {
+        if (pair.getValue().contains(relClasses.indexOf(RelNode.class))) {
+          buff.append("    default:\n");
+        } else {
+          for (Integer integer : pair.getValue()) {
+            buff.append("    case ").append(integer).append(":\n");
+          }
+        }
+        buff.append(pair.getKey());
+      }
+    }
+    ClassDeclaration decl = new ClassDeclaration(0, name, Object.class,
+        ImmutableList.<Type>of(), ImmutableList.<MemberDeclaration>of());
+    final List<Object> argList = new 
ArrayList<Object>(Pair.right(providerList));
+    argList.add(0, ImmutableList.copyOf(relClasses));
+    try {
+      return compile(decl, buff.toString(), def, argList);
+    } catch (CompileException | IOException e) {
+      System.out.println(buff);
+      throw Throwables.propagate(e);
+    }
+  }
+
+  private static String
+  findProvider(List<Pair<String, MetadataHandler>> providerList,
+      Class<?> declaringClass) {
+    for (Pair<String, MetadataHandler> pair : providerList) {
+      if (declaringClass.isInstance(pair.right)) {
+        return pair.left;
+      }
+    }
+    throw new AssertionError("not found: " + declaringClass);
+  }
+
+  /** Returns e.g. ", ignoreNulls". */
+  private static StringBuilder argList(StringBuilder buff, Method method) {
+    for (Ord<Class<?>> t : Ord.zip(method.getParameterTypes())) {
+      buff.append(", a").append(t.i);
+    }
+    return buff;
+  }
+
+  /** Returns e.g. ", ignoreNulls". */
+  private static StringBuilder safeArgList(StringBuilder buff, Method method) {
+    for (Ord<Class<?>> t : Ord.zip(method.getParameterTypes())) {
+      if (Primitive.is(t.e)) {
+        buff.append(", a").append(t.i);
+      } else if (RexNode.class.isAssignableFrom(t.e)) {
+        // For RexNode, convert to string, because equals does not look deep.
+        //   a1 == null ? "" : a1.toString()
+        buff.append(", a").append(t.i).append(" == null ? \"\" : a")
+            .append(t.i).append(".toString()");
+      } else {
+        buff.append(", ") .append(NullSentinel.class.getName())
+            .append(".mask(a").append(t.i).append(")");
+      }
+    }
+    return buff;
+  }
+
+  /** Returns e.g. ",\n boolean ignoreNulls". */
+  private static StringBuilder paramList(StringBuilder buff, Method method) {
+    for (Ord<Class<?>> t : Ord.zip(method.getParameterTypes())) {
+      buff.append(",\n      ").append(t.e.getName()).append(" a").append(t.i);
+    }
+    return buff;
+  }
+
+  static <M extends Metadata> MetadataHandler<M>
+  compile(ClassDeclaration expr, String s, MetadataDef<M> def,
+      List<Object> argList) throws CompileException, IOException {
+    final ICompilerFactory compilerFactory;
+    try {
+      compilerFactory = CompilerFactoryFactory.getDefaultCompilerFactory();
+    } catch (Exception e) {
+      throw new IllegalStateException(
+          "Unable to instantiate java compiler", e);
+    }
+    final IClassBodyEvaluator cbe = compilerFactory.newClassBodyEvaluator();
+    cbe.setClassName(expr.name);
+    cbe.setImplementedInterfaces(new Class[]{def.handlerClass});
+    cbe.setParentClassLoader(JaninoRexCompiler.class.getClassLoader());
+    if (CalcitePrepareImpl.DEBUG) {
+      // Add line numbers to the generated janino class
+      cbe.setDebuggingInformation(true, true, true);
+      System.out.println(s);
+    }
+    cbe.cook(new StringReader(s));
+    final Constructor constructor = 
cbe.getClazz().getDeclaredConstructors()[0];
+    final Object o;
+    try {
+      o = constructor.newInstance(argList.toArray());
+    } catch (InstantiationException
+        | IllegalAccessException
+        | InvocationTargetException e) {
+      throw Throwables.propagate(e);
+    }
+    return def.handlerClass.cast(o);
+  }
+
+  synchronized <M extends Metadata, H extends MetadataHandler<M>> H
+  create(MetadataDef<M> def) {
+    try {
+      final Key key = new Key((MetadataDef) def, provider,
+          ImmutableList.copyOf(ALL_RELS));
+      //noinspection unchecked
+      return (H) HANDLERS.get(key);
+    } catch (ExecutionException e) {
+      throw Throwables.propagate(e);
+    }
+  }
+
+  synchronized <M extends Metadata, H extends MetadataHandler<M>> H
+  revise(Class<? extends RelNode> rClass, MetadataDef<M> def) {
+    if (ALL_RELS.add(rClass)) {
+      HANDLERS.invalidateAll();
+    }
+    //noinspection unchecked
+    return (H) create(def);
+  }
+
+  /** Registers some classes. Does not flush the providers, but next time we
+   * need to generate a provider, it will handle all of these classes. So,
+   * calling this method reduces the number of times we need to re-generate. */
+  public void register(Iterable<Class<? extends RelNode>> classes) {
+    // Register the classes and their base classes up to RelNode. Don't bother
+    // to remove duplicates; addAll will do that.
+    final List<Class<? extends RelNode>> list = Lists.newArrayList(classes);
+    for (int i = 0; i < list.size(); i++) {
+      final Class<? extends RelNode> c = list.get(i);
+      final Class s = c.getSuperclass();
+      if (s != null && RelNode.class.isAssignableFrom(s)) {
+        //noinspection unchecked
+        list.add(s);
+      }
+    }
+    if (ALL_RELS.addAll(list)) {
+      HANDLERS.invalidateAll();
+    }
+  }
+
+  /** Exception that indicates there there should be a handler for
+   * this class but there is not. The action is probably to
+   * re-generate the handler class. */
+  public static class NoHandler extends ControlFlowException {
+    public final Class<? extends RelNode> relClass;
+
+    public NoHandler(Class<? extends RelNode> relClass) {
+      this.relClass = relClass;
+    }
+  }
+
+  /** Key for the cache. */
+  private static class Key {
+    public final MetadataDef def;
+    public final RelMetadataProvider provider;
+    public final ImmutableList<Class<? extends RelNode>> relClasses;
+
+    private Key(MetadataDef def, RelMetadataProvider provider,
+        ImmutableList<Class<? extends RelNode>> relClassList) {
+      this.def = def;
+      this.provider = provider;
+      this.relClasses = relClassList;
+    }
+
+    @Override public int hashCode() {
+      return (def.hashCode() * 37
+          + provider.hashCode()) * 37
+          + relClasses.hashCode();
+    }
+
+    @Override public boolean equals(Object obj) {
+      return this == obj
+          || obj instanceof Key
+          && ((Key) obj).def.equals(def)
+          && ((Key) obj).provider.equals(provider)
+          && ((Key) obj).relClasses.equals(relClasses);
+    }
+  }
+}
+
+// End JaninoRelMetadataProvider.java

http://git-wip-us.apache.org/repos/asf/calcite/blob/d14040c5/core/src/main/java/org/apache/calcite/rel/metadata/MetadataDef.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/calcite/rel/metadata/MetadataDef.java 
b/core/src/main/java/org/apache/calcite/rel/metadata/MetadataDef.java
new file mode 100644
index 0000000..e7924e7
--- /dev/null
+++ b/core/src/main/java/org/apache/calcite/rel/metadata/MetadataDef.java
@@ -0,0 +1,67 @@
+/*
+ * 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.Pair;
+
+import com.google.common.collect.ImmutableList;
+
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Definition of metadata.
+ *
+ * @param <M> Kind of metadata
+ */
+public class MetadataDef<M extends Metadata> {
+  public final Class<M> metadataClass;
+  public final Class<? extends MetadataHandler<M>> handlerClass;
+  public final ImmutableList<Method> methods;
+
+  private MetadataDef(Class<M> metadataClass,
+      Class<? extends MetadataHandler<M>> handlerClass, Method... methods) {
+    this.metadataClass = metadataClass;
+    this.handlerClass = handlerClass;
+    this.methods = ImmutableList.copyOf(methods);
+    final Method[] handlerMethods = handlerClass.getDeclaredMethods();
+
+    // Handler must have the same methods as Metadata, each method having
+    // additional "subclass-of-RelNode, RelMetadataQuery" parameters.
+    assert handlerMethods.length == methods.length;
+    for (Pair<Method, Method> pair : Pair.zip(methods, handlerMethods)) {
+      final List<Class<?>> leftTypes =
+          Arrays.asList(pair.left.getParameterTypes());
+      final List<Class<?>> rightTypes =
+          Arrays.asList(pair.right.getParameterTypes());
+      assert leftTypes.size() + 2 == rightTypes.size();
+      assert RelNode.class.isAssignableFrom(rightTypes.get(0));
+      assert RelMetadataQuery.class == rightTypes.get(1);
+      assert leftTypes.equals(rightTypes.subList(2, rightTypes.size()));
+    }
+  }
+
+  /** Creates a {@link org.apache.calcite.rel.metadata.MetadataDef}. */
+  public static <M extends Metadata> MetadataDef<M> of(Class<M> metadataClass,
+      Class<? extends MetadataHandler<M>> handlerClass, Method... methods) {
+    return new MetadataDef<>(metadataClass, handlerClass, methods);
+  }
+}
+
+// End MetadataDef.java

http://git-wip-us.apache.org/repos/asf/calcite/blob/d14040c5/core/src/main/java/org/apache/calcite/rel/metadata/MetadataHandler.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/calcite/rel/metadata/MetadataHandler.java 
b/core/src/main/java/org/apache/calcite/rel/metadata/MetadataHandler.java
new file mode 100644
index 0000000..95f8d62
--- /dev/null
+++ b/core/src/main/java/org/apache/calcite/rel/metadata/MetadataHandler.java
@@ -0,0 +1,28 @@
+/*
+ * 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;
+
+/**
+ * Marker interface for a handler of metadata.
+ *
+ * @param <M> Kind of metadata
+ */
+public interface MetadataHandler<M extends Metadata> {
+  MetadataDef<M> getDef();
+}
+
+// End MetadataHandler.java

http://git-wip-us.apache.org/repos/asf/calcite/blob/d14040c5/core/src/main/java/org/apache/calcite/rel/metadata/NullSentinel.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/calcite/rel/metadata/NullSentinel.java 
b/core/src/main/java/org/apache/calcite/rel/metadata/NullSentinel.java
index 3f5dcf6..4215211 100644
--- a/core/src/main/java/org/apache/calcite/rel/metadata/NullSentinel.java
+++ b/core/src/main/java/org/apache/calcite/rel/metadata/NullSentinel.java
@@ -17,18 +17,22 @@
 package org.apache.calcite.rel.metadata;
 
 /** Placeholder for null values. */
-// package-private
-enum NullSentinel {
-  INSTANCE;
+public enum NullSentinel {
+  /** Placeholder for a null value. */
+  INSTANCE,
 
-  static Comparable mask(Comparable value) {
+  /** Placeholder that means that a request for metadata is already active,
+   * therefore this request forms a cycle. */
+  ACTIVE;
+
+  public static Comparable mask(Comparable value) {
     if (value == null) {
       return INSTANCE;
     }
     return value;
   }
 
-  static Object mask(Object value) {
+  public static Object mask(Object value) {
     if (value == null) {
       return INSTANCE;
     }

http://git-wip-us.apache.org/repos/asf/calcite/blob/d14040c5/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 8cdb55a..5f6d680 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
@@ -27,9 +27,7 @@ import org.apache.calcite.util.Util;
 
 import com.google.common.base.Throwables;
 import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
-import com.google.common.collect.Sets;
+import com.google.common.collect.ImmutableMap;
 
 import java.lang.reflect.InvocationHandler;
 import java.lang.reflect.InvocationTargetException;
@@ -39,6 +37,8 @@ import java.lang.reflect.Proxy;
 import java.lang.reflect.UndeclaredThrowableException;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -63,6 +63,7 @@ public class ReflectiveRelMetadataProvider
   //~ Instance fields --------------------------------------------------------
   private final ConcurrentMap<Class<RelNode>, UnboundMetadata> map;
   private final Class<? extends Metadata> metadataClass0;
+  private final ImmutableMap<Method, MetadataHandler> handlerMap;
 
   //~ Constructors -----------------------------------------------------------
 
@@ -71,13 +72,16 @@ public class ReflectiveRelMetadataProvider
    *
    * @param map Map
    * @param metadataClass0 Metadata class
+   * @param handlerMap Methods handled and the objects to call them on
    */
   protected ReflectiveRelMetadataProvider(
       ConcurrentMap<Class<RelNode>, UnboundMetadata> map,
-      Class<? extends Metadata> metadataClass0) {
+      Class<? extends Metadata> metadataClass0,
+      Map<Method, MetadataHandler> handlerMap) {
     assert !map.isEmpty() : "are your methods named wrong?";
     this.map = map;
     this.metadataClass0 = metadataClass0;
+    this.handlerMap = ImmutableMap.copyOf(handlerMap);
   }
 
   /** Returns an implementation of {@link RelMetadataProvider} that scans for
@@ -98,54 +102,32 @@ public class ReflectiveRelMetadataProvider
    * or {@link org.apache.calcite.rel.core.Filter}.</p>
    */
   public static RelMetadataProvider reflectiveSource(Method method,
-      Object target) {
+      MetadataHandler target) {
     return reflectiveSource(target, ImmutableList.of(method));
   }
 
   /** Returns a reflective metadata provider that implements several
    * methods. */
-  public static RelMetadataProvider reflectiveSource(Object target,
+  public static RelMetadataProvider reflectiveSource(MetadataHandler target,
       Method... methods) {
     return reflectiveSource(target, ImmutableList.copyOf(methods));
   }
 
-  private static RelMetadataProvider reflectiveSource(final Object target,
+  private static RelMetadataProvider
+  reflectiveSource(final MetadataHandler target,
       final ImmutableList<Method> methods) {
-    assert methods.size() > 0;
-    final Method method0 = methods.get(0);
-    @SuppressWarnings("unchecked")
-    final Class<Metadata> metadataClass0 = (Class) method0.getDeclaringClass();
-    assert Metadata.class.isAssignableFrom(metadataClass0);
-    for (Method method : methods) {
-      assert method.getDeclaringClass() == metadataClass0;
-    }
-
-    // Find the distinct set of RelNode classes handled by this provider,
-    // ordered base-class first.
-    final Set<Class<RelNode>> classes = Sets.newHashSet();
-    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 Space2 space = Space2.create(target, methods);
 
-    // This needs to be a councurrent map since RelMetadataProvider are cached 
in static
+    // This needs to be a concurrent map since RelMetadataProvider are cached 
in static
     // fields, thus the map is subject to concurrent modifications later.
     // See map.put in 
org.apache.calcite.rel.metadata.ReflectiveRelMetadataProvider.apply(
     // java.lang.Class<? extends org.apache.calcite.rel.RelNode>)
     final ConcurrentMap<Class<RelNode>, UnboundMetadata> methodsMap = new 
ConcurrentHashMap<>();
-    for (Class<RelNode> key : classes) {
+    for (Class<RelNode> key : space.classes) {
       ImmutableNullableList.Builder<Method> builder =
           ImmutableNullableList.builder();
       for (final Method method : methods) {
-        builder.add(find(handlerMap, key, method));
+        builder.add(space.find(key, method));
       }
       final List<Method> handlerMethods = builder.build();
       final UnboundMetadata function =
@@ -153,8 +135,8 @@ public class ReflectiveRelMetadataProvider
             public Metadata bind(final RelNode rel,
                 final RelMetadataQuery mq) {
               return (Metadata) Proxy.newProxyInstance(
-                  metadataClass0.getClassLoader(),
-                  new Class[]{metadataClass0},
+                  space.metadataClass0.getClassLoader(),
+                  new Class[]{space.metadataClass0},
                   new InvocationHandler() {
                     public Object invoke(Object proxy, Method method,
                         Object[] args) throws Throwable {
@@ -170,7 +152,7 @@ public class ReflectiveRelMetadataProvider
                       }
                       if (method.equals(
                           BuiltInMethod.OBJECT_TO_STRING.method)) {
-                        return metadataClass0.getSimpleName() + "(" + rel
+                        return space.metadataClass0.getSimpleName() + "(" + rel
                             + ")";
                       }
                       int i = methods.indexOf(method);
@@ -206,7 +188,7 @@ public class ReflectiveRelMetadataProvider
                         }
                         key = FlatLists.copyOf(args2);
                       }
-                      if (!mq.set.add(key)) {
+                      if (mq.map.put(key, NullSentinel.INSTANCE) != null) {
                         throw CyclicMetadataException.INSTANCE;
                       }
                       try {
@@ -216,7 +198,7 @@ public class ReflectiveRelMetadataProvider
                         Throwables.propagateIfPossible(e.getCause());
                         throw e;
                       } finally {
-                        mq.set.remove(key);
+                        mq.map.remove(key);
                       }
                     }
                   });
@@ -224,39 +206,21 @@ public class ReflectiveRelMetadataProvider
           };
       methodsMap.put(key, function);
     }
-    return new ReflectiveRelMetadataProvider(methodsMap, metadataClass0);
+    return new ReflectiveRelMetadataProvider(methodsMap, space.metadataClass0,
+        space.providerMap);
   }
 
-  /** 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}. */
-  @SuppressWarnings({ "unchecked", "SuspiciousMethodCalls" })
-  private static Method find(Map<Pair<Class<RelNode>, Method>,
-      Method> handlerMap, Class<RelNode> relNodeClass, Method method) {
-    List<Class<RelNode>> newSources = Lists.newArrayList();
-    Method implementingMethod;
-    while (relNodeClass != null) {
-      implementingMethod = handlerMap.get(Pair.of(relNodeClass, method));
-      if (implementingMethod != null) {
-        return implementingMethod;
-      } else {
-        newSources.add(relNodeClass);
-      }
-      for (Class<?> clazz : relNodeClass.getInterfaces()) {
-        if (RelNode.class.isAssignableFrom(clazz)) {
-          implementingMethod = handlerMap.get(Pair.of(clazz, method));
-          if (implementingMethod != null) {
-            return implementingMethod;
-          }
-        }
-      }
-      if (RelNode.class.isAssignableFrom(relNodeClass.getSuperclass())) {
-        relNodeClass = (Class<RelNode>) relNodeClass.getSuperclass();
-      } else {
-        relNodeClass = null;
+  public <M extends Metadata> Map<Method, MetadataHandler<M>>
+  handlers(MetadataDef<M> def) {
+    final ImmutableMap.Builder<Method, MetadataHandler<M>> builder =
+        ImmutableMap.builder();
+    for (Map.Entry<Method, MetadataHandler> entry : handlerMap.entrySet()) {
+      if (def.methods.contains(entry.getKey())) {
+        //noinspection unchecked
+        builder.put(entry.getKey(), entry.getValue());
       }
     }
-    return null;
+    return builder.build();
   }
 
   private static boolean couldImplement(Method handlerMethod, Method method) {
@@ -318,6 +282,91 @@ public class ReflectiveRelMetadataProvider
       }
     }
   }
+
+  /** Workspace for computing which methods can act as handlers for
+   * given metadata methods. */
+  static class Space {
+    final Set<Class<RelNode>> classes = new HashSet<>();
+    final Map<Pair<Class<RelNode>, Method>, Method> handlerMap = new 
HashMap<>();
+    final ImmutableMap<Method, MetadataHandler> providerMap;
+
+    Space(Map<Method, MetadataHandler> providerMap) {
+      this.providerMap = ImmutableMap.copyOf(providerMap);
+
+      // Find the distinct set of RelNode classes handled by this provider,
+      // ordered base-class first.
+      for (Map.Entry<Method, MetadataHandler> entry : providerMap.entrySet()) {
+        final Method method = entry.getKey();
+        final MetadataHandler provider = entry.getValue();
+        for (final Method handlerMethod : provider.getClass().getMethods()) {
+          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);
+          }
+        }
+      }
+    }
+
+    /** 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}. */
+    @SuppressWarnings({ "unchecked", "SuspiciousMethodCalls" })
+    Method find(Class<? extends RelNode> relNodeClass, Method method) {
+      Method implementingMethod;
+      while (relNodeClass != null) {
+        implementingMethod = handlerMap.get(Pair.of(relNodeClass, method));
+        if (implementingMethod != null) {
+          return implementingMethod;
+        }
+        for (Class<?> clazz : relNodeClass.getInterfaces()) {
+          if (RelNode.class.isAssignableFrom(clazz)) {
+            implementingMethod = handlerMap.get(Pair.of(clazz, method));
+            if (implementingMethod != null) {
+              return implementingMethod;
+            }
+          }
+        }
+        if (RelNode.class.isAssignableFrom(relNodeClass.getSuperclass())) {
+          relNodeClass = (Class<RelNode>) relNodeClass.getSuperclass();
+        } else {
+          relNodeClass = null;
+        }
+      }
+      return null;
+    }
+  }
+
+  /** Extended work space. */
+  static class Space2 extends Space {
+    private Class<Metadata> metadataClass0;
+
+    public Space2(Class<Metadata> metadataClass0,
+        ImmutableMap<Method, MetadataHandler> providerMap) {
+      super(providerMap);
+      this.metadataClass0 = metadataClass0;
+    }
+
+    public static Space2 create(MetadataHandler target,
+        ImmutableList<Method> methods) {
+      assert methods.size() > 0;
+      final Method method0 = methods.get(0);
+      //noinspection unchecked
+      Class<Metadata> metadataClass0 = (Class) method0.getDeclaringClass();
+      assert Metadata.class.isAssignableFrom(metadataClass0);
+      for (Method method : methods) {
+        assert method.getDeclaringClass() == metadataClass0;
+      }
+
+      final ImmutableMap.Builder<Method, MetadataHandler> providerBuilder =
+          ImmutableMap.builder();
+      for (final Method method : methods) {
+        providerBuilder.put(method, target);
+      }
+      return new Space2(metadataClass0, providerBuilder.build());
+    }
+  }
 }
 
 // End ReflectiveRelMetadataProvider.java

http://git-wip-us.apache.org/repos/asf/calcite/blob/d14040c5/core/src/main/java/org/apache/calcite/rel/metadata/RelMdCollation.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/calcite/rel/metadata/RelMdCollation.java 
b/core/src/main/java/org/apache/calcite/rel/metadata/RelMdCollation.java
index 92b325d..2867657 100644
--- a/core/src/main/java/org/apache/calcite/rel/metadata/RelMdCollation.java
+++ b/core/src/main/java/org/apache/calcite/rel/metadata/RelMdCollation.java
@@ -67,7 +67,8 @@ import java.util.TreeSet;
  * {@link org.apache.calcite.rel.metadata.RelMetadataQuery#collations}
  * for the standard logical algebra.
  */
-public class RelMdCollation {
+public class RelMdCollation
+    implements MetadataHandler<BuiltInMetadata.Collation> {
   public static final RelMetadataProvider SOURCE =
       ReflectiveRelMetadataProvider.reflectiveSource(
           BuiltInMethod.COLLATIONS.method, new RelMdCollation());
@@ -78,6 +79,10 @@ public class RelMdCollation {
 
   //~ Methods ----------------------------------------------------------------
 
+  public MetadataDef<BuiltInMetadata.Collation> getDef() {
+    return BuiltInMetadata.Collation.DEF;
+  }
+
   /** Catch-all implementation for
    * {@link BuiltInMetadata.Collation#collations()},
    * invoked using reflection, for any relational expression not

http://git-wip-us.apache.org/repos/asf/calcite/blob/d14040c5/core/src/main/java/org/apache/calcite/rel/metadata/RelMdColumnOrigins.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/calcite/rel/metadata/RelMdColumnOrigins.java 
b/core/src/main/java/org/apache/calcite/rel/metadata/RelMdColumnOrigins.java
index 3038260..9944b17 100644
--- a/core/src/main/java/org/apache/calcite/rel/metadata/RelMdColumnOrigins.java
+++ b/core/src/main/java/org/apache/calcite/rel/metadata/RelMdColumnOrigins.java
@@ -42,7 +42,8 @@ import java.util.Set;
  * RelMdColumnOrigins supplies a default implementation of
  * {@link RelMetadataQuery#getColumnOrigins} for the standard logical algebra.
  */
-public class RelMdColumnOrigins {
+public class RelMdColumnOrigins
+    implements MetadataHandler<BuiltInMetadata.ColumnOrigin> {
   public static final RelMetadataProvider SOURCE =
       ReflectiveRelMetadataProvider.reflectiveSource(
           BuiltInMethod.COLUMN_ORIGIN.method, new RelMdColumnOrigins());
@@ -53,6 +54,10 @@ public class RelMdColumnOrigins {
 
   //~ Methods ----------------------------------------------------------------
 
+  public MetadataDef<BuiltInMetadata.ColumnOrigin> getDef() {
+    return BuiltInMetadata.ColumnOrigin.DEF;
+  }
+
   public Set<RelColumnOrigin> getColumnOrigins(Aggregate rel,
       RelMetadataQuery mq, int iOutputColumn) {
     if (iOutputColumn < rel.getGroupCount()) {

http://git-wip-us.apache.org/repos/asf/calcite/blob/d14040c5/core/src/main/java/org/apache/calcite/rel/metadata/RelMdColumnUniqueness.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/calcite/rel/metadata/RelMdColumnUniqueness.java 
b/core/src/main/java/org/apache/calcite/rel/metadata/RelMdColumnUniqueness.java
index c872075..60fefb5 100644
--- 
a/core/src/main/java/org/apache/calcite/rel/metadata/RelMdColumnUniqueness.java
+++ 
b/core/src/main/java/org/apache/calcite/rel/metadata/RelMdColumnUniqueness.java
@@ -55,7 +55,8 @@ import java.util.Set;
  * RelMdColumnUniqueness supplies a default implementation of
  * {@link RelMetadataQuery#areColumnsUnique} for the standard logical algebra.
  */
-public class RelMdColumnUniqueness {
+public class RelMdColumnUniqueness
+    implements MetadataHandler<BuiltInMetadata.ColumnUniqueness> {
   public static final RelMetadataProvider SOURCE =
       ReflectiveRelMetadataProvider.reflectiveSource(
           BuiltInMethod.COLUMN_UNIQUENESS.method, new RelMdColumnUniqueness());
@@ -66,6 +67,10 @@ public class RelMdColumnUniqueness {
 
   //~ Methods ----------------------------------------------------------------
 
+  public MetadataDef<BuiltInMetadata.ColumnUniqueness> getDef() {
+    return BuiltInMetadata.ColumnUniqueness.DEF;
+  }
+
   public Boolean areColumnsUnique(TableScan rel, RelMetadataQuery mq,
       ImmutableBitSet columns, boolean ignoreNulls) {
     return rel.getTable().isKey(columns);

http://git-wip-us.apache.org/repos/asf/calcite/blob/d14040c5/core/src/main/java/org/apache/calcite/rel/metadata/RelMdDistinctRowCount.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/calcite/rel/metadata/RelMdDistinctRowCount.java 
b/core/src/main/java/org/apache/calcite/rel/metadata/RelMdDistinctRowCount.java
index eef245e..bf3fc3a 100644
--- 
a/core/src/main/java/org/apache/calcite/rel/metadata/RelMdDistinctRowCount.java
+++ 
b/core/src/main/java/org/apache/calcite/rel/metadata/RelMdDistinctRowCount.java
@@ -45,7 +45,8 @@ import java.util.List;
  * {@link RelMetadataQuery#getDistinctRowCount} for the standard logical
  * algebra.
  */
-public class RelMdDistinctRowCount {
+public class RelMdDistinctRowCount
+    implements MetadataHandler<BuiltInMetadata.DistinctRowCount> {
   public static final RelMetadataProvider SOURCE =
       ReflectiveRelMetadataProvider.reflectiveSource(
           BuiltInMethod.DISTINCT_ROW_COUNT.method, new 
RelMdDistinctRowCount());
@@ -56,6 +57,10 @@ public class RelMdDistinctRowCount {
 
   //~ Methods ----------------------------------------------------------------
 
+  public MetadataDef<BuiltInMetadata.DistinctRowCount> getDef() {
+    return BuiltInMetadata.DistinctRowCount.DEF;
+  }
+
   /** Catch-all implementation for
    * {@link 
BuiltInMetadata.DistinctRowCount#getDistinctRowCount(ImmutableBitSet, RexNode)},
    * invoked using reflection.

http://git-wip-us.apache.org/repos/asf/calcite/blob/d14040c5/core/src/main/java/org/apache/calcite/rel/metadata/RelMdDistribution.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/calcite/rel/metadata/RelMdDistribution.java 
b/core/src/main/java/org/apache/calcite/rel/metadata/RelMdDistribution.java
index 80f4e37..b594e4d 100644
--- a/core/src/main/java/org/apache/calcite/rel/metadata/RelMdDistribution.java
+++ b/core/src/main/java/org/apache/calcite/rel/metadata/RelMdDistribution.java
@@ -46,7 +46,8 @@ import java.util.List;
  * {@link RelMetadataQuery#distribution}
  * for the standard logical algebra.
  */
-public class RelMdDistribution {
+public class RelMdDistribution
+    implements MetadataHandler<BuiltInMetadata.Distribution> {
   public static final RelMetadataProvider SOURCE =
       ReflectiveRelMetadataProvider.reflectiveSource(
           BuiltInMethod.DISTRIBUTION.method, new RelMdDistribution());
@@ -57,6 +58,10 @@ public class RelMdDistribution {
 
   //~ Methods ----------------------------------------------------------------
 
+  public MetadataDef<BuiltInMetadata.Distribution> getDef() {
+    return BuiltInMetadata.Distribution.DEF;
+  }
+
   /** Fallback method to deduce distribution for any relational expression not
    * handled by a more specific method.
    *

http://git-wip-us.apache.org/repos/asf/calcite/blob/d14040c5/core/src/main/java/org/apache/calcite/rel/metadata/RelMdExplainVisibility.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/calcite/rel/metadata/RelMdExplainVisibility.java
 
b/core/src/main/java/org/apache/calcite/rel/metadata/RelMdExplainVisibility.java
index bf51f2f..473b30a 100644
--- 
a/core/src/main/java/org/apache/calcite/rel/metadata/RelMdExplainVisibility.java
+++ 
b/core/src/main/java/org/apache/calcite/rel/metadata/RelMdExplainVisibility.java
@@ -24,7 +24,8 @@ import org.apache.calcite.util.BuiltInMethod;
  * RelMdExplainVisibility supplies a default implementation of
  * {@link RelMetadataQuery#isVisibleInExplain} for the standard logical 
algebra.
  */
-public class RelMdExplainVisibility {
+public class RelMdExplainVisibility
+    implements MetadataHandler<BuiltInMetadata.ExplainVisibility> {
   public static final RelMetadataProvider SOURCE =
       ReflectiveRelMetadataProvider.reflectiveSource(
           BuiltInMethod.EXPLAIN_VISIBILITY.method,
@@ -36,6 +37,10 @@ public class RelMdExplainVisibility {
 
   //~ Methods ----------------------------------------------------------------
 
+  public MetadataDef<BuiltInMetadata.ExplainVisibility> getDef() {
+    return BuiltInMetadata.ExplainVisibility.DEF;
+  }
+
   /** Catch-all implementation for
    * {@link 
BuiltInMetadata.ExplainVisibility#isVisibleInExplain(SqlExplainLevel)},
    * invoked using reflection.

http://git-wip-us.apache.org/repos/asf/calcite/blob/d14040c5/core/src/main/java/org/apache/calcite/rel/metadata/RelMdMaxRowCount.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/calcite/rel/metadata/RelMdMaxRowCount.java 
b/core/src/main/java/org/apache/calcite/rel/metadata/RelMdMaxRowCount.java
index ab9739f..dd811a7 100644
--- a/core/src/main/java/org/apache/calcite/rel/metadata/RelMdMaxRowCount.java
+++ b/core/src/main/java/org/apache/calcite/rel/metadata/RelMdMaxRowCount.java
@@ -38,13 +38,18 @@ import org.apache.calcite.util.Util;
  * RelMdMaxRowCount supplies a default implementation of
  * {@link RelMetadataQuery#getMaxRowCount} for the standard logical algebra.
  */
-public class RelMdMaxRowCount {
+public class RelMdMaxRowCount
+    implements MetadataHandler<BuiltInMetadata.MaxRowCount> {
   public static final RelMetadataProvider SOURCE =
       ReflectiveRelMetadataProvider.reflectiveSource(
           BuiltInMethod.MAX_ROW_COUNT.method, new RelMdMaxRowCount());
 
   //~ Methods ----------------------------------------------------------------
 
+  public MetadataDef<BuiltInMetadata.MaxRowCount> getDef() {
+    return BuiltInMetadata.MaxRowCount.DEF;
+  }
+
   public Double getMaxRowCount(Union rel, RelMetadataQuery mq) {
     double rowCount = 0.0;
     for (RelNode input : rel.getInputs()) {

http://git-wip-us.apache.org/repos/asf/calcite/blob/d14040c5/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
index c760b0e..359af35 100644
--- a/core/src/main/java/org/apache/calcite/rel/metadata/RelMdMemory.java
+++ b/core/src/main/java/org/apache/calcite/rel/metadata/RelMdMemory.java
@@ -27,7 +27,7 @@ import org.apache.calcite.util.BuiltInMethod;
  * @see RelMetadataQuery#isPhaseTransition
  * @see RelMetadataQuery#splitCount
  */
-public class RelMdMemory {
+public class RelMdMemory implements MetadataHandler<BuiltInMetadata.Memory> {
   /** Source for
    * {@link org.apache.calcite.rel.metadata.BuiltInMetadata.Memory}. */
   public static final RelMetadataProvider SOURCE =
@@ -42,6 +42,10 @@ public class RelMdMemory {
 
   //~ Methods ----------------------------------------------------------------
 
+  public MetadataDef<BuiltInMetadata.Memory> getDef() {
+    return BuiltInMetadata.Memory.DEF;
+  }
+
   /** Catch-all implementation for
    * {@link BuiltInMetadata.Memory#memory()},
    * invoked using reflection.

http://git-wip-us.apache.org/repos/asf/calcite/blob/d14040c5/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
index dcd69f1..4d144aa 100644
--- a/core/src/main/java/org/apache/calcite/rel/metadata/RelMdParallelism.java
+++ b/core/src/main/java/org/apache/calcite/rel/metadata/RelMdParallelism.java
@@ -30,7 +30,8 @@ import org.apache.calcite.util.BuiltInMethod;
  * @see org.apache.calcite.rel.metadata.RelMetadataQuery#isPhaseTransition
  * @see org.apache.calcite.rel.metadata.RelMetadataQuery#splitCount
  */
-public class RelMdParallelism {
+public class RelMdParallelism
+    implements MetadataHandler<BuiltInMetadata.Parallelism> {
   /** Source for
    * {@link org.apache.calcite.rel.metadata.BuiltInMetadata.Parallelism}. */
   public static final RelMetadataProvider SOURCE =
@@ -44,6 +45,10 @@ public class RelMdParallelism {
 
   //~ Methods ----------------------------------------------------------------
 
+  public MetadataDef<BuiltInMetadata.Parallelism> getDef() {
+    return BuiltInMetadata.Parallelism.DEF;
+  }
+
   /** Catch-all implementation for
    * {@link BuiltInMetadata.Parallelism#isPhaseTransition()},
    * invoked using reflection.

http://git-wip-us.apache.org/repos/asf/calcite/blob/d14040c5/core/src/main/java/org/apache/calcite/rel/metadata/RelMdPercentageOriginalRows.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/calcite/rel/metadata/RelMdPercentageOriginalRows.java
 
b/core/src/main/java/org/apache/calcite/rel/metadata/RelMdPercentageOriginalRows.java
index bde07a9..e50c8d0 100644
--- 
a/core/src/main/java/org/apache/calcite/rel/metadata/RelMdPercentageOriginalRows.java
+++ 
b/core/src/main/java/org/apache/calcite/rel/metadata/RelMdPercentageOriginalRows.java
@@ -33,7 +33,8 @@ import java.util.List;
  * {@link RelMetadataQuery#getPercentageOriginalRows} for the standard logical
  * algebra.
  */
-public class RelMdPercentageOriginalRows {
+public class RelMdPercentageOriginalRows
+    implements MetadataHandler<BuiltInMetadata.PercentageOriginalRows> {
   private static final RelMdPercentageOriginalRows INSTANCE =
       new RelMdPercentageOriginalRows();
 
@@ -53,6 +54,10 @@ public class RelMdPercentageOriginalRows {
 
   private RelMdPercentageOriginalRows() {}
 
+  public MetadataDef<BuiltInMetadata.PercentageOriginalRows> getDef() {
+    return BuiltInMetadata.PercentageOriginalRows.DEF;
+  }
+
   public Double getPercentageOriginalRows(Aggregate rel, RelMetadataQuery mq) {
     // REVIEW jvs 28-Mar-2006: The assumption here seems to be that
     // aggregation does not apply any filtering, so it does not modify the

http://git-wip-us.apache.org/repos/asf/calcite/blob/d14040c5/core/src/main/java/org/apache/calcite/rel/metadata/RelMdPopulationSize.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/calcite/rel/metadata/RelMdPopulationSize.java 
b/core/src/main/java/org/apache/calcite/rel/metadata/RelMdPopulationSize.java
index 180883f..67b3fe9 100644
--- 
a/core/src/main/java/org/apache/calcite/rel/metadata/RelMdPopulationSize.java
+++ 
b/core/src/main/java/org/apache/calcite/rel/metadata/RelMdPopulationSize.java
@@ -36,7 +36,8 @@ import java.util.List;
  * RelMdPopulationSize supplies a default implementation of
  * {@link RelMetadataQuery#getPopulationSize} for the standard logical algebra.
  */
-public class RelMdPopulationSize {
+public class RelMdPopulationSize
+    implements MetadataHandler<BuiltInMetadata.PopulationSize> {
   public static final RelMetadataProvider SOURCE =
       ReflectiveRelMetadataProvider.reflectiveSource(
           BuiltInMethod.POPULATION_SIZE.method, new RelMdPopulationSize());
@@ -47,6 +48,10 @@ public class RelMdPopulationSize {
 
   //~ Methods ----------------------------------------------------------------
 
+  public MetadataDef<BuiltInMetadata.PopulationSize> getDef() {
+    return BuiltInMetadata.PopulationSize.DEF;
+  }
+
   public Double getPopulationSize(Filter rel, RelMetadataQuery mq,
       ImmutableBitSet groupKey) {
     return mq.getPopulationSize(rel.getInput(), groupKey);

http://git-wip-us.apache.org/repos/asf/calcite/blob/d14040c5/core/src/main/java/org/apache/calcite/rel/metadata/RelMdPredicates.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/calcite/rel/metadata/RelMdPredicates.java 
b/core/src/main/java/org/apache/calcite/rel/metadata/RelMdPredicates.java
index 4b1a894..7300624 100644
--- a/core/src/main/java/org/apache/calcite/rel/metadata/RelMdPredicates.java
+++ b/core/src/main/java/org/apache/calcite/rel/metadata/RelMdPredicates.java
@@ -115,12 +115,17 @@ import java.util.SortedMap;
  *   </ul>
  * </ol>
  */
-public class RelMdPredicates {
+public class RelMdPredicates
+    implements MetadataHandler<BuiltInMetadata.Predicates> {
   public static final RelMetadataProvider SOURCE = 
ReflectiveRelMetadataProvider
       .reflectiveSource(BuiltInMethod.PREDICATES.method, new 
RelMdPredicates());
 
   private static final List<RexNode> EMPTY_LIST = ImmutableList.of();
 
+  public MetadataDef<BuiltInMetadata.Predicates> getDef() {
+    return BuiltInMetadata.Predicates.DEF;
+  }
+
   /** Catch-all implementation for
    * {@link BuiltInMetadata.Predicates#getPredicates()},
    * invoked using reflection.

Reply via email to