IMPALA-13517: Support overloaded || operator

The || operator is used for both "or" and "concat". A new
Impala custom operator is created to handle both of them,
treating the precedence of the operator as if it's an "or".

The "or" is chosen if both parameters are null or boolean, as
taken from logic in CompoundVerticalBarExpr.

At convertlet time (when converting from SqlNode to RelNode),
the real operator is placed into the RexNode.

Change-Id: Iabaf02e84b769db1419bd96e1d1b30b8f83d56f5
Reviewed-on: http://gerrit.cloudera.org:8080/22105
Reviewed-by: Steve Carlin <scarlin@cloudera.com>
Tested-by: Steve Carlin <scarlin@cloudera.com>
This commit is contained in:
Steve Carlin
2024-11-05 14:28:51 -08:00
parent 1157d6e10f
commit 30979e7d30
5 changed files with 131 additions and 14 deletions

View File

@@ -123,6 +123,7 @@ import org.apache.calcite.util.Util;
import org.apache.calcite.util.trace.CalciteTrace;
import org.apache.impala.calcite.type.ImpalaSqlIntervalQualifier;
import org.apache.impala.calcite.operators.ImpalaCustomOperatorTable;
import org.apache.impala.calcite.operators.ImpalaConcatOrOperator;
import org.apache.impala.calcite.util.ParserUtil;
import com.google.common.collect.ImmutableList;
@@ -7740,17 +7741,17 @@ SqlBinaryOperator BinaryRowOperator() :
}
return SqlStdOperatorTable.NOT_EQUALS;
}
| <PLUS> { return SqlStdOperatorTable.PLUS; }
| <MINUS> { return SqlStdOperatorTable.MINUS; }
| <STAR> { return SqlStdOperatorTable.MULTIPLY; }
| <SLASH> { return SqlStdOperatorTable.DIVIDE; }
| <PLUS> { return ImpalaCustomOperatorTable.PLUS; }
| <MINUS> { return ImpalaCustomOperatorTable.MINUS; }
| <STAR> { return ImpalaCustomOperatorTable.MULTIPLY; }
| <SLASH> { return ImpalaCustomOperatorTable.DIVIDE; }
| <PERCENT_REMAINDER> {
if (!this.conformance.isPercentRemainderAllowed()) {
throw SqlUtil.newContextException(getPos(), RESOURCE.percentRemainderNotAllowed());
}
return SqlStdOperatorTable.PERCENT_REMAINDER;
}
| <CONCAT> { return SqlStdOperatorTable.CONCAT; }
| <CONCAT> { return ImpalaCustomOperatorTable.CONCAT_OR; }
| <AND> { return SqlStdOperatorTable.AND; }
| <OR> { return SqlStdOperatorTable.OR; }
| LOOKAHEAD(2) <IS> <DISTINCT> <FROM> { return SqlStdOperatorTable.IS_DISTINCT_FROM; }

View File

@@ -104,6 +104,10 @@ public class RexCallConverter {
return null;
}
if (fn.functionName().equals("or")) {
return createCompoundExpr(rexCall, params);
}
Type impalaRetType = ImpalaTypeConverter.createImpalaType(fn.getReturnType(),
rexCall.getType().getPrecision(), rexCall.getType().getScale());

View File

@@ -0,0 +1,82 @@
// 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.impala.calcite.operators;
import com.google.common.base.Preconditions;
import org.apache.calcite.rel.type.RelDataType;
import org.apache.calcite.sql.SqlBinaryOperator;
import org.apache.calcite.sql.SqlCallBinding;
import org.apache.calcite.sql.SqlFunction;
import org.apache.calcite.sql.SqlFunctionCategory;
import org.apache.calcite.sql.SqlKind;
import org.apache.calcite.sql.SqlOperandCountRange;
import org.apache.calcite.sql.SqlOperator;
import org.apache.calcite.sql.SqlOperatorBinding;
import org.apache.calcite.sql.SqlSyntax;
import org.apache.calcite.sql.type.SqlOperandCountRanges;
import org.apache.calcite.sql.type.SqlOperandTypeChecker;
import org.apache.calcite.sql.type.SqlTypeName;
import org.apache.impala.calcite.type.ImpalaTypeConverter;
import org.apache.impala.catalog.Function;
import org.apache.impala.catalog.Type;
import java.util.List;
/**
* Operator used for overloaded || which is used for both "or" and "concat".
* When both arguments are boolean, we assume it's an "or". This logic can be
* found in CompoundVerticalBarExpr.
*/
public class ImpalaConcatOrOperator extends SqlBinaryOperator {
public static ImpalaConcatOrOperator INSTANCE = new ImpalaConcatOrOperator();
public ImpalaConcatOrOperator() {
// use 22 for precedence value which is the same as SqlStdOperatorTable.OR
super("||", SqlKind.OTHER, 22, true, null, null, null);
}
@Override
public RelDataType inferReturnType(SqlOperatorBinding opBinding) {
final List<RelDataType> operandTypes =
CommonOperatorFunctions.getOperandTypes(opBinding);
Preconditions.checkState(operandTypes.size() == 2);
if (isOrOperand(opBinding, operandTypes.get(0), 0) &&
isOrOperand(opBinding, operandTypes.get(1), 1)) {
return ImpalaTypeConverter.getRelDataType(Type.BOOLEAN);
}
return ImpalaTypeConverter.getRelDataType(Type.STRING);
}
@Override
public SqlOperandCountRange getOperandCountRange() {
return SqlOperandCountRanges.of(2);
}
@Override
public boolean checkOperandTypes(SqlCallBinding callBinding, boolean throwOnFailure) {
// Validation for operand types are done when checking for signature in
// the inferReturnType method.
return true;
}
private boolean isOrOperand(SqlOperatorBinding opBinding, RelDataType opType,
int opNum) {
return opType.getSqlTypeName() == SqlTypeName.BOOLEAN
|| opBinding.isOperandNull(opNum, false);
}
}

View File

@@ -16,6 +16,7 @@
*/
package org.apache.impala.calcite.operators;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import org.apache.calcite.rel.type.RelDataType;
import org.apache.calcite.rex.RexBuilder;
@@ -23,7 +24,9 @@ import org.apache.calcite.rex.RexNode;
import org.apache.calcite.sql.SqlCall;
import org.apache.calcite.sql.SqlKind;
import org.apache.calcite.sql.SqlNode;
import org.apache.calcite.sql.SqlOperator;
import org.apache.calcite.sql.fun.SqlStdOperatorTable;
import org.apache.calcite.sql.type.SqlTypeName;
import org.apache.calcite.sql2rel.ReflectiveConvertletTable;
import org.apache.calcite.sql2rel.SqlRexContext;
import org.apache.calcite.sql2rel.SqlRexConvertlet;
@@ -31,6 +34,7 @@ import org.apache.calcite.sql2rel.StandardConvertletTable;
import org.apache.impala.calcite.operators.ImpalaCustomOperatorTable;
import java.util.List;
import java.util.Set;
/**
* ImpalaConvertletTable adds the ability to override any converlets in the
@@ -39,6 +43,13 @@ import java.util.List;
* RexNodes from SqlNodes.
*/
public class ImpalaConvertletTable extends ReflectiveConvertletTable {
// Map of Calcite names to an Impala function name when the names are different
public static Set<String> IMPALA_OVERRIDE_CONVERTLETS =
ImmutableSet.<String> builder()
.add("||")
.build();
public static final ImpalaConvertletTable INSTANCE =
new ImpalaConvertletTable();
@@ -47,6 +58,7 @@ public class ImpalaConvertletTable extends ReflectiveConvertletTable {
registerOp(ImpalaCastFunction.INSTANCE, this::convertExplicitCast);
registerOp(SqlStdOperatorTable.IS_DISTINCT_FROM, this::convertIsDistinctFrom);
registerOp(SqlStdOperatorTable.IS_NOT_DISTINCT_FROM, this::convertIsNotDistinctFrom);
registerOp(ImpalaConcatOrOperator.INSTANCE, this::convertConcatOr);
}
@Override
@@ -70,6 +82,12 @@ public class ImpalaConvertletTable extends ReflectiveConvertletTable {
return super.get(call);
}
// Registered convertlets need to call our convertlet rather than the
// StandardConvertletTable convertlet which will not find the function.
if (IMPALA_OVERRIDE_CONVERTLETS.contains(call.getOperator().getName())) {
return super.get(call);
}
return StandardConvertletTable.INSTANCE.get(call);
}
@@ -108,4 +126,24 @@ public class ImpalaConvertletTable extends ReflectiveConvertletTable {
List<RexNode> operands = Lists.newArrayList(cx.convertExpression(expr));
return rexBuilder.makeCall(returnType, ImpalaCastFunction.INSTANCE, operands);
}
/**
* Convertlet to decide whether the || operator is an "or" or a "concat".
* If the return type is BOOLEAN, then we know it's an OR.
*/
protected RexNode convertConcatOr (
SqlRexContext cx, SqlCall call) {
final SqlNode expr1 = call.operand(0);
final SqlNode expr2 = call.operand(1);
final RexBuilder rexBuilder = cx.getRexBuilder();
RelDataType returnType =
cx.getValidator().getValidatedNodeTypeIfKnown(call);
List<RexNode> operands = Lists.newArrayList(
cx.convertExpression(expr1),
cx.convertExpression(expr2));
SqlOperator op = returnType.getSqlTypeName().equals(SqlTypeName.BOOLEAN)
? SqlStdOperatorTable.OR
: SqlStdOperatorTable.CONCAT;
return rexBuilder.makeCall(returnType, op, operands);
}
}

View File

@@ -222,15 +222,7 @@ public class ImpalaCustomOperatorTable extends ReflectiveSqlOperatorTable {
public static final SqlAggFunction GROUPING_ID =
new ImpalaGroupingIdFunction();
public static final SqlBinaryOperator CONCAT =
new SqlBinaryOperator(
"||",
SqlKind.OTHER,
60,
true,
STRING_TYPE,
null,
OperandTypes.STRING_SAME_SAME_OR_ARRAY_SAME_SAME);
public static final ImpalaConcatOrOperator CONCAT_OR = ImpalaConcatOrOperator.INSTANCE;
// The explicit cast function was created to deal with the cast function using
// Impala behavior. The operator is in the operator table because the Calcite