You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@flink.apache.org by tw...@apache.org on 2021/02/02 10:18:24 UTC

[flink] branch release-1.12 updated: [FLINK-20942][table] Digest of FLOAT literals throws UnsupportedOperationException

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

twalthr pushed a commit to branch release-1.12
in repository https://gitbox.apache.org/repos/asf/flink.git


The following commit(s) were added to refs/heads/release-1.12 by this push:
     new 3624a08  [FLINK-20942][table] Digest of FLOAT literals throws UnsupportedOperationException
3624a08 is described below

commit 3624a08082bf4d977f078274d8c97f5c55681ff5
Author: yuzhao.cyz <yu...@gmail.com>
AuthorDate: Fri Jan 29 15:30:44 2021 +0800

    [FLINK-20942][table] Digest of FLOAT literals throws UnsupportedOperationException
    
    This closes #14801.
---
 .../java/org/apache/calcite/rex/RexLiteral.java    | 1179 ++++++++++++++++++++
 .../planner/runtime/batch/sql/CalcITCase.scala     |   33 +-
 .../java/org/apache/calcite/rex/RexLiteral.java    | 1179 ++++++++++++++++++++
 3 files changed, 2390 insertions(+), 1 deletion(-)

diff --git a/flink-table/flink-table-planner-blink/src/main/java/org/apache/calcite/rex/RexLiteral.java b/flink-table/flink-table-planner-blink/src/main/java/org/apache/calcite/rex/RexLiteral.java
new file mode 100644
index 0000000..dfb6808
--- /dev/null
+++ b/flink-table/flink-table-planner-blink/src/main/java/org/apache/calcite/rex/RexLiteral.java
@@ -0,0 +1,1179 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.calcite.rex;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+import org.apache.calcite.avatica.util.ByteString;
+import org.apache.calcite.avatica.util.DateTimeUtils;
+import org.apache.calcite.avatica.util.TimeUnit;
+import org.apache.calcite.config.CalciteSystemProperty;
+import org.apache.calcite.linq4j.function.Functions;
+import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.type.RelDataType;
+import org.apache.calcite.rel.type.RelDataTypeField;
+import org.apache.calcite.runtime.FlatLists;
+import org.apache.calcite.runtime.GeoFunctions;
+import org.apache.calcite.runtime.Geometries;
+import org.apache.calcite.sql.SqlCollation;
+import org.apache.calcite.sql.SqlKind;
+import org.apache.calcite.sql.SqlOperator;
+import org.apache.calcite.sql.fun.SqlStdOperatorTable;
+import org.apache.calcite.sql.parser.SqlParserUtil;
+import org.apache.calcite.sql.type.SqlTypeName;
+import org.apache.calcite.util.CompositeList;
+import org.apache.calcite.util.ConversionUtil;
+import org.apache.calcite.util.DateString;
+import org.apache.calcite.util.Litmus;
+import org.apache.calcite.util.NlsString;
+import org.apache.calcite.util.Sarg;
+import org.apache.calcite.util.TimeString;
+import org.apache.calcite.util.TimestampString;
+import org.apache.calcite.util.Util;
+
+import java.io.PrintWriter;
+import java.math.BigDecimal;
+import java.nio.ByteBuffer;
+import java.nio.charset.Charset;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Objects;
+import java.util.TimeZone;
+
+/**
+ * Constant value in a row-expression.
+ *
+ * <p>There are several methods for creating literals in {@link RexBuilder}: {@link
+ * RexBuilder#makeLiteral(boolean)} and so forth.
+ *
+ * <p>How is the value stored? In that respect, the class is somewhat of a black box. There is a
+ * {@link #getValue} method which returns the value as an object, but the type of that value is
+ * implementation detail, and it is best that your code does not depend upon that knowledge. It is
+ * better to use task-oriented methods such as {@link #getValue2} and {@link #toJavaString}.
+ *
+ * <p>The allowable types and combinations are:
+ *
+ * <table>
+ * <caption>Allowable types for RexLiteral instances</caption>
+ * <tr>
+ * <th>TypeName</th>
+ * <th>Meaning</th>
+ * <th>Value type</th>
+ * </tr>
+ * <tr>
+ * <td>{@link SqlTypeName#NULL}</td>
+ * <td>The null value. It has its own special type.</td>
+ * <td>null</td>
+ * </tr>
+ * <tr>
+ * <td>{@link SqlTypeName#BOOLEAN}</td>
+ * <td>Boolean, namely <code>TRUE</code>, <code>FALSE</code> or <code>
+ * UNKNOWN</code>.</td>
+ * <td>{@link Boolean}, or null represents the UNKNOWN value</td>
+ * </tr>
+ * <tr>
+ * <td>{@link SqlTypeName#DECIMAL}</td>
+ * <td>Exact number, for example <code>0</code>, <code>-.5</code>, <code>
+ * 12345</code>.</td>
+ * <td>{@link BigDecimal}</td>
+ * </tr>
+ * <tr>
+ * <td>{@link SqlTypeName#DOUBLE}</td>
+ * <td>Approximate number, for example <code>6.023E-23</code>.</td>
+ * <td>{@link BigDecimal}</td>
+ * </tr>
+ * <tr>
+ * <td>{@link SqlTypeName#DATE}</td>
+ * <td>Date, for example <code>DATE '1969-04'29'</code></td>
+ * <td>{@link Calendar};
+ *     also {@link Calendar} (UTC time zone)
+ *     and {@link Integer} (days since POSIX epoch)</td>
+ * </tr>
+ * <tr>
+ * <td>{@link SqlTypeName#TIME}</td>
+ * <td>Time, for example <code>TIME '18:37:42.567'</code></td>
+ * <td>{@link Calendar};
+ *     also {@link Calendar} (UTC time zone)
+ *     and {@link Integer} (milliseconds since midnight)</td>
+ * </tr>
+ * <tr>
+ * <td>{@link SqlTypeName#TIMESTAMP}</td>
+ * <td>Timestamp, for example <code>TIMESTAMP '1969-04-29
+ * 18:37:42.567'</code></td>
+ * <td>{@link TimestampString};
+ *     also {@link Calendar} (UTC time zone)
+ *     and {@link Long} (milliseconds since POSIX epoch)</td>
+ * </tr>
+ * <tr>
+ * <td>{@link SqlTypeName#INTERVAL_DAY},
+ *     {@link SqlTypeName#INTERVAL_DAY_HOUR},
+ *     {@link SqlTypeName#INTERVAL_DAY_MINUTE},
+ *     {@link SqlTypeName#INTERVAL_DAY_SECOND},
+ *     {@link SqlTypeName#INTERVAL_HOUR},
+ *     {@link SqlTypeName#INTERVAL_HOUR_MINUTE},
+ *     {@link SqlTypeName#INTERVAL_HOUR_SECOND},
+ *     {@link SqlTypeName#INTERVAL_MINUTE},
+ *     {@link SqlTypeName#INTERVAL_MINUTE_SECOND},
+ *     {@link SqlTypeName#INTERVAL_SECOND}</td>
+ * <td>Interval, for example <code>INTERVAL '4:3:2' HOUR TO SECOND</code></td>
+ * <td>{@link BigDecimal};
+ *     also {@link Long} (milliseconds)</td>
+ * </tr>
+ * <tr>
+ * <td>{@link SqlTypeName#INTERVAL_YEAR},
+ *     {@link SqlTypeName#INTERVAL_YEAR_MONTH},
+ *     {@link SqlTypeName#INTERVAL_MONTH}</td>
+ * <td>Interval, for example <code>INTERVAL '2-3' YEAR TO MONTH</code></td>
+ * <td>{@link BigDecimal};
+ *     also {@link Integer} (months)</td>
+ * </tr>
+ * <tr>
+ * <td>{@link SqlTypeName#CHAR}</td>
+ * <td>Character constant, for example <code>'Hello, world!'</code>, <code>
+ * ''</code>, <code>_N'Bonjour'</code>, <code>_ISO-8859-1'It''s superman!'
+ * COLLATE SHIFT_JIS$ja_JP$2</code>. These are always CHAR, never VARCHAR.</td>
+ * <td>{@link NlsString};
+ *     also {@link String}</td>
+ * </tr>
+ * <tr>
+ * <td>{@link SqlTypeName#BINARY}</td>
+ * <td>Binary constant, for example <code>X'7F34'</code>. (The number of hexits
+ * must be even; see above.) These constants are always BINARY, never
+ * VARBINARY.</td>
+ * <td>{@link ByteBuffer};
+ *     also {@code byte[]}</td>
+ * </tr>
+ * <tr>
+ * <td>{@link SqlTypeName#SYMBOL}</td>
+ * <td>A symbol is a special type used to make parsing easier; it is not part of
+ * the SQL standard, and is not exposed to end-users. It is used to hold a flag,
+ * such as the LEADING flag in a call to the function <code>
+ * TRIM([LEADING|TRAILING|BOTH] chars FROM string)</code>.</td>
+ * <td>An enum class</td>
+ * </tr>
+ * </table>
+ *
+ * <p>Line 374 ~ 376: fix FLINK-20942, the file should be removed when upgrade to CALCITE 1.27.0
+ * release.
+ */
+public class RexLiteral extends RexNode {
+    // ~ Instance fields --------------------------------------------------------
+
+    /**
+     * The value of this literal. Must be consistent with its type, as per {@link
+     * #valueMatchesType}. For example, you can't store an {@link Integer} value here just because
+     * you feel like it -- all numbers are represented by a {@link BigDecimal}. But since this field
+     * is private, it doesn't really matter how the values are stored.
+     */
+    private final Comparable value;
+
+    /** The real type of this literal, as reported by {@link #getType}. */
+    private final RelDataType type;
+
+    // TODO jvs 26-May-2006:  Use SqlTypeFamily instead; it exists
+    // for exactly this purpose (to avoid the confusion which results
+    // from overloading SqlTypeName).
+    /**
+     * An indication of the broad type of this literal -- even if its type isn't a SQL type.
+     * Sometimes this will be different than the SQL type; for example, all exact numbers, including
+     * integers have typeName {@link SqlTypeName#DECIMAL}. See {@link #valueMatchesType} for the
+     * definitive story.
+     */
+    private final SqlTypeName typeName;
+
+    private static final ImmutableList<TimeUnit> TIME_UNITS =
+            ImmutableList.copyOf(TimeUnit.values());
+
+    // ~ Constructors -----------------------------------------------------------
+
+    /** Creates a <code>RexLiteral</code>. */
+    RexLiteral(Comparable value, RelDataType type, SqlTypeName typeName) {
+        this.value = value;
+        this.type = Objects.requireNonNull(type);
+        this.typeName = Objects.requireNonNull(typeName);
+        Preconditions.checkArgument(valueMatchesType(value, typeName, true));
+        Preconditions.checkArgument((value == null) == type.isNullable());
+        Preconditions.checkArgument(typeName != SqlTypeName.ANY);
+        this.digest = computeDigest(RexDigestIncludeType.OPTIONAL);
+    }
+
+    // ~ Methods ----------------------------------------------------------------
+
+    /**
+     * Returns a string which concisely describes the definition of this rex literal. Two literals
+     * are equivalent if and only if their digests are the same.
+     *
+     * <p>The digest does not contain the expression's identity, but does include the identity of
+     * children.
+     *
+     * <p>Technically speaking 1:INT differs from 1:FLOAT, so we need data type in the literal's
+     * digest, however we want to avoid extra verbosity of the {@link RelNode#getDigest()} for
+     * readability purposes, so we omit type info in certain cases. For instance, 1:INT becomes 1
+     * (INT is implied by default), however 1:BIGINT always holds the type
+     *
+     * <p>Here's a non-exhaustive list of the "well known cases":
+     *
+     * <ul>
+     *   <li>Hide "NOT NULL" for not null literals
+     *   <li>Hide INTEGER, BOOLEAN, SYMBOL, TIME(0), TIMESTAMP(0), DATE(0) types
+     *   <li>Hide collation when it matches IMPLICIT/COERCIBLE
+     *   <li>Hide charset when it matches default
+     *   <li>Hide CHAR(xx) when literal length is equal to the precision of the type. In other
+     *       words, use 'Bob' instead of 'Bob':CHAR(3)
+     *   <li>Hide BOOL for AND/OR arguments. In other words, AND(true, null) means null is BOOL.
+     *   <li>Hide types for literals in simple binary operations (e.g. +, -, *, /, comparison) when
+     *       type of the other argument is clear. See {@link RexCall#computeDigest(boolean)} For
+     *       instance: =(true. null) means null is BOOL. =($0, null) means the type of null matches
+     *       the type of $0.
+     * </ul>
+     *
+     * @param includeType whether the digest should include type or not
+     * @return digest
+     */
+    public final String computeDigest(RexDigestIncludeType includeType) {
+        if (includeType == RexDigestIncludeType.OPTIONAL) {
+            if (digest != null) {
+                // digest is initialized with OPTIONAL, so cached value matches for
+                // includeType=OPTIONAL as well
+                return digest;
+            }
+            // Compute we should include the type or not
+            includeType = digestIncludesType();
+        } else if (digest != null && includeType == digestIncludesType()) {
+            // The digest is always computed with includeType=OPTIONAL
+            // If it happened to omit the type, we want to optimize computeDigest(NO_TYPE) as well
+            // If the digest includes the type, we want to optimize computeDigest(ALWAYS)
+            return digest;
+        }
+
+        return toJavaString(value, typeName, type, includeType);
+    }
+
+    /**
+     * Returns true if {@link RexDigestIncludeType#OPTIONAL} digest would include data type.
+     *
+     * @see RexCall#computeDigest(boolean)
+     * @return true if {@link RexDigestIncludeType#OPTIONAL} digest would include data type
+     */
+    RexDigestIncludeType digestIncludesType() {
+        return shouldIncludeType(value, type);
+    }
+
+    /** Returns whether a value is appropriate for its type. (We have rules about these things!) */
+    public static boolean valueMatchesType(Comparable value, SqlTypeName typeName, boolean strict) {
+        if (value == null) {
+            return true;
+        }
+        switch (typeName) {
+            case BOOLEAN:
+                // Unlike SqlLiteral, we do not allow boolean null.
+                return value instanceof Boolean;
+            case NULL:
+                return false; // value should have been null
+            case INTEGER: // not allowed -- use Decimal
+            case TINYINT:
+            case SMALLINT:
+                if (strict) {
+                    throw Util.unexpected(typeName);
+                }
+                // fall through
+            case DECIMAL:
+            case DOUBLE:
+            case FLOAT:
+            case REAL:
+            case BIGINT:
+                return value instanceof BigDecimal;
+            case DATE:
+                return value instanceof DateString;
+            case TIME:
+                return value instanceof TimeString;
+            case TIME_WITH_LOCAL_TIME_ZONE:
+                return value instanceof TimeString;
+            case TIMESTAMP:
+                return value instanceof TimestampString;
+            case TIMESTAMP_WITH_LOCAL_TIME_ZONE:
+                return value instanceof TimestampString;
+            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:
+                // The value of a DAY-TIME interval (whatever the start and end units,
+                // even say HOUR TO MINUTE) is in milliseconds (perhaps fractional
+                // milliseconds). The value of a YEAR-MONTH interval is in months.
+                return value instanceof BigDecimal;
+            case VARBINARY: // not allowed -- use Binary
+                if (strict) {
+                    throw Util.unexpected(typeName);
+                }
+                // fall through
+            case BINARY:
+                return value instanceof ByteString;
+            case VARCHAR: // not allowed -- use Char
+                if (strict) {
+                    throw Util.unexpected(typeName);
+                }
+                // fall through
+            case CHAR:
+                // A SqlLiteral's charset and collation are optional; not so a
+                // RexLiteral.
+                return (value instanceof NlsString)
+                        && (((NlsString) value).getCharset() != null)
+                        && (((NlsString) value).getCollation() != null);
+            case SARG:
+                return value instanceof Sarg;
+            case SYMBOL:
+                return value instanceof Enum;
+            case ROW:
+            case MULTISET:
+                return value instanceof List;
+            case GEOMETRY:
+                return value instanceof Geometries.Geom;
+            case ANY:
+                // Literal of type ANY is not legal. "CAST(2 AS ANY)" remains
+                // an integer literal surrounded by a cast function.
+                return false;
+            default:
+                throw Util.unexpected(typeName);
+        }
+    }
+
+    /** Returns the strict literal type for a given type. */
+    public static SqlTypeName strictTypeName(RelDataType type) {
+        final SqlTypeName typeName = type.getSqlTypeName();
+        switch (typeName) {
+            case INTEGER:
+            case TINYINT:
+            case SMALLINT:
+                return SqlTypeName.DECIMAL;
+            case REAL:
+            case FLOAT:
+                return SqlTypeName.DOUBLE;
+            case VARBINARY:
+                return SqlTypeName.BINARY;
+            case VARCHAR:
+                return SqlTypeName.CHAR;
+            default:
+                return typeName;
+        }
+    }
+
+    private static String toJavaString(
+            Comparable value,
+            SqlTypeName typeName,
+            RelDataType type,
+            RexDigestIncludeType includeType) {
+        assert includeType != RexDigestIncludeType.OPTIONAL
+                : "toJavaString must not be called with includeType=OPTIONAL";
+        if (value == null) {
+            return includeType == RexDigestIncludeType.NO_TYPE
+                    ? "null"
+                    : "null:" + type.getFullTypeString();
+        }
+        StringBuilder sb = new StringBuilder();
+        appendAsJava(value, sb, typeName, type, false, includeType);
+
+        if (includeType != RexDigestIncludeType.NO_TYPE) {
+            sb.append(':');
+            final String fullTypeString = type.getFullTypeString();
+            if (!fullTypeString.endsWith("NOT NULL")) {
+                sb.append(fullTypeString);
+            } else {
+                // Trim " NOT NULL". Apparently, the literal is not null, so we just print the data
+                // type.
+                sb.append(fullTypeString, 0, fullTypeString.length() - 9);
+            }
+        }
+        return sb.toString();
+    }
+
+    /**
+     * Computes if data type can be omitted from the digset.
+     *
+     * <p>For instance, {@code 1:BIGINT} has to keep data type while {@code 1:INT} should be
+     * represented as just {@code 1}.
+     *
+     * <p>Implementation assumption: this method should be fast. In fact might call {@link
+     * NlsString#getValue()} which could decode the string, however we rely on the cache there.
+     *
+     * @see RexLiteral#computeDigest(RexDigestIncludeType)
+     * @param value value of the literal
+     * @param type type of the literal
+     * @return NO_TYPE when type can be omitted, ALWAYS otherwise
+     */
+    private static RexDigestIncludeType shouldIncludeType(Comparable value, RelDataType type) {
+        if (type.isNullable()) {
+            // This means "null literal", so we require a type for it
+            // There might be exceptions like AND(null, true) which are handled by
+            // RexCall#computeDigest
+            return RexDigestIncludeType.ALWAYS;
+        }
+        // The variable here simplifies debugging (one can set a breakpoint at return)
+        // final ensures we set the value in all the branches, and it ensures the value is set just
+        // once
+        final RexDigestIncludeType includeType;
+        if (type.getSqlTypeName() == SqlTypeName.BOOLEAN
+                || type.getSqlTypeName() == SqlTypeName.INTEGER
+                || type.getSqlTypeName() == SqlTypeName.SYMBOL) {
+            // We don't want false:BOOLEAN NOT NULL, so we don't print type information for
+            // non-nullable BOOLEAN and INTEGER
+            includeType = RexDigestIncludeType.NO_TYPE;
+        } else if (type.getSqlTypeName() == SqlTypeName.CHAR && value instanceof NlsString) {
+            NlsString nlsString = (NlsString) value;
+
+            // Ignore type information for 'Bar':CHAR(3)
+            if (((nlsString.getCharset() != null
+                                    && type.getCharset().equals(nlsString.getCharset()))
+                            || (nlsString.getCharset() == null
+                                    && SqlCollation.IMPLICIT
+                                            .getCharset()
+                                            .equals(type.getCharset())))
+                    && nlsString.getCollation().equals(type.getCollation())
+                    && ((NlsString) value).getValue().length() == type.getPrecision()) {
+                includeType = RexDigestIncludeType.NO_TYPE;
+            } else {
+                includeType = RexDigestIncludeType.ALWAYS;
+            }
+        } else if (type.getPrecision() == 0
+                && (type.getSqlTypeName() == SqlTypeName.TIME
+                        || type.getSqlTypeName() == SqlTypeName.TIMESTAMP
+                        || type.getSqlTypeName() == SqlTypeName.DATE)) {
+            // Ignore type information for '12:23:20':TIME(0)
+            // Note that '12:23:20':TIME WITH LOCAL TIME ZONE
+            includeType = RexDigestIncludeType.NO_TYPE;
+        } else {
+            includeType = RexDigestIncludeType.ALWAYS;
+        }
+        return includeType;
+    }
+
+    /**
+     * Returns whether a value is valid as a constant value, using the same criteria as {@link
+     * #valueMatchesType}.
+     */
+    public static boolean validConstant(Object o, Litmus litmus) {
+        if (o == null
+                || o instanceof BigDecimal
+                || o instanceof NlsString
+                || o instanceof ByteString
+                || o instanceof Boolean) {
+            return litmus.succeed();
+        } else if (o instanceof List) {
+            List list = (List) o;
+            for (Object o1 : list) {
+                if (!validConstant(o1, litmus)) {
+                    return litmus.fail("not a constant: {}", o1);
+                }
+            }
+            return litmus.succeed();
+        } else if (o instanceof Map) {
+            @SuppressWarnings("unchecked")
+            final Map<Object, Object> map = (Map) o;
+            for (Map.Entry entry : map.entrySet()) {
+                if (!validConstant(entry.getKey(), litmus)) {
+                    return litmus.fail("not a constant: {}", entry.getKey());
+                }
+                if (!validConstant(entry.getValue(), litmus)) {
+                    return litmus.fail("not a constant: {}", entry.getValue());
+                }
+            }
+            return litmus.succeed();
+        } else {
+            return litmus.fail("not a constant: {}", o);
+        }
+    }
+
+    /**
+     * Returns a list of the time units covered by an interval type such as HOUR TO SECOND. Adds
+     * MILLISECOND if the end is SECOND, to deal with fractional seconds.
+     */
+    private static List<TimeUnit> getTimeUnits(SqlTypeName typeName) {
+        final TimeUnit start = typeName.getStartUnit();
+        final TimeUnit end = typeName.getEndUnit();
+        final ImmutableList<TimeUnit> list = TIME_UNITS.subList(start.ordinal(), end.ordinal() + 1);
+        if (end == TimeUnit.SECOND) {
+            return CompositeList.of(list, ImmutableList.of(TimeUnit.MILLISECOND));
+        }
+        return list;
+    }
+
+    private String intervalString(BigDecimal v) {
+        final List<TimeUnit> timeUnits = getTimeUnits(type.getSqlTypeName());
+        final StringBuilder b = new StringBuilder();
+        for (TimeUnit timeUnit : timeUnits) {
+            final BigDecimal[] result = v.divideAndRemainder(timeUnit.multiplier);
+            if (b.length() > 0) {
+                b.append(timeUnit.separator);
+            }
+            final int width = b.length() == 0 ? -1 : width(timeUnit); // don't pad 1st
+            pad(b, result[0].toString(), width);
+            v = result[1];
+        }
+        if (Util.last(timeUnits) == TimeUnit.MILLISECOND) {
+            while (b.toString().matches(".*\\.[0-9]*0")) {
+                if (b.toString().endsWith(".0")) {
+                    b.setLength(b.length() - 2); // remove ".0"
+                } else {
+                    b.setLength(b.length() - 1); // remove "0"
+                }
+            }
+        }
+        return b.toString();
+    }
+
+    private static void pad(StringBuilder b, String s, int width) {
+        if (width >= 0) {
+            for (int i = s.length(); i < width; i++) {
+                b.append('0');
+            }
+        }
+        b.append(s);
+    }
+
+    private static int width(TimeUnit timeUnit) {
+        switch (timeUnit) {
+            case MILLISECOND:
+                return 3;
+            case HOUR:
+            case MINUTE:
+            case SECOND:
+                return 2;
+            default:
+                return -1;
+        }
+    }
+
+    /** Prints the value this literal as a Java string constant. */
+    public void printAsJava(PrintWriter pw) {
+        Util.asStringBuilder(
+                pw,
+                sb -> appendAsJava(value, sb, typeName, type, true, RexDigestIncludeType.NO_TYPE));
+    }
+
+    /**
+     * Appends the specified value in the provided destination as a Java string. The value must be
+     * consistent with the type, as per {@link #valueMatchesType}.
+     *
+     * <p>Typical return values:
+     *
+     * <ul>
+     *   <li>true
+     *   <li>null
+     *   <li>"Hello, world!"
+     *   <li>1.25
+     *   <li>1234ABCD
+     * </ul>
+     *
+     * @param value Value to be appended to the provided destination as a Java string
+     * @param sb Destination to which to append the specified value
+     * @param typeName Type name to be used for the transformation of the value to a Java string
+     * @param type Type to be used for the transformation of the value to a Java string
+     * @param includeType Whether to include the data type in the Java representation
+     */
+    private static void appendAsJava(
+            Comparable value,
+            StringBuilder sb,
+            SqlTypeName typeName,
+            RelDataType type,
+            boolean java,
+            RexDigestIncludeType includeType) {
+        switch (typeName) {
+            case CHAR:
+                NlsString nlsString = (NlsString) value;
+                if (java) {
+                    Util.printJavaString(sb, nlsString.getValue(), true);
+                } else {
+                    boolean includeCharset =
+                            (nlsString.getCharsetName() != null)
+                                    && !nlsString
+                                            .getCharsetName()
+                                            .equals(CalciteSystemProperty.DEFAULT_CHARSET.value());
+                    sb.append(nlsString.asSql(includeCharset, false));
+                }
+                break;
+            case BOOLEAN:
+                assert value instanceof Boolean;
+                sb.append(value.toString());
+                break;
+            case DECIMAL:
+                assert value instanceof BigDecimal;
+                sb.append(value.toString());
+                break;
+            case DOUBLE:
+                assert value instanceof BigDecimal;
+                sb.append(Util.toScientificNotation((BigDecimal) value));
+                break;
+            case BIGINT:
+                assert value instanceof BigDecimal;
+                long narrowLong = ((BigDecimal) value).longValue();
+                sb.append(String.valueOf(narrowLong));
+                sb.append('L');
+                break;
+            case BINARY:
+                assert value instanceof ByteString;
+                sb.append("X'");
+                sb.append(((ByteString) value).toString(16));
+                sb.append("'");
+                break;
+            case NULL:
+                assert value == null;
+                sb.append("null");
+                break;
+            case SARG:
+                assert value instanceof Sarg;
+                //noinspection unchecked,rawtypes
+                Util.asStringBuilder(sb, sb2 -> printSarg(sb2, (Sarg) value, type));
+                break;
+            case SYMBOL:
+                assert value instanceof Enum;
+                sb.append("FLAG(");
+                sb.append(value.toString());
+                sb.append(")");
+                break;
+            case DATE:
+                assert value instanceof DateString;
+                sb.append(value.toString());
+                break;
+            case TIME:
+            case TIME_WITH_LOCAL_TIME_ZONE:
+                assert value instanceof TimeString;
+                sb.append(value.toString());
+                break;
+            case TIMESTAMP:
+            case TIMESTAMP_WITH_LOCAL_TIME_ZONE:
+                assert value instanceof TimestampString;
+                sb.append(value.toString());
+                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:
+                assert value instanceof BigDecimal;
+                sb.append(value.toString());
+                break;
+            case MULTISET:
+            case ROW:
+                final List<RexLiteral> list = (List) value;
+                Util.asStringBuilder(
+                        sb,
+                        sb2 ->
+                                Util.printList(
+                                        sb,
+                                        list.size(),
+                                        (sb3, i) ->
+                                                sb3.append(
+                                                        list.get(i).computeDigest(includeType))));
+                break;
+            case GEOMETRY:
+                final String wkt = GeoFunctions.ST_AsWKT((Geometries.Geom) value);
+                sb.append(wkt);
+                break;
+            default:
+                assert valueMatchesType(value, typeName, true);
+                throw Util.needToImplement(typeName);
+        }
+    }
+
+    private static <C extends Comparable<C>> void printSarg(
+            StringBuilder sb, Sarg<C> sarg, RelDataType type) {
+        sarg.printTo(sb, (sb2, value) -> sb2.append(toLiteral(type, value)));
+    }
+
+    /**
+     * Converts a value to a temporary literal, for the purposes of generating a digest. Literals of
+     * type ROW and MULTISET require that their components are also literals.
+     */
+    private static RexLiteral toLiteral(RelDataType type, Comparable<?> value) {
+        final SqlTypeName typeName = strictTypeName(type);
+        switch (typeName) {
+            case ROW:
+                final List<Comparable<?>> fieldValues = (List) value;
+                final List<RelDataTypeField> fields = type.getFieldList();
+                final List<RexLiteral> fieldLiterals =
+                        FlatLists.of(
+                                Functions.generate(
+                                        fieldValues.size(),
+                                        i ->
+                                                toLiteral(
+                                                        fields.get(i).getType(),
+                                                        fieldValues.get(i))));
+                return new RexLiteral((Comparable) fieldLiterals, type, typeName);
+
+            case MULTISET:
+                final List<Comparable<?>> elementValues = (List) value;
+                final List<RexLiteral> elementLiterals =
+                        FlatLists.of(
+                                Functions.generate(
+                                        elementValues.size(),
+                                        i ->
+                                                toLiteral(
+                                                        type.getComponentType(),
+                                                        elementValues.get(i))));
+                return new RexLiteral((Comparable) elementLiterals, type, typeName);
+
+            default:
+                return new RexLiteral(value, type, typeName);
+        }
+    }
+
+    /**
+     * Converts a Jdbc string into a RexLiteral. This method accepts a string, as returned by the
+     * Jdbc method ResultSet.getString(), and restores the string into an equivalent RexLiteral. It
+     * allows one to use Jdbc strings as a common format for data.
+     *
+     * <p>If a null literal is provided, then a null pointer will be returned.
+     *
+     * @param type data type of literal to be read
+     * @param typeName type family of literal
+     * @param literal the (non-SQL encoded) string representation, as returned by the Jdbc call to
+     *     return a column as a string
+     * @return a typed RexLiteral, or null
+     */
+    public static RexLiteral fromJdbcString(
+            RelDataType type, SqlTypeName typeName, String literal) {
+        if (literal == null) {
+            return null;
+        }
+
+        switch (typeName) {
+            case CHAR:
+                Charset charset = type.getCharset();
+                SqlCollation collation = type.getCollation();
+                NlsString str = new NlsString(literal, charset.name(), collation);
+                return new RexLiteral(str, type, typeName);
+            case BOOLEAN:
+                boolean b = ConversionUtil.toBoolean(literal);
+                return new RexLiteral(b, type, typeName);
+            case DECIMAL:
+            case DOUBLE:
+                BigDecimal d = new BigDecimal(literal);
+                return new RexLiteral(d, type, typeName);
+            case BINARY:
+                byte[] bytes = ConversionUtil.toByteArrayFromString(literal, 16);
+                return new RexLiteral(new ByteString(bytes), type, typeName);
+            case NULL:
+                return new RexLiteral(null, type, typeName);
+            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:
+                long millis = SqlParserUtil.intervalToMillis(literal, type.getIntervalQualifier());
+                return new RexLiteral(BigDecimal.valueOf(millis), type, typeName);
+            case INTERVAL_YEAR:
+            case INTERVAL_YEAR_MONTH:
+            case INTERVAL_MONTH:
+                long months = SqlParserUtil.intervalToMonths(literal, type.getIntervalQualifier());
+                return new RexLiteral(BigDecimal.valueOf(months), type, typeName);
+            case DATE:
+            case TIME:
+            case TIMESTAMP:
+                String format = getCalendarFormat(typeName);
+                TimeZone tz = DateTimeUtils.UTC_ZONE;
+                final Comparable v;
+                switch (typeName) {
+                    case DATE:
+                        final Calendar cal =
+                                DateTimeUtils.parseDateFormat(
+                                        literal, new SimpleDateFormat(format, Locale.ROOT), tz);
+                        if (cal == null) {
+                            throw new AssertionError(
+                                    "fromJdbcString: invalid date/time value '" + literal + "'");
+                        }
+                        v = DateString.fromCalendarFields(cal);
+                        break;
+                    default:
+                        // Allow fractional seconds for times and timestamps
+                        assert format != null;
+                        final DateTimeUtils.PrecisionTime ts =
+                                DateTimeUtils.parsePrecisionDateTimeLiteral(
+                                        literal, new SimpleDateFormat(format, Locale.ROOT), tz, -1);
+                        if (ts == null) {
+                            throw new AssertionError(
+                                    "fromJdbcString: invalid date/time value '" + literal + "'");
+                        }
+                        switch (typeName) {
+                            case TIMESTAMP:
+                                v =
+                                        TimestampString.fromCalendarFields(ts.getCalendar())
+                                                .withFraction(ts.getFraction());
+                                break;
+                            case TIME:
+                                v =
+                                        TimeString.fromCalendarFields(ts.getCalendar())
+                                                .withFraction(ts.getFraction());
+                                break;
+                            default:
+                                throw new AssertionError();
+                        }
+                }
+                return new RexLiteral(v, type, typeName);
+
+            case SYMBOL:
+                // Symbols are for internal use
+            default:
+                throw new AssertionError("fromJdbcString: unsupported type");
+        }
+    }
+
+    private static String getCalendarFormat(SqlTypeName typeName) {
+        switch (typeName) {
+            case DATE:
+                return DateTimeUtils.DATE_FORMAT_STRING;
+            case TIME:
+                return DateTimeUtils.TIME_FORMAT_STRING;
+            case TIMESTAMP:
+                return DateTimeUtils.TIMESTAMP_FORMAT_STRING;
+            default:
+                throw new AssertionError("getCalendarFormat: unknown type");
+        }
+    }
+
+    public SqlTypeName getTypeName() {
+        return typeName;
+    }
+
+    public RelDataType getType() {
+        return type;
+    }
+
+    @Override
+    public SqlKind getKind() {
+        return SqlKind.LITERAL;
+    }
+
+    /** Returns whether this literal's value is null. */
+    public boolean isNull() {
+        return value == null;
+    }
+
+    /**
+     * Returns the value of this literal.
+     *
+     * <p>For backwards compatibility, returns DATE. TIME and TIMESTAMP as a {@link Calendar} value
+     * in UTC time zone.
+     */
+    public Comparable getValue() {
+        assert valueMatchesType(value, typeName, true) : value;
+        if (value == null) {
+            return null;
+        }
+        switch (typeName) {
+            case TIME:
+            case DATE:
+            case TIMESTAMP:
+                return getValueAs(Calendar.class);
+            default:
+                return value;
+        }
+    }
+
+    /**
+     * Returns the value of this literal, in the form that the calculator program builder wants it.
+     */
+    public Object getValue2() {
+        if (value == null) {
+            return null;
+        }
+        switch (typeName) {
+            case CHAR:
+                return getValueAs(String.class);
+            case DECIMAL:
+            case TIMESTAMP:
+            case TIMESTAMP_WITH_LOCAL_TIME_ZONE:
+                return getValueAs(Long.class);
+            case DATE:
+            case TIME:
+            case TIME_WITH_LOCAL_TIME_ZONE:
+                return getValueAs(Integer.class);
+            default:
+                return value;
+        }
+    }
+
+    /** Returns the value of this literal, in the form that the rex-to-lix translator wants it. */
+    public Object getValue3() {
+        if (value == null) {
+            return null;
+        }
+        switch (typeName) {
+            case DECIMAL:
+                assert value instanceof BigDecimal;
+                return value;
+            default:
+                return getValue2();
+        }
+    }
+
+    /** Returns the value of this literal, in the form that {@link RexInterpreter} wants it. */
+    public Comparable getValue4() {
+        if (value == null) {
+            return null;
+        }
+        switch (typeName) {
+            case TIMESTAMP:
+            case TIMESTAMP_WITH_LOCAL_TIME_ZONE:
+                return getValueAs(Long.class);
+            case DATE:
+            case TIME:
+            case TIME_WITH_LOCAL_TIME_ZONE:
+                return getValueAs(Integer.class);
+            default:
+                return value;
+        }
+    }
+
+    /**
+     * Returns the value of this literal as an instance of the specified class.
+     *
+     * <p>The following SQL types allow more than one form:
+     *
+     * <ul>
+     *   <li>CHAR as {@link NlsString} or {@link String}
+     *   <li>TIME as {@link TimeString}, {@link Integer} (milliseconds since midnight), {@link
+     *       Calendar} (in UTC)
+     *   <li>DATE as {@link DateString}, {@link Integer} (days since 1970-01-01), {@link Calendar}
+     *   <li>TIMESTAMP as {@link TimestampString}, {@link Long} (milliseconds since 1970-01-01
+     *       00:00:00), {@link Calendar}
+     *   <li>DECIMAL as {@link BigDecimal} or {@link Long}
+     * </ul>
+     *
+     * <p>Called with {@code clazz} = {@link Comparable}, returns the value in its native form.
+     *
+     * @param clazz Desired return type
+     * @param <T> Return type
+     * @return Value of this literal in the desired type
+     */
+    public <T> T getValueAs(Class<T> clazz) {
+        if (value == null || clazz.isInstance(value)) {
+            return clazz.cast(value);
+        }
+        switch (typeName) {
+            case BINARY:
+                if (clazz == byte[].class) {
+                    return clazz.cast(((ByteString) value).getBytes());
+                }
+                break;
+            case CHAR:
+                if (clazz == String.class) {
+                    return clazz.cast(((NlsString) value).getValue());
+                } else if (clazz == Character.class) {
+                    return clazz.cast(((NlsString) value).getValue().charAt(0));
+                }
+                break;
+            case VARCHAR:
+                if (clazz == String.class) {
+                    return clazz.cast(((NlsString) value).getValue());
+                }
+                break;
+            case DECIMAL:
+                if (clazz == Long.class) {
+                    return clazz.cast(((BigDecimal) value).unscaledValue().longValue());
+                }
+                // fall through
+            case BIGINT:
+            case INTEGER:
+            case SMALLINT:
+            case TINYINT:
+            case DOUBLE:
+            case REAL:
+            case FLOAT:
+                if (clazz == Long.class) {
+                    return clazz.cast(((BigDecimal) value).longValue());
+                } else if (clazz == Integer.class) {
+                    return clazz.cast(((BigDecimal) value).intValue());
+                } else if (clazz == Short.class) {
+                    return clazz.cast(((BigDecimal) value).shortValue());
+                } else if (clazz == Byte.class) {
+                    return clazz.cast(((BigDecimal) value).byteValue());
+                } else if (clazz == Double.class) {
+                    return clazz.cast(((BigDecimal) value).doubleValue());
+                } else if (clazz == Float.class) {
+                    return clazz.cast(((BigDecimal) value).floatValue());
+                }
+                break;
+            case DATE:
+                if (clazz == Integer.class) {
+                    return clazz.cast(((DateString) value).getDaysSinceEpoch());
+                } else if (clazz == Calendar.class) {
+                    return clazz.cast(((DateString) value).toCalendar());
+                }
+                break;
+            case TIME:
+                if (clazz == Integer.class) {
+                    return clazz.cast(((TimeString) value).getMillisOfDay());
+                } else if (clazz == Calendar.class) {
+                    // Note: Nanos are ignored
+                    return clazz.cast(((TimeString) value).toCalendar());
+                }
+                break;
+            case TIME_WITH_LOCAL_TIME_ZONE:
+                if (clazz == Integer.class) {
+                    // Milliseconds since 1970-01-01 00:00:00
+                    return clazz.cast(((TimeString) value).getMillisOfDay());
+                }
+                break;
+            case TIMESTAMP:
+                if (clazz == Long.class) {
+                    // Milliseconds since 1970-01-01 00:00:00
+                    return clazz.cast(((TimestampString) value).getMillisSinceEpoch());
+                } else if (clazz == Calendar.class) {
+                    // Note: Nanos are ignored
+                    return clazz.cast(((TimestampString) value).toCalendar());
+                }
+                break;
+            case TIMESTAMP_WITH_LOCAL_TIME_ZONE:
+                if (clazz == Long.class) {
+                    // Milliseconds since 1970-01-01 00:00:00
+                    return clazz.cast(((TimestampString) value).getMillisSinceEpoch());
+                } else if (clazz == Calendar.class) {
+                    // Note: Nanos are ignored
+                    return clazz.cast(((TimestampString) value).toCalendar());
+                }
+                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:
+                if (clazz == Integer.class) {
+                    return clazz.cast(((BigDecimal) value).intValue());
+                } else if (clazz == Long.class) {
+                    return clazz.cast(((BigDecimal) value).longValue());
+                } else if (clazz == String.class) {
+                    return clazz.cast(intervalString(getValueAs(BigDecimal.class).abs()));
+                } else if (clazz == Boolean.class) {
+                    // return whether negative
+                    return clazz.cast(getValueAs(BigDecimal.class).signum() < 0);
+                }
+                break;
+        }
+        throw new AssertionError("cannot convert " + typeName + " literal to " + clazz);
+    }
+
+    public static boolean booleanValue(RexNode node) {
+        return (Boolean) ((RexLiteral) node).value;
+    }
+
+    public boolean isAlwaysTrue() {
+        if (typeName != SqlTypeName.BOOLEAN) {
+            return false;
+        }
+        return booleanValue(this);
+    }
+
+    public boolean isAlwaysFalse() {
+        if (typeName != SqlTypeName.BOOLEAN) {
+            return false;
+        }
+        return !booleanValue(this);
+    }
+
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        return (obj instanceof RexLiteral)
+                && equals(((RexLiteral) obj).value, value)
+                && equals(((RexLiteral) obj).type, type);
+    }
+
+    public int hashCode() {
+        return Objects.hash(value, type);
+    }
+
+    public static Comparable value(RexNode node) {
+        return findValue(node);
+    }
+
+    public static int intValue(RexNode node) {
+        final Comparable value = findValue(node);
+        return ((Number) value).intValue();
+    }
+
+    public static String stringValue(RexNode node) {
+        final Comparable value = findValue(node);
+        return (value == null) ? null : ((NlsString) value).getValue();
+    }
+
+    private static Comparable findValue(RexNode node) {
+        if (node instanceof RexLiteral) {
+            return ((RexLiteral) node).value;
+        }
+        if (node instanceof RexCall) {
+            final RexCall call = (RexCall) node;
+            final SqlOperator operator = call.getOperator();
+            if (operator == SqlStdOperatorTable.CAST) {
+                return findValue(call.getOperands().get(0));
+            }
+            if (operator == SqlStdOperatorTable.UNARY_MINUS) {
+                final BigDecimal value = (BigDecimal) findValue(call.getOperands().get(0));
+                return value.negate();
+            }
+        }
+        throw new AssertionError("not a literal: " + node);
+    }
+
+    public static boolean isNullLiteral(RexNode node) {
+        return (node instanceof RexLiteral) && (((RexLiteral) node).value == null);
+    }
+
+    private static boolean equals(Object o1, Object o2) {
+        return Objects.equals(o1, o2);
+    }
+
+    public <R> R accept(RexVisitor<R> visitor) {
+        return visitor.visitLiteral(this);
+    }
+
+    public <R, P> R accept(RexBiVisitor<R, P> visitor, P arg) {
+        return visitor.visitLiteral(this, arg);
+    }
+}
diff --git a/flink-table/flink-table-planner-blink/src/test/scala/org/apache/flink/table/planner/runtime/batch/sql/CalcITCase.scala b/flink-table/flink-table-planner-blink/src/test/scala/org/apache/flink/table/planner/runtime/batch/sql/CalcITCase.scala
index 2d799d1..d405cf9 100644
--- a/flink-table/flink-table-planner-blink/src/test/scala/org/apache/flink/table/planner/runtime/batch/sql/CalcITCase.scala
+++ b/flink-table/flink-table-planner-blink/src/test/scala/org/apache/flink/table/planner/runtime/batch/sql/CalcITCase.scala
@@ -26,7 +26,7 @@ import org.apache.flink.api.common.typeinfo.Types
 import org.apache.flink.api.common.typeinfo.Types.INSTANT
 import org.apache.flink.api.java.typeutils._
 import org.apache.flink.api.scala._
-import org.apache.flink.table.api.ValidationException
+import org.apache.flink.table.api.{DataTypes, ValidationException}
 import org.apache.flink.table.api.config.ExecutionConfigOptions
 import org.apache.flink.table.data.{DecimalDataUtils, TimestampData}
 import org.apache.flink.table.data.util.DataFormatConverters.LocalDateConverter
@@ -42,6 +42,8 @@ import org.apache.flink.table.planner.utils.DateTimeTestUtil
 import org.apache.flink.table.planner.utils.DateTimeTestUtil._
 import org.apache.flink.table.runtime.functions.SqlDateTimeUtils.unixTimestampToLocalDateTime
 import org.apache.flink.types.Row
+import org.apache.flink.util.CollectionUtil
+
 import org.junit.Assert.assertEquals
 import org.junit._
 
@@ -1310,4 +1312,33 @@ class CalcITCase extends BatchTestBase {
     )
   }
 
+  @Test
+  def testFloatIn(): Unit = {
+    val source = tEnv.fromValues(
+      DataTypes.ROW(
+        DataTypes.FIELD("f0", DataTypes.FLOAT()),
+        DataTypes.FIELD("f1", DataTypes.FLOAT()),
+        DataTypes.FIELD("f2", DataTypes.FLOAT())),
+      row(1.0f, 11.0f, 12.0f),
+      row(2.0f, 21.0f, 22.0f),
+      row(3.0f, 31.0f, 32.0f),
+      row(4.0f, 41.0f, 42.0f),
+      row(5.0f, 51.0f, 52.0f)
+    )
+
+    tEnv.createTemporaryView("myTable", source)
+
+    val query = """
+                  |select * from myTable where f0 in (1.0, 2.0, 3.0)
+                  |""".stripMargin;
+
+    checkResult(
+      query,
+      Seq(
+        row(1.0f, 11.0f, 12.0f),
+        row(2.0f, 21.0f, 22.0f),
+        row(3.0f, 31.0f, 32.0f))
+    )
+  }
+
 }
diff --git a/flink-table/flink-table-planner/src/main/java/org/apache/calcite/rex/RexLiteral.java b/flink-table/flink-table-planner/src/main/java/org/apache/calcite/rex/RexLiteral.java
new file mode 100644
index 0000000..dfb6808
--- /dev/null
+++ b/flink-table/flink-table-planner/src/main/java/org/apache/calcite/rex/RexLiteral.java
@@ -0,0 +1,1179 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.calcite.rex;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+import org.apache.calcite.avatica.util.ByteString;
+import org.apache.calcite.avatica.util.DateTimeUtils;
+import org.apache.calcite.avatica.util.TimeUnit;
+import org.apache.calcite.config.CalciteSystemProperty;
+import org.apache.calcite.linq4j.function.Functions;
+import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.type.RelDataType;
+import org.apache.calcite.rel.type.RelDataTypeField;
+import org.apache.calcite.runtime.FlatLists;
+import org.apache.calcite.runtime.GeoFunctions;
+import org.apache.calcite.runtime.Geometries;
+import org.apache.calcite.sql.SqlCollation;
+import org.apache.calcite.sql.SqlKind;
+import org.apache.calcite.sql.SqlOperator;
+import org.apache.calcite.sql.fun.SqlStdOperatorTable;
+import org.apache.calcite.sql.parser.SqlParserUtil;
+import org.apache.calcite.sql.type.SqlTypeName;
+import org.apache.calcite.util.CompositeList;
+import org.apache.calcite.util.ConversionUtil;
+import org.apache.calcite.util.DateString;
+import org.apache.calcite.util.Litmus;
+import org.apache.calcite.util.NlsString;
+import org.apache.calcite.util.Sarg;
+import org.apache.calcite.util.TimeString;
+import org.apache.calcite.util.TimestampString;
+import org.apache.calcite.util.Util;
+
+import java.io.PrintWriter;
+import java.math.BigDecimal;
+import java.nio.ByteBuffer;
+import java.nio.charset.Charset;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Objects;
+import java.util.TimeZone;
+
+/**
+ * Constant value in a row-expression.
+ *
+ * <p>There are several methods for creating literals in {@link RexBuilder}: {@link
+ * RexBuilder#makeLiteral(boolean)} and so forth.
+ *
+ * <p>How is the value stored? In that respect, the class is somewhat of a black box. There is a
+ * {@link #getValue} method which returns the value as an object, but the type of that value is
+ * implementation detail, and it is best that your code does not depend upon that knowledge. It is
+ * better to use task-oriented methods such as {@link #getValue2} and {@link #toJavaString}.
+ *
+ * <p>The allowable types and combinations are:
+ *
+ * <table>
+ * <caption>Allowable types for RexLiteral instances</caption>
+ * <tr>
+ * <th>TypeName</th>
+ * <th>Meaning</th>
+ * <th>Value type</th>
+ * </tr>
+ * <tr>
+ * <td>{@link SqlTypeName#NULL}</td>
+ * <td>The null value. It has its own special type.</td>
+ * <td>null</td>
+ * </tr>
+ * <tr>
+ * <td>{@link SqlTypeName#BOOLEAN}</td>
+ * <td>Boolean, namely <code>TRUE</code>, <code>FALSE</code> or <code>
+ * UNKNOWN</code>.</td>
+ * <td>{@link Boolean}, or null represents the UNKNOWN value</td>
+ * </tr>
+ * <tr>
+ * <td>{@link SqlTypeName#DECIMAL}</td>
+ * <td>Exact number, for example <code>0</code>, <code>-.5</code>, <code>
+ * 12345</code>.</td>
+ * <td>{@link BigDecimal}</td>
+ * </tr>
+ * <tr>
+ * <td>{@link SqlTypeName#DOUBLE}</td>
+ * <td>Approximate number, for example <code>6.023E-23</code>.</td>
+ * <td>{@link BigDecimal}</td>
+ * </tr>
+ * <tr>
+ * <td>{@link SqlTypeName#DATE}</td>
+ * <td>Date, for example <code>DATE '1969-04'29'</code></td>
+ * <td>{@link Calendar};
+ *     also {@link Calendar} (UTC time zone)
+ *     and {@link Integer} (days since POSIX epoch)</td>
+ * </tr>
+ * <tr>
+ * <td>{@link SqlTypeName#TIME}</td>
+ * <td>Time, for example <code>TIME '18:37:42.567'</code></td>
+ * <td>{@link Calendar};
+ *     also {@link Calendar} (UTC time zone)
+ *     and {@link Integer} (milliseconds since midnight)</td>
+ * </tr>
+ * <tr>
+ * <td>{@link SqlTypeName#TIMESTAMP}</td>
+ * <td>Timestamp, for example <code>TIMESTAMP '1969-04-29
+ * 18:37:42.567'</code></td>
+ * <td>{@link TimestampString};
+ *     also {@link Calendar} (UTC time zone)
+ *     and {@link Long} (milliseconds since POSIX epoch)</td>
+ * </tr>
+ * <tr>
+ * <td>{@link SqlTypeName#INTERVAL_DAY},
+ *     {@link SqlTypeName#INTERVAL_DAY_HOUR},
+ *     {@link SqlTypeName#INTERVAL_DAY_MINUTE},
+ *     {@link SqlTypeName#INTERVAL_DAY_SECOND},
+ *     {@link SqlTypeName#INTERVAL_HOUR},
+ *     {@link SqlTypeName#INTERVAL_HOUR_MINUTE},
+ *     {@link SqlTypeName#INTERVAL_HOUR_SECOND},
+ *     {@link SqlTypeName#INTERVAL_MINUTE},
+ *     {@link SqlTypeName#INTERVAL_MINUTE_SECOND},
+ *     {@link SqlTypeName#INTERVAL_SECOND}</td>
+ * <td>Interval, for example <code>INTERVAL '4:3:2' HOUR TO SECOND</code></td>
+ * <td>{@link BigDecimal};
+ *     also {@link Long} (milliseconds)</td>
+ * </tr>
+ * <tr>
+ * <td>{@link SqlTypeName#INTERVAL_YEAR},
+ *     {@link SqlTypeName#INTERVAL_YEAR_MONTH},
+ *     {@link SqlTypeName#INTERVAL_MONTH}</td>
+ * <td>Interval, for example <code>INTERVAL '2-3' YEAR TO MONTH</code></td>
+ * <td>{@link BigDecimal};
+ *     also {@link Integer} (months)</td>
+ * </tr>
+ * <tr>
+ * <td>{@link SqlTypeName#CHAR}</td>
+ * <td>Character constant, for example <code>'Hello, world!'</code>, <code>
+ * ''</code>, <code>_N'Bonjour'</code>, <code>_ISO-8859-1'It''s superman!'
+ * COLLATE SHIFT_JIS$ja_JP$2</code>. These are always CHAR, never VARCHAR.</td>
+ * <td>{@link NlsString};
+ *     also {@link String}</td>
+ * </tr>
+ * <tr>
+ * <td>{@link SqlTypeName#BINARY}</td>
+ * <td>Binary constant, for example <code>X'7F34'</code>. (The number of hexits
+ * must be even; see above.) These constants are always BINARY, never
+ * VARBINARY.</td>
+ * <td>{@link ByteBuffer};
+ *     also {@code byte[]}</td>
+ * </tr>
+ * <tr>
+ * <td>{@link SqlTypeName#SYMBOL}</td>
+ * <td>A symbol is a special type used to make parsing easier; it is not part of
+ * the SQL standard, and is not exposed to end-users. It is used to hold a flag,
+ * such as the LEADING flag in a call to the function <code>
+ * TRIM([LEADING|TRAILING|BOTH] chars FROM string)</code>.</td>
+ * <td>An enum class</td>
+ * </tr>
+ * </table>
+ *
+ * <p>Line 374 ~ 376: fix FLINK-20942, the file should be removed when upgrade to CALCITE 1.27.0
+ * release.
+ */
+public class RexLiteral extends RexNode {
+    // ~ Instance fields --------------------------------------------------------
+
+    /**
+     * The value of this literal. Must be consistent with its type, as per {@link
+     * #valueMatchesType}. For example, you can't store an {@link Integer} value here just because
+     * you feel like it -- all numbers are represented by a {@link BigDecimal}. But since this field
+     * is private, it doesn't really matter how the values are stored.
+     */
+    private final Comparable value;
+
+    /** The real type of this literal, as reported by {@link #getType}. */
+    private final RelDataType type;
+
+    // TODO jvs 26-May-2006:  Use SqlTypeFamily instead; it exists
+    // for exactly this purpose (to avoid the confusion which results
+    // from overloading SqlTypeName).
+    /**
+     * An indication of the broad type of this literal -- even if its type isn't a SQL type.
+     * Sometimes this will be different than the SQL type; for example, all exact numbers, including
+     * integers have typeName {@link SqlTypeName#DECIMAL}. See {@link #valueMatchesType} for the
+     * definitive story.
+     */
+    private final SqlTypeName typeName;
+
+    private static final ImmutableList<TimeUnit> TIME_UNITS =
+            ImmutableList.copyOf(TimeUnit.values());
+
+    // ~ Constructors -----------------------------------------------------------
+
+    /** Creates a <code>RexLiteral</code>. */
+    RexLiteral(Comparable value, RelDataType type, SqlTypeName typeName) {
+        this.value = value;
+        this.type = Objects.requireNonNull(type);
+        this.typeName = Objects.requireNonNull(typeName);
+        Preconditions.checkArgument(valueMatchesType(value, typeName, true));
+        Preconditions.checkArgument((value == null) == type.isNullable());
+        Preconditions.checkArgument(typeName != SqlTypeName.ANY);
+        this.digest = computeDigest(RexDigestIncludeType.OPTIONAL);
+    }
+
+    // ~ Methods ----------------------------------------------------------------
+
+    /**
+     * Returns a string which concisely describes the definition of this rex literal. Two literals
+     * are equivalent if and only if their digests are the same.
+     *
+     * <p>The digest does not contain the expression's identity, but does include the identity of
+     * children.
+     *
+     * <p>Technically speaking 1:INT differs from 1:FLOAT, so we need data type in the literal's
+     * digest, however we want to avoid extra verbosity of the {@link RelNode#getDigest()} for
+     * readability purposes, so we omit type info in certain cases. For instance, 1:INT becomes 1
+     * (INT is implied by default), however 1:BIGINT always holds the type
+     *
+     * <p>Here's a non-exhaustive list of the "well known cases":
+     *
+     * <ul>
+     *   <li>Hide "NOT NULL" for not null literals
+     *   <li>Hide INTEGER, BOOLEAN, SYMBOL, TIME(0), TIMESTAMP(0), DATE(0) types
+     *   <li>Hide collation when it matches IMPLICIT/COERCIBLE
+     *   <li>Hide charset when it matches default
+     *   <li>Hide CHAR(xx) when literal length is equal to the precision of the type. In other
+     *       words, use 'Bob' instead of 'Bob':CHAR(3)
+     *   <li>Hide BOOL for AND/OR arguments. In other words, AND(true, null) means null is BOOL.
+     *   <li>Hide types for literals in simple binary operations (e.g. +, -, *, /, comparison) when
+     *       type of the other argument is clear. See {@link RexCall#computeDigest(boolean)} For
+     *       instance: =(true. null) means null is BOOL. =($0, null) means the type of null matches
+     *       the type of $0.
+     * </ul>
+     *
+     * @param includeType whether the digest should include type or not
+     * @return digest
+     */
+    public final String computeDigest(RexDigestIncludeType includeType) {
+        if (includeType == RexDigestIncludeType.OPTIONAL) {
+            if (digest != null) {
+                // digest is initialized with OPTIONAL, so cached value matches for
+                // includeType=OPTIONAL as well
+                return digest;
+            }
+            // Compute we should include the type or not
+            includeType = digestIncludesType();
+        } else if (digest != null && includeType == digestIncludesType()) {
+            // The digest is always computed with includeType=OPTIONAL
+            // If it happened to omit the type, we want to optimize computeDigest(NO_TYPE) as well
+            // If the digest includes the type, we want to optimize computeDigest(ALWAYS)
+            return digest;
+        }
+
+        return toJavaString(value, typeName, type, includeType);
+    }
+
+    /**
+     * Returns true if {@link RexDigestIncludeType#OPTIONAL} digest would include data type.
+     *
+     * @see RexCall#computeDigest(boolean)
+     * @return true if {@link RexDigestIncludeType#OPTIONAL} digest would include data type
+     */
+    RexDigestIncludeType digestIncludesType() {
+        return shouldIncludeType(value, type);
+    }
+
+    /** Returns whether a value is appropriate for its type. (We have rules about these things!) */
+    public static boolean valueMatchesType(Comparable value, SqlTypeName typeName, boolean strict) {
+        if (value == null) {
+            return true;
+        }
+        switch (typeName) {
+            case BOOLEAN:
+                // Unlike SqlLiteral, we do not allow boolean null.
+                return value instanceof Boolean;
+            case NULL:
+                return false; // value should have been null
+            case INTEGER: // not allowed -- use Decimal
+            case TINYINT:
+            case SMALLINT:
+                if (strict) {
+                    throw Util.unexpected(typeName);
+                }
+                // fall through
+            case DECIMAL:
+            case DOUBLE:
+            case FLOAT:
+            case REAL:
+            case BIGINT:
+                return value instanceof BigDecimal;
+            case DATE:
+                return value instanceof DateString;
+            case TIME:
+                return value instanceof TimeString;
+            case TIME_WITH_LOCAL_TIME_ZONE:
+                return value instanceof TimeString;
+            case TIMESTAMP:
+                return value instanceof TimestampString;
+            case TIMESTAMP_WITH_LOCAL_TIME_ZONE:
+                return value instanceof TimestampString;
+            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:
+                // The value of a DAY-TIME interval (whatever the start and end units,
+                // even say HOUR TO MINUTE) is in milliseconds (perhaps fractional
+                // milliseconds). The value of a YEAR-MONTH interval is in months.
+                return value instanceof BigDecimal;
+            case VARBINARY: // not allowed -- use Binary
+                if (strict) {
+                    throw Util.unexpected(typeName);
+                }
+                // fall through
+            case BINARY:
+                return value instanceof ByteString;
+            case VARCHAR: // not allowed -- use Char
+                if (strict) {
+                    throw Util.unexpected(typeName);
+                }
+                // fall through
+            case CHAR:
+                // A SqlLiteral's charset and collation are optional; not so a
+                // RexLiteral.
+                return (value instanceof NlsString)
+                        && (((NlsString) value).getCharset() != null)
+                        && (((NlsString) value).getCollation() != null);
+            case SARG:
+                return value instanceof Sarg;
+            case SYMBOL:
+                return value instanceof Enum;
+            case ROW:
+            case MULTISET:
+                return value instanceof List;
+            case GEOMETRY:
+                return value instanceof Geometries.Geom;
+            case ANY:
+                // Literal of type ANY is not legal. "CAST(2 AS ANY)" remains
+                // an integer literal surrounded by a cast function.
+                return false;
+            default:
+                throw Util.unexpected(typeName);
+        }
+    }
+
+    /** Returns the strict literal type for a given type. */
+    public static SqlTypeName strictTypeName(RelDataType type) {
+        final SqlTypeName typeName = type.getSqlTypeName();
+        switch (typeName) {
+            case INTEGER:
+            case TINYINT:
+            case SMALLINT:
+                return SqlTypeName.DECIMAL;
+            case REAL:
+            case FLOAT:
+                return SqlTypeName.DOUBLE;
+            case VARBINARY:
+                return SqlTypeName.BINARY;
+            case VARCHAR:
+                return SqlTypeName.CHAR;
+            default:
+                return typeName;
+        }
+    }
+
+    private static String toJavaString(
+            Comparable value,
+            SqlTypeName typeName,
+            RelDataType type,
+            RexDigestIncludeType includeType) {
+        assert includeType != RexDigestIncludeType.OPTIONAL
+                : "toJavaString must not be called with includeType=OPTIONAL";
+        if (value == null) {
+            return includeType == RexDigestIncludeType.NO_TYPE
+                    ? "null"
+                    : "null:" + type.getFullTypeString();
+        }
+        StringBuilder sb = new StringBuilder();
+        appendAsJava(value, sb, typeName, type, false, includeType);
+
+        if (includeType != RexDigestIncludeType.NO_TYPE) {
+            sb.append(':');
+            final String fullTypeString = type.getFullTypeString();
+            if (!fullTypeString.endsWith("NOT NULL")) {
+                sb.append(fullTypeString);
+            } else {
+                // Trim " NOT NULL". Apparently, the literal is not null, so we just print the data
+                // type.
+                sb.append(fullTypeString, 0, fullTypeString.length() - 9);
+            }
+        }
+        return sb.toString();
+    }
+
+    /**
+     * Computes if data type can be omitted from the digset.
+     *
+     * <p>For instance, {@code 1:BIGINT} has to keep data type while {@code 1:INT} should be
+     * represented as just {@code 1}.
+     *
+     * <p>Implementation assumption: this method should be fast. In fact might call {@link
+     * NlsString#getValue()} which could decode the string, however we rely on the cache there.
+     *
+     * @see RexLiteral#computeDigest(RexDigestIncludeType)
+     * @param value value of the literal
+     * @param type type of the literal
+     * @return NO_TYPE when type can be omitted, ALWAYS otherwise
+     */
+    private static RexDigestIncludeType shouldIncludeType(Comparable value, RelDataType type) {
+        if (type.isNullable()) {
+            // This means "null literal", so we require a type for it
+            // There might be exceptions like AND(null, true) which are handled by
+            // RexCall#computeDigest
+            return RexDigestIncludeType.ALWAYS;
+        }
+        // The variable here simplifies debugging (one can set a breakpoint at return)
+        // final ensures we set the value in all the branches, and it ensures the value is set just
+        // once
+        final RexDigestIncludeType includeType;
+        if (type.getSqlTypeName() == SqlTypeName.BOOLEAN
+                || type.getSqlTypeName() == SqlTypeName.INTEGER
+                || type.getSqlTypeName() == SqlTypeName.SYMBOL) {
+            // We don't want false:BOOLEAN NOT NULL, so we don't print type information for
+            // non-nullable BOOLEAN and INTEGER
+            includeType = RexDigestIncludeType.NO_TYPE;
+        } else if (type.getSqlTypeName() == SqlTypeName.CHAR && value instanceof NlsString) {
+            NlsString nlsString = (NlsString) value;
+
+            // Ignore type information for 'Bar':CHAR(3)
+            if (((nlsString.getCharset() != null
+                                    && type.getCharset().equals(nlsString.getCharset()))
+                            || (nlsString.getCharset() == null
+                                    && SqlCollation.IMPLICIT
+                                            .getCharset()
+                                            .equals(type.getCharset())))
+                    && nlsString.getCollation().equals(type.getCollation())
+                    && ((NlsString) value).getValue().length() == type.getPrecision()) {
+                includeType = RexDigestIncludeType.NO_TYPE;
+            } else {
+                includeType = RexDigestIncludeType.ALWAYS;
+            }
+        } else if (type.getPrecision() == 0
+                && (type.getSqlTypeName() == SqlTypeName.TIME
+                        || type.getSqlTypeName() == SqlTypeName.TIMESTAMP
+                        || type.getSqlTypeName() == SqlTypeName.DATE)) {
+            // Ignore type information for '12:23:20':TIME(0)
+            // Note that '12:23:20':TIME WITH LOCAL TIME ZONE
+            includeType = RexDigestIncludeType.NO_TYPE;
+        } else {
+            includeType = RexDigestIncludeType.ALWAYS;
+        }
+        return includeType;
+    }
+
+    /**
+     * Returns whether a value is valid as a constant value, using the same criteria as {@link
+     * #valueMatchesType}.
+     */
+    public static boolean validConstant(Object o, Litmus litmus) {
+        if (o == null
+                || o instanceof BigDecimal
+                || o instanceof NlsString
+                || o instanceof ByteString
+                || o instanceof Boolean) {
+            return litmus.succeed();
+        } else if (o instanceof List) {
+            List list = (List) o;
+            for (Object o1 : list) {
+                if (!validConstant(o1, litmus)) {
+                    return litmus.fail("not a constant: {}", o1);
+                }
+            }
+            return litmus.succeed();
+        } else if (o instanceof Map) {
+            @SuppressWarnings("unchecked")
+            final Map<Object, Object> map = (Map) o;
+            for (Map.Entry entry : map.entrySet()) {
+                if (!validConstant(entry.getKey(), litmus)) {
+                    return litmus.fail("not a constant: {}", entry.getKey());
+                }
+                if (!validConstant(entry.getValue(), litmus)) {
+                    return litmus.fail("not a constant: {}", entry.getValue());
+                }
+            }
+            return litmus.succeed();
+        } else {
+            return litmus.fail("not a constant: {}", o);
+        }
+    }
+
+    /**
+     * Returns a list of the time units covered by an interval type such as HOUR TO SECOND. Adds
+     * MILLISECOND if the end is SECOND, to deal with fractional seconds.
+     */
+    private static List<TimeUnit> getTimeUnits(SqlTypeName typeName) {
+        final TimeUnit start = typeName.getStartUnit();
+        final TimeUnit end = typeName.getEndUnit();
+        final ImmutableList<TimeUnit> list = TIME_UNITS.subList(start.ordinal(), end.ordinal() + 1);
+        if (end == TimeUnit.SECOND) {
+            return CompositeList.of(list, ImmutableList.of(TimeUnit.MILLISECOND));
+        }
+        return list;
+    }
+
+    private String intervalString(BigDecimal v) {
+        final List<TimeUnit> timeUnits = getTimeUnits(type.getSqlTypeName());
+        final StringBuilder b = new StringBuilder();
+        for (TimeUnit timeUnit : timeUnits) {
+            final BigDecimal[] result = v.divideAndRemainder(timeUnit.multiplier);
+            if (b.length() > 0) {
+                b.append(timeUnit.separator);
+            }
+            final int width = b.length() == 0 ? -1 : width(timeUnit); // don't pad 1st
+            pad(b, result[0].toString(), width);
+            v = result[1];
+        }
+        if (Util.last(timeUnits) == TimeUnit.MILLISECOND) {
+            while (b.toString().matches(".*\\.[0-9]*0")) {
+                if (b.toString().endsWith(".0")) {
+                    b.setLength(b.length() - 2); // remove ".0"
+                } else {
+                    b.setLength(b.length() - 1); // remove "0"
+                }
+            }
+        }
+        return b.toString();
+    }
+
+    private static void pad(StringBuilder b, String s, int width) {
+        if (width >= 0) {
+            for (int i = s.length(); i < width; i++) {
+                b.append('0');
+            }
+        }
+        b.append(s);
+    }
+
+    private static int width(TimeUnit timeUnit) {
+        switch (timeUnit) {
+            case MILLISECOND:
+                return 3;
+            case HOUR:
+            case MINUTE:
+            case SECOND:
+                return 2;
+            default:
+                return -1;
+        }
+    }
+
+    /** Prints the value this literal as a Java string constant. */
+    public void printAsJava(PrintWriter pw) {
+        Util.asStringBuilder(
+                pw,
+                sb -> appendAsJava(value, sb, typeName, type, true, RexDigestIncludeType.NO_TYPE));
+    }
+
+    /**
+     * Appends the specified value in the provided destination as a Java string. The value must be
+     * consistent with the type, as per {@link #valueMatchesType}.
+     *
+     * <p>Typical return values:
+     *
+     * <ul>
+     *   <li>true
+     *   <li>null
+     *   <li>"Hello, world!"
+     *   <li>1.25
+     *   <li>1234ABCD
+     * </ul>
+     *
+     * @param value Value to be appended to the provided destination as a Java string
+     * @param sb Destination to which to append the specified value
+     * @param typeName Type name to be used for the transformation of the value to a Java string
+     * @param type Type to be used for the transformation of the value to a Java string
+     * @param includeType Whether to include the data type in the Java representation
+     */
+    private static void appendAsJava(
+            Comparable value,
+            StringBuilder sb,
+            SqlTypeName typeName,
+            RelDataType type,
+            boolean java,
+            RexDigestIncludeType includeType) {
+        switch (typeName) {
+            case CHAR:
+                NlsString nlsString = (NlsString) value;
+                if (java) {
+                    Util.printJavaString(sb, nlsString.getValue(), true);
+                } else {
+                    boolean includeCharset =
+                            (nlsString.getCharsetName() != null)
+                                    && !nlsString
+                                            .getCharsetName()
+                                            .equals(CalciteSystemProperty.DEFAULT_CHARSET.value());
+                    sb.append(nlsString.asSql(includeCharset, false));
+                }
+                break;
+            case BOOLEAN:
+                assert value instanceof Boolean;
+                sb.append(value.toString());
+                break;
+            case DECIMAL:
+                assert value instanceof BigDecimal;
+                sb.append(value.toString());
+                break;
+            case DOUBLE:
+                assert value instanceof BigDecimal;
+                sb.append(Util.toScientificNotation((BigDecimal) value));
+                break;
+            case BIGINT:
+                assert value instanceof BigDecimal;
+                long narrowLong = ((BigDecimal) value).longValue();
+                sb.append(String.valueOf(narrowLong));
+                sb.append('L');
+                break;
+            case BINARY:
+                assert value instanceof ByteString;
+                sb.append("X'");
+                sb.append(((ByteString) value).toString(16));
+                sb.append("'");
+                break;
+            case NULL:
+                assert value == null;
+                sb.append("null");
+                break;
+            case SARG:
+                assert value instanceof Sarg;
+                //noinspection unchecked,rawtypes
+                Util.asStringBuilder(sb, sb2 -> printSarg(sb2, (Sarg) value, type));
+                break;
+            case SYMBOL:
+                assert value instanceof Enum;
+                sb.append("FLAG(");
+                sb.append(value.toString());
+                sb.append(")");
+                break;
+            case DATE:
+                assert value instanceof DateString;
+                sb.append(value.toString());
+                break;
+            case TIME:
+            case TIME_WITH_LOCAL_TIME_ZONE:
+                assert value instanceof TimeString;
+                sb.append(value.toString());
+                break;
+            case TIMESTAMP:
+            case TIMESTAMP_WITH_LOCAL_TIME_ZONE:
+                assert value instanceof TimestampString;
+                sb.append(value.toString());
+                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:
+                assert value instanceof BigDecimal;
+                sb.append(value.toString());
+                break;
+            case MULTISET:
+            case ROW:
+                final List<RexLiteral> list = (List) value;
+                Util.asStringBuilder(
+                        sb,
+                        sb2 ->
+                                Util.printList(
+                                        sb,
+                                        list.size(),
+                                        (sb3, i) ->
+                                                sb3.append(
+                                                        list.get(i).computeDigest(includeType))));
+                break;
+            case GEOMETRY:
+                final String wkt = GeoFunctions.ST_AsWKT((Geometries.Geom) value);
+                sb.append(wkt);
+                break;
+            default:
+                assert valueMatchesType(value, typeName, true);
+                throw Util.needToImplement(typeName);
+        }
+    }
+
+    private static <C extends Comparable<C>> void printSarg(
+            StringBuilder sb, Sarg<C> sarg, RelDataType type) {
+        sarg.printTo(sb, (sb2, value) -> sb2.append(toLiteral(type, value)));
+    }
+
+    /**
+     * Converts a value to a temporary literal, for the purposes of generating a digest. Literals of
+     * type ROW and MULTISET require that their components are also literals.
+     */
+    private static RexLiteral toLiteral(RelDataType type, Comparable<?> value) {
+        final SqlTypeName typeName = strictTypeName(type);
+        switch (typeName) {
+            case ROW:
+                final List<Comparable<?>> fieldValues = (List) value;
+                final List<RelDataTypeField> fields = type.getFieldList();
+                final List<RexLiteral> fieldLiterals =
+                        FlatLists.of(
+                                Functions.generate(
+                                        fieldValues.size(),
+                                        i ->
+                                                toLiteral(
+                                                        fields.get(i).getType(),
+                                                        fieldValues.get(i))));
+                return new RexLiteral((Comparable) fieldLiterals, type, typeName);
+
+            case MULTISET:
+                final List<Comparable<?>> elementValues = (List) value;
+                final List<RexLiteral> elementLiterals =
+                        FlatLists.of(
+                                Functions.generate(
+                                        elementValues.size(),
+                                        i ->
+                                                toLiteral(
+                                                        type.getComponentType(),
+                                                        elementValues.get(i))));
+                return new RexLiteral((Comparable) elementLiterals, type, typeName);
+
+            default:
+                return new RexLiteral(value, type, typeName);
+        }
+    }
+
+    /**
+     * Converts a Jdbc string into a RexLiteral. This method accepts a string, as returned by the
+     * Jdbc method ResultSet.getString(), and restores the string into an equivalent RexLiteral. It
+     * allows one to use Jdbc strings as a common format for data.
+     *
+     * <p>If a null literal is provided, then a null pointer will be returned.
+     *
+     * @param type data type of literal to be read
+     * @param typeName type family of literal
+     * @param literal the (non-SQL encoded) string representation, as returned by the Jdbc call to
+     *     return a column as a string
+     * @return a typed RexLiteral, or null
+     */
+    public static RexLiteral fromJdbcString(
+            RelDataType type, SqlTypeName typeName, String literal) {
+        if (literal == null) {
+            return null;
+        }
+
+        switch (typeName) {
+            case CHAR:
+                Charset charset = type.getCharset();
+                SqlCollation collation = type.getCollation();
+                NlsString str = new NlsString(literal, charset.name(), collation);
+                return new RexLiteral(str, type, typeName);
+            case BOOLEAN:
+                boolean b = ConversionUtil.toBoolean(literal);
+                return new RexLiteral(b, type, typeName);
+            case DECIMAL:
+            case DOUBLE:
+                BigDecimal d = new BigDecimal(literal);
+                return new RexLiteral(d, type, typeName);
+            case BINARY:
+                byte[] bytes = ConversionUtil.toByteArrayFromString(literal, 16);
+                return new RexLiteral(new ByteString(bytes), type, typeName);
+            case NULL:
+                return new RexLiteral(null, type, typeName);
+            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:
+                long millis = SqlParserUtil.intervalToMillis(literal, type.getIntervalQualifier());
+                return new RexLiteral(BigDecimal.valueOf(millis), type, typeName);
+            case INTERVAL_YEAR:
+            case INTERVAL_YEAR_MONTH:
+            case INTERVAL_MONTH:
+                long months = SqlParserUtil.intervalToMonths(literal, type.getIntervalQualifier());
+                return new RexLiteral(BigDecimal.valueOf(months), type, typeName);
+            case DATE:
+            case TIME:
+            case TIMESTAMP:
+                String format = getCalendarFormat(typeName);
+                TimeZone tz = DateTimeUtils.UTC_ZONE;
+                final Comparable v;
+                switch (typeName) {
+                    case DATE:
+                        final Calendar cal =
+                                DateTimeUtils.parseDateFormat(
+                                        literal, new SimpleDateFormat(format, Locale.ROOT), tz);
+                        if (cal == null) {
+                            throw new AssertionError(
+                                    "fromJdbcString: invalid date/time value '" + literal + "'");
+                        }
+                        v = DateString.fromCalendarFields(cal);
+                        break;
+                    default:
+                        // Allow fractional seconds for times and timestamps
+                        assert format != null;
+                        final DateTimeUtils.PrecisionTime ts =
+                                DateTimeUtils.parsePrecisionDateTimeLiteral(
+                                        literal, new SimpleDateFormat(format, Locale.ROOT), tz, -1);
+                        if (ts == null) {
+                            throw new AssertionError(
+                                    "fromJdbcString: invalid date/time value '" + literal + "'");
+                        }
+                        switch (typeName) {
+                            case TIMESTAMP:
+                                v =
+                                        TimestampString.fromCalendarFields(ts.getCalendar())
+                                                .withFraction(ts.getFraction());
+                                break;
+                            case TIME:
+                                v =
+                                        TimeString.fromCalendarFields(ts.getCalendar())
+                                                .withFraction(ts.getFraction());
+                                break;
+                            default:
+                                throw new AssertionError();
+                        }
+                }
+                return new RexLiteral(v, type, typeName);
+
+            case SYMBOL:
+                // Symbols are for internal use
+            default:
+                throw new AssertionError("fromJdbcString: unsupported type");
+        }
+    }
+
+    private static String getCalendarFormat(SqlTypeName typeName) {
+        switch (typeName) {
+            case DATE:
+                return DateTimeUtils.DATE_FORMAT_STRING;
+            case TIME:
+                return DateTimeUtils.TIME_FORMAT_STRING;
+            case TIMESTAMP:
+                return DateTimeUtils.TIMESTAMP_FORMAT_STRING;
+            default:
+                throw new AssertionError("getCalendarFormat: unknown type");
+        }
+    }
+
+    public SqlTypeName getTypeName() {
+        return typeName;
+    }
+
+    public RelDataType getType() {
+        return type;
+    }
+
+    @Override
+    public SqlKind getKind() {
+        return SqlKind.LITERAL;
+    }
+
+    /** Returns whether this literal's value is null. */
+    public boolean isNull() {
+        return value == null;
+    }
+
+    /**
+     * Returns the value of this literal.
+     *
+     * <p>For backwards compatibility, returns DATE. TIME and TIMESTAMP as a {@link Calendar} value
+     * in UTC time zone.
+     */
+    public Comparable getValue() {
+        assert valueMatchesType(value, typeName, true) : value;
+        if (value == null) {
+            return null;
+        }
+        switch (typeName) {
+            case TIME:
+            case DATE:
+            case TIMESTAMP:
+                return getValueAs(Calendar.class);
+            default:
+                return value;
+        }
+    }
+
+    /**
+     * Returns the value of this literal, in the form that the calculator program builder wants it.
+     */
+    public Object getValue2() {
+        if (value == null) {
+            return null;
+        }
+        switch (typeName) {
+            case CHAR:
+                return getValueAs(String.class);
+            case DECIMAL:
+            case TIMESTAMP:
+            case TIMESTAMP_WITH_LOCAL_TIME_ZONE:
+                return getValueAs(Long.class);
+            case DATE:
+            case TIME:
+            case TIME_WITH_LOCAL_TIME_ZONE:
+                return getValueAs(Integer.class);
+            default:
+                return value;
+        }
+    }
+
+    /** Returns the value of this literal, in the form that the rex-to-lix translator wants it. */
+    public Object getValue3() {
+        if (value == null) {
+            return null;
+        }
+        switch (typeName) {
+            case DECIMAL:
+                assert value instanceof BigDecimal;
+                return value;
+            default:
+                return getValue2();
+        }
+    }
+
+    /** Returns the value of this literal, in the form that {@link RexInterpreter} wants it. */
+    public Comparable getValue4() {
+        if (value == null) {
+            return null;
+        }
+        switch (typeName) {
+            case TIMESTAMP:
+            case TIMESTAMP_WITH_LOCAL_TIME_ZONE:
+                return getValueAs(Long.class);
+            case DATE:
+            case TIME:
+            case TIME_WITH_LOCAL_TIME_ZONE:
+                return getValueAs(Integer.class);
+            default:
+                return value;
+        }
+    }
+
+    /**
+     * Returns the value of this literal as an instance of the specified class.
+     *
+     * <p>The following SQL types allow more than one form:
+     *
+     * <ul>
+     *   <li>CHAR as {@link NlsString} or {@link String}
+     *   <li>TIME as {@link TimeString}, {@link Integer} (milliseconds since midnight), {@link
+     *       Calendar} (in UTC)
+     *   <li>DATE as {@link DateString}, {@link Integer} (days since 1970-01-01), {@link Calendar}
+     *   <li>TIMESTAMP as {@link TimestampString}, {@link Long} (milliseconds since 1970-01-01
+     *       00:00:00), {@link Calendar}
+     *   <li>DECIMAL as {@link BigDecimal} or {@link Long}
+     * </ul>
+     *
+     * <p>Called with {@code clazz} = {@link Comparable}, returns the value in its native form.
+     *
+     * @param clazz Desired return type
+     * @param <T> Return type
+     * @return Value of this literal in the desired type
+     */
+    public <T> T getValueAs(Class<T> clazz) {
+        if (value == null || clazz.isInstance(value)) {
+            return clazz.cast(value);
+        }
+        switch (typeName) {
+            case BINARY:
+                if (clazz == byte[].class) {
+                    return clazz.cast(((ByteString) value).getBytes());
+                }
+                break;
+            case CHAR:
+                if (clazz == String.class) {
+                    return clazz.cast(((NlsString) value).getValue());
+                } else if (clazz == Character.class) {
+                    return clazz.cast(((NlsString) value).getValue().charAt(0));
+                }
+                break;
+            case VARCHAR:
+                if (clazz == String.class) {
+                    return clazz.cast(((NlsString) value).getValue());
+                }
+                break;
+            case DECIMAL:
+                if (clazz == Long.class) {
+                    return clazz.cast(((BigDecimal) value).unscaledValue().longValue());
+                }
+                // fall through
+            case BIGINT:
+            case INTEGER:
+            case SMALLINT:
+            case TINYINT:
+            case DOUBLE:
+            case REAL:
+            case FLOAT:
+                if (clazz == Long.class) {
+                    return clazz.cast(((BigDecimal) value).longValue());
+                } else if (clazz == Integer.class) {
+                    return clazz.cast(((BigDecimal) value).intValue());
+                } else if (clazz == Short.class) {
+                    return clazz.cast(((BigDecimal) value).shortValue());
+                } else if (clazz == Byte.class) {
+                    return clazz.cast(((BigDecimal) value).byteValue());
+                } else if (clazz == Double.class) {
+                    return clazz.cast(((BigDecimal) value).doubleValue());
+                } else if (clazz == Float.class) {
+                    return clazz.cast(((BigDecimal) value).floatValue());
+                }
+                break;
+            case DATE:
+                if (clazz == Integer.class) {
+                    return clazz.cast(((DateString) value).getDaysSinceEpoch());
+                } else if (clazz == Calendar.class) {
+                    return clazz.cast(((DateString) value).toCalendar());
+                }
+                break;
+            case TIME:
+                if (clazz == Integer.class) {
+                    return clazz.cast(((TimeString) value).getMillisOfDay());
+                } else if (clazz == Calendar.class) {
+                    // Note: Nanos are ignored
+                    return clazz.cast(((TimeString) value).toCalendar());
+                }
+                break;
+            case TIME_WITH_LOCAL_TIME_ZONE:
+                if (clazz == Integer.class) {
+                    // Milliseconds since 1970-01-01 00:00:00
+                    return clazz.cast(((TimeString) value).getMillisOfDay());
+                }
+                break;
+            case TIMESTAMP:
+                if (clazz == Long.class) {
+                    // Milliseconds since 1970-01-01 00:00:00
+                    return clazz.cast(((TimestampString) value).getMillisSinceEpoch());
+                } else if (clazz == Calendar.class) {
+                    // Note: Nanos are ignored
+                    return clazz.cast(((TimestampString) value).toCalendar());
+                }
+                break;
+            case TIMESTAMP_WITH_LOCAL_TIME_ZONE:
+                if (clazz == Long.class) {
+                    // Milliseconds since 1970-01-01 00:00:00
+                    return clazz.cast(((TimestampString) value).getMillisSinceEpoch());
+                } else if (clazz == Calendar.class) {
+                    // Note: Nanos are ignored
+                    return clazz.cast(((TimestampString) value).toCalendar());
+                }
+                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:
+                if (clazz == Integer.class) {
+                    return clazz.cast(((BigDecimal) value).intValue());
+                } else if (clazz == Long.class) {
+                    return clazz.cast(((BigDecimal) value).longValue());
+                } else if (clazz == String.class) {
+                    return clazz.cast(intervalString(getValueAs(BigDecimal.class).abs()));
+                } else if (clazz == Boolean.class) {
+                    // return whether negative
+                    return clazz.cast(getValueAs(BigDecimal.class).signum() < 0);
+                }
+                break;
+        }
+        throw new AssertionError("cannot convert " + typeName + " literal to " + clazz);
+    }
+
+    public static boolean booleanValue(RexNode node) {
+        return (Boolean) ((RexLiteral) node).value;
+    }
+
+    public boolean isAlwaysTrue() {
+        if (typeName != SqlTypeName.BOOLEAN) {
+            return false;
+        }
+        return booleanValue(this);
+    }
+
+    public boolean isAlwaysFalse() {
+        if (typeName != SqlTypeName.BOOLEAN) {
+            return false;
+        }
+        return !booleanValue(this);
+    }
+
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        return (obj instanceof RexLiteral)
+                && equals(((RexLiteral) obj).value, value)
+                && equals(((RexLiteral) obj).type, type);
+    }
+
+    public int hashCode() {
+        return Objects.hash(value, type);
+    }
+
+    public static Comparable value(RexNode node) {
+        return findValue(node);
+    }
+
+    public static int intValue(RexNode node) {
+        final Comparable value = findValue(node);
+        return ((Number) value).intValue();
+    }
+
+    public static String stringValue(RexNode node) {
+        final Comparable value = findValue(node);
+        return (value == null) ? null : ((NlsString) value).getValue();
+    }
+
+    private static Comparable findValue(RexNode node) {
+        if (node instanceof RexLiteral) {
+            return ((RexLiteral) node).value;
+        }
+        if (node instanceof RexCall) {
+            final RexCall call = (RexCall) node;
+            final SqlOperator operator = call.getOperator();
+            if (operator == SqlStdOperatorTable.CAST) {
+                return findValue(call.getOperands().get(0));
+            }
+            if (operator == SqlStdOperatorTable.UNARY_MINUS) {
+                final BigDecimal value = (BigDecimal) findValue(call.getOperands().get(0));
+                return value.negate();
+            }
+        }
+        throw new AssertionError("not a literal: " + node);
+    }
+
+    public static boolean isNullLiteral(RexNode node) {
+        return (node instanceof RexLiteral) && (((RexLiteral) node).value == null);
+    }
+
+    private static boolean equals(Object o1, Object o2) {
+        return Objects.equals(o1, o2);
+    }
+
+    public <R> R accept(RexVisitor<R> visitor) {
+        return visitor.visitLiteral(this);
+    }
+
+    public <R, P> R accept(RexBiVisitor<R, P> visitor, P arg) {
+        return visitor.visitLiteral(this, arg);
+    }
+}