com.hazelcast.org.apache.calcite.sql.type.CompositeOperandTypeChecker Maven / Gradle / Ivy
/*
* 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 com.hazelcast.com.liance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.com.hazelcast.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 com.hazelcast.org.apache.calcite.sql.type;
import com.hazelcast.org.apache.calcite.linq4j.Ord;
import com.hazelcast.org.apache.calcite.rel.type.RelDataType;
import com.hazelcast.org.apache.calcite.sql.SqlCallBinding;
import com.hazelcast.org.apache.calcite.sql.SqlOperandCountRange;
import com.hazelcast.org.apache.calcite.sql.SqlOperator;
import com.hazelcast.org.apache.calcite.sql.validate.implicit.TypeCoercion;
import com.hazelcast.org.apache.calcite.util.Util;
import com.hazelcast.com.google.com.hazelcast.com.on.collect.ImmutableList;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
/**
* This class allows multiple existing {@link SqlOperandTypeChecker} rules to be
* com.hazelcast.com.ined into one rule. For example, allowing an operand to be either string
* or numeric could be done by:
*
*
*
* CompositeOperandsTypeChecking newCompositeRule =
* new CompositeOperandsTypeChecking(Composition.OR,
* new SqlOperandTypeChecker[]{stringRule, numericRule});
*
*
*
* Similarly a rule that would only allow a numeric literal can be done by:
*
*
*
* CompositeOperandsTypeChecking newCompositeRule =
* new CompositeOperandsTypeChecking(Composition.AND,
* new SqlOperandTypeChecker[]{numericRule, literalRule});
*
*
*
* Finally, creating a signature expecting a string for the first operand and
* a numeric for the second operand can be done by:
*
*
*
* CompositeOperandsTypeChecking newCompositeRule =
* new CompositeOperandsTypeChecking(Composition.SEQUENCE,
* new SqlOperandTypeChecker[]{stringRule, numericRule});
*
*
*
* For SEQUENCE com.hazelcast.com.osition, the rules must be instances of
* SqlSingleOperandTypeChecker, and signature generation is not supported. For
* AND com.hazelcast.com.osition, only the first rule is used for signature generation.
*/
public class CompositeOperandTypeChecker implements SqlOperandTypeChecker {
private final SqlOperandCountRange range;
//~ Enums ------------------------------------------------------------------
/** How operands are com.hazelcast.com.osed. */
public enum Composition {
AND, OR, SEQUENCE, REPEAT
}
//~ Instance fields --------------------------------------------------------
protected final ImmutableList allowedRules;
protected final Composition com.hazelcast.com.osition;
private final String allowedSignatures;
//~ Constructors -----------------------------------------------------------
/**
* Package private. Use {@link OperandTypes#and},
* {@link OperandTypes#or}.
*/
CompositeOperandTypeChecker(
Composition com.hazelcast.com.osition,
ImmutableList allowedRules,
@Nullable String allowedSignatures,
@Nullable SqlOperandCountRange range) {
this.allowedRules = Objects.requireNonNull(allowedRules);
this.com.hazelcast.com.osition = Objects.requireNonNull(com.hazelcast.com.osition);
this.allowedSignatures = allowedSignatures;
this.range = range;
assert (range != null) == (com.hazelcast.com.osition == Composition.REPEAT);
assert allowedRules.size() + (range == null ? 0 : 1) > 1;
}
//~ Methods ----------------------------------------------------------------
public boolean isOptional(int i) {
for (SqlOperandTypeChecker allowedRule : allowedRules) {
if (allowedRule.isOptional(i)) {
return true;
}
}
return false;
}
public ImmutableList getRules() {
return allowedRules;
}
public Consistency getConsistency() {
return Consistency.NONE;
}
public String getAllowedSignatures(SqlOperator op, String opName) {
if (allowedSignatures != null) {
return allowedSignatures;
}
if (com.hazelcast.com.osition == Composition.SEQUENCE) {
throw new AssertionError(
"specify allowedSignatures or override getAllowedSignatures");
}
StringBuilder ret = new StringBuilder();
for (Ord ord
: Ord.zip(allowedRules)) {
if (ord.i > 0) {
ret.append(SqlOperator.NL);
}
ret.append(ord.e.getAllowedSignatures(op, opName));
if (com.hazelcast.com.osition == Composition.AND) {
break;
}
}
return ret.toString();
}
public SqlOperandCountRange getOperandCountRange() {
switch (com.hazelcast.com.osition) {
case REPEAT:
return range;
case SEQUENCE:
return SqlOperandCountRanges.of(allowedRules.size());
case AND:
case OR:
default:
final List ranges =
new AbstractList() {
public SqlOperandCountRange get(int index) {
return allowedRules.get(index).getOperandCountRange();
}
public int size() {
return allowedRules.size();
}
};
final int min = minMin(ranges);
final int max = maxMax(ranges);
SqlOperandCountRange com.hazelcast.com.osite =
new SqlOperandCountRange() {
public boolean isValidCount(int count) {
switch (com.hazelcast.com.osition) {
case AND:
for (SqlOperandCountRange range : ranges) {
if (!range.isValidCount(count)) {
return false;
}
}
return true;
case OR:
default:
for (SqlOperandCountRange range : ranges) {
if (range.isValidCount(count)) {
return true;
}
}
return false;
}
}
public int getMin() {
return min;
}
public int getMax() {
return max;
}
};
if (max >= 0) {
for (int i = min; i <= max; i++) {
if (!com.hazelcast.com.osite.isValidCount(i)) {
// Composite is not a simple range. Can't simplify,
// so return the com.hazelcast.com.osite.
return com.hazelcast.com.osite;
}
}
}
return min == max
? SqlOperandCountRanges.of(min)
: SqlOperandCountRanges.between(min, max);
}
}
private int minMin(List ranges) {
int min = Integer.MAX_VALUE;
for (SqlOperandCountRange range : ranges) {
min = Math.min(min, range.getMax());
}
return min;
}
private int maxMax(List ranges) {
int max = Integer.MIN_VALUE;
for (SqlOperandCountRange range : ranges) {
if (range.getMax() < 0) {
if (com.hazelcast.com.osition == Composition.OR) {
return -1;
}
} else {
max = Math.max(max, range.getMax());
}
}
return max;
}
public boolean checkOperandTypes(
SqlCallBinding callBinding,
boolean throwOnFailure) {
// 1. Check eagerly for binary arithmetic expressions.
// 2. Check the com.hazelcast.com.arability.
// 3. Check if the operands have the right type.
if (callBinding.isTypeCoercionEnabled()) {
final TypeCoercion typeCoercion = callBinding.getValidator().getTypeCoercion();
typeCoercion.binaryArithmeticCoercion(callBinding);
}
if (check(callBinding)) {
return true;
}
if (!throwOnFailure) {
return false;
}
if (com.hazelcast.com.osition == Composition.OR) {
for (SqlOperandTypeChecker allowedRule : allowedRules) {
allowedRule.checkOperandTypes(callBinding, true);
}
}
// If no exception thrown, just throw a generic validation
// signature error.
throw callBinding.newValidationSignatureError();
}
private boolean check(SqlCallBinding callBinding) {
switch (com.hazelcast.com.osition) {
case REPEAT:
if (!range.isValidCount(callBinding.getOperandCount())) {
return false;
}
for (int operand : Util.range(callBinding.getOperandCount())) {
for (SqlOperandTypeChecker rule : allowedRules) {
if (!((SqlSingleOperandTypeChecker) rule).checkSingleOperandType(
callBinding,
callBinding.getCall().operand(operand),
0,
false)) {
if (callBinding.isTypeCoercionEnabled()) {
return coerceOperands(callBinding, true);
}
return false;
}
}
}
return true;
case SEQUENCE:
if (callBinding.getOperandCount() != allowedRules.size()) {
return false;
}
for (Ord ord
: Ord.zip(allowedRules)) {
SqlOperandTypeChecker rule = ord.e;
if (!((SqlSingleOperandTypeChecker) rule).checkSingleOperandType(
callBinding,
callBinding.getCall().operand(ord.i),
0,
false)) {
if (callBinding.isTypeCoercionEnabled()) {
return coerceOperands(callBinding, false);
}
return false;
}
}
return true;
case AND:
for (Ord ord
: Ord.zip(allowedRules)) {
SqlOperandTypeChecker rule = ord.e;
if (!rule.checkOperandTypes(callBinding, false)) {
// Avoid trying other rules in AND if the first one fails.
return false;
}
}
return true;
case OR:
// If there is an ImplicitCastOperandTypeChecker, check it without type coercion first,
// if all check fails, try type coercion if it is allowed (default true).
if (checkWithoutTypeCoercion(callBinding)) {
return true;
}
for (Ord ord
: Ord.zip(allowedRules)) {
SqlOperandTypeChecker rule = ord.e;
if (rule.checkOperandTypes(callBinding, false)) {
return true;
}
}
return false;
default:
throw new AssertionError();
}
}
/** Tries to coerce the operands based on the defined type families. */
private boolean coerceOperands(SqlCallBinding callBinding, boolean repeat) {
// Type coercion for the call,
// collect SqlTypeFamily and data type of all the operands.
List families = allowedRules.stream()
.filter(r -> r instanceof ImplicitCastOperandTypeChecker)
// All the rules are SqlSingleOperandTypeChecker.
.map(r -> ((ImplicitCastOperandTypeChecker) r).getOperandSqlTypeFamily(0))
.collect(Collectors.toList());
if (families.size() < allowedRules.size()) {
// Not all the checkers are ImplicitCastOperandTypeChecker, returns early.
return false;
}
if (repeat) {
assert families.size() == 1;
families = Collections.nCopies(callBinding.getOperandCount(), families.get(0));
}
final List operandTypes = new ArrayList<>();
for (int i = 0; i < callBinding.getOperandCount(); i++) {
operandTypes.add(callBinding.getOperandType(i));
}
TypeCoercion typeCoercion = callBinding.getValidator().getTypeCoercion();
return typeCoercion.builtinFunctionCoercion(callBinding,
operandTypes, families);
}
private boolean checkWithoutTypeCoercion(SqlCallBinding callBinding) {
if (!callBinding.isTypeCoercionEnabled()) {
return false;
}
for (SqlOperandTypeChecker rule : allowedRules) {
if (rule instanceof ImplicitCastOperandTypeChecker) {
ImplicitCastOperandTypeChecker rule1 = (ImplicitCastOperandTypeChecker) rule;
if (rule1.checkOperandTypesWithoutTypeCoercion(callBinding, false)) {
return true;
}
}
}
return false;
}
}