org.apache.calcite.rex.RexLiteral Maven / Gradle / Ivy
Show all versions of calcite-core Show documentation
/*
* 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.rel.RelNode;
import org.apache.calcite.rel.type.RelDataType;
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.TimeString;
import org.apache.calcite.util.TimestampString;
import org.apache.calcite.util.Util;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import java.io.IOException;
import java.io.PrintWriter;
import java.math.BigDecimal;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.text.SimpleDateFormat;
import java.util.AbstractList;
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.
*
* There are several methods for creating literals in {@link RexBuilder}:
* {@link RexBuilder#makeLiteral(boolean)} and so forth.
*
* 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}.
*
* The allowable types and combinations are:
*
*
* Allowable types for RexLiteral instances
*
* TypeName
* Meaning
* Value type
*
*
* {@link SqlTypeName#NULL}
* The null value. It has its own special type.
* null
*
*
* {@link SqlTypeName#BOOLEAN}
* Boolean, namely TRUE
, FALSE
or
* UNKNOWN
.
* {@link Boolean}, or null represents the UNKNOWN value
*
*
* {@link SqlTypeName#DECIMAL}
* Exact number, for example 0
, -.5
,
* 12345
.
* {@link BigDecimal}
*
*
* {@link SqlTypeName#DOUBLE}
* Approximate number, for example 6.023E-23
.
* {@link BigDecimal}
*
*
* {@link SqlTypeName#DATE}
* Date, for example DATE '1969-04'29'
* {@link Calendar};
* also {@link Calendar} (UTC time zone)
* and {@link Integer} (days since POSIX epoch)
*
*
* {@link SqlTypeName#TIME}
* Time, for example TIME '18:37:42.567'
* {@link Calendar};
* also {@link Calendar} (UTC time zone)
* and {@link Integer} (milliseconds since midnight)
*
*
* {@link SqlTypeName#TIMESTAMP}
* Timestamp, for example TIMESTAMP '1969-04-29
* 18:37:42.567'
* {@link TimestampString};
* also {@link Calendar} (UTC time zone)
* and {@link Long} (milliseconds since POSIX epoch)
*
*
* {@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}
* Interval, for example INTERVAL '4:3:2' HOUR TO SECOND
* {@link BigDecimal};
* also {@link Long} (milliseconds)
*
*
* {@link SqlTypeName#INTERVAL_YEAR},
* {@link SqlTypeName#INTERVAL_YEAR_MONTH},
* {@link SqlTypeName#INTERVAL_MONTH}
* Interval, for example INTERVAL '2-3' YEAR TO MONTH
* {@link BigDecimal};
* also {@link Integer} (months)
*
*
* {@link SqlTypeName#CHAR}
* Character constant, for example 'Hello, world!'
,
* ''
, _N'Bonjour'
, _ISO-8859-1'It''s superman!'
* COLLATE SHIFT_JIS$ja_JP$2
. These are always CHAR, never VARCHAR.
* {@link NlsString};
* also {@link String}
*
*
* {@link SqlTypeName#BINARY}
* Binary constant, for example X'7F34'
. (The number of hexits
* must be even; see above.) These constants are always BINARY, never
* VARBINARY.
* {@link ByteBuffer};
* also {@code byte[]}
*
*
* {@link SqlTypeName#SYMBOL}
* 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
* TRIM([LEADING|TRAILING|BOTH] chars FROM string)
.
* An enum class
*
*
*/
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 TIME_UNITS =
ImmutableList.copyOf(TimeUnit.values());
//~ Constructors -----------------------------------------------------------
/**
* Creates a RexLiteral
.
*/
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);
}
//~ 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.
*
* The digest does not contain the expression's identity, but does include the identity
* of children.
*
*
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
*
*
Here's a non-exhaustive list of the "well known cases":
*
- Hide "NOT NULL" for not null literals
*
- Hide INTEGER, BOOLEAN, SYMBOL, TIME(0), TIMESTAMP(0), DATE(0) types
*
- Hide collation when it matches IMPLICIT/COERCIBLE
*
- Hide charset when it matches default
*
- Hide CHAR(xx) when literal length is equal to the precision of the type.
* In other words, use 'Bob' instead of 'Bob':CHAR(3)
*
- Hide BOOL for AND/OR arguments. In other words, AND(true, null) means
* null is BOOL.
*
- 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.
*
*
* @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);
}
@Override public String toString() {
if (digest == null) {
digest = computeDigest(RexDigestIncludeType.OPTIONAL);
}
return digest;
}
/**
* 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);
}
/**
* @return whether 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 SYMBOL:
return value instanceof Enum;
case ROW:
case MULTISET:
return value instanceof List;
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);
}
}
private static String toJavaString(
Comparable value,
SqlTypeName typeName, RelDataType type,
RexDigestIncludeType includeType) {
assert includeType != RexDigestIncludeType.OPTIONAL
: "toJavaString must not be called with includeType=OPTIONAL";
String fullTypeString = type.getFullTypeString();
if (value == null) {
return includeType == RexDigestIncludeType.NO_TYPE ? "null" : "null:" + fullTypeString;
}
StringBuilder sb = new StringBuilder();
appendAsJava(value, sb, typeName, false, includeType);
if (includeType != RexDigestIncludeType.NO_TYPE) {
sb.append(':');
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.
* For instance, {@code 1:BIGINT} has to keep data type while {@code 1:INT}
* should be represented as just {@code 1}.
*
*
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) {
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