diff --git a/core/src/main/java/org/apache/calcite/adapter/enumerable/RexImpTable.java b/core/src/main/java/org/apache/calcite/adapter/enumerable/RexImpTable.java index 5ffd55297018..3f63268eeafa 100644 --- a/core/src/main/java/org/apache/calcite/adapter/enumerable/RexImpTable.java +++ b/core/src/main/java/org/apache/calcite/adapter/enumerable/RexImpTable.java @@ -341,7 +341,10 @@ import static org.apache.calcite.sql.fun.SqlStdOperatorTable.ASIN; import static org.apache.calcite.sql.fun.SqlStdOperatorTable.ATAN; import static org.apache.calcite.sql.fun.SqlStdOperatorTable.ATAN2; +import static org.apache.calcite.sql.fun.SqlStdOperatorTable.BITAND; import static org.apache.calcite.sql.fun.SqlStdOperatorTable.BITCOUNT; +import static org.apache.calcite.sql.fun.SqlStdOperatorTable.BITOR; +import static org.apache.calcite.sql.fun.SqlStdOperatorTable.BITXOR; import static org.apache.calcite.sql.fun.SqlStdOperatorTable.BIT_AND; import static org.apache.calcite.sql.fun.SqlStdOperatorTable.BIT_OR; import static org.apache.calcite.sql.fun.SqlStdOperatorTable.BIT_XOR; @@ -600,6 +603,12 @@ Builder populate() { NullPolicy.STRICT); defineMethod(GETBIT, BuiltInMethod.BIT_GET.method, NullPolicy.STRICT); + defineMethod(BITAND, BuiltInMethod.BIT_AND.method, + NullPolicy.STRICT); + defineMethod(BITOR, BuiltInMethod.BIT_OR.method, + NullPolicy.STRICT); + defineMethod(BITXOR, BuiltInMethod.BIT_XOR.method, + NullPolicy.STRICT); map.put(CONCAT, new ConcatImplementor()); defineMethod(CONCAT_FUNCTION, BuiltInMethod.MULTI_STRING_CONCAT.method, NullPolicy.STRICT); diff --git a/core/src/main/java/org/apache/calcite/runtime/CalciteResource.java b/core/src/main/java/org/apache/calcite/runtime/CalciteResource.java index b0a2a00a0c2c..67236d9c7456 100644 --- a/core/src/main/java/org/apache/calcite/runtime/CalciteResource.java +++ b/core/src/main/java/org/apache/calcite/runtime/CalciteResource.java @@ -612,6 +612,9 @@ ExInst intervalFractionalSecondPrecisionOutOfRange( @BaseMessage("Argument to function ''{0}'' must not be NULL") ExInst argumentMustNotBeNull(String a0); + @BaseMessage("At least one argument to function ''{0}'' must not be NULL") + ExInst atLeastOneArgumentMustNotBeNull(String a0); + @BaseMessage("Illegal use of ''NULL''") ExInst nullIllegal(); diff --git a/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java b/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java index 0c2634ab508d..ba60ed6e5b1f 100644 --- a/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java +++ b/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java @@ -2835,6 +2835,20 @@ public static long bitAnd(long b0, long b1) { return b0 & b1; } + /** Bitwise function BITAND applied to a Long and int value. + * Needed for handling NULL for the first argument. + */ + public static long bitAnd(Long b0, int b1) { + return b0 & b1; + } + + /** Bitwise function BITAND applied to a Long and int value. + * Needed for handling NULL for the second argument. + */ + public static long bitAnd(int b0, Long b1) { + return b0 & b1; + } + /** Bitwise function BIT_AND applied to binary values. */ public static ByteString bitAnd(ByteString b0, ByteString b1) { return binaryOperator(b0, b1, (x, y) -> (byte) (x & y)); @@ -2882,6 +2896,20 @@ public static long bitOr(long b0, long b1) { return b0 | b1; } + /** Bitwise function BITOR applied to a Long and int value. + * Needed for handling NULL for the first argument. + */ + public static long bitOr(Long b0, int b1) { + return b0 | b1; + } + + /** Bitwise function BITOR applied to a Long and int value. + * Needed for handling NULL for the second argument. + */ + public static long bitOr(int b0, Long b1) { + return b0 | b1; + } + /** Bitwise function BIT_OR applied to binary values. */ public static ByteString bitOr(ByteString b0, ByteString b1) { return binaryOperator(b0, b1, (x, y) -> (byte) (x | y)); @@ -2892,6 +2920,20 @@ public static long bitXor(long b0, long b1) { return b0 ^ b1; } + /** Bitwise function BITXOR applied to a Long and int value. + * Needed for handling NULL for the first argument. + */ + public static long bitXor(Long b0, int b1) { + return b0 ^ b1; + } + + /** Bitwise function BITXOR applied to a Long and int value. + * Needed for handling NULL for the second argument. + */ + public static long bitXor(int b0, Long b1) { + return b0 ^ b1; + } + /** Bitwise function BIT_XOR applied to binary values. */ public static ByteString bitXor(ByteString b0, ByteString b1) { return binaryOperator(b0, b1, (x, y) -> (byte) (x ^ y)); diff --git a/core/src/main/java/org/apache/calcite/sql/SqlKind.java b/core/src/main/java/org/apache/calcite/sql/SqlKind.java index 62379b302e83..64f692830109 100644 --- a/core/src/main/java/org/apache/calcite/sql/SqlKind.java +++ b/core/src/main/java/org/apache/calcite/sql/SqlKind.java @@ -1107,6 +1107,15 @@ public enum SqlKind { /** The {@code AGGREGATE} aggregate function. */ AGGREGATE_FN, + /** The {@code BITAND} scalar function. */ + BITAND, + + /** The {@code BITOR} scalar function. */ + BITOR, + + /** The {@code BITXOR} scalar function. */ + BITXOR, + /** The {@code BIT_AND} aggregate function. */ BIT_AND, diff --git a/core/src/main/java/org/apache/calcite/sql/fun/SqlStdOperatorTable.java b/core/src/main/java/org/apache/calcite/sql/fun/SqlStdOperatorTable.java index aed849d4de34..2be1800acc6f 100644 --- a/core/src/main/java/org/apache/calcite/sql/fun/SqlStdOperatorTable.java +++ b/core/src/main/java/org/apache/calcite/sql/fun/SqlStdOperatorTable.java @@ -1196,6 +1196,30 @@ public class SqlStdOperatorTable extends ReflectiveSqlOperatorTable { .create("BITCOUNT", ReturnTypes.BIGINT_NULLABLE, OperandTypes.INTEGER.or(OperandTypes.BINARY), SqlFunctionCategory.NUMERIC); + /** + * BITAND scalar function. + */ + public static final SqlFunction BITAND = + SqlBasicFunction.create("BITAND", SqlKind.BITAND, + ReturnTypes.LARGEST_INT_OR_FIRST_NON_NULL, + OperandTypes.INTEGER_INTEGER.or(OperandTypes.BINARY_BINARY)); + + /** + * BITOR scalar function. + */ + public static final SqlFunction BITOR = + SqlBasicFunction.create("BITOR", SqlKind.BITOR, + ReturnTypes.LARGEST_INT_OR_FIRST_NON_NULL, + OperandTypes.INTEGER_INTEGER.or(OperandTypes.BINARY_BINARY)); + + /** + * BITXOR scalar function. + */ + public static final SqlFunction BITXOR = + SqlBasicFunction.create("BITXOR", SqlKind.BITXOR, + ReturnTypes.LARGEST_INT_OR_FIRST_NON_NULL, + OperandTypes.INTEGER_INTEGER.or(OperandTypes.BINARY_BINARY)); + /** * BIT_AND aggregate function. */ diff --git a/core/src/main/java/org/apache/calcite/sql/type/OperandTypes.java b/core/src/main/java/org/apache/calcite/sql/type/OperandTypes.java index a9544ca463ec..39bdad21cac2 100644 --- a/core/src/main/java/org/apache/calcite/sql/type/OperandTypes.java +++ b/core/src/main/java/org/apache/calcite/sql/type/OperandTypes.java @@ -398,6 +398,9 @@ public static SqlOperandTypeChecker variadic( public static final SqlSingleOperandTypeChecker INTEGER = family(SqlTypeFamily.INTEGER); + public static final SqlSingleOperandTypeChecker INTEGER_INTEGER = + family(SqlTypeFamily.INTEGER, SqlTypeFamily.INTEGER); + public static final SqlSingleOperandTypeChecker NUMERIC_OPTIONAL_NUMERIC = family(ImmutableList.of(SqlTypeFamily.NUMERIC, SqlTypeFamily.NUMERIC), // Second operand optional (operand index 0, 1) diff --git a/core/src/main/java/org/apache/calcite/sql/type/ReturnTypes.java b/core/src/main/java/org/apache/calcite/sql/type/ReturnTypes.java index e4e8e11818dd..e2e4c2fb5f87 100644 --- a/core/src/main/java/org/apache/calcite/sql/type/ReturnTypes.java +++ b/core/src/main/java/org/apache/calcite/sql/type/ReturnTypes.java @@ -613,6 +613,44 @@ public static SqlCall stripSeparator(SqlCall call) { public static final SqlReturnTypeInference ARG0_EXCEPT_INTEGER_NULLABLE = ARG0_EXCEPT_INTEGER.andThen(SqlTypeTransforms.TO_NULLABLE); + /** + * Chooses a type to return. + * If all arguments are null, return nullable integer type. + * If all arguments are integer types, choose the largest integer type. Nullable + * if any argument is nullable. + * As a fallback, choose the type of the first argument that is not of the NULL type. + * Nullable if at least one argument is nullable. + */ + public static final SqlReturnTypeInference LARGEST_INT_OR_FIRST_NON_NULL = + opBinding -> { + final RelDataTypeFactory typeFactory = opBinding.getTypeFactory(); + RelDataType largestIntegerType = null; + RelDataType firstNonNullType = null; + boolean allArgsInteger = true; + boolean nullable = false; + for (RelDataType opType : opBinding.collectOperandTypes()) { + if (firstNonNullType == null && SqlTypeName.NULL != opType.getSqlTypeName()) { + firstNonNullType = opType; + } + if (SqlTypeName.INT_TYPES.contains(opType.getSqlTypeName()) + && (largestIntegerType == null + || largestIntegerType.getPrecision() < opType.getPrecision())) { + largestIntegerType = opType; + } else { + allArgsInteger = false; + } + nullable |= opType.isNullable(); + } + if (allArgsInteger && largestIntegerType != null) { + return typeFactory.createTypeWithNullability(largestIntegerType, nullable); + } else if (firstNonNullType != null) { + return typeFactory.createTypeWithNullability(firstNonNullType, nullable); + } + throw opBinding.newError( + RESOURCE.atLeastOneArgumentMustNotBeNull( + opBinding.getOperator().getName())); + }; + /** * Returns the same type as the multiset carries. The multiset type returned * is the least restrictive of the call's multiset operands diff --git a/core/src/main/resources/org/apache/calcite/runtime/CalciteResource.properties b/core/src/main/resources/org/apache/calcite/runtime/CalciteResource.properties index c860c001652e..55bf0ba73c3b 100644 --- a/core/src/main/resources/org/apache/calcite/runtime/CalciteResource.properties +++ b/core/src/main/resources/org/apache/calcite/runtime/CalciteResource.properties @@ -206,6 +206,7 @@ ArgumentMustBeNumericLiteralInRange=Argument to function ''{0}'' must be a numer ValidationError=Validation Error: {0} IllegalLocaleFormat=Locale ''{0}'' in an illegal format ArgumentMustNotBeNull=Argument to function ''{0}'' must not be NULL +AtLeastOneArgumentMustNotBeNull=At least one argument to function ''{0}'' must not be NULL NullIllegal=Illegal use of ''NULL'' DynamicParamIllegal=Illegal use of dynamic parameter InvalidBoolean=''{0}'' is not a valid boolean value diff --git a/site/_docs/reference.md b/site/_docs/reference.md index e55ff1be74dc..a57486c7a261 100644 --- a/site/_docs/reference.md +++ b/site/_docs/reference.md @@ -2750,6 +2750,9 @@ In the following: | * | ASINH(numeric) | Returns the inverse hyperbolic sine of *numeric* | p | ATAND(numeric) | Returns the inverse tangent of *numeric* in degrees as a double. Returns NaN if *numeric* is NaN. | * | ATANH(numeric) | Returns the inverse hyperbolic tangent of *numeric* +| * | BITAND(value1, value2) | Returns the bitwise AND of *value1* and *value2*. *value1* and *value2* must both be integer or binary values. Binary values must be of the same length. +| * | BITOR(value1, value2) | Returns the bitwise OR of *value1* and *value2*. *value1* and *value2* must both be integer or binary values. Binary values must be of the same length. +| * | BITXOR(value1, value2) | Returns the bitwise XOR of *value1* and *value2*. *value1* and *value2* must both be integer or binary values. Binary values must be of the same length. | f | BITAND_AGG(value) | Equivalent to `BIT_AND(value)` | f | BITOR_AGG(value) | Equivalent to `BIT_OR(value)` | * | BITCOUNT(value) | Returns the bitwise COUNT of *value* or NULL if *value* is NULL. *value* must be and integer or binary value. diff --git a/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java b/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java index 41619a9d9200..3a04c8f6bb12 100644 --- a/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java +++ b/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java @@ -15478,6 +15478,132 @@ private static void checkLogicalOrFunc(SqlOperatorFixture f) { f.checkAgg("logical_or(x)", values4, isNullValue()); } + @Test void testBitAndScalarFunc() { + final SqlOperatorFixture f = fixture(); + f.setFor(SqlStdOperatorTable.BITAND, VmName.EXPAND); + f.checkFails("bitand(^*^)", "Unknown identifier '\\*'", false); + f.checkScalar("bitand(2, 3)", "2", "INTEGER NOT NULL"); + f.checkScalar("bitand(CAST(2 AS INTEGER), CAST(3 AS BIGINT))", "2", "BIGINT NOT NULL"); + f.checkScalar("bitand(-5, 7)", "3", "INTEGER NOT NULL"); + f.checkScalar("bitand(-5, -31)", "-31", "INTEGER NOT NULL"); + f.checkScalar("bitand(CAST(-5 AS TINYINT), CAST(7 AS TINYINT))", "3", "TINYINT NOT NULL"); + f.checkScalar("bitand(CAST(-5 AS TINYINT), CAST(-31 AS TINYINT))", "-31", "TINYINT NOT NULL"); + f.checkType("bitand(CAST(2 AS TINYINT), CAST(6 AS TINYINT))", "TINYINT NOT NULL"); + f.checkType("bitand(CAST(2 AS SMALLINT), CAST(6 AS SMALLINT))", "SMALLINT NOT NULL"); + f.checkType("bitand(CAST(2 AS BIGINT), CAST(6 AS BIGINT))", "BIGINT NOT NULL"); + f.checkScalar("bitand(CAST(x'0201' AS BINARY(2)), CAST(x'07f9' AS BINARY(2)))", "0201", + "BINARY(2) NOT NULL"); + f.checkScalar("bitand(CAST(x'0201' AS VARBINARY(2)), CAST(x'07f9' AS VARBINARY(2)))", "0201", + "VARBINARY(2) NOT NULL"); + f.checkFails("^bitand(1.2, 1.3)^", + "Cannot apply 'BITAND' to arguments of type '" + + "BITAND\\(, \\)'\\. Supported form\\(s\\): '" + + "BITAND\\(, \\)'\n" + + "'BITAND\\(, \\)'", + false); + f.checkFails("^bitand()^", + "Invalid number of arguments to function 'BITAND'. Was expecting 2 arguments", + false); + f.checkFails("^bitand(1)^", + "Invalid number of arguments to function 'BITAND'. Was expecting 2 arguments", + false); + f.checkFails("^bitand(1, 2, 3)^", + "Invalid number of arguments to function 'BITAND'. Was expecting 2 arguments", + false); + f.checkNull("bitand(NULL, 1)"); + f.checkNull("bitand(1, NULL)"); + f.checkFails("^bitand(NULL, NULL)^", + "At least one argument to function 'BITAND' must not be NULL", + false); + f.checkFails("bitand(CAST(x'0201' AS VARBINARY), CAST(x'02' AS VARBINARY))", + "Different length for bitwise operands: the first: 2, the second: 1", + true); + } + + @Test void testBitOrScalarFunc() { + final SqlOperatorFixture f = fixture(); + f.setFor(SqlStdOperatorTable.BITOR, VmName.EXPAND); + f.checkFails("bitor(^*^)", "Unknown identifier '\\*'", false); + f.checkScalar("bitor(2, 4)", "6", "INTEGER NOT NULL"); + f.checkScalar("bitor(CAST(2 AS INTEGER), CAST(4 AS BIGINT))", "6", "BIGINT NOT NULL"); + f.checkScalar("bitor(-5, 7)", "-1", "INTEGER NOT NULL"); + f.checkScalar("bitor(-5, -31)", "-5", "INTEGER NOT NULL"); + f.checkScalar("bitor(CAST(-5 AS TINYINT), CAST(7 AS TINYINT))", "-1", "TINYINT NOT NULL"); + f.checkScalar("bitor(CAST(-5 AS TINYINT), CAST(-31 AS TINYINT))", "-5", "TINYINT NOT NULL"); + f.checkType("bitor(CAST(2 AS TINYINT), CAST(6 AS TINYINT))", "TINYINT NOT NULL"); + f.checkType("bitor(CAST(2 AS SMALLINT), CAST(6 AS SMALLINT))", "SMALLINT NOT NULL"); + f.checkType("bitor(CAST(2 AS BIGINT), CAST(6 AS BIGINT))", "BIGINT NOT NULL"); + f.checkScalar("bitor(CAST(x'0201' AS BINARY(2)), CAST(x'07f9' AS BINARY(2)))", "07f9", + "BINARY(2) NOT NULL"); + f.checkScalar("bitor(CAST(x'0201' AS VARBINARY(2)), CAST(x'07f9' AS VARBINARY(2)))", "07f9", + "VARBINARY(2) NOT NULL"); + f.checkFails("^bitor(1.2, 1.3)^", + "Cannot apply 'BITOR' to arguments of type '" + + "BITOR\\(, \\)'\\. Supported form\\(s\\): '" + + "BITOR\\(, \\)'\n" + + "'BITOR\\(, \\)'", + false); + f.checkFails("^bitor()^", + "Invalid number of arguments to function 'BITOR'. Was expecting 2 arguments", + false); + f.checkFails("^bitor(1)^", + "Invalid number of arguments to function 'BITOR'. Was expecting 2 arguments", + false); + f.checkFails("^bitor(1, 2, 3)^", + "Invalid number of arguments to function 'BITOR'. Was expecting 2 arguments", + false); + f.checkNull("bitor(NULL, 1)"); + f.checkNull("bitor(1, NULL)"); + f.checkFails("^bitor(NULL, NULL)^", + "At least one argument to function 'BITOR' must not be NULL", + false); + f.checkFails("bitor(CAST(x'0201' AS VARBINARY), CAST(x'02' AS VARBINARY))", + "Different length for bitwise operands: the first: 2, the second: 1", + true); + } + + @Test void testBitXorScalarFunc() { + final SqlOperatorFixture f = fixture(); + f.setFor(SqlStdOperatorTable.BITXOR, VmName.EXPAND); + f.checkFails("bitxor(^*^)", "Unknown identifier '\\*'", false); + f.checkScalar("bitxor(2, 3)", "1", "INTEGER NOT NULL"); + f.checkScalar("bitxor(CAST(2 AS INTEGER), CAST(3 AS BIGINT))", "1", "BIGINT NOT NULL"); + f.checkScalar("bitxor(-5, 7)", "-4", "INTEGER NOT NULL"); + f.checkScalar("bitxor(-5, -31)", "26", "INTEGER NOT NULL"); + f.checkScalar("bitxor(CAST(-5 AS TINYINT), CAST(7 AS TINYINT))", "-4", "TINYINT NOT NULL"); + f.checkScalar("bitxor(CAST(-5 AS TINYINT), CAST(-31 AS TINYINT))", "26", "TINYINT NOT NULL"); + f.checkType("bitxor(CAST(2 AS TINYINT), CAST(6 AS TINYINT))", "TINYINT NOT NULL"); + f.checkType("bitxor(CAST(2 AS SMALLINT), CAST(6 AS SMALLINT))", "SMALLINT NOT NULL"); + f.checkType("bitxor(CAST(2 AS BIGINT), CAST(6 AS BIGINT))", "BIGINT NOT NULL"); + f.checkScalar("bitxor(CAST(x'0201' AS BINARY(2)), CAST(x'07f9' AS BINARY(2)))", "05f8", + "BINARY(2) NOT NULL"); + f.checkScalar("bitxor(CAST(x'0201' AS VARBINARY(2)), CAST(x'07f9' AS VARBINARY(2)))", "05f8", + "VARBINARY(2) NOT NULL"); + f.checkFails("^bitxor(1.2, 1.3)^", + "Cannot apply 'BITXOR' to arguments of type '" + + "BITXOR\\(, \\)'\\. Supported form\\(s\\): '" + + "BITXOR\\(, \\)'\n" + + "'BITXOR\\(, \\)'", + false); + f.checkFails("^bitxor()^", + "Invalid number of arguments to function 'BITXOR'. Was expecting 2 arguments", + false); + f.checkFails("^bitxor(1)^", + "Invalid number of arguments to function 'BITXOR'. Was expecting 2 arguments", + false); + f.checkFails("^bitxor(1, 2, 3)^", + "Invalid number of arguments to function 'BITXOR'. Was expecting 2 arguments", + false); + f.checkNull("bitxor(NULL, 1)"); + f.checkNull("bitxor(1, NULL)"); + f.checkFails("^bitxor(NULL, NULL)^", + "At least one argument to function 'BITXOR' must not be NULL", + false); + f.checkFails("bitxor(CAST(x'0201' AS VARBINARY), CAST(x'02' AS VARBINARY))", + "Different length for bitwise operands: the first: 2, the second: 1", + true); + } + @Test void testBitAndAggFunc() { final SqlOperatorFixture f = fixture(); f.setFor(SqlLibraryOperators.BITAND_AGG, VmName.EXPAND);