Skip to content

Commit

Permalink
[CALCITE-3779] Implement BITAND, BITOR, BITXOR scalar functions
Browse files Browse the repository at this point in the history
* All three functions accept either a pair of Integer or Binary values as arguments
* Binary arguments must be of the same length
* Returns NULL if any argument is NULL
* If all arguments are NULL, throw an error
* Returns a value of the same type as the first argument if both arguments are not null
  • Loading branch information
normanj-bitquill authored and mihaibudiu committed Sep 10, 2024
1 parent 67405c3 commit 890f9ad
Show file tree
Hide file tree
Showing 10 changed files with 258 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -612,6 +612,9 @@ ExInst<SqlValidatorException> intervalFractionalSecondPrecisionOutOfRange(
@BaseMessage("Argument to function ''{0}'' must not be NULL")
ExInst<SqlValidatorException> argumentMustNotBeNull(String a0);

@BaseMessage("At least one argument to function ''{0}'' must not be NULL")
ExInst<SqlValidatorException> atLeastOneArgumentMustNotBeNull(String a0);

@BaseMessage("Illegal use of ''NULL''")
ExInst<SqlValidatorException> nullIllegal();

Expand Down
42 changes: 42 additions & 0 deletions core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java
Original file line number Diff line number Diff line change
Expand Up @@ -2835,6 +2835,20 @@ public static long bitAnd(long b0, long b1) {
return b0 & b1;
}

/** Bitwise function <code>BITAND</code> 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 <code>BITAND</code> 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 <code>BIT_AND</code> applied to binary values. */
public static ByteString bitAnd(ByteString b0, ByteString b1) {
return binaryOperator(b0, b1, (x, y) -> (byte) (x & y));
Expand Down Expand Up @@ -2882,6 +2896,20 @@ public static long bitOr(long b0, long b1) {
return b0 | b1;
}

/** Bitwise function <code>BITOR</code> 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 <code>BITOR</code> 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 <code>BIT_OR</code> applied to binary values. */
public static ByteString bitOr(ByteString b0, ByteString b1) {
return binaryOperator(b0, b1, (x, y) -> (byte) (x | y));
Expand All @@ -2892,6 +2920,20 @@ public static long bitXor(long b0, long b1) {
return b0 ^ b1;
}

/** Bitwise function <code>BITXOR</code> 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 <code>BITXOR</code> 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 <code>BIT_XOR</code> applied to binary values. */
public static ByteString bitXor(ByteString b0, ByteString b1) {
return binaryOperator(b0, b1, (x, y) -> (byte) (x ^ y));
Expand Down
9 changes: 9 additions & 0 deletions core/src/main/java/org/apache/calcite/sql/SqlKind.java
Original file line number Diff line number Diff line change
Expand Up @@ -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,

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1196,6 +1196,30 @@ public class SqlStdOperatorTable extends ReflectiveSqlOperatorTable {
.create("BITCOUNT", ReturnTypes.BIGINT_NULLABLE,
OperandTypes.INTEGER.or(OperandTypes.BINARY), SqlFunctionCategory.NUMERIC);

/**
* <code>BITAND</code> 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));

/**
* <code>BITOR</code> 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));

/**
* <code>BITXOR</code> 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));

/**
* <code>BIT_AND</code> aggregate function.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
38 changes: 38 additions & 0 deletions core/src/main/java/org/apache/calcite/sql/type/ReturnTypes.java
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions site/_docs/reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
126 changes: 126 additions & 0 deletions testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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\\(<DECIMAL\\(2, 1\\)>, <DECIMAL\\(2, 1\\)>\\)'\\. Supported form\\(s\\): '"
+ "BITAND\\(<INTEGER>, <INTEGER>\\)'\n"
+ "'BITAND\\(<BINARY>, <BINARY>\\)'",
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\\(<DECIMAL\\(2, 1\\)>, <DECIMAL\\(2, 1\\)>\\)'\\. Supported form\\(s\\): '"
+ "BITOR\\(<INTEGER>, <INTEGER>\\)'\n"
+ "'BITOR\\(<BINARY>, <BINARY>\\)'",
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\\(<DECIMAL\\(2, 1\\)>, <DECIMAL\\(2, 1\\)>\\)'\\. Supported form\\(s\\): '"
+ "BITXOR\\(<INTEGER>, <INTEGER>\\)'\n"
+ "'BITXOR\\(<BINARY>, <BINARY>\\)'",
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);
Expand Down

0 comments on commit 890f9ad

Please sign in to comment.