You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by tl...@apache.org on 2021/06/01 09:38:59 UTC
[ignite] branch sql-calcite updated: IGNITE-14778 Calcite
integration. Expression executor (#9127)
This is an automated email from the ASF dual-hosted git repository.
tledkov pushed a commit to branch sql-calcite
in repository https://gitbox.apache.org/repos/asf/ignite.git
The following commit(s) were added to refs/heads/sql-calcite by this push:
new 2fd48de IGNITE-14778 Calcite integration. Expression executor (#9127)
2fd48de is described below
commit 2fd48dec82b2022e5d625a3283653e2be9f10777
Author: korlov42 <ko...@gridgain.com>
AuthorDate: Tue Jun 1 12:38:40 2021 +0300
IGNITE-14778 Calcite integration. Expression executor (#9127)
---
.../query/calcite/CalciteQueryProcessor.java | 6 +-
.../query/calcite/exec/exp/CallImplementor.java | 42 +
.../query/calcite/exec/exp/ConverterUtils.java | 455 ++++
.../calcite/exec/exp/ExpressionFactoryImpl.java | 3 +-
.../calcite/exec/exp/IgniteBuiltInMethod.java | 45 +
.../{fun => exec/exp}/IgniteSqlFunctions.java | 18 +-
.../calcite/exec/exp/ImplementableFunction.java | 36 +
.../query/calcite/exec/exp/NotNullImplementor.java | 43 +
.../exec/exp/ReflectiveCallNotNullImplementor.java | 82 +
.../query/calcite/exec/exp/RexImpTable.java | 2575 ++++++++++++++++++++
.../query/calcite/exec/exp/RexToLixTranslator.java | 1311 ++++++++++
.../exec/exp/TableFunctionCallImplementor.java | 44 +
.../calcite/sql/fun/IgniteSqlOperatorTable.java | 65 +
.../calcite/sql/fun/SqlSystemRangeFunction.java | 60 +
14 files changed, 4766 insertions(+), 19 deletions(-)
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/CalciteQueryProcessor.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/CalciteQueryProcessor.java
index 86001b2..b14491b 100644
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/CalciteQueryProcessor.java
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/CalciteQueryProcessor.java
@@ -18,9 +18,9 @@
package org.apache.ignite.internal.processors.query.calcite;
import java.util.List;
+
import org.apache.calcite.config.Lex;
import org.apache.calcite.plan.Contexts;
-import org.apache.calcite.prepare.CalciteCatalogReader;
import org.apache.calcite.rel.core.Aggregate;
import org.apache.calcite.rel.hint.HintStrategyTable;
import org.apache.calcite.sql.fun.SqlLibrary;
@@ -48,7 +48,6 @@ import org.apache.ignite.internal.processors.query.calcite.exec.MailboxRegistry;
import org.apache.ignite.internal.processors.query.calcite.exec.MailboxRegistryImpl;
import org.apache.ignite.internal.processors.query.calcite.exec.QueryTaskExecutor;
import org.apache.ignite.internal.processors.query.calcite.exec.QueryTaskExecutorImpl;
-import org.apache.ignite.internal.processors.query.calcite.fun.IgniteSqlFunctions;
import org.apache.ignite.internal.processors.query.calcite.message.MessageService;
import org.apache.ignite.internal.processors.query.calcite.message.MessageServiceImpl;
import org.apache.ignite.internal.processors.query.calcite.metadata.AffinityService;
@@ -61,6 +60,7 @@ import org.apache.ignite.internal.processors.query.calcite.prepare.QueryPlanCach
import org.apache.ignite.internal.processors.query.calcite.schema.SchemaHolder;
import org.apache.ignite.internal.processors.query.calcite.schema.SchemaHolderImpl;
import org.apache.ignite.internal.processors.query.calcite.sql.IgniteSqlParserImpl;
+import org.apache.ignite.internal.processors.query.calcite.sql.fun.IgniteSqlOperatorTable;
import org.apache.ignite.internal.processors.query.calcite.type.IgniteTypeSystem;
import org.apache.ignite.internal.processors.query.calcite.util.LifecycleAware;
import org.apache.ignite.internal.processors.query.calcite.util.Service;
@@ -103,7 +103,7 @@ public class CalciteQueryProcessor extends GridProcessorAdapter implements Query
SqlLibrary.POSTGRESQL,
SqlLibrary.ORACLE,
SqlLibrary.MYSQL),
- CalciteCatalogReader.operatorTable(IgniteSqlFunctions.class.getName())))
+ IgniteSqlOperatorTable.instance()))
// Context provides a way to store data within the planner session that can be accessed in planner rules.
.context(Contexts.empty())
// Custom cost factory to use during optimization
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/CallImplementor.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/CallImplementor.java
new file mode 100644
index 0000000..6328938
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/CallImplementor.java
@@ -0,0 +1,42 @@
+/*
+ * 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.ignite.internal.processors.query.calcite.exec.exp;
+
+import org.apache.calcite.linq4j.tree.Expression;
+import org.apache.calcite.rex.RexCall;
+
+/**
+ * Implements a call via given translator.
+ *
+ * @see org.apache.calcite.schema.ScalarFunction
+ * @see org.apache.calcite.schema.TableFunction
+ * @see RexImpTable
+ */
+public interface CallImplementor {
+ /**
+ * Implements a call.
+ *
+ * @param translator Translator for the call
+ * @param call Call that should be implemented
+ * @param nullAs The desired mode of {@code null} translation
+ * @return Translated call
+ */
+ Expression implement(
+ RexToLixTranslator translator,
+ RexCall call,
+ RexImpTable.NullAs nullAs);
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/ConverterUtils.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/ConverterUtils.java
new file mode 100644
index 0000000..8706e28
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/ConverterUtils.java
@@ -0,0 +1,455 @@
+/*
+ * 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.ignite.internal.processors.query.calcite.exec.exp;
+
+import java.lang.reflect.Type;
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.calcite.adapter.enumerable.RexImpTable;
+import org.apache.calcite.linq4j.tree.ConstantExpression;
+import org.apache.calcite.linq4j.tree.ConstantUntypedNull;
+import org.apache.calcite.linq4j.tree.Expression;
+import org.apache.calcite.linq4j.tree.ExpressionType;
+import org.apache.calcite.linq4j.tree.Expressions;
+import org.apache.calcite.linq4j.tree.Primitive;
+import org.apache.calcite.linq4j.tree.Types;
+import org.apache.calcite.linq4j.tree.UnaryExpression;
+import org.apache.calcite.rel.type.RelDataType;
+import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.runtime.SqlFunctions;
+import org.apache.calcite.util.BuiltInMethod;
+import org.apache.calcite.util.Util;
+
+/** */
+public class ConverterUtils {
+ /** */
+ private ConverterUtils() {
+ }
+
+ /**
+ * In Calcite, {@code java.sql.Date} and {@code java.sql.Time} are stored as {@code Integer} type, {@code
+ * java.sql.Timestamp} is stored as {@code Long} type.
+ */
+ static Expression toInternal(Expression operand, Type targetType) {
+ return toInternal(operand, operand.getType(), targetType);
+ }
+
+ /** */
+ private static Expression toInternal(Expression operand,
+ Type fromType, Type targetType) {
+ if (fromType == java.sql.Date.class) {
+ if (targetType == int.class)
+ return Expressions.call(BuiltInMethod.DATE_TO_INT.method, operand);
+ else if (targetType == Integer.class)
+ return Expressions.call(BuiltInMethod.DATE_TO_INT_OPTIONAL.method, operand);
+ }
+ else if (fromType == java.sql.Time.class) {
+ if (targetType == int.class)
+ return Expressions.call(BuiltInMethod.TIME_TO_INT.method, operand);
+ else if (targetType == Integer.class)
+ return Expressions.call(BuiltInMethod.TIME_TO_INT_OPTIONAL.method, operand);
+ }
+ else if (fromType == java.sql.Timestamp.class) {
+ if (targetType == long.class)
+ return Expressions.call(BuiltInMethod.TIMESTAMP_TO_LONG.method, operand);
+ else if (targetType == Long.class)
+ return Expressions.call(BuiltInMethod.TIMESTAMP_TO_LONG_OPTIONAL.method, operand);
+ }
+ return operand;
+ }
+
+ /** Converts from internal representation to JDBC representation used by
+ * arguments of user-defined functions. For example, converts date values from
+ * {@code int} to {@link java.sql.Date}. */
+ private static Expression fromInternal(Expression operand, Type targetType) {
+ return fromInternal(operand, operand.getType(), targetType);
+ }
+
+ /** */
+ private static Expression fromInternal(Expression operand,
+ Type fromType, Type targetType) {
+ if (operand == ConstantUntypedNull.INSTANCE)
+ return operand;
+ if (!(operand.getType() instanceof Class))
+ return operand;
+ if (Types.isAssignableFrom(targetType, fromType))
+ return operand;
+ if (targetType == java.sql.Date.class) {
+ // E.g. from "int" or "Integer" to "java.sql.Date",
+ // generate "SqlFunctions.internalToDate".
+ if (isA(fromType, Primitive.INT))
+ return Expressions.call(BuiltInMethod.INTERNAL_TO_DATE.method, operand);
+ }
+ else if (targetType == java.sql.Time.class) {
+ // E.g. from "int" or "Integer" to "java.sql.Time",
+ // generate "SqlFunctions.internalToTime".
+ if (isA(fromType, Primitive.INT))
+ return Expressions.call(BuiltInMethod.INTERNAL_TO_TIME.method, operand);
+ }
+ else if (targetType == java.sql.Timestamp.class) {
+ // E.g. from "long" or "Long" to "java.sql.Timestamp",
+ // generate "SqlFunctions.internalToTimestamp".
+ if (isA(fromType, Primitive.LONG))
+ return Expressions.call(BuiltInMethod.INTERNAL_TO_TIMESTAMP.method, operand);
+ }
+ if (Primitive.is(operand.type)
+ && Primitive.isBox(targetType)) {
+ // E.g. operand is "int", target is "Long", generate "(long) operand".
+ return Expressions.convert_(operand,
+ Primitive.ofBox(targetType).primitiveClass);
+ }
+ return operand;
+ }
+
+ /** */
+ static List<Expression> fromInternal(Class<?>[] targetTypes,
+ List<Expression> expressions) {
+ final List<Expression> list = new ArrayList<>();
+ if (targetTypes.length == expressions.size()) {
+ for (int i = 0; i < expressions.size(); i++)
+ list.add(fromInternal(expressions.get(i), targetTypes[i]));
+ } else {
+ int j = 0;
+ for (int i = 0; i < expressions.size(); i++) {
+ Class<?> type;
+ if (!targetTypes[j].isArray()) {
+ type = targetTypes[j];
+ j++;
+ } else
+ type = targetTypes[j].getComponentType();
+
+ list.add(fromInternal(expressions.get(i), type));
+ }
+ }
+ return list;
+ }
+
+ /** */
+ private static Type toInternal(RelDataType type) {
+ return toInternal(type, false);
+ }
+
+ /** */
+ static Type toInternal(RelDataType type, boolean forceNotNull) {
+ switch (type.getSqlTypeName()) {
+ case DATE:
+ case TIME:
+ return type.isNullable() && !forceNotNull ? Integer.class : int.class;
+ case TIMESTAMP:
+ return type.isNullable() && !forceNotNull ? Long.class : long.class;
+ default:
+ return null; // we don't care; use the default storage type
+ }
+ }
+
+ /** */
+ static List<Type> internalTypes(List<? extends RexNode> operandList) {
+ return Util.transform(operandList, node -> toInternal(node.getType()));
+ }
+
+ /**
+ * Convert {@code operand} to target type {@code toType}.
+ *
+ * @param operand The expression to convert
+ * @param toType Target type
+ * @return A new expression with type {@code toType} or original if there is no need to convert
+ */
+ public static Expression convert(Expression operand, Type toType) {
+ final Type fromType = operand.getType();
+ return convert(operand, fromType, toType);
+ }
+
+ /**
+ * Convert {@code operand} to target type {@code toType}.
+ *
+ * @param operand The expression to convert
+ * @param fromType Field type
+ * @param toType Target type
+ * @return A new expression with type {@code toType} or original if there is no need to convert
+ */
+ public static Expression convert(Expression operand, Type fromType, Type toType) {
+ if (!Types.needTypeCast(fromType, toType))
+ return operand;
+ // E.g. from "Short" to "int".
+ // Generate "x.intValue()".
+ final Primitive toPrimitive = Primitive.of(toType);
+ final Primitive toBox = Primitive.ofBox(toType);
+ final Primitive fromBox = Primitive.ofBox(fromType);
+ final Primitive fromPrimitive = Primitive.of(fromType);
+ final boolean fromNumber = fromType instanceof Class
+ && Number.class.isAssignableFrom((Class)fromType);
+ if (fromType == String.class) {
+ if (toPrimitive != null) {
+ switch (toPrimitive) {
+ case CHAR:
+ case SHORT:
+ case INT:
+ case LONG:
+ case FLOAT:
+ case DOUBLE:
+ // Generate "SqlFunctions.toShort(x)".
+ return Expressions.call(
+ SqlFunctions.class,
+ "to" + SqlFunctions.initcap(toPrimitive.primitiveName),
+ operand);
+ default:
+ // Generate "Short.parseShort(x)".
+ return Expressions.call(
+ toPrimitive.boxClass,
+ "parse" + SqlFunctions.initcap(toPrimitive.primitiveName),
+ operand);
+ }
+ }
+ if (toBox != null) {
+ switch (toBox) {
+ case CHAR:
+ // Generate "SqlFunctions.toCharBoxed(x)".
+ return Expressions.call(
+ SqlFunctions.class,
+ "to" + SqlFunctions.initcap(toBox.primitiveName) + "Boxed",
+ operand);
+ default:
+ // Generate "Short.valueOf(x)".
+ return Expressions.call(
+ toBox.boxClass,
+ "valueOf",
+ operand);
+ }
+ }
+ }
+ if (toPrimitive != null) {
+ if (fromPrimitive != null) {
+ // E.g. from "float" to "double"
+ return Expressions.convert_(
+ operand, toPrimitive.primitiveClass);
+ }
+ if (fromNumber || fromBox == Primitive.CHAR) {
+ // Generate "x.shortValue()".
+ return Expressions.unbox(operand, toPrimitive);
+ }
+ else {
+ // E.g. from "Object" to "short".
+ // Generate "SqlFunctions.toShort(x)"
+ return Expressions.call(
+ SqlFunctions.class,
+ "to" + SqlFunctions.initcap(toPrimitive.primitiveName),
+ operand);
+ }
+ }
+ else if (fromNumber && toBox != null) {
+ // E.g. from "Short" to "Integer"
+ // Generate "x == null ? null : Integer.valueOf(x.intValue())"
+ return Expressions.condition(
+ Expressions.equal(operand, RexImpTable.NULL_EXPR),
+ RexImpTable.NULL_EXPR,
+ Expressions.box(
+ Expressions.unbox(operand, toBox),
+ toBox));
+ }
+ else if (fromPrimitive != null && toBox != null) {
+ // E.g. from "int" to "Long".
+ // Generate Long.valueOf(x)
+ // Eliminate primitive casts like Long.valueOf((long) x)
+ if (operand instanceof UnaryExpression) {
+ UnaryExpression una = (UnaryExpression)operand;
+ if (una.nodeType == ExpressionType.Convert
+ && Primitive.of(una.getType()) == toBox) {
+ Primitive origin = Primitive.of(una.expression.type);
+ if (origin != null && toBox.assignableFrom(origin))
+ return Expressions.box(una.expression, toBox);
+ }
+ }
+ if (fromType == toBox.primitiveClass)
+ return Expressions.box(operand, toBox);
+ // E.g., from "int" to "Byte".
+ // Convert it first and generate "Byte.valueOf((byte)x)"
+ // Because there is no method "Byte.valueOf(int)" in Byte
+ return Expressions.box(
+ Expressions.convert_(operand, toBox.primitiveClass),
+ toBox);
+ }
+ // Convert datetime types to internal storage type:
+ // 1. java.sql.Date -> int or Integer
+ // 2. java.sql.Time -> int or Integer
+ // 3. java.sql.Timestamp -> long or Long
+ if (representAsInternalType(fromType)) {
+ final Expression internalTypedOperand =
+ toInternal(operand, fromType, toType);
+ if (operand != internalTypedOperand)
+ return internalTypedOperand;
+ }
+ // Convert internal storage type to datetime types:
+ // 1. int or Integer -> java.sql.Date
+ // 2. int or Integer -> java.sql.Time
+ // 3. long or Long -> java.sql.Timestamp
+ if (representAsInternalType(toType)) {
+ final Expression originTypedOperand =
+ fromInternal(operand, fromType, toType);
+ if (operand != originTypedOperand)
+ return originTypedOperand;
+ }
+ if (toType == BigDecimal.class) {
+ if (fromBox != null) {
+ // E.g. from "Integer" to "BigDecimal".
+ // Generate "x == null ? null : new BigDecimal(x.intValue())"
+ return Expressions.condition(
+ Expressions.equal(operand, RexImpTable.NULL_EXPR),
+ RexImpTable.NULL_EXPR,
+ Expressions.new_(
+ BigDecimal.class,
+ Expressions.unbox(operand, fromBox)));
+ }
+ if (fromPrimitive != null) {
+ // E.g. from "int" to "BigDecimal".
+ // Generate "new BigDecimal(x)"
+ return Expressions.new_(BigDecimal.class, operand);
+ }
+ // E.g. from "Object" to "BigDecimal".
+ // Generate "x == null ? null : SqlFunctions.toBigDecimal(x)"
+ return Expressions.condition(
+ Expressions.equal(operand, RexImpTable.NULL_EXPR),
+ RexImpTable.NULL_EXPR,
+ Expressions.call(
+ SqlFunctions.class,
+ "toBigDecimal",
+ operand));
+ }
+ else if (toType == String.class) {
+ if (fromPrimitive != null) {
+ switch (fromPrimitive) {
+ case DOUBLE:
+ case FLOAT:
+ // E.g. from "double" to "String"
+ // Generate "SqlFunctions.toString(x)"
+ return Expressions.call(
+ SqlFunctions.class,
+ "toString",
+ operand);
+ default:
+ // E.g. from "int" to "String"
+ // Generate "Integer.toString(x)"
+ return Expressions.call(
+ fromPrimitive.boxClass,
+ "toString",
+ operand);
+ }
+ }
+ else if (fromType == BigDecimal.class) {
+ // E.g. from "BigDecimal" to "String"
+ // Generate "SqlFunctions.toString(x)"
+ return Expressions.condition(
+ Expressions.equal(operand, RexImpTable.NULL_EXPR),
+ RexImpTable.NULL_EXPR,
+ Expressions.call(
+ SqlFunctions.class,
+ "toString",
+ operand));
+ }
+ else {
+ Expression result;
+ try {
+ // Avoid to generate code like:
+ // "null.toString()" or "(xxx) null.toString()"
+ if (operand instanceof ConstantExpression) {
+ ConstantExpression ce = (ConstantExpression)operand;
+ if (ce.value == null)
+ return Expressions.convert_(operand, toType);
+ }
+ // Try to call "toString()" method
+ // E.g. from "Integer" to "String"
+ // Generate "x == null ? null : x.toString()"
+ result = Expressions.condition(
+ Expressions.equal(operand, RexImpTable.NULL_EXPR),
+ RexImpTable.NULL_EXPR,
+ Expressions.call(operand, "toString"));
+ }
+ catch (RuntimeException e) {
+ // For some special cases, e.g., "BuiltInMethod.LESSER",
+ // its return type is generic ("Comparable"), which contains
+ // no "toString()" method. We fall through to "(String)x".
+ return Expressions.convert_(operand, toType);
+ }
+ return result;
+ }
+ }
+ return Expressions.convert_(operand, toType);
+ }
+
+ /** */
+ private static boolean isA(Type fromType, Primitive primitive) {
+ return Primitive.of(fromType) == primitive
+ || Primitive.ofBox(fromType) == primitive;
+ }
+
+ /** */
+ private static boolean representAsInternalType(Type type) {
+ return type == java.sql.Date.class
+ || type == java.sql.Time.class
+ || type == java.sql.Timestamp.class;
+ }
+
+ /**
+ * In {@link org.apache.calcite.sql.type.SqlTypeAssignmentRule},
+ * some rules decide whether one type can be assignable to another type.
+ * Based on these rules, a function can accept arguments with assignable types.
+ *
+ * <p>For example, a function with Long type operand can accept Integer as input.
+ * See {@code org.apache.calcite.sql.SqlUtil#filterRoutinesByParameterType()} for details.
+ *
+ * <p>During query execution, some of the assignable types need explicit conversion
+ * to the target types. i.e., Decimal expression should be converted to Integer
+ * before it is assigned to the Integer type Lvalue(In Java, Decimal can not be assigned to
+ * Integer directly).
+ *
+ * @param targetTypes Formal operand types declared for the function arguments
+ * @param arguments Input expressions to the function
+ * @return Input expressions with probable type conversion
+ */
+ static List<Expression> convertAssignableTypes(Class<?>[] targetTypes,
+ List<Expression> arguments) {
+ final List<Expression> list = new ArrayList<>();
+ if (targetTypes.length == arguments.size()) {
+ for (int i = 0; i < arguments.size(); i++)
+ list.add(convertAssignableType(arguments.get(i), targetTypes[i]));
+ } else {
+ int j = 0;
+ for (Expression argument: arguments) {
+ Class<?> type;
+ if (!targetTypes[j].isArray()) {
+ type = targetTypes[j];
+ j++;
+ } else
+ type = targetTypes[j].getComponentType();
+
+ list.add(convertAssignableType(argument, type));
+ }
+ }
+ return list;
+ }
+
+ /**
+ * Handles decimal type specifically with explicit type conversion.
+ */
+ private static Expression convertAssignableType(Expression argument, Type targetType) {
+ if (targetType != BigDecimal.class)
+ return argument;
+
+ return convert(argument, targetType);
+ }
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/ExpressionFactoryImpl.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/ExpressionFactoryImpl.java
index e9f24f2..472bd6c 100644
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/ExpressionFactoryImpl.java
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/ExpressionFactoryImpl.java
@@ -34,8 +34,6 @@ import com.google.common.collect.Ordering;
import com.google.common.primitives.Primitives;
import org.apache.calcite.DataContext;
import org.apache.calcite.adapter.enumerable.EnumUtils;
-import org.apache.calcite.adapter.enumerable.RexToLixTranslator;
-import org.apache.calcite.adapter.enumerable.RexToLixTranslator.InputGetter;
import org.apache.calcite.linq4j.function.Function1;
import org.apache.calcite.linq4j.tree.BlockBuilder;
import org.apache.calcite.linq4j.tree.Expression;
@@ -60,6 +58,7 @@ import org.apache.calcite.sql.validate.SqlConformance;
import org.apache.ignite.internal.processors.query.calcite.exec.ExecutionContext;
import org.apache.ignite.internal.processors.query.calcite.exec.RowHandler;
import org.apache.ignite.internal.processors.query.calcite.exec.RowHandler.RowFactory;
+import org.apache.ignite.internal.processors.query.calcite.exec.exp.RexToLixTranslator.InputGetter;
import org.apache.ignite.internal.processors.query.calcite.exec.exp.agg.AccumulatorWrapper;
import org.apache.ignite.internal.processors.query.calcite.exec.exp.agg.AccumulatorsFactory;
import org.apache.ignite.internal.processors.query.calcite.exec.exp.agg.AggregateType;
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/IgniteBuiltInMethod.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/IgniteBuiltInMethod.java
new file mode 100644
index 0000000..bbcc881
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/IgniteBuiltInMethod.java
@@ -0,0 +1,45 @@
+/*
+ * 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.ignite.internal.processors.query.calcite.exec.exp;
+
+import java.lang.reflect.Method;
+
+import org.apache.calcite.linq4j.tree.Types;
+
+/**
+ * Built-in methods.
+ */
+public enum IgniteBuiltInMethod {
+ /** */
+ SYSTEM_RANGE2(IgniteSqlFunctions.class, "systemRange", Object.class, Object.class),
+
+ /** */
+ SYSTEM_RANGE3(IgniteSqlFunctions.class, "systemRange", Object.class, Object.class, Object.class);
+
+ /** */
+ public final Method method;
+
+ /** */
+ IgniteBuiltInMethod(Method method) {
+ this.method = method;
+ }
+
+ /** Defines a method. */
+ IgniteBuiltInMethod(Class clazz, String methodName, Class... argumentTypes) {
+ this(Types.lookupMethod(clazz, methodName, argumentTypes));
+ }
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/fun/IgniteSqlFunctions.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/IgniteSqlFunctions.java
similarity index 88%
rename from modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/fun/IgniteSqlFunctions.java
rename to modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/IgniteSqlFunctions.java
index 7c914ba..203c676 100644
--- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/fun/IgniteSqlFunctions.java
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/IgniteSqlFunctions.java
@@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.ignite.internal.processors.query.calcite.fun;
+package org.apache.ignite.internal.processors.query.calcite.exec.exp;
import org.apache.calcite.DataContext;
import org.apache.calcite.config.CalciteConnectionConfig;
@@ -22,13 +22,11 @@ import org.apache.calcite.linq4j.AbstractEnumerable;
import org.apache.calcite.linq4j.Enumerable;
import org.apache.calcite.linq4j.Enumerator;
import org.apache.calcite.linq4j.Linq4j;
-import org.apache.calcite.linq4j.function.Strict;
import org.apache.calcite.rel.type.RelDataType;
import org.apache.calcite.rel.type.RelDataTypeFactory;
import org.apache.calcite.schema.ScannableTable;
import org.apache.calcite.schema.Schema;
import org.apache.calcite.schema.Statistic;
-import org.apache.calcite.schema.Statistics;
import org.apache.calcite.sql.SqlCall;
import org.apache.calcite.sql.SqlNode;
import org.apache.calcite.sql.type.SqlTypeName;
@@ -46,21 +44,15 @@ public class IgniteSqlFunctions {
}
/** SQL SYSTEM_RANGE(start, end) table function. */
- public static ScannableTable system_range(Object rangeStart, Object rangeEnd) {
+ public static ScannableTable systemRange(Object rangeStart, Object rangeEnd) {
return new RangeTable(rangeStart, rangeEnd, 1L);
}
/** SQL SYSTEM_RANGE(start, end, increment) table function. */
- public static ScannableTable system_range(Object rangeStart, Object rangeEnd, Object increment) {
+ public static ScannableTable systemRange(Object rangeStart, Object rangeEnd, Object increment) {
return new RangeTable(rangeStart, rangeEnd, increment);
}
- /** SQL LENGTH(string) function. */
- @Strict
- public static int length(String str) {
- return str.length();
- }
-
/** */
private static class RangeTable implements ScannableTable {
/** Start of the range. */
@@ -140,9 +132,7 @@ public class IgniteSqlFunctions {
/** {@inheritDoc} */
@Override public Statistic getStatistic() {
- // We can't get access to this method from physical node on planning phase and Calcite dosn't use it either,
- // so we can return any value here, it can't be used.
- return Statistics.UNKNOWN;
+ throw new UnsupportedOperationException();
}
/** {@inheritDoc} */
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/ImplementableFunction.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/ImplementableFunction.java
new file mode 100644
index 0000000..a30c01f
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/ImplementableFunction.java
@@ -0,0 +1,36 @@
+/*
+ * 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.ignite.internal.processors.query.calcite.exec.exp;
+
+import org.apache.calcite.schema.Function;
+import org.apache.calcite.schema.ScalarFunction;
+import org.apache.calcite.schema.TableFunction;
+
+/**
+ * Function that can be translated to java code.
+ *
+ * @see ScalarFunction
+ * @see TableFunction
+ */
+public interface ImplementableFunction extends Function {
+ /**
+ * Returns implementor that translates the function to linq4j expression.
+ *
+ * @return implementor that translates the function to linq4j expression.
+ */
+ CallImplementor getImplementor();
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/NotNullImplementor.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/NotNullImplementor.java
new file mode 100644
index 0000000..2ff3061
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/NotNullImplementor.java
@@ -0,0 +1,43 @@
+/*
+ * 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.ignite.internal.processors.query.calcite.exec.exp;
+
+import java.util.List;
+
+import org.apache.calcite.linq4j.tree.Expression;
+import org.apache.calcite.rex.RexCall;
+
+/**
+ * Simplified version of {@link CallImplementor} that does not know about null semantics.
+ *
+ * @see RexImpTable
+ * @see CallImplementor
+ */
+public interface NotNullImplementor {
+ /**
+ * Implements a call with assumption that all the null-checking is implemented by caller.
+ *
+ * @param translator translator to implement the code
+ * @param call call to implement
+ * @param translatedOperands arguments of a call
+ * @return expression that implements given call
+ */
+ Expression implement(
+ RexToLixTranslator translator,
+ RexCall call,
+ List<Expression> translatedOperands);
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/ReflectiveCallNotNullImplementor.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/ReflectiveCallNotNullImplementor.java
new file mode 100644
index 0000000..857605b
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/ReflectiveCallNotNullImplementor.java
@@ -0,0 +1,82 @@
+/*
+ * 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.ignite.internal.processors.query.calcite.exec.exp;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.List;
+
+import org.apache.calcite.linq4j.tree.Expression;
+import org.apache.calcite.linq4j.tree.Expressions;
+import org.apache.calcite.rex.RexCall;
+
+/**
+ * Implementation of {@link NotNullImplementor} that calls a given {@link Method}.
+ *
+ * <p>When method is not static, a new instance of the required class is
+ * created.
+ */
+public class ReflectiveCallNotNullImplementor implements NotNullImplementor {
+ /** */
+ protected final Method method;
+
+ /**
+ * Constructor of ReflectiveCallNotNullImplementor.
+ *
+ * @param method Method that is used to implement the call
+ */
+ public ReflectiveCallNotNullImplementor(Method method) {
+ this.method = method;
+ }
+
+ /** {@inheritDoc} */
+ @Override public Expression implement(RexToLixTranslator translator,
+ RexCall call, List<Expression> translatedOperands) {
+ translatedOperands =
+ ConverterUtils.fromInternal(method.getParameterTypes(), translatedOperands);
+ translatedOperands =
+ ConverterUtils.convertAssignableTypes(method.getParameterTypes(), translatedOperands);
+ final Expression callExpr;
+ if ((method.getModifiers() & Modifier.STATIC) != 0)
+ callExpr = Expressions.call(method, translatedOperands);
+
+ else {
+ // The UDF class must have a public zero-args constructor.
+ // Assume that the validator checked already.
+ final Expression target =
+ Expressions.new_(method.getDeclaringClass());
+ callExpr = Expressions.call(target, method, translatedOperands);
+ }
+ if (!containsCheckedException(method))
+ return callExpr;
+
+ return translator.handleMethodCheckedExceptions(callExpr);
+ }
+
+ /** */
+ private boolean containsCheckedException(Method method) {
+ Class[] exceptions = method.getExceptionTypes();
+ if (exceptions == null || exceptions.length == 0)
+ return false;
+
+ for (Class clazz : exceptions) {
+ if (!RuntimeException.class.isAssignableFrom(clazz))
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/RexImpTable.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/RexImpTable.java
new file mode 100644
index 0000000..1cc958e
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/RexImpTable.java
@@ -0,0 +1,2575 @@
+/*
+ * 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.ignite.internal.processors.query.calcite.exec.exp;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.lang.reflect.Type;
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+
+import com.google.common.collect.ImmutableList;
+import org.apache.calcite.adapter.enumerable.EnumUtils;
+import org.apache.calcite.adapter.enumerable.NullPolicy;
+import org.apache.calcite.adapter.java.JavaTypeFactory;
+import org.apache.calcite.avatica.util.DateTimeUtils;
+import org.apache.calcite.avatica.util.TimeUnit;
+import org.apache.calcite.avatica.util.TimeUnitRange;
+import org.apache.calcite.linq4j.tree.BlockBuilder;
+import org.apache.calcite.linq4j.tree.ConstantExpression;
+import org.apache.calcite.linq4j.tree.Expression;
+import org.apache.calcite.linq4j.tree.ExpressionType;
+import org.apache.calcite.linq4j.tree.Expressions;
+import org.apache.calcite.linq4j.tree.MemberExpression;
+import org.apache.calcite.linq4j.tree.MethodCallExpression;
+import org.apache.calcite.linq4j.tree.OptimizeShuttle;
+import org.apache.calcite.linq4j.tree.ParameterExpression;
+import org.apache.calcite.linq4j.tree.Primitive;
+import org.apache.calcite.rel.type.RelDataType;
+import org.apache.calcite.rel.type.RelDataTypeFactory;
+import org.apache.calcite.rel.type.RelDataTypeFactoryImpl;
+import org.apache.calcite.rex.RexCall;
+import org.apache.calcite.rex.RexLiteral;
+import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.runtime.SqlFunctions;
+import org.apache.calcite.schema.QueryableTable;
+import org.apache.calcite.schema.SchemaPlus;
+import org.apache.calcite.sql.SqlBinaryOperator;
+import org.apache.calcite.sql.SqlJsonEmptyOrError;
+import org.apache.calcite.sql.SqlJsonValueEmptyOrErrorBehavior;
+import org.apache.calcite.sql.SqlKind;
+import org.apache.calcite.sql.SqlOperator;
+import org.apache.calcite.sql.SqlTableFunction;
+import org.apache.calcite.sql.SqlTypeConstructorFunction;
+import org.apache.calcite.sql.fun.SqlStdOperatorTable;
+import org.apache.calcite.sql.fun.SqlTrimFunction;
+import org.apache.calcite.sql.type.SqlTypeName;
+import org.apache.calcite.sql.type.SqlTypeUtil;
+import org.apache.calcite.sql.validate.SqlUserDefinedFunction;
+import org.apache.calcite.sql.validate.SqlUserDefinedTableFunction;
+import org.apache.calcite.sql.validate.SqlUserDefinedTableMacro;
+import org.apache.calcite.util.BuiltInMethod;
+import org.apache.calcite.util.Util;
+
+import static org.apache.calcite.adapter.enumerable.EnumUtils.generateCollatorExpression;
+import static org.apache.calcite.linq4j.tree.ExpressionType.Add;
+import static org.apache.calcite.linq4j.tree.ExpressionType.Divide;
+import static org.apache.calcite.linq4j.tree.ExpressionType.Equal;
+import static org.apache.calcite.linq4j.tree.ExpressionType.GreaterThan;
+import static org.apache.calcite.linq4j.tree.ExpressionType.GreaterThanOrEqual;
+import static org.apache.calcite.linq4j.tree.ExpressionType.LessThan;
+import static org.apache.calcite.linq4j.tree.ExpressionType.LessThanOrEqual;
+import static org.apache.calcite.linq4j.tree.ExpressionType.Multiply;
+import static org.apache.calcite.linq4j.tree.ExpressionType.Negate;
+import static org.apache.calcite.linq4j.tree.ExpressionType.NotEqual;
+import static org.apache.calcite.linq4j.tree.ExpressionType.Subtract;
+import static org.apache.calcite.linq4j.tree.ExpressionType.UnaryPlus;
+import static org.apache.calcite.sql.fun.SqlLibraryOperators.CHR;
+import static org.apache.calcite.sql.fun.SqlLibraryOperators.COMPRESS;
+import static org.apache.calcite.sql.fun.SqlLibraryOperators.CONCAT2;
+import static org.apache.calcite.sql.fun.SqlLibraryOperators.CONCAT_FUNCTION;
+import static org.apache.calcite.sql.fun.SqlLibraryOperators.COSH;
+import static org.apache.calcite.sql.fun.SqlLibraryOperators.DATE;
+import static org.apache.calcite.sql.fun.SqlLibraryOperators.DATE_FROM_UNIX_DATE;
+import static org.apache.calcite.sql.fun.SqlLibraryOperators.DAYNAME;
+import static org.apache.calcite.sql.fun.SqlLibraryOperators.DIFFERENCE;
+import static org.apache.calcite.sql.fun.SqlLibraryOperators.EXISTS_NODE;
+import static org.apache.calcite.sql.fun.SqlLibraryOperators.EXTRACT_VALUE;
+import static org.apache.calcite.sql.fun.SqlLibraryOperators.EXTRACT_XML;
+import static org.apache.calcite.sql.fun.SqlLibraryOperators.FROM_BASE64;
+import static org.apache.calcite.sql.fun.SqlLibraryOperators.JSON_DEPTH;
+import static org.apache.calcite.sql.fun.SqlLibraryOperators.JSON_KEYS;
+import static org.apache.calcite.sql.fun.SqlLibraryOperators.JSON_LENGTH;
+import static org.apache.calcite.sql.fun.SqlLibraryOperators.JSON_PRETTY;
+import static org.apache.calcite.sql.fun.SqlLibraryOperators.JSON_REMOVE;
+import static org.apache.calcite.sql.fun.SqlLibraryOperators.JSON_STORAGE_SIZE;
+import static org.apache.calcite.sql.fun.SqlLibraryOperators.JSON_TYPE;
+import static org.apache.calcite.sql.fun.SqlLibraryOperators.LEFT;
+import static org.apache.calcite.sql.fun.SqlLibraryOperators.MD5;
+import static org.apache.calcite.sql.fun.SqlLibraryOperators.MONTHNAME;
+import static org.apache.calcite.sql.fun.SqlLibraryOperators.REGEXP_REPLACE;
+import static org.apache.calcite.sql.fun.SqlLibraryOperators.REPEAT;
+import static org.apache.calcite.sql.fun.SqlLibraryOperators.REVERSE;
+import static org.apache.calcite.sql.fun.SqlLibraryOperators.RIGHT;
+import static org.apache.calcite.sql.fun.SqlLibraryOperators.SHA1;
+import static org.apache.calcite.sql.fun.SqlLibraryOperators.SINH;
+import static org.apache.calcite.sql.fun.SqlLibraryOperators.SOUNDEX;
+import static org.apache.calcite.sql.fun.SqlLibraryOperators.SPACE;
+import static org.apache.calcite.sql.fun.SqlLibraryOperators.STRCMP;
+import static org.apache.calcite.sql.fun.SqlLibraryOperators.TANH;
+import static org.apache.calcite.sql.fun.SqlLibraryOperators.TIMESTAMP_MICROS;
+import static org.apache.calcite.sql.fun.SqlLibraryOperators.TIMESTAMP_MILLIS;
+import static org.apache.calcite.sql.fun.SqlLibraryOperators.TIMESTAMP_SECONDS;
+import static org.apache.calcite.sql.fun.SqlLibraryOperators.TO_BASE64;
+import static org.apache.calcite.sql.fun.SqlLibraryOperators.TRANSLATE3;
+import static org.apache.calcite.sql.fun.SqlLibraryOperators.UNIX_DATE;
+import static org.apache.calcite.sql.fun.SqlLibraryOperators.UNIX_MICROS;
+import static org.apache.calcite.sql.fun.SqlLibraryOperators.UNIX_MILLIS;
+import static org.apache.calcite.sql.fun.SqlLibraryOperators.UNIX_SECONDS;
+import static org.apache.calcite.sql.fun.SqlLibraryOperators.XML_TRANSFORM;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.ABS;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.ACOS;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.AND;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.ARRAY_VALUE_CONSTRUCTOR;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.ASCII;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.ASIN;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.ATAN;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.ATAN2;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.CARDINALITY;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.CAST;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.CBRT;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.CEIL;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.CHARACTER_LENGTH;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.CHAR_LENGTH;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.COALESCE;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.CONCAT;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.COS;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.COT;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.CURRENT_CATALOG;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.CURRENT_DATE;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.CURRENT_PATH;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.CURRENT_ROLE;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.CURRENT_TIME;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.CURRENT_TIMESTAMP;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.CURRENT_USER;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.CURRENT_VALUE;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.DATETIME_PLUS;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.DEFAULT;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.DEGREES;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.DIVIDE;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.DIVIDE_INTEGER;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.ELEMENT;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.EQUALS;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.EXP;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.EXTRACT;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.FLOOR;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.GREATER_THAN;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.GREATER_THAN_OR_EQUAL;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.INITCAP;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.IS_A_SET;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.IS_EMPTY;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.IS_FALSE;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.IS_JSON_ARRAY;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.IS_JSON_OBJECT;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.IS_JSON_SCALAR;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.IS_JSON_VALUE;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.IS_NOT_A_SET;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.IS_NOT_EMPTY;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.IS_NOT_FALSE;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.IS_NOT_JSON_ARRAY;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.IS_NOT_JSON_OBJECT;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.IS_NOT_JSON_SCALAR;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.IS_NOT_JSON_VALUE;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.IS_NOT_NULL;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.IS_NOT_TRUE;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.IS_NULL;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.IS_TRUE;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.ITEM;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.JSON_ARRAY;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.JSON_EXISTS;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.JSON_OBJECT;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.JSON_QUERY;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.JSON_VALUE;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.JSON_VALUE_EXPRESSION;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.LAST_DAY;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.LESS_THAN;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.LESS_THAN_OR_EQUAL;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.LIKE;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.LN;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.LOCALTIME;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.LOCALTIMESTAMP;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.LOG10;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.LOWER;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.MAP_VALUE_CONSTRUCTOR;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.MEMBER_OF;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.MINUS;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.MINUS_DATE;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.MOD;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.MULTIPLY;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.MULTISET_EXCEPT;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.MULTISET_EXCEPT_DISTINCT;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.MULTISET_INTERSECT;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.MULTISET_INTERSECT_DISTINCT;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.MULTISET_UNION;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.MULTISET_UNION_DISTINCT;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.NEXT_VALUE;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.NOT;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.NOT_EQUALS;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.NOT_LIKE;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.NOT_SIMILAR_TO;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.NOT_SUBMULTISET_OF;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.OR;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.OVERLAY;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.PI;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.PLUS;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.POSITION;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.POWER;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.RADIANS;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.RAND;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.RAND_INTEGER;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.REINTERPRET;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.REPLACE;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.ROUND;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.ROW;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.SESSION_USER;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.SIGN;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.SIMILAR_TO;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.SIN;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.SLICE;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.STRUCT_ACCESS;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.SUBMULTISET_OF;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.SUBSTRING;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.SYSTEM_USER;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.TAN;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.TRIM;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.TRUNCATE;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.UNARY_MINUS;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.UNARY_PLUS;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.UPPER;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.USER;
+import static org.apache.ignite.internal.processors.query.calcite.sql.fun.IgniteSqlOperatorTable.LENGTH;
+import static org.apache.ignite.internal.processors.query.calcite.sql.fun.IgniteSqlOperatorTable.SYSTEM_RANGE;
+
+/**
+ * Contains implementations of Rex operators as Java code.
+ */
+public class RexImpTable {
+ /** */
+ public static final RexImpTable INSTANCE = new RexImpTable();
+
+ /** */
+ public static final ConstantExpression NULL_EXPR = Expressions.constant(null);
+
+ /** */
+ public static final ConstantExpression FALSE_EXPR = Expressions.constant(false);
+
+ /** */
+ public static final ConstantExpression TRUE_EXPR = Expressions.constant(true);
+
+ /** */
+ public static final MemberExpression BOXED_FALSE_EXPR = Expressions.field(null, Boolean.class, "FALSE");
+
+ /** */
+ public static final MemberExpression BOXED_TRUE_EXPR = Expressions.field(null, Boolean.class, "TRUE");
+
+ /** */
+ private final Map<SqlOperator, RexCallImplementor> map = new HashMap<>();
+
+ /** */
+ RexImpTable() {
+ defineMethod(ROW, BuiltInMethod.ARRAY.method, NullPolicy.NONE);
+ defineMethod(UPPER, BuiltInMethod.UPPER.method, NullPolicy.STRICT);
+ defineMethod(LOWER, BuiltInMethod.LOWER.method, NullPolicy.STRICT);
+ defineMethod(INITCAP, BuiltInMethod.INITCAP.method, NullPolicy.STRICT);
+ defineMethod(TO_BASE64, BuiltInMethod.TO_BASE64.method, NullPolicy.STRICT);
+ defineMethod(FROM_BASE64, BuiltInMethod.FROM_BASE64.method, NullPolicy.STRICT);
+ defineMethod(MD5, BuiltInMethod.MD5.method, NullPolicy.STRICT);
+ defineMethod(SHA1, BuiltInMethod.SHA1.method, NullPolicy.STRICT);
+ defineMethod(SUBSTRING, BuiltInMethod.SUBSTRING.method, NullPolicy.STRICT);
+ defineMethod(LEFT, BuiltInMethod.LEFT.method, NullPolicy.ANY);
+ defineMethod(RIGHT, BuiltInMethod.RIGHT.method, NullPolicy.ANY);
+ defineMethod(REPLACE, BuiltInMethod.REPLACE.method, NullPolicy.STRICT);
+ defineMethod(TRANSLATE3, BuiltInMethod.TRANSLATE3.method, NullPolicy.STRICT);
+ defineMethod(CHR, "chr", NullPolicy.STRICT);
+ defineMethod(CHARACTER_LENGTH, BuiltInMethod.CHAR_LENGTH.method, NullPolicy.STRICT);
+ defineMethod(CHAR_LENGTH, BuiltInMethod.CHAR_LENGTH.method, NullPolicy.STRICT);
+ defineMethod(LENGTH, BuiltInMethod.CHAR_LENGTH.method, NullPolicy.STRICT);
+ defineMethod(CONCAT, BuiltInMethod.STRING_CONCAT.method, NullPolicy.STRICT);
+ defineMethod(CONCAT_FUNCTION, BuiltInMethod.MULTI_STRING_CONCAT.method, NullPolicy.STRICT);
+ defineMethod(CONCAT2, BuiltInMethod.STRING_CONCAT.method, NullPolicy.STRICT);
+ defineMethod(OVERLAY, BuiltInMethod.OVERLAY.method, NullPolicy.STRICT);
+ defineMethod(POSITION, BuiltInMethod.POSITION.method, NullPolicy.STRICT);
+ defineMethod(ASCII, BuiltInMethod.ASCII.method, NullPolicy.STRICT);
+ defineMethod(REPEAT, BuiltInMethod.REPEAT.method, NullPolicy.STRICT);
+ defineMethod(SPACE, BuiltInMethod.SPACE.method, NullPolicy.STRICT);
+ defineMethod(STRCMP, BuiltInMethod.STRCMP.method, NullPolicy.STRICT);
+ defineMethod(SOUNDEX, BuiltInMethod.SOUNDEX.method, NullPolicy.STRICT);
+ defineMethod(DIFFERENCE, BuiltInMethod.DIFFERENCE.method, NullPolicy.STRICT);
+ defineMethod(REVERSE, BuiltInMethod.REVERSE.method, NullPolicy.STRICT);
+
+ map.put(TRIM, new TrimImplementor());
+
+ // logical
+ map.put(AND, new LogicalAndImplementor());
+ map.put(OR, new LogicalOrImplementor());
+ map.put(NOT, new LogicalNotImplementor());
+
+ // comparisons
+ defineBinary(LESS_THAN, LessThan, NullPolicy.STRICT, "lt");
+ defineBinary(LESS_THAN_OR_EQUAL, LessThanOrEqual, NullPolicy.STRICT, "le");
+ defineBinary(GREATER_THAN, GreaterThan, NullPolicy.STRICT, "gt");
+ defineBinary(GREATER_THAN_OR_EQUAL, GreaterThanOrEqual, NullPolicy.STRICT,
+ "ge");
+ defineBinary(EQUALS, Equal, NullPolicy.STRICT, "eq");
+ defineBinary(NOT_EQUALS, NotEqual, NullPolicy.STRICT, "ne");
+
+ // arithmetic
+ defineBinary(PLUS, Add, NullPolicy.STRICT, "plus");
+ defineBinary(MINUS, Subtract, NullPolicy.STRICT, "minus");
+ defineBinary(MULTIPLY, Multiply, NullPolicy.STRICT, "multiply");
+ defineBinary(DIVIDE, Divide, NullPolicy.STRICT, "divide");
+ defineBinary(DIVIDE_INTEGER, Divide, NullPolicy.STRICT, "divide");
+ defineUnary(UNARY_MINUS, Negate, NullPolicy.STRICT,
+ BuiltInMethod.BIG_DECIMAL_NEGATE.getMethodName());
+ defineUnary(UNARY_PLUS, UnaryPlus, NullPolicy.STRICT, null);
+
+ defineMethod(MOD, "mod", NullPolicy.STRICT);
+ defineMethod(EXP, "exp", NullPolicy.STRICT);
+ defineMethod(POWER, "power", NullPolicy.STRICT);
+ defineMethod(LN, "ln", NullPolicy.STRICT);
+ defineMethod(LOG10, "log10", NullPolicy.STRICT);
+ defineMethod(ABS, "abs", NullPolicy.STRICT);
+
+ map.put(RAND, new RandImplementor());
+ map.put(RAND_INTEGER, new RandIntegerImplementor());
+
+ defineMethod(ACOS, "acos", NullPolicy.STRICT);
+ defineMethod(ASIN, "asin", NullPolicy.STRICT);
+ defineMethod(ATAN, "atan", NullPolicy.STRICT);
+ defineMethod(ATAN2, "atan2", NullPolicy.STRICT);
+ defineMethod(CBRT, "cbrt", NullPolicy.STRICT);
+ defineMethod(COS, "cos", NullPolicy.STRICT);
+ defineMethod(COSH, "cosh", NullPolicy.STRICT);
+ defineMethod(COT, "cot", NullPolicy.STRICT);
+ defineMethod(DEGREES, "degrees", NullPolicy.STRICT);
+ defineMethod(RADIANS, "radians", NullPolicy.STRICT);
+ defineMethod(ROUND, "sround", NullPolicy.STRICT);
+ defineMethod(SIGN, "sign", NullPolicy.STRICT);
+ defineMethod(SIN, "sin", NullPolicy.STRICT);
+ defineMethod(SINH, "sinh", NullPolicy.STRICT);
+ defineMethod(TAN, "tan", NullPolicy.STRICT);
+ defineMethod(TANH, "tanh", NullPolicy.STRICT);
+ defineMethod(TRUNCATE, "struncate", NullPolicy.STRICT);
+
+ map.put(PI, new PiImplementor());
+
+ // datetime
+ map.put(DATETIME_PLUS, new DatetimeArithmeticImplementor());
+ map.put(MINUS_DATE, new DatetimeArithmeticImplementor());
+ map.put(EXTRACT, new ExtractImplementor());
+ map.put(FLOOR,
+ new FloorImplementor(BuiltInMethod.FLOOR.method.getName(),
+ BuiltInMethod.UNIX_TIMESTAMP_FLOOR.method,
+ BuiltInMethod.UNIX_DATE_FLOOR.method));
+ map.put(CEIL,
+ new FloorImplementor(BuiltInMethod.CEIL.method.getName(),
+ BuiltInMethod.UNIX_TIMESTAMP_CEIL.method,
+ BuiltInMethod.UNIX_DATE_CEIL.method));
+
+ defineMethod(LAST_DAY, "lastDay", NullPolicy.STRICT);
+ map.put(DAYNAME,
+ new PeriodNameImplementor("dayName",
+ BuiltInMethod.DAYNAME_WITH_TIMESTAMP,
+ BuiltInMethod.DAYNAME_WITH_DATE));
+ map.put(MONTHNAME,
+ new PeriodNameImplementor("monthName",
+ BuiltInMethod.MONTHNAME_WITH_TIMESTAMP,
+ BuiltInMethod.MONTHNAME_WITH_DATE));
+ defineMethod(TIMESTAMP_SECONDS, "timestampSeconds", NullPolicy.STRICT);
+ defineMethod(TIMESTAMP_MILLIS, "timestampMillis", NullPolicy.STRICT);
+ defineMethod(TIMESTAMP_MICROS, "timestampMicros", NullPolicy.STRICT);
+ defineMethod(UNIX_SECONDS, "unixSeconds", NullPolicy.STRICT);
+ defineMethod(UNIX_MILLIS, "unixMillis", NullPolicy.STRICT);
+ defineMethod(UNIX_MICROS, "unixMicros", NullPolicy.STRICT);
+ defineMethod(DATE_FROM_UNIX_DATE, "dateFromUnixDate", NullPolicy.STRICT);
+ defineMethod(UNIX_DATE, "unixDate", NullPolicy.STRICT);
+
+ map.put(IS_NULL, new IsNullImplementor());
+ map.put(IS_NOT_NULL, new IsNotNullImplementor());
+ map.put(IS_TRUE, new IsTrueImplementor());
+ map.put(IS_NOT_TRUE, new IsNotTrueImplementor());
+ map.put(IS_FALSE, new IsFalseImplementor());
+ map.put(IS_NOT_FALSE, new IsNotFalseImplementor());
+
+ // LIKE and SIMILAR
+ final MethodImplementor likeImplementor =
+ new MethodImplementor(BuiltInMethod.LIKE.method, NullPolicy.STRICT,
+ false);
+ map.put(LIKE, likeImplementor);
+ map.put(NOT_LIKE, likeImplementor);
+ final MethodImplementor similarImplementor =
+ new MethodImplementor(BuiltInMethod.SIMILAR.method, NullPolicy.STRICT,
+ false);
+ map.put(SIMILAR_TO, similarImplementor);
+ map.put(NOT_SIMILAR_TO, NotImplementor.of(similarImplementor));
+
+ // POSIX REGEX
+ final MethodImplementor posixRegexImplementor =
+ new MethodImplementor(BuiltInMethod.POSIX_REGEX.method,
+ NullPolicy.STRICT, false);
+ map.put(SqlStdOperatorTable.POSIX_REGEX_CASE_INSENSITIVE,
+ posixRegexImplementor);
+ map.put(SqlStdOperatorTable.POSIX_REGEX_CASE_SENSITIVE,
+ posixRegexImplementor);
+ map.put(SqlStdOperatorTable.NEGATED_POSIX_REGEX_CASE_INSENSITIVE,
+ NotImplementor.of(posixRegexImplementor));
+ map.put(SqlStdOperatorTable.NEGATED_POSIX_REGEX_CASE_SENSITIVE,
+ NotImplementor.of(posixRegexImplementor));
+ map.put(REGEXP_REPLACE, new RegexpReplaceImplementor());
+
+ // Multisets & arrays
+ defineMethod(CARDINALITY, BuiltInMethod.COLLECTION_SIZE.method,
+ NullPolicy.STRICT);
+ defineMethod(SLICE, BuiltInMethod.SLICE.method, NullPolicy.NONE);
+ defineMethod(ELEMENT, BuiltInMethod.ELEMENT.method, NullPolicy.STRICT);
+ defineMethod(STRUCT_ACCESS, BuiltInMethod.STRUCT_ACCESS.method, NullPolicy.ANY);
+ defineMethod(MEMBER_OF, BuiltInMethod.MEMBER_OF.method, NullPolicy.NONE);
+ final MethodImplementor isEmptyImplementor =
+ new MethodImplementor(BuiltInMethod.IS_EMPTY.method, NullPolicy.NONE,
+ false);
+ map.put(IS_EMPTY, isEmptyImplementor);
+ map.put(IS_NOT_EMPTY, NotImplementor.of(isEmptyImplementor));
+ final MethodImplementor isASetImplementor =
+ new MethodImplementor(BuiltInMethod.IS_A_SET.method, NullPolicy.NONE,
+ false);
+ map.put(IS_A_SET, isASetImplementor);
+ map.put(IS_NOT_A_SET, NotImplementor.of(isASetImplementor));
+ defineMethod(MULTISET_INTERSECT_DISTINCT,
+ BuiltInMethod.MULTISET_INTERSECT_DISTINCT.method, NullPolicy.NONE);
+ defineMethod(MULTISET_INTERSECT,
+ BuiltInMethod.MULTISET_INTERSECT_ALL.method, NullPolicy.NONE);
+ defineMethod(MULTISET_EXCEPT_DISTINCT,
+ BuiltInMethod.MULTISET_EXCEPT_DISTINCT.method, NullPolicy.NONE);
+ defineMethod(MULTISET_EXCEPT, BuiltInMethod.MULTISET_EXCEPT_ALL.method, NullPolicy.NONE);
+ defineMethod(MULTISET_UNION_DISTINCT,
+ BuiltInMethod.MULTISET_UNION_DISTINCT.method, NullPolicy.NONE);
+ defineMethod(MULTISET_UNION, BuiltInMethod.MULTISET_UNION_ALL.method, NullPolicy.NONE);
+ final MethodImplementor subMultisetImplementor =
+ new MethodImplementor(BuiltInMethod.SUBMULTISET_OF.method, NullPolicy.NONE, false);
+ map.put(SUBMULTISET_OF, subMultisetImplementor);
+ map.put(NOT_SUBMULTISET_OF, NotImplementor.of(subMultisetImplementor));
+
+ map.put(COALESCE, new CoalesceImplementor());
+ map.put(CAST, new CastImplementor());
+ map.put(DATE, new CastImplementor());
+
+ map.put(REINTERPRET, new ReinterpretImplementor());
+
+ final RexCallImplementor value = new ValueConstructorImplementor();
+ map.put(MAP_VALUE_CONSTRUCTOR, value);
+ map.put(ARRAY_VALUE_CONSTRUCTOR, value);
+ map.put(ITEM, new ItemImplementor());
+
+ map.put(DEFAULT, new DefaultImplementor());
+
+ // Sequences
+ defineMethod(CURRENT_VALUE, BuiltInMethod.SEQUENCE_CURRENT_VALUE.method,
+ NullPolicy.STRICT);
+ defineMethod(NEXT_VALUE, BuiltInMethod.SEQUENCE_NEXT_VALUE.method,
+ NullPolicy.STRICT);
+
+ // Compression Operators
+ defineMethod(COMPRESS, BuiltInMethod.COMPRESS.method, NullPolicy.ARG0);
+
+ // Xml Operators
+ defineMethod(EXTRACT_VALUE, BuiltInMethod.EXTRACT_VALUE.method, NullPolicy.ARG0);
+ defineMethod(XML_TRANSFORM, BuiltInMethod.XML_TRANSFORM.method, NullPolicy.ARG0);
+ defineMethod(EXTRACT_XML, BuiltInMethod.EXTRACT_XML.method, NullPolicy.ARG0);
+ defineMethod(EXISTS_NODE, BuiltInMethod.EXISTS_NODE.method, NullPolicy.ARG0);
+
+ // Json Operators
+ defineMethod(JSON_VALUE_EXPRESSION,
+ BuiltInMethod.JSON_VALUE_EXPRESSION.method, NullPolicy.STRICT);
+ defineMethod(JSON_EXISTS, BuiltInMethod.JSON_EXISTS.method, NullPolicy.ARG0);
+ map.put(JSON_VALUE,
+ new JsonValueImplementor(BuiltInMethod.JSON_VALUE.method));
+ defineMethod(JSON_QUERY, BuiltInMethod.JSON_QUERY.method, NullPolicy.ARG0);
+ defineMethod(JSON_TYPE, BuiltInMethod.JSON_TYPE.method, NullPolicy.ARG0);
+ defineMethod(JSON_DEPTH, BuiltInMethod.JSON_DEPTH.method, NullPolicy.ARG0);
+ defineMethod(JSON_KEYS, BuiltInMethod.JSON_KEYS.method, NullPolicy.ARG0);
+ defineMethod(JSON_PRETTY, BuiltInMethod.JSON_PRETTY.method, NullPolicy.ARG0);
+ defineMethod(JSON_LENGTH, BuiltInMethod.JSON_LENGTH.method, NullPolicy.ARG0);
+ defineMethod(JSON_REMOVE, BuiltInMethod.JSON_REMOVE.method, NullPolicy.ARG0);
+ defineMethod(JSON_STORAGE_SIZE, BuiltInMethod.JSON_STORAGE_SIZE.method, NullPolicy.ARG0);
+ defineMethod(JSON_OBJECT, BuiltInMethod.JSON_OBJECT.method, NullPolicy.NONE);
+ defineMethod(JSON_ARRAY, BuiltInMethod.JSON_ARRAY.method, NullPolicy.NONE);
+ map.put(IS_JSON_VALUE,
+ new MethodImplementor(BuiltInMethod.IS_JSON_VALUE.method,
+ NullPolicy.NONE, false));
+ map.put(IS_JSON_OBJECT,
+ new MethodImplementor(BuiltInMethod.IS_JSON_OBJECT.method,
+ NullPolicy.NONE, false));
+ map.put(IS_JSON_ARRAY,
+ new MethodImplementor(BuiltInMethod.IS_JSON_ARRAY.method,
+ NullPolicy.NONE, false));
+ map.put(IS_JSON_SCALAR,
+ new MethodImplementor(BuiltInMethod.IS_JSON_SCALAR.method,
+ NullPolicy.NONE, false));
+ map.put(IS_NOT_JSON_VALUE,
+ NotImplementor.of(
+ new MethodImplementor(BuiltInMethod.IS_JSON_VALUE.method,
+ NullPolicy.NONE, false)));
+ map.put(IS_NOT_JSON_OBJECT,
+ NotImplementor.of(
+ new MethodImplementor(BuiltInMethod.IS_JSON_OBJECT.method,
+ NullPolicy.NONE, false)));
+ map.put(IS_NOT_JSON_ARRAY,
+ NotImplementor.of(
+ new MethodImplementor(BuiltInMethod.IS_JSON_ARRAY.method,
+ NullPolicy.NONE, false)));
+ map.put(IS_NOT_JSON_SCALAR,
+ NotImplementor.of(
+ new MethodImplementor(BuiltInMethod.IS_JSON_SCALAR.method,
+ NullPolicy.NONE, false)));
+
+ // System functions
+ final SystemFunctionImplementor systemFunctionImplementor = new SystemFunctionImplementor();
+ map.put(USER, systemFunctionImplementor);
+ map.put(CURRENT_USER, systemFunctionImplementor);
+ map.put(SESSION_USER, systemFunctionImplementor);
+ map.put(SYSTEM_USER, systemFunctionImplementor);
+ map.put(CURRENT_PATH, systemFunctionImplementor);
+ map.put(CURRENT_ROLE, systemFunctionImplementor);
+ map.put(CURRENT_CATALOG, systemFunctionImplementor);
+ map.put(SYSTEM_RANGE, systemFunctionImplementor);
+
+ // Current time functions
+ map.put(CURRENT_TIME, systemFunctionImplementor);
+ map.put(CURRENT_TIMESTAMP, systemFunctionImplementor);
+ map.put(CURRENT_DATE, systemFunctionImplementor);
+ map.put(LOCALTIME, systemFunctionImplementor);
+ map.put(LOCALTIMESTAMP, systemFunctionImplementor);
+ }
+
+ /** */
+ private <T> Supplier<T> constructorSupplier(Class<T> klass) {
+ final Constructor<T> constructor;
+ try {
+ constructor = klass.getDeclaredConstructor();
+ }
+ catch (NoSuchMethodException e) {
+ throw new IllegalArgumentException(
+ klass + " should implement zero arguments constructor");
+ }
+ return () -> {
+ try {
+ return constructor.newInstance();
+ }
+ catch (InstantiationException | IllegalAccessException
+ | InvocationTargetException e) {
+ throw new IllegalStateException(
+ "Error while creating aggregate implementor " + constructor, e);
+ }
+ };
+ }
+
+ /** */
+ private void defineMethod(SqlOperator operator, String functionName, NullPolicy nullPolicy) {
+ map.put(operator, new MethodNameImplementor(functionName, nullPolicy, false));
+ }
+
+ /** */
+ private void defineMethod(SqlOperator operator, Method method, NullPolicy nullPolicy) {
+ map.put(operator, new MethodImplementor(method, nullPolicy, false));
+ }
+
+ /** */
+ private void defineUnary(SqlOperator operator, ExpressionType expressionType,
+ NullPolicy nullPolicy, String backupMethodName) {
+ map.put(operator, new UnaryImplementor(expressionType, nullPolicy, backupMethodName));
+ }
+
+ /** */
+ private void defineBinary(SqlOperator operator, ExpressionType expressionType,
+ NullPolicy nullPolicy, String backupMethodName) {
+ map.put(operator,
+ new BinaryImplementor(nullPolicy, true, expressionType,
+ backupMethodName));
+ }
+
+ /** */
+ private static RexCallImplementor wrapAsRexCallImplementor(
+ final CallImplementor implementor) {
+ return new AbstractRexCallImplementor(NullPolicy.NONE, false) {
+ @Override String getVariableName() {
+ return "udf";
+ }
+
+ @Override Expression implementSafe(RexToLixTranslator translator,
+ RexCall call, List<Expression> argValueList) {
+ return implementor.implement(translator, call, RexImpTable.NullAs.NULL);
+ }
+ };
+ }
+
+ /** */
+ public RexCallImplementor get(final SqlOperator operator) {
+ if (operator instanceof SqlUserDefinedFunction) {
+ org.apache.calcite.schema.Function udf =
+ ((SqlUserDefinedFunction)operator).getFunction();
+ if (!(udf instanceof ImplementableFunction)) {
+ throw new IllegalStateException("User defined function " + operator
+ + " must implement ImplementableFunction");
+ }
+ CallImplementor implementor = ((ImplementableFunction)udf).getImplementor();
+ return wrapAsRexCallImplementor(implementor);
+ }
+ else if (operator instanceof SqlTypeConstructorFunction)
+ return map.get(SqlStdOperatorTable.ROW);
+
+ return map.get(operator);
+ }
+
+ /** */
+ static Expression optimize(Expression expression) {
+ return expression.accept(new OptimizeShuttle());
+ }
+
+ /** */
+ static Expression optimize2(Expression operand, Expression expression) {
+ if (Primitive.is(operand.getType())) {
+ // Primitive values cannot be null
+ return optimize(expression);
+ }
+
+ return optimize(
+ Expressions.condition(
+ Expressions.equal(operand, NULL_EXPR),
+ NULL_EXPR,
+ expression));
+ }
+
+ /** */
+ private static RelDataType toSql(RelDataTypeFactory typeFactory,
+ RelDataType type) {
+ if (type instanceof RelDataTypeFactoryImpl.JavaType) {
+ final SqlTypeName typeName = type.getSqlTypeName();
+ if (typeName != null && typeName != SqlTypeName.OTHER) {
+ return typeFactory.createTypeWithNullability(
+ typeFactory.createSqlType(typeName),
+ type.isNullable());
+ }
+ }
+ return type;
+ }
+
+ /** */
+ private static <E> boolean allSame(List<E> list) {
+ E prev = null;
+ for (E e : list) {
+ if (prev != null && !prev.equals(e))
+ return false;
+
+ prev = e;
+ }
+ return true;
+ }
+
+ /**
+ * Strategy what an operator should return if one of its arguments is null.
+ */
+ public enum NullAs {
+ /**
+ * The most common policy among the SQL built-in operators. If one of the arguments is null, returns null.
+ */
+ NULL,
+
+ /**
+ * If one of the arguments is null, the function returns false. Example: {@code IS NOT NULL}.
+ */
+ FALSE,
+
+ /**
+ * If one of the arguments is null, the function returns true. Example: {@code IS NULL}.
+ */
+ TRUE,
+
+ /**
+ * It is not possible for any of the arguments to be null. If the argument type is nullable, the enclosing code
+ * will already have performed a not-null check. This may allow the operator implementor to generate a more
+ * efficient implementation, for example, by avoiding boxing or unboxing.
+ */
+ NOT_POSSIBLE,
+
+ /** Return false if result is not null, true if result is null. */
+ IS_NULL,
+
+ /** Return true if result is not null, false if result is null. */
+ IS_NOT_NULL;
+
+ public static NullAs of(boolean nullable) {
+ return nullable ? NULL : NOT_POSSIBLE;
+ }
+
+ /**
+ * Adapts an expression with "normal" result to one that adheres to this particular policy.
+ */
+ public Expression handle(Expression x) {
+ switch (Primitive.flavor(x.getType())) {
+ case PRIMITIVE:
+ // Expression cannot be null. We can skip any runtime checks.
+ switch (this) {
+ case NULL:
+ case NOT_POSSIBLE:
+ case FALSE:
+ case TRUE:
+ return x;
+ case IS_NULL:
+ return FALSE_EXPR;
+ case IS_NOT_NULL:
+ return TRUE_EXPR;
+ default:
+ throw new AssertionError();
+ }
+ case BOX:
+ switch (this) {
+ case NOT_POSSIBLE:
+ return ConverterUtils.convert(x,
+ Primitive.ofBox(x.getType()).primitiveClass);
+ }
+ // fall through
+ }
+ switch (this) {
+ case NULL:
+ case NOT_POSSIBLE:
+ return x;
+ case FALSE:
+ return Expressions.call(BuiltInMethod.IS_TRUE.method, x);
+ case TRUE:
+ return Expressions.call(BuiltInMethod.IS_NOT_FALSE.method, x);
+ case IS_NULL:
+ return Expressions.equal(x, NULL_EXPR);
+ case IS_NOT_NULL:
+ return Expressions.notEqual(x, NULL_EXPR);
+ default:
+ throw new AssertionError();
+ }
+ }
+ }
+
+ /** */
+ static Expression getDefaultValue(Type type) {
+ if (Primitive.is(type)) {
+ Primitive p = Primitive.of(type);
+ return Expressions.constant(p.defaultValue, type);
+ }
+ return Expressions.constant(null, type);
+ }
+
+ /**
+ * Multiplies an expression by a constant and divides by another constant, optimizing appropriately.
+ *
+ * <p>For example, {@code multiplyDivide(e, 10, 1000)} returns
+ * {@code e / 100}.
+ */
+ public static Expression multiplyDivide(Expression e, BigDecimal multiplier,
+ BigDecimal divider) {
+ if (multiplier.equals(BigDecimal.ONE)) {
+ if (divider.equals(BigDecimal.ONE))
+ return e;
+
+ return Expressions.divide(e,
+ Expressions.constant(divider.intValueExact()));
+ }
+ final BigDecimal x =
+ multiplier.divide(divider, RoundingMode.UNNECESSARY);
+ switch (x.compareTo(BigDecimal.ONE)) {
+ case 0:
+ return e;
+ case 1:
+ return Expressions.multiply(e, Expressions.constant(x.intValueExact()));
+ case -1:
+ return multiplyDivide(e, BigDecimal.ONE, x);
+ default:
+ throw new AssertionError();
+ }
+ }
+
+ /** Implementor for the {@code TRIM} function. */
+ private static class TrimImplementor extends AbstractRexCallImplementor {
+ /** */
+ TrimImplementor() {
+ super(NullPolicy.STRICT, false);
+ }
+
+ /** {@inheritDoc} */
+ @Override String getVariableName() {
+ return "trim";
+ }
+
+ /** {@inheritDoc} */
+ @Override Expression implementSafe(final RexToLixTranslator translator,
+ final RexCall call, final List<Expression> argValueList) {
+ final boolean strict = !translator.conformance.allowExtendedTrim();
+ final Object value = translator.getLiteralValue(argValueList.get(0));
+ SqlTrimFunction.Flag flag = (SqlTrimFunction.Flag)value;
+ return Expressions.call(
+ BuiltInMethod.TRIM.method,
+ Expressions.constant(
+ flag == SqlTrimFunction.Flag.BOTH
+ || flag == SqlTrimFunction.Flag.LEADING),
+ Expressions.constant(
+ flag == SqlTrimFunction.Flag.BOTH
+ || flag == SqlTrimFunction.Flag.TRAILING),
+ argValueList.get(1),
+ argValueList.get(2),
+ Expressions.constant(strict));
+ }
+ }
+
+ /**
+ * Implementor for the {@code MONTHNAME} and {@code DAYNAME} functions. Each takes a {@link java.util.Locale}
+ * argument.
+ */
+ private static class PeriodNameImplementor extends MethodNameImplementor {
+ /** */
+ private final BuiltInMethod timestampMethod;
+
+ /** */
+ private final BuiltInMethod dateMethod;
+
+ /** */
+ PeriodNameImplementor(String methodName, BuiltInMethod timestampMethod,
+ BuiltInMethod dateMethod) {
+ super(methodName, NullPolicy.STRICT, false);
+ this.timestampMethod = timestampMethod;
+ this.dateMethod = dateMethod;
+ }
+
+ /** {@inheritDoc} */
+ @Override String getVariableName() {
+ return "periodName";
+ }
+
+ /** {@inheritDoc} */
+ @Override Expression implementSafe(final RexToLixTranslator translator,
+ final RexCall call, final List<Expression> argValueList) {
+ Expression operand = argValueList.get(0);
+ final RelDataType type = call.operands.get(0).getType();
+ switch (type.getSqlTypeName()) {
+ case TIMESTAMP:
+ return getExpression(translator, operand, timestampMethod);
+ case DATE:
+ return getExpression(translator, operand, dateMethod);
+ default:
+ throw new AssertionError("unknown type " + type);
+ }
+ }
+
+ /** */
+ protected Expression getExpression(RexToLixTranslator translator,
+ Expression operand, BuiltInMethod builtInMethod) {
+ final MethodCallExpression locale =
+ Expressions.call(BuiltInMethod.LOCALE.method, translator.getRoot());
+ return Expressions.call(builtInMethod.method.getDeclaringClass(),
+ builtInMethod.method.getName(), operand, locale);
+ }
+ }
+
+ /** Implementor for the {@code FLOOR} and {@code CEIL} functions. */
+ private static class FloorImplementor extends MethodNameImplementor {
+ /** */
+ final Method timestampMethod;
+
+ /** */
+ final Method dateMethod;
+
+ /** */
+ FloorImplementor(String methodName, Method timestampMethod, Method dateMethod) {
+ super(methodName, NullPolicy.STRICT, false);
+ this.timestampMethod = timestampMethod;
+ this.dateMethod = dateMethod;
+ }
+
+ /** {@inheritDoc} */
+ @Override String getVariableName() {
+ return "floor";
+ }
+
+ /** {@inheritDoc} */
+ @Override Expression implementSafe(final RexToLixTranslator translator,
+ final RexCall call, final List<Expression> argValueList) {
+ switch (call.getOperands().size()) {
+ case 1:
+ switch (call.getType().getSqlTypeName()) {
+ case BIGINT:
+ case INTEGER:
+ case SMALLINT:
+ case TINYINT:
+ return argValueList.get(0);
+ default:
+ return super.implementSafe(translator, call, argValueList);
+ }
+
+ case 2:
+ final Type type;
+ final Method floorMethod;
+ final boolean preFloor;
+ Expression operand = argValueList.get(0);
+ switch (call.getType().getSqlTypeName()) {
+ case TIMESTAMP_WITH_LOCAL_TIME_ZONE:
+ operand = Expressions.call(
+ BuiltInMethod.TIMESTAMP_WITH_LOCAL_TIME_ZONE_TO_TIMESTAMP.method,
+ operand,
+ Expressions.call(BuiltInMethod.TIME_ZONE.method, translator.getRoot()));
+ // fall through
+ case TIMESTAMP:
+ type = long.class;
+ floorMethod = timestampMethod;
+ preFloor = true;
+ break;
+ default:
+ type = int.class;
+ floorMethod = dateMethod;
+ preFloor = false;
+ }
+ final TimeUnitRange timeUnitRange =
+ (TimeUnitRange)translator.getLiteralValue(argValueList.get(1));
+ switch (timeUnitRange) {
+ case YEAR:
+ case QUARTER:
+ case MONTH:
+ case WEEK:
+ case DAY:
+ final Expression operand1 =
+ preFloor ? call(operand, type, TimeUnit.DAY) : operand;
+ return Expressions.call(floorMethod,
+ translator.getLiteral(argValueList.get(1)), operand1);
+ case NANOSECOND:
+ default:
+ return call(operand, type, timeUnitRange.startUnit);
+ }
+
+ default:
+ throw new AssertionError();
+ }
+ }
+
+ /** */
+ private Expression call(Expression operand, Type type,
+ TimeUnit timeUnit) {
+ return Expressions.call(SqlFunctions.class, methodName,
+ ConverterUtils.convert(operand, type),
+ ConverterUtils.convert(
+ Expressions.constant(timeUnit.multiplier), type));
+ }
+ }
+
+ /** Implementor for a function that generates calls to a given method. */
+ private static class MethodImplementor extends AbstractRexCallImplementor {
+ /** */
+ protected final Method method;
+
+ /** */
+ MethodImplementor(Method method, NullPolicy nullPolicy, boolean harmonize) {
+ super(nullPolicy, harmonize);
+ this.method = method;
+ }
+
+ /** {@inheritDoc} */
+ @Override String getVariableName() {
+ return "method_call";
+ }
+
+ /** {@inheritDoc} */
+ @Override Expression implementSafe(RexToLixTranslator translator,
+ RexCall call, List<Expression> argValueList) {
+ final Expression expression;
+ Class clazz = method.getDeclaringClass();
+ if (Modifier.isStatic(method.getModifiers()))
+ expression = EnumUtils.call(clazz, method.getName(), argValueList);
+ else {
+ expression = EnumUtils.call(clazz, method.getName(),
+ Util.skip(argValueList, 1), argValueList.get(0));
+ }
+ return expression;
+ }
+ }
+
+ /**
+ * Implementor for JSON_VALUE function, convert to solid format "JSON_VALUE(json_doc, path, empty_behavior,
+ * empty_default, error_behavior, error default)" in order to simplify the runtime implementation.
+ *
+ * <p>We should avoid this when we support
+ * variable arguments function.
+ */
+ private static class JsonValueImplementor extends MethodImplementor {
+ /** */
+ JsonValueImplementor(Method method) {
+ super(method, NullPolicy.ARG0, false);
+ }
+
+ /** {@inheritDoc} */
+ @Override Expression implementSafe(RexToLixTranslator translator,
+ RexCall call, List<Expression> argValueList) {
+ final Expression expression;
+ final List<Expression> newOperands = new ArrayList<>();
+ newOperands.add(argValueList.get(0));
+ newOperands.add(argValueList.get(1));
+ List<Expression> leftExprs = Util.skip(argValueList, 2);
+ // Default value for JSON_VALUE behaviors.
+ Expression emptyBehavior = Expressions.constant(SqlJsonValueEmptyOrErrorBehavior.NULL);
+ Expression defaultValueOnEmpty = Expressions.constant(null);
+ Expression errorBehavior = Expressions.constant(SqlJsonValueEmptyOrErrorBehavior.NULL);
+ Expression defaultValueOnError = Expressions.constant(null);
+ // Patched up with user defines.
+ if (!leftExprs.isEmpty()) {
+ for (int i = 0; i < leftExprs.size(); i++) {
+ Expression expr = leftExprs.get(i);
+ final Object exprVal = translator.getLiteralValue(expr);
+ if (exprVal != null) {
+ int defaultSymbolIdx = i - 2;
+ if (exprVal == SqlJsonEmptyOrError.EMPTY) {
+ if (defaultSymbolIdx >= 0
+ && translator.getLiteralValue(leftExprs.get(defaultSymbolIdx))
+ == SqlJsonValueEmptyOrErrorBehavior.DEFAULT) {
+ defaultValueOnEmpty = leftExprs.get(i - 1);
+ emptyBehavior = leftExprs.get(defaultSymbolIdx);
+ }
+ else
+ emptyBehavior = leftExprs.get(i - 1);
+ }
+ else if (exprVal == SqlJsonEmptyOrError.ERROR) {
+ if (defaultSymbolIdx >= 0
+ && translator.getLiteralValue(leftExprs.get(defaultSymbolIdx))
+ == SqlJsonValueEmptyOrErrorBehavior.DEFAULT) {
+ defaultValueOnError = leftExprs.get(i - 1);
+ errorBehavior = leftExprs.get(defaultSymbolIdx);
+ }
+ else
+ errorBehavior = leftExprs.get(i - 1);
+ }
+ }
+ }
+ }
+ newOperands.add(emptyBehavior);
+ newOperands.add(defaultValueOnEmpty);
+ newOperands.add(errorBehavior);
+ newOperands.add(defaultValueOnError);
+ Class clazz = method.getDeclaringClass();
+ expression = EnumUtils.call(clazz, method.getName(), newOperands);
+
+ final Type returnType =
+ translator.typeFactory.getJavaClass(call.getType());
+ return EnumUtils.convert(expression, returnType);
+ }
+ }
+
+ /**
+ * Implementor for SQL functions that generates calls to a given method name.
+ *
+ * <p>Use this, as opposed to {@link MethodImplementor}, if the SQL function
+ * is overloaded; then you can use one implementor for several overloads.
+ */
+ private static class MethodNameImplementor extends AbstractRexCallImplementor {
+ /** */
+ protected final String methodName;
+
+ /** */
+ MethodNameImplementor(String methodName,
+ NullPolicy nullPolicy, boolean harmonize) {
+ super(nullPolicy, harmonize);
+ this.methodName = methodName;
+ }
+
+ /** {@inheritDoc} */
+ @Override String getVariableName() {
+ return "method_name_call";
+ }
+
+ /** {@inheritDoc} */
+ @Override Expression implementSafe(RexToLixTranslator translator,
+ RexCall call, List<Expression> argValueList) {
+ return EnumUtils.call(
+ SqlFunctions.class,
+ methodName,
+ argValueList);
+ }
+ }
+
+ /** Implementor for binary operators. */
+ private static class BinaryImplementor extends AbstractRexCallImplementor {
+ /**
+ * Types that can be arguments to comparison operators such as {@code <}.
+ */
+ private static final List<Primitive> COMP_OP_TYPES =
+ ImmutableList.of(
+ Primitive.BYTE,
+ Primitive.CHAR,
+ Primitive.SHORT,
+ Primitive.INT,
+ Primitive.LONG,
+ Primitive.FLOAT,
+ Primitive.DOUBLE);
+
+ /** */
+ private static final List<SqlBinaryOperator> COMPARISON_OPERATORS =
+ ImmutableList.of(
+ SqlStdOperatorTable.LESS_THAN,
+ SqlStdOperatorTable.LESS_THAN_OR_EQUAL,
+ SqlStdOperatorTable.GREATER_THAN,
+ SqlStdOperatorTable.GREATER_THAN_OR_EQUAL);
+
+ /** */
+ private static final List<SqlBinaryOperator> EQUALS_OPERATORS =
+ ImmutableList.of(
+ SqlStdOperatorTable.EQUALS,
+ SqlStdOperatorTable.NOT_EQUALS);
+
+ /** */
+ public static final String METHOD_POSTFIX_FOR_ANY_TYPE = "Any";
+
+ /** */
+ private final ExpressionType expressionType;
+
+ /** */
+ private final String backupMethodName;
+
+ /** */
+ BinaryImplementor(NullPolicy nullPolicy, boolean harmonize,
+ ExpressionType expressionType, String backupMethodName) {
+ super(nullPolicy, harmonize);
+ this.expressionType = expressionType;
+ this.backupMethodName = backupMethodName;
+ }
+
+ /** {@inheritDoc} */
+ @Override String getVariableName() {
+ return "binary_call";
+ }
+
+ /** {@inheritDoc} */
+ @Override Expression implementSafe(
+ final RexToLixTranslator translator,
+ final RexCall call,
+ final List<Expression> argValueList) {
+ // neither nullable:
+ // return x OP y
+ // x nullable
+ // null_returns_null
+ // return x == null ? null : x OP y
+ // ignore_null
+ // return x == null ? null : y
+ // x, y both nullable
+ // null_returns_null
+ // return x == null || y == null ? null : x OP y
+ // ignore_null
+ // return x == null ? y : y == null ? x : x OP y
+ if (backupMethodName != null) {
+ // If one or both operands have ANY type, use the late-binding backup
+ // method.
+ if (anyAnyOperands(call))
+ return callBackupMethodAnyType(translator, call, argValueList);
+
+ final Type type0 = argValueList.get(0).getType();
+ final Type type1 = argValueList.get(1).getType();
+ final SqlBinaryOperator op = (SqlBinaryOperator)call.getOperator();
+ final RelDataType relDataType0 = call.getOperands().get(0).getType();
+ final Expression fieldComparator = generateCollatorExpression(relDataType0.getCollation());
+ if (fieldComparator != null)
+ argValueList.add(fieldComparator);
+
+ final Primitive primitive = Primitive.ofBoxOr(type0);
+ if (primitive == null
+ || type1 == BigDecimal.class
+ || COMPARISON_OPERATORS.contains(op)
+ && !COMP_OP_TYPES.contains(primitive)) {
+ return Expressions.call(SqlFunctions.class, backupMethodName,
+ argValueList);
+ }
+ // When checking equals or not equals on two primitive boxing classes
+ // (i.e. Long x, Long y), we should fall back to call `SqlFunctions.eq(x, y)`
+ // or `SqlFunctions.ne(x, y)`, rather than `x == y`
+ final Primitive boxPrimitive0 = Primitive.ofBox(type0);
+ final Primitive boxPrimitive1 = Primitive.ofBox(type1);
+ if (EQUALS_OPERATORS.contains(op)
+ && boxPrimitive0 != null && boxPrimitive1 != null) {
+ return Expressions.call(SqlFunctions.class, backupMethodName,
+ argValueList);
+ }
+ }
+ return Expressions.makeBinary(expressionType,
+ argValueList.get(0), argValueList.get(1));
+ }
+
+ /** Returns whether any of a call's operands have ANY type. */
+ private boolean anyAnyOperands(RexCall call) {
+ for (RexNode operand : call.operands) {
+ if (operand.getType().getSqlTypeName() == SqlTypeName.ANY)
+ return true;
+ }
+ return false;
+ }
+
+ /** */
+ private Expression callBackupMethodAnyType(RexToLixTranslator translator,
+ RexCall call, List<Expression> expressions) {
+ final String backupMethodNameForAnyType =
+ backupMethodName + METHOD_POSTFIX_FOR_ANY_TYPE;
+
+ // one or both of parameter(s) is(are) ANY type
+ final Expression expression0 = maybeBox(expressions.get(0));
+ final Expression expression1 = maybeBox(expressions.get(1));
+ return Expressions.call(SqlFunctions.class, backupMethodNameForAnyType,
+ expression0, expression1);
+ }
+
+ /** */
+ private Expression maybeBox(Expression expression) {
+ final Primitive primitive = Primitive.of(expression.getType());
+ if (primitive != null)
+ expression = Expressions.box(expression, primitive);
+
+ return expression;
+ }
+ }
+
+ /** Implementor for unary operators. */
+ private static class UnaryImplementor extends AbstractRexCallImplementor {
+ /** */
+ private final ExpressionType expressionType;
+
+ /** */
+ private final String backupMethodName;
+
+ /** */
+ UnaryImplementor(ExpressionType expressionType, NullPolicy nullPolicy,
+ String backupMethodName) {
+ super(nullPolicy, false);
+ this.expressionType = expressionType;
+ this.backupMethodName = backupMethodName;
+ }
+
+ /** {@inheritDoc} */
+ @Override String getVariableName() {
+ return "unary_call";
+ }
+
+ /** {@inheritDoc} */
+ @Override Expression implementSafe(RexToLixTranslator translator,
+ RexCall call, List<Expression> argValueList) {
+ final Expression argValue = argValueList.get(0);
+
+ final Expression e;
+ //Special case for implementing unary minus with BigDecimal type
+ //for other data type(except BigDecimal) '-' operator is OK, but for
+ //BigDecimal, we should call negate method of BigDecimal
+ if (expressionType == ExpressionType.Negate && argValue.type == BigDecimal.class
+ && null != backupMethodName)
+ e = Expressions.call(argValue, backupMethodName);
+ else
+ e = Expressions.makeUnary(expressionType, argValue);
+
+ if (e.type.equals(argValue.type))
+ return e;
+ // Certain unary operators do not preserve type. For example, the "-"
+ // operator applied to a "byte" expression returns an "int".
+ return Expressions.convert_(e, argValue.type);
+ }
+ }
+
+ /** Implementor for the {@code EXTRACT(unit FROM datetime)} function. */
+ private static class ExtractImplementor extends AbstractRexCallImplementor {
+ /** */
+ ExtractImplementor() {
+ super(NullPolicy.STRICT, false);
+ }
+
+ /** {@inheritDoc} */
+ @Override String getVariableName() {
+ return "extract";
+ }
+
+ /** {@inheritDoc} */
+ @Override Expression implementSafe(final RexToLixTranslator translator,
+ final RexCall call, final List<Expression> argValueList) {
+ final TimeUnitRange timeUnitRange =
+ (TimeUnitRange)translator.getLiteralValue(argValueList.get(0));
+ final TimeUnit unit = timeUnitRange.startUnit;
+ Expression operand = argValueList.get(1);
+ final SqlTypeName sqlTypeName =
+ call.operands.get(1).getType().getSqlTypeName();
+ switch (unit) {
+ case MILLENNIUM:
+ case CENTURY:
+ case YEAR:
+ case QUARTER:
+ case MONTH:
+ case DAY:
+ case DOW:
+ case DECADE:
+ case DOY:
+ case ISODOW:
+ case ISOYEAR:
+ case WEEK:
+ switch (sqlTypeName) {
+ case INTERVAL_YEAR:
+ case INTERVAL_YEAR_MONTH:
+ case INTERVAL_MONTH:
+ case INTERVAL_DAY:
+ case INTERVAL_DAY_HOUR:
+ case INTERVAL_DAY_MINUTE:
+ case INTERVAL_DAY_SECOND:
+ case INTERVAL_HOUR:
+ case INTERVAL_HOUR_MINUTE:
+ case INTERVAL_HOUR_SECOND:
+ case INTERVAL_MINUTE:
+ case INTERVAL_MINUTE_SECOND:
+ case INTERVAL_SECOND:
+ break;
+ case TIMESTAMP_WITH_LOCAL_TIME_ZONE:
+ operand = Expressions.call(
+ BuiltInMethod.TIMESTAMP_WITH_LOCAL_TIME_ZONE_TO_TIMESTAMP.method,
+ operand,
+ Expressions.call(BuiltInMethod.TIME_ZONE.method, translator.getRoot()));
+ // fall through
+ case TIMESTAMP:
+ operand = Expressions.call(BuiltInMethod.FLOOR_DIV.method,
+ operand, Expressions.constant(TimeUnit.DAY.multiplier.longValue()));
+ // fall through
+ case DATE:
+ return Expressions.call(BuiltInMethod.UNIX_DATE_EXTRACT.method,
+ argValueList.get(0), operand);
+ default:
+ throw new AssertionError("unexpected " + sqlTypeName);
+ }
+ break;
+ case MILLISECOND:
+ case MICROSECOND:
+ case NANOSECOND:
+ if (sqlTypeName == SqlTypeName.DATE)
+ return Expressions.constant(0L);
+
+ operand = mod(operand, TimeUnit.MINUTE.multiplier.longValue());
+ return Expressions.multiply(
+ operand, Expressions.constant((long)(1 / unit.multiplier.doubleValue())));
+ case EPOCH:
+ switch (sqlTypeName) {
+ case DATE:
+ // convert to milliseconds
+ operand = Expressions.multiply(operand,
+ Expressions.constant(TimeUnit.DAY.multiplier.longValue()));
+ // fall through
+ case TIMESTAMP:
+ // convert to seconds
+ return Expressions.divide(operand,
+ Expressions.constant(TimeUnit.SECOND.multiplier.longValue()));
+ case TIMESTAMP_WITH_LOCAL_TIME_ZONE:
+ operand = Expressions.call(
+ BuiltInMethod.TIMESTAMP_WITH_LOCAL_TIME_ZONE_TO_TIMESTAMP.method,
+ operand,
+ Expressions.call(BuiltInMethod.TIME_ZONE.method, translator.getRoot()));
+ return Expressions.divide(operand,
+ Expressions.constant(TimeUnit.SECOND.multiplier.longValue()));
+ case INTERVAL_YEAR:
+ case INTERVAL_YEAR_MONTH:
+ case INTERVAL_MONTH:
+ case INTERVAL_DAY:
+ case INTERVAL_DAY_HOUR:
+ case INTERVAL_DAY_MINUTE:
+ case INTERVAL_DAY_SECOND:
+ case INTERVAL_HOUR:
+ case INTERVAL_HOUR_MINUTE:
+ case INTERVAL_HOUR_SECOND:
+ case INTERVAL_MINUTE:
+ case INTERVAL_MINUTE_SECOND:
+ case INTERVAL_SECOND:
+ // no convertlet conversion, pass it as extract
+ throw new AssertionError("unexpected " + sqlTypeName);
+ }
+ break;
+ case HOUR:
+ case MINUTE:
+ case SECOND:
+ switch (sqlTypeName) {
+ case DATE:
+ return Expressions.multiply(operand, Expressions.constant(0L));
+ }
+ break;
+ }
+
+ operand = mod(operand, getFactor(unit));
+ if (unit == TimeUnit.QUARTER)
+ operand = Expressions.subtract(operand, Expressions.constant(1L));
+
+ operand = Expressions.divide(operand,
+ Expressions.constant(unit.multiplier.longValue()));
+ if (unit == TimeUnit.QUARTER)
+ operand = Expressions.add(operand, Expressions.constant(1L));
+
+ return operand;
+ }
+ }
+
+ /** */
+ private static Expression mod(Expression operand, long factor) {
+ if (factor == 1L)
+ return operand;
+ else {
+ return Expressions.call(BuiltInMethod.FLOOR_MOD.method,
+ operand, Expressions.constant(factor));
+ }
+ }
+
+ /** */
+ private static long getFactor(TimeUnit unit) {
+ switch (unit) {
+ case DAY:
+ return 1L;
+ case HOUR:
+ return TimeUnit.DAY.multiplier.longValue();
+ case MINUTE:
+ return TimeUnit.HOUR.multiplier.longValue();
+ case SECOND:
+ return TimeUnit.MINUTE.multiplier.longValue();
+ case MILLISECOND:
+ return TimeUnit.SECOND.multiplier.longValue();
+ case MONTH:
+ return TimeUnit.YEAR.multiplier.longValue();
+ case QUARTER:
+ return TimeUnit.YEAR.multiplier.longValue();
+ case YEAR:
+ case DECADE:
+ case CENTURY:
+ case MILLENNIUM:
+ return 1L;
+ default:
+ throw Util.unexpected(unit);
+ }
+ }
+
+ /** Implementor for the SQL {@code COALESCE} operator. */
+ private static class CoalesceImplementor extends AbstractRexCallImplementor {
+ /** */
+ CoalesceImplementor() {
+ super(NullPolicy.NONE, false);
+ }
+
+ /** {@inheritDoc} */
+ @Override String getVariableName() {
+ return "coalesce";
+ }
+
+ /** {@inheritDoc} */
+ @Override Expression implementSafe(final RexToLixTranslator translator,
+ final RexCall call, final List<Expression> argValueList) {
+ return implementRecurse(translator, argValueList);
+ }
+
+ /** */
+ private Expression implementRecurse(RexToLixTranslator translator,
+ final List<Expression> argValueList) {
+ if (argValueList.size() == 1)
+ return argValueList.get(0);
+ else {
+ return Expressions.condition(
+ translator.checkNotNull(argValueList.get(0)),
+ argValueList.get(0),
+ implementRecurse(translator, Util.skip(argValueList)));
+ }
+ }
+ }
+
+ /** Implementor for the SQL {@code CAST} operator. */
+ private static class CastImplementor extends AbstractRexCallImplementor {
+ /** */
+ CastImplementor() {
+ super(NullPolicy.STRICT, false);
+ }
+
+ /** {@inheritDoc} */
+ @Override String getVariableName() {
+ return "cast";
+ }
+
+ /** {@inheritDoc} */
+ @Override Expression implementSafe(final RexToLixTranslator translator,
+ final RexCall call, final List<Expression> argValueList) {
+ assert call.getOperands().size() == 1;
+ final RelDataType sourceType = call.getOperands().get(0).getType();
+
+ // Short-circuit if no cast is required
+ RexNode arg = call.getOperands().get(0);
+ if (call.getType().equals(sourceType)) {
+ // No cast required, omit cast
+ return argValueList.get(0);
+ }
+ if (SqlTypeUtil.equalSansNullability(translator.typeFactory,
+ call.getType(), arg.getType())
+ && translator.deref(arg) instanceof RexLiteral) {
+ return RexToLixTranslator.translateLiteral(
+ (RexLiteral)translator.deref(arg), call.getType(),
+ translator.typeFactory, NullAs.NULL);
+ }
+ final RelDataType targetType =
+ nullifyType(translator.typeFactory, call.getType(), false);
+ return translator.translateCast(sourceType,
+ targetType, argValueList.get(0));
+ }
+
+ /** */
+ private RelDataType nullifyType(JavaTypeFactory typeFactory,
+ final RelDataType type, final boolean nullable) {
+ if (type instanceof RelDataTypeFactoryImpl.JavaType) {
+ final Primitive primitive = Primitive.ofBox(
+ ((RelDataTypeFactoryImpl.JavaType)type).getJavaClass());
+ if (primitive != null)
+ return typeFactory.createJavaType(primitive.primitiveClass);
+ }
+ return typeFactory.createTypeWithNullability(type, nullable);
+ }
+ }
+
+ /** Implementor for the {@code REINTERPRET} internal SQL operator. */
+ private static class ReinterpretImplementor extends AbstractRexCallImplementor {
+ /** */
+ ReinterpretImplementor() {
+ super(NullPolicy.STRICT, false);
+ }
+
+ /** {@inheritDoc} */
+ @Override String getVariableName() {
+ return "reInterpret";
+ }
+
+ /** {@inheritDoc} */
+ @Override Expression implementSafe(final RexToLixTranslator translator,
+ final RexCall call, final List<Expression> argValueList) {
+ assert call.getOperands().size() == 1;
+ return argValueList.get(0);
+ }
+ }
+
+ /** Implementor for a value-constructor. */
+ private static class ValueConstructorImplementor
+ extends AbstractRexCallImplementor {
+
+ /** */
+ ValueConstructorImplementor() {
+ super(NullPolicy.NONE, false);
+ }
+
+ /** {@inheritDoc} */
+ @Override String getVariableName() {
+ return "value_constructor";
+ }
+
+ /** {@inheritDoc} */
+ @Override Expression implementSafe(RexToLixTranslator translator,
+ final RexCall call, final List<Expression> argValueList) {
+ SqlKind kind = call.getOperator().getKind();
+ final BlockBuilder blockBuilder = translator.getBlockBuilder();
+ switch (kind) {
+ case MAP_VALUE_CONSTRUCTOR:
+ Expression map =
+ blockBuilder.append("map", Expressions.new_(LinkedHashMap.class),
+ false);
+ for (int i = 0; i < argValueList.size(); i++) {
+ Expression key = argValueList.get(i++);
+ Expression value = argValueList.get(i);
+ blockBuilder.add(
+ Expressions.statement(
+ Expressions.call(map, BuiltInMethod.MAP_PUT.method,
+ Expressions.box(key), Expressions.box(value))));
+ }
+ return map;
+ case ARRAY_VALUE_CONSTRUCTOR:
+ Expression lyst =
+ blockBuilder.append("list", Expressions.new_(ArrayList.class),
+ false);
+ for (Expression value : argValueList) {
+ blockBuilder.add(
+ Expressions.statement(
+ Expressions.call(lyst, BuiltInMethod.COLLECTION_ADD.method,
+ Expressions.box(value))));
+ }
+ return lyst;
+ default:
+ throw new AssertionError("unexpected: " + kind);
+ }
+ }
+ }
+
+ /** Implementor for the {@code ITEM} SQL operator. */
+ private static class ItemImplementor extends AbstractRexCallImplementor {
+ /** */
+ ItemImplementor() {
+ super(NullPolicy.STRICT, false);
+ }
+
+ /** {@inheritDoc} */
+ @Override String getVariableName() {
+ return "item";
+ }
+
+ // Since we follow PostgreSQL's semantics that an out-of-bound reference
+ // returns NULL, x[y] can return null even if x and y are both NOT NULL.
+ // (In SQL standard semantics, an out-of-bound reference to an array
+ // throws an exception.)
+ /** {@inheritDoc} */
+ @Override Expression implementSafe(final RexToLixTranslator translator,
+ final RexCall call, final List<Expression> argValueList) {
+ final MethodImplementor implementor =
+ getImplementor(call.getOperands().get(0).getType().getSqlTypeName());
+ return implementor.implementSafe(translator, call, argValueList);
+ }
+
+ /** */
+ private MethodImplementor getImplementor(SqlTypeName sqlTypeName) {
+ switch (sqlTypeName) {
+ case ARRAY:
+ return new MethodImplementor(BuiltInMethod.ARRAY_ITEM.method, nullPolicy, false);
+ case MAP:
+ return new MethodImplementor(BuiltInMethod.MAP_ITEM.method, nullPolicy, false);
+ default:
+ return new MethodImplementor(BuiltInMethod.ANY_ITEM.method, nullPolicy, false);
+ }
+ }
+ }
+
+ /**
+ * Implementor for SQL system functions.
+ *
+ * <p>Several of these are represented internally as constant values, set
+ * per execution.
+ */
+ private static class SystemFunctionImplementor
+ extends AbstractRexCallImplementor {
+ /** */
+ SystemFunctionImplementor() {
+ super(NullPolicy.NONE, false);
+ }
+
+ /** {@inheritDoc} */
+ @Override String getVariableName() {
+ return "system_func";
+ }
+
+ /** {@inheritDoc} */
+ @Override Expression implementSafe(final RexToLixTranslator translator,
+ final RexCall call, final List<Expression> argValueList) {
+ final SqlOperator op = call.getOperator();
+ final Expression root = translator.getRoot();
+ if (op == CURRENT_USER
+ || op == SESSION_USER
+ || op == USER)
+ return Expressions.call(BuiltInMethod.USER.method, root);
+ else if (op == SYSTEM_USER)
+ return Expressions.call(BuiltInMethod.SYSTEM_USER.method, root);
+ else if (op == CURRENT_PATH
+ || op == CURRENT_ROLE
+ || op == CURRENT_CATALOG) {
+ // By default, the CURRENT_ROLE and CURRENT_CATALOG functions return the
+ // empty string because a role or a catalog has to be set explicitly.
+ return Expressions.constant("");
+ }
+ else if (op == CURRENT_TIMESTAMP)
+ return Expressions.call(BuiltInMethod.CURRENT_TIMESTAMP.method, root);
+ else if (op == CURRENT_TIME)
+ return Expressions.call(BuiltInMethod.CURRENT_TIME.method, root);
+ else if (op == CURRENT_DATE)
+ return Expressions.call(BuiltInMethod.CURRENT_DATE.method, root);
+ else if (op == LOCALTIMESTAMP)
+ return Expressions.call(BuiltInMethod.LOCAL_TIMESTAMP.method, root);
+ else if (op == LOCALTIME)
+ return Expressions.call(BuiltInMethod.LOCAL_TIME.method, root);
+ else if (op == SYSTEM_RANGE) {
+ if (call.getOperands().size() == 2)
+ return createTableFunctionImplementor(IgniteBuiltInMethod.SYSTEM_RANGE2.method)
+ .implement(translator, call, NullAs.NULL);
+
+ if (call.getOperands().size() == 3)
+ return createTableFunctionImplementor(IgniteBuiltInMethod.SYSTEM_RANGE3.method)
+ .implement(translator, call, NullAs.NULL);
+ }
+
+ throw new AssertionError("unknown function " + op);
+ }
+ }
+
+ /** Implementor for the {@code NOT} operator. */
+ private static class NotImplementor extends AbstractRexCallImplementor {
+ /** */
+ private AbstractRexCallImplementor implementor;
+
+ /** */
+ private NotImplementor(AbstractRexCallImplementor implementor) {
+ super(null, false);
+ this.implementor = implementor;
+ }
+
+ /** */
+ static AbstractRexCallImplementor of(AbstractRexCallImplementor implementor) {
+ return new NotImplementor(implementor);
+ }
+
+ /** {@inheritDoc} */
+ @Override String getVariableName() {
+ return "not";
+ }
+
+ /** {@inheritDoc} */
+ @Override Expression implementSafe(final RexToLixTranslator translator,
+ final RexCall call, final List<Expression> argValueList) {
+ final Expression expression =
+ implementor.implementSafe(translator, call, argValueList);
+ return Expressions.not(expression);
+ }
+ }
+
+ /** Implementor for various datetime arithmetic. */
+ private static class DatetimeArithmeticImplementor
+ extends AbstractRexCallImplementor {
+ /** */
+ DatetimeArithmeticImplementor() {
+ super(NullPolicy.STRICT, false);
+ }
+
+ /** {@inheritDoc} */
+ @Override String getVariableName() {
+ return "dateTime_arithmetic";
+ }
+
+ /** {@inheritDoc} */
+ @Override Expression implementSafe(final RexToLixTranslator translator,
+ final RexCall call, final List<Expression> argValueList) {
+ final RexNode operand0 = call.getOperands().get(0);
+ Expression trop0 = argValueList.get(0);
+ final SqlTypeName typeName1 =
+ call.getOperands().get(1).getType().getSqlTypeName();
+ Expression trop1 = argValueList.get(1);
+ final SqlTypeName typeName = call.getType().getSqlTypeName();
+ switch (operand0.getType().getSqlTypeName()) {
+ case DATE:
+ switch (typeName) {
+ case TIMESTAMP:
+ trop0 = Expressions.convert_(
+ Expressions.multiply(trop0,
+ Expressions.constant(DateTimeUtils.MILLIS_PER_DAY)),
+ long.class);
+ break;
+ default:
+ switch (typeName1) {
+ case INTERVAL_DAY:
+ case INTERVAL_DAY_HOUR:
+ case INTERVAL_DAY_MINUTE:
+ case INTERVAL_DAY_SECOND:
+ case INTERVAL_HOUR:
+ case INTERVAL_HOUR_MINUTE:
+ case INTERVAL_HOUR_SECOND:
+ case INTERVAL_MINUTE:
+ case INTERVAL_MINUTE_SECOND:
+ case INTERVAL_SECOND:
+ trop1 = Expressions.convert_(
+ Expressions.divide(trop1,
+ Expressions.constant(DateTimeUtils.MILLIS_PER_DAY)),
+ int.class);
+ }
+ }
+ break;
+ case TIME:
+ trop1 = Expressions.convert_(trop1, int.class);
+ break;
+ }
+ switch (typeName1) {
+ case INTERVAL_YEAR:
+ case INTERVAL_YEAR_MONTH:
+ case INTERVAL_MONTH:
+ switch (call.getKind()) {
+ case MINUS:
+ trop1 = Expressions.negate(trop1);
+ }
+ switch (typeName) {
+ case TIME:
+ return Expressions.convert_(trop0, long.class);
+ default:
+ final BuiltInMethod method =
+ operand0.getType().getSqlTypeName() == SqlTypeName.TIMESTAMP
+ ? BuiltInMethod.ADD_MONTHS
+ : BuiltInMethod.ADD_MONTHS_INT;
+ return Expressions.call(method.method, trop0, trop1);
+ }
+
+ case INTERVAL_DAY:
+ case INTERVAL_DAY_HOUR:
+ case INTERVAL_DAY_MINUTE:
+ case INTERVAL_DAY_SECOND:
+ case INTERVAL_HOUR:
+ case INTERVAL_HOUR_MINUTE:
+ case INTERVAL_HOUR_SECOND:
+ case INTERVAL_MINUTE:
+ case INTERVAL_MINUTE_SECOND:
+ case INTERVAL_SECOND:
+ switch (call.getKind()) {
+ case MINUS:
+ return normalize(typeName, Expressions.subtract(trop0, trop1));
+ default:
+ return normalize(typeName, Expressions.add(trop0, trop1));
+ }
+
+ default:
+ switch (call.getKind()) {
+ case MINUS:
+ switch (typeName) {
+ case INTERVAL_YEAR:
+ case INTERVAL_YEAR_MONTH:
+ case INTERVAL_MONTH:
+ return Expressions.call(BuiltInMethod.SUBTRACT_MONTHS.method,
+ trop0, trop1);
+ }
+ TimeUnit fromUnit =
+ typeName1 == SqlTypeName.DATE ? TimeUnit.DAY : TimeUnit.MILLISECOND;
+ TimeUnit toUnit = TimeUnit.MILLISECOND;
+ return multiplyDivide(
+ Expressions.convert_(Expressions.subtract(trop0, trop1),
+ (Class)long.class),
+ fromUnit.multiplier, toUnit.multiplier);
+ default:
+ throw new AssertionError(call);
+ }
+ }
+ }
+
+ /** Normalizes a TIME value into 00:00:00..23:59:39. */
+ private Expression normalize(SqlTypeName typeName, Expression e) {
+ switch (typeName) {
+ case TIME:
+ return Expressions.call(BuiltInMethod.FLOOR_MOD.method, e,
+ Expressions.constant(DateTimeUtils.MILLIS_PER_DAY));
+ default:
+ return e;
+ }
+ }
+ }
+
+ /** Null-safe implementor of {@code RexCall}s. */
+ public interface RexCallImplementor {
+ /** */
+ RexToLixTranslator.Result implement(
+ RexToLixTranslator translator,
+ RexCall call,
+ List<RexToLixTranslator.Result> arguments);
+ }
+
+ /**
+ * Abstract implementation of the {@link RexCallImplementor} interface.
+ *
+ * <p>It is not always safe to execute the {@link RexCall} directly due to
+ * the special null arguments. Therefore, the generated code logic is conditional correspondingly.
+ *
+ * <p>For example, {@code a + b} will generate two declaration statements:
+ *
+ * <blockquote>
+ * <code>
+ * final Integer xxx_value = (a_isNull || b_isNull) ? null : plus(a, b);<br> final boolean xxx_isNull = xxx_value ==
+ * null;
+ * </code>
+ * </blockquote>
+ */
+ private abstract static class AbstractRexCallImplementor
+ implements RexCallImplementor {
+ /** */
+ final NullPolicy nullPolicy;
+
+ /** */
+ private final boolean harmonize;
+
+ /** */
+ AbstractRexCallImplementor(NullPolicy nullPolicy, boolean harmonize) {
+ this.nullPolicy = nullPolicy;
+ this.harmonize = harmonize;
+ }
+
+ /** {@inheritDoc} */
+ @Override public RexToLixTranslator.Result implement(
+ final RexToLixTranslator translator,
+ final RexCall call,
+ final List<RexToLixTranslator.Result> arguments) {
+ final List<Expression> argIsNullList = new ArrayList<>();
+ final List<Expression> argValueList = new ArrayList<>();
+ for (RexToLixTranslator.Result result : arguments) {
+ argIsNullList.add(result.isNullVariable);
+ argValueList.add(result.valueVariable);
+ }
+ final Expression condition = getCondition(argIsNullList);
+ final ParameterExpression valueVariable =
+ genValueStatement(translator, call, argValueList, condition);
+ final ParameterExpression isNullVariable =
+ genIsNullStatement(translator, valueVariable);
+ return new RexToLixTranslator.Result(isNullVariable, valueVariable);
+ }
+
+ /** */
+ // Variable name facilitates reasoning about issues when necessary
+ abstract String getVariableName();
+
+ /** Figures out conditional expression according to NullPolicy. */
+ Expression getCondition(final List<Expression> argIsNullList) {
+ if (argIsNullList.isEmpty()
+ || nullPolicy == null
+ || nullPolicy == NullPolicy.NONE)
+ return FALSE_EXPR;
+
+ if (nullPolicy == NullPolicy.ARG0)
+ return argIsNullList.get(0);
+
+ return Expressions.foldOr(argIsNullList);
+ }
+
+ /** */
+ // E.g., "final Integer xxx_value = (a_isNull || b_isNull) ? null : plus(a, b)"
+ private ParameterExpression genValueStatement(
+ final RexToLixTranslator translator,
+ final RexCall call, final List<Expression> argValueList,
+ final Expression condition) {
+ List<Expression> optimizedArgValueList = argValueList;
+ if (harmonize) {
+ optimizedArgValueList =
+ harmonize(optimizedArgValueList, translator, call);
+ }
+ optimizedArgValueList = unboxIfNecessary(optimizedArgValueList);
+
+ final Expression callValue =
+ implementSafe(translator, call, optimizedArgValueList);
+
+ // In general, RexCall's type is correct for code generation
+ // and thus we should ensure the consistency.
+ // However, for some special cases (e.g., TableFunction),
+ // the implementation's type is correct, we can't convert it.
+ final SqlOperator op = call.getOperator();
+ final Type returnType = translator.typeFactory.getJavaClass(call.getType());
+ final boolean noConvert = (returnType == null)
+ || (returnType == callValue.getType())
+ || (op instanceof SqlTableFunction)
+ || (op instanceof SqlUserDefinedTableMacro)
+ || (op instanceof SqlUserDefinedTableFunction);
+ final Expression convertedCallValue =
+ noConvert
+ ? callValue
+ : ConverterUtils.convert(callValue, returnType);
+
+ final Expression valueExpression =
+ Expressions.condition(condition,
+ getIfTrue(convertedCallValue.getType(), argValueList),
+ convertedCallValue);
+ final ParameterExpression value =
+ Expressions.parameter(convertedCallValue.getType(),
+ translator.getBlockBuilder().newName(getVariableName() + "_value"));
+ translator.getBlockBuilder().add(
+ Expressions.declare(Modifier.FINAL, value, valueExpression));
+ return value;
+ }
+
+ /** */
+ Expression getIfTrue(Type type, final List<Expression> argValueList) {
+ return getDefaultValue(type);
+ }
+
+ /** */
+ // E.g., "final boolean xxx_isNull = xxx_value == null"
+ private ParameterExpression genIsNullStatement(
+ final RexToLixTranslator translator, final ParameterExpression value) {
+ final ParameterExpression isNullVariable =
+ Expressions.parameter(Boolean.TYPE,
+ translator.getBlockBuilder().newName(getVariableName() + "_isNull"));
+ final Expression isNullExpression = translator.checkNull(value);
+ translator.getBlockBuilder().add(
+ Expressions.declare(Modifier.FINAL, isNullVariable, isNullExpression));
+ return isNullVariable;
+ }
+
+ /** Ensures that operands have identical type. */
+ private List<Expression> harmonize(final List<Expression> argValueList,
+ final RexToLixTranslator translator, final RexCall call) {
+ int nullCount = 0;
+ final List<RelDataType> types = new ArrayList<>();
+ final RelDataTypeFactory typeFactory =
+ translator.builder.getTypeFactory();
+ for (RexNode operand : call.getOperands()) {
+ RelDataType type = operand.getType();
+ type = toSql(typeFactory, type);
+ if (translator.isNullable(operand))
+ ++nullCount;
+ else
+ type = typeFactory.createTypeWithNullability(type, false);
+
+ types.add(type);
+ }
+ if (allSame(types)) {
+ // Operands have the same nullability and type. Return them
+ // unchanged.
+ return argValueList;
+ }
+ final RelDataType type = typeFactory.leastRestrictive(types);
+ if (type == null) {
+ // There is no common type. Presumably this is a binary operator with
+ // asymmetric arguments (e.g. interval / integer) which is not intended
+ // to be harmonized.
+ return argValueList;
+ }
+ assert (nullCount > 0) == type.isNullable();
+ final Type javaClass =
+ translator.typeFactory.getJavaClass(type);
+ final List<Expression> harmonizedArgValues = new ArrayList<>();
+ for (Expression argValue : argValueList) {
+ harmonizedArgValues.add(
+ EnumUtils.convert(argValue, javaClass));
+ }
+ return harmonizedArgValues;
+ }
+
+ /**
+ * Under null check, it is safe to unbox the operands before entering the implementor.
+ */
+ private List<Expression> unboxIfNecessary(final List<Expression> argValueList) {
+ List<Expression> unboxValueList = argValueList;
+ if (nullPolicy == NullPolicy.STRICT || nullPolicy == NullPolicy.ANY
+ || nullPolicy == NullPolicy.SEMI_STRICT) {
+ unboxValueList = argValueList.stream()
+ .map(this::unboxExpression)
+ .collect(Collectors.toList());
+ }
+ if (nullPolicy == NullPolicy.ARG0 && !argValueList.isEmpty()) {
+ final Expression unboxArg0 = unboxExpression(unboxValueList.get(0));
+ unboxValueList.set(0, unboxArg0);
+ }
+ return unboxValueList;
+ }
+
+ /** */
+ private Expression unboxExpression(final Expression argValue) {
+ Primitive fromBox = Primitive.ofBox(argValue.getType());
+ if (fromBox == null || fromBox == Primitive.VOID)
+ return argValue;
+
+ // Optimization: for "long x";
+ // "Long.valueOf(x)" generates "x"
+ if (argValue instanceof MethodCallExpression) {
+ MethodCallExpression mce = (MethodCallExpression)argValue;
+ if (mce.method.getName().equals("valueOf") && mce.expressions.size() == 1) {
+ Expression originArg = mce.expressions.get(0);
+ if (Primitive.of(originArg.type) == fromBox)
+ return originArg;
+ }
+ }
+ return NullAs.NOT_POSSIBLE.handle(argValue);
+ }
+
+ /** */
+ abstract Expression implementSafe(RexToLixTranslator translator,
+ RexCall call, List<Expression> argValueList);
+ }
+
+ /**
+ * Implementor for the {@code AND} operator.
+ *
+ * <p>If any of the arguments are false, result is false;
+ * else if any arguments are null, result is null; else true.
+ */
+ private static class LogicalAndImplementor extends AbstractRexCallImplementor {
+ /** */
+ LogicalAndImplementor() {
+ super(NullPolicy.NONE, true);
+ }
+
+ /** {@inheritDoc} */
+ @Override String getVariableName() {
+ return "logical_and";
+ }
+
+ /** {@inheritDoc} */
+ @Override public RexToLixTranslator.Result implement(final RexToLixTranslator translator,
+ final RexCall call, final List<RexToLixTranslator.Result> arguments) {
+ final List<Expression> argIsNullList = new ArrayList<>();
+ for (RexToLixTranslator.Result result : arguments)
+ argIsNullList.add(result.isNullVariable);
+
+ final List<Expression> nullAsTrue =
+ arguments.stream()
+ .map(result ->
+ Expressions.condition(result.isNullVariable, TRUE_EXPR,
+ result.valueVariable))
+ .collect(Collectors.toList());
+ final Expression hasFalse =
+ Expressions.not(Expressions.foldAnd(nullAsTrue));
+ final Expression hasNull = Expressions.foldOr(argIsNullList);
+ final Expression callExpression =
+ Expressions.condition(hasFalse, BOXED_FALSE_EXPR,
+ Expressions.condition(hasNull, NULL_EXPR, BOXED_TRUE_EXPR));
+ final RexImpTable.NullAs nullAs = translator.isNullable(call)
+ ? RexImpTable.NullAs.NULL : RexImpTable.NullAs.NOT_POSSIBLE;
+ final Expression valueExpression = nullAs.handle(callExpression);
+ final ParameterExpression valueVariable =
+ Expressions.parameter(valueExpression.getType(),
+ translator.getBlockBuilder().newName(getVariableName() + "_value"));
+ final Expression isNullExpression = translator.checkNull(valueVariable);
+ final ParameterExpression isNullVariable =
+ Expressions.parameter(Boolean.TYPE,
+ translator.getBlockBuilder().newName(getVariableName() + "_isNull"));
+ translator.getBlockBuilder().add(
+ Expressions.declare(Modifier.FINAL, valueVariable, valueExpression));
+ translator.getBlockBuilder().add(
+ Expressions.declare(Modifier.FINAL, isNullVariable, isNullExpression));
+ return new RexToLixTranslator.Result(isNullVariable, valueVariable);
+ }
+
+ /** {@inheritDoc} */
+ @Override Expression implementSafe(final RexToLixTranslator translator,
+ final RexCall call, final List<Expression> argValueList) {
+ return null;
+ }
+ }
+
+ /**
+ * Implementor for the {@code OR} operator.
+ *
+ * <p>If any of the arguments are true, result is true;
+ * else if any arguments are null, result is null; else false.
+ */
+ private static class LogicalOrImplementor extends AbstractRexCallImplementor {
+ /** */
+ LogicalOrImplementor() {
+ super(NullPolicy.NONE, true);
+ }
+
+ /** {@inheritDoc} */
+ @Override String getVariableName() {
+ return "logical_or";
+ }
+
+ /** {@inheritDoc} */
+ @Override public RexToLixTranslator.Result implement(final RexToLixTranslator translator,
+ final RexCall call, final List<RexToLixTranslator.Result> arguments) {
+ final List<Expression> argIsNullList = new ArrayList<>();
+ for (RexToLixTranslator.Result result : arguments)
+ argIsNullList.add(result.isNullVariable);
+
+ final List<Expression> nullAsFalse =
+ arguments.stream()
+ .map(result ->
+ Expressions.condition(result.isNullVariable, FALSE_EXPR,
+ result.valueVariable))
+ .collect(Collectors.toList());
+ final Expression hasTrue = Expressions.foldOr(nullAsFalse);
+ final Expression hasNull = Expressions.foldOr(argIsNullList);
+ final Expression callExpression =
+ Expressions.condition(hasTrue, BOXED_TRUE_EXPR,
+ Expressions.condition(hasNull, NULL_EXPR, BOXED_FALSE_EXPR));
+ final RexImpTable.NullAs nullAs = translator.isNullable(call)
+ ? RexImpTable.NullAs.NULL : RexImpTable.NullAs.NOT_POSSIBLE;
+ final Expression valueExpression = nullAs.handle(callExpression);
+ final ParameterExpression valueVariable =
+ Expressions.parameter(valueExpression.getType(),
+ translator.getBlockBuilder().newName(getVariableName() + "_value"));
+ final Expression isNullExpression = translator.checkNull(valueExpression);
+ final ParameterExpression isNullVariable =
+ Expressions.parameter(Boolean.TYPE,
+ translator.getBlockBuilder().newName(getVariableName() + "_isNull"));
+ translator.getBlockBuilder().add(
+ Expressions.declare(Modifier.FINAL, valueVariable, valueExpression));
+ translator.getBlockBuilder().add(
+ Expressions.declare(Modifier.FINAL, isNullVariable, isNullExpression));
+ return new RexToLixTranslator.Result(isNullVariable, valueVariable);
+ }
+
+ /** {@inheritDoc} */
+ @Override Expression implementSafe(final RexToLixTranslator translator,
+ final RexCall call, final List<Expression> argValueList) {
+ return null;
+ }
+ }
+
+ /**
+ * Implementor for the {@code NOT} operator.
+ *
+ * <p>If any of the arguments are false, result is true;
+ * else if any arguments are null, result is null; else false.
+ */
+ private static class LogicalNotImplementor extends AbstractRexCallImplementor {
+ /** */
+ LogicalNotImplementor() {
+ super(NullPolicy.NONE, true);
+ }
+
+ /** {@inheritDoc} */
+ @Override String getVariableName() {
+ return "logical_not";
+ }
+
+ /** {@inheritDoc} */
+ @Override Expression implementSafe(final RexToLixTranslator translator,
+ final RexCall call, final List<Expression> argValueList) {
+ return Expressions.call(BuiltInMethod.NOT.method, argValueList);
+ }
+ }
+
+ /**
+ * Implementation that calls a given {@link Method}.
+ *
+ * <p>When method is not static, a new instance of the required class is
+ * created.
+ */
+ private static class ReflectiveImplementor extends AbstractRexCallImplementor {
+ /** */
+ protected final Method method;
+
+ /** */
+ ReflectiveImplementor(Method method, NullPolicy nullPolicy) {
+ super(nullPolicy, false);
+ this.method = method;
+ }
+
+ /** {@inheritDoc} */
+ @Override String getVariableName() {
+ return "reflective_" + method.getName();
+ }
+
+ /** {@inheritDoc} */
+ @Override Expression implementSafe(RexToLixTranslator translator,
+ RexCall call, List<Expression> argValueList) {
+ List<Expression> argValueList0 = ConverterUtils.fromInternal(method.getParameterTypes(), argValueList);
+ if ((method.getModifiers() & Modifier.STATIC) != 0)
+ return Expressions.call(method, argValueList0);
+
+ // The UDF class must have a public zero-args constructor.
+ // Assume that the validator checked already.
+ final Expression target = Expressions.new_(method.getDeclaringClass());
+ return Expressions.call(target, method, argValueList0);
+ }
+ }
+
+ /** Implementor for the {@code RAND} function. */
+ private static class RandImplementor extends AbstractRexCallImplementor {
+ /** */
+ private final AbstractRexCallImplementor[] implementors = {
+ new ReflectiveImplementor(BuiltInMethod.RAND.method, nullPolicy),
+ new ReflectiveImplementor(BuiltInMethod.RAND_SEED.method, nullPolicy)
+ };
+
+ /** */
+ RandImplementor() {
+ super(NullPolicy.STRICT, false);
+ }
+
+ /** {@inheritDoc} */
+ @Override String getVariableName() {
+ return "rand";
+ }
+
+ /** {@inheritDoc} */
+ @Override Expression implementSafe(final RexToLixTranslator translator,
+ final RexCall call, final List<Expression> argValueList) {
+ return implementors[call.getOperands().size()]
+ .implementSafe(translator, call, argValueList);
+ }
+ }
+
+ /** Implementor for the {@code RAND_INTEGER} function. */
+ private static class RandIntegerImplementor extends AbstractRexCallImplementor {
+ /** */
+ private final AbstractRexCallImplementor[] implementors = {
+ null,
+ new ReflectiveImplementor(BuiltInMethod.RAND_INTEGER.method, nullPolicy),
+ new ReflectiveImplementor(BuiltInMethod.RAND_INTEGER_SEED.method, nullPolicy)
+ };
+
+ /** */
+ RandIntegerImplementor() {
+ super(NullPolicy.STRICT, false);
+ }
+
+ /** {@inheritDoc} */
+ @Override String getVariableName() {
+ return "rand_integer";
+ }
+
+ /** {@inheritDoc} */
+ @Override Expression implementSafe(final RexToLixTranslator translator,
+ final RexCall call, final List<Expression> argValueList) {
+ return implementors[call.getOperands().size()]
+ .implementSafe(translator, call, argValueList);
+ }
+ }
+
+ /** Implementor for the {@code PI} operator. */
+ private static class PiImplementor extends AbstractRexCallImplementor {
+ /** */
+ PiImplementor() {
+ super(NullPolicy.NONE, false);
+ }
+
+ /** {@inheritDoc} */
+ @Override String getVariableName() {
+ return "pi";
+ }
+
+ /** {@inheritDoc} */
+ @Override Expression implementSafe(final RexToLixTranslator translator,
+ final RexCall call, final List<Expression> argValueList) {
+ return Expressions.constant(Math.PI);
+ }
+ }
+
+ /** Implementor for the {@code IS FALSE} SQL operator. */
+ private static class IsFalseImplementor extends AbstractRexCallImplementor {
+ /** */
+ IsFalseImplementor() {
+ super(NullPolicy.STRICT, false);
+ }
+
+ /** {@inheritDoc} */
+ @Override String getVariableName() {
+ return "is_false";
+ }
+
+ /** {@inheritDoc} */
+ @Override Expression getIfTrue(Type type, final List<Expression> argValueList) {
+ return Expressions.constant(false, type);
+ }
+
+ /** {@inheritDoc} */
+ @Override Expression implementSafe(final RexToLixTranslator translator,
+ final RexCall call, final List<Expression> argValueList) {
+ return Expressions.equal(argValueList.get(0), FALSE_EXPR);
+ }
+ }
+
+ /** Implementor for the {@code IS NOT FALSE} SQL operator. */
+ private static class IsNotFalseImplementor extends AbstractRexCallImplementor {
+ /** */
+ IsNotFalseImplementor() {
+ super(NullPolicy.STRICT, false);
+ }
+
+ /** {@inheritDoc} */
+ @Override String getVariableName() {
+ return "is_not_false";
+ }
+
+ /** {@inheritDoc} */
+ @Override Expression getIfTrue(Type type, final List<Expression> argValueList) {
+ return Expressions.constant(true, type);
+ }
+
+ /** {@inheritDoc} */
+ @Override Expression implementSafe(final RexToLixTranslator translator,
+ final RexCall call, final List<Expression> argValueList) {
+ return Expressions.notEqual(argValueList.get(0), FALSE_EXPR);
+ }
+ }
+
+ /** Implementor for the {@code IS NOT NULL} SQL operator. */
+ private static class IsNotNullImplementor extends AbstractRexCallImplementor {
+ /** */
+ IsNotNullImplementor() {
+ super(NullPolicy.STRICT, false);
+ }
+
+ /** {@inheritDoc} */
+ @Override String getVariableName() {
+ return "is_not_null";
+ }
+
+ /** {@inheritDoc} */
+ @Override Expression getIfTrue(Type type, final List<Expression> argValueList) {
+ return Expressions.constant(false, type);
+ }
+
+ /** {@inheritDoc} */
+ @Override Expression implementSafe(final RexToLixTranslator translator,
+ final RexCall call, final List<Expression> argValueList) {
+ return Expressions.notEqual(argValueList.get(0), NULL_EXPR);
+ }
+ }
+
+ /** Implementor for the {@code IS NOT TRUE} SQL operator. */
+ private static class IsNotTrueImplementor extends AbstractRexCallImplementor {
+ /** */
+ IsNotTrueImplementor() {
+ super(NullPolicy.STRICT, false);
+ }
+
+ /** {@inheritDoc} */
+ @Override String getVariableName() {
+ return "is_not_true";
+ }
+
+ /** {@inheritDoc} */
+ @Override Expression getIfTrue(Type type, final List<Expression> argValueList) {
+ return Expressions.constant(true, type);
+ }
+
+ /** {@inheritDoc} */
+ @Override Expression implementSafe(final RexToLixTranslator translator,
+ final RexCall call, final List<Expression> argValueList) {
+ return Expressions.notEqual(argValueList.get(0), TRUE_EXPR);
+ }
+ }
+
+ /** Implementor for the {@code IS NULL} SQL operator. */
+ private static class IsNullImplementor extends AbstractRexCallImplementor {
+ /** */
+ IsNullImplementor() {
+ super(NullPolicy.STRICT, false);
+ }
+
+ /** {@inheritDoc} */
+ @Override String getVariableName() {
+ return "is_null";
+ }
+
+ /** {@inheritDoc} */
+ @Override Expression getIfTrue(Type type, final List<Expression> argValueList) {
+ return Expressions.constant(true, type);
+ }
+
+ /** {@inheritDoc} */
+ @Override Expression implementSafe(final RexToLixTranslator translator,
+ final RexCall call, final List<Expression> argValueList) {
+ return Expressions.equal(argValueList.get(0), NULL_EXPR);
+ }
+ }
+
+ /** Implementor for the {@code IS TRUE} SQL operator. */
+ private static class IsTrueImplementor extends AbstractRexCallImplementor {
+ /** */
+ IsTrueImplementor() {
+ super(NullPolicy.STRICT, false);
+ }
+
+ /** {@inheritDoc} */
+ @Override String getVariableName() {
+ return "is_true";
+ }
+
+ /** {@inheritDoc} */
+ @Override Expression getIfTrue(Type type, final List<Expression> argValueList) {
+ return Expressions.constant(false, type);
+ }
+
+ /** {@inheritDoc} */
+ @Override Expression implementSafe(final RexToLixTranslator translator,
+ final RexCall call, final List<Expression> argValueList) {
+ return Expressions.equal(argValueList.get(0), TRUE_EXPR);
+ }
+ }
+
+ /** Implementor for the {@code REGEXP_REPLACE} function. */
+ private static class RegexpReplaceImplementor extends AbstractRexCallImplementor {
+ private final AbstractRexCallImplementor[] implementors = {
+ new ReflectiveImplementor(BuiltInMethod.REGEXP_REPLACE3.method, nullPolicy),
+ new ReflectiveImplementor(BuiltInMethod.REGEXP_REPLACE4.method, nullPolicy),
+ new ReflectiveImplementor(BuiltInMethod.REGEXP_REPLACE5.method, nullPolicy),
+ new ReflectiveImplementor(BuiltInMethod.REGEXP_REPLACE6.method, nullPolicy),
+ };
+
+ /** */
+ RegexpReplaceImplementor() {
+ super(NullPolicy.STRICT, false);
+ }
+
+ @Override String getVariableName() {
+ return "regexp_replace";
+ }
+
+ /** {@inheritDoc} */
+ @Override Expression implementSafe(RexToLixTranslator translator,
+ RexCall call, List<Expression> argValueList) {
+ return implementors[call.getOperands().size() - 3]
+ .implementSafe(translator, call, argValueList);
+ }
+ }
+
+ /** Implementor for the {@code DEFAULT} function. */
+ private static class DefaultImplementor extends AbstractRexCallImplementor {
+ /** */
+ DefaultImplementor() {
+ super(NullPolicy.NONE, false);
+ }
+
+ /** {@inheritDoc} */
+ @Override String getVariableName() {
+ return "default";
+ }
+
+ /** {@inheritDoc} */
+ @Override Expression implementSafe(final RexToLixTranslator translator,
+ final RexCall call, final List<Expression> argValueList) {
+ return Expressions.constant(null);
+ }
+ }
+
+ /** */
+ private static CallImplementor createTableFunctionImplementor(final Method method) {
+ return createImplementor(
+ new ReflectiveCallNotNullImplementor(method) {
+ @Override public Expression implement(RexToLixTranslator translator,
+ RexCall call, List<Expression> translatedOperands) {
+ Expression expr = super.implement(translator, call,
+ translatedOperands);
+ final Class<?> returnType = method.getReturnType();
+ if (QueryableTable.class.isAssignableFrom(returnType)) {
+ Expression queryable = Expressions.call(
+ Expressions.convert_(expr, QueryableTable.class),
+ BuiltInMethod.QUERYABLE_TABLE_AS_QUERYABLE.method,
+ Expressions.call(translator.getRoot(),
+ BuiltInMethod.DATA_CONTEXT_GET_QUERY_PROVIDER.method),
+ Expressions.constant(null, SchemaPlus.class),
+ Expressions.constant(call.getOperator().getName(), String.class));
+ expr = Expressions.call(queryable,
+ BuiltInMethod.QUERYABLE_AS_ENUMERABLE.method);
+ } else {
+ expr = Expressions.call(expr,
+ BuiltInMethod.SCANNABLE_TABLE_SCAN.method,
+ translator.getRoot());
+ }
+ return expr;
+ }
+ }, NullPolicy.NONE, false);
+ }
+
+ /** */
+ public static CallImplementor createImplementor(
+ final NotNullImplementor implementor,
+ final NullPolicy nullPolicy,
+ final boolean harmonize) {
+ return (translator, call, nullAs) -> {
+ final RexImpTable.RexCallImplementor rexCallImplementor
+ = createRexCallImplementor(implementor, nullPolicy, harmonize);
+ final List<RexToLixTranslator.Result> arguments = translator.getCallOperandResult(call);
+ assert arguments != null;
+ final RexToLixTranslator.Result result = rexCallImplementor.implement(translator, call, arguments);
+ return nullAs.handle(result.valueVariable);
+ };
+ }
+
+ /** */
+ private static RexCallImplementor createRexCallImplementor(
+ final NotNullImplementor implementor,
+ final NullPolicy nullPolicy,
+ final boolean harmonize) {
+ return new AbstractRexCallImplementor(nullPolicy, harmonize) {
+ @Override String getVariableName() {
+ return "not_null_udf";
+ }
+
+ @Override Expression implementSafe(RexToLixTranslator translator,
+ RexCall call, List<Expression> argValueList) {
+ return implementor.implement(translator, call, argValueList);
+ }
+ };
+ }
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/RexToLixTranslator.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/RexToLixTranslator.java
new file mode 100644
index 0000000..f7da047
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/RexToLixTranslator.java
@@ -0,0 +1,1311 @@
+/*
+ * 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.ignite.internal.processors.query.calcite.exec.exp;
+
+import java.lang.reflect.Modifier;
+import java.lang.reflect.Type;
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+import com.google.common.collect.ImmutableList;
+import org.apache.calcite.adapter.enumerable.PhysType;
+import org.apache.calcite.adapter.java.JavaTypeFactory;
+import org.apache.calcite.avatica.util.ByteString;
+import org.apache.calcite.avatica.util.DateTimeUtils;
+import org.apache.calcite.linq4j.function.Function1;
+import org.apache.calcite.linq4j.tree.BlockBuilder;
+import org.apache.calcite.linq4j.tree.BlockStatement;
+import org.apache.calcite.linq4j.tree.CatchBlock;
+import org.apache.calcite.linq4j.tree.ConstantExpression;
+import org.apache.calcite.linq4j.tree.Expression;
+import org.apache.calcite.linq4j.tree.Expressions;
+import org.apache.calcite.linq4j.tree.ParameterExpression;
+import org.apache.calcite.linq4j.tree.Primitive;
+import org.apache.calcite.linq4j.tree.Statement;
+import org.apache.calcite.rel.type.RelDataType;
+import org.apache.calcite.rex.RexBuilder;
+import org.apache.calcite.rex.RexCall;
+import org.apache.calcite.rex.RexCorrelVariable;
+import org.apache.calcite.rex.RexDynamicParam;
+import org.apache.calcite.rex.RexFieldAccess;
+import org.apache.calcite.rex.RexInputRef;
+import org.apache.calcite.rex.RexLiteral;
+import org.apache.calcite.rex.RexLocalRef;
+import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.rex.RexOver;
+import org.apache.calcite.rex.RexPatternFieldRef;
+import org.apache.calcite.rex.RexProgram;
+import org.apache.calcite.rex.RexRangeRef;
+import org.apache.calcite.rex.RexSubQuery;
+import org.apache.calcite.rex.RexTableInputRef;
+import org.apache.calcite.rex.RexUtil;
+import org.apache.calcite.rex.RexVisitor;
+import org.apache.calcite.runtime.GeoFunctions;
+import org.apache.calcite.runtime.Geometries;
+import org.apache.calcite.sql.SqlIntervalQualifier;
+import org.apache.calcite.sql.SqlOperator;
+import org.apache.calcite.sql.fun.SqlStdOperatorTable;
+import org.apache.calcite.sql.type.SqlTypeUtil;
+import org.apache.calcite.sql.validate.SqlConformance;
+import org.apache.calcite.util.BuiltInMethod;
+import org.apache.calcite.util.ControlFlowException;
+import org.apache.calcite.util.Pair;
+
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.CASE;
+import static org.apache.calcite.sql.fun.SqlStdOperatorTable.SEARCH;
+
+/**
+ * Translates {@link RexNode REX expressions} to {@link Expression linq4j expressions}.
+ */
+public class RexToLixTranslator implements RexVisitor<RexToLixTranslator.Result> {
+ /** */
+ final JavaTypeFactory typeFactory;
+
+ /** */
+ final RexBuilder builder;
+
+ /** */
+ private final RexProgram program;
+
+ /** */
+ final SqlConformance conformance;
+
+ /** */
+ private final Expression root;
+
+ /** */
+ final RexToLixTranslator.InputGetter inputGetter;
+
+ /** */
+ private final BlockBuilder list;
+
+ /** */
+ private final Function1<String, InputGetter> correlates;
+
+ /**
+ * Map from RexLiteral's variable name to its literal, which is often a ({@link ConstantExpression})) It is used in
+ * the some {@code RexCall}'s implementors, such as {@code ExtractImplementor}.
+ *
+ * @see #getLiteral
+ * @see #getLiteralValue
+ */
+ private final Map<Expression, Expression> literalMap = new HashMap<>();
+
+ /**
+ * For {@code RexCall}, keep the list of its operand's {@code Result}. It is useful when creating a {@code
+ * CallImplementor}.
+ */
+ private final Map<RexCall, List<Result>> callOperandResultMap = new HashMap<>();
+
+ /**
+ * Map from RexNode under specific storage type to its Result, to avoid generating duplicate code. For {@code
+ * RexInputRef}, {@code RexDynamicParam} and {@code RexFieldAccess}.
+ */
+ private final Map<Pair<RexNode, Type>, Result> rexWithStorageTypeResultMap = new HashMap<>();
+
+ /**
+ * Map from RexNode to its Result, to avoid generating duplicate code. For {@code RexLiteral} and {@code RexCall}.
+ */
+ private final Map<RexNode, Result> rexResultMap = new HashMap<>();
+
+ /** */
+ private Type currentStorageType;
+
+ /** */
+ private RexToLixTranslator(RexProgram program,
+ JavaTypeFactory typeFactory,
+ Expression root,
+ InputGetter inputGetter,
+ BlockBuilder list,
+ RexBuilder builder,
+ SqlConformance conformance,
+ Function1<String, InputGetter> correlates) {
+ this.program = program; // may be null
+ this.typeFactory = Objects.requireNonNull(typeFactory);
+ this.conformance = Objects.requireNonNull(conformance);
+ this.root = Objects.requireNonNull(root);
+ this.inputGetter = inputGetter;
+ this.list = Objects.requireNonNull(list);
+ this.builder = Objects.requireNonNull(builder);
+ this.correlates = correlates; // may be null
+ }
+
+ /**
+ * Translates a {@link RexProgram} to a sequence of expressions and declarations.
+ *
+ * @param program Program to be translated
+ * @param typeFactory Type factory
+ * @param conformance SQL conformance
+ * @param list List of statements, populated with declarations
+ * @param outputPhysType Output type, or null
+ * @param root Root expression
+ * @param inputGetter Generates expressions for inputs
+ * @param correlates Provider of references to the values of correlated variables
+ * @return Sequence of expressions, optional condition
+ */
+ public static List<Expression> translateProjects(RexProgram program,
+ JavaTypeFactory typeFactory, SqlConformance conformance,
+ BlockBuilder list, PhysType outputPhysType, Expression root,
+ InputGetter inputGetter, Function1<String, InputGetter> correlates) {
+ List<Type> storageTypes = null;
+ if (outputPhysType != null) {
+ final RelDataType rowType = outputPhysType.getRowType();
+ storageTypes = new ArrayList<>(rowType.getFieldCount());
+ for (int i = 0; i < rowType.getFieldCount(); i++)
+ storageTypes.add(outputPhysType.getJavaFieldType(i));
+ }
+ return new RexToLixTranslator(program, typeFactory, root, inputGetter,
+ list, new RexBuilder(typeFactory), conformance, null)
+ .setCorrelates(correlates)
+ .translateList(program.getProjectList(), storageTypes);
+ }
+
+ /** */
+ Expression translate(RexNode expr) {
+ final RexImpTable.NullAs nullAs =
+ RexImpTable.NullAs.of(isNullable(expr));
+ return translate(expr, nullAs);
+ }
+
+ /** */
+ Expression translate(RexNode expr, RexImpTable.NullAs nullAs) {
+ return translate(expr, nullAs, null);
+ }
+
+ /** */
+ Expression translate(RexNode expr, Type storageType) {
+ final RexImpTable.NullAs nullAs =
+ RexImpTable.NullAs.of(isNullable(expr));
+ return translate(expr, nullAs, storageType);
+ }
+
+ /** */
+ Expression translate(RexNode expr, RexImpTable.NullAs nullAs,
+ Type storageType) {
+ currentStorageType = storageType;
+ final Result result = expr.accept(this);
+ final Expression translated =
+ ConverterUtils.toInternal(result.valueVariable, storageType);
+ assert translated != null;
+ // When we asked for not null input that would be stored as box, avoid unboxing
+ if (RexImpTable.NullAs.NOT_POSSIBLE == nullAs
+ && translated.type.equals(storageType))
+ return translated;
+
+ return nullAs.handle(translated);
+ }
+
+ /** */
+ Expression translateCast(
+ RelDataType sourceType,
+ RelDataType targetType,
+ Expression operand) {
+ Expression convert = null;
+ switch (targetType.getSqlTypeName()) {
+ case ANY:
+ convert = operand;
+ break;
+ case DATE:
+ switch (sourceType.getSqlTypeName()) {
+ case CHAR:
+ case VARCHAR:
+ convert =
+ Expressions.call(BuiltInMethod.STRING_TO_DATE.method, operand);
+ break;
+ case TIMESTAMP:
+ convert = Expressions.convert_(
+ Expressions.call(BuiltInMethod.FLOOR_DIV.method,
+ operand, Expressions.constant(DateTimeUtils.MILLIS_PER_DAY)),
+ int.class);
+ break;
+ case TIMESTAMP_WITH_LOCAL_TIME_ZONE:
+ convert = RexImpTable.optimize2(
+ operand,
+ Expressions.call(
+ BuiltInMethod.TIMESTAMP_WITH_LOCAL_TIME_ZONE_TO_DATE.method,
+ operand,
+ Expressions.call(BuiltInMethod.TIME_ZONE.method, root)));
+ }
+ break;
+ case TIME:
+ switch (sourceType.getSqlTypeName()) {
+ case CHAR:
+ case VARCHAR:
+ convert =
+ Expressions.call(BuiltInMethod.STRING_TO_TIME.method, operand);
+ break;
+ case TIME_WITH_LOCAL_TIME_ZONE:
+ convert = RexImpTable.optimize2(
+ operand,
+ Expressions.call(
+ BuiltInMethod.TIME_WITH_LOCAL_TIME_ZONE_TO_TIME.method,
+ operand,
+ Expressions.call(BuiltInMethod.TIME_ZONE.method, root)));
+ break;
+ case TIMESTAMP:
+ convert = Expressions.convert_(
+ Expressions.call(
+ BuiltInMethod.FLOOR_MOD.method,
+ operand,
+ Expressions.constant(DateTimeUtils.MILLIS_PER_DAY)),
+ int.class);
+ break;
+ case TIMESTAMP_WITH_LOCAL_TIME_ZONE:
+ convert = RexImpTable.optimize2(
+ operand,
+ Expressions.call(
+ BuiltInMethod.TIMESTAMP_WITH_LOCAL_TIME_ZONE_TO_TIME.method,
+ operand,
+ Expressions.call(BuiltInMethod.TIME_ZONE.method, root)));
+ }
+ break;
+ case TIME_WITH_LOCAL_TIME_ZONE:
+ switch (sourceType.getSqlTypeName()) {
+ case CHAR:
+ case VARCHAR:
+ convert =
+ Expressions.call(BuiltInMethod.STRING_TO_TIME_WITH_LOCAL_TIME_ZONE.method, operand);
+ break;
+ case TIME:
+ convert = Expressions.call(
+ BuiltInMethod.TIME_STRING_TO_TIME_WITH_LOCAL_TIME_ZONE.method,
+ RexImpTable.optimize2(
+ operand,
+ Expressions.call(
+ BuiltInMethod.UNIX_TIME_TO_STRING.method,
+ operand)),
+ Expressions.call(BuiltInMethod.TIME_ZONE.method, root));
+ break;
+ case TIMESTAMP:
+ convert = Expressions.call(
+ BuiltInMethod.TIMESTAMP_STRING_TO_TIMESTAMP_WITH_LOCAL_TIME_ZONE.method,
+ RexImpTable.optimize2(
+ operand,
+ Expressions.call(
+ BuiltInMethod.UNIX_TIMESTAMP_TO_STRING.method,
+ operand)),
+ Expressions.call(BuiltInMethod.TIME_ZONE.method, root));
+ break;
+ case TIMESTAMP_WITH_LOCAL_TIME_ZONE:
+ convert = RexImpTable.optimize2(
+ operand,
+ Expressions.call(
+ BuiltInMethod.TIMESTAMP_WITH_LOCAL_TIME_ZONE_TO_TIME_WITH_LOCAL_TIME_ZONE.method,
+ operand));
+ }
+ break;
+ case TIMESTAMP:
+ switch (sourceType.getSqlTypeName()) {
+ case CHAR:
+ case VARCHAR:
+ convert =
+ Expressions.call(BuiltInMethod.STRING_TO_TIMESTAMP.method, operand);
+ break;
+ case DATE:
+ convert = Expressions.multiply(
+ Expressions.convert_(operand, long.class),
+ Expressions.constant(DateTimeUtils.MILLIS_PER_DAY));
+ break;
+ case TIME:
+ convert =
+ Expressions.add(
+ Expressions.multiply(
+ Expressions.convert_(
+ Expressions.call(BuiltInMethod.CURRENT_DATE.method, root),
+ long.class),
+ Expressions.constant(DateTimeUtils.MILLIS_PER_DAY)),
+ Expressions.convert_(operand, long.class));
+ break;
+ case TIME_WITH_LOCAL_TIME_ZONE:
+ convert = RexImpTable.optimize2(
+ operand,
+ Expressions.call(
+ BuiltInMethod.TIME_WITH_LOCAL_TIME_ZONE_TO_TIMESTAMP.method,
+ Expressions.call(
+ BuiltInMethod.UNIX_DATE_TO_STRING.method,
+ Expressions.call(BuiltInMethod.CURRENT_DATE.method, root)),
+ operand,
+ Expressions.call(BuiltInMethod.TIME_ZONE.method, root)));
+ break;
+ case TIMESTAMP_WITH_LOCAL_TIME_ZONE:
+ convert = RexImpTable.optimize2(
+ operand,
+ Expressions.call(
+ BuiltInMethod.TIMESTAMP_WITH_LOCAL_TIME_ZONE_TO_TIMESTAMP.method,
+ operand,
+ Expressions.call(BuiltInMethod.TIME_ZONE.method, root)));
+ }
+ break;
+ case TIMESTAMP_WITH_LOCAL_TIME_ZONE:
+ switch (sourceType.getSqlTypeName()) {
+ case CHAR:
+ case VARCHAR:
+ convert =
+ Expressions.call(
+ BuiltInMethod.STRING_TO_TIMESTAMP_WITH_LOCAL_TIME_ZONE.method,
+ operand);
+ break;
+ case DATE:
+ convert = Expressions.call(
+ BuiltInMethod.TIMESTAMP_STRING_TO_TIMESTAMP_WITH_LOCAL_TIME_ZONE.method,
+ RexImpTable.optimize2(
+ operand,
+ Expressions.call(
+ BuiltInMethod.UNIX_TIMESTAMP_TO_STRING.method,
+ Expressions.multiply(
+ Expressions.convert_(operand, long.class),
+ Expressions.constant(DateTimeUtils.MILLIS_PER_DAY)))),
+ Expressions.call(BuiltInMethod.TIME_ZONE.method, root));
+ break;
+ case TIME:
+ convert = Expressions.call(
+ BuiltInMethod.TIMESTAMP_STRING_TO_TIMESTAMP_WITH_LOCAL_TIME_ZONE.method,
+ RexImpTable.optimize2(
+ operand,
+ Expressions.call(
+ BuiltInMethod.UNIX_TIMESTAMP_TO_STRING.method,
+ Expressions.add(
+ Expressions.multiply(
+ Expressions.convert_(
+ Expressions.call(BuiltInMethod.CURRENT_DATE.method, root),
+ long.class),
+ Expressions.constant(DateTimeUtils.MILLIS_PER_DAY)),
+ Expressions.convert_(operand, long.class)))),
+ Expressions.call(BuiltInMethod.TIME_ZONE.method, root));
+ break;
+ case TIME_WITH_LOCAL_TIME_ZONE:
+ convert = RexImpTable.optimize2(
+ operand,
+ Expressions.call(
+ BuiltInMethod.TIME_WITH_LOCAL_TIME_ZONE_TO_TIMESTAMP_WITH_LOCAL_TIME_ZONE.method,
+ Expressions.call(
+ BuiltInMethod.UNIX_DATE_TO_STRING.method,
+ Expressions.call(BuiltInMethod.CURRENT_DATE.method, root)),
+ operand));
+ break;
+ case TIMESTAMP:
+ convert = Expressions.call(
+ BuiltInMethod.TIMESTAMP_STRING_TO_TIMESTAMP_WITH_LOCAL_TIME_ZONE.method,
+ RexImpTable.optimize2(
+ operand,
+ Expressions.call(
+ BuiltInMethod.UNIX_TIMESTAMP_TO_STRING.method,
+ operand)),
+ Expressions.call(BuiltInMethod.TIME_ZONE.method, root));
+ }
+ break;
+ case BOOLEAN:
+ switch (sourceType.getSqlTypeName()) {
+ case CHAR:
+ case VARCHAR:
+ convert = Expressions.call(
+ BuiltInMethod.STRING_TO_BOOLEAN.method,
+ operand);
+ }
+ break;
+ case CHAR:
+ case VARCHAR:
+ final SqlIntervalQualifier interval =
+ sourceType.getIntervalQualifier();
+ switch (sourceType.getSqlTypeName()) {
+ case DATE:
+ convert = RexImpTable.optimize2(
+ operand,
+ Expressions.call(
+ BuiltInMethod.UNIX_DATE_TO_STRING.method,
+ operand));
+ break;
+ case TIME:
+ convert = RexImpTable.optimize2(
+ operand,
+ Expressions.call(
+ BuiltInMethod.UNIX_TIME_TO_STRING.method,
+ operand));
+ break;
+ case TIME_WITH_LOCAL_TIME_ZONE:
+ convert = RexImpTable.optimize2(
+ operand,
+ Expressions.call(
+ BuiltInMethod.TIME_WITH_LOCAL_TIME_ZONE_TO_STRING.method,
+ operand,
+ Expressions.call(BuiltInMethod.TIME_ZONE.method, root)));
+ break;
+ case TIMESTAMP:
+ convert = RexImpTable.optimize2(
+ operand,
+ Expressions.call(
+ BuiltInMethod.UNIX_TIMESTAMP_TO_STRING.method,
+ operand));
+ break;
+ case TIMESTAMP_WITH_LOCAL_TIME_ZONE:
+ convert = RexImpTable.optimize2(
+ operand,
+ Expressions.call(
+ BuiltInMethod.TIMESTAMP_WITH_LOCAL_TIME_ZONE_TO_STRING.method,
+ operand,
+ Expressions.call(BuiltInMethod.TIME_ZONE.method, root)));
+ break;
+ case INTERVAL_YEAR:
+ case INTERVAL_YEAR_MONTH:
+ case INTERVAL_MONTH:
+ convert = RexImpTable.optimize2(
+ operand,
+ Expressions.call(
+ BuiltInMethod.INTERVAL_YEAR_MONTH_TO_STRING.method,
+ operand,
+ Expressions.constant(interval.timeUnitRange)));
+ break;
+ case INTERVAL_DAY:
+ case INTERVAL_DAY_HOUR:
+ case INTERVAL_DAY_MINUTE:
+ case INTERVAL_DAY_SECOND:
+ case INTERVAL_HOUR:
+ case INTERVAL_HOUR_MINUTE:
+ case INTERVAL_HOUR_SECOND:
+ case INTERVAL_MINUTE:
+ case INTERVAL_MINUTE_SECOND:
+ case INTERVAL_SECOND:
+ convert = RexImpTable.optimize2(
+ operand,
+ Expressions.call(
+ BuiltInMethod.INTERVAL_DAY_TIME_TO_STRING.method,
+ operand,
+ Expressions.constant(interval.timeUnitRange),
+ Expressions.constant(
+ interval.getFractionalSecondPrecision(
+ typeFactory.getTypeSystem()))));
+ break;
+ case BOOLEAN:
+ convert = RexImpTable.optimize2(
+ operand,
+ Expressions.call(
+ BuiltInMethod.BOOLEAN_TO_STRING.method,
+ operand));
+ break;
+ }
+ }
+ if (convert == null)
+ convert = ConverterUtils.convert(operand, typeFactory.getJavaClass(targetType));
+
+ // Going from anything to CHAR(n) or VARCHAR(n), make sure value is no
+ // longer than n.
+ boolean pad = false;
+ boolean truncate = true;
+ switch (targetType.getSqlTypeName()) {
+ case CHAR:
+ case BINARY:
+ pad = true;
+ // fall through
+ case VARCHAR:
+ case VARBINARY:
+ final int targetPrecision = targetType.getPrecision();
+ if (targetPrecision >= 0) {
+ switch (sourceType.getSqlTypeName()) {
+ case CHAR:
+ case VARCHAR:
+ case BINARY:
+ case VARBINARY:
+ // If this is a widening cast, no need to truncate.
+ final int sourcePrecision = sourceType.getPrecision();
+ if (SqlTypeUtil.comparePrecision(sourcePrecision, targetPrecision) <= 0)
+ truncate = false;
+
+ // If this is a widening cast, no need to pad.
+ if (SqlTypeUtil.comparePrecision(sourcePrecision, targetPrecision) >= 0)
+ pad = false;
+
+ // fall through
+ default:
+ if (truncate || pad) {
+ convert =
+ Expressions.call(
+ pad
+ ? BuiltInMethod.TRUNCATE_OR_PAD.method
+ : BuiltInMethod.TRUNCATE.method,
+ convert,
+ Expressions.constant(targetPrecision));
+ }
+ }
+ }
+ break;
+ case TIMESTAMP:
+ int targetScale = targetType.getScale();
+ if (targetScale == RelDataType.SCALE_NOT_SPECIFIED)
+ targetScale = 0;
+
+ if (targetScale < sourceType.getScale()) {
+ convert =
+ Expressions.call(
+ BuiltInMethod.ROUND_LONG.method,
+ convert,
+ Expressions.constant(
+ (long)Math.pow(10, 3 - targetScale)));
+ }
+ break;
+ case INTERVAL_YEAR:
+ case INTERVAL_YEAR_MONTH:
+ case INTERVAL_MONTH:
+ case INTERVAL_DAY:
+ case INTERVAL_DAY_HOUR:
+ case INTERVAL_DAY_MINUTE:
+ case INTERVAL_DAY_SECOND:
+ case INTERVAL_HOUR:
+ case INTERVAL_HOUR_MINUTE:
+ case INTERVAL_HOUR_SECOND:
+ case INTERVAL_MINUTE:
+ case INTERVAL_MINUTE_SECOND:
+ case INTERVAL_SECOND:
+ switch (sourceType.getSqlTypeName().getFamily()) {
+ case NUMERIC:
+ final BigDecimal multiplier = targetType.getSqlTypeName().getEndUnit().multiplier;
+ final BigDecimal divider = BigDecimal.ONE;
+ convert = RexImpTable.multiplyDivide(convert, multiplier, divider);
+ }
+ }
+ return scaleIntervalToNumber(sourceType, targetType, convert);
+ }
+
+ /**
+ * Dereferences an expression if it is a {@link RexLocalRef}.
+ */
+ public RexNode deref(RexNode expr) {
+ if (expr instanceof RexLocalRef) {
+ RexLocalRef ref = (RexLocalRef)expr;
+ final RexNode e2 = program.getExprList().get(ref.getIndex());
+ assert ref.getType().equals(e2.getType());
+ return e2;
+ }
+
+ return expr;
+ }
+
+ /**
+ * Translates a literal.
+ *
+ * @throws ControlFlowException if literal is null but {@code nullAs} is {@link RexImpTable.NullAs#NOT_POSSIBLE}.
+ */
+ public static Expression translateLiteral(
+ RexLiteral literal,
+ RelDataType type,
+ JavaTypeFactory typeFactory,
+ RexImpTable.NullAs nullAs) {
+ if (literal.isNull()) {
+ switch (nullAs) {
+ case TRUE:
+ case IS_NULL:
+ return RexImpTable.TRUE_EXPR;
+ case FALSE:
+ case IS_NOT_NULL:
+ return RexImpTable.FALSE_EXPR;
+ case NOT_POSSIBLE:
+ throw new ControlFlowException();
+ case NULL:
+ default:
+ return RexImpTable.NULL_EXPR;
+ }
+ }
+ else {
+ switch (nullAs) {
+ case IS_NOT_NULL:
+ return RexImpTable.TRUE_EXPR;
+ case IS_NULL:
+ return RexImpTable.FALSE_EXPR;
+ }
+ }
+ Type javaClass = typeFactory.getJavaClass(type);
+ final Object value2;
+ switch (literal.getType().getSqlTypeName()) {
+ case DECIMAL:
+ final BigDecimal bd = literal.getValueAs(BigDecimal.class);
+ if (javaClass == float.class)
+ return Expressions.constant(bd, javaClass);
+ else if (javaClass == double.class)
+ return Expressions.constant(bd, javaClass);
+ assert javaClass == BigDecimal.class;
+ return Expressions.new_(BigDecimal.class,
+ Expressions.constant(bd.toString()));
+ case DATE:
+ case TIME:
+ case TIME_WITH_LOCAL_TIME_ZONE:
+ case INTERVAL_YEAR:
+ case INTERVAL_YEAR_MONTH:
+ case INTERVAL_MONTH:
+ value2 = literal.getValueAs(Integer.class);
+ javaClass = int.class;
+ break;
+ case TIMESTAMP:
+ case TIMESTAMP_WITH_LOCAL_TIME_ZONE:
+ case INTERVAL_DAY:
+ case INTERVAL_DAY_HOUR:
+ case INTERVAL_DAY_MINUTE:
+ case INTERVAL_DAY_SECOND:
+ case INTERVAL_HOUR:
+ case INTERVAL_HOUR_MINUTE:
+ case INTERVAL_HOUR_SECOND:
+ case INTERVAL_MINUTE:
+ case INTERVAL_MINUTE_SECOND:
+ case INTERVAL_SECOND:
+ value2 = literal.getValueAs(Long.class);
+ javaClass = long.class;
+ break;
+ case CHAR:
+ case VARCHAR:
+ value2 = literal.getValueAs(String.class);
+ break;
+ case BINARY:
+ case VARBINARY:
+ return Expressions.new_(
+ ByteString.class,
+ Expressions.constant(
+ literal.getValueAs(byte[].class),
+ byte[].class));
+ case GEOMETRY:
+ final Geometries.Geom geom = literal.getValueAs(Geometries.Geom.class);
+ final String wkt = GeoFunctions.ST_AsWKT(geom);
+ return Expressions.call(null, BuiltInMethod.ST_GEOM_FROM_TEXT.method,
+ Expressions.constant(wkt));
+ case SYMBOL:
+ value2 = literal.getValueAs(Enum.class);
+ javaClass = value2.getClass();
+ break;
+ default:
+ final Primitive primitive = Primitive.ofBoxOr(javaClass);
+ final Comparable value = literal.getValueAs(Comparable.class);
+
+ value2 = primitive != null && value instanceof Number ? primitive.number((Number)value) : value;
+ }
+ return Expressions.constant(value2, javaClass);
+ }
+
+ /** */
+ public List<Expression> translateList(
+ List<RexNode> operandList,
+ RexImpTable.NullAs nullAs) {
+ return translateList(operandList, nullAs,
+ ConverterUtils.internalTypes(operandList));
+ }
+
+ /** */
+ public List<Expression> translateList(
+ List<RexNode> operandList,
+ RexImpTable.NullAs nullAs,
+ List<? extends Type> storageTypes) {
+ final List<Expression> list = new ArrayList<>();
+ for (Pair<RexNode, ? extends Type> e : Pair.zip(operandList, storageTypes))
+ list.add(translate(e.left, nullAs, e.right));
+
+ return list;
+ }
+
+ /**
+ * Translates the list of {@code RexNode}, using the default output types. This might be suboptimal in terms of
+ * additional box-unbox when you use the translation later. If you know the java class that will be used to store
+ * the results, use {@link RexToLixTranslator#translateList(List, List)} version.
+ *
+ * @param operandList list of RexNodes to translate
+ * @return translated expressions
+ */
+ public List<Expression> translateList(List<? extends RexNode> operandList) {
+ return translateList(operandList, ConverterUtils.internalTypes(operandList));
+ }
+
+ /**
+ * Translates the list of {@code RexNode}, while optimizing for output storage. For instance, if the result of
+ * translation is going to be stored in {@code Object[]}, and the input is {@code Object[]} as well, then translator
+ * will avoid casting, boxing, etc.
+ *
+ * @param operandList list of RexNodes to translate
+ * @param storageTypes hints of the java classes that will be used to store translation results. Use null to use
+ * default storage type
+ * @return translated expressions
+ */
+ public List<Expression> translateList(List<? extends RexNode> operandList,
+ List<? extends Type> storageTypes) {
+ final List<Expression> list = new ArrayList<>(operandList.size());
+
+ for (int i = 0; i < operandList.size(); i++) {
+ RexNode rex = operandList.get(i);
+ Type desiredType = null;
+ if (storageTypes != null)
+ desiredType = storageTypes.get(i);
+
+ final Expression translate = translate(rex, desiredType);
+ list.add(translate);
+ // desiredType is still a hint, thus we might get any kind of output
+ // (boxed or not) when hint was provided.
+ // It is favourable to get the type matching desired type
+ if (desiredType == null && !isNullable(rex)) {
+ assert !Primitive.isBox(translate.getType())
+ : "Not-null boxed primitive should come back as primitive: "
+ + rex + ", " + translate.getType();
+ }
+ }
+ return list;
+ }
+
+ /**
+ * Returns whether an expression is nullable.
+ *
+ * @param e Expression
+ * @return Whether expression is nullable
+ */
+ public boolean isNullable(RexNode e) {
+ return e.getType().isNullable();
+ }
+
+ /** */
+ public RexToLixTranslator setBlock(BlockBuilder block) {
+ if (block == list)
+ return this;
+
+ return new RexToLixTranslator(program, typeFactory, root, inputGetter,
+ block, builder, conformance, correlates);
+ }
+
+ /** */
+ public RexToLixTranslator setCorrelates(
+ Function1<String, InputGetter> correlates) {
+ if (this.correlates == correlates)
+ return this;
+
+ return new RexToLixTranslator(program, typeFactory, root, inputGetter, list,
+ builder, conformance, correlates);
+ }
+
+ /** */
+ public Expression getRoot() {
+ return root;
+ }
+
+ /** */
+ private static Expression scaleIntervalToNumber(
+ RelDataType sourceType,
+ RelDataType targetType,
+ Expression operand) {
+ switch (targetType.getSqlTypeName().getFamily()) {
+ case NUMERIC:
+ switch (sourceType.getSqlTypeName()) {
+ case INTERVAL_YEAR:
+ case INTERVAL_YEAR_MONTH:
+ case INTERVAL_MONTH:
+ case INTERVAL_DAY:
+ case INTERVAL_DAY_HOUR:
+ case INTERVAL_DAY_MINUTE:
+ case INTERVAL_DAY_SECOND:
+ case INTERVAL_HOUR:
+ case INTERVAL_HOUR_MINUTE:
+ case INTERVAL_HOUR_SECOND:
+ case INTERVAL_MINUTE:
+ case INTERVAL_MINUTE_SECOND:
+ case INTERVAL_SECOND:
+ // Scale to the given field.
+ final BigDecimal multiplier = BigDecimal.ONE;
+ final BigDecimal divider =
+ sourceType.getSqlTypeName().getEndUnit().multiplier;
+ return RexImpTable.multiplyDivide(operand, multiplier, divider);
+ }
+ }
+ return operand;
+ }
+
+ /**
+ * Visit {@code RexInputRef}. If it has never been visited under current storage type before, {@code
+ * RexToLixTranslator} generally produces three lines of code. For example, when visiting a column (named
+ * commission) in table Employee, the generated code snippet is: {@code final Employee current =(Employee)
+ * inputEnumerator.current(); final Integer input_value = current.commission; final boolean input_isNull =
+ * input_value == null; }
+ */
+ @Override public Result visitInputRef(RexInputRef inputRef) {
+ final Pair<RexNode, Type> key = Pair.of(inputRef, currentStorageType);
+ // If the RexInputRef has been visited under current storage type already,
+ // it is not necessary to visit it again, just return the result.
+ if (rexWithStorageTypeResultMap.containsKey(key))
+ return rexWithStorageTypeResultMap.get(key);
+
+ // Generate one line of code to get the input, e.g.,
+ // "final Employee current =(Employee) inputEnumerator.current();"
+ final Expression valueExpression = inputGetter.field(
+ list, inputRef.getIndex(), currentStorageType);
+
+ // Generate one line of code for the value of RexInputRef, e.g.,
+ // "final Integer input_value = current.commission;"
+ final ParameterExpression valueVariable =
+ Expressions.parameter(
+ valueExpression.getType(), list.newName("input_value"));
+ list.add(Expressions.declare(Modifier.FINAL, valueVariable, valueExpression));
+
+ // Generate one line of code to check whether RexInputRef is null, e.g.,
+ // "final boolean input_isNull = input_value == null;"
+ final Expression isNullExpression = checkNull(valueVariable);
+ final ParameterExpression isNullVariable =
+ Expressions.parameter(
+ Boolean.TYPE, list.newName("input_isNull"));
+ list.add(Expressions.declare(Modifier.FINAL, isNullVariable, isNullExpression));
+
+ final Result result = new Result(isNullVariable, valueVariable);
+
+ // Cache <RexInputRef, currentStorageType>'s result
+ rexWithStorageTypeResultMap.put(key, result);
+
+ return new Result(isNullVariable, valueVariable);
+ }
+
+ /** {@inheritDoc} */
+ @Override public Result visitLocalRef(RexLocalRef localRef) {
+ return deref(localRef).accept(this);
+ }
+
+ /**
+ * Visit {@code RexLiteral}. If it has never been visited before, {@code RexToLixTranslator} will generate two lines
+ * of code. For example, when visiting a primitive int (10), the generated code snippet is: {@code final int
+ * literal_value = 10; final boolean literal_isNull = false; }
+ */
+ @Override public Result visitLiteral(RexLiteral literal) {
+ // If the RexLiteral has been visited already, just return the result
+ if (rexResultMap.containsKey(literal))
+ return rexResultMap.get(literal);
+
+ // Generate one line of code for the value of RexLiteral, e.g.,
+ // "final int literal_value = 10;"
+ final Expression valueExpression = literal.isNull()
+ // Note: even for null literal, we can't loss its type information
+ ? getTypedNullLiteral(literal)
+ : translateLiteral(literal, literal.getType(),
+ typeFactory, RexImpTable.NullAs.NOT_POSSIBLE);
+ final ParameterExpression valueVariable =
+ Expressions.parameter(valueExpression.getType(),
+ list.newName("literal_value"));
+ list.add(Expressions.declare(Modifier.FINAL, valueVariable, valueExpression));
+
+ // Generate one line of code to check whether RexLiteral is null, e.g.,
+ // "final boolean literal_isNull = false;"
+ final Expression isNullExpression =
+ literal.isNull() ? RexImpTable.TRUE_EXPR : RexImpTable.FALSE_EXPR;
+ final ParameterExpression isNullVariable = Expressions.parameter(
+ Boolean.TYPE, list.newName("literal_isNull"));
+ list.add(Expressions.declare(Modifier.FINAL, isNullVariable, isNullExpression));
+
+ // Maintain the map from valueVariable (ParameterExpression) to real Expression
+ literalMap.put(valueVariable, valueExpression);
+ final Result result = new Result(isNullVariable, valueVariable);
+ // Cache RexLiteral's result
+ rexResultMap.put(literal, result);
+ return result;
+ }
+
+ /**
+ * Returns an {@code Expression} for null literal without losing its type information.
+ */
+ private ConstantExpression getTypedNullLiteral(RexLiteral literal) {
+ assert literal.isNull();
+ Type javaClass = typeFactory.getJavaClass(literal.getType());
+ switch (literal.getType().getSqlTypeName()) {
+ case DATE:
+ case TIME:
+ case TIME_WITH_LOCAL_TIME_ZONE:
+ case INTERVAL_YEAR:
+ case INTERVAL_YEAR_MONTH:
+ case INTERVAL_MONTH:
+ javaClass = Integer.class;
+ break;
+ case TIMESTAMP:
+ case TIMESTAMP_WITH_LOCAL_TIME_ZONE:
+ case INTERVAL_DAY:
+ case INTERVAL_DAY_HOUR:
+ case INTERVAL_DAY_MINUTE:
+ case INTERVAL_DAY_SECOND:
+ case INTERVAL_HOUR:
+ case INTERVAL_HOUR_MINUTE:
+ case INTERVAL_HOUR_SECOND:
+ case INTERVAL_MINUTE:
+ case INTERVAL_MINUTE_SECOND:
+ case INTERVAL_SECOND:
+ javaClass = Long.class;
+ break;
+ }
+ return javaClass == null || javaClass == Void.class
+ ? RexImpTable.NULL_EXPR
+ : Expressions.constant(null, javaClass);
+ }
+
+ /**
+ * Visit {@code RexCall}. For most {@code SqlOperator}s, we can get the implementor from {@code RexImpTable}.
+ * Several operators (e.g., CaseWhen) with special semantics need to be implemented separately.
+ */
+ @Override public Result visitCall(RexCall call) {
+ if (rexResultMap.containsKey(call))
+ return rexResultMap.get(call);
+
+ final SqlOperator operator = call.getOperator();
+ if (operator == CASE)
+ return implementCaseWhen(call);
+
+ if (operator == SEARCH)
+ return RexUtil.expandSearch(builder, program, call).accept(this);
+
+ final RexImpTable.RexCallImplementor implementor =
+ RexImpTable.INSTANCE.get(operator);
+ if (implementor == null)
+ throw new RuntimeException("cannot translate call " + call);
+
+ final List<RexNode> operandList = call.getOperands();
+ final List<Type> storageTypes = ConverterUtils.internalTypes(operandList);
+ final List<Result> operandResults = new ArrayList<>();
+ for (int i = 0; i < operandList.size(); i++) {
+ final Result operandResult =
+ implementCallOperand(operandList.get(i), storageTypes.get(i), this);
+ operandResults.add(operandResult);
+ }
+ callOperandResultMap.put(call, operandResults);
+ final Result result = implementor.implement(this, call, operandResults);
+ rexResultMap.put(call, result);
+ return result;
+ }
+
+ /** */
+ private static Result implementCallOperand(final RexNode operand,
+ final Type storageType, final RexToLixTranslator translator) {
+ final Type originalStorageType = translator.currentStorageType;
+ translator.currentStorageType = storageType;
+ Result operandResult = operand.accept(translator);
+ if (storageType != null)
+ operandResult = translator.toInnerStorageType(operandResult, storageType);
+ translator.currentStorageType = originalStorageType;
+ return operandResult;
+ }
+
+ /** */
+ private static Expression implementCallOperand2(final RexNode operand,
+ final Type storageType, final RexToLixTranslator translator) {
+ final Type originalStorageType = translator.currentStorageType;
+ translator.currentStorageType = storageType;
+ final Expression result = translator.translate(operand);
+ translator.currentStorageType = originalStorageType;
+ return result;
+ }
+
+ /**
+ * The CASE operator is SQL’s way of handling if/then logic. Different with other {@code RexCall}s, it is not safe
+ * to implement its operands first. For example: {@code select case when s=0 then false else 100/s > 0 end from
+ * (values (1),(0)) ax(s); }
+ */
+ private Result implementCaseWhen(RexCall call) {
+ final Type returnType = typeFactory.getJavaClass(call.getType());
+ final ParameterExpression valueVariable =
+ Expressions.parameter(returnType,
+ list.newName("case_when_value"));
+ list.add(Expressions.declare(0, valueVariable, null));
+ final List<RexNode> operandList = call.getOperands();
+ implementRecursively(this, operandList, valueVariable, 0);
+ final Expression isNullExpression = checkNull(valueVariable);
+ final ParameterExpression isNullVariable =
+ Expressions.parameter(
+ Boolean.TYPE, list.newName("case_when_isNull"));
+ list.add(Expressions.declare(Modifier.FINAL, isNullVariable, isNullExpression));
+ final Result result = new Result(isNullVariable, valueVariable);
+ rexResultMap.put(call, result);
+ return result;
+ }
+
+ /**
+ * Case statements of the form: {@code CASE WHEN a THEN b [WHEN c THEN d]* [ELSE e] END}. When {@code a = true},
+ * returns {@code b}; when {@code c = true}, returns {@code d}; else returns {@code e}.
+ *
+ * <p>We generate code that looks like:
+ *
+ * <blockquote><pre>
+ * int case_when_value;
+ * ......code for a......
+ * if (!a_isNull && a_value) {
+ * ......code for b......
+ * case_when_value = res(b_isNull, b_value);
+ * } else {
+ * ......code for c......
+ * if (!c_isNull && c_value) {
+ * ......code for d......
+ * case_when_value = res(d_isNull, d_value);
+ * } else {
+ * ......code for e......
+ * case_when_value = res(e_isNull, e_value);
+ * }
+ * }
+ * </pre></blockquote>
+ */
+ private void implementRecursively(final RexToLixTranslator currentTranslator,
+ final List<RexNode> operandList, final ParameterExpression valueVariable, int pos) {
+ final BlockBuilder currentBlockBuilder = currentTranslator.getBlockBuilder();
+ final List<Type> storageTypes = ConverterUtils.internalTypes(operandList);
+ // [ELSE] clause
+ if (pos == operandList.size() - 1) {
+ Expression res = implementCallOperand2(operandList.get(pos),
+ storageTypes.get(pos), currentTranslator);
+ currentBlockBuilder.add(
+ Expressions.statement(
+ Expressions.assign(valueVariable,
+ ConverterUtils.convert(res, valueVariable.getType()))));
+ return;
+ }
+ // Condition code: !a_isNull && a_value
+ final RexNode testerNode = operandList.get(pos);
+ final Result testerResult = implementCallOperand(testerNode,
+ storageTypes.get(pos), currentTranslator);
+ final Expression tester = Expressions.andAlso(
+ Expressions.not(testerResult.isNullVariable),
+ testerResult.valueVariable);
+ // Code for {if} branch
+ final RexNode ifTrueNode = operandList.get(pos + 1);
+ final BlockBuilder ifTrueBlockBuilder =
+ new BlockBuilder(true, currentBlockBuilder);
+ final RexToLixTranslator ifTrueTranslator =
+ currentTranslator.setBlock(ifTrueBlockBuilder);
+ final Expression ifTrueRes = implementCallOperand2(ifTrueNode,
+ storageTypes.get(pos + 1), ifTrueTranslator);
+ // Assign the value: case_when_value = ifTrueRes
+ ifTrueBlockBuilder.add(
+ Expressions.statement(
+ Expressions.assign(valueVariable,
+ ConverterUtils.convert(ifTrueRes, valueVariable.getType()))));
+ final BlockStatement ifTrue = ifTrueBlockBuilder.toBlock();
+ // There is no [ELSE] clause
+ if (pos + 1 == operandList.size() - 1) {
+ currentBlockBuilder.add(
+ Expressions.ifThen(tester, ifTrue));
+ return;
+ }
+ // Generate code for {else} branch recursively
+ final BlockBuilder ifFalseBlockBuilder =
+ new BlockBuilder(true, currentBlockBuilder);
+ final RexToLixTranslator ifFalseTranslator =
+ currentTranslator.setBlock(ifFalseBlockBuilder);
+ implementRecursively(ifFalseTranslator, operandList, valueVariable, pos + 2);
+ final BlockStatement ifFalse = ifFalseBlockBuilder.toBlock();
+ currentBlockBuilder.add(
+ Expressions.ifThenElse(tester, ifTrue, ifFalse));
+ }
+
+ /** */
+ private Result toInnerStorageType(final Result result, final Type storageType) {
+ final Expression valueExpression =
+ ConverterUtils.toInternal(result.valueVariable, storageType);
+ if (valueExpression.equals(result.valueVariable))
+ return result;
+
+ final ParameterExpression valueVariable =
+ Expressions.parameter(
+ valueExpression.getType(),
+ list.newName(result.valueVariable.name + "_inner_type"));
+ list.add(Expressions.declare(Modifier.FINAL, valueVariable, valueExpression));
+ final ParameterExpression isNullVariable = result.isNullVariable;
+ return new Result(isNullVariable, valueVariable);
+ }
+
+ /** {@inheritDoc} */
+ @Override public Result visitDynamicParam(RexDynamicParam dynamicParam) {
+ final Pair<RexNode, Type> key = Pair.of(dynamicParam, currentStorageType);
+ if (rexWithStorageTypeResultMap.containsKey(key))
+ return rexWithStorageTypeResultMap.get(key);
+
+ final Type storageType = currentStorageType != null
+ ? currentStorageType : typeFactory.getJavaClass(dynamicParam.getType());
+ final Expression valueExpression = ConverterUtils.convert(
+ Expressions.call(root, BuiltInMethod.DATA_CONTEXT_GET.method,
+ Expressions.constant("?" + dynamicParam.getIndex())),
+ storageType);
+ final ParameterExpression valueVariable =
+ Expressions.parameter(valueExpression.getType(), list.newName("value_dynamic_param"));
+ list.add(Expressions.declare(Modifier.FINAL, valueVariable, valueExpression));
+ final ParameterExpression isNullVariable =
+ Expressions.parameter(Boolean.TYPE, list.newName("isNull_dynamic_param"));
+ list.add(Expressions.declare(Modifier.FINAL, isNullVariable, checkNull(valueVariable)));
+ final Result result = new Result(isNullVariable, valueVariable);
+ rexWithStorageTypeResultMap.put(key, result);
+ return result;
+ }
+
+ /** {@inheritDoc} */
+ @Override public Result visitFieldAccess(RexFieldAccess fieldAccess) {
+ final Pair<RexNode, Type> key = Pair.of(fieldAccess, currentStorageType);
+ if (rexWithStorageTypeResultMap.containsKey(key))
+ return rexWithStorageTypeResultMap.get(key);
+
+ final RexNode target = deref(fieldAccess.getReferenceExpr());
+ int fieldIndex = fieldAccess.getField().getIndex();
+ String fieldName = fieldAccess.getField().getName();
+ switch (target.getKind()) {
+ case CORREL_VARIABLE:
+ if (correlates == null) {
+ throw new RuntimeException("Cannot translate " + fieldAccess
+ + " since correlate variables resolver is not defined");
+ }
+ final RexToLixTranslator.InputGetter getter =
+ correlates.apply(((RexCorrelVariable)target).getName());
+ final Expression input = getter.field(
+ list, fieldIndex, currentStorageType);
+ final Expression condition = checkNull(input);
+ final ParameterExpression valueVariable =
+ Expressions.parameter(input.getType(), list.newName("corInp_value"));
+ list.add(Expressions.declare(Modifier.FINAL, valueVariable, input));
+ final ParameterExpression isNullVariable =
+ Expressions.parameter(Boolean.TYPE, list.newName("corInp_isNull"));
+ final Expression isNullExpression = Expressions.condition(
+ condition,
+ RexImpTable.TRUE_EXPR,
+ checkNull(valueVariable));
+ list.add(Expressions.declare(Modifier.FINAL, isNullVariable, isNullExpression));
+ final Result result1 = new Result(isNullVariable, valueVariable);
+ rexWithStorageTypeResultMap.put(key, result1);
+ return result1;
+ default:
+ RexNode rxIndex =
+ builder.makeLiteral(fieldIndex, typeFactory.createType(int.class), true);
+ RexNode rxName =
+ builder.makeLiteral(fieldName, typeFactory.createType(String.class), true);
+ RexCall accessCall = (RexCall)builder.makeCall(
+ fieldAccess.getType(), SqlStdOperatorTable.STRUCT_ACCESS,
+ ImmutableList.of(target, rxIndex, rxName));
+ final Result result2 = accessCall.accept(this);
+ rexWithStorageTypeResultMap.put(key, result2);
+ return result2;
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override public Result visitOver(RexOver over) {
+ throw new RuntimeException("cannot translate expression " + over);
+ }
+
+ /** {@inheritDoc} */
+ @Override public Result visitCorrelVariable(RexCorrelVariable correlVariable) {
+ throw new RuntimeException("Cannot translate " + correlVariable
+ + ". Correlated variables should always be referenced by field access");
+ }
+
+ /** {@inheritDoc} */
+ @Override public Result visitRangeRef(RexRangeRef rangeRef) {
+ throw new RuntimeException("cannot translate expression " + rangeRef);
+ }
+
+ /** {@inheritDoc} */
+ @Override public Result visitSubQuery(RexSubQuery subQuery) {
+ throw new RuntimeException("cannot translate expression " + subQuery);
+ }
+
+ /** {@inheritDoc} */
+ @Override public Result visitTableInputRef(RexTableInputRef fieldRef) {
+ throw new RuntimeException("cannot translate expression " + fieldRef);
+ }
+
+ /** {@inheritDoc} */
+ @Override public Result visitPatternFieldRef(RexPatternFieldRef fieldRef) {
+ return visitInputRef(fieldRef);
+ }
+
+ /** */
+ Expression checkNull(Expression expr) {
+ if (Primitive.flavor(expr.getType()) == Primitive.Flavor.PRIMITIVE)
+ return RexImpTable.FALSE_EXPR;
+
+ return Expressions.equal(expr, RexImpTable.NULL_EXPR);
+ }
+
+ /** */
+ Expression checkNotNull(Expression expr) {
+ if (Primitive.flavor(expr.getType()) == Primitive.Flavor.PRIMITIVE)
+ return RexImpTable.TRUE_EXPR;
+
+ return Expressions.notEqual(expr, RexImpTable.NULL_EXPR);
+ }
+
+ /** */
+ BlockBuilder getBlockBuilder() {
+ return list;
+ }
+
+ /** */
+ Expression getLiteral(Expression literalVariable) {
+ return literalMap.get(literalVariable);
+ }
+
+ /** Returns the value of a literal. */
+ Object getLiteralValue(Expression expr) {
+ if (expr instanceof ParameterExpression) {
+ final Expression constantExpr = literalMap.get(expr);
+ return getLiteralValue(constantExpr);
+ }
+ if (expr instanceof ConstantExpression)
+ return ((ConstantExpression)expr).value;
+
+ return null;
+ }
+
+ /** */
+ List<Result> getCallOperandResult(RexCall call) {
+ return callOperandResultMap.get(call);
+ }
+
+ /** Translates a field of an input to an expression. */
+ public interface InputGetter {
+ /** */
+ Expression field(BlockBuilder list, int index, Type storageType);
+ }
+
+ /** Result of translating a {@code RexNode}. */
+ public static class Result {
+ /** */
+ final ParameterExpression isNullVariable;
+
+ /** */
+ final ParameterExpression valueVariable;
+
+ /** */
+ public Result(ParameterExpression isNullVariable,
+ ParameterExpression valueVariable) {
+ this.isNullVariable = isNullVariable;
+ this.valueVariable = valueVariable;
+ }
+ }
+
+ /**
+ * Handle checked Exceptions declared in Method. In such case,
+ * method call should be wrapped in a try...catch block.
+ * "
+ * final Type method_call;
+ * try {
+ * method_call = callExpr
+ * } catch (Exception e) {
+ * throw new RuntimeException(e);
+ * }
+ * "
+ */
+ Expression handleMethodCheckedExceptions(Expression callExpr) {
+ // Try statement
+ ParameterExpression methodCall = Expressions.parameter(
+ callExpr.getType(), list.newName("method_call"));
+ list.add(Expressions.declare(Modifier.FINAL, methodCall, null));
+ Statement st = Expressions.statement(Expressions.assign(methodCall, callExpr));
+ // Catch Block, wrap checked exception in unchecked exception
+ ParameterExpression e = Expressions.parameter(0, Exception.class, "e");
+ Expression uncheckedException = Expressions.new_(RuntimeException.class, e);
+ CatchBlock cb = Expressions.catch_(e, Expressions.throw_(uncheckedException));
+ list.add(Expressions.tryCatch(st, cb));
+ return methodCall;
+ }
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/TableFunctionCallImplementor.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/TableFunctionCallImplementor.java
new file mode 100644
index 0000000..dd6460c
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/TableFunctionCallImplementor.java
@@ -0,0 +1,44 @@
+/*
+ * 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.ignite.internal.processors.query.calcite.exec.exp;
+
+import org.apache.calcite.adapter.enumerable.PhysType;
+import org.apache.calcite.linq4j.tree.Expression;
+import org.apache.calcite.rex.RexCall;
+
+/**
+ * Implements a table-valued function call.
+ */
+public interface TableFunctionCallImplementor {
+ /**
+ * Implements a table-valued function call.
+ *
+ * @param translator Translator for the call.
+ * @param inputEnumerable Table parameter of the call.
+ * @param call Call that should be implemented.
+ * @param inputPhysType Physical type of the table parameter.
+ * @param outputPhysType Physical type of the call.
+ * @return Expression that implements the call.
+ */
+ Expression implement(
+ RexToLixTranslator translator,
+ Expression inputEnumerable,
+ RexCall call,
+ PhysType inputPhysType,
+ PhysType outputPhysType
+ );
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/sql/fun/IgniteSqlOperatorTable.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/sql/fun/IgniteSqlOperatorTable.java
new file mode 100644
index 0000000..42925b7
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/sql/fun/IgniteSqlOperatorTable.java
@@ -0,0 +1,65 @@
+/*
+ * 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.ignite.internal.processors.query.calcite.sql.fun;
+
+import org.apache.calcite.sql.SqlFunction;
+import org.apache.calcite.sql.SqlFunctionCategory;
+import org.apache.calcite.sql.SqlKind;
+import org.apache.calcite.sql.type.OperandTypes;
+import org.apache.calcite.sql.type.ReturnTypes;
+import org.apache.calcite.sql.util.ReflectiveSqlOperatorTable;
+
+/**
+ * Operator table that contains only Ignite-specific functions and operators.
+ */
+public class IgniteSqlOperatorTable extends ReflectiveSqlOperatorTable {
+ /**
+ * The table of contains Ignite-specific operators.
+ */
+ private static IgniteSqlOperatorTable instance;
+
+ /**
+ *
+ */
+ public static final SqlFunction LENGTH =
+ new SqlFunction(
+ "LENGTH",
+ SqlKind.OTHER_FUNCTION,
+ ReturnTypes.INTEGER_NULLABLE,
+ null,
+ OperandTypes.CHARACTER,
+ SqlFunctionCategory.NUMERIC);
+
+ /**
+ *
+ */
+ public static final SqlFunction SYSTEM_RANGE = new SqlSystemRangeFunction();
+
+ /**
+ * Returns the Ignite operator table, creating it if necessary.
+ */
+ public static synchronized IgniteSqlOperatorTable instance() {
+ if (instance == null) {
+ // Creates and initializes the standard operator table.
+ // Uses two-phase construction, because we can't initialize the
+ // table until the constructor of the sub-class has completed.
+ instance = new IgniteSqlOperatorTable();
+ instance.init();
+ }
+ return instance;
+ }
+}
diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/sql/fun/SqlSystemRangeFunction.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/sql/fun/SqlSystemRangeFunction.java
new file mode 100644
index 0000000..8728dcc
--- /dev/null
+++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/sql/fun/SqlSystemRangeFunction.java
@@ -0,0 +1,60 @@
+/*
+ * 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.ignite.internal.processors.query.calcite.sql.fun;
+
+import org.apache.calcite.sql.SqlFunction;
+import org.apache.calcite.sql.SqlFunctionCategory;
+import org.apache.calcite.sql.SqlKind;
+import org.apache.calcite.sql.SqlOperatorBinding;
+import org.apache.calcite.sql.SqlTableFunction;
+import org.apache.calcite.sql.type.OperandTypes;
+import org.apache.calcite.sql.type.ReturnTypes;
+import org.apache.calcite.sql.type.SqlReturnTypeInference;
+import org.apache.calcite.sql.type.SqlTypeFamily;
+import org.apache.calcite.sql.type.SqlTypeName;
+import org.apache.calcite.sql.validate.SqlMonotonicity;
+
+/**
+ * Definition of the "SYSTEM_RANGE" builtin SQL function.
+ */
+public class SqlSystemRangeFunction extends SqlFunction implements SqlTableFunction {
+ /**
+ * Creates the SqlSystemRangeFunction.
+ */
+ SqlSystemRangeFunction() {
+ super(
+ "SYSTEM_RANGE",
+ SqlKind.OTHER_FUNCTION,
+ ReturnTypes.CURSOR,
+ null,
+ OperandTypes.or(
+ OperandTypes.family(SqlTypeFamily.NUMERIC, SqlTypeFamily.NUMERIC),
+ OperandTypes.family(SqlTypeFamily.NUMERIC, SqlTypeFamily.NUMERIC, SqlTypeFamily.NUMERIC)
+ ),
+ SqlFunctionCategory.USER_DEFINED_TABLE_FUNCTION);
+ }
+
+ /** {@inheritDoc} */
+ @Override public SqlMonotonicity getMonotonicity(SqlOperatorBinding call) {
+ return SqlMonotonicity.MONOTONIC;
+ }
+
+ /** {@inheritDoc} */
+ @Override public SqlReturnTypeInference getRowTypeInference() {
+ return cb -> cb.getTypeFactory().builder().add("X", SqlTypeName.BIGINT).build();
+ }
+}