mihaibudiu commented on code in PR #5073:
URL: https://github.com/apache/calcite/pull/5073#discussion_r3562047946
##########
testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java:
##########
@@ -17158,6 +17168,204 @@ private static void
checkLogicalOrFunc(SqlOperatorFixture f) {
f.checkNull("LEFTSHIFT(CAST(NULL AS INTEGER UNSIGNED), 2)");
}
+ /**
+ * Test cases for
+ * <a
href="https://issues.apache.org/jira/browse/CALCITE-7639">[CALCITE-7639]
+ * Support bitwise right shift (>>) operator and RIGHTSHIFT
function</a>.
+ */
+ @Test void testRightShiftScalarFunc() {
+ final SqlOperatorFixture f = fixture();
+ f.setFor(SqlStdOperatorTable.BIT_RIGHT_SHIFT, VmName.EXPAND);
+
+ // === Basic functionality ===
+ f.checkScalar("8 >> 2", "2", "INTEGER NOT NULL");
+ f.checkScalar("1024 >> 10", "1", "INTEGER NOT NULL");
+ f.checkScalar("0 >> 5", "0", "INTEGER NOT NULL");
+
+ // === Type coercion and signed (arithmetic) behavior ===
+ f.checkScalar("CAST(16 AS INTEGER) >> CAST(3 AS BIGINT)", "2", "INTEGER
NOT NULL");
+ f.checkScalar("-20 >> 2", "-5", "INTEGER NOT NULL");
+ f.checkScalar("-40 >> 3", "-5", "INTEGER NOT NULL");
+ f.checkScalar("CAST(-20 AS TINYINT) >> CAST(2 AS TINYINT)", "-5", "TINYINT
NOT NULL");
+
+ // === Verify return type matches first argument type ===
+ f.checkType("CAST(8 AS TINYINT) >> CAST(2 AS TINYINT)", "TINYINT NOT
NULL");
+ f.checkType("CAST(8 AS SMALLINT) >> CAST(2 AS SMALLINT)", "SMALLINT NOT
NULL");
+ f.checkType("CAST(8 AS INTEGER) >> CAST(2 AS INTEGER)", "INTEGER NOT
NULL");
+ f.checkType("CAST(8 AS BIGINT) >> CAST(2 AS BIGINT)", "BIGINT NOT NULL");
+ f.checkScalar("CAST(8 AS BIGINT) >> CAST(2 AS BIGINT)", "2", "BIGINT NOT
NULL");
+
+ // === BigInt shifts with explicit BIGINT inputs
(arithmetic/sign-preserving) ===
+ f.checkScalar("CAST(4611686018427387904 AS BIGINT) >> 62", "1", "BIGINT
NOT NULL"); // 2^62
+ f.checkScalar("CAST(9223372036854775807 AS BIGINT) >> 1",
+ BigInteger.valueOf(Long.MAX_VALUE).shiftRight(1).toString(), "BIGINT
NOT NULL");
+ f.checkScalar("CAST(-1 AS BIGINT) >> 63", "-1", "BIGINT NOT NULL"); //
sign bit preserved
+ f.checkScalar("CAST(-1 AS BIGINT) >> 1", "-1", "BIGINT NOT NULL");
+ f.checkScalar("CAST(1000000000 AS BIGINT) >> 5", "31250000", "BIGINT NOT
NULL");
+
+ // === Shift amount normalized using modulo of the bit width ===
+ f.checkScalar("CAST(1024 AS BIGINT) >> 64", "1024", "BIGINT NOT NULL"); //
64 % 64 = 0
+ f.checkScalar("CAST(1024 AS BIGINT) >> 74", "1", "BIGINT NOT NULL"); // 74
% 64 = 10
+ f.checkScalar("1 >> 32", "1", "INTEGER NOT NULL"); // 32 % 32 = 0
+ f.checkScalar("123 >> 60", "0", "INTEGER NOT NULL"); // 60 % 32 = 28
+
+ // === Unsigned types ===
+ f.checkScalar("CAST(252 AS TINYINT UNSIGNED) >> 2", "63", "TINYINT
UNSIGNED NOT NULL");
+ f.checkScalar("CAST(65280 AS SMALLINT UNSIGNED) >> 8", "255", "SMALLINT
UNSIGNED NOT NULL");
+ f.checkScalar("CAST(4294901760 AS INTEGER UNSIGNED) >> 16", "65535",
+ "INTEGER UNSIGNED NOT NULL");
+ f.checkScalar("CAST(2147483648 AS INTEGER UNSIGNED) >> 31", "1", "INTEGER
UNSIGNED NOT NULL");
+ f.checkScalar("CAST(1 AS INTEGER UNSIGNED) >> -1", "2147483648", "INTEGER
UNSIGNED NOT NULL");
+ // BIGINT UNSIGNED with the high bit set (2^63, built via 1 << 63): the
+ // right shift must be logical, not arithmetic (the raw long is negative).
+ f.checkScalar("CAST(1 AS BIGINT UNSIGNED) << 63 >> 4",
+ "576460752303423488", "BIGINT UNSIGNED NOT NULL");
+
+ // A BIGINT shift amount is accepted (INTEGER family), but an unsigned
shift
+ // amount is not: the second operand must be a signed integer type.
+ f.checkScalar("CAST(8 AS INTEGER) >> CAST(2 AS BIGINT)", "2", "INTEGER NOT
NULL");
+ f.checkFails("^8 >> CAST(2 AS INTEGER UNSIGNED)^",
+ "Cannot apply '>>' to arguments of type '<INTEGER> >> <INTEGER
UNSIGNED>'\\. "
+ + "Supported form\\(s\\): '<INTEGER> >> <INTEGER>'\\n'<BINARY> >>
<INTEGER>'\\n"
+ + "'<UNSIGNED_NUMERIC> >> <INTEGER>'",
+ false);
+
+ // === Negative shift counts (normalized via modulo, then shifted the
other way) ===
+ // A negative right shift shifts left by the normalized magnitude (here
+ // 32 - 2 = 30); it is not the same as a left shift by the given amount.
+ f.checkScalar("1 >> -2", "1073741824", "INTEGER NOT NULL"); // 1 << 30
+ f.checkScalar("8 >> -1", "0", "INTEGER NOT NULL");
+ f.checkScalar("16 >> -2", "0", "INTEGER NOT NULL");
+
+ // === Shift by zero and large shifts ===
+ f.checkScalar("0 >> 32", "0", "INTEGER NOT NULL");
+ f.checkScalar("0 >> 100", "0", "INTEGER NOT NULL");
+
+ // === Binary type tests ===
+ f.checkScalar("CAST(X'FF' AS BINARY(1)) >> 1", "7f", "BINARY(1) NOT NULL");
+ f.checkScalar("CAST(X'F0' AS BINARY(1)) >> 4", "0f", "BINARY(1) NOT NULL");
+ f.checkScalar("CAST(X'08' AS BINARY(1)) >> 3", "01", "BINARY(1) NOT NULL");
+ f.checkScalar("CAST(X'00' AS BINARY(1)) >> 5", "00", "BINARY(1) NOT NULL");
+
+ f.checkScalar("CAST(X'FFFF' AS BINARY(2)) >> 1", "ff7f", "BINARY(2) NOT
NULL");
+ f.checkScalar("CAST(X'1234' AS BINARY(2)) >> 4", "4103", "BINARY(2) NOT
NULL");
+ f.checkScalar("CAST(X'1234' AS BINARY(2)) >> 8", "3400", "BINARY(2) NOT
NULL");
+
+ f.checkScalar("CAST(X'FF' AS BINARY(1)) >> 8", "ff", "BINARY(1) NOT NULL");
+ f.checkScalar("CAST(X'FFFF' AS BINARY(2)) >> 16", "ffff", "BINARY(2) NOT
NULL");
+
+ f.checkScalar("CAST(X'ABCD' AS BINARY(2)) >> 0", "abcd", "BINARY(2) NOT
NULL");
+ f.checkScalar("CAST(X'123456' AS BINARY(3)) >> 4", "416305", "BINARY(3)
NOT NULL");
+ f.checkScalar("CAST(X'0001' AS BINARY(2)) >> 1", "8000", "BINARY(2) NOT
NULL");
+ f.checkScalar("CAST(X'8000' AS BINARY(2)) >> 1", "4000", "BINARY(2) NOT
NULL");
+ f.checkScalar("CAST(X'F0' AS BINARY(1)) >> -4", "0f", "BINARY(1) NOT
NULL");
+
+ // === Invalid argument types ===
+ f.checkFails("^1.2 >> 2^",
+ "Cannot apply '>>' to arguments of type '<DECIMAL\\(2, 1\\)> >>
<INTEGER>'\\. Supported "
+ + "form\\(s\\): '<INTEGER> >> <INTEGER>'\\n'<BINARY> >>
<INTEGER>'\\n'<UNSIGNED_NUMERIC> "
+ + ">> <INTEGER>'",
+ false);
+
+ // === Null propagation ===
+ f.checkNull("CAST(NULL AS INTEGER) >> 5");
+ f.checkNull("10 >> CAST(NULL AS INTEGER)");
+ f.checkNull("CAST(NULL AS INTEGER) >> CAST(NULL AS INTEGER)");
+ f.checkNull("CAST(NULL AS INTEGER UNSIGNED) >> 2");
+ }
+
+ @Test void testRightShiftFunctionCall() {
+ final SqlOperatorFixture f = fixture();
+ f.setFor(SqlStdOperatorTable.BIT_RIGHT_SHIFT, VmName.EXPAND);
+
+ // === Basic functionality ===
+ f.checkScalar("RIGHTSHIFT(8, 2)", "2", "INTEGER NOT NULL");
+ f.checkScalar("RIGHTSHIFT(1024, 10)", "1", "INTEGER NOT NULL");
+ f.checkScalar("RIGHTSHIFT(0, 5)", "0", "INTEGER NOT NULL");
+
+ // === Type coercion and signed (arithmetic) behavior ===
+ f.checkScalar("RIGHTSHIFT(CAST(16 AS INTEGER), CAST(3 AS BIGINT))", "2",
"INTEGER NOT NULL");
+ f.checkScalar("RIGHTSHIFT(-20, 2)", "-5", "INTEGER NOT NULL");
+ f.checkScalar("RIGHTSHIFT(-40, 3)", "-5", "INTEGER NOT NULL");
+ f.checkScalar("RIGHTSHIFT(CAST(-20 AS TINYINT), CAST(2 AS TINYINT))",
"-5", "TINYINT NOT NULL");
+
+ // === Verify return type matches first argument type ===
+ f.checkType("RIGHTSHIFT(CAST(8 AS TINYINT), CAST(2 AS TINYINT))", "TINYINT
NOT NULL");
+ f.checkType("RIGHTSHIFT(CAST(8 AS SMALLINT), CAST(2 AS SMALLINT))",
"SMALLINT NOT NULL");
+ f.checkType("RIGHTSHIFT(CAST(8 AS INTEGER), CAST(2 AS INTEGER))", "INTEGER
NOT NULL");
+ f.checkType("RIGHTSHIFT(CAST(8 AS BIGINT), CAST(2 AS BIGINT))", "BIGINT
NOT NULL");
+ f.checkScalar("RIGHTSHIFT(CAST(8 AS BIGINT), CAST(2 AS BIGINT))", "2",
"BIGINT NOT NULL");
+
+ // === BigInt shifts with explicit BIGINT inputs ===
+ f.checkScalar("RIGHTSHIFT(CAST(4611686018427387904 AS BIGINT), 62)", "1",
"BIGINT NOT NULL");
+ f.checkScalar("RIGHTSHIFT(CAST(9223372036854775807 AS BIGINT), 1)",
+ BigInteger.valueOf(Long.MAX_VALUE).shiftRight(1).toString(), "BIGINT
NOT NULL");
+ f.checkScalar("RIGHTSHIFT(CAST(-1 AS BIGINT), 63)", "-1", "BIGINT NOT
NULL");
+ f.checkScalar("RIGHTSHIFT(CAST(-1 AS BIGINT), 1)", "-1", "BIGINT NOT
NULL");
+ f.checkScalar("RIGHTSHIFT(CAST(1000000000 AS BIGINT), 5)", "31250000",
"BIGINT NOT NULL");
+
+ // === Shift amount normalized using modulo of the bit width ===
+ f.checkScalar("RIGHTSHIFT(CAST(1024 AS BIGINT), 64)", "1024", "BIGINT NOT
NULL");
+ f.checkScalar("RIGHTSHIFT(CAST(1024 AS BIGINT), 74)", "1", "BIGINT NOT
NULL");
+ f.checkScalar("RIGHTSHIFT(1, 32)", "1", "INTEGER NOT NULL");
+ f.checkScalar("RIGHTSHIFT(123, 60)", "0", "INTEGER NOT NULL");
+
+ // === Unsigned types ===
+ f.checkScalar("RIGHTSHIFT(CAST(252 AS TINYINT UNSIGNED), 2)", "63",
+ "TINYINT UNSIGNED NOT NULL");
+ f.checkScalar("RIGHTSHIFT(CAST(65280 AS SMALLINT UNSIGNED), 8)", "255",
+ "SMALLINT UNSIGNED NOT NULL");
+ f.checkScalar("RIGHTSHIFT(CAST(4294901760 AS INTEGER UNSIGNED), 16)",
"65535",
+ "INTEGER UNSIGNED NOT NULL");
+ f.checkScalar("RIGHTSHIFT(CAST(2147483648 AS INTEGER UNSIGNED), 31)", "1",
+ "INTEGER UNSIGNED NOT NULL");
+ f.checkScalar("RIGHTSHIFT(CAST(1 AS INTEGER UNSIGNED), -1)", "2147483648",
+ "INTEGER UNSIGNED NOT NULL");
+
+ // === Negative shifts ===
+ f.checkScalar("RIGHTSHIFT(8, -1)", "0", "INTEGER NOT NULL");
+ f.checkScalar("RIGHTSHIFT(16, -2)", "0", "INTEGER NOT NULL");
+
+ // === Large shifts ===
+ f.checkScalar("RIGHTSHIFT(0, 32)", "0", "INTEGER NOT NULL");
+ f.checkScalar("RIGHTSHIFT(0, 100)", "0", "INTEGER NOT NULL");
+
+ // === Binary types ===
+ f.checkScalar("RIGHTSHIFT(CAST(X'FF' AS BINARY(1)), 1)", "7f", "BINARY(1)
NOT NULL");
Review Comment:
I think this is somewhat related to
https://issues.apache.org/jira/browse/CALCITE-7368, which however treats BINARY
values as big-endian.
It may be nice to have `CAST(int AS BINARY(N)) << x = CAST((int << x) AS
BINARY(N))`, but maybe this is not possible?
I think shifts should follow the semantics of an existing database which
provides this operation on BINARY values. Since we have already left shifts,
isn't the semantics imposed? We should have asked this question when we
implemented left shift. We should have a design before we have an
implementation.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]