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();
+    }
+}