| /* |
| * 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.Spaces; |
| import org.apache.calcite.avatica.util.TimeUnit; |
| import org.apache.calcite.rel.RelNode; |
| import org.apache.calcite.rel.core.AggregateCall; |
| import org.apache.calcite.rel.core.CorrelationId; |
| import org.apache.calcite.rel.type.RelDataType; |
| import org.apache.calcite.rel.type.RelDataTypeFactory; |
| import org.apache.calcite.rel.type.RelDataTypeField; |
| import org.apache.calcite.runtime.FlatLists; |
| import org.apache.calcite.runtime.Geometries; |
| import org.apache.calcite.sql.SqlAggFunction; |
| import org.apache.calcite.sql.SqlCollation; |
| import org.apache.calcite.sql.SqlIntervalQualifier; |
| import org.apache.calcite.sql.SqlKind; |
| import org.apache.calcite.sql.SqlOperator; |
| import org.apache.calcite.sql.SqlSpecialOperator; |
| import org.apache.calcite.sql.SqlUtil; |
| import org.apache.calcite.sql.fun.SqlCountAggFunction; |
| import org.apache.calcite.sql.fun.SqlStdOperatorTable; |
| import org.apache.calcite.sql.type.ArraySqlType; |
| import org.apache.calcite.sql.type.MapSqlType; |
| import org.apache.calcite.sql.type.MultisetSqlType; |
| import org.apache.calcite.sql.type.SqlTypeFamily; |
| import org.apache.calcite.sql.type.SqlTypeName; |
| import org.apache.calcite.sql.type.SqlTypeUtil; |
| import org.apache.calcite.util.DateString; |
| import org.apache.calcite.util.NlsString; |
| import org.apache.calcite.util.Pair; |
| 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 com.google.common.base.Preconditions; |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.ImmutableRangeSet; |
| import com.google.common.collect.Range; |
| import com.google.common.collect.RangeSet; |
| import com.google.common.collect.TreeRangeSet; |
| |
| import org.checkerframework.checker.nullness.qual.Nullable; |
| import org.checkerframework.checker.nullness.qual.PolyNull; |
| |
| import java.math.BigDecimal; |
| import java.math.MathContext; |
| import java.math.RoundingMode; |
| import java.nio.charset.Charset; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Calendar; |
| import java.util.List; |
| import java.util.Locale; |
| import java.util.Map; |
| import java.util.Objects; |
| import java.util.function.IntPredicate; |
| |
| import static org.apache.calcite.linq4j.Nullness.castNonNull; |
| |
| /** |
| * Factory for row expressions. |
| * |
| * <p>Some common literal values (NULL, TRUE, FALSE, 0, 1, '') are cached.</p> |
| */ |
| public class RexBuilder { |
| /** |
| * Special operator that accesses an unadvertised field of an input record. |
| * This operator cannot be used in SQL queries; it is introduced temporarily |
| * during sql-to-rel translation, then replaced during the process that |
| * trims unwanted fields. |
| */ |
| public static final SqlSpecialOperator GET_OPERATOR = |
| new SqlSpecialOperator("_get", SqlKind.OTHER_FUNCTION); |
| |
| /** The smallest valid {@code int} value, as a {@link BigDecimal}. */ |
| private static final BigDecimal INT_MIN = |
| BigDecimal.valueOf(Integer.MIN_VALUE); |
| |
| /** The largest valid {@code int} value, as a {@link BigDecimal}. */ |
| private static final BigDecimal INT_MAX = |
| BigDecimal.valueOf(Integer.MAX_VALUE); |
| |
| //~ Instance fields -------------------------------------------------------- |
| |
| protected final RelDataTypeFactory typeFactory; |
| private final RexLiteral booleanTrue; |
| private final RexLiteral booleanFalse; |
| private final RexLiteral charEmpty; |
| private final RexLiteral constantNull; |
| private final SqlStdOperatorTable opTab = SqlStdOperatorTable.instance(); |
| |
| //~ Constructors ----------------------------------------------------------- |
| |
| /** |
| * Creates a RexBuilder. |
| * |
| * @param typeFactory Type factory |
| */ |
| @SuppressWarnings("method.invocation.invalid") |
| public RexBuilder(RelDataTypeFactory typeFactory) { |
| this.typeFactory = typeFactory; |
| this.booleanTrue = |
| makeLiteral( |
| Boolean.TRUE, |
| typeFactory.createSqlType(SqlTypeName.BOOLEAN), |
| SqlTypeName.BOOLEAN); |
| this.booleanFalse = |
| makeLiteral( |
| Boolean.FALSE, |
| typeFactory.createSqlType(SqlTypeName.BOOLEAN), |
| SqlTypeName.BOOLEAN); |
| this.charEmpty = |
| makeLiteral( |
| new NlsString("", null, null), |
| typeFactory.createSqlType(SqlTypeName.CHAR, 0), |
| SqlTypeName.CHAR); |
| this.constantNull = |
| makeLiteral( |
| null, |
| typeFactory.createSqlType(SqlTypeName.NULL), |
| SqlTypeName.NULL); |
| } |
| |
| /** Creates a list of {@link org.apache.calcite.rex.RexInputRef} expressions, |
| * projecting the fields of a given record type. */ |
| public List<RexNode> identityProjects(final RelDataType rowType) { |
| return Util.transform(rowType.getFieldList(), |
| input -> new RexInputRef(input.getIndex(), input.getType())); |
| } |
| |
| //~ Methods ---------------------------------------------------------------- |
| |
| /** |
| * Returns this RexBuilder's type factory. |
| * |
| * @return type factory |
| */ |
| public RelDataTypeFactory getTypeFactory() { |
| return typeFactory; |
| } |
| |
| /** |
| * Returns this RexBuilder's operator table. |
| * |
| * @return operator table |
| */ |
| public SqlStdOperatorTable getOpTab() { |
| return opTab; |
| } |
| |
| /** |
| * Creates an expression accessing a given named field from a record. |
| * |
| * <p>NOTE: Be careful choosing the value of {@code caseSensitive}. |
| * If the field name was supplied by an end-user (e.g. as a column alias in |
| * SQL), use your session's case-sensitivity setting. |
| * Only hard-code {@code true} if you are sure that the field name is |
| * internally generated. |
| * Hard-coding {@code false} is almost certainly wrong.</p> |
| * |
| * @param expr Expression yielding a record |
| * @param fieldName Name of field in record |
| * @param caseSensitive Whether match is case-sensitive |
| * @return Expression accessing a given named field |
| */ |
| public RexNode makeFieldAccess(RexNode expr, String fieldName, |
| boolean caseSensitive) { |
| final RelDataType type = expr.getType(); |
| final RelDataTypeField field = |
| type.getField(fieldName, caseSensitive, false); |
| if (field == null) { |
| throw new AssertionError("Type '" + type + "' has no field '" |
| + fieldName + "'"); |
| } |
| return makeFieldAccessInternal(expr, field); |
| } |
| |
| /** |
| * Creates an expression accessing a field with a given ordinal from a |
| * record. |
| * |
| * @param expr Expression yielding a record |
| * @param i Ordinal of field |
| * @return Expression accessing given field |
| */ |
| public RexNode makeFieldAccess( |
| RexNode expr, |
| int i) { |
| final RelDataType type = expr.getType(); |
| final List<RelDataTypeField> fields = type.getFieldList(); |
| if ((i < 0) || (i >= fields.size())) { |
| throw new AssertionError("Field ordinal " + i + " is invalid for " |
| + " type '" + type + "'"); |
| } |
| return makeFieldAccessInternal(expr, fields.get(i)); |
| } |
| |
| /** |
| * Creates an expression accessing a given field from a record. |
| * |
| * @param expr Expression yielding a record |
| * @param field Field |
| * @return Expression accessing given field |
| */ |
| private RexNode makeFieldAccessInternal( |
| RexNode expr, |
| final RelDataTypeField field) { |
| if (expr instanceof RexRangeRef) { |
| RexRangeRef range = (RexRangeRef) expr; |
| if (field.getIndex() < 0) { |
| return makeCall( |
| field.getType(), |
| GET_OPERATOR, |
| ImmutableList.of( |
| expr, |
| makeLiteral(field.getName()))); |
| } |
| return new RexInputRef( |
| range.getOffset() + field.getIndex(), |
| field.getType()); |
| } |
| return new RexFieldAccess(expr, field); |
| } |
| |
| /** |
| * Creates a call with a list of arguments and a predetermined type. |
| */ |
| public RexNode makeCall( |
| RelDataType returnType, |
| SqlOperator op, |
| List<RexNode> exprs) { |
| return new RexCall(returnType, op, exprs); |
| } |
| |
| /** |
| * Creates a call with an array of arguments. |
| * |
| * <p>If you already know the return type of the call, then |
| * {@link #makeCall(org.apache.calcite.rel.type.RelDataType, org.apache.calcite.sql.SqlOperator, java.util.List)} |
| * is preferred.</p> |
| */ |
| public RexNode makeCall( |
| SqlOperator op, |
| List<? extends RexNode> exprs) { |
| final RelDataType type = deriveReturnType(op, exprs); |
| return new RexCall(type, op, exprs); |
| } |
| |
| /** |
| * Creates a call with a list of arguments. |
| * |
| * <p>Equivalent to |
| * <code>makeCall(op, exprList.toArray(new RexNode[exprList.size()]))</code>. |
| */ |
| public final RexNode makeCall( |
| SqlOperator op, |
| RexNode... exprs) { |
| return makeCall(op, ImmutableList.copyOf(exprs)); |
| } |
| |
| /** |
| * Derives the return type of a call to an operator. |
| * |
| * @param op the operator being called |
| * @param exprs actual operands |
| * @return derived type |
| */ |
| public RelDataType deriveReturnType( |
| SqlOperator op, |
| List<? extends RexNode> exprs) { |
| return op.inferReturnType( |
| new RexCallBinding(typeFactory, op, exprs, |
| ImmutableList.of())); |
| } |
| |
| /** |
| * Creates a reference to an aggregate call, checking for repeated calls. |
| * |
| * <p>Argument types help to optimize for repeated aggregates. |
| * For instance count(42) is equivalent to count(*).</p> |
| * |
| * @param aggCall aggregate call to be added |
| * @param groupCount number of groups in the aggregate relation |
| * @param aggCalls destination list of aggregate calls |
| * @param aggCallMapping the dictionary of already added calls |
| * @param isNullable Whether input field i is nullable |
| * |
| * @return Rex expression for the given aggregate call |
| */ |
| public RexNode addAggCall(AggregateCall aggCall, int groupCount, |
| List<AggregateCall> aggCalls, |
| Map<AggregateCall, RexNode> aggCallMapping, |
| IntPredicate isNullable) { |
| if (aggCall.getAggregation() instanceof SqlCountAggFunction |
| && !aggCall.isDistinct()) { |
| final List<Integer> args = aggCall.getArgList(); |
| final List<Integer> nullableArgs = nullableArgs(args, isNullable); |
| aggCall = aggCall.withArgList(nullableArgs); |
| } |
| RexNode rex = aggCallMapping.get(aggCall); |
| if (rex == null) { |
| int index = aggCalls.size() + groupCount; |
| aggCalls.add(aggCall); |
| rex = makeInputRef(aggCall.getType(), index); |
| aggCallMapping.put(aggCall, rex); |
| } |
| return rex; |
| } |
| |
| @Deprecated // to be removed before 2.0 |
| public RexNode addAggCall(final AggregateCall aggCall, int groupCount, |
| List<AggregateCall> aggCalls, |
| Map<AggregateCall, RexNode> aggCallMapping, |
| final @Nullable List<RelDataType> aggArgTypes) { |
| return addAggCall(aggCall, groupCount, aggCalls, aggCallMapping, i -> |
| Objects.requireNonNull(aggArgTypes, "aggArgTypes") |
| .get(aggCall.getArgList().indexOf(i)).isNullable()); |
| } |
| |
| /** |
| * Creates a reference to an aggregate call, checking for repeated calls. |
| */ |
| @Deprecated // to be removed before 2.0 |
| public RexNode addAggCall(AggregateCall aggCall, int groupCount, |
| boolean indicator, List<AggregateCall> aggCalls, |
| Map<AggregateCall, RexNode> aggCallMapping, |
| final @Nullable List<RelDataType> aggArgTypes) { |
| Preconditions.checkArgument(!indicator, |
| "indicator is deprecated, use GROUPING function instead"); |
| return addAggCall(aggCall, groupCount, aggCalls, |
| aggCallMapping, aggArgTypes); |
| |
| } |
| |
| private static List<Integer> nullableArgs(List<Integer> list0, |
| IntPredicate isNullable) { |
| return list0.stream() |
| .filter(isNullable::test) |
| .collect(Util.toImmutableList()); |
| } |
| |
| @Deprecated // to be removed before 2.0 |
| public RexNode makeOver(RelDataType type, SqlAggFunction operator, |
| List<RexNode> exprs, List<RexNode> partitionKeys, |
| ImmutableList<RexFieldCollation> orderKeys, |
| RexWindowBound lowerBound, RexWindowBound upperBound, |
| boolean rows, boolean allowPartial, boolean nullWhenCountZero, |
| boolean distinct) { |
| return makeOver(type, operator, exprs, partitionKeys, orderKeys, lowerBound, |
| upperBound, rows, allowPartial, nullWhenCountZero, distinct, false); |
| } |
| |
| /** |
| * Creates a call to a windowed agg. |
| */ |
| public RexNode makeOver( |
| RelDataType type, |
| SqlAggFunction operator, |
| List<RexNode> exprs, |
| List<RexNode> partitionKeys, |
| ImmutableList<RexFieldCollation> orderKeys, |
| RexWindowBound lowerBound, |
| RexWindowBound upperBound, |
| boolean rows, |
| boolean allowPartial, |
| boolean nullWhenCountZero, |
| boolean distinct, |
| boolean ignoreNulls) { |
| final RexWindow window = |
| makeWindow( |
| partitionKeys, |
| orderKeys, |
| lowerBound, |
| upperBound, |
| rows); |
| final RexOver over = new RexOver(type, operator, exprs, window, |
| distinct, ignoreNulls); |
| RexNode result = over; |
| |
| // This should be correct but need time to go over test results. |
| // Also want to look at combing with section below. |
| if (nullWhenCountZero) { |
| final RelDataType bigintType = |
| typeFactory.createSqlType(SqlTypeName.BIGINT); |
| result = makeCall( |
| SqlStdOperatorTable.CASE, |
| makeCall( |
| SqlStdOperatorTable.GREATER_THAN, |
| new RexOver( |
| bigintType, |
| SqlStdOperatorTable.COUNT, |
| exprs, |
| window, |
| distinct, |
| ignoreNulls), |
| makeLiteral( |
| BigDecimal.ZERO, |
| bigintType, |
| SqlTypeName.DECIMAL)), |
| ensureType(type, // SUM0 is non-nullable, thus need a cast |
| new RexOver(typeFactory.createTypeWithNullability(type, false), |
| operator, exprs, window, distinct, ignoreNulls), |
| false), |
| makeNullLiteral(type)); |
| } |
| if (!allowPartial) { |
| Preconditions.checkArgument(rows, "DISALLOW PARTIAL over RANGE"); |
| final RelDataType bigintType = |
| typeFactory.createSqlType(SqlTypeName.BIGINT); |
| // todo: read bound |
| result = |
| makeCall( |
| SqlStdOperatorTable.CASE, |
| makeCall( |
| SqlStdOperatorTable.GREATER_THAN_OR_EQUAL, |
| new RexOver( |
| bigintType, |
| SqlStdOperatorTable.COUNT, |
| ImmutableList.of(), |
| window, |
| distinct, |
| ignoreNulls), |
| makeLiteral( |
| BigDecimal.valueOf(2), |
| bigintType, |
| SqlTypeName.DECIMAL)), |
| result, |
| constantNull); |
| } |
| return result; |
| } |
| |
| /** |
| * Creates a window specification. |
| * |
| * @param partitionKeys Partition keys |
| * @param orderKeys Order keys |
| * @param lowerBound Lower bound |
| * @param upperBound Upper bound |
| * @param rows Whether physical. True if row-based, false if |
| * range-based |
| * @return window specification |
| */ |
| public RexWindow makeWindow( |
| List<RexNode> partitionKeys, |
| ImmutableList<RexFieldCollation> orderKeys, |
| RexWindowBound lowerBound, |
| RexWindowBound upperBound, |
| boolean rows) { |
| if (lowerBound.isUnbounded() && lowerBound.isPreceding() |
| && upperBound.isUnbounded() && upperBound.isFollowing()) { |
| // RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING |
| // is equivalent to |
| // ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING |
| // but we prefer "RANGE" |
| rows = false; |
| } |
| return new RexWindow( |
| partitionKeys, |
| orderKeys, |
| lowerBound, |
| upperBound, |
| rows); |
| } |
| |
| /** |
| * Creates a constant for the SQL <code>NULL</code> value. |
| * |
| * @deprecated Use {@link #makeNullLiteral(RelDataType)}, which produces a |
| * NULL of the correct type |
| */ |
| @Deprecated // to be removed before 2.0 |
| public RexLiteral constantNull() { |
| return constantNull; |
| } |
| |
| /** |
| * Creates an expression referencing a correlation variable. |
| * |
| * @param id Name of variable |
| * @param type Type of variable |
| * @return Correlation variable |
| */ |
| public RexNode makeCorrel( |
| RelDataType type, |
| CorrelationId id) { |
| return new RexCorrelVariable(id, type); |
| } |
| |
| /** |
| * Creates an invocation of the NEW operator. |
| * |
| * @param type Type to be instantiated |
| * @param exprs Arguments to NEW operator |
| * @return Expression invoking NEW operator |
| */ |
| public RexNode makeNewInvocation( |
| RelDataType type, |
| List<RexNode> exprs) { |
| return new RexCall( |
| type, |
| SqlStdOperatorTable.NEW, |
| exprs); |
| } |
| |
| /** |
| * Creates a call to the CAST operator. |
| * |
| * @param type Type to cast to |
| * @param exp Expression being cast |
| * @return Call to CAST operator |
| */ |
| public RexNode makeCast( |
| RelDataType type, |
| RexNode exp) { |
| return makeCast(type, exp, false); |
| } |
| |
| /** |
| * Creates a call to the CAST operator, expanding if possible, and optionally |
| * also preserving nullability. |
| * |
| * <p>Tries to expand the cast, and therefore the result may be something |
| * other than a {@link RexCall} to the CAST operator, such as a |
| * {@link RexLiteral}. |
| * |
| * @param type Type to cast to |
| * @param exp Expression being cast |
| * @param matchNullability Whether to ensure the result has the same |
| * nullability as {@code type} |
| * @return Call to CAST operator |
| */ |
| public RexNode makeCast( |
| RelDataType type, |
| RexNode exp, |
| boolean matchNullability) { |
| final SqlTypeName sqlType = type.getSqlTypeName(); |
| if (exp instanceof RexLiteral) { |
| RexLiteral literal = (RexLiteral) exp; |
| Comparable value = literal.getValueAs(Comparable.class); |
| SqlTypeName typeName = literal.getTypeName(); |
| if (canRemoveCastFromLiteral(type, value, typeName)) { |
| switch (typeName) { |
| 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; |
| typeName = type.getSqlTypeName(); |
| switch (typeName) { |
| case BIGINT: |
| case INTEGER: |
| case SMALLINT: |
| case TINYINT: |
| case FLOAT: |
| case REAL: |
| case DECIMAL: |
| BigDecimal value2 = (BigDecimal) value; |
| final BigDecimal multiplier = |
| baseUnit(literal.getTypeName()).multiplier; |
| final BigDecimal divider = |
| literal.getTypeName().getEndUnit().multiplier; |
| value = value2.multiply(multiplier) |
| .divide(divider, 0, RoundingMode.HALF_DOWN); |
| break; |
| default: |
| break; |
| } |
| |
| // Not all types are allowed for literals |
| switch (typeName) { |
| case INTEGER: |
| typeName = SqlTypeName.BIGINT; |
| break; |
| default: |
| break; |
| } |
| break; |
| default: |
| break; |
| } |
| final RexLiteral literal2 = |
| makeLiteral(value, type, typeName); |
| if (type.isNullable() |
| && !literal2.getType().isNullable() |
| && matchNullability) { |
| return makeAbstractCast(type, literal2); |
| } |
| return literal2; |
| } |
| } else if (SqlTypeUtil.isExactNumeric(type) |
| && SqlTypeUtil.isInterval(exp.getType())) { |
| return makeCastIntervalToExact(type, exp); |
| } else if (sqlType == SqlTypeName.BOOLEAN |
| && SqlTypeUtil.isExactNumeric(exp.getType())) { |
| return makeCastExactToBoolean(type, exp); |
| } else if (exp.getType().getSqlTypeName() == SqlTypeName.BOOLEAN |
| && SqlTypeUtil.isExactNumeric(type)) { |
| return makeCastBooleanToExact(type, exp); |
| } |
| return makeAbstractCast(type, exp); |
| } |
| |
| /** Returns the lowest granularity unit for the given unit. |
| * YEAR and MONTH intervals are stored as months; |
| * HOUR, MINUTE, SECOND intervals are stored as milliseconds. */ |
| protected static TimeUnit baseUnit(SqlTypeName unit) { |
| if (unit.isYearMonth()) { |
| return TimeUnit.MONTH; |
| } else { |
| return TimeUnit.MILLISECOND; |
| } |
| } |
| |
| boolean canRemoveCastFromLiteral(RelDataType toType, @Nullable Comparable value, |
| SqlTypeName fromTypeName) { |
| final SqlTypeName sqlType = toType.getSqlTypeName(); |
| if (!RexLiteral.valueMatchesType(value, sqlType, false)) { |
| return false; |
| } |
| if (toType.getSqlTypeName() != fromTypeName |
| && SqlTypeFamily.DATETIME.getTypeNames().contains(fromTypeName)) { |
| return false; |
| } |
| if (value instanceof NlsString) { |
| final int length = ((NlsString) value).getValue().length(); |
| switch (toType.getSqlTypeName()) { |
| case CHAR: |
| return SqlTypeUtil.comparePrecision(toType.getPrecision(), length) == 0; |
| case VARCHAR: |
| return SqlTypeUtil.comparePrecision(toType.getPrecision(), length) >= 0; |
| default: |
| throw new AssertionError(toType); |
| } |
| } |
| if (value instanceof ByteString) { |
| final int length = ((ByteString) value).length(); |
| switch (toType.getSqlTypeName()) { |
| case BINARY: |
| return SqlTypeUtil.comparePrecision(toType.getPrecision(), length) == 0; |
| case VARBINARY: |
| return SqlTypeUtil.comparePrecision(toType.getPrecision(), length) >= 0; |
| default: |
| throw new AssertionError(toType); |
| } |
| } |
| |
| if (toType.getSqlTypeName() == SqlTypeName.DECIMAL) { |
| final BigDecimal decimalValue = (BigDecimal) value; |
| return SqlTypeUtil.isValidDecimalValue(decimalValue, toType); |
| } |
| |
| return true; |
| } |
| |
| private RexNode makeCastExactToBoolean(RelDataType toType, RexNode exp) { |
| return makeCall(toType, |
| SqlStdOperatorTable.NOT_EQUALS, |
| ImmutableList.of(exp, makeZeroLiteral(exp.getType()))); |
| } |
| |
| private RexNode makeCastBooleanToExact(RelDataType toType, RexNode exp) { |
| final RexNode casted = makeCall(SqlStdOperatorTable.CASE, |
| exp, |
| makeExactLiteral(BigDecimal.ONE, toType), |
| makeZeroLiteral(toType)); |
| if (!exp.getType().isNullable()) { |
| return casted; |
| } |
| return makeCall(toType, |
| SqlStdOperatorTable.CASE, |
| ImmutableList.of(makeCall(SqlStdOperatorTable.IS_NOT_NULL, exp), |
| casted, makeNullLiteral(toType))); |
| } |
| |
| private RexNode makeCastIntervalToExact(RelDataType toType, RexNode exp) { |
| final TimeUnit endUnit = exp.getType().getSqlTypeName().getEndUnit(); |
| final TimeUnit baseUnit = baseUnit(exp.getType().getSqlTypeName()); |
| final BigDecimal multiplier = baseUnit.multiplier; |
| final BigDecimal divider = endUnit.multiplier; |
| RexNode value = multiplyDivide(decodeIntervalOrDecimal(exp), |
| multiplier, divider); |
| return ensureType(toType, value, false); |
| } |
| |
| public RexNode multiplyDivide(RexNode e, BigDecimal multiplier, |
| BigDecimal divider) { |
| assert multiplier.signum() > 0; |
| assert divider.signum() > 0; |
| switch (multiplier.compareTo(divider)) { |
| case 0: |
| return e; |
| case 1: |
| // E.g. multiplyDivide(e, 1000, 10) ==> e * 100 |
| return makeCall(SqlStdOperatorTable.MULTIPLY, e, |
| makeExactLiteral( |
| multiplier.divide(divider, RoundingMode.UNNECESSARY))); |
| case -1: |
| // E.g. multiplyDivide(e, 10, 1000) ==> e / 100 |
| return makeCall(SqlStdOperatorTable.DIVIDE_INTEGER, e, |
| makeExactLiteral( |
| divider.divide(multiplier, RoundingMode.UNNECESSARY))); |
| default: |
| throw new AssertionError(multiplier + "/" + divider); |
| } |
| } |
| |
| /** |
| * Casts a decimal's integer representation to a decimal node. If the |
| * expression is not the expected integer type, then it is casted first. |
| * |
| * <p>An overflow check may be requested to ensure the internal value |
| * does not exceed the maximum value of the decimal type. |
| * |
| * @param value integer representation of decimal |
| * @param type type integer will be reinterpreted as |
| * @param checkOverflow indicates whether an overflow check is required |
| * when reinterpreting this particular value as the |
| * decimal type. A check usually not required for |
| * arithmetic, but is often required for rounding and |
| * explicit casts. |
| * @return the integer reinterpreted as an opaque decimal type |
| */ |
| public RexNode encodeIntervalOrDecimal( |
| RexNode value, |
| RelDataType type, |
| boolean checkOverflow) { |
| RelDataType bigintType = |
| typeFactory.createSqlType(SqlTypeName.BIGINT); |
| RexNode cast = ensureType(bigintType, value, true); |
| return makeReinterpretCast(type, cast, makeLiteral(checkOverflow)); |
| } |
| |
| /** |
| * Retrieves an INTERVAL or DECIMAL node's integer representation. |
| * |
| * @param node the interval or decimal value as an opaque type |
| * @return an integer representation of the decimal value |
| */ |
| public RexNode decodeIntervalOrDecimal(RexNode node) { |
| assert SqlTypeUtil.isDecimal(node.getType()) |
| || SqlTypeUtil.isInterval(node.getType()); |
| RelDataType bigintType = typeFactory.createSqlType(SqlTypeName.BIGINT); |
| return makeReinterpretCast( |
| matchNullability(bigintType, node), node, makeLiteral(false)); |
| } |
| |
| /** |
| * Creates a call to the CAST operator. |
| * |
| * @param type Type to cast to |
| * @param exp Expression being cast |
| * @return Call to CAST operator |
| */ |
| public RexNode makeAbstractCast( |
| RelDataType type, |
| RexNode exp) { |
| return new RexCall( |
| type, |
| SqlStdOperatorTable.CAST, |
| ImmutableList.of(exp)); |
| } |
| |
| /** |
| * Makes a reinterpret cast. |
| * |
| * @param type type returned by the cast |
| * @param exp expression to be casted |
| * @param checkOverflow whether an overflow check is required |
| * @return a RexCall with two operands and a special return type |
| */ |
| public RexNode makeReinterpretCast( |
| RelDataType type, |
| RexNode exp, |
| RexNode checkOverflow) { |
| List<RexNode> args; |
| if ((checkOverflow != null) && checkOverflow.isAlwaysTrue()) { |
| args = ImmutableList.of(exp, checkOverflow); |
| } else { |
| args = ImmutableList.of(exp); |
| } |
| return new RexCall( |
| type, |
| SqlStdOperatorTable.REINTERPRET, |
| args); |
| } |
| |
| /** |
| * Makes a cast of a value to NOT NULL; |
| * no-op if the type already has NOT NULL. |
| */ |
| public RexNode makeNotNull(RexNode exp) { |
| final RelDataType type = exp.getType(); |
| if (!type.isNullable()) { |
| return exp; |
| } |
| final RelDataType notNullType = |
| typeFactory.createTypeWithNullability(type, false); |
| return makeAbstractCast(notNullType, exp); |
| } |
| |
| /** |
| * Creates a reference to all the fields in the row. That is, the whole row |
| * as a single record object. |
| * |
| * @param input Input relational expression |
| */ |
| public RexNode makeRangeReference(RelNode input) { |
| return new RexRangeRef(input.getRowType(), 0); |
| } |
| |
| /** |
| * Creates a reference to all the fields in the row. |
| * |
| * <p>For example, if the input row has type <code>T{f0,f1,f2,f3,f4}</code> |
| * then <code>makeRangeReference(T{f0,f1,f2,f3,f4}, S{f3,f4}, 3)</code> is |
| * an expression which yields the last 2 fields. |
| * |
| * @param type Type of the resulting range record. |
| * @param offset Index of first field. |
| * @param nullable Whether the record is nullable. |
| */ |
| public RexRangeRef makeRangeReference( |
| RelDataType type, |
| int offset, |
| boolean nullable) { |
| if (nullable && !type.isNullable()) { |
| type = |
| typeFactory.createTypeWithNullability( |
| type, |
| nullable); |
| } |
| return new RexRangeRef(type, offset); |
| } |
| |
| /** |
| * Creates a reference to a given field of the input record. |
| * |
| * @param type Type of field |
| * @param i Ordinal of field |
| * @return Reference to field |
| */ |
| public RexInputRef makeInputRef( |
| RelDataType type, |
| int i) { |
| type = SqlTypeUtil.addCharsetAndCollation(type, typeFactory); |
| return new RexInputRef(i, type); |
| } |
| |
| /** |
| * Creates a reference to a given field of the input relational expression. |
| * |
| * @param input Input relational expression |
| * @param i Ordinal of field |
| * @return Reference to field |
| * |
| * @see #identityProjects(RelDataType) |
| */ |
| public RexInputRef makeInputRef(RelNode input, int i) { |
| return makeInputRef(input.getRowType().getFieldList().get(i).getType(), i); |
| } |
| |
| /** |
| * Creates a reference to a given field of the pattern. |
| * |
| * @param alpha the pattern name |
| * @param type Type of field |
| * @param i Ordinal of field |
| * @return Reference to field of pattern |
| */ |
| public RexPatternFieldRef makePatternFieldRef(String alpha, RelDataType type, int i) { |
| type = SqlTypeUtil.addCharsetAndCollation(type, typeFactory); |
| return new RexPatternFieldRef(alpha, i, type); |
| } |
| |
| /** |
| * Create a reference to local variable. |
| * |
| * @param type Type of variable |
| * @param i Ordinal of variable |
| * @return Reference to local variable |
| */ |
| public RexLocalRef makeLocalRef(RelDataType type, int i) { |
| type = SqlTypeUtil.addCharsetAndCollation(type, typeFactory); |
| return new RexLocalRef(i, type); |
| } |
| |
| /** |
| * Creates a literal representing a flag. |
| * |
| * @param flag Flag value |
| */ |
| public RexLiteral makeFlag(Enum flag) { |
| assert flag != null; |
| return makeLiteral(flag, |
| typeFactory.createSqlType(SqlTypeName.SYMBOL), |
| SqlTypeName.SYMBOL); |
| } |
| |
| /** |
| * Internal method to create a call to a literal. Code outside this package |
| * should call one of the type-specific methods such as |
| * {@link #makeDateLiteral(DateString)}, {@link #makeLiteral(boolean)}, |
| * {@link #makeLiteral(String)}. |
| * |
| * @param o Value of literal, must be appropriate for the type |
| * @param type Type of literal |
| * @param typeName SQL type of literal |
| * @return Literal |
| */ |
| protected RexLiteral makeLiteral( |
| @Nullable Comparable o, |
| RelDataType type, |
| SqlTypeName typeName) { |
| // All literals except NULL have NOT NULL types. |
| type = typeFactory.createTypeWithNullability(type, o == null); |
| int p; |
| switch (typeName) { |
| case CHAR: |
| // Character literals must have a charset and collation. Populate |
| // from the type if necessary. |
| assert o instanceof NlsString; |
| NlsString nlsString = (NlsString) o; |
| if (nlsString.getCollation() == null |
| || nlsString.getCharset() == null |
| || !Objects.equals(nlsString.getCharset(), type.getCharset()) |
| || !Objects.equals(nlsString.getCollation(), type.getCollation())) { |
| assert type.getSqlTypeName() == SqlTypeName.CHAR |
| || type.getSqlTypeName() == SqlTypeName.VARCHAR; |
| Charset charset = type.getCharset(); |
| assert charset != null : "type.getCharset() must not be null"; |
| assert type.getCollation() != null : "type.getCollation() must not be null"; |
| o = new NlsString( |
| nlsString.getValue(), |
| charset.name(), |
| type.getCollation()); |
| } |
| break; |
| case TIME: |
| case TIME_WITH_LOCAL_TIME_ZONE: |
| assert o instanceof TimeString; |
| p = type.getPrecision(); |
| if (p == RelDataType.PRECISION_NOT_SPECIFIED) { |
| p = 0; |
| } |
| o = ((TimeString) o).round(p); |
| break; |
| case TIMESTAMP: |
| case TIMESTAMP_WITH_LOCAL_TIME_ZONE: |
| assert o instanceof TimestampString; |
| p = type.getPrecision(); |
| if (p == RelDataType.PRECISION_NOT_SPECIFIED) { |
| p = 0; |
| } |
| o = ((TimestampString) o).round(p); |
| break; |
| default: |
| break; |
| } |
| if (typeName == SqlTypeName.DECIMAL |
| && !SqlTypeUtil.isValidDecimalValue((BigDecimal) o, type)) { |
| throw new IllegalArgumentException( |
| "Cannot convert " + o + " to " + type + " due to overflow"); |
| } |
| return new RexLiteral(o, type, typeName); |
| } |
| |
| /** |
| * Creates a boolean literal. |
| */ |
| public RexLiteral makeLiteral(boolean b) { |
| return b ? booleanTrue : booleanFalse; |
| } |
| |
| /** |
| * Creates a numeric literal. |
| */ |
| public RexLiteral makeExactLiteral(BigDecimal bd) { |
| RelDataType relType; |
| int scale = bd.scale(); |
| assert scale >= 0; |
| assert scale <= typeFactory.getTypeSystem().getMaxNumericScale() : scale; |
| if (scale == 0) { |
| if (bd.compareTo(INT_MIN) >= 0 && bd.compareTo(INT_MAX) <= 0) { |
| relType = typeFactory.createSqlType(SqlTypeName.INTEGER); |
| } else { |
| relType = typeFactory.createSqlType(SqlTypeName.BIGINT); |
| } |
| } else { |
| int precision = bd.unscaledValue().abs().toString().length(); |
| if (precision > scale) { |
| // bd is greater than or equal to 1 |
| relType = |
| typeFactory.createSqlType(SqlTypeName.DECIMAL, precision, scale); |
| } else { |
| // bd is less than 1 |
| relType = |
| typeFactory.createSqlType(SqlTypeName.DECIMAL, scale + 1, scale); |
| } |
| } |
| return makeExactLiteral(bd, relType); |
| } |
| |
| /** |
| * Creates a BIGINT literal. |
| */ |
| public RexLiteral makeBigintLiteral(@Nullable BigDecimal bd) { |
| RelDataType bigintType = |
| typeFactory.createSqlType( |
| SqlTypeName.BIGINT); |
| return makeLiteral(bd, bigintType, SqlTypeName.DECIMAL); |
| } |
| |
| /** |
| * Creates a numeric literal. |
| */ |
| public RexLiteral makeExactLiteral(@Nullable BigDecimal bd, RelDataType type) { |
| return makeLiteral(bd, type, SqlTypeName.DECIMAL); |
| } |
| |
| /** |
| * Creates a byte array literal. |
| */ |
| public RexLiteral makeBinaryLiteral(ByteString byteString) { |
| return makeLiteral( |
| byteString, |
| typeFactory.createSqlType(SqlTypeName.BINARY, byteString.length()), |
| SqlTypeName.BINARY); |
| } |
| |
| /** |
| * Creates a double-precision literal. |
| */ |
| public RexLiteral makeApproxLiteral(BigDecimal bd) { |
| // Validator should catch if underflow is allowed |
| // If underflow is allowed, let underflow become zero |
| if (bd.doubleValue() == 0) { |
| bd = BigDecimal.ZERO; |
| } |
| return makeApproxLiteral(bd, typeFactory.createSqlType(SqlTypeName.DOUBLE)); |
| } |
| |
| /** |
| * Creates an approximate numeric literal (double or float). |
| * |
| * @param bd literal value |
| * @param type approximate numeric type |
| * @return new literal |
| */ |
| public RexLiteral makeApproxLiteral(@Nullable BigDecimal bd, RelDataType type) { |
| assert SqlTypeFamily.APPROXIMATE_NUMERIC.getTypeNames().contains( |
| type.getSqlTypeName()); |
| return makeLiteral(bd, type, SqlTypeName.DOUBLE); |
| } |
| |
| /** |
| * Creates a search argument literal. |
| */ |
| public RexLiteral makeSearchArgumentLiteral(Sarg s, RelDataType type) { |
| return makeLiteral(Objects.requireNonNull(s, "s"), type, SqlTypeName.SARG); |
| } |
| |
| /** |
| * Creates a character string literal. |
| */ |
| public RexLiteral makeLiteral(String s) { |
| assert s != null; |
| return makePreciseStringLiteral(s); |
| } |
| |
| /** |
| * Creates a character string literal with type CHAR and default charset and |
| * collation. |
| * |
| * @param s String value |
| * @return Character string literal |
| */ |
| protected RexLiteral makePreciseStringLiteral(String s) { |
| assert s != null; |
| if (s.equals("")) { |
| return charEmpty; |
| } |
| return makeCharLiteral(new NlsString(s, null, null)); |
| } |
| |
| /** |
| * Creates a character string literal with type CHAR. |
| * |
| * @param value String value in bytes |
| * @param charsetName SQL-level charset name |
| * @param collation Sql collation |
| * @return String literal |
| */ |
| protected RexLiteral makePreciseStringLiteral(ByteString value, |
| String charsetName, SqlCollation collation) { |
| return makeCharLiteral(new NlsString(value, charsetName, collation)); |
| } |
| |
| /** |
| * Ensures expression is interpreted as a specified type. The returned |
| * expression may be wrapped with a cast. |
| * |
| * @param type desired type |
| * @param node expression |
| * @param matchNullability whether to correct nullability of specified |
| * type to match the expression; this usually should |
| * be true, except for explicit casts which can |
| * override default nullability |
| * @return a casted expression or the original expression |
| */ |
| public RexNode ensureType( |
| RelDataType type, |
| RexNode node, |
| boolean matchNullability) { |
| RelDataType targetType = type; |
| if (matchNullability) { |
| targetType = matchNullability(type, node); |
| } |
| |
| if (targetType.getSqlTypeName() == SqlTypeName.ANY |
| && (!matchNullability |
| || targetType.isNullable() == node.getType().isNullable())) { |
| return node; |
| } |
| |
| if (!node.getType().equals(targetType)) { |
| return makeCast(targetType, node); |
| } |
| return node; |
| } |
| |
| /** |
| * Ensures that a type's nullability matches a value's nullability. |
| */ |
| public RelDataType matchNullability( |
| RelDataType type, |
| RexNode value) { |
| boolean typeNullability = type.isNullable(); |
| boolean valueNullability = value.getType().isNullable(); |
| if (typeNullability != valueNullability) { |
| return typeFactory.createTypeWithNullability(type, valueNullability); |
| } |
| return type; |
| } |
| |
| /** |
| * Creates a character string literal from an {@link NlsString}. |
| * |
| * <p>If the string's charset and collation are not set, uses the system |
| * defaults. |
| */ |
| public RexLiteral makeCharLiteral(NlsString str) { |
| assert str != null; |
| RelDataType type = SqlUtil.createNlsStringType(typeFactory, str); |
| return makeLiteral(str, type, SqlTypeName.CHAR); |
| } |
| |
| // CHECKSTYLE: IGNORE 1 |
| /** @deprecated Use {@link #makeDateLiteral(DateString)}. */ |
| @Deprecated // to be removed before 2.0 |
| public RexLiteral makeDateLiteral(Calendar calendar) { |
| return makeDateLiteral(DateString.fromCalendarFields(calendar)); |
| } |
| |
| /** |
| * Creates a Date literal. |
| */ |
| public RexLiteral makeDateLiteral(DateString date) { |
| return makeLiteral(Objects.requireNonNull(date, "date"), |
| typeFactory.createSqlType(SqlTypeName.DATE), SqlTypeName.DATE); |
| } |
| |
| // CHECKSTYLE: IGNORE 1 |
| /** @deprecated Use {@link #makeTimeLiteral(TimeString, int)}. */ |
| @Deprecated // to be removed before 2.0 |
| public RexLiteral makeTimeLiteral(Calendar calendar, int precision) { |
| return makeTimeLiteral(TimeString.fromCalendarFields(calendar), precision); |
| } |
| |
| /** |
| * Creates a Time literal. |
| */ |
| public RexLiteral makeTimeLiteral(TimeString time, int precision) { |
| return makeLiteral(Objects.requireNonNull(time, "time"), |
| typeFactory.createSqlType(SqlTypeName.TIME, precision), |
| SqlTypeName.TIME); |
| } |
| |
| /** |
| * Creates a Time with local time-zone literal. |
| */ |
| public RexLiteral makeTimeWithLocalTimeZoneLiteral( |
| TimeString time, |
| int precision) { |
| return makeLiteral(Objects.requireNonNull(time, "time"), |
| typeFactory.createSqlType(SqlTypeName.TIME_WITH_LOCAL_TIME_ZONE, precision), |
| SqlTypeName.TIME_WITH_LOCAL_TIME_ZONE); |
| } |
| |
| // CHECKSTYLE: IGNORE 1 |
| /** @deprecated Use {@link #makeTimestampLiteral(TimestampString, int)}. */ |
| @Deprecated // to be removed before 2.0 |
| public RexLiteral makeTimestampLiteral(Calendar calendar, int precision) { |
| return makeTimestampLiteral(TimestampString.fromCalendarFields(calendar), |
| precision); |
| } |
| |
| /** |
| * Creates a Timestamp literal. |
| */ |
| public RexLiteral makeTimestampLiteral(TimestampString timestamp, |
| int precision) { |
| return makeLiteral(Objects.requireNonNull(timestamp, "timestamp"), |
| typeFactory.createSqlType(SqlTypeName.TIMESTAMP, precision), |
| SqlTypeName.TIMESTAMP); |
| } |
| |
| /** |
| * Creates a Timestamp with local time-zone literal. |
| */ |
| public RexLiteral makeTimestampWithLocalTimeZoneLiteral( |
| TimestampString timestamp, |
| int precision) { |
| return makeLiteral(Objects.requireNonNull(timestamp, "timestamp"), |
| typeFactory.createSqlType(SqlTypeName.TIMESTAMP_WITH_LOCAL_TIME_ZONE, precision), |
| SqlTypeName.TIMESTAMP_WITH_LOCAL_TIME_ZONE); |
| } |
| |
| /** |
| * Creates a literal representing an interval type, for example |
| * {@code YEAR TO MONTH} or {@code DOW}. |
| */ |
| public RexLiteral makeIntervalLiteral( |
| SqlIntervalQualifier intervalQualifier) { |
| assert intervalQualifier != null; |
| return makeFlag(intervalQualifier.timeUnitRange); |
| } |
| |
| /** |
| * Creates a literal representing an interval value, for example |
| * {@code INTERVAL '3-7' YEAR TO MONTH}. |
| */ |
| public RexLiteral makeIntervalLiteral( |
| @Nullable BigDecimal v, |
| SqlIntervalQualifier intervalQualifier) { |
| return makeLiteral( |
| v, |
| typeFactory.createSqlIntervalType(intervalQualifier), |
| intervalQualifier.typeName()); |
| } |
| |
| /** |
| * Creates a reference to a dynamic parameter. |
| * |
| * @param type Type of dynamic parameter |
| * @param index Index of dynamic parameter |
| * @return Expression referencing dynamic parameter |
| */ |
| public RexDynamicParam makeDynamicParam( |
| RelDataType type, |
| int index) { |
| return new RexDynamicParam(type, index); |
| } |
| |
| /** |
| * Creates a literal whose value is NULL, with a particular type. |
| * |
| * <p>The typing is necessary because RexNodes are strictly typed. For |
| * example, in the Rex world the <code>NULL</code> parameter to <code> |
| * SUBSTRING(NULL FROM 2 FOR 4)</code> must have a valid VARCHAR type so |
| * that the result type can be determined. |
| * |
| * @param type Type to cast NULL to |
| * @return NULL literal of given type |
| */ |
| public RexLiteral makeNullLiteral(RelDataType type) { |
| if (!type.isNullable()) { |
| type = typeFactory.createTypeWithNullability(type, true); |
| } |
| return (RexLiteral) makeCast(type, constantNull); |
| } |
| |
| // CHECKSTYLE: IGNORE 1 |
| /** @deprecated Use {@link #makeNullLiteral(RelDataType)} */ |
| @Deprecated // to be removed before 2.0 |
| public RexNode makeNullLiteral(SqlTypeName typeName, int precision) { |
| return makeNullLiteral(typeFactory.createSqlType(typeName, precision)); |
| } |
| |
| // CHECKSTYLE: IGNORE 1 |
| /** @deprecated Use {@link #makeNullLiteral(RelDataType)} */ |
| @Deprecated // to be removed before 2.0 |
| public RexNode makeNullLiteral(SqlTypeName typeName) { |
| return makeNullLiteral(typeFactory.createSqlType(typeName)); |
| } |
| |
| /** Creates a {@link RexNode} representation a SQL "arg IN (point, ...)" |
| * expression. |
| * |
| * <p>If all of the expressions are literals, creates a call {@link Sarg} |
| * literal, "SEARCH(arg, SARG([point0..point0], [point1..point1], ...)"; |
| * otherwise creates a disjunction, "arg = point0 OR arg = point1 OR ...". */ |
| public RexNode makeIn(RexNode arg, List<? extends RexNode> ranges) { |
| if (areAssignable(arg, ranges)) { |
| final Sarg sarg = toSarg(Comparable.class, ranges, RexUnknownAs.UNKNOWN); |
| if (sarg != null) { |
| final RexNode range0 = ranges.get(0); |
| return makeCall(SqlStdOperatorTable.SEARCH, |
| arg, |
| makeSearchArgumentLiteral(sarg, range0.getType())); |
| } |
| } |
| return RexUtil.composeDisjunction(this, ranges.stream() |
| .map(r -> makeCall(SqlStdOperatorTable.EQUALS, arg, r)) |
| .collect(Util.toImmutableList())); |
| } |
| |
| /** Returns whether and argument and bounds are have types that are |
| * sufficiently compatible to be converted to a {@link Sarg}. */ |
| private static boolean areAssignable(RexNode arg, List<? extends RexNode> bounds) { |
| for (RexNode bound : bounds) { |
| if (!SqlTypeUtil.inSameFamily(arg.getType(), bound.getType()) |
| && !(arg.getType().isStruct() && bound.getType().isStruct())) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| /** Creates a {@link RexNode} representation a SQL |
| * "arg BETWEEN lower AND upper" expression. |
| * |
| * <p>If the expressions are all literals of compatible type, creates a call |
| * to {@link Sarg} literal, {@code SEARCH(arg, SARG([lower..upper])}; |
| * otherwise creates a disjunction, {@code arg >= lower AND arg <= upper}. */ |
| @SuppressWarnings("BetaApi") |
| public RexNode makeBetween(RexNode arg, RexNode lower, RexNode upper) { |
| final Comparable lowerValue = toComparable(Comparable.class, lower); |
| final Comparable upperValue = toComparable(Comparable.class, upper); |
| if (lowerValue != null |
| && upperValue != null |
| && areAssignable(arg, Arrays.asList(lower, upper))) { |
| final Sarg sarg = |
| Sarg.of(RexUnknownAs.UNKNOWN, |
| ImmutableRangeSet.<Comparable>of( |
| Range.closed(lowerValue, upperValue))); |
| return makeCall(SqlStdOperatorTable.SEARCH, arg, |
| makeSearchArgumentLiteral(sarg, lower.getType())); |
| } |
| return makeCall(SqlStdOperatorTable.AND, |
| makeCall(SqlStdOperatorTable.GREATER_THAN_OR_EQUAL, arg, lower), |
| makeCall(SqlStdOperatorTable.LESS_THAN_OR_EQUAL, arg, upper)); |
| } |
| |
| /** Converts a list of expressions to a search argument, or returns null if |
| * not possible. */ |
| @SuppressWarnings({"BetaApi", "UnstableApiUsage"}) |
| private static <C extends Comparable<C>> @Nullable Sarg<C> toSarg(Class<C> clazz, |
| List<? extends RexNode> ranges, RexUnknownAs unknownAs) { |
| if (ranges.isEmpty()) { |
| // Cannot convert an empty list to a Sarg (by this interface, at least) |
| // because we use the type of the first element. |
| return null; |
| } |
| final RangeSet<C> rangeSet = TreeRangeSet.create(); |
| for (RexNode range : ranges) { |
| final C value = toComparable(clazz, range); |
| if (value == null) { |
| return null; |
| } |
| rangeSet.add(Range.singleton(value)); |
| } |
| return Sarg.of(unknownAs, rangeSet); |
| } |
| |
| private static <C extends Comparable<C>> @Nullable C toComparable(Class<C> clazz, |
| RexNode point) { |
| switch (point.getKind()) { |
| case LITERAL: |
| final RexLiteral literal = (RexLiteral) point; |
| return literal.getValueAs(clazz); |
| |
| case ROW: |
| final RexCall call = (RexCall) point; |
| final ImmutableList.Builder<Comparable> b = ImmutableList.builder(); |
| for (RexNode operand : call.operands) { |
| //noinspection unchecked |
| final Comparable value = toComparable(Comparable.class, operand); |
| if (value == null) { |
| return null; // not a constant value |
| } |
| b.add(value); |
| } |
| return clazz.cast(FlatLists.ofComparable(b.build())); |
| |
| default: |
| return null; // not a constant value |
| } |
| } |
| |
| /** |
| * Creates a copy of an expression, which may have been created using a |
| * different RexBuilder and/or {@link RelDataTypeFactory}, using this |
| * RexBuilder. |
| * |
| * @param expr Expression |
| * @return Copy of expression |
| * @see RelDataTypeFactory#copyType(RelDataType) |
| */ |
| public RexNode copy(RexNode expr) { |
| return expr.accept(new RexCopier(this)); |
| } |
| |
| /** |
| * Creates a literal of the default value for the given type. |
| * |
| * <p>This value is:</p> |
| * |
| * <ul> |
| * <li>0 for numeric types; |
| * <li>FALSE for BOOLEAN; |
| * <li>The epoch for TIMESTAMP and DATE; |
| * <li>Midnight for TIME; |
| * <li>The empty string for string types (CHAR, BINARY, VARCHAR, VARBINARY). |
| * </ul> |
| * |
| * @param type Type |
| * @return Simple literal |
| */ |
| public RexLiteral makeZeroLiteral(RelDataType type) { |
| return makeLiteral(zeroValue(type), type); |
| } |
| |
| private static Comparable zeroValue(RelDataType type) { |
| switch (type.getSqlTypeName()) { |
| case CHAR: |
| return new NlsString(Spaces.of(type.getPrecision()), null, null); |
| case VARCHAR: |
| return new NlsString("", null, null); |
| case BINARY: |
| return new ByteString(new byte[type.getPrecision()]); |
| case VARBINARY: |
| return ByteString.EMPTY; |
| case TINYINT: |
| case SMALLINT: |
| case INTEGER: |
| case BIGINT: |
| case DECIMAL: |
| case FLOAT: |
| case REAL: |
| case DOUBLE: |
| return BigDecimal.ZERO; |
| case BOOLEAN: |
| return false; |
| case TIME: |
| case DATE: |
| case TIMESTAMP: |
| return DateTimeUtils.ZERO_CALENDAR; |
| case TIME_WITH_LOCAL_TIME_ZONE: |
| return new TimeString(0, 0, 0); |
| case TIMESTAMP_WITH_LOCAL_TIME_ZONE: |
| return new TimestampString(0, 0, 0, 0, 0, 0); |
| default: |
| throw Util.unexpected(type.getSqlTypeName()); |
| } |
| } |
| |
| /** |
| * Creates a literal of a given type, padding values of constant-width |
| * types to match their type, not allowing casts. |
| * |
| * @param value Value |
| * @param type Type |
| * @return Simple literal |
| */ |
| public RexLiteral makeLiteral(@Nullable Object value, RelDataType type) { |
| return (RexLiteral) makeLiteral(value, type, false, false); |
| } |
| |
| /** |
| * Creates a literal of a given type, padding values of constant-width |
| * types to match their type. |
| * |
| * @param value Value |
| * @param type Type |
| * @param allowCast Whether to allow a cast. If false, value is always a |
| * {@link RexLiteral} but may not be the exact type |
| * @return Simple literal, or cast simple literal |
| */ |
| public RexNode makeLiteral(@Nullable Object value, RelDataType type, |
| boolean allowCast) { |
| return makeLiteral(value, type, allowCast, false); |
| } |
| |
| /** |
| * Creates a literal of a given type. The value is assumed to be |
| * compatible with the type. |
| * |
| * <p>The {@code trim} parameter controls whether to trim values of |
| * constant-width types such as {@code CHAR}. Consider a call to |
| * {@code makeLiteral("foo ", CHAR(5)}, and note that the value is too short |
| * for its type. If {@code trim} is true, the value is converted to "foo" |
| * and the type to {@code CHAR(3)}; if {@code trim} is false, the value is |
| * right-padded with spaces to {@code "foo "}, to match the type |
| * {@code CHAR(5)}. |
| * |
| * @param value Value |
| * @param type Type |
| * @param allowCast Whether to allow a cast. If false, value is always a |
| * {@link RexLiteral} but may not be the exact type |
| * @param trim Whether to trim values and type to the shortest equivalent |
| * value; for example whether to convert CHAR(4) 'foo ' |
| * to CHAR(3) 'foo' |
| * @return Simple literal, or cast simple literal |
| */ |
| public RexNode makeLiteral(@Nullable Object value, RelDataType type, |
| boolean allowCast, boolean trim) { |
| if (value == null) { |
| return makeCast(type, constantNull); |
| } |
| if (type.isNullable()) { |
| final RelDataType typeNotNull = |
| typeFactory.createTypeWithNullability(type, false); |
| if (allowCast) { |
| RexNode literalNotNull = makeLiteral(value, typeNotNull, allowCast); |
| return makeAbstractCast(type, literalNotNull); |
| } |
| type = typeNotNull; |
| } |
| value = clean(value, type); |
| RexLiteral literal; |
| final List<RexNode> operands; |
| final SqlTypeName sqlTypeName = type.getSqlTypeName(); |
| switch (sqlTypeName) { |
| case CHAR: |
| final NlsString nlsString = (NlsString) value; |
| if (trim) { |
| return makeCharLiteral(nlsString.rtrim()); |
| } else { |
| return makeCharLiteral(padRight(nlsString, type.getPrecision())); |
| } |
| case VARCHAR: |
| literal = makeCharLiteral((NlsString) value); |
| if (allowCast) { |
| return makeCast(type, literal); |
| } else { |
| return literal; |
| } |
| case BINARY: |
| return makeBinaryLiteral( |
| padRight((ByteString) value, type.getPrecision())); |
| case VARBINARY: |
| literal = makeBinaryLiteral((ByteString) value); |
| if (allowCast) { |
| return makeCast(type, literal); |
| } else { |
| return literal; |
| } |
| case TINYINT: |
| case SMALLINT: |
| case INTEGER: |
| case BIGINT: |
| case DECIMAL: |
| return makeExactLiteral((BigDecimal) value, type); |
| case FLOAT: |
| case REAL: |
| case DOUBLE: |
| return makeApproxLiteral((BigDecimal) value, type); |
| case BOOLEAN: |
| return (Boolean) value ? booleanTrue : booleanFalse; |
| case TIME: |
| return makeTimeLiteral((TimeString) value, type.getPrecision()); |
| case TIME_WITH_LOCAL_TIME_ZONE: |
| return makeTimeWithLocalTimeZoneLiteral((TimeString) value, type.getPrecision()); |
| case DATE: |
| return makeDateLiteral((DateString) value); |
| case TIMESTAMP: |
| return makeTimestampLiteral((TimestampString) value, type.getPrecision()); |
| case TIMESTAMP_WITH_LOCAL_TIME_ZONE: |
| return makeTimestampWithLocalTimeZoneLiteral((TimestampString) value, type.getPrecision()); |
| 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: |
| return makeIntervalLiteral((BigDecimal) value, |
| castNonNull(type.getIntervalQualifier())); |
| case SYMBOL: |
| return makeFlag((Enum) value); |
| case MAP: |
| final MapSqlType mapType = (MapSqlType) type; |
| @SuppressWarnings("unchecked") |
| final Map<Object, Object> map = (Map) value; |
| operands = new ArrayList<>(); |
| for (Map.Entry<Object, Object> entry : map.entrySet()) { |
| operands.add( |
| makeLiteral(entry.getKey(), mapType.getKeyType(), allowCast)); |
| operands.add( |
| makeLiteral(entry.getValue(), mapType.getValueType(), allowCast)); |
| } |
| return makeCall(SqlStdOperatorTable.MAP_VALUE_CONSTRUCTOR, operands); |
| case ARRAY: |
| final ArraySqlType arrayType = (ArraySqlType) type; |
| @SuppressWarnings("unchecked") |
| final List<Object> listValue = (List) value; |
| operands = new ArrayList<>(); |
| for (Object entry : listValue) { |
| operands.add( |
| makeLiteral(entry, arrayType.getComponentType(), allowCast)); |
| } |
| return makeCall(SqlStdOperatorTable.ARRAY_VALUE_CONSTRUCTOR, operands); |
| case MULTISET: |
| final MultisetSqlType multisetType = (MultisetSqlType) type; |
| operands = new ArrayList<>(); |
| for (Object entry : (List) value) { |
| final RexNode e = entry instanceof RexLiteral |
| ? (RexNode) entry |
| : makeLiteral(entry, multisetType.getComponentType(), allowCast); |
| operands.add(e); |
| } |
| if (allowCast) { |
| return makeCall(SqlStdOperatorTable.MULTISET_VALUE, operands); |
| } else { |
| return new RexLiteral((Comparable) FlatLists.of(operands), type, |
| sqlTypeName); |
| } |
| case ROW: |
| operands = new ArrayList<>(); |
| //noinspection unchecked |
| for (Pair<RelDataTypeField, Object> pair |
| : Pair.zip(type.getFieldList(), (List<Object>) value)) { |
| final RexNode e = pair.right instanceof RexLiteral |
| ? (RexNode) pair.right |
| : makeLiteral(pair.right, pair.left.getType(), allowCast); |
| operands.add(e); |
| } |
| return new RexLiteral((Comparable) FlatLists.of(operands), type, |
| sqlTypeName); |
| case GEOMETRY: |
| return new RexLiteral((Comparable) value, guessType(value), |
| SqlTypeName.GEOMETRY); |
| case ANY: |
| return makeLiteral(value, guessType(value), allowCast); |
| default: |
| throw new IllegalArgumentException( |
| "Cannot create literal for type '" + sqlTypeName + "'"); |
| } |
| } |
| |
| /** Converts the type of a value to comply with |
| * {@link org.apache.calcite.rex.RexLiteral#valueMatchesType}. |
| * |
| * <p>Returns null if and only if {@code o} is null. */ |
| private static @PolyNull Object clean(@PolyNull Object o, RelDataType type) { |
| if (o == null) { |
| return o; |
| } |
| switch (type.getSqlTypeName()) { |
| case TINYINT: |
| case SMALLINT: |
| case INTEGER: |
| case BIGINT: |
| case DECIMAL: |
| 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 (o instanceof BigDecimal) { |
| return o; |
| } |
| assert !(o instanceof Float || o instanceof Double) |
| : String.format(Locale.ROOT, |
| "%s is not compatible with %s, try to use makeExactLiteral", |
| o.getClass().getCanonicalName(), |
| type.getSqlTypeName()); |
| return new BigDecimal(((Number) o).longValue()); |
| case FLOAT: |
| if (o instanceof BigDecimal) { |
| return o; |
| } |
| return new BigDecimal(((Number) o).doubleValue(), MathContext.DECIMAL32) |
| .stripTrailingZeros(); |
| case REAL: |
| case DOUBLE: |
| if (o instanceof BigDecimal) { |
| return o; |
| } |
| return new BigDecimal(((Number) o).doubleValue(), MathContext.DECIMAL64) |
| .stripTrailingZeros(); |
| case CHAR: |
| case VARCHAR: |
| if (o instanceof NlsString) { |
| return o; |
| } |
| assert type.getCharset() != null : type + ".getCharset() must not be null"; |
| return new NlsString((String) o, type.getCharset().name(), |
| type.getCollation()); |
| case TIME: |
| if (o instanceof TimeString) { |
| return o; |
| } else if (o instanceof Calendar) { |
| if (!((Calendar) o).getTimeZone().equals(DateTimeUtils.UTC_ZONE)) { |
| throw new AssertionError(); |
| } |
| return TimeString.fromCalendarFields((Calendar) o); |
| } else { |
| return TimeString.fromMillisOfDay((Integer) o); |
| } |
| case TIME_WITH_LOCAL_TIME_ZONE: |
| if (o instanceof TimeString) { |
| return o; |
| } else { |
| return TimeString.fromMillisOfDay((Integer) o); |
| } |
| case DATE: |
| if (o instanceof DateString) { |
| return o; |
| } else if (o instanceof Calendar) { |
| if (!((Calendar) o).getTimeZone().equals(DateTimeUtils.UTC_ZONE)) { |
| throw new AssertionError(); |
| } |
| return DateString.fromCalendarFields((Calendar) o); |
| } else { |
| return DateString.fromDaysSinceEpoch((Integer) o); |
| } |
| case TIMESTAMP: |
| if (o instanceof TimestampString) { |
| return o; |
| } else if (o instanceof Calendar) { |
| if (!((Calendar) o).getTimeZone().equals(DateTimeUtils.UTC_ZONE)) { |
| throw new AssertionError(); |
| } |
| return TimestampString.fromCalendarFields((Calendar) o); |
| } else { |
| return TimestampString.fromMillisSinceEpoch((Long) o); |
| } |
| case TIMESTAMP_WITH_LOCAL_TIME_ZONE: |
| if (o instanceof TimestampString) { |
| return o; |
| } else { |
| return TimestampString.fromMillisSinceEpoch((Long) o); |
| } |
| default: |
| return o; |
| } |
| } |
| |
| private RelDataType guessType(@Nullable Object value) { |
| if (value == null) { |
| return typeFactory.createSqlType(SqlTypeName.NULL); |
| } |
| if (value instanceof Float || value instanceof Double) { |
| return typeFactory.createSqlType(SqlTypeName.DOUBLE); |
| } |
| if (value instanceof Number) { |
| return typeFactory.createSqlType(SqlTypeName.BIGINT); |
| } |
| if (value instanceof Boolean) { |
| return typeFactory.createSqlType(SqlTypeName.BOOLEAN); |
| } |
| if (value instanceof String) { |
| return typeFactory.createSqlType(SqlTypeName.CHAR, |
| ((String) value).length()); |
| } |
| if (value instanceof ByteString) { |
| return typeFactory.createSqlType(SqlTypeName.BINARY, |
| ((ByteString) value).length()); |
| } |
| if (value instanceof Geometries.Geom) { |
| return typeFactory.createSqlType(SqlTypeName.GEOMETRY); |
| } |
| throw new AssertionError("unknown type " + value.getClass()); |
| } |
| |
| /** Returns an {@link NlsString} with spaces to make it at least a given |
| * length. */ |
| private static NlsString padRight(NlsString s, int length) { |
| if (s.getValue().length() >= length) { |
| return s; |
| } |
| return s.copy(padRight(s.getValue(), length)); |
| } |
| |
| /** Returns a string padded with spaces to make it at least a given length. */ |
| private static String padRight(String s, int length) { |
| if (s.length() >= length) { |
| return s; |
| } |
| return new StringBuilder() |
| .append(s) |
| .append(Spaces.MAX, s.length(), length) |
| .toString(); |
| } |
| |
| /** Returns a byte-string padded with zero bytes to make it at least a given |
| * length. */ |
| private static ByteString padRight(ByteString s, int length) { |
| if (s.length() >= length) { |
| return s; |
| } |
| return new ByteString(Arrays.copyOf(s.getBytes(), length)); |
| } |
| } |