SqlCallBinding.java
/*
* 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;
import org.apache.calcite.avatica.util.TimeUnit;
import org.apache.calcite.rel.type.RelDataType;
import org.apache.calcite.rel.type.TimeFrame;
import org.apache.calcite.rel.type.TimeFrameSet;
import org.apache.calcite.runtime.CalciteException;
import org.apache.calcite.runtime.Resources;
import org.apache.calcite.sql.fun.SqlLiteralChainOperator;
import org.apache.calcite.sql.fun.SqlStdOperatorTable;
import org.apache.calcite.sql.parser.SqlParserPos;
import org.apache.calcite.sql.type.SqlOperandMetadata;
import org.apache.calcite.sql.type.SqlOperandTypeChecker;
import org.apache.calcite.sql.type.SqlTypeName;
import org.apache.calcite.sql.type.SqlTypeUtil;
import org.apache.calcite.sql.validate.SelectScope;
import org.apache.calcite.sql.validate.SqlMonotonicity;
import org.apache.calcite.sql.validate.SqlNameMatcher;
import org.apache.calcite.sql.validate.SqlNameMatchers;
import org.apache.calcite.sql.validate.SqlValidator;
import org.apache.calcite.sql.validate.SqlValidatorException;
import org.apache.calcite.sql.validate.SqlValidatorNamespace;
import org.apache.calcite.sql.validate.SqlValidatorScope;
import org.apache.calcite.sql.validate.SqlValidatorUtil;
import org.apache.calcite.util.ImmutableNullableList;
import org.apache.calcite.util.NlsString;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import static org.apache.calcite.util.Static.RESOURCE;
import static java.util.Objects.requireNonNull;
/**
* <code>SqlCallBinding</code> implements {@link SqlOperatorBinding} by
* analyzing to the operands of a {@link SqlCall} with a {@link SqlValidator}.
*/
public class SqlCallBinding extends SqlOperatorBinding {
/** Static nested class required due to
* <a href="https://issues.apache.org/jira/browse/CALCITE-4393">[CALCITE-4393]
* ExceptionInInitializerError due to NPE in SqlCallBinding caused by circular dependency</a>.
* The static field inside it cannot be part of the outer class: it must be defined
* within a nested class in order to break the cycle during class loading. */
private static class DefaultCallHolder {
private static final SqlCall DEFAULT_CALL =
SqlStdOperatorTable.DEFAULT.createCall(SqlParserPos.ZERO);
}
//~ Instance fields --------------------------------------------------------
private final SqlValidator validator;
private final SqlValidatorScope scope;
private final SqlCall call;
//~ Constructors -----------------------------------------------------------
/**
* Creates a call binding.
*
* @param validator Validator
* @param scope Scope of call
* @param call Call node
*/
public SqlCallBinding(SqlValidator validator, SqlValidatorScope scope,
SqlCall call) {
super(validator.getTypeFactory(), call.getOperator());
this.validator = validator;
this.scope = requireNonNull(scope, "scope");
this.call = call;
}
//~ Methods ----------------------------------------------------------------
@Deprecated
@Override public int getGroupCount() {
final SelectScope selectScope =
SqlValidatorUtil.getEnclosingSelectScope(scope);
if (selectScope == null) {
// Probably "VALUES expr". Treat same as "SELECT expr GROUP BY ()"
return 0;
}
final SqlSelect select = selectScope.getNode();
final SqlNodeList group = select.getGroup();
if (group != null) {
int n = 0;
for (SqlNode groupItem : group) {
if (!(groupItem instanceof SqlNodeList)
|| ((SqlNodeList) groupItem).size() != 0) {
++n;
}
}
return n;
}
return validator.isAggregate(select) ? 0 : -1;
}
@Override public boolean hasEmptyGroup() {
final SelectScope selectScope =
SqlValidatorUtil.getEnclosingSelectScope(scope);
if (selectScope == null) {
// Probably "VALUES expr". Treat same as "SELECT expr GROUP BY ()"
return true;
}
final SqlSelect select = selectScope.getNode();
final SqlNodeList group = select.getGroup();
if (group != null) {
return SqlValidatorUtil.hasEmptyGroup(group);
}
return validator.isAggregate(select);
}
/**
* Returns the validator.
*/
public SqlValidator getValidator() {
return validator;
}
/**
* Returns the scope of the call.
*/
public SqlValidatorScope getScope() {
return scope;
}
/**
* Returns the call node.
*/
public SqlCall getCall() {
return call;
}
/** Returns the operands to a call permuted into the same order as the
* formal parameters of the function. */
public List<SqlNode> operands() {
if (hasAssignment()
&& !(call.getOperator() instanceof SqlUnresolvedFunction)) {
return permutedOperands(call);
} else {
final List<SqlNode> operandList = call.getOperandList();
final SqlOperandTypeChecker checker =
call.getOperator().getOperandTypeChecker();
if (checker == null) {
return operandList;
}
final SqlOperandCountRange range = checker.getOperandCountRange();
final List<SqlNode> list = Lists.newArrayList(operandList);
while (list.size() < range.getMax()
&& checker.isOptional(list.size())
&& checker.isFixedParameters()) {
list.add(DefaultCallHolder.DEFAULT_CALL);
}
return list;
}
}
/** Returns whether arguments have name assignment. */
private boolean hasAssignment() {
for (SqlNode operand : call.getOperandList()) {
if (operand != null
&& operand.getKind() == SqlKind.ARGUMENT_ASSIGNMENT) {
return true;
}
}
return false;
}
/** Returns the operands to a call permuted into the same order as the
* formal parameters of the function. */
private List<SqlNode> permutedOperands(final SqlCall call) {
final SqlOperator operator = call.getOperator();
final SqlOperandMetadata operandMetadata =
requireNonNull((SqlOperandMetadata) operator.getOperandTypeChecker(),
() -> "operandTypeChecker is null for " + call
+ ", operator " + operator);
final List<String> paramNames = operandMetadata.paramNames();
final List<SqlNode> permuted = new ArrayList<>();
// Always use case-insensitive lookup for parameter names
final SqlNameMatcher nameMatcher =
SqlNameMatchers.withCaseSensitive(false);
for (final String paramName : paramNames) {
for (int j = 0; j < call.getOperandList().size(); j++) {
final SqlCall call2 = call.operand(j);
assert call2.getKind() == SqlKind.ARGUMENT_ASSIGNMENT;
final SqlIdentifier operandID = call2.operand(1);
final String operandName = operandID.getSimple();
if (nameMatcher.matches(operandName, paramName)) {
permuted.add(call2.operand(0));
break;
}
// the last operand, there is still no match.
if (j == call.getOperandList().size() - 1) {
if (operandMetadata.isFixedParameters()) {
// Not like user defined functions, we do not patch up the operands
// with DEFAULT and then convert to nulls during sql-to-rel conversion.
// Thus, there is no need to show the optional operands in the plan and
// decide if the optional operand is null when code generation.
permuted.add(DefaultCallHolder.DEFAULT_CALL);
}
}
}
}
return permuted;
}
/**
* Returns a particular operand.
*/
public SqlNode operand(int i) {
return operands().get(i);
}
/** Returns a call that is equivalent except that arguments have been
* permuted into the logical order. Any arguments whose default value is being
* used are null. */
public SqlCall permutedCall() {
final List<SqlNode> operandList = operands();
if (operandList.equals(call.getOperandList())) {
return call;
}
return call.getOperator().createCall(call.pos, operandList);
}
@Override public SqlMonotonicity getOperandMonotonicity(int ordinal) {
return call.getOperandList().get(ordinal).getMonotonicity(scope);
}
@SuppressWarnings("deprecation")
@Override public @Nullable String getStringLiteralOperand(int ordinal) {
SqlNode node = call.operand(ordinal);
final Object o = SqlLiteral.value(node);
return o instanceof NlsString ? ((NlsString) o).getValue() : null;
}
@SuppressWarnings("deprecation")
@Override public int getIntLiteralOperand(int ordinal) {
SqlNode node = call.operand(ordinal);
final Object o = SqlLiteral.value(node);
if (o instanceof BigDecimal) {
BigDecimal bd = (BigDecimal) o;
try {
return bd.intValueExact();
} catch (ArithmeticException e) {
throw SqlUtil.newContextException(node.pos,
RESOURCE.numberLiteralOutOfRange(bd.toString()));
}
}
throw new AssertionError();
}
@Override public <T extends Object> @Nullable T getOperandLiteralValue(int ordinal,
Class<T> clazz) {
final SqlNode node = operand(ordinal);
return valueAs(node, clazz);
}
private <T extends Object> @Nullable T valueAs(SqlNode node, Class<T> clazz) {
final SqlLiteral literal;
switch (node.getKind()) {
case ARRAY_VALUE_CONSTRUCTOR:
final List<@Nullable Object> list = new ArrayList<>();
for (SqlNode o : ((SqlCall) node).getOperandList()) {
list.add(valueAs(o, Object.class));
}
return clazz.cast(ImmutableNullableList.copyOf(list));
case MAP_VALUE_CONSTRUCTOR:
final ImmutableMap.Builder<Object, Object> builder2 =
ImmutableMap.builder();
final List<SqlNode> operands = ((SqlCall) node).getOperandList();
for (int i = 0; i < operands.size(); i += 2) {
final SqlNode key = operands.get(i);
final SqlNode value = operands.get(i + 1);
builder2.put(requireNonNull(valueAs(key, Object.class), "key"),
requireNonNull(valueAs(value, Object.class), "value"));
}
return clazz.cast(builder2.build());
case CAST:
return valueAs(((SqlCall) node).operand(0), clazz);
case LITERAL:
literal = (SqlLiteral) node;
if (literal.getTypeName() == SqlTypeName.NULL) {
return null;
}
return literal.getValueAs(clazz);
case LITERAL_CHAIN:
literal = SqlLiteralChainOperator.concatenateOperands((SqlCall) node);
return literal.getValueAs(clazz);
case INTERVAL_QUALIFIER:
final SqlIntervalQualifier q = (SqlIntervalQualifier) node;
if (q.timeFrameName != null) {
// Custom time frames can only be cast to String. You can do more with
// them when validator has resolved to a TimeFrame.
final TimeFrameSet timeFrameSet = validator.getTimeFrameSet();
final TimeFrame timeFrame = timeFrameSet.getOpt(q.timeFrameName);
if (clazz == String.class) {
return clazz.cast(q.timeFrameName);
}
if (clazz == TimeUnit.class
&& timeFrame != null) {
TimeUnit timeUnit = timeFrameSet.getUnit(timeFrame);
return clazz.cast(timeUnit);
}
return null;
}
final SqlIntervalLiteral.IntervalValue intervalValue =
new SqlIntervalLiteral.IntervalValue(q, 1, q.toString());
literal = new SqlLiteral(intervalValue, q.typeName(), q.pos);
return literal.getValueAs(clazz);
case DEFAULT:
return null; // currently NULL is the only default value
default:
if (SqlUtil.isNullLiteral(node, true)) {
return null; // NULL literal
}
return null; // not a literal
}
}
@Override public boolean isOperandNull(int ordinal, boolean allowCast) {
return SqlUtil.isNullLiteral(operand(ordinal), allowCast);
}
@Override public boolean isOperandLiteral(int ordinal, boolean allowCast) {
return SqlUtil.isLiteral(operand(ordinal), allowCast);
}
@Override public int getOperandCount() {
return call.getOperandList().size();
}
@Override public RelDataType getOperandType(int ordinal) {
final SqlNode operand = call.operand(ordinal);
final RelDataType type = SqlTypeUtil.deriveType(this, operand);
final SqlValidatorNamespace namespace = validator.getNamespace(operand);
if (namespace != null) {
return namespace.getType();
}
return type;
}
@Override public @Nullable RelDataType getCursorOperand(int ordinal) {
final SqlNode operand = call.operand(ordinal);
if (!SqlUtil.isCallTo(operand, SqlStdOperatorTable.CURSOR)) {
return null;
}
final SqlCall cursorCall = (SqlCall) operand;
final SqlNode query = cursorCall.operand(0);
return SqlTypeUtil.deriveType(this, query);
}
@Override public @Nullable String getColumnListParamInfo(
int ordinal,
String paramName,
List<String> columnList) {
final SqlNode operand = call.operand(ordinal);
if (!SqlUtil.isCallTo(operand, SqlStdOperatorTable.ROW)) {
return null;
}
columnList.addAll(
SqlIdentifier.simpleNames(((SqlCall) operand).getOperandList()));
return validator.getParentCursor(paramName);
}
@Override public CalciteException newError(
Resources.ExInst<SqlValidatorException> e) {
return validator.newValidationError(call, e);
}
/**
* Constructs a new validation signature error for the call.
*
* @return signature exception
*/
public CalciteException newValidationSignatureError() {
return validator.newValidationError(call,
RESOURCE.canNotApplyOp2Type(getOperator().getName(),
call.getCallSignature(validator, scope),
getOperator().getAllowedSignatures()));
}
/**
* Constructs a new validation error for the call. (Do not use this to
* construct a validation error for other nodes such as an operands.)
*
* @param ex underlying exception
* @return wrapped exception
*/
public CalciteException newValidationError(
Resources.ExInst<SqlValidatorException> ex) {
return validator.newValidationError(call, ex);
}
/**
* Returns whether to allow implicit type coercion when validation.
* This is a short-cut method.
*/
public boolean isTypeCoercionEnabled() {
return validator.config().typeCoercionEnabled();
}
}