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 1fa5cbff68b3..7c2e7565d924 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 @@ -346,6 +346,7 @@ 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.BITNOT; 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; @@ -702,6 +703,8 @@ void populate1() { NullPolicy.STRICT); defineMethod(BITXOR, BuiltInMethod.BIT_XOR.method, NullPolicy.STRICT); + defineMethod(BITNOT, BuiltInMethod.BIT_NOT.method, + NullPolicy.STRICT); define(CONCAT, new ConcatImplementor()); defineMethod(CONCAT_FUNCTION, BuiltInMethod.MULTI_STRING_CONCAT.method, NullPolicy.STRICT); 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 9e4250e44b28..de84a23a2905 100644 --- a/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java +++ b/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java @@ -2940,6 +2940,21 @@ public static ByteString bitXor(ByteString b0, ByteString b1) { return binaryOperator(b0, b1, (x, y) -> (byte) (x ^ y)); } + /** Bitwise function BIT_NOT applied to integer values. */ + public static long bitNot(long b) { + return ~b; + } + + /** Bitwise function BIT_NOT applied to a binary value. */ + public static ByteString bitNot(ByteString b) { + final byte[] result = new byte[b.length()]; + for (int i = 0; i < b.length(); i++) { + result[i] = (byte) ~b.byteAt(i); + } + + return new ByteString(result); + } + /** * Utility for bitwise function applied to two byteString values. * 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 64f692830109..03a0178f0564 100644 --- a/core/src/main/java/org/apache/calcite/sql/SqlKind.java +++ b/core/src/main/java/org/apache/calcite/sql/SqlKind.java @@ -1116,6 +1116,9 @@ public enum SqlKind { /** The {@code BITXOR} scalar function. */ BITXOR, + /** The {@code BITNOT} scalar function. */ + BITNOT, + /** 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 2be1800acc6f..00dd8df9b3b5 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 @@ -1220,6 +1220,14 @@ public class SqlStdOperatorTable extends ReflectiveSqlOperatorTable { ReturnTypes.LARGEST_INT_OR_FIRST_NON_NULL, OperandTypes.INTEGER_INTEGER.or(OperandTypes.BINARY_BINARY)); + /** + * BITNOT scalar function. + */ + public static final SqlFunction BITNOT = + SqlBasicFunction.create("BITNOT", SqlKind.BITNOT, + ReturnTypes.ARG0_OR_INTEGER, + OperandTypes.INTEGER.or(OperandTypes.BINARY)); + /** * BIT_AND aggregate function. */ 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 e2e4c2fb5f87..ae8596e01e86 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,16 @@ public static SqlCall stripSeparator(SqlCall call) { public static final SqlReturnTypeInference ARG0_EXCEPT_INTEGER_NULLABLE = ARG0_EXCEPT_INTEGER.andThen(SqlTypeTransforms.TO_NULLABLE); + public static final SqlReturnTypeInference ARG0_OR_INTEGER = + opBinding -> { + final RelDataTypeFactory typeFactory = opBinding.getTypeFactory(); + if (SqlTypeName.NULL == opBinding.getOperandType(0).getSqlTypeName()) { + return typeFactory.createTypeWithNullability( + typeFactory.createSqlType(SqlTypeName.INTEGER), true); + } + return opBinding.getOperandType(0); + }; + /** * Chooses a type to return. * If all arguments are null, return nullable integer type. diff --git a/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java b/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java index a923fcd842ef..05a9355447b5 100644 --- a/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java +++ b/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java @@ -647,6 +647,7 @@ public enum BuiltInMethod { BITCOUNT(SqlFunctions.class, "bitCount", BigDecimal.class), BIT_OR(SqlFunctions.class, "bitOr", long.class, long.class), BIT_XOR(SqlFunctions.class, "bitXor", long.class, long.class), + BIT_NOT(SqlFunctions.class, "bitNot", long.class), MODIFIABLE_TABLE_GET_MODIFIABLE_COLLECTION(ModifiableTable.class, "getModifiableCollection"), SCANNABLE_TABLE_SCAN(ScannableTable.class, "scan", DataContext.class), diff --git a/site/_docs/reference.md b/site/_docs/reference.md index 3d855df7fb72..c18bf6f69189 100644 --- a/site/_docs/reference.md +++ b/site/_docs/reference.md @@ -2753,6 +2753,7 @@ In the following: | * | 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. +| * | BITNOT(value) | Returns the bitwise NOT of *value*. *value* must be either an integer type or a binary value. | 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 96772c7ce1f8..a6e1c0b311da 100644 --- a/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java +++ b/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java @@ -15738,6 +15738,29 @@ private static void checkLogicalOrFunc(SqlOperatorFixture f) { true); } + @Test void testBitNotScalarFunc() { + final SqlOperatorFixture f = fixture(); + f.setFor(SqlStdOperatorTable.BITXOR, VmName.EXPAND); + f.checkFails("bitnot(^*^)", "Unknown identifier '\\*'", false); + f.checkScalar("bitnot(2)", Integer.toString(~2), "INTEGER NOT NULL"); + f.checkScalar("bitnot(-5)", Integer.toString(~-5), "INTEGER NOT NULL"); + f.checkScalar("bitnot(CAST(-5 AS TINYINT))", Byte.toString((byte) ~-5), "TINYINT NOT NULL"); + f.checkScalar("bitnot(CAST(2 AS SMALLINT))", Short.toString((short) ~2), "SMALLINT NOT NULL"); + f.checkScalar("bitnot(CAST(2 AS INTEGER))", Integer.toString(~2), "INTEGER NOT NULL"); + f.checkScalar("bitnot(CAST(2 AS BIGINT))", Long.toString(~2), "BIGINT NOT NULL"); + f.checkScalar("bitnot(CAST(x'0201' AS BINARY(2)))", "fdfe", + "BINARY(2) NOT NULL"); + f.checkScalar("bitnot(CAST(x'0201' AS VARBINARY(2)))", "fdfe", + "VARBINARY(2) NOT NULL"); + f.checkFails("^bitnot()^", + "Invalid number of arguments to function 'BITNOT'. Was expecting 1 arguments", + false); + f.checkFails("^bitnot(1, 2)^", + "Invalid number of arguments to function 'BITNOT'. Was expecting 1 arguments", + false); + f.checkNull("bitnot(NULL)"); + } + @Test void testBitAndAggFunc() { final SqlOperatorFixture f = fixture(); f.setFor(SqlLibraryOperators.BITAND_AGG, VmName.EXPAND);