| /* |
| * 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 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.SpatialTypeFunctions; |
| 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.TimeWithTimeZoneString; |
| import org.apache.calcite.util.TimestampString; |
| import org.apache.calcite.util.TimestampWithTimeZoneString; |
| import org.apache.calcite.util.Util; |
| |
| import com.google.common.collect.ImmutableList; |
| |
| import org.checkerframework.checker.initialization.qual.UnknownInitialization; |
| import org.checkerframework.checker.nullness.qual.Nullable; |
| import org.checkerframework.checker.nullness.qual.PolyNull; |
| import org.checkerframework.checker.nullness.qual.RequiresNonNull; |
| import org.checkerframework.dataflow.qual.Pure; |
| import org.locationtech.jts.geom.Geometry; |
| |
| 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; |
| |
| import static com.google.common.base.Preconditions.checkArgument; |
| |
| import static org.apache.calcite.linq4j.Nullness.castNonNull; |
| import static org.apache.calcite.rel.type.RelDataTypeImpl.NON_NULLABLE_SUFFIX; |
| |
| import static java.util.Objects.requireNonNull; |
| |
| /** |
| * 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> |
| */ |
| 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 @Nullable 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( |
| @Nullable Comparable value, |
| RelDataType type, |
| SqlTypeName typeName) { |
| this.value = value; |
| this.type = requireNonNull(type, "type"); |
| this.typeName = requireNonNull(typeName, "typeName"); |
| checkArgument(valueMatchesType(value, typeName, true)); |
| checkArgument((value == null) == type.isNullable()); |
| 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 |
| */ |
| @RequiresNonNull({"typeName", "type"}) |
| public final String computeDigest( |
| @UnknownInitialization RexLiteral this, |
| 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 |
| */ |
| @RequiresNonNull("type") |
| RexDigestIncludeType digestIncludesType( |
| @UnknownInitialization RexLiteral this) { |
| return shouldIncludeType(value, type); |
| } |
| |
| /** Returns whether a value is appropriate for its type. (We have rules about |
| * these things!) */ |
| public static boolean valueMatchesType( |
| @Nullable 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: |
| case TIME_WITH_LOCAL_TIME_ZONE: |
| return value instanceof TimeString; |
| case TIME_TZ: |
| return value instanceof TimeWithTimeZoneString; |
| case TIMESTAMP: |
| case TIMESTAMP_WITH_LOCAL_TIME_ZONE: |
| return value instanceof TimestampString; |
| case TIMESTAMP_TZ: |
| return value instanceof TimestampWithTimeZoneString; |
| 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 Geometry; |
| 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. The rules should keep |
| * sync with what {@link RexBuilder#makeLiteral} defines. |
| */ |
| 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: |
| case DOUBLE: |
| return SqlTypeName.DOUBLE; |
| case VARBINARY: |
| return SqlTypeName.BINARY; |
| case VARCHAR: |
| return SqlTypeName.CHAR; |
| default: |
| return typeName; |
| } |
| } |
| |
| private static String toJavaString( |
| @Nullable 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(NON_NULLABLE_SUFFIX)) { |
| 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() - NON_NULLABLE_SUFFIX.length()); |
| } |
| } |
| return sb.toString(); |
| } |
| |
| /** |
| * Computes if data type can be omitted from the digest. |
| * |
| * <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(@Nullable 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 |
| && Objects.equals(type.getCharset(), nlsString.getCharset())) |
| || (nlsString.getCharset() == null |
| && Objects.equals(SqlCollation.IMPLICIT.getCharset(), type.getCharset()))) |
| && Objects.equals(nlsString.getCollation(), 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(@Nullable 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> |
| * <li>null</li> |
| * <li>"Hello, world!"</li> |
| * <li>1.25</li> |
| * <li>1234ABCD</li> |
| * </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(@Nullable Comparable value, StringBuilder sb, |
| SqlTypeName typeName, RelDataType type, boolean java, |
| RexDigestIncludeType includeType) { |
| switch (typeName) { |
| case CHAR: |
| NlsString nlsString = (NlsString) castNonNull(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: |
| case FLOAT: |
| 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 TIME_TZ: |
| assert value instanceof TimeWithTimeZoneString; |
| sb.append(value); |
| break; |
| case TIMESTAMP: |
| case TIMESTAMP_WITH_LOCAL_TIME_ZONE: |
| assert value instanceof TimestampString; |
| sb.append(value.toString()); |
| break; |
| case TIMESTAMP_TZ: |
| assert value instanceof TimestampWithTimeZoneString; |
| sb.append(value); |
| 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: |
| assert value instanceof List : "value must implement List: " + value; |
| @SuppressWarnings("unchecked") final List<RexLiteral> list = |
| (List<RexLiteral>) castNonNull(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 = SpatialTypeFunctions.ST_AsWKT((Geometry) castNonNull(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: |
| assert value instanceof List : "value must implement List: " + value; |
| 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: |
| assert value instanceof List : "value must implement List: " + value; |
| final List<Comparable<?>> elementValues = (List) value; |
| final List<RexLiteral> elementLiterals = |
| FlatLists.of( |
| Functions.generate(elementValues.size(), i -> |
| toLiteral(castNonNull(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>Returns null if and only if {@code literal} is null. |
| * |
| * @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 @PolyNull RexLiteral fromJdbcString( |
| RelDataType type, |
| SqlTypeName typeName, |
| @PolyNull String literal) { |
| if (literal == null) { |
| return null; |
| } |
| |
| switch (typeName) { |
| case CHAR: |
| Charset charset = requireNonNull(type.getCharset(), () -> "charset for " + type); |
| 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: |
| case REAL: |
| case FLOAT: |
| 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, |
| castNonNull(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, |
| castNonNull(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; |
| } |
| |
| @Override 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. |
| */ |
| @Pure |
| public @Nullable 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 @Nullable 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: |
| case TIMESTAMP_TZ: |
| return getValueAs(Long.class); |
| case DATE: |
| case TIME: |
| case TIME_WITH_LOCAL_TIME_ZONE: |
| case TIME_TZ: |
| 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 @Nullable 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 @Nullable 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> @Nullable 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 TIME_TZ: |
| if (clazz == Integer.class) { |
| return clazz.cast(((TimeWithTimeZoneString) value).getLocalTimeString().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_TZ: |
| if (clazz == Long.class) { |
| return clazz.cast(((TimestampWithTimeZoneString) value) |
| .getLocalTimestampString() |
| .getMillisSinceEpoch()); |
| } else if (clazz == Calendar.class) { |
| TimestampWithTimeZoneString ts = (TimestampWithTimeZoneString) value; |
| return clazz.cast(ts.getLocalTimestampString().toCalendar(ts.getTimeZone())); |
| } |
| 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(castNonNull(getValueAs(BigDecimal.class)).abs())); |
| } else if (clazz == Boolean.class) { |
| // return whether negative |
| return clazz.cast(castNonNull(getValueAs(BigDecimal.class)).signum() < 0); |
| } |
| break; |
| default: |
| break; |
| } |
| throw new AssertionError("cannot convert " + typeName |
| + " literal to " + clazz); |
| } |
| |
| public static boolean booleanValue(RexNode node) { |
| return (Boolean) castNonNull(((RexLiteral) node).value); |
| } |
| |
| @Override public boolean isAlwaysTrue() { |
| if (typeName != SqlTypeName.BOOLEAN) { |
| return false; |
| } |
| return booleanValue(this); |
| } |
| |
| @Override public boolean isAlwaysFalse() { |
| if (typeName != SqlTypeName.BOOLEAN) { |
| return false; |
| } |
| return !booleanValue(this); |
| } |
| |
| @Override public boolean equals(@Nullable Object obj) { |
| if (this == obj) { |
| return true; |
| } |
| return (obj instanceof RexLiteral) |
| && Objects.equals(((RexLiteral) obj).value, value) |
| && Objects.equals(((RexLiteral) obj).type, type); |
| } |
| |
| @Override public int hashCode() { |
| return Objects.hash(value, type); |
| } |
| |
| public static @Nullable Comparable value(RexNode node) { |
| return findValue(node); |
| } |
| |
| public static int intValue(RexNode node) { |
| final Comparable value = castNonNull(findValue(node)); |
| return ((Number) value).intValue(); |
| } |
| |
| public static @Nullable String stringValue(RexNode node) { |
| final Comparable value = findValue(node); |
| return (value == null) ? null : ((NlsString) value).getValue(); |
| } |
| |
| private static @Nullable 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 requireNonNull(value, () -> "can't negate null in " + node).negate(); |
| } |
| } |
| throw new AssertionError("not a literal: " + node); |
| } |
| |
| public static boolean isNullLiteral(RexNode node) { |
| return (node instanceof RexLiteral) |
| && (((RexLiteral) node).value == null); |
| } |
| |
| @Override public <R> R accept(RexVisitor<R> visitor) { |
| return visitor.visitLiteral(this); |
| } |
| |
| @Override public <R, P> R accept(RexBiVisitor<R, P> visitor, P arg) { |
| return visitor.visitLiteral(this, arg); |
| } |
| } |