blob: 96ef1aabe65c67d260323b9571d64a9639268b12 [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;
import org.apache.calcite.rel.type.RelDataType;
import org.apache.calcite.sql.fun.SqlStdOperatorTable;
import org.apache.calcite.sql.type.SqlOperandTypeChecker;
import org.apache.calcite.sql.type.SqlOperandTypeInference;
import org.apache.calcite.sql.type.SqlReturnTypeInference;
import org.apache.calcite.sql.type.SqlTypeUtil;
import org.apache.calcite.sql.validate.SqlMonotonicity;
import org.apache.calcite.sql.validate.SqlValidator;
import org.apache.calcite.sql.validate.SqlValidatorScope;
import org.apache.calcite.util.Litmus;
import org.apache.calcite.util.Util;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.math.BigDecimal;
import java.nio.charset.Charset;
import static org.apache.calcite.util.Static.RESOURCE;
import static java.util.Objects.requireNonNull;
/**
* <code>SqlBinaryOperator</code> is a binary operator.
*/
public class SqlBinaryOperator extends SqlOperator {
//~ Constructors -----------------------------------------------------------
/**
* Creates a SqlBinaryOperator.
*
* @param name Name of operator
* @param kind Kind
* @param prec Precedence
* @param leftAssoc Left-associativity
* @param returnTypeInference Strategy to infer return type
* @param operandTypeInference Strategy to infer operand types
* @param operandTypeChecker Validator for operand types
*/
public SqlBinaryOperator(
String name,
SqlKind kind,
int prec,
boolean leftAssoc,
@Nullable SqlReturnTypeInference returnTypeInference,
@Nullable SqlOperandTypeInference operandTypeInference,
@Nullable SqlOperandTypeChecker operandTypeChecker) {
super(
name,
kind,
leftPrec(prec, leftAssoc),
rightPrec(prec, leftAssoc),
returnTypeInference,
operandTypeInference,
operandTypeChecker);
}
//~ Methods ----------------------------------------------------------------
@Override public SqlSyntax getSyntax() {
return SqlSyntax.BINARY;
}
@Override public @Nullable String getSignatureTemplate(final int operandsCount) {
Util.discard(operandsCount);
// op0 opname op1
return "{1} {0} {2}";
}
/**
* {@inheritDoc}
*
* <p>Returns true for most operators but false for the '.' operator;
* consider
*
* <blockquote>
* <pre>x.y + 5 * 6</pre>
* </blockquote>
*/
@Override boolean needsSpace() {
return !getName().equals(".");
}
@Override public @Nullable SqlOperator reverse() {
switch (kind) {
case EQUALS:
case NOT_EQUALS:
case IS_DISTINCT_FROM:
case IS_NOT_DISTINCT_FROM:
case OR:
case AND:
case PLUS:
case TIMES:
return this;
case GREATER_THAN:
return SqlStdOperatorTable.LESS_THAN;
case GREATER_THAN_OR_EQUAL:
return SqlStdOperatorTable.LESS_THAN_OR_EQUAL;
case LESS_THAN:
return SqlStdOperatorTable.GREATER_THAN;
case LESS_THAN_OR_EQUAL:
return SqlStdOperatorTable.GREATER_THAN_OR_EQUAL;
default:
return null;
}
}
@Override protected RelDataType adjustType(
SqlValidator validator,
final SqlCall call,
RelDataType type) {
return convertType(validator, call, type);
}
private RelDataType convertType(SqlValidator validator, SqlCall call, RelDataType type) {
RelDataType operandType0 =
validator.getValidatedNodeType(call.operand(0));
RelDataType operandType1 =
validator.getValidatedNodeType(call.operand(1));
if (SqlTypeUtil.inCharFamily(operandType0)
&& SqlTypeUtil.inCharFamily(operandType1)) {
Charset cs0 = operandType0.getCharset();
Charset cs1 = operandType1.getCharset();
assert (null != cs0) && (null != cs1)
: "An implicit or explicit charset should have been set";
if (!cs0.equals(cs1)) {
throw validator.newValidationError(call,
RESOURCE.incompatibleCharset(getName(), cs0.name(), cs1.name()));
}
SqlCollation collation0 = operandType0.getCollation();
SqlCollation collation1 = operandType1.getCollation();
assert (null != collation0) && (null != collation1)
: "An implicit or explicit collation should have been set";
// Validation will occur inside getCoercibilityDyadicOperator...
SqlCollation resultCol =
SqlCollation.getCoercibilityDyadicOperator(
collation0,
collation1);
if (SqlTypeUtil.inCharFamily(type)) {
type =
validator.getTypeFactory()
.createTypeWithCharsetAndCollation(
type,
type.getCharset(),
requireNonNull(resultCol, "resultCol"));
}
}
return type;
}
@Override public RelDataType deriveType(
SqlValidator validator,
SqlValidatorScope scope,
SqlCall call) {
RelDataType type = super.deriveType(validator, scope, call);
return convertType(validator, call, type);
}
@Override public SqlMonotonicity getMonotonicity(SqlOperatorBinding call) {
if (getName().equals("/")) {
if (call.isOperandNull(0, true)
|| call.isOperandNull(1, true)) {
// null result => CONSTANT monotonicity
return SqlMonotonicity.CONSTANT;
}
final SqlMonotonicity mono0 = call.getOperandMonotonicity(0);
final SqlMonotonicity mono1 = call.getOperandMonotonicity(1);
if (mono1 == SqlMonotonicity.CONSTANT) {
if (call.isOperandLiteral(1, false)) {
BigDecimal value = call.getOperandLiteralValue(1, BigDecimal.class);
if (value == null) {
return SqlMonotonicity.CONSTANT;
}
switch (value.signum()) {
case -1:
// mono / -ve constant --> reverse mono, unstrict
return mono0.reverse().unstrict();
case 0:
// mono / zero --> constant (infinity!)
return SqlMonotonicity.CONSTANT;
default:
// mono / +ve constant * mono1 --> mono, unstrict
return mono0.unstrict();
}
}
}
}
return super.getMonotonicity(call);
}
@Override public boolean validRexOperands(int count, Litmus litmus) {
if (count != 2) {
// Special exception for AND and OR.
if ((this == SqlStdOperatorTable.AND
|| this == SqlStdOperatorTable.OR)
&& count > 2) {
return true;
}
return litmus.fail("wrong operand count {} for {}", count, this);
}
return litmus.succeed();
}
}