Skip to content

Commit

Permalink
IGNITE-23658 SQL Calcite: Add support for bitwise operations (BITAND,…
Browse files Browse the repository at this point in the history
… BITOR, BITXOR) - Fixes #11655.

Signed-off-by: Aleksey Plekhanov <[email protected]>
  • Loading branch information
Vladsz83 authored and alex-plekhanov committed Nov 20, 2024
1 parent 0255021 commit ec498dd
Show file tree
Hide file tree
Showing 4 changed files with 190 additions and 1 deletion.
2 changes: 1 addition & 1 deletion docs/_docs/SQL/sql-calcite.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ The Calcite-based SQL engine currently supports:
|`UPPER`, `LOWER`, `INITCAP`, `TO_BASE64`, `FROM_BASE64`, `MD5`, `SHA1`, `SUBSTRING`, `LEFT`, `RIGHT`, `REPLACE`, `TRANSLATE`, `CHR`, `CHAR_LENGTH`, `CHARACTER_LENGTH`, `LENGTH`, `CONCAT`, `OVERLAY`, `POSITION`, `ASCII`, `REPEAT`, `SPACE`, `STRCMP`, `SOUNDEX`, `DIFFERENCE`, `REVERSE`, `TRIM`, `LTRIM`, `RTRIM`, `REGEXP_REPLACE`

|Math functions
|`MOD`, `EXP`, `POWER`, `LN`, `LOG10`, `ABS`, `RAND`, `RAND_INTEGER`, `ACOS`, `ACOSH`, `ASIN`, `ASINH`, `ATAN`, `ATANH`, `ATAN2`, `SQRT`, `CBRT`, `COS`, `COSH`, `COT`, `COTH`, `DEGREES`, `RADIANS`, `ROUND`, `SIGN`, `SIN`, `SINH`, `TAN`, `TANH`, `SEC`, `SECH`, `CSC`, `CSCH`, `TRUNCATE`, `PI`
|`MOD`, `EXP`, `POWER`, `LN`, `LOG10`, `ABS`, `RAND`, `RAND_INTEGER`, `ACOS`, `ACOSH`, `ASIN`, `ASINH`, `ATAN`, `ATANH`, `ATAN2`, `SQRT`, `CBRT`, `COS`, `COSH`, `COT`, `COTH`, `DEGREES`, `RADIANS`, `ROUND`, `SIGN`, `SIN`, `SINH`, `TAN`, `TANH`, `SEC`, `SECH`, `CSC`, `CSCH`, `TRUNCATE`, `PI`, `BITAND`, `BITOR`, `BITXOR`

|Date and time functions
|`EXTRACT`, `FLOOR`, `CEIL`, `TIMESTAMPADD`, `TIMESTAMPDIFF`, `LAST_DATE`, `DAYNAME`, `MONTHNAME`, `DAYOFMONTH`, `DAYOFWEEK`, `DAYOFYEAR`, `YEAR`, `QUARTER`, `MONTH`, `WEEK`, `HOUR`, `MINUTE`, `SECOND`, `TIMESTAMP_SECONDS`, `TIMESTAMP_MILLIS`, `TIMESTAMP_MICROS`, `UNIX_SECONDS`, `UNIX_MILLIS`, `UNIX_MICROS`, `UNIX_DATE`, `DATE_FROM_UNIX_DATE`, `DATE`, `TIME`, `DATETIME`, `CURRENT_TIME`, `CURRENT_TIMESTAMP`, `CURRENT_DATE`, `LOCALTIME`, `LOCALTIMESTAMP`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,9 @@
import static org.apache.calcite.sql.fun.SqlStdOperatorTable.UNARY_PLUS;
import static org.apache.calcite.sql.fun.SqlStdOperatorTable.UPPER;
import static org.apache.calcite.util.ReflectUtil.isStatic;
import static org.apache.ignite.internal.processors.query.calcite.sql.fun.IgniteOwnSqlOperatorTable.BITAND;
import static org.apache.ignite.internal.processors.query.calcite.sql.fun.IgniteOwnSqlOperatorTable.BITOR;
import static org.apache.ignite.internal.processors.query.calcite.sql.fun.IgniteOwnSqlOperatorTable.BITXOR;
import static org.apache.ignite.internal.processors.query.calcite.sql.fun.IgniteOwnSqlOperatorTable.GREATEST2;
import static org.apache.ignite.internal.processors.query.calcite.sql.fun.IgniteOwnSqlOperatorTable.LEAST2;
import static org.apache.ignite.internal.processors.query.calcite.sql.fun.IgniteOwnSqlOperatorTable.NULL_BOUND;
Expand Down Expand Up @@ -564,6 +567,10 @@ public class RexImpTable {
// Operator IS_NOT_DISTINCT_FROM is removed by RexSimplify, but still possible in join conditions, so
// implementation required.
defineMethod(IS_NOT_DISTINCT_FROM, IgniteMethod.IS_NOT_DISTINCT_FROM.method(), NullPolicy.NONE);

defineMethod(BITAND, BuiltInMethod.BIT_AND.method, NullPolicy.ANY);
defineMethod(BITOR, BuiltInMethod.BIT_OR.method, NullPolicy.ANY);
defineMethod(BITXOR, BuiltInMethod.BIT_XOR.method, NullPolicy.ANY);
}

/** */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,10 @@
import org.apache.calcite.sql.SqlFunction;
import org.apache.calcite.sql.SqlFunctionCategory;
import org.apache.calcite.sql.SqlKind;
import org.apache.calcite.sql.type.InferTypes;
import org.apache.calcite.sql.type.OperandTypes;
import org.apache.calcite.sql.type.ReturnTypes;
import org.apache.calcite.sql.type.SqlTypeFamily;
import org.apache.calcite.sql.type.SqlTypeName;
import org.apache.calcite.sql.type.SqlTypeTransforms;
import org.apache.calcite.sql.util.ReflectiveSqlOperatorTable;
Expand Down Expand Up @@ -123,6 +125,36 @@ public class IgniteOwnSqlOperatorTable extends ReflectiveSqlOperatorTable {
OperandTypes.SAME_SAME,
SqlFunctionCategory.SYSTEM);

/** Bitwise '&' of two values. */
public static final SqlFunction BITAND =
new SqlFunction(
"BITAND",
SqlKind.OTHER_FUNCTION,
ReturnTypes.LEAST_RESTRICTIVE,
InferTypes.RETURN_TYPE,
OperandTypes.family(SqlTypeFamily.INTEGER, SqlTypeFamily.INTEGER),
SqlFunctionCategory.NUMERIC);

/** Bitwise '|' of two values. */
public static final SqlFunction BITOR =
new SqlFunction(
"BITOR",
SqlKind.OTHER_FUNCTION,
ReturnTypes.LEAST_RESTRICTIVE,
InferTypes.RETURN_TYPE,
OperandTypes.family(SqlTypeFamily.INTEGER, SqlTypeFamily.INTEGER),
SqlFunctionCategory.NUMERIC);

/** Bitwise '^' of two values. */
public static final SqlFunction BITXOR =
new SqlFunction(
"BITXOR",
SqlKind.OTHER_FUNCTION,
ReturnTypes.LEAST_RESTRICTIVE,
InferTypes.RETURN_TYPE,
OperandTypes.family(SqlTypeFamily.INTEGER, SqlTypeFamily.INTEGER),
SqlFunctionCategory.NUMERIC);

/**
* Returns the Ignite operator table, creating it if necessary.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,15 @@
import java.sql.Date;
import java.sql.Time;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.List;
import java.util.function.LongFunction;
import org.apache.calcite.sql.validate.SqlValidatorException;
import org.apache.ignite.IgniteCache;
import org.apache.ignite.calcite.CalciteQueryEngineConfiguration;
import org.apache.ignite.configuration.CacheConfiguration;
import org.apache.ignite.internal.processors.query.IgniteSQLException;
import org.apache.ignite.internal.util.typedef.F;
import org.apache.ignite.internal.util.typedef.internal.U;
import org.junit.Test;

Expand Down Expand Up @@ -58,6 +60,154 @@ public void testQueryEngine() {
assertQuery("SELECT QUERY_ENGINE()").returns(CalciteQueryEngineConfiguration.ENGINE_NAME).check();
}

/** */
@Test
public void testBitwiseOperationsWithTable() {
try {
sql("CREATE TABLE TBL(i INT PRIMARY KEY, s SMALLINT, l BIGINT)");

for (int i = 0; i < 100; ++i)
sql("INSERT INTO TBL values (?, ?, ?)", i, i, i);

sql("INSERT INTO TBL values (?, ?, ?)", Short.MAX_VALUE + 1, Short.MAX_VALUE, Short.MAX_VALUE);

assertQuery("SELECT BITAND(i + 1, i + 1) FROM TBL WHERE i=0").returns(1).check();
assertQuery("SELECT BITAND(s, s) FROM TBL WHERE i=1").returns((short)1).check();
assertQuery("SELECT BITAND((SELECT l FROM TBL WHERE i=3), (SELECT l FROM TBL WHERE i=1))").returns(1L).check();
assertQuery("SELECT BITOR((SELECT s FROM TBL WHERE i=?), (SELECT i FROM TBL WHERE i=?))").withParams(14, 1)
.returns(15).check();

assertQuery("SELECT BITAND((SELECT s FROM TBL WHERE s=3), (SELECT l FROM TBL WHERE i=1))").returns(1L).check();

assertQuery("SELECT BITXOR(1000::BIGINT, i) FROM TBL WHERE i=93").returns(949L).check();
assertQuery("SELECT BITAND(?, i) FROM TBL WHERE i=73").withParams(NULL_RESULT).returns(NULL_RESULT).check();
assertQuery("SELECT BITAND(l, ?) FROM TBL WHERE l=45").withParams(NULL_RESULT).returns(NULL_RESULT).check();
assertQuery("SELECT BITAND(?, s) FROM TBL WHERE s=40").withParams(NULL_RESULT).returns(NULL_RESULT).check();

assertQuery("SELECT BITAND(i, s) FROM TBL WHERE l=?").withParams(Short.MAX_VALUE).returns(0).check();
assertQuery("SELECT BITOR(i, s) FROM TBL WHERE l=?").withParams(Short.MAX_VALUE).returns(65535).check();
assertQuery("SELECT BITXOR(i, s) FROM TBL WHERE l=?").withParams(Short.MAX_VALUE).returns(65535).check();
}
finally {
sql("DROP TABLE if EXISTS TBL");
}
}

/** */
@Test
public void testBitwiseOperations() {
doTestBitwiseOperations(false);

doTestBitwiseOperations(true);
}

/** */
private void doTestBitwiseOperations(boolean dynamic) {
for (List<Object> paramSet : bitwiseParams(dynamic)) {
assert paramSet.size() == 6;

int idx = 0;

String op = paramSet.get(idx++).toString();
Object p1 = paramSet.get(idx++);
String cast1 = (String)paramSet.get(idx++);
Object p2 = paramSet.get(idx++);
String cast2 = (String)paramSet.get(idx++);
Object res = paramSet.get(idx);

cast1 = cast1 == null ? "" : "::" + cast1;
cast2 = cast2 == null ? "" : "::" + cast2;

log.info("Op: " + op + ", dynamic=" + dynamic + ", p1=" + p1 + ", p2=" + p2 + ", expected=" + res);

if (dynamic) {
String sql = "SELECT BIT" + op + "(?" + cast1 + ", ?" + cast2 + ')';

if (res instanceof Exception)
assertThrows(sql, (Class<? extends Exception>)res.getClass(), ((Throwable)res).getMessage(), p1, p2);
else
assertQuery(sql).withParams(p1, p2).returns(res).check();
}
else {
String sql = "SELECT BIT" + op + '(' + p1 + cast1 + ", " + p2 + cast2 + ')';

if (res instanceof Exception)
assertThrows(sql, (Class<? extends Exception>)res.getClass(), ((Throwable)res).getMessage());
else
assertQuery(sql).returns(res).check();
}
}
}

/** Bitwise operation params: operation, param1, cast1, param2, cast2, result. */
private Iterable<List<Object>> bitwiseParams(boolean dynamic) {
List<List<Object>> res = new ArrayList<>(100);

SqlValidatorException andErr = new SqlValidatorException("Cannot apply 'BITAND' to arguments of type", null);
SqlValidatorException orErr = new SqlValidatorException("Cannot apply 'BITOR' to arguments of type", null);
SqlValidatorException xorErr = new SqlValidatorException("Cannot apply 'BITXOR' to arguments of type", null);

// BITAND
res.add(F.asList("AND", 1, null, 1.0, null, andErr));
res.add(F.asList("AND", 1.0, null, 1, null, andErr));
res.add(F.asList("AND", 1, null, 1.0f, null, andErr));
res.add(F.asList("AND", 1.0f, null, 1, null, andErr));
res.add(F.asList("AND", null, null, null, null, null));
res.add(F.asList("AND", 1, null, 1, null, 1));
res.add(F.asList("AND", 1, null, 0, null, 0));
res.add(F.asList("AND", 0, null, 1, null, 0));
res.add(F.asList("AND", 1, null, 1, "BIGINT", 1L));
res.add(F.asList("AND", 1, "TINYINT", 1, "INT", 1));
res.add(F.asList("AND", 1, "TINYINT", 1, "SMALLINT", (short)1));
res.add(F.asList("AND", 0, "TINYINT", 0, "TINYINT", (byte)0));
res.add(F.asList("AND", 1, "TINYINT", 1, "SMALLINT", (short)1));
res.add(F.asList("AND", 15, null, 7, null, 7));
res.add(F.asList("AND", -1, null, 1, null, 1));
res.add(F.asList("AND", (short)32767, null, 65535, null, 32767));
res.add(F.asList("AND", null, null, 1, null, null));
res.add(F.asList("AND", 1, "SMALLINT", null, null, null));
// BITOR
res.add(F.asList("OR", 1, null, 1.0, null, orErr));
res.add(F.asList("OR", 1.0, null, 1, null, orErr));
res.add(F.asList("OR", 1, null, 1.0f, null, orErr));
res.add(F.asList("OR", 1.0f, null, 1, null, orErr));
res.add(F.asList("OR", 1, null, 1, null, 1));
res.add(F.asList("OR", 1, null, 0, null, 1));
res.add(F.asList("OR", 0, null, 1, null, 1));
res.add(F.asList("OR", 1, null, 1, "BIGINT", 1L));
res.add(F.asList("OR", 1, "TINYINT", 1, "INT", 1));
res.add(F.asList("OR", 1, "TINYINT", 1, "SMALLINT", (short)1));
res.add(F.asList("OR", 0, "TINYINT", 0, "TINYINT", (byte)0));
res.add(F.asList("OR", 1, "TINYINT", 1, "SMALLINT", (short)1));
res.add(F.asList("OR", 8, null, 7, null, 15));
res.add(F.asList("OR", -1, null, 1, null, -1));
res.add(F.asList("OR", (short)32767, null, 65535, null, 65535));
res.add(F.asList("OR", (short)32767, null, 65536, null, 98303));
res.add(F.asList("OR", null, null, 1, null, null));
res.add(F.asList("OR", 1, null, null, null, null));
// BITXOR
res.add(F.asList("XOR", 1, null, 1.0, null, xorErr));
res.add(F.asList("XOR", 1.0, null, 1, null, xorErr));
res.add(F.asList("XOR", 1, null, 1.0f, null, xorErr));
res.add(F.asList("XOR", 1.0f, null, 1, null, xorErr));
res.add(F.asList("XOR", 1, null, 1, null, 0));
res.add(F.asList("XOR", 1, null, 0, null, 1));
res.add(F.asList("XOR", 0, null, 1, null, 1));
res.add(F.asList("XOR", 1, null, 1, "BIGINT", 0L));
res.add(F.asList("XOR", 1, "TINYINT", 1, "INT", 0));
res.add(F.asList("XOR", 1, "TINYINT", 1, "SMALLINT", (short)0));
res.add(F.asList("XOR", 0, "TINYINT", 0, "TINYINT", (byte)0));
res.add(F.asList("XOR", 1, "TINYINT", 1, "SMALLINT", (short)0));
res.add(F.asList("XOR", 8, null, 7, null, 15));
res.add(F.asList("XOR", -1, null, 1, null, -2));
res.add(F.asList("XOR", (short)32767, null, 65535, null, 32768));
res.add(F.asList("XOR", (short)32767, null, 65536, null, 98303));
res.add(F.asList("XOR", null, null, 1, "TINYINT", null));
res.add(F.asList("XOR", 1, null, null, null, null));

return res;
}

/** */
@Test
public void testCurrentDateTimeTimeStamp() {
Expand Down

0 comments on commit ec498dd

Please sign in to comment.