All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.hazelcast.org.apache.calcite.sql.fun.SqlCaseOperator Maven / Gradle / Ivy

There is a newer version: 5.4.0
Show newest version
/*
 * 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 com.hazelcast.org.apache.calcite.sql.fun;

import com.hazelcast.org.apache.calcite.rel.type.RelDataType;
import com.hazelcast.org.apache.calcite.rel.type.RelDataTypeFactory;
import com.hazelcast.org.apache.calcite.rex.RexCall;
import com.hazelcast.org.apache.calcite.rex.RexCallBinding;
import com.hazelcast.org.apache.calcite.rex.RexNode;
import com.hazelcast.org.apache.calcite.sql.SqlBasicCall;
import com.hazelcast.org.apache.calcite.sql.SqlCall;
import com.hazelcast.org.apache.calcite.sql.SqlCallBinding;
import com.hazelcast.org.apache.calcite.sql.SqlKind;
import com.hazelcast.org.apache.calcite.sql.SqlLiteral;
import com.hazelcast.org.apache.calcite.sql.SqlNode;
import com.hazelcast.org.apache.calcite.sql.SqlNodeList;
import com.hazelcast.org.apache.calcite.sql.SqlOperandCountRange;
import com.hazelcast.org.apache.calcite.sql.SqlOperator;
import com.hazelcast.org.apache.calcite.sql.SqlOperatorBinding;
import com.hazelcast.org.apache.calcite.sql.SqlSyntax;
import com.hazelcast.org.apache.calcite.sql.SqlUtil;
import com.hazelcast.org.apache.calcite.sql.SqlWriter;
import com.hazelcast.org.apache.calcite.sql.parser.SqlParserPos;
import com.hazelcast.org.apache.calcite.sql.type.InferTypes;
import com.hazelcast.org.apache.calcite.sql.type.SqlOperandCountRanges;
import com.hazelcast.org.apache.calcite.sql.type.SqlTypeUtil;
import com.hazelcast.org.apache.calcite.sql.validate.SqlValidator;
import com.hazelcast.org.apache.calcite.sql.validate.SqlValidatorImpl;
import com.hazelcast.org.apache.calcite.sql.validate.SqlValidatorScope;
import com.hazelcast.org.apache.calcite.sql.validate.implicit.TypeCoercion;
import com.hazelcast.org.apache.calcite.util.Litmus;
import com.hazelcast.org.apache.calcite.util.Pair;

import com.hazelcast.com.google.common.collect.Iterables;

import com.hazelcast.org.checkerframework.checker.nullness.qual.Nullable;

import java.util.ArrayList;
import java.util.List;

import static com.hazelcast.org.apache.calcite.util.Static.RESOURCE;

import static java.util.Objects.requireNonNull;

/**
 * An operator describing a CASE, NULLIF or 
 * COALESCE expression. All of these forms are normalized at parse time
 * to a to a simple CASE statement like this:
 *
 * 
CASE
 *   WHEN <when expression_0> THEN <then expression_0>
 *   WHEN <when expression_1> THEN <then expression_1>
 *   ...
 *   WHEN <when expression_N> THEN <then expression_N>
 *   ELSE <else expression>
 * END
* *

The switched form of the CASE statement is normalized to the * simple form by inserting calls to the = operator. For * example,

* *
CASE x + y
 *   WHEN 1 THEN 'fee'
 *   WHEN 2 THEN 'fie'
 *   ELSE 'foe'
 * END
* *

becomes

* *
CASE
 * WHEN Equals(x + y, 1) THEN 'fee'
 * WHEN Equals(x + y, 2) THEN 'fie'
 * ELSE 'foe'
 * END
* *

REVIEW jhyde 2004/3/19 Does Equals handle NULL semantics * correctly?

* *

COALESCE(x, y, z) becomes

* *
CASE
 * WHEN x IS NOT NULL THEN x
 * WHEN y IS NOT NULL THEN y
 * ELSE z
 * END
* *

NULLIF(x, -1) becomes

* *
CASE
 * WHEN x = -1 THEN NULL
 * ELSE x
 * END
* *

Note that some of these normalizations cause expressions to be duplicated. * This may make it more difficult to write optimizer rules (because the rules * will have to deduce that expressions are equivalent). It also requires that * some part of the planning process (probably the generator of the calculator * program) does common sub-expression elimination.

* *

REVIEW jhyde 2004/3/19. Expanding expressions at parse time has some other * drawbacks. It is more difficult to give meaningful validation errors: given * COALESCE(DATE '2004-03-18', 3.5), do we issue a type-checking * error against a CASE operator? Second, I'd like to use the * {@link SqlNode} object model to generate SQL to send to 3rd-party databases, * but there's now no way to represent a call to COALESCE or NULLIF. All in all, * it would be better to have operators for COALESCE, NULLIF, and both simple * and switched forms of CASE, then translate to simple CASE when building the * {@link com.hazelcast.org.apache.calcite.rex.RexNode} tree.

* *

The arguments are physically represented as follows:

* *
    *
  • The when expressions are stored in a {@link SqlNodeList} * whenList.
  • *
  • The then expressions are stored in a {@link SqlNodeList} * thenList.
  • *
  • The else expression is stored as a regular {@link SqlNode}.
  • *
*/ public class SqlCaseOperator extends SqlOperator { public static final SqlCaseOperator INSTANCE = new SqlCaseOperator(); //~ Constructors ----------------------------------------------------------- private SqlCaseOperator() { super("CASE", SqlKind.CASE, MDX_PRECEDENCE, true, null, InferTypes.RETURN_TYPE, null); } //~ Methods ---------------------------------------------------------------- @Override public void validateCall( SqlCall call, SqlValidator validator, SqlValidatorScope scope, SqlValidatorScope operandScope) { final SqlCase sqlCase = (SqlCase) call; final SqlNodeList whenOperands = sqlCase.getWhenOperands(); final SqlNodeList thenOperands = sqlCase.getThenOperands(); final SqlNode elseOperand = sqlCase.getElseOperand(); for (SqlNode operand : whenOperands) { operand.validateExpr(validator, operandScope); } for (SqlNode operand : thenOperands) { operand.validateExpr(validator, operandScope); } if (elseOperand != null) { elseOperand.validateExpr(validator, operandScope); } } @Override public RelDataType deriveType( SqlValidator validator, SqlValidatorScope scope, SqlCall call) { // Do not try to derive the types of the operands. We will do that // later, top down. return validateOperands(validator, scope, call); } @Override public boolean checkOperandTypes( SqlCallBinding callBinding, boolean throwOnFailure) { SqlCase caseCall = (SqlCase) callBinding.getCall(); SqlNodeList whenList = caseCall.getWhenOperands(); SqlNodeList thenList = caseCall.getThenOperands(); assert whenList.size() == thenList.size(); // checking that search conditions are ok... for (SqlNode node : whenList) { // should throw validation error if something wrong... RelDataType type = SqlTypeUtil.deriveType(callBinding, node); if (!SqlTypeUtil.inBooleanFamily(type)) { if (throwOnFailure) { throw callBinding.newError(RESOURCE.expectedBoolean()); } return false; } } boolean foundNotNull = false; for (SqlNode node : thenList) { if (!SqlUtil.isNullLiteral(node, false)) { foundNotNull = true; } } if (!SqlUtil.isNullLiteral( caseCall.getElseOperand(), false)) { foundNotNull = true; } if (!foundNotNull) { // according to the sql standard we can not have all of the THEN // statements and the ELSE returning null if (throwOnFailure && !callBinding.isTypeCoercionEnabled()) { throw callBinding.newError(RESOURCE.mustNotNullInElse()); } return false; } return true; } @Override public RelDataType inferReturnType( SqlOperatorBinding opBinding) { // REVIEW jvs 4-June-2005: can't these be unified? if (!(opBinding instanceof SqlCallBinding)) { return inferTypeFromOperands(opBinding); } return inferTypeFromValidator((SqlCallBinding) opBinding); } private static RelDataType inferTypeFromValidator( SqlCallBinding callBinding) { SqlCase caseCall = (SqlCase) callBinding.getCall(); SqlNodeList thenList = caseCall.getThenOperands(); ArrayList nullList = new ArrayList<>(); List argTypes = new ArrayList<>(); final SqlNodeList whenOperands = caseCall.getWhenOperands(); final RelDataTypeFactory typeFactory = callBinding.getTypeFactory(); for (int i = 0; i < thenList.size(); i++) { SqlNode node = thenList.get(i); RelDataType type = SqlTypeUtil.deriveType(callBinding, node); SqlNode operand = whenOperands.get(i); if (operand.getKind() == SqlKind.IS_NOT_NULL && type.isNullable()) { SqlBasicCall call = (SqlBasicCall) operand; if (call.getOperandList().get(0).equalsDeep(node, Litmus.IGNORE)) { // We're sure that the type is not nullable if the kind is IS NOT NULL. type = typeFactory.createTypeWithNullability(type, false); } } argTypes.add(type); if (SqlUtil.isNullLiteral(node, false)) { nullList.add(node); } } SqlNode elseOp = requireNonNull(caseCall.getElseOperand(), () -> "elseOperand for " + caseCall); argTypes.add( SqlTypeUtil.deriveType(callBinding, elseOp)); if (SqlUtil.isNullLiteral(elseOp, false)) { nullList.add(elseOp); } RelDataType ret = typeFactory.leastRestrictive(argTypes); if (null == ret) { boolean coerced = false; if (callBinding.isTypeCoercionEnabled()) { TypeCoercion typeCoercion = callBinding.getValidator().getTypeCoercion(); RelDataType commonType = typeCoercion.getWiderTypeFor(argTypes, true); // commonType is always with nullability as false, we do not consider the // nullability when deducing the common type. Use the deduced type // (with the correct nullability) in SqlValidator // instead of the commonType as the return type. if (null != commonType) { coerced = typeCoercion.caseWhenCoercion(callBinding); if (coerced) { ret = SqlTypeUtil.deriveType(callBinding); } } } if (!coerced) { throw callBinding.newValidationError(RESOURCE.illegalMixingOfTypes()); } } final SqlValidatorImpl validator = (SqlValidatorImpl) callBinding.getValidator(); requireNonNull(ret, () -> "return type for " + callBinding); for (SqlNode node : nullList) { validator.setValidatedNodeType(node, ret); } return ret; } private static RelDataType inferTypeFromOperands(SqlOperatorBinding opBinding) { final RelDataTypeFactory typeFactory = opBinding.getTypeFactory(); final List argTypes = opBinding.collectOperandTypes(); assert (argTypes.size() % 2) == 1 : "odd number of arguments expected: " + argTypes.size(); assert argTypes.size() > 1 : "CASE must have more than 1 argument. Given " + argTypes.size() + ", " + argTypes; List thenTypes = new ArrayList<>(); for (int j = 1; j < (argTypes.size() - 1); j += 2) { RelDataType argType = argTypes.get(j); if (opBinding instanceof RexCallBinding) { final RexCallBinding rexCallBinding = (RexCallBinding) opBinding; final RexNode whenNode = rexCallBinding.operands().get(j - 1); final RexNode thenNode = rexCallBinding.operands().get(j); if (whenNode.getKind() == SqlKind.IS_NOT_NULL && argType.isNullable()) { // Type is not nullable if the kind is IS NOT NULL. final RexCall isNotNullCall = (RexCall) whenNode; if (isNotNullCall.getOperands().get(0).equals(thenNode)) { argType = typeFactory.createTypeWithNullability(argType, false); } } } thenTypes.add(argType); } thenTypes.add(Iterables.getLast(argTypes)); return requireNonNull( typeFactory.leastRestrictive(thenTypes), () -> "Can't find leastRestrictive type for " + thenTypes); } @Override public SqlOperandCountRange getOperandCountRange() { return SqlOperandCountRanges.any(); } @Override public SqlSyntax getSyntax() { return SqlSyntax.SPECIAL; } @SuppressWarnings("argument.type.incompatible") @Override public SqlCall createCall( @Nullable SqlLiteral functionQualifier, SqlParserPos pos, @Nullable SqlNode... operands) { assert functionQualifier == null; assert operands.length == 4; return new SqlCase(pos, operands[0], (SqlNodeList) operands[1], (SqlNodeList) operands[2], operands[3]); } @Override public void unparse(SqlWriter writer, SqlCall call_, int leftPrec, int rightPrec) { SqlCase kase = (SqlCase) call_; final SqlWriter.Frame frame = writer.startList(SqlWriter.FrameTypeEnum.CASE, "CASE", "END"); assert kase.whenList.size() == kase.thenList.size(); if (kase.value != null) { kase.value.unparse(writer, 0, 0); } for (Pair pair : Pair.zip(kase.whenList, kase.thenList)) { writer.sep("WHEN"); pair.left.unparse(writer, 0, 0); writer.sep("THEN"); pair.right.unparse(writer, 0, 0); } SqlNode elseExpr = kase.elseExpr; if (elseExpr != null) { writer.sep("ELSE"); elseExpr.unparse(writer, 0, 0); } writer.endList(frame); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy