This is an automated email from the ASF dual-hosted git repository.

mihaibudiu pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/calcite.git


The following commit(s) were added to refs/heads/main by this push:
     new 896f90a4aa [CALCITE-7631] Introduce a composable RexImplementorTable 
SPI for operator code generation
896f90a4aa is described below

commit 896f90a4aa81facf2341c8765a25c79d7b315f0e
Author: Darpan Lunagariya <[email protected]>
AuthorDate: Tue Jun 30 10:05:40 2026 +0530

    [CALCITE-7631] Introduce a composable RexImplementorTable SPI for operator 
code generation
    
    Enumerable code generation resolved operator implementors only through the
    RexImpTable singleton, whose sole extension hook is ImplementableFunction 
for
    schema user-defined functions. Operators registered through a 
SqlOperatorTable
    (custom or dialect operators) therefore had no code-generation or
    constant-reduction path.
    
    Extract a RexImplementorTable interface (the get() lookups for scalar,
    aggregate, match and table-function operators) that RexImpTable implements, 
and
    add RexImplementorTables.chain() so an extension can layer its own 
implementors
    ahead of the built-ins -- the code-generation counterpart of 
SqlOperatorTable.
    Thread an injectable RexImplementorTable (defaulting to the built-ins) 
through
    RexToLixTranslator (scalar code generation) and RexExecutorImpl (constant
    folding); deprecate the table-less translateProjects/translateCondition
    overloads and migrate all callers. The match and table-function lookups now
    return null on a miss so a chained table can fall through.
---
 .../calcite/adapter/enumerable/EnumUtils.java      |   2 +-
 .../calcite/adapter/enumerable/EnumerableCalc.java |   6 +-
 .../adapter/enumerable/EnumerableMatch.java        |  11 +-
 .../enumerable/EnumerableRelImplementor.java       |   7 +
 .../calcite/adapter/enumerable/RexImpTable.java    |  33 +--
 .../adapter/enumerable/RexImplementorTable.java    |  56 +++++
 .../adapter/enumerable/RexImplementorTables.java   | 126 +++++++++++
 .../adapter/enumerable/RexToLixTranslator.java     |  81 ++++++--
 .../calcite/interpreter/JaninoRexCompiler.java     |   4 +-
 .../org/apache/calcite/rex/RexExecutorImpl.java    |  25 ++-
 .../enumerable/RexImplementorTableTest.java        | 230 +++++++++++++++++++++
 .../apache/calcite/adapter/spark/SparkRules.java   |   5 +-
 12 files changed, 543 insertions(+), 43 deletions(-)

diff --git 
a/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumUtils.java 
b/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumUtils.java
index 018ba8bb75..2ed244dfa8 100644
--- a/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumUtils.java
+++ b/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumUtils.java
@@ -987,7 +987,7 @@ static Expression generatePredicate(
                         right_, rightPhysType)),
                 implementor.allCorrelateVariables,
                 implementor.getConformance(),
-                nullable)));
+                nullable, implementor.getRexImplementorTable())));
     Class clazz = nullable ? NullablePredicate2.class : Predicate2.class;
     return Expressions.lambda(clazz, builder.toBlock(), left_, right_);
   }
diff --git 
a/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableCalc.java 
b/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableCalc.java
index e1b1cdcb29..03cd0e419a 100644
--- 
a/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableCalc.java
+++ 
b/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableCalc.java
@@ -166,7 +166,8 @@ public static EnumerableCalc create(final RelNode input,
               typeFactory,
               builder2,
               new RexToLixTranslator.InputGetterImpl(input, result.physType),
-              implementor.allCorrelateVariables, implementor.getConformance());
+              implementor.allCorrelateVariables, implementor.getConformance(),
+              false, implementor.getRexImplementorTable());
       builder2.add(
           Expressions.ifThen(
               condition,
@@ -198,7 +199,8 @@ public static EnumerableCalc create(final RelNode input,
             physType,
             DataContext.ROOT,
             new RexToLixTranslator.InputGetterImpl(input, result.physType),
-            implementor.allCorrelateVariables);
+            implementor.allCorrelateVariables,
+            implementor.getRexImplementorTable());
     builder3.add(
         Expressions.return_(
             null, physType.record(expressions)));
diff --git 
a/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableMatch.java 
b/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableMatch.java
index 2fdd2b41ec..77d86cf5d7 100644
--- 
a/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableMatch.java
+++ 
b/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableMatch.java
@@ -247,7 +247,9 @@ private static Expression 
implementMeasure(RexToLixTranslator translator,
     case PREV:
     case CLASSIFIER:
       matchFunction = (SqlMatchFunction) ((RexCall) value).getOperator();
-      matchImplementor = RexImpTable.INSTANCE.get(matchFunction);
+      matchImplementor =
+          requireNonNull(RexImpTable.INSTANCE.get(matchFunction),
+              () -> "no implementor for match function " + matchFunction);
 
       // Work with the implementor
       return matchImplementor.implement(translator, (RexCall) value,
@@ -266,7 +268,9 @@ private static Expression 
implementMeasure(RexToLixTranslator translator,
       case CLASSIFIER:
         final RexCall call = (RexCall) operands.get(0);
         matchFunction = (SqlMatchFunction) call.getOperator();
-        matchImplementor = RexImpTable.INSTANCE.get(matchFunction);
+        matchImplementor =
+            requireNonNull(RexImpTable.INSTANCE.get(matchFunction),
+                () -> "no implementor for match function " + matchFunction);
         // Work with the implementor
         requireNonNull((PassedRowsInputGetter) translator.inputGetter, 
"inputGetter")
             .setIndex(null);
@@ -316,7 +320,8 @@ private Expression 
implementMatcher(EnumerableRelImplementor implementor,
               builder2,
               inputGetter1,
               implementor.allCorrelateVariables,
-              implementor.getConformance());
+              implementor.getConformance(),
+              false, implementor.getRexImplementorTable());
 
       builder2.add(Expressions.return_(null, condition));
       final Expression predicate_ =
diff --git 
a/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableRelImplementor.java
 
b/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableRelImplementor.java
index 53d0296f7a..8d27d6f74c 100644
--- 
a/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableRelImplementor.java
+++ 
b/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableRelImplementor.java
@@ -488,6 +488,13 @@ public EnumerableRel.Result result(PhysType physType, 
BlockStatement block) {
         SqlConformanceEnum.DEFAULT);
   }
 
+  /** Returns the table of code-generation implementors to use, defaulting to
+   * the built-in {@link RexImpTable#instance()}. */
+  public RexImplementorTable getRexImplementorTable() {
+    return (RexImplementorTable) map.getOrDefault("_rexImplementorTable",
+        RexImpTable.INSTANCE);
+  }
+
   /** Visitor that finds types in an {@link Expression} tree. */
   @VisibleForTesting
   static class TypeFinder extends VisitorImpl<Void> {
diff --git 
a/core/src/main/java/org/apache/calcite/adapter/enumerable/RexImpTable.java 
b/core/src/main/java/org/apache/calcite/adapter/enumerable/RexImpTable.java
index c78336e172..acbd66cb1c 100644
--- a/core/src/main/java/org/apache/calcite/adapter/enumerable/RexImpTable.java
+++ b/core/src/main/java/org/apache/calcite/adapter/enumerable/RexImpTable.java
@@ -557,7 +557,7 @@
  *
  * <p>Immutable.
  */
-public class RexImpTable {
+public class RexImpTable implements RexImplementorTable {
   /** The singleton instance. */
   public static final RexImpTable INSTANCE;
 
@@ -569,6 +569,11 @@ public class RexImpTable {
     INSTANCE = new RexImpTable(builder);
   }
 
+  /** Returns the table of built-in implementors. */
+  public static RexImplementorTable instance() {
+    return INSTANCE;
+  }
+
   public static final ConstantExpression NULL_EXPR =
       Expressions.constant(null);
   public static final ConstantExpression FALSE_EXPR =
@@ -1457,7 +1462,9 @@ private static RexCallImplementor 
createRexCallImplementor(
     };
   }
 
-  private static RexCallImplementor wrapAsRexCallImplementor(
+  /** Wraps a {@link CallImplementor} (for example, one built with
+   * {@link #createImplementor}) as a {@link RexCallImplementor}. */
+  public static RexCallImplementor wrapAsRexCallImplementor(
       final CallImplementor implementor) {
     return new AbstractRexCallImplementor("udf", NullPolicy.NONE, false) {
       @Override Expression implementSafe(RexToLixTranslator translator,
@@ -1467,7 +1474,7 @@ private static RexCallImplementor 
wrapAsRexCallImplementor(
     };
   }
 
-  public @Nullable RexCallImplementor get(final SqlOperator operator) {
+  @Override public @Nullable RexCallImplementor get(final SqlOperator 
operator) {
     if (operator instanceof SqlUserDefinedFunction) {
       org.apache.calcite.schema.Function udf =
           ((SqlUserDefinedFunction) operator).getFunction();
@@ -1502,7 +1509,7 @@ private static RexCallImplementor 
wrapAsRexCallImplementor(
     return null;
   }
 
-  public @Nullable AggImplementor get(final SqlAggFunction aggregation,
+  @Override public @Nullable AggImplementor get(final SqlAggFunction 
aggregation,
       boolean forWindowAggregate) {
     if (aggregation instanceof SqlUserDefinedAggFunction) {
       final SqlUserDefinedAggFunction udaf =
@@ -1531,24 +1538,18 @@ private static RexCallImplementor 
wrapAsRexCallImplementor(
     return aggSupplier.get();
   }
 
-  public MatchImplementor get(final SqlMatchFunction function) {
+  @Override public @Nullable MatchImplementor get(
+      final SqlMatchFunction function) {
     final Supplier<? extends MatchImplementor> supplier =
         matchMap.get(function);
-    if (supplier != null) {
-      return supplier.get();
-    } else {
-      throw new IllegalStateException("Supplier should not be null");
-    }
+    return supplier != null ? supplier.get() : null;
   }
 
-  public TableFunctionCallImplementor get(final SqlWindowTableFunction 
operator) {
+  @Override public @Nullable TableFunctionCallImplementor get(
+      final SqlWindowTableFunction operator) {
     final Supplier<? extends TableFunctionCallImplementor> supplier =
         tvfImplementorMap.get(operator);
-    if (supplier != null) {
-      return supplier.get();
-    } else {
-      throw new IllegalStateException("Supplier should not be null");
-    }
+    return supplier != null ? supplier.get() : null;
   }
 
   static Expression optimize(Expression expression) {
diff --git 
a/core/src/main/java/org/apache/calcite/adapter/enumerable/RexImplementorTable.java
 
b/core/src/main/java/org/apache/calcite/adapter/enumerable/RexImplementorTable.java
new file mode 100644
index 0000000000..9c135bf83a
--- /dev/null
+++ 
b/core/src/main/java/org/apache/calcite/adapter/enumerable/RexImplementorTable.java
@@ -0,0 +1,56 @@
+/*
+ * 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.adapter.enumerable;
+
+import org.apache.calcite.adapter.enumerable.RexImpTable.RexCallImplementor;
+import org.apache.calcite.sql.SqlAggFunction;
+import org.apache.calcite.sql.SqlMatchFunction;
+import org.apache.calcite.sql.SqlOperator;
+import org.apache.calcite.sql.SqlWindowTableFunction;
+
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+/**
+ * Provides the implementor that generates code for calls to an operator.
+ *
+ * <p>Enumerable code generation translates each operator call into
+ * {@linkplain org.apache.calcite.linq4j.tree.Expression linq4j code} using an
+ * implementor. This table looks up that implementor for a scalar operator, an
+ * aggregate function, a {@code MATCH_RECOGNIZE} function, or a windowed table
+ * function.
+ *
+ * <p>A lookup returns {@code null} if this table has no implementor for the
+ * given operator.
+ */
+public interface RexImplementorTable {
+  /** Returns the implementor of a scalar operator, or null if this table has
+   * none. */
+  @Nullable RexCallImplementor get(SqlOperator operator);
+
+  /** Returns the implementor of an aggregate function (in window context when
+   * {@code forWindowAggregate} is true), or null if this table has none. */
+  @Nullable AggImplementor get(SqlAggFunction aggregation,
+      boolean forWindowAggregate);
+
+  /** Returns the implementor of a {@code MATCH_RECOGNIZE} function, or null if
+   * this table has none. */
+  @Nullable MatchImplementor get(SqlMatchFunction function);
+
+  /** Returns the implementor of a windowed table function, or null if this
+   * table has none. */
+  @Nullable TableFunctionCallImplementor get(SqlWindowTableFunction operator);
+}
diff --git 
a/core/src/main/java/org/apache/calcite/adapter/enumerable/RexImplementorTables.java
 
b/core/src/main/java/org/apache/calcite/adapter/enumerable/RexImplementorTables.java
new file mode 100644
index 0000000000..22b3340ee8
--- /dev/null
+++ 
b/core/src/main/java/org/apache/calcite/adapter/enumerable/RexImplementorTables.java
@@ -0,0 +1,126 @@
+/*
+ * 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.adapter.enumerable;
+
+import org.apache.calcite.adapter.enumerable.RexImpTable.RexCallImplementor;
+import org.apache.calcite.sql.SqlAggFunction;
+import org.apache.calcite.sql.SqlMatchFunction;
+import org.apache.calcite.sql.SqlOperator;
+import org.apache.calcite.sql.SqlWindowTableFunction;
+
+import com.google.common.collect.ImmutableList;
+
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Utilities for {@link RexImplementorTable}.
+ */
+public abstract class RexImplementorTables {
+  private RexImplementorTables() {
+  }
+
+  /** Creates a table that consults each of the given tables in turn, returning
+   * the first non-null implementor.
+   *
+   * <p>Earlier tables take precedence: when more than one table has an
+   * implementor for the same operator, the one earliest in the list is
+   * returned. Listing {@link RexImpTable#instance()} last makes the built-in
+   * implementors the fallback. */
+  public static RexImplementorTable chain(RexImplementorTable... tables) {
+    return chain(ImmutableList.copyOf(tables));
+  }
+
+  /** Creates a table that consults each of the given tables in turn.
+   *
+   * @see #chain(RexImplementorTable...) */
+  public static RexImplementorTable chain(
+      Iterable<? extends RexImplementorTable> tables) {
+    final List<RexImplementorTable> list = new ArrayList<>();
+    for (RexImplementorTable table : tables) {
+      addFlattened(list, table);
+    }
+    if (list.size() == 1) {
+      return list.get(0);
+    }
+    return new Chain(ImmutableList.copyOf(list));
+  }
+
+  private static void addFlattened(List<RexImplementorTable> list,
+      RexImplementorTable table) {
+    if (table instanceof Chain) {
+      list.addAll(((Chain) table).tables);
+    } else {
+      list.add(table);
+    }
+  }
+
+  /** Implementor table that consults a list of tables in order, returning the
+   * first non-null implementor. */
+  private static class Chain implements RexImplementorTable {
+    final ImmutableList<RexImplementorTable> tables;
+
+    Chain(ImmutableList<RexImplementorTable> tables) {
+      this.tables = tables;
+    }
+
+    @Override public @Nullable RexCallImplementor get(SqlOperator operator) {
+      for (RexImplementorTable table : tables) {
+        final RexCallImplementor implementor = table.get(operator);
+        if (implementor != null) {
+          return implementor;
+        }
+      }
+      return null;
+    }
+
+    @Override public @Nullable AggImplementor get(SqlAggFunction aggregation,
+        boolean forWindowAggregate) {
+      for (RexImplementorTable table : tables) {
+        final AggImplementor implementor =
+            table.get(aggregation, forWindowAggregate);
+        if (implementor != null) {
+          return implementor;
+        }
+      }
+      return null;
+    }
+
+    @Override public @Nullable MatchImplementor get(SqlMatchFunction function) 
{
+      for (RexImplementorTable table : tables) {
+        final MatchImplementor implementor = table.get(function);
+        if (implementor != null) {
+          return implementor;
+        }
+      }
+      return null;
+    }
+
+    @Override public @Nullable TableFunctionCallImplementor get(
+        SqlWindowTableFunction operator) {
+      for (RexImplementorTable table : tables) {
+        final TableFunctionCallImplementor implementor = table.get(operator);
+        if (implementor != null) {
+          return implementor;
+        }
+      }
+      return null;
+    }
+  }
+}
diff --git 
a/core/src/main/java/org/apache/calcite/adapter/enumerable/RexToLixTranslator.java
 
b/core/src/main/java/org/apache/calcite/adapter/enumerable/RexToLixTranslator.java
index 7d2cb6b732..15924844ca 100644
--- 
a/core/src/main/java/org/apache/calcite/adapter/enumerable/RexToLixTranslator.java
+++ 
b/core/src/main/java/org/apache/calcite/adapter/enumerable/RexToLixTranslator.java
@@ -127,6 +127,8 @@ public class RexToLixTranslator implements 
RexVisitor<RexToLixTranslator.Result>
   private final @Nullable BlockBuilder staticList;
   private final @Nullable Function1<String, InputGetter> correlates;
 
+  private final RexImplementorTable implementorTable;
+
   /**
    * Map from RexLiteral's variable name to its literal, which is often a
    * ({@link org.apache.calcite.linq4j.tree.ConstantExpression}))
@@ -163,7 +165,8 @@ private RexToLixTranslator(@Nullable RexProgram program,
       @Nullable BlockBuilder staticList,
       RexBuilder builder,
       SqlConformance conformance,
-      @Nullable Function1<String, InputGetter> correlates) {
+      @Nullable Function1<String, InputGetter> correlates,
+      RexImplementorTable implementorTable) {
     this.program = program; // may be null
     this.typeFactory = requireNonNull(typeFactory, "typeFactory");
     this.conformance = requireNonNull(conformance, "conformance");
@@ -173,6 +176,23 @@ private RexToLixTranslator(@Nullable RexProgram program,
     this.staticList = staticList;
     this.builder = requireNonNull(builder, "builder");
     this.correlates = correlates; // may be null
+    this.implementorTable = requireNonNull(implementorTable, 
"implementorTable");
+  }
+
+  /**
+   * Translates a {@link RexProgram} to a sequence of expressions and
+   * declarations, using the built-in implementor table.
+   *
+   * @deprecated Use {@link #translateProjects(RexProgram, JavaTypeFactory, 
SqlConformance, BlockBuilder, BlockBuilder, PhysType, Expression, InputGetter, 
Function1, RexImplementorTable)}.
+   */
+  @Deprecated // to be removed before 2.0
+  public static List<Expression> translateProjects(RexProgram program,
+      JavaTypeFactory typeFactory, SqlConformance conformance,
+      BlockBuilder list, @Nullable BlockBuilder staticList,
+      @Nullable PhysType outputPhysType, Expression root,
+      InputGetter inputGetter, @Nullable Function1<String, InputGetter> 
correlates) {
+    return translateProjects(program, typeFactory, conformance, list, 
staticList,
+        outputPhysType, root, inputGetter, correlates, RexImpTable.INSTANCE);
   }
 
   /**
@@ -189,13 +209,15 @@ private RexToLixTranslator(@Nullable RexProgram program,
    * @param inputGetter Generates expressions for inputs
    * @param correlates Provider of references to the values of correlated
    *                   variables
+   * @param implementorTable Table of implementors for operator code generation
    * @return Sequence of expressions, optional condition
    */
   public static List<Expression> translateProjects(RexProgram program,
       JavaTypeFactory typeFactory, SqlConformance conformance,
       BlockBuilder list, @Nullable BlockBuilder staticList,
       @Nullable PhysType outputPhysType, Expression root,
-      InputGetter inputGetter, @Nullable Function1<String, InputGetter> 
correlates) {
+      InputGetter inputGetter, @Nullable Function1<String, InputGetter> 
correlates,
+      RexImplementorTable implementorTable) {
     List<Type> storageTypes = null;
     if (outputPhysType != null) {
       final RelDataType rowType = outputPhysType.getRowType();
@@ -205,18 +227,25 @@ public static List<Expression> 
translateProjects(RexProgram program,
       }
     }
     return new RexToLixTranslator(program, typeFactory, root, inputGetter,
-        list, staticList, new RexBuilder(typeFactory), conformance,  null)
+        list, staticList, new RexBuilder(typeFactory), conformance, null,
+        implementorTable)
         .setCorrelates(correlates)
         .translateList(program.getProjectList(), storageTypes);
   }
 
+  /**
+   * Translates a {@link RexProgram} to a sequence of expressions and
+   * declarations, using the built-in implementor table.
+   *
+   * @deprecated Use {@link #translateProjects(RexProgram, JavaTypeFactory, 
SqlConformance, BlockBuilder, BlockBuilder, PhysType, Expression, InputGetter, 
Function1, RexImplementorTable)}.
+   */
   @Deprecated // to be removed before 2.0
   public static List<Expression> translateProjects(RexProgram program,
       JavaTypeFactory typeFactory, SqlConformance conformance,
       BlockBuilder list, @Nullable PhysType outputPhysType, Expression root,
       InputGetter inputGetter, @Nullable Function1<String, InputGetter> 
correlates) {
     return translateProjects(program, typeFactory, conformance, list, null,
-        outputPhysType, root, inputGetter, correlates);
+        outputPhysType, root, inputGetter, correlates, RexImpTable.INSTANCE);
   }
 
   public static Expression translateTableFunction(JavaTypeFactory typeFactory,
@@ -225,7 +254,8 @@ public static Expression 
translateTableFunction(JavaTypeFactory typeFactory,
       PhysType inputPhysType, PhysType outputPhysType) {
     final RexToLixTranslator translator =
         new RexToLixTranslator(null, typeFactory, root, null, list,
-            null, new RexBuilder(typeFactory), conformance, null);
+            null, new RexBuilder(typeFactory), conformance, null,
+            RexImpTable.INSTANCE);
     return translator
         .translateTableFunction(rexCall, inputEnumerable, inputPhysType,
             outputPhysType);
@@ -237,7 +267,8 @@ public static RexToLixTranslator 
forAggregation(JavaTypeFactory typeFactory,
       SqlConformance conformance) {
     final ParameterExpression root = DataContext.ROOT;
     return new RexToLixTranslator(null, typeFactory, root, inputGetter, list,
-        null, new RexBuilder(typeFactory), conformance, null);
+        null, new RexBuilder(typeFactory), conformance, null,
+        RexImpTable.INSTANCE);
   }
 
   Expression translate(RexNode expr) {
@@ -1218,7 +1249,7 @@ private Expression translateTableFunction(RexCall 
rexCall, Expression inputEnume
       PhysType inputPhysType, PhysType outputPhysType) {
     assert rexCall.getOperator() instanceof SqlWindowTableFunction;
     TableFunctionCallImplementor implementor =
-        RexImpTable.INSTANCE.get((SqlWindowTableFunction) 
rexCall.getOperator());
+        implementorTable.get((SqlWindowTableFunction) rexCall.getOperator());
     if (implementor == null) {
       throw Util.needToImplement("implementor of " + 
rexCall.getOperator().getName());
     }
@@ -1226,16 +1257,41 @@ private Expression translateTableFunction(RexCall 
rexCall, Expression inputEnume
         this, inputEnumerable, rexCall, inputPhysType, outputPhysType);
   }
 
+  /**
+   * Translates the condition of a {@link RexProgram} to a Java expression,
+   * using the built-in implementor table.
+   *
+   * @deprecated Use {@link #translateCondition(RexProgram, JavaTypeFactory, 
BlockBuilder, InputGetter, Function1, SqlConformance, boolean, 
RexImplementorTable)}.
+   */
+  @Deprecated // to be removed before 2.0
   public static Expression translateCondition(RexProgram program,
       JavaTypeFactory typeFactory, BlockBuilder list, InputGetter inputGetter,
       Function1<String, InputGetter> correlates, SqlConformance conformance) {
     return translateCondition(program, typeFactory, list, inputGetter,
-        correlates, conformance, false);
+        correlates, conformance, false, RexImpTable.INSTANCE);
   }
 
+  /**
+   * Translates the condition of a {@link RexProgram} to a Java expression,
+   * using the built-in implementor table.
+   *
+   * @deprecated Use {@link #translateCondition(RexProgram, JavaTypeFactory, 
BlockBuilder, InputGetter, Function1, SqlConformance, boolean, 
RexImplementorTable)}.
+   */
+  @Deprecated // to be removed before 2.0
   public static Expression translateCondition(RexProgram program,
       JavaTypeFactory typeFactory, BlockBuilder list, InputGetter inputGetter,
       Function1<String, InputGetter> correlates, SqlConformance conformance, 
boolean nullable) {
+    return translateCondition(program, typeFactory, list, inputGetter, 
correlates,
+        conformance, nullable, RexImpTable.INSTANCE);
+  }
+
+  /**
+   * Translates the condition of a {@link RexProgram} to a Java expression.
+   */
+  public static Expression translateCondition(RexProgram program,
+      JavaTypeFactory typeFactory, BlockBuilder list, InputGetter inputGetter,
+      Function1<String, InputGetter> correlates, SqlConformance conformance,
+      boolean nullable, RexImplementorTable implementorTable) {
     RexLocalRef condition = program.getCondition();
     if (condition == null) {
       return RexImpTable.TRUE_EXPR;
@@ -1243,7 +1299,8 @@ public static Expression translateCondition(RexProgram 
program,
     final ParameterExpression root = DataContext.ROOT;
     RexToLixTranslator translator =
         new RexToLixTranslator(program, typeFactory, root, inputGetter, list,
-            null, new RexBuilder(typeFactory), conformance, null);
+            null, new RexBuilder(typeFactory), conformance, null,
+            implementorTable);
     translator = translator.setCorrelates(correlates);
     return translator.translate(
         condition,
@@ -1264,7 +1321,7 @@ public RexToLixTranslator setBlock(BlockBuilder list) {
       return this;
     }
     return new RexToLixTranslator(program, typeFactory, root, inputGetter, 
list,
-        staticList, builder, conformance, correlates);
+        staticList, builder, conformance, correlates, implementorTable);
   }
 
   public RexToLixTranslator setCorrelates(
@@ -1273,7 +1330,7 @@ public RexToLixTranslator setCorrelates(
       return this;
     }
     return new RexToLixTranslator(program, typeFactory, root, inputGetter, 
list,
-        staticList, builder, conformance, correlates);
+        staticList, builder, conformance, correlates, implementorTable);
   }
 
   public Expression getRoot() {
@@ -1494,7 +1551,7 @@ private ConstantExpression getTypedNullLiteral(RexLiteral 
literal) {
       return RexUtil.expandSearch(builder, program, call).accept(this);
     }
     final RexImpTable.RexCallImplementor implementor =
-        RexImpTable.INSTANCE.get(operator);
+        implementorTable.get(operator);
     if (implementor == null) {
       throw new RuntimeException("cannot translate call " + call);
     }
diff --git 
a/core/src/main/java/org/apache/calcite/interpreter/JaninoRexCompiler.java 
b/core/src/main/java/org/apache/calcite/interpreter/JaninoRexCompiler.java
index bca4f85ef5..db553ca9fb 100644
--- a/core/src/main/java/org/apache/calcite/interpreter/JaninoRexCompiler.java
+++ b/core/src/main/java/org/apache/calcite/interpreter/JaninoRexCompiler.java
@@ -19,6 +19,7 @@
 import org.apache.calcite.DataContext;
 import org.apache.calcite.adapter.enumerable.JavaRowFormat;
 import org.apache.calcite.adapter.enumerable.PhysTypeImpl;
+import org.apache.calcite.adapter.enumerable.RexImpTable;
 import org.apache.calcite.adapter.enumerable.RexToLixTranslator;
 import org.apache.calcite.config.CalciteSystemProperty;
 import org.apache.calcite.jdbc.JavaTypeFactoryImpl;
@@ -103,7 +104,8 @@ public JaninoRexCompiler(RexBuilder rexBuilder) {
         SqlConformanceEnum.DEFAULT; // TODO: get this from implementor
     final List<Expression> expressionList =
         RexToLixTranslator.translateProjects(program, javaTypeFactory,
-            conformance, list, staticList, null, root, inputGetter, 
correlates);
+            conformance, list, staticList, null, root, inputGetter, correlates,
+            RexImpTable.INSTANCE);
     Ord.forEach(expressionList, (expression, i) ->
         list.add(
             Expressions.statement(
diff --git a/core/src/main/java/org/apache/calcite/rex/RexExecutorImpl.java 
b/core/src/main/java/org/apache/calcite/rex/RexExecutorImpl.java
index acfbbdc56d..7f06abf58c 100644
--- a/core/src/main/java/org/apache/calcite/rex/RexExecutorImpl.java
+++ b/core/src/main/java/org/apache/calcite/rex/RexExecutorImpl.java
@@ -18,6 +18,8 @@
 
 import org.apache.calcite.DataContext;
 import org.apache.calcite.adapter.enumerable.EnumUtils;
+import org.apache.calcite.adapter.enumerable.RexImpTable;
+import org.apache.calcite.adapter.enumerable.RexImplementorTable;
 import org.apache.calcite.adapter.enumerable.RexToLixTranslator;
 import org.apache.calcite.adapter.enumerable.RexToLixTranslator.InputGetter;
 import org.apache.calcite.adapter.java.JavaTypeFactory;
@@ -57,20 +59,29 @@
 public class RexExecutorImpl implements RexExecutor {
 
   private final DataContext dataContext;
+  private final RexImplementorTable implementorTable;
 
   public RexExecutorImpl(DataContext dataContext) {
+    this(dataContext, RexImpTable.INSTANCE);
+  }
+
+  public RexExecutorImpl(DataContext dataContext,
+      RexImplementorTable implementorTable) {
     this.dataContext = dataContext;
+    this.implementorTable = implementorTable;
   }
 
   private static String compile(RexBuilder rexBuilder, List<RexNode> constExps,
-      RexToLixTranslator.InputGetter getter) {
+      RexToLixTranslator.InputGetter getter,
+      RexImplementorTable implementorTable) {
     final RelDataTypeFactory typeFactory = rexBuilder.getTypeFactory();
     final RelDataType emptyRowType = typeFactory.builder().build();
-    return compile(rexBuilder, constExps, getter, emptyRowType);
+    return compile(rexBuilder, constExps, getter, emptyRowType, 
implementorTable);
   }
 
   private static String compile(RexBuilder rexBuilder, List<RexNode> constExps,
-      RexToLixTranslator.InputGetter getter, RelDataType rowType) {
+      RexToLixTranslator.InputGetter getter, RelDataType rowType,
+      RexImplementorTable implementorTable) {
     final RexProgramBuilder programBuilder =
         new RexProgramBuilder(rowType, rexBuilder);
     for (RexNode node : constExps) {
@@ -93,7 +104,8 @@ private static String compile(RexBuilder rexBuilder, 
List<RexNode> constExps,
     final RexProgram program = programBuilder.getProgram();
     final List<Expression> expressions =
         RexToLixTranslator.translateProjects(program, javaTypeFactory,
-            conformance, blockBuilder, null, null, root_, getter, null);
+            conformance, blockBuilder, null, null, root_, getter, null,
+            implementorTable);
     blockBuilder.add(
         Expressions.return_(null,
             Expressions.newArrayInit(Object[].class, expressions)));
@@ -121,7 +133,8 @@ public static RexExecutable getExecutable(RexBuilder 
rexBuilder, List<RexNode> e
     final JavaTypeFactoryImpl typeFactory =
         new JavaTypeFactoryImpl(rexBuilder.getTypeFactory().getTypeSystem());
     final InputGetter getter = new DataContextInputGetter(rowType, 
typeFactory);
-    final String code = compile(rexBuilder, exps, getter, rowType);
+    final String code =
+        compile(rexBuilder, exps, getter, rowType, RexImpTable.INSTANCE);
     return new RexExecutable(code, "generated Rex code");
   }
 
@@ -155,7 +168,7 @@ public static RexExecutable getExecutable(RexBuilder 
rexBuilder, List<RexNode> e
     try {
       String code = compile(rexBuilder, exps, (list, index, storageType) -> {
         throw new UnsupportedOperationException();
-      });
+      }, implementorTable);
 
       final RexExecutable executable = new RexExecutable(code, exps);
       executable.setDataContext(dataContext);
diff --git 
a/core/src/test/java/org/apache/calcite/adapter/enumerable/RexImplementorTableTest.java
 
b/core/src/test/java/org/apache/calcite/adapter/enumerable/RexImplementorTableTest.java
new file mode 100644
index 0000000000..6fb3039a61
--- /dev/null
+++ 
b/core/src/test/java/org/apache/calcite/adapter/enumerable/RexImplementorTableTest.java
@@ -0,0 +1,230 @@
+/*
+ * 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.adapter.enumerable;
+
+import org.apache.calcite.DataContext;
+import org.apache.calcite.adapter.enumerable.RexImpTable.RexCallImplementor;
+import org.apache.calcite.adapter.java.JavaTypeFactory;
+import org.apache.calcite.jdbc.JavaTypeFactoryImpl;
+import org.apache.calcite.linq4j.tree.BlockBuilder;
+import org.apache.calcite.linq4j.tree.Expression;
+import org.apache.calcite.linq4j.tree.Expressions;
+import org.apache.calcite.rel.type.RelDataType;
+import org.apache.calcite.rel.type.RelDataTypeSystem;
+import org.apache.calcite.rex.RexBuilder;
+import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.rex.RexProgram;
+import org.apache.calcite.rex.RexProgramBuilder;
+import org.apache.calcite.sql.SqlAggFunction;
+import org.apache.calcite.sql.SqlFunction;
+import org.apache.calcite.sql.SqlFunctionCategory;
+import org.apache.calcite.sql.SqlKind;
+import org.apache.calcite.sql.SqlMatchFunction;
+import org.apache.calcite.sql.SqlOperator;
+import org.apache.calcite.sql.SqlWindowTableFunction;
+import org.apache.calcite.sql.fun.SqlBasicAggFunction;
+import org.apache.calcite.sql.fun.SqlStdOperatorTable;
+import org.apache.calcite.sql.type.OperandTypes;
+import org.apache.calcite.sql.type.ReturnTypes;
+import org.apache.calcite.sql.validate.SqlConformanceEnum;
+
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.junit.jupiter.api.Test;
+
+import java.lang.reflect.Type;
+import java.util.List;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.hasSize;
+import static org.hamcrest.Matchers.sameInstance;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+/**
+ * Tests for the {@link RexImplementorTable} SPI, its
+ * {@link RexImplementorTables#chain composition}, and its use by
+ * {@link RexToLixTranslator}.
+ */
+class RexImplementorTableTest {
+  /** A scalar operator that has no built-in implementor. */
+  private static final SqlOperator MY_FN =
+      new SqlFunction("MY_CUSTOM_FN", SqlKind.OTHER_FUNCTION,
+          ReturnTypes.BOOLEAN, null, OperandTypes.ANY,
+          SqlFunctionCategory.USER_DEFINED_FUNCTION);
+
+  /** A sentinel implementor; only its identity matters to these tests. */
+  private static final RexCallImplementor SENTINEL =
+      (translator, call, arguments) -> {
+        throw new UnsupportedOperationException("sentinel");
+      };
+
+  /** A sentinel aggregate implementor; only its identity matters. */
+  private static final AggImplementor AGG_SENTINEL = new AggImplementor() {
+    @Override public List<Type> getStateType(AggContext info) {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override public void implementReset(AggContext info, AggResetContext 
reset) {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override public void implementAdd(AggContext info, AggAddContext add) {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override public Expression implementResult(AggContext info,
+        AggResultContext result) {
+      throw new UnsupportedOperationException();
+    }
+  };
+
+  /** An aggregate function with no built-in implementor. */
+  private static final SqlAggFunction MY_AGG =
+      SqlBasicAggFunction.create("MY_CUSTOM_AGG", SqlKind.OTHER_FUNCTION,
+          ReturnTypes.BIGINT, OperandTypes.ANY);
+
+  /** Implementor table that knows a single scalar operator. */
+  private static final class SingleScalarTable implements RexImplementorTable {
+    private final SqlOperator operator;
+    private final RexCallImplementor implementor;
+
+    SingleScalarTable(SqlOperator operator, RexCallImplementor implementor) {
+      this.operator = operator;
+      this.implementor = implementor;
+    }
+
+    @Override public @Nullable RexCallImplementor get(SqlOperator op) {
+      return op == operator ? implementor : null;
+    }
+
+    @Override public @Nullable AggImplementor get(SqlAggFunction aggregation,
+        boolean forWindowAggregate) {
+      return null;
+    }
+
+    @Override public @Nullable MatchImplementor get(SqlMatchFunction function) 
{
+      return null;
+    }
+
+    @Override public @Nullable TableFunctionCallImplementor get(
+        SqlWindowTableFunction operator) {
+      return null;
+    }
+  }
+
+  /** The built-in table has no implementor for an unregistered operator. */
+  @Test void builtinHasNoImplementorForUnknownOperator() {
+    assertThat(RexImpTable.instance().get(MY_FN), is(nullValue()));
+  }
+
+  /** A chained extension table supplies the implementor for its own operator,
+   * while the built-in table still resolves standard operators. */
+  @Test void chainResolvesExtensionThenFallsBackToBuiltin() {
+    final RexImplementorTable chain =
+        RexImplementorTables.chain(new SingleScalarTable(MY_FN, SENTINEL),
+            RexImpTable.instance());
+    assertThat(chain.get(MY_FN), is(sameInstance(SENTINEL)));
+    assertThat(chain.get(SqlStdOperatorTable.UPPER), is(notNullValue()));
+  }
+
+  /** A table earlier in the chain overrides a built-in implementor. */
+  @Test void earlierTableOverridesBuiltin() {
+    final RexImplementorTable chain =
+        RexImplementorTables.chain(
+            new SingleScalarTable(SqlStdOperatorTable.UPPER, SENTINEL),
+            RexImpTable.instance());
+    assertThat(chain.get(SqlStdOperatorTable.UPPER), 
is(sameInstance(SENTINEL)));
+  }
+
+  /** A single-element chain returns that table itself, with no wrapper. */
+  @Test void singleElementChainIsIdentity() {
+    final RexImplementorTable table = new SingleScalarTable(MY_FN, SENTINEL);
+    assertThat(RexImplementorTables.chain(table), is(sameInstance(table)));
+  }
+
+  /** An injected table drives project code generation for an operator that the
+   * built-in table cannot translate on its own. */
+  @Test void injectedTableDrivesProjectCodeGen() {
+    final JavaTypeFactory typeFactory =
+        new JavaTypeFactoryImpl(RelDataTypeSystem.DEFAULT);
+    final RexBuilder rexBuilder = new RexBuilder(typeFactory);
+    final RelDataType emptyRowType = typeFactory.builder().build();
+    final RexNode call =
+        rexBuilder.makeCall(MY_FN, rexBuilder.makeLiteral(true));
+    final RexProgramBuilder programBuilder =
+        new RexProgramBuilder(emptyRowType, rexBuilder);
+    programBuilder.addProject(call, "c0");
+    final RexProgram program = programBuilder.getProgram();
+    final RexToLixTranslator.InputGetter inputGetter =
+        (list, index, storageType) -> {
+          throw new UnsupportedOperationException();
+        };
+
+    // The built-in table alone cannot translate MY_FN.
+    assertThrows(RuntimeException.class, () ->
+        RexToLixTranslator.translateProjects(program, typeFactory,
+            SqlConformanceEnum.DEFAULT, new BlockBuilder(), null, null,
+            DataContext.ROOT, inputGetter, null, RexImpTable.instance()));
+
+    // A chained table that supplies MY_FN's implementor makes code-gen 
succeed.
+    final RexCallImplementor implementor =
+        RexImpTable.wrapAsRexCallImplementor(
+            RexImpTable.createImplementor(
+                (translator, c, operands) -> Expressions.constant(true),
+                NullPolicy.NONE, false));
+    final RexImplementorTable table =
+        RexImplementorTables.chain(new SingleScalarTable(MY_FN, implementor),
+            RexImpTable.instance());
+    final List<Expression> expressions =
+        RexToLixTranslator.translateProjects(program, typeFactory,
+            SqlConformanceEnum.DEFAULT, new BlockBuilder(), null, null,
+            DataContext.ROOT, inputGetter, null, table);
+    assertThat(expressions, is(notNullValue()));
+    assertThat(expressions, hasSize(1));
+  }
+
+  /** A chained extension table supplies an aggregate implementor that the
+   * built-in table does not have, while still resolving built-in aggregates. 
*/
+  @Test void chainResolvesCustomAggregateImplementor() {
+    final RexImplementorTable custom = new RexImplementorTable() {
+      @Override public @Nullable RexCallImplementor get(SqlOperator operator) {
+        return null;
+      }
+
+      @Override public @Nullable AggImplementor get(SqlAggFunction aggregation,
+          boolean forWindowAggregate) {
+        return aggregation == MY_AGG ? AGG_SENTINEL : null;
+      }
+
+      @Override public @Nullable MatchImplementor get(SqlMatchFunction 
function) {
+        return null;
+      }
+
+      @Override public @Nullable TableFunctionCallImplementor get(
+          SqlWindowTableFunction operator) {
+        return null;
+      }
+    };
+    final RexImplementorTable chain =
+        RexImplementorTables.chain(custom, RexImpTable.instance());
+    assertThat(RexImpTable.instance().get(MY_AGG, false), is(nullValue()));
+    assertThat(chain.get(MY_AGG, false), is(sameInstance(AGG_SENTINEL)));
+    assertThat(chain.get(SqlStdOperatorTable.COUNT, false), 
is(notNullValue()));
+  }
+}
diff --git 
a/spark/src/main/java/org/apache/calcite/adapter/spark/SparkRules.java 
b/spark/src/main/java/org/apache/calcite/adapter/spark/SparkRules.java
index 2ebda2879c..eb38a6dc17 100644
--- a/spark/src/main/java/org/apache/calcite/adapter/spark/SparkRules.java
+++ b/spark/src/main/java/org/apache/calcite/adapter/spark/SparkRules.java
@@ -376,7 +376,7 @@ public int getFlags() {
                 typeFactory,
                 builder2,
                 new RexToLixTranslator.InputGetterImpl(e_, result.physType),
-                null, implementor.getConformance());
+                null, implementor.getConformance(), false, 
RexImpTable.INSTANCE);
         builder2.add(
             Expressions.ifThen(
                 Expressions.not(condition),
@@ -396,7 +396,8 @@ public int getFlags() {
               null,
               DataContext.ROOT,
               new RexToLixTranslator.InputGetterImpl(e_, result.physType),
-              null);
+              null,
+              RexImpTable.INSTANCE);
       builder2.add(
           Expressions.return_(null,
               Expressions.convert_(


Reply via email to