blob: 67367ba3f4153b62477a4cdd1d4d5a5f3ae1f83d [file] [log] [blame]
/*
* 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.sql.type;
import org.apache.calcite.avatica.util.TimeUnitRange;
import org.apache.calcite.rel.type.RelDataType;
import org.apache.calcite.rel.type.RelDataTypeComparability;
import org.apache.calcite.rel.type.RelDataTypeFactory;
import org.apache.calcite.sql.SqlCall;
import org.apache.calcite.sql.SqlCallBinding;
import org.apache.calcite.sql.SqlIdentifier;
import org.apache.calcite.sql.SqlIntervalQualifier;
import org.apache.calcite.sql.SqlLambda;
import org.apache.calcite.sql.SqlLiteral;
import org.apache.calcite.sql.SqlNode;
import org.apache.calcite.sql.SqlOperandCountRange;
import org.apache.calcite.sql.SqlOperator;
import org.apache.calcite.sql.SqlOperatorBinding;
import org.apache.calcite.sql.SqlUtil;
import org.apache.calcite.sql.util.SqlBasicVisitor;
import org.apache.calcite.sql.validate.SqlLambdaScope;
import org.apache.calcite.sql.validate.SqlValidator;
import org.apache.calcite.sql.validate.SqlValidatorScope;
import org.apache.calcite.util.ImmutableIntList;
import org.apache.calcite.util.Pair;
import org.apache.calcite.util.Util;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.IntFunction;
import java.util.function.Predicate;
import static com.google.common.base.Preconditions.checkArgument;
import static org.apache.calcite.sql.type.NonNullableAccessors.getKeyTypeOrThrow;
import static org.apache.calcite.util.Static.RESOURCE;
import static java.util.Objects.requireNonNull;
/**
* Strategies for checking operand types.
*
* <p>This class defines singleton instances of strategy objects for operand
* type-checking. {@link org.apache.calcite.sql.type.ReturnTypes}
* and {@link org.apache.calcite.sql.type.InferTypes} provide similar strategies
* for operand type inference and operator return type inference.
*
* <p>Note to developers: avoid anonymous inner classes here except for unique,
* non-generalizable strategies; anything else belongs in a reusable top-level
* class. If you find yourself copying and pasting an existing strategy's
* anonymous inner class, you're making a mistake.
*
* @see org.apache.calcite.sql.type.SqlOperandTypeChecker
* @see org.apache.calcite.sql.type.ReturnTypes
* @see org.apache.calcite.sql.type.InferTypes
*/
public abstract class OperandTypes {
private OperandTypes() {
}
/**
* Creates a checker that passes if each operand is a member of a
* corresponding family.
*/
public static FamilyOperandTypeChecker family(SqlTypeFamily... families) {
return new FamilyOperandTypeChecker(ImmutableList.copyOf(families),
i -> false);
}
/**
* Creates a checker that passes if each operand is a member of a
* corresponding family, and allows specified parameters to be optional.
*/
public static FamilyOperandTypeChecker family(List<SqlTypeFamily> families,
Predicate<Integer> optional) {
return new FamilyOperandTypeChecker(families, optional);
}
/**
* Creates a checker that passes if each operand is a member of a
* corresponding family.
*/
public static FamilyOperandTypeChecker family(List<SqlTypeFamily> families) {
return family(families, i -> false);
}
/**
* Creates a checker that passes if the operand is a function with
* a given return type and parameter types. This method can be used
* to check a lambda expression.
*/
public static SqlSingleOperandTypeChecker function(SqlTypeFamily returnTypeFamily,
SqlTypeFamily... paramTypeFamilies) {
return new LambdaFamilyOperandTypeChecker(
returnTypeFamily, ImmutableList.copyOf(paramTypeFamilies));
}
/**
* Creates a checker that passes if the operand is a function with
* a given return type and parameter types. This method can be used
* to check a lambda expression.
*/
public static SqlSingleOperandTypeChecker function(SqlTypeFamily returnTypeFamily,
List<SqlTypeFamily> paramTypeFamilies) {
return new LambdaFamilyOperandTypeChecker(returnTypeFamily, paramTypeFamilies);
}
/**
* Creates a single-operand checker that passes if the operand's type has a
* particular {@link SqlTypeName}.
*/
public static SqlSingleOperandTypeChecker typeName(SqlTypeName typeName) {
return new TypeNameChecker(typeName);
}
/**
* Creates a checker that passes if the operand is an interval appropriate for
* a given date/time type. For example, the time frame HOUR is appropriate for
* type TIMESTAMP or DATE but not TIME.
*/
public static SqlSingleOperandTypeChecker interval(
Iterable<TimeUnitRange> ranges) {
final Set<TimeUnitRange> set = ImmutableSet.copyOf(ranges);
return new IntervalOperandTypeChecker(intervalQualifier ->
set.contains(intervalQualifier.timeUnitRange));
}
/**
* Creates a checker for DATE intervals (YEAR, WEEK, ISOWEEK,
* WEEK_WEDNESDAY, etc.)
*/
public static SqlSingleOperandTypeChecker dateInterval() {
return new IntervalOperandTypeChecker(SqlIntervalQualifier::isDate);
}
/**
* Creates a checker for TIME intervals (HOUR, SECOND, etc.)
*/
public static SqlSingleOperandTypeChecker timeInterval() {
return new IntervalOperandTypeChecker(SqlIntervalQualifier::isTime);
}
/**
* Creates a checker for TIMESTAMP intervals (YEAR, WEEK, ISOWEEK,
* WEEK_WEDNESDAY, HOUR, SECOND, etc.)
*/
public static SqlSingleOperandTypeChecker timestampInterval() {
return new IntervalOperandTypeChecker(SqlIntervalQualifier::isTimestamp);
}
/**
* Creates a checker for user-defined functions (including user-defined
* aggregate functions, table functions, and table macros).
*
* <p>Unlike built-in functions, there is a fixed number of parameters,
* and the parameters have names. You can ask for the type of a parameter
* without providing a particular call (and with it actual arguments) but you
* do need to provide a type factory, and therefore the types are only good
* for the duration of the current statement.
*/
public static SqlOperandMetadata operandMetadata(List<SqlTypeFamily> families,
Function<RelDataTypeFactory, List<RelDataType>> typesFactory,
IntFunction<String> operandName, Predicate<Integer> optional) {
return new OperandMetadataImpl(families, typesFactory, operandName,
optional);
}
/**
* Creates a checker that passes if any one of the rules passes.
*/
public static SqlOperandTypeChecker or(SqlOperandTypeChecker... rules) {
return composite(CompositeOperandTypeChecker.Composition.OR,
ImmutableList.copyOf(rules), null, null, null);
}
/**
* Creates a checker that passes if all of the rules pass.
*/
public static SqlOperandTypeChecker and(SqlOperandTypeChecker... rules) {
return and_(ImmutableList.copyOf(rules));
}
private static SqlOperandTypeChecker and_(
Iterable<SqlOperandTypeChecker> rules) {
return composite(CompositeOperandTypeChecker.Composition.AND,
ImmutableList.copyOf(rules), null, null, null);
}
/**
* Creates a single-operand checker that passes if any one of the rules
* passes.
*/
public static SqlSingleOperandTypeChecker or(
SqlSingleOperandTypeChecker... rules) {
return compositeSingle(CompositeOperandTypeChecker.Composition.OR,
ImmutableList.copyOf(rules), null);
}
/**
* Creates a single-operand checker that passes if all of the rules
* pass.
*/
public static SqlSingleOperandTypeChecker and(
SqlSingleOperandTypeChecker... rules) {
return compositeSingle(CompositeOperandTypeChecker.Composition.AND,
ImmutableList.copyOf(rules), null);
}
/** Creates a CompositeSingleOperandTypeChecker. Outside this package, use
* {@link SqlSingleOperandTypeChecker#and(SqlSingleOperandTypeChecker)},
* {@link OperandTypes#and}, {@link OperandTypes#or} and similar. */
private static SqlSingleOperandTypeChecker compositeSingle(
CompositeOperandTypeChecker.Composition composition,
List<? extends SqlSingleOperandTypeChecker> allowedRules,
@Nullable String allowedSignatures) {
final List<SqlSingleOperandTypeChecker> list = new ArrayList<>(allowedRules);
switch (composition) {
default:
break;
case AND:
case OR:
flatten(list, c -> c instanceof CompositeSingleOperandTypeChecker
&& ((CompositeSingleOperandTypeChecker) c).composition == composition
? ((CompositeSingleOperandTypeChecker) c).getRules()
: null);
}
if (list.size() == 1) {
return list.get(0);
}
return new CompositeSingleOperandTypeChecker(composition,
ImmutableList.copyOf(list), allowedSignatures);
}
/**
* Creates an operand checker from a sequence of single-operand checkers.
*/
public static SqlOperandTypeChecker sequence(String allowedSignatures,
SqlSingleOperandTypeChecker... rules) {
return new CompositeOperandTypeChecker(
CompositeOperandTypeChecker.Composition.SEQUENCE,
ImmutableList.copyOf(rules), allowedSignatures, null, null);
}
/**
* Creates an operand checker from a sequence of single-operand checkers,
* generating the signature from the components.
*/
public static SqlOperandTypeChecker sequence(
BiFunction<SqlOperator, String, String> signatureGenerator,
SqlSingleOperandTypeChecker... rules) {
return new CompositeOperandTypeChecker(
CompositeOperandTypeChecker.Composition.SEQUENCE,
ImmutableList.copyOf(rules), null, signatureGenerator, null);
}
/**
* Creates a checker that passes if all of the rules pass for each operand,
* using a given operand count strategy.
*/
public static SqlOperandTypeChecker repeat(SqlOperandCountRange range,
SqlSingleOperandTypeChecker... rules) {
return new CompositeOperandTypeChecker(
CompositeOperandTypeChecker.Composition.REPEAT,
ImmutableList.copyOf(rules), null, null, range);
}
/**
* Creates an operand checker that applies a single-operand checker to
* the {@code ordinal}th operand.
*/
public static SqlOperandTypeChecker nth(int ordinal, int operandCount,
SqlSingleOperandTypeChecker rule) {
SqlSingleOperandTypeChecker[] rules =
new SqlSingleOperandTypeChecker[operandCount];
Arrays.fill(rules, ANY);
rules[ordinal] = rule;
return sequence("", rules);
}
/** Creates a CompositeOperandTypeChecker. Outside this package, use
* {@link SqlSingleOperandTypeChecker#and(SqlSingleOperandTypeChecker)},
* {@link OperandTypes#and}, {@link OperandTypes#or} and similar. */
static SqlOperandTypeChecker composite(
CompositeOperandTypeChecker.Composition composition,
List<? extends SqlOperandTypeChecker> allowedRules,
@Nullable String allowedSignatures,
@Nullable BiFunction<SqlOperator, String, String> signatureGenerator,
@Nullable SqlOperandCountRange range) {
final List<SqlOperandTypeChecker> list = new ArrayList<>(allowedRules);
switch (composition) {
default:
break;
case AND:
case OR:
flatten(list, c -> c instanceof CompositeOperandTypeChecker
&& ((CompositeOperandTypeChecker) c).composition == composition
? ((CompositeOperandTypeChecker) c).getRules()
: null);
}
if (list.size() == 1) {
return list.get(0);
}
return new CompositeOperandTypeChecker(composition,
ImmutableList.copyOf(list), allowedSignatures, signatureGenerator, range);
}
/** Helper for {@link #compositeSingle} and {@link #composite}. */
private static <E> void flatten(List<E> list,
Function<E, @Nullable List<? extends E>> expander) {
for (int i = 0; i < list.size();) {
@Nullable List<? extends E> list2 = expander.apply(list.get(i));
if (list2 == null) {
++i;
} else {
list.remove(i);
list.addAll(i, list2);
}
}
}
// ----------------------------------------------------------------------
// SqlOperandTypeChecker definitions
// ----------------------------------------------------------------------
//~ Static fields/initializers ---------------------------------------------
/**
* Operand type-checking strategy for an operator which takes no operands.
*/
public static final SqlSingleOperandTypeChecker NILADIC = family();
/**
* Operand type-checking strategy for an operator with no restrictions on
* number or type of operands.
*/
public static final SqlOperandTypeChecker VARIADIC =
variadic(SqlOperandCountRanges.any());
/** Operand type-checking strategy that allows one or more operands. */
public static final SqlOperandTypeChecker ONE_OR_MORE =
variadic(SqlOperandCountRanges.from(1));
public static SqlOperandTypeChecker variadic(
final SqlOperandCountRange range) {
return new SqlOperandTypeChecker() {
@Override public boolean checkOperandTypes(
SqlCallBinding callBinding,
boolean throwOnFailure) {
return range.isValidCount(callBinding.getOperandCount());
}
@Override public SqlOperandCountRange getOperandCountRange() {
return range;
}
@Override public String getAllowedSignatures(SqlOperator op, String opName) {
return opName + "(...)";
}
};
}
public static final SqlSingleOperandTypeChecker BOOLEAN =
family(SqlTypeFamily.BOOLEAN);
public static final SqlSingleOperandTypeChecker BOOLEAN_BOOLEAN =
family(SqlTypeFamily.BOOLEAN, SqlTypeFamily.BOOLEAN);
public static final SqlSingleOperandTypeChecker NUMERIC =
family(SqlTypeFamily.NUMERIC);
public static final SqlSingleOperandTypeChecker INTEGER =
family(SqlTypeFamily.INTEGER);
public static final SqlSingleOperandTypeChecker NUMERIC_OPTIONAL_NUMERIC =
family(ImmutableList.of(SqlTypeFamily.NUMERIC, SqlTypeFamily.NUMERIC),
// Second operand optional (operand index 0, 1)
number -> number == 1);
public static final SqlSingleOperandTypeChecker NUMERIC_OPTIONAL_INTEGER =
family(ImmutableList.of(SqlTypeFamily.NUMERIC, SqlTypeFamily.INTEGER),
// Second operand optional (operand index 0, 1)
number -> number == 1);
public static final SqlOperandTypeChecker NUMERIC_INT32 =
sequence(
(operator, name) -> operator.getName() + "(<NUMERIC>, <INTEGER>)",
family(SqlTypeFamily.NUMERIC),
// Only 32-bit integer allowed for the second argument
new TypeNameChecker(SqlTypeName.INTEGER));
public static final SqlSingleOperandTypeChecker NUMERIC_CHARACTER =
family(SqlTypeFamily.NUMERIC, SqlTypeFamily.CHARACTER);
public static final SqlSingleOperandTypeChecker NUMERIC_INTEGER =
family(SqlTypeFamily.NUMERIC, SqlTypeFamily.INTEGER);
public static final SqlSingleOperandTypeChecker NUMERIC_NUMERIC =
family(SqlTypeFamily.NUMERIC, SqlTypeFamily.NUMERIC);
public static final SqlSingleOperandTypeChecker EXACT_NUMERIC =
family(SqlTypeFamily.EXACT_NUMERIC);
public static final SqlSingleOperandTypeChecker EXACT_NUMERIC_EXACT_NUMERIC =
family(SqlTypeFamily.EXACT_NUMERIC, SqlTypeFamily.EXACT_NUMERIC);
public static final SqlSingleOperandTypeChecker BINARY =
family(SqlTypeFamily.BINARY);
public static final SqlSingleOperandTypeChecker BINARY_BINARY =
family(SqlTypeFamily.BINARY, SqlTypeFamily.BINARY);
public static final SqlSingleOperandTypeChecker STRING =
family(SqlTypeFamily.STRING);
public static final FamilyOperandTypeChecker STRING_STRING =
family(SqlTypeFamily.STRING, SqlTypeFamily.STRING);
public static final FamilyOperandTypeChecker STRING_OPTIONAL_STRING =
family(
ImmutableList.of(SqlTypeFamily.STRING, SqlTypeFamily.STRING),
// Second operand optional (operand index 0, 1)
number -> number == 1);
public static final FamilyOperandTypeChecker STRING_STRING_STRING =
family(SqlTypeFamily.STRING, SqlTypeFamily.STRING, SqlTypeFamily.STRING);
public static final FamilyOperandTypeChecker STRING_STRING_OPTIONAL_STRING =
family(
ImmutableList.of(SqlTypeFamily.STRING, SqlTypeFamily.STRING,
SqlTypeFamily.STRING),
// Third operand optional (operand index 0, 1, 2)
number -> number == 2);
public static final FamilyOperandTypeChecker STRING_OPTIONAL_STRING_OPTIONAL_STRING =
family(
ImmutableList.of(SqlTypeFamily.STRING, SqlTypeFamily.STRING,
SqlTypeFamily.STRING),
// Second and Third operand both are optional (operand index 0, 1, 2)
number -> number == 1 || number == 2);
public static final FamilyOperandTypeChecker STRING_NUMERIC_OPTIONAL_STRING =
family(
ImmutableList.of(SqlTypeFamily.STRING, SqlTypeFamily.NUMERIC,
SqlTypeFamily.STRING),
// Third operand optional (operand index 0, 1, 2)
number -> number == 2);
public static final SqlSingleOperandTypeChecker CHARACTER =
family(SqlTypeFamily.CHARACTER);
public static final SqlSingleOperandTypeChecker DATETIME =
family(SqlTypeFamily.DATETIME);
public static final SqlSingleOperandTypeChecker DATE =
family(SqlTypeFamily.DATE);
public static final SqlSingleOperandTypeChecker TIME =
family(SqlTypeFamily.TIME);
public static final SqlSingleOperandTypeChecker TIMESTAMP =
family(SqlTypeFamily.TIMESTAMP);
public static final SqlSingleOperandTypeChecker DATE_OR_TIMESTAMP =
DATE.or(TIMESTAMP);
/** Type-checker that matches "TIMESTAMP WITH LOCAL TIME ZONE" but not other
* members of the "TIMESTAMP" family (e.g. "TIMESTAMP"). */
public static final SqlSingleOperandTypeChecker TIMESTAMP_LTZ =
new TypeNameChecker(SqlTypeName.TIMESTAMP_WITH_LOCAL_TIME_ZONE);
/** Type-checker that matches "TIMESTAMP" but not other members of the
* "TIMESTAMP" family (e.g. "TIMESTAMP WITH LOCAL TIME ZONE"). */
public static final SqlSingleOperandTypeChecker TIMESTAMP_NTZ =
new TypeNameChecker(SqlTypeName.TIMESTAMP);
public static final SqlSingleOperandTypeChecker INTERVAL =
family(SqlTypeFamily.DATETIME_INTERVAL);
public static final SqlSingleOperandTypeChecker CHARACTER_CHARACTER =
family(SqlTypeFamily.CHARACTER, SqlTypeFamily.CHARACTER);
public static final SqlSingleOperandTypeChecker CHARACTER_CHARACTER_DATETIME =
family(SqlTypeFamily.CHARACTER, SqlTypeFamily.CHARACTER, SqlTypeFamily.DATETIME);
public static final SqlSingleOperandTypeChecker CHARACTER_DATE =
family(SqlTypeFamily.CHARACTER, SqlTypeFamily.DATE);
public static final SqlSingleOperandTypeChecker CHARACTER_TIME =
family(SqlTypeFamily.CHARACTER, SqlTypeFamily.TIME);
public static final SqlSingleOperandTypeChecker PERIOD =
new PeriodOperandTypeChecker();
public static final SqlSingleOperandTypeChecker PERIOD_OR_DATETIME =
PERIOD.or(DATETIME);
public static final FamilyOperandTypeChecker INTERVAL_INTERVAL =
family(SqlTypeFamily.DATETIME_INTERVAL, SqlTypeFamily.DATETIME_INTERVAL);
public static final SqlSingleOperandTypeChecker MULTISET =
family(SqlTypeFamily.MULTISET);
public static final SqlSingleOperandTypeChecker ARRAY =
family(SqlTypeFamily.ARRAY);
public static final SqlSingleOperandTypeChecker ARRAY_ARRAY =
family(SqlTypeFamily.ARRAY, SqlTypeFamily.ARRAY);
public static final SqlSingleOperandTypeChecker ARRAY_OR_MAP =
OperandTypes.family(SqlTypeFamily.ARRAY)
.or(OperandTypes.family(SqlTypeFamily.MAP))
.or(OperandTypes.family(SqlTypeFamily.ANY));
public static final SqlOperandTypeChecker STRING_ARRAY_CHARACTER_OPTIONAL_CHARACTER =
new FamilyOperandTypeChecker(
ImmutableList.of(SqlTypeFamily.ARRAY, SqlTypeFamily.CHARACTER, SqlTypeFamily.CHARACTER),
i -> i == 2) {
@SuppressWarnings("argument.type.incompatible")
@Override public boolean checkOperandTypes(
SqlCallBinding callBinding,
boolean throwOnFailure) {
if (!super.checkOperandTypes(callBinding, throwOnFailure)) {
return false;
}
RelDataType elementType = callBinding.getOperandType(0).getComponentType();
if (elementType == null || !SqlTypeUtil.isString(elementType)) {
if (throwOnFailure) {
throw callBinding.newValidationSignatureError();
}
return false;
}
return true;
}
@Override public String getAllowedSignatures(SqlOperator op, String opName) {
return opName + "(<STRING ARRAY>, <CHARACTER>[, <CHARACTER>])";
}
};
public static final SqlSingleOperandTypeChecker ARRAY_OF_INTEGER =
new FamilyOperandTypeChecker(ImmutableList.of(SqlTypeFamily.ARRAY), i -> false) {
@Override public boolean checkSingleOperandType(SqlCallBinding callBinding, SqlNode operand,
int iFormalOperand, SqlTypeFamily family, boolean throwOnFailure) {
if (!super.checkSingleOperandType(
callBinding,
operand,
iFormalOperand,
family,
throwOnFailure)) {
return false;
}
RelDataType type = SqlTypeUtil.deriveType(callBinding, operand);
if (SqlTypeUtil.isNull(type)) {
return true;
}
RelDataType componentType =
requireNonNull(type.getComponentType(), "componentType");
if (SqlTypeUtil.isIntType(componentType) || SqlTypeUtil.isNull(componentType)) {
return true;
}
if (throwOnFailure) {
throw callBinding.newValidationSignatureError();
}
return false;
}
@Override public String getAllowedSignatures(SqlOperator op, String opName) {
return opName + "(<INTEGER ARRAY>)";
}
};
/** Checks that returns whether a value is a multiset or an array.
* Cf Java, where list and set are collections but a map is not. */
public static final SqlSingleOperandTypeChecker COLLECTION =
family(SqlTypeFamily.MULTISET).or(family(SqlTypeFamily.ARRAY));
public static final SqlSingleOperandTypeChecker COLLECTION_OR_MAP =
family(SqlTypeFamily.MULTISET)
.or(family(SqlTypeFamily.ARRAY))
.or(family(SqlTypeFamily.MAP));
public static final SqlSingleOperandTypeChecker MAP =
family(SqlTypeFamily.MAP);
public static final SqlOperandTypeChecker ARRAY_FUNCTION =
new ArrayFunctionOperandTypeChecker();
public static final SqlOperandTypeChecker ARRAY_ELEMENT =
new ArrayElementOperandTypeChecker(true, true);
public static final SqlOperandTypeChecker ARRAY_ELEMENT_NONNULL =
new ArrayElementOperandTypeChecker(false, true);
/** Type checker that accepts an ARRAY as the first argument, but not
* an expression with type NULL (i.e. a NULL literal). */
public static final SqlOperandTypeChecker ARRAY_NONNULL =
family(SqlTypeFamily.ARRAY).and(new NotNullOperandTypeChecker(1, false));
public static final SqlOperandTypeChecker ARRAY_INSERT =
new ArrayInsertOperandTypeChecker();
public static final SqlSingleOperandTypeChecker MAP_FROM_ENTRIES =
new MapFromEntriesOperandTypeChecker();
public static final SqlSingleOperandTypeChecker MAP_FUNCTION =
new MapFunctionOperandTypeChecker();
public static final SqlOperandTypeChecker MAP_KEY =
new MapKeyOperandTypeChecker();
/**
* Operand type-checking strategy where type must be a literal or NULL.
*/
public static final SqlSingleOperandTypeChecker NULLABLE_LITERAL =
new LiteralOperandTypeChecker(true);
/**
* Operand type-checking strategy type must be a non-NULL literal.
*/
public static final SqlSingleOperandTypeChecker LITERAL =
new LiteralOperandTypeChecker(false);
/**
* Operand type-checking strategy where all types must be non-NULL value.
*/
public static final SqlSingleOperandTypeChecker NONNULL_NONNULL =
new NotNullOperandTypeChecker(2, false);
/**
* Operand type-checking strategy type must be a boolean non-NULL literal.
*/
public static final SqlSingleOperandTypeChecker BOOLEAN_LITERAL =
new FamilyOperandTypeChecker(ImmutableList.of(SqlTypeFamily.BOOLEAN),
i -> false) {
@Override public boolean checkSingleOperandType(
SqlCallBinding callBinding,
SqlNode operand,
int iFormalOperand,
SqlTypeFamily family,
boolean throwOnFailure) {
if (!LITERAL.checkSingleOperandType(
callBinding,
operand,
iFormalOperand,
throwOnFailure)) {
return false;
}
if (!super.checkSingleOperandType(
callBinding,
operand,
iFormalOperand,
family,
throwOnFailure)) {
return false;
}
final SqlLiteral arg = (SqlLiteral) operand;
final boolean isBooleanLiteral =
SqlLiteral.valueMatchesType(arg.getValue(), SqlTypeName.BOOLEAN);
if (!isBooleanLiteral) {
if (throwOnFailure) {
throw callBinding.newError(
RESOURCE.argumentMustBeBooleanLiteral(
callBinding.getOperator().getName()));
}
return false;
}
return true;
}
};
/**
* Operand type-checking strategy where the first operand must be array and
* the second operand must be a boolean non-NULL literal.
*/
public static final SqlSingleOperandTypeChecker
ARRAY_BOOLEAN_LITERAL =
new FamilyOperandTypeChecker(
ImmutableList.of(SqlTypeFamily.ARRAY, SqlTypeFamily.BOOLEAN),
i -> false) {
@Override public boolean checkSingleOperandType(
SqlCallBinding callBinding, SqlNode operand,
int iFormalOperand, SqlTypeFamily family,
boolean throwOnFailure) {
if (iFormalOperand == 0) {
return super.checkSingleOperandType(callBinding, operand,
iFormalOperand, family, throwOnFailure);
}
return BOOLEAN_LITERAL.checkSingleOperandType(
callBinding, operand, iFormalOperand, throwOnFailure);
}
};
/**
* Operand type-checking strategy type must be a positive integer non-NULL
* literal.
*/
public static final SqlSingleOperandTypeChecker POSITIVE_INTEGER_LITERAL =
new FamilyOperandTypeChecker(ImmutableList.of(SqlTypeFamily.INTEGER),
i -> false) {
@Override public boolean checkSingleOperandType(
SqlCallBinding callBinding,
SqlNode operand,
int iFormalOperand,
SqlTypeFamily family,
boolean throwOnFailure) {
if (!LITERAL.checkSingleOperandType(
callBinding,
operand,
iFormalOperand,
throwOnFailure)) {
return false;
}
if (!super.checkSingleOperandType(
callBinding,
operand,
iFormalOperand,
family,
throwOnFailure)) {
return false;
}
final SqlLiteral arg = (SqlLiteral) operand;
final BigDecimal value = arg.getValueAs(BigDecimal.class);
if (value.compareTo(BigDecimal.ZERO) < 0
|| hasFractionalPart(value)) {
if (throwOnFailure) {
throw callBinding.newError(
RESOURCE.argumentMustBePositiveInteger(
callBinding.getOperator().getName()));
}
return false;
}
if (value.compareTo(BigDecimal.valueOf(Integer.MAX_VALUE)) > 0) {
if (throwOnFailure) {
throw callBinding.newError(
RESOURCE.numberLiteralOutOfRange(value.toString()));
}
return false;
}
return true;
}
/** Returns whether a number has any fractional part.
*
* @see BigDecimal#longValueExact() */
private boolean hasFractionalPart(BigDecimal bd) {
return bd.precision() - bd.scale() <= 0;
}
};
/**
* Operand type-checking strategy where type must be a numeric non-NULL
* literal in the range 0 to 1 inclusive.
*/
public static final SqlSingleOperandTypeChecker UNIT_INTERVAL_NUMERIC_LITERAL =
new FamilyOperandTypeChecker(ImmutableList.of(SqlTypeFamily.NUMERIC),
i -> false) {
@Override public boolean checkSingleOperandType(
SqlCallBinding callBinding, SqlNode operand,
int iFormalOperand, SqlTypeFamily family, boolean throwOnFailure) {
if (!LITERAL.checkSingleOperandType(callBinding, operand,
0, throwOnFailure)) {
return false;
}
if (!super.checkSingleOperandType(callBinding, operand,
iFormalOperand, family, throwOnFailure)) {
return false;
}
final SqlLiteral arg = (SqlLiteral) operand;
final BigDecimal value = arg.getValueAs(BigDecimal.class);
if (value.compareTo(BigDecimal.ZERO) < 0
|| value.compareTo(BigDecimal.ONE) > 0) {
if (throwOnFailure) {
throw callBinding.newError(
RESOURCE.argumentMustBeNumericLiteralInRange(
callBinding.getOperator().getName(), 0, 1));
}
return false;
}
return true;
}
};
/**
* Operand type-checking strategy where the first operand must be numeric and
* the second operand must be a numeric non-NULL literal in the range 0 to 1
* inclusive.
*/
public static final SqlSingleOperandTypeChecker
NUMERIC_UNIT_INTERVAL_NUMERIC_LITERAL =
new FamilyOperandTypeChecker(
ImmutableList.of(SqlTypeFamily.NUMERIC, SqlTypeFamily.NUMERIC),
i -> false) {
@Override public boolean checkSingleOperandType(
SqlCallBinding callBinding, SqlNode operand,
int iFormalOperand, SqlTypeFamily family, boolean throwOnFailure) {
if (iFormalOperand == 0) {
return super.checkSingleOperandType(callBinding, operand,
iFormalOperand, family, throwOnFailure);
}
return UNIT_INTERVAL_NUMERIC_LITERAL.checkSingleOperandType(
callBinding, operand, iFormalOperand, throwOnFailure);
}
};
/**
* Operand type-checking strategy where two operands must both be in the
* same type family.
*/
public static final SqlSingleOperandTypeChecker SAME_SAME =
new SameOperandTypeChecker(2);
public static final SqlSingleOperandTypeChecker SAME_SAME_INTEGER =
new SameOperandTypeExceptLastOperandChecker(3, "INTEGER");
/**
* Operand type-checking strategy where three operands must all be in the
* same type family.
*/
public static final SqlSingleOperandTypeChecker SAME_SAME_SAME =
new SameOperandTypeChecker(3);
/**
* Operand type-checking strategy where any number of operands must all be
* in the same type family.
*/
public static final SqlOperandTypeChecker SAME_VARIADIC =
new SameOperandTypeChecker(-1);
/**
* Operand type-checking strategy where any positive number of operands must all be
* in the same type family.
*/
public static final SqlOperandTypeChecker AT_LEAST_ONE_SAME_VARIADIC =
new SameOperandTypeChecker(-1) {
@Override public SqlOperandCountRange getOperandCountRange() {
return SqlOperandCountRanges.from(1);
}
};
/**
* Operand type-checking strategy where a given list of operands must all
* have the same type.
*/
public static SqlSingleOperandTypeChecker same(int operandCount,
int... ordinals) {
final ImmutableIntList ordinalList = ImmutableIntList.of(ordinals);
checkArgument(ordinalList.size() >= 2);
checkArgument(Util.isDistinct(ordinalList));
return new SameOperandTypeChecker(operandCount) {
@Override protected List<Integer> getOperandList(int operandCount) {
return ordinalList;
}
};
}
/**
* Operand type-checking strategy where operand types must allow ordered
* comparisons.
*/
public static final SqlOperandTypeChecker COMPARABLE_ORDERED_COMPARABLE_ORDERED =
new ComparableOperandTypeChecker(2, RelDataTypeComparability.ALL,
SqlOperandTypeChecker.Consistency.COMPARE);
/**
* Operand type-checking strategy where operand type must allow ordered
* comparisons. Used when instance comparisons are made on single operand
* functions
*/
public static final SqlOperandTypeChecker COMPARABLE_ORDERED =
new ComparableOperandTypeChecker(1, RelDataTypeComparability.ALL,
SqlOperandTypeChecker.Consistency.NONE);
/**
* Operand type-checking strategy where operand types must allow unordered
* comparisons.
*/
public static final SqlOperandTypeChecker COMPARABLE_UNORDERED_COMPARABLE_UNORDERED =
new ComparableOperandTypeChecker(2, RelDataTypeComparability.UNORDERED,
SqlOperandTypeChecker.Consistency.LEAST_RESTRICTIVE);
/**
* Operand type-checking strategy where two operands must both be in the
* same string type family.
*/
public static final SqlSingleOperandTypeChecker STRING_SAME_SAME =
STRING_STRING.and(SAME_SAME);
/**
* Operand type-checking strategy where three operands must all be in the
* same string type family.
*/
public static final SqlSingleOperandTypeChecker STRING_SAME_SAME_SAME =
STRING_STRING_STRING.and(SAME_SAME_SAME);
public static final SqlSingleOperandTypeChecker STRING_STRING_INTEGER =
family(SqlTypeFamily.STRING, SqlTypeFamily.STRING, SqlTypeFamily.INTEGER);
public static final SqlSingleOperandTypeChecker STRING_STRING_INTEGER_INTEGER =
family(SqlTypeFamily.STRING, SqlTypeFamily.STRING,
SqlTypeFamily.INTEGER, SqlTypeFamily.INTEGER);
public static final SqlSingleOperandTypeChecker STRING_STRING_OPTIONAL_INTEGER_OPTIONAL_INTEGER =
family(
ImmutableList.of(SqlTypeFamily.STRING, SqlTypeFamily.STRING, SqlTypeFamily.INTEGER,
SqlTypeFamily.INTEGER), i -> i == 2 || i == 3);
public static final SqlSingleOperandTypeChecker
STRING_STRING_OPTIONAL_INTEGER_OPTIONAL_INTEGER_OPTIONAL_INTEGER =
family(
ImmutableList.of(SqlTypeFamily.STRING, SqlTypeFamily.STRING, SqlTypeFamily.INTEGER,
SqlTypeFamily.INTEGER, SqlTypeFamily.INTEGER), i -> i == 2 || i == 3 || i == 4);
public static final SqlSingleOperandTypeChecker STRING_INTEGER =
family(SqlTypeFamily.STRING, SqlTypeFamily.INTEGER);
public static final SqlSingleOperandTypeChecker STRING_INTEGER_INTEGER =
family(SqlTypeFamily.STRING, SqlTypeFamily.INTEGER,
SqlTypeFamily.INTEGER);
public static final SqlSingleOperandTypeChecker STRING_INTEGER_OPTIONAL_INTEGER =
family(
ImmutableList.of(SqlTypeFamily.STRING, SqlTypeFamily.INTEGER,
SqlTypeFamily.INTEGER), i -> i == 2);
public static final SqlSingleOperandTypeChecker STRING_NUMERIC =
family(SqlTypeFamily.STRING, SqlTypeFamily.NUMERIC);
public static final SqlSingleOperandTypeChecker STRING_NUMERIC_NUMERIC =
family(SqlTypeFamily.STRING, SqlTypeFamily.NUMERIC, SqlTypeFamily.NUMERIC);
/** Operand type-checking strategy where the first operand is a character or
* binary string (CHAR, VARCHAR, BINARY or VARBINARY), and the second operand
* is INTEGER. */
public static final SqlSingleOperandTypeChecker CBSTRING_INTEGER =
family(SqlTypeFamily.STRING, SqlTypeFamily.INTEGER)
.or(family(SqlTypeFamily.BINARY, SqlTypeFamily.INTEGER));
/**
* Operand type-checking strategy where two operands must both be in the
* same string type family and last type is INTEGER.
*/
public static final SqlSingleOperandTypeChecker STRING_SAME_SAME_INTEGER =
STRING_STRING_INTEGER.and(SAME_SAME_INTEGER);
public static final SqlSingleOperandTypeChecker STRING_SAME_SAME_OR_ARRAY_SAME_SAME =
or(STRING_SAME_SAME,
and(OperandTypes.SAME_SAME, family(SqlTypeFamily.ARRAY, SqlTypeFamily.ARRAY)));
public static final SqlSingleOperandTypeChecker ANY =
family(SqlTypeFamily.ANY);
public static final SqlSingleOperandTypeChecker ANY_ANY =
family(SqlTypeFamily.ANY, SqlTypeFamily.ANY);
public static final SqlSingleOperandTypeChecker ANY_IGNORE =
family(SqlTypeFamily.ANY, SqlTypeFamily.IGNORE);
public static final SqlSingleOperandTypeChecker IGNORE_ANY =
family(SqlTypeFamily.IGNORE, SqlTypeFamily.ANY);
public static final SqlSingleOperandTypeChecker ANY_NUMERIC =
family(SqlTypeFamily.ANY, SqlTypeFamily.NUMERIC);
public static final SqlSingleOperandTypeChecker ANY_NUMERIC_ANY =
family(SqlTypeFamily.ANY, SqlTypeFamily.NUMERIC, SqlTypeFamily.ANY);
public static final SqlSingleOperandTypeChecker ANY_STRING_STRING =
family(SqlTypeFamily.ANY, SqlTypeFamily.STRING, SqlTypeFamily.STRING);
public static final SqlSingleOperandTypeChecker ANY_STRING_OPTIONAL_STRING =
family(ImmutableList.of(SqlTypeFamily.ANY, SqlTypeFamily.STRING, SqlTypeFamily.STRING),
// Third operand optional (operand index 0, 1, 2)
number -> number == 2);
/**
* Operand type-checking strategy used by {@code ARG_MIN(value, comp)} and
* similar functions, where the first operand can have any type and the second
* must be comparable.
*/
public static final SqlOperandTypeChecker ANY_COMPARABLE =
new SqlOperandTypeChecker() {
@Override public boolean checkOperandTypes(SqlCallBinding callBinding,
boolean throwOnFailure) {
getOperandCountRange().isValidCount(callBinding.getOperandCount());
RelDataType type = callBinding.getOperandType(1);
return type.getComparability() == RelDataTypeComparability.ALL;
}
@Override public SqlOperandCountRange getOperandCountRange() {
return SqlOperandCountRanges.of(2);
}
@Override public String getAllowedSignatures(SqlOperator op, String opName) {
return opName + "(<ANY>, <COMPARABLE_TYPE>)";
}
};
public static final SqlSingleOperandTypeChecker CURSOR =
family(SqlTypeFamily.CURSOR);
public static final SqlOperandTypeChecker MEASURE =
new FamilyOperandTypeChecker(ImmutableList.of(SqlTypeFamily.ANY),
i -> false) {
@Override public boolean checkSingleOperandType(
SqlCallBinding callBinding, SqlNode operand,
int iFormalOperand, SqlTypeFamily family, boolean throwOnFailure) {
if (!super.checkSingleOperandType(callBinding, operand, iFormalOperand, family,
throwOnFailure)) {
return false;
}
final SqlValidatorScope scope = callBinding.getScope();
if (!scope.isMeasureRef(operand)) {
if (throwOnFailure) {
throw callBinding.newValidationError(
RESOURCE.argumentMustBeMeasure(
callBinding.getOperator().getName()));
}
return false;
}
return true;
}
};
/**
* Parameter type-checking strategy where type must a nullable time interval,
* nullable time interval.
*/
public static final SqlSingleOperandTypeChecker INTERVAL_SAME_SAME =
INTERVAL_INTERVAL.and(SAME_SAME);
public static final SqlSingleOperandTypeChecker NUMERIC_INTERVAL =
family(SqlTypeFamily.NUMERIC, SqlTypeFamily.DATETIME_INTERVAL);
public static final SqlSingleOperandTypeChecker INTERVAL_NUMERIC =
family(SqlTypeFamily.DATETIME_INTERVAL, SqlTypeFamily.NUMERIC);
public static final SqlSingleOperandTypeChecker TIME_INTERVAL =
family(SqlTypeFamily.TIME, SqlTypeFamily.DATETIME_INTERVAL);
public static final SqlSingleOperandTypeChecker TIMESTAMP_INTERVAL =
family(SqlTypeFamily.TIMESTAMP, SqlTypeFamily.DATETIME_INTERVAL);
public static final SqlSingleOperandTypeChecker DATE_INTERVAL =
family(SqlTypeFamily.DATE, SqlTypeFamily.DATETIME_INTERVAL);
public static final SqlSingleOperandTypeChecker DATE_CHARACTER =
family(SqlTypeFamily.DATE, SqlTypeFamily.CHARACTER);
public static final SqlSingleOperandTypeChecker DATE_TIME =
family(SqlTypeFamily.DATE, SqlTypeFamily.TIME);
public static final SqlSingleOperandTypeChecker DATETIME_INTERVAL =
family(SqlTypeFamily.DATETIME, SqlTypeFamily.DATETIME_INTERVAL);
public static final SqlSingleOperandTypeChecker TIMESTAMP_STRING =
family(SqlTypeFamily.TIMESTAMP, SqlTypeFamily.STRING);
public static final SqlSingleOperandTypeChecker DATETIME_INTERVAL_INTERVAL =
family(SqlTypeFamily.DATETIME, SqlTypeFamily.DATETIME_INTERVAL,
SqlTypeFamily.DATETIME_INTERVAL);
public static final SqlSingleOperandTypeChecker DATETIME_INTERVAL_INTERVAL_TIME =
family(SqlTypeFamily.DATETIME, SqlTypeFamily.DATETIME_INTERVAL,
SqlTypeFamily.DATETIME_INTERVAL, SqlTypeFamily.TIME);
public static final SqlSingleOperandTypeChecker DATETIME_INTERVAL_TIME =
family(SqlTypeFamily.DATETIME, SqlTypeFamily.DATETIME_INTERVAL,
SqlTypeFamily.TIME);
public static final SqlSingleOperandTypeChecker INTERVAL_DATETIME =
family(SqlTypeFamily.DATETIME_INTERVAL, SqlTypeFamily.DATETIME);
public static final SqlSingleOperandTypeChecker INTERVALINTERVAL_INTERVALDATETIME =
INTERVAL_SAME_SAME.or(INTERVAL_DATETIME);
// TODO: datetime+interval checking missing
// TODO: interval+datetime checking missing
public static final SqlSingleOperandTypeChecker PLUS_OPERATOR =
NUMERIC_NUMERIC
.or(INTERVAL_SAME_SAME)
.or(DATETIME_INTERVAL)
.or(INTERVAL_DATETIME);
/**
* Type-checking strategy for the "*" operator.
*/
public static final SqlSingleOperandTypeChecker MULTIPLY_OPERATOR =
NUMERIC_NUMERIC
.or(INTERVAL_NUMERIC)
.or(NUMERIC_INTERVAL);
/**
* Type-checking strategy for the "/" operator.
*/
public static final SqlSingleOperandTypeChecker DIVISION_OPERATOR =
NUMERIC_NUMERIC.or(INTERVAL_NUMERIC);
public static final SqlSingleOperandTypeChecker MINUS_OPERATOR =
// TODO: compatibility check
NUMERIC_NUMERIC.or(INTERVAL_SAME_SAME).or(DATETIME_INTERVAL);
public static final FamilyOperandTypeChecker MINUS_DATE_OPERATOR =
new FamilyOperandTypeChecker(
ImmutableList.of(SqlTypeFamily.DATETIME, SqlTypeFamily.DATETIME,
SqlTypeFamily.DATETIME_INTERVAL),
i -> false) {
@Override public boolean checkOperandTypes(
SqlCallBinding callBinding,
boolean throwOnFailure) {
if (!super.checkOperandTypes(callBinding, throwOnFailure)) {
return false;
}
return SAME_SAME.checkOperandTypes(callBinding, throwOnFailure);
}
@Override public boolean checkOperandTypesWithoutTypeCoercion(
final SqlCallBinding callBinding,
final boolean throwOnFailure) {
if (!super.checkOperandTypesWithoutTypeCoercion(callBinding, throwOnFailure)) {
return false;
}
return SAME_SAME.checkOperandTypes(callBinding, throwOnFailure);
}
};
public static final SqlSingleOperandTypeChecker NUMERIC_OR_INTERVAL =
NUMERIC.or(INTERVAL);
public static final SqlSingleOperandTypeChecker NUMERIC_OR_STRING =
NUMERIC.or(STRING);
/** Checker that returns whether a value is a multiset of records or an
* array of records.
*
* @see #COLLECTION */
public static final SqlSingleOperandTypeChecker RECORD_COLLECTION =
new RecordTypeWithOneFieldChecker(
sqlTypeName ->
sqlTypeName != SqlTypeName.ARRAY && sqlTypeName != SqlTypeName.MULTISET) {
@Override public String getAllowedSignatures(SqlOperator op, String opName) {
return "UNNEST(<MULTISET>)";
}
};
public static final SqlOperandTypeChecker EXISTS =
new SqlOperandTypeChecker() {
@Override public boolean checkOperandTypes(
SqlCallBinding callBinding,
boolean throwOnFailure) {
// The first operand must be an array type
ARRAY.checkSingleOperandType(callBinding, callBinding.operand(0), 0, throwOnFailure);
final RelDataType arrayType =
SqlTypeUtil.deriveType(callBinding, callBinding.operand(0));
final RelDataType componentType =
requireNonNull(arrayType.getComponentType(), "componentType");
// The second operand is a function(array_element_type)->boolean type
LambdaRelOperandTypeChecker lambdaChecker =
new LambdaRelOperandTypeChecker(
SqlTypeFamily.BOOLEAN,
ImmutableList.of(componentType));
return lambdaChecker.checkSingleOperandType(
callBinding,
callBinding.operand(1),
1,
throwOnFailure);
}
@Override public SqlOperandCountRange getOperandCountRange() {
return SqlOperandCountRanges.of(2);
}
@Override public String getAllowedSignatures(SqlOperator op, String opName) {
return "EXISTS(<ARRAY>, <FUNCTION(ARRAY_ELEMENT_TYPE)->BOOLEAN>)";
}
};
/**
* Checker for record just has one field.
*/
private abstract static class RecordTypeWithOneFieldChecker
implements SqlSingleOperandTypeChecker {
private final Predicate<SqlTypeName> typeNamePredicate;
private RecordTypeWithOneFieldChecker(Predicate<SqlTypeName> predicate) {
this.typeNamePredicate = predicate;
}
@Override public boolean checkSingleOperandType(
SqlCallBinding callBinding,
SqlNode node,
int iFormalOperand,
boolean throwOnFailure) {
assert 0 == iFormalOperand;
RelDataType type = SqlTypeUtil.deriveType(callBinding, node);
boolean validationError = false;
if (!type.isStruct()) {
validationError = true;
} else if (type.getFieldList().size() != 1) {
validationError = true;
} else {
SqlTypeName typeName =
type.getFieldList().get(0).getType().getSqlTypeName();
if (typeNamePredicate.test(typeName)) {
validationError = true;
}
}
if (validationError && throwOnFailure) {
throw callBinding.newValidationSignatureError();
}
return !validationError;
}
}
/** Checker that returns whether a value is a collection (multiset or array)
* of scalar or record values. */
public static final SqlSingleOperandTypeChecker SCALAR_OR_RECORD_COLLECTION =
COLLECTION.or(RECORD_COLLECTION);
public static final SqlSingleOperandTypeChecker SCALAR_OR_RECORD_COLLECTION_OR_MAP =
COLLECTION_OR_MAP.or(
new RecordTypeWithOneFieldChecker(
sqlTypeName ->
sqlTypeName != SqlTypeName.MULTISET
&& sqlTypeName != SqlTypeName.ARRAY
&& sqlTypeName != SqlTypeName.MAP) {
@Override public String getAllowedSignatures(SqlOperator op, String opName) {
return "UNNEST(<MULTISET>)\nUNNEST(<ARRAY>)\nUNNEST(<MAP>)";
}
});
public static final SqlOperandTypeChecker MULTISET_MULTISET =
new MultisetOperandTypeChecker();
/**
* Operand type-checking strategy for a set operator (UNION, INTERSECT,
* EXCEPT).
*/
public static final SqlOperandTypeChecker SET_OP =
new SetopOperandTypeChecker();
public static final SqlOperandTypeChecker RECORD_TO_SCALAR =
new SqlSingleOperandTypeChecker() {
@Override public boolean checkSingleOperandType(
SqlCallBinding callBinding,
SqlNode node,
int iFormalOperand,
boolean throwOnFailure) {
assert 0 == iFormalOperand;
RelDataType type = SqlTypeUtil.deriveType(callBinding, node);
boolean validationError = false;
if (!type.isStruct()) {
validationError = true;
} else if (type.getFieldList().size() != 1) {
validationError = true;
}
if (validationError && throwOnFailure) {
throw callBinding.newValidationSignatureError();
}
return !validationError;
}
@Override public String getAllowedSignatures(SqlOperator op, String opName) {
return SqlUtil.getAliasedSignature(op, opName,
ImmutableList.of("RECORDTYPE(SINGLE FIELD)"));
}
};
/** Checker that returns whether a value is a array of record type with two fields. */
private static class MapFromEntriesOperandTypeChecker
implements SqlSingleOperandTypeChecker {
@Override public boolean checkSingleOperandType(SqlCallBinding callBinding,
SqlNode node, int iFormalOperand, boolean throwOnFailure) {
assert 0 == iFormalOperand;
RelDataType type = SqlTypeUtil.deriveType(callBinding, node);
RelDataType componentType = requireNonNull(type.getComponentType(), "componentType");
boolean valid = false;
if (type.getSqlTypeName() == SqlTypeName.ARRAY
&& componentType.getSqlTypeName() == SqlTypeName.ROW
&& componentType.getFieldCount() == 2) {
valid = true;
}
if (!valid && throwOnFailure) {
throw callBinding.newValidationSignatureError();
}
return valid;
}
@Override public String getAllowedSignatures(SqlOperator op, String opName) {
return SqlUtil.getAliasedSignature(op, opName,
ImmutableList.of("ARRAY<RECORDTYPE(TWO FIELDS)>"));
}
}
/**
* Operand type-checking strategy for a ARRAY function, it allows empty array.
*/
private static class ArrayFunctionOperandTypeChecker
extends SameOperandTypeChecker {
ArrayFunctionOperandTypeChecker() {
// The args of array are non-fixed, so we set to -1 here. then operandCount
// can dynamically set according to the number of input args.
// details please see SameOperandTypeChecker#getOperandList.
super(-1);
}
@Override protected boolean checkOperandTypesImpl(
SqlOperatorBinding operatorBinding,
boolean throwOnFailure,
@Nullable SqlCallBinding callBinding) {
if (throwOnFailure && callBinding == null) {
throw new IllegalArgumentException(
"callBinding must be non-null in case throwOnFailure=true");
}
int nOperandsActual = nOperands;
if (nOperandsActual == -1) {
nOperandsActual = operatorBinding.getOperandCount();
}
RelDataType[] types = new RelDataType[nOperandsActual];
final List<Integer> operandList =
getOperandList(operatorBinding.getOperandCount());
for (int i : operandList) {
types[i] = operatorBinding.getOperandType(i);
}
for (int i : operandList) {
if (i > 0) {
// we replace SqlTypeUtil.isComparable with SqlTypeUtil.leastRestrictiveForComparison
// to handle struct type and NULL constant.
// details please see: https://issues.apache.org/jira/browse/CALCITE-6163
RelDataType type =
SqlTypeUtil.leastRestrictiveForComparison(operatorBinding.getTypeFactory(),
types[i], types[i - 1]);
if (type == null) {
if (!throwOnFailure) {
return false;
}
throw requireNonNull(callBinding, "callBinding").newValidationError(
RESOURCE.needSameTypeParameter());
}
}
}
return true;
}
}
/**
* Operand type-checking strategy for a MAP function, it allows empty map.
*/
private static class MapFunctionOperandTypeChecker
extends SameOperandTypeChecker {
MapFunctionOperandTypeChecker() {
// The args of map are non-fixed, so we set to -1 here. then operandCount
// can dynamically set according to the number of input args.
// details please see SameOperandTypeChecker#getOperandList.
super(-1);
}
@Override public boolean checkOperandTypes(final SqlCallBinding callBinding,
final boolean throwOnFailure) {
final List<RelDataType> argTypes =
SqlTypeUtil.deriveType(callBinding, callBinding.operands());
// allows empty map
if (argTypes.isEmpty()) {
return true;
}
// the size of map arg types must be even.
if (argTypes.size() % 2 != 0) {
throw callBinding.newValidationError(RESOURCE.mapRequiresEvenArgCount());
}
final Pair<@Nullable RelDataType, @Nullable RelDataType> componentType =
getComponentTypes(
callBinding.getTypeFactory(), argTypes);
// check key type & value type
if (null == componentType.left || null == componentType.right) {
if (throwOnFailure) {
throw callBinding.newValidationError(RESOURCE.needSameTypeParameter());
}
return false;
}
return true;
}
/**
* Extract the key type and value type of arg types.
*/
private static Pair<@Nullable RelDataType, @Nullable RelDataType> getComponentTypes(
RelDataTypeFactory typeFactory,
List<RelDataType> argTypes) {
// Util.quotientList(argTypes, 2, 0):
// This extracts all elements at even indices from argTypes.
// It represents the types of keys in the map as they are placed at even positions
// e.g. 0, 2, 4, etc.
// Symmetrically, Util.quotientList(argTypes, 2, 1) represents odd-indexed elements.
// details please see Util.quotientList.
return Pair.of(
typeFactory.leastRestrictive(Util.quotientList(argTypes, 2, 0)),
typeFactory.leastRestrictive(Util.quotientList(argTypes, 2, 1)));
}
}
/** Operand type-checker that accepts period types. Examples:
*
* <ul>
* <li>PERIOD (DATETIME, DATETIME)
* <li>PERIOD (DATETIME, INTERVAL)
* <li>[ROW] (DATETIME, DATETIME)
* <li>[ROW] (DATETIME, INTERVAL)
* </ul> */
private static class PeriodOperandTypeChecker
implements SqlSingleOperandTypeChecker {
@Override public boolean checkSingleOperandType(SqlCallBinding callBinding,
SqlNode node, int iFormalOperand, boolean throwOnFailure) {
assert 0 == iFormalOperand;
RelDataType type = SqlTypeUtil.deriveType(callBinding, node);
boolean valid = false;
if (type.isStruct() && type.getFieldList().size() == 2) {
final RelDataType t0 = type.getFieldList().get(0).getType();
final RelDataType t1 = type.getFieldList().get(1).getType();
if (SqlTypeUtil.isDatetime(t0)) {
if (SqlTypeUtil.isDatetime(t1)) {
// t0 must be comparable with t1; (DATE, TIMESTAMP) is not valid
if (SqlTypeUtil.sameNamedType(t0, t1)) {
valid = true;
}
} else if (SqlTypeUtil.isInterval(t1)) {
valid = true;
}
}
}
if (!valid && throwOnFailure) {
throw callBinding.newValidationSignatureError();
}
return valid;
}
@Override public String getAllowedSignatures(SqlOperator op, String opName) {
return SqlUtil.getAliasedSignature(op, opName,
ImmutableList.of("PERIOD (DATETIME, INTERVAL)",
"PERIOD (DATETIME, DATETIME)"));
}
}
/**
* Parameter type-checking strategy where types must be Map and Map key type.
*/
private static class MapKeyOperandTypeChecker extends SameOperandTypeChecker {
MapKeyOperandTypeChecker() {
super(2);
}
@Override public boolean checkOperandTypes(
SqlCallBinding callBinding,
boolean throwOnFailure) {
final SqlNode op0 = callBinding.operand(0);
if (!OperandTypes.MAP.checkSingleOperandType(
callBinding,
op0,
0,
throwOnFailure)) {
return false;
}
final RelDataType mapKeyType =
getKeyTypeOrThrow(SqlTypeUtil.deriveType(callBinding, op0));
final SqlNode op1 = callBinding.operand(1);
RelDataType opType1 = SqlTypeUtil.deriveType(callBinding, op1);
RelDataType biggest =
callBinding.getTypeFactory().leastRestrictive(
ImmutableList.of(mapKeyType, opType1));
if (biggest == null) {
if (throwOnFailure) {
throw callBinding.newError(
RESOURCE.typeNotComparable(
mapKeyType.toString(), opType1.toString()));
}
return false;
}
return true;
}
}
/** Checker that passes if the operand's type has a particular
* {@link SqlTypeName}. */
private static class TypeNameChecker implements SqlSingleOperandTypeChecker,
ImplicitCastOperandTypeChecker {
final SqlTypeName typeName;
TypeNameChecker(SqlTypeName typeName) {
this.typeName = requireNonNull(typeName, "typeName");
}
@Override public boolean checkSingleOperandType(SqlCallBinding callBinding,
SqlNode operand, int iFormalOperand, boolean throwOnFailure) {
final RelDataType operandType =
callBinding.getValidator().getValidatedNodeType(operand);
return operandType.getSqlTypeName() == typeName;
}
@Override public boolean checkOperandTypesWithoutTypeCoercion(
SqlCallBinding callBinding, boolean throwOnFailure) {
// FIXME we assume that there is exactly one operand
return checkSingleOperandType(callBinding, callBinding.operand(0), 0,
throwOnFailure);
}
@Override public SqlTypeFamily getOperandSqlTypeFamily(int iFormalOperand) {
return requireNonNull(typeName.getFamily(), "family");
}
@Override public String getAllowedSignatures(SqlOperator op, String opName) {
return opName + "(" + typeName.getSpaceName() + ")";
}
}
/**
* Operand type-checking strategy where the type of the operand is a lambda
* expression with a given return type and argument {@link SqlTypeFamily}s.
*/
private static class LambdaFamilyOperandTypeChecker
extends LambdaOperandTypeChecker {
private final List<SqlTypeFamily> argFamilies;
LambdaFamilyOperandTypeChecker(
SqlTypeFamily returnTypeFamily,
List<SqlTypeFamily> argFamilies) {
super(returnTypeFamily);
this.argFamilies = ImmutableList.copyOf(argFamilies);
}
@Override public String getAllowedSignatures(SqlOperator op, String opName) {
ImmutableList.Builder<SqlTypeFamily> builder = ImmutableList.builder();
builder.addAll(argFamilies);
builder.add(returnTypeFamily);
return SqlUtil.getAliasedSignature(op, opName, builder.build());
}
@Override public boolean checkOperandTypes(SqlCallBinding callBinding,
boolean throwOnFailure) {
return false;
}
@Override public boolean checkSingleOperandType(
SqlCallBinding callBinding, SqlNode operand, int iFormalOperand, boolean throwOnFailure) {
if (!(operand instanceof SqlLambda)
|| ((SqlLambda) operand).getParameters().size() != argFamilies.size()) {
if (throwOnFailure) {
throw callBinding.newValidationSignatureError();
}
return false;
}
final SqlLambda lambdaExpr = (SqlLambda) operand;
if (SqlUtil.isNullLiteral(lambdaExpr.getExpression(), false)) {
checkNull(callBinding, lambdaExpr, throwOnFailure);
}
final SqlValidator validator = callBinding.getValidator();
if (!lambdaExpr.getParameters().isEmpty()
&& !argFamilies.stream().allMatch(f -> f == SqlTypeFamily.ANY)) {
// Replace the parameter types in the lambda expression.
final SqlLambdaScope scope =
(SqlLambdaScope) validator.getLambdaScope(lambdaExpr);
for (int i = 0; i < argFamilies.size(); i++) {
final SqlNode param = lambdaExpr.getParameters().get(i);
final RelDataType type =
argFamilies.get(i).getDefaultConcreteType(callBinding.getTypeFactory());
if (type != null) {
scope.getParameterTypes().put(param.toString(), type);
}
}
lambdaExpr.accept(new TypeRemover(validator));
// Given the new relDataType, re-validate the lambda expression.
validator.validateLambda(lambdaExpr);
}
return checkReturnType(validator, callBinding, lambdaExpr, throwOnFailure);
}
}
/**
* Operand type-checking strategy where the type of the operand is a lambda
* expression with a given return type and argument {@link RelDataType}s.
*/
private static class LambdaRelOperandTypeChecker
extends LambdaOperandTypeChecker {
private final List<RelDataType> argTypes;
LambdaRelOperandTypeChecker(
SqlTypeFamily returnTypeFamily,
List<RelDataType> argTypes) {
super(returnTypeFamily);
this.argTypes = argTypes;
}
@Override public String getAllowedSignatures(SqlOperator op, String opName) {
ImmutableList.Builder<SqlTypeFamily> builder = ImmutableList.builder();
argTypes.stream()
.map(t -> requireNonNull(t.getSqlTypeName().getFamily()))
.forEach(builder::add);
builder.add(returnTypeFamily);
return SqlUtil.getAliasedSignature(op, opName, builder.build());
}
@Override public boolean checkSingleOperandType(SqlCallBinding callBinding, SqlNode operand,
int iFormalOperand,
boolean throwOnFailure) {
if (!(operand instanceof SqlLambda)
|| ((SqlLambda) operand).getParameters().size() != argTypes.size()) {
if (throwOnFailure) {
throw callBinding.newValidationSignatureError();
}
return false;
}
final SqlLambda lambdaExpr = (SqlLambda) operand;
if (SqlUtil.isNullLiteral(lambdaExpr.getExpression(), false)) {
checkNull(callBinding, lambdaExpr, throwOnFailure);
}
// Replace the parameter types in the lambda expression.
final SqlValidator validator = callBinding.getValidator();
final SqlLambdaScope scope =
(SqlLambdaScope) validator.getLambdaScope(lambdaExpr);
for (int i = 0; i < argTypes.size(); i++) {
final SqlNode param = lambdaExpr.getParameters().get(i);
final RelDataType type = argTypes.get(i);
if (type != null) {
scope.getParameterTypes().put(param.toString(), type);
}
}
lambdaExpr.accept(new TypeRemover(validator));
// Given the new relDataType, re-validate the lambda expression.
validator.validateLambda(lambdaExpr);
return checkReturnType(validator, callBinding, lambdaExpr, throwOnFailure);
}
}
/**
* Abstract base class for type-checking strategies involving lambda expressions.
* This class provides common functionality for checking the type of lambda expression.
*/
private abstract static class LambdaOperandTypeChecker
implements SqlSingleOperandTypeChecker {
protected final SqlTypeFamily returnTypeFamily;
LambdaOperandTypeChecker(SqlTypeFamily returnTypeFamily) {
this.returnTypeFamily = requireNonNull(returnTypeFamily, "returnTypeFamily");
}
protected boolean checkNull(
SqlCallBinding callBinding,
SqlLambda lambdaExpr,
boolean throwOnFailure) {
if (callBinding.isTypeCoercionEnabled()) {
return true;
}
if (throwOnFailure) {
throw callBinding.getValidator().newValidationError(lambdaExpr.getExpression(),
RESOURCE.nullIllegal());
}
return false;
}
protected boolean checkReturnType(
SqlValidator validator,
SqlCallBinding callBinding,
SqlLambda lambdaExpr,
boolean throwOnFailure) {
final RelDataType newType = validator.getValidatedNodeType(lambdaExpr);
assert newType instanceof FunctionSqlType;
final SqlTypeName returnTypeName =
((FunctionSqlType) newType).getReturnType().getSqlTypeName();
if (returnTypeName == SqlTypeName.ANY
|| returnTypeFamily.getTypeNames().contains(returnTypeName)) {
return true;
}
if (throwOnFailure) {
throw callBinding.newValidationSignatureError();
}
return false;
}
/**
* Visitor that removes the relDataType of a sqlNode and its children in the
* validator. Now this visitor is only used for removing the relDataType
* when we check lambda operand. Since lambda expressions will be
* validated for the second time based on the given parameter type,
* the type cached during the first validation must be cleared.
*/
protected static class TypeRemover extends SqlBasicVisitor<Void> {
private final SqlValidator validator;
protected TypeRemover(SqlValidator validator) {
this.validator = validator;
}
@Override public Void visit(SqlIdentifier id) {
validator.removeValidatedNodeType(id);
return super.visit(id);
}
@Override public Void visit(SqlCall call) {
validator.removeValidatedNodeType(call);
return super.visit(call);
}
}
}
}