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_(