| /* |
| * 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) |
| .or(family(SqlTypeFamily.INTERVAL_DAY_TIME, SqlTypeFamily.INTERVAL_YEAR_MONTH)); |
| |
| // 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); |
| } |
| } |
| } |
| } |