This is an automated email from the ASF dual-hosted git repository.

tanner pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/calcite.git


The following commit(s) were added to refs/heads/main by this push:
     new 0a0fd871fa [CALCITE-6205] Add BITAND_AGG, BITOR_AGG functions (enabled 
in Snowflake library)
0a0fd871fa is described below

commit 0a0fd871fa9b734f5730f05a1725e7bb560d3799
Author: Tanner Clary <[email protected]>
AuthorDate: Tue Jan 16 10:49:03 2024 -0800

    [CALCITE-6205] Add BITAND_AGG, BITOR_AGG functions (enabled in Snowflake 
library)
---
 .../calcite/adapter/enumerable/RexImpTable.java    |   6 +-
 .../calcite/sql/dialect/SnowflakeSqlDialect.java   |  10 ++
 .../calcite/sql/fun/SqlBitOpAggFunction.java       |  19 ++-
 .../calcite/sql/fun/SqlLibraryOperators.java       |  12 ++
 .../calcite/sql2rel/StandardConvertletTable.java   |   2 +
 .../calcite/rel/rel2sql/RelToSqlConverterTest.java |  22 +++
 site/_docs/reference.md                            |   2 +
 .../org/apache/calcite/test/SqlOperatorTest.java   | 152 ++++++++++++---------
 8 files changed, 162 insertions(+), 63 deletions(-)

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 c1288c730d..71c65fb599 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
@@ -143,6 +143,8 @@ import static 
org.apache.calcite.sql.fun.SqlLibraryOperators.ARRAY_TO_STRING;
 import static org.apache.calcite.sql.fun.SqlLibraryOperators.ARRAY_UNION;
 import static org.apache.calcite.sql.fun.SqlLibraryOperators.ASINH;
 import static org.apache.calcite.sql.fun.SqlLibraryOperators.ATANH;
+import static org.apache.calcite.sql.fun.SqlLibraryOperators.BITAND_AGG;
+import static org.apache.calcite.sql.fun.SqlLibraryOperators.BITOR_AGG;
 import static org.apache.calcite.sql.fun.SqlLibraryOperators.BIT_GET;
 import static org.apache.calcite.sql.fun.SqlLibraryOperators.BIT_LENGTH;
 import static org.apache.calcite.sql.fun.SqlLibraryOperators.BOOL_AND;
@@ -1041,6 +1043,8 @@ public class RexImpTable {
       aggMap.put(LOGICAL_OR, minMax);
       final Supplier<BitOpImplementor> bitop =
           constructorSupplier(BitOpImplementor.class);
+      aggMap.put(BITAND_AGG, bitop);
+      aggMap.put(BITOR_AGG, bitop);
       aggMap.put(BIT_AND, bitop);
       aggMap.put(BIT_OR, bitop);
       aggMap.put(BIT_XOR, bitop);
@@ -1793,7 +1797,7 @@ public class RexImpTable {
       if (SqlTypeUtil.isBinary(info.returnRelType())) {
         start = Expressions.field(null, ByteString.class, "EMPTY");
       } else {
-        Object initValue = info.aggregation() == BIT_AND ? -1L : 0;
+        Object initValue = info.aggregation().kind == SqlKind.BIT_AND ? -1L : 
0;
         start = Expressions.constant(initValue, info.returnType());
       }
 
diff --git 
a/core/src/main/java/org/apache/calcite/sql/dialect/SnowflakeSqlDialect.java 
b/core/src/main/java/org/apache/calcite/sql/dialect/SnowflakeSqlDialect.java
index f7d8741491..9b38821202 100644
--- a/core/src/main/java/org/apache/calcite/sql/dialect/SnowflakeSqlDialect.java
+++ b/core/src/main/java/org/apache/calcite/sql/dialect/SnowflakeSqlDialect.java
@@ -43,6 +43,16 @@ public class SnowflakeSqlDialect extends SqlDialect {
   @Override public void unparseCall(final SqlWriter writer, final SqlCall 
call, final int leftPrec,
       final int rightPrec) {
     switch (call.getKind()) {
+    case BIT_AND:
+      SqlCall bitAndCall = SqlLibraryOperators.BITAND_AGG
+          .createCall(SqlParserPos.ZERO, call.getOperandList());
+      super.unparseCall(writer, bitAndCall, leftPrec, rightPrec);
+      break;
+    case BIT_OR:
+      SqlCall bitOrCall = SqlLibraryOperators.BITOR_AGG
+          .createCall(SqlParserPos.ZERO, call.getOperandList());
+      super.unparseCall(writer, bitOrCall, leftPrec, rightPrec);
+      break;
     case CHAR_LENGTH:
       SqlCall lengthCall = SqlLibraryOperators.LENGTH
           .createCall(SqlParserPos.ZERO, call.getOperandList());
diff --git 
a/core/src/main/java/org/apache/calcite/sql/fun/SqlBitOpAggFunction.java 
b/core/src/main/java/org/apache/calcite/sql/fun/SqlBitOpAggFunction.java
index 17eacae87f..808724d02d 100644
--- a/core/src/main/java/org/apache/calcite/sql/fun/SqlBitOpAggFunction.java
+++ b/core/src/main/java/org/apache/calcite/sql/fun/SqlBitOpAggFunction.java
@@ -39,7 +39,7 @@ public class SqlBitOpAggFunction extends SqlAggFunction {
 
   //~ Constructors -----------------------------------------------------------
 
-  /** Creates a SqlBitOpAggFunction. */
+  /** Creates a SqlBitOpAggFunction from a SqlKind. */
   public SqlBitOpAggFunction(SqlKind kind) {
     super(kind.name(),
         null,
@@ -56,6 +56,23 @@ public class SqlBitOpAggFunction extends SqlAggFunction {
         || kind == SqlKind.BIT_XOR);
   }
 
+  /** Creates a SqlBitOpAggFunction from a name and SqlKind. */
+  public SqlBitOpAggFunction(String name, SqlKind kind) {
+    super(name,
+        null,
+        kind,
+        ReturnTypes.ARG0_NULLABLE_IF_EMPTY,
+        null,
+        OperandTypes.INTEGER.or(OperandTypes.BINARY),
+        SqlFunctionCategory.NUMERIC,
+        false,
+        false,
+        Optionality.FORBIDDEN);
+    Preconditions.checkArgument(kind == SqlKind.BIT_AND
+        || kind == SqlKind.BIT_OR
+        || kind == SqlKind.BIT_XOR);
+  }
+
   @Override public <T extends Object> @Nullable T unwrap(Class<T> clazz) {
     if (clazz.isInstance(SqlSplittableAggFunction.SelfSplitter.INSTANCE)) {
       return clazz.cast(SqlSplittableAggFunction.SelfSplitter.INSTANCE);
diff --git 
a/core/src/main/java/org/apache/calcite/sql/fun/SqlLibraryOperators.java 
b/core/src/main/java/org/apache/calcite/sql/fun/SqlLibraryOperators.java
index d4c8b9d1ea..265084c4f6 100644
--- a/core/src/main/java/org/apache/calcite/sql/fun/SqlLibraryOperators.java
+++ b/core/src/main/java/org/apache/calcite/sql/fun/SqlLibraryOperators.java
@@ -2203,6 +2203,18 @@ public abstract class SqlLibraryOperators {
           InferTypes.FIRST_KNOWN,
           OperandTypes.COMPARABLE_UNORDERED_COMPARABLE_UNORDERED);
 
+  /** The "BITAND_AGG(expression)" function. Equivalent to
+  * the standard "BIT_AND(expression)". */
+  @LibraryOperator(libraries = {SNOWFLAKE})
+  public static final SqlAggFunction BITAND_AGG =
+      new SqlBitOpAggFunction("BITAND_AGG", SqlKind.BIT_AND);
+
+  /** The "BITOR_AGG(expression)" function. Equivalent to
+  * the standard "BIT_OR(expression)". */
+  @LibraryOperator(libraries = {SNOWFLAKE})
+  public static final SqlAggFunction BITOR_AGG =
+      new SqlBitOpAggFunction("BITOR_AGG", SqlKind.BIT_OR);
+
   /** The "BIT_LENGTH(string or binary)" function. */
   @LibraryOperator(libraries = {SPARK})
   public static final SqlFunction BIT_LENGTH =
diff --git 
a/core/src/main/java/org/apache/calcite/sql2rel/StandardConvertletTable.java 
b/core/src/main/java/org/apache/calcite/sql2rel/StandardConvertletTable.java
index d9bc1d27d5..ccd946c437 100644
--- a/core/src/main/java/org/apache/calcite/sql2rel/StandardConvertletTable.java
+++ b/core/src/main/java/org/apache/calcite/sql2rel/StandardConvertletTable.java
@@ -137,6 +137,8 @@ public class StandardConvertletTable extends 
ReflectiveConvertletTable {
     addAlias(SqlLibraryOperators.REGEXP_SUBSTR, 
SqlLibraryOperators.REGEXP_EXTRACT);
     addAlias(SqlLibraryOperators.ENDSWITH, SqlLibraryOperators.ENDS_WITH);
     addAlias(SqlLibraryOperators.STARTSWITH, SqlLibraryOperators.STARTS_WITH);
+    addAlias(SqlLibraryOperators.BITAND_AGG, SqlStdOperatorTable.BIT_AND);
+    addAlias(SqlLibraryOperators.BITOR_AGG, SqlStdOperatorTable.BIT_OR);
 
     // Register convertlets for specific objects.
     registerOp(SqlStdOperatorTable.CAST, this::convertCast);
diff --git 
a/core/src/test/java/org/apache/calcite/rel/rel2sql/RelToSqlConverterTest.java 
b/core/src/test/java/org/apache/calcite/rel/rel2sql/RelToSqlConverterTest.java
index 7a5bc0c499..258aca0efa 100644
--- 
a/core/src/test/java/org/apache/calcite/rel/rel2sql/RelToSqlConverterTest.java
+++ 
b/core/src/test/java/org/apache/calcite/rel/rel2sql/RelToSqlConverterTest.java
@@ -6662,6 +6662,28 @@ class RelToSqlConverterTest {
     sql(sql).withMysql().ok(expectedMysql);
   }
 
+  /** Test case for
+   * <a 
href="https://issues.apache.org/jira/browse/CALCITE-6205";>[CALCITE-6205]
+   * Add BITAND_AGG, BITOR_AGG functions (enabled in Snowflake library)</a>. */
+  @Test void testBitAndAgg() {
+    final String query = "select bit_and(\"product_id\")\n"
+        + "from \"product\"";
+    final String expectedSnowflake = "SELECT BITAND_AGG(\"product_id\")\n"
+        + "FROM \"foodmart\".\"product\"";
+    
sql(query).withLibrary(SqlLibrary.SNOWFLAKE).withSnowflake().ok(expectedSnowflake);
+  }
+
+  /** Test case for
+   * <a 
href="https://issues.apache.org/jira/browse/CALCITE-6205";>[CALCITE-6205]
+   * Add BITAND_AGG, BITOR_AGG functions (enabled in Snowflake library)</a>. */
+  @Test void testBitOrAgg() {
+    final String query = "select bit_or(\"product_id\")\n"
+        + "from \"product\"";
+    final String expectedSnowflake = "SELECT BITOR_AGG(\"product_id\")\n"
+        + "FROM \"foodmart\".\"product\"";
+    
sql(query).withLibrary(SqlLibrary.SNOWFLAKE).withSnowflake().ok(expectedSnowflake);
+  }
+
   /** Test case for
    * <a 
href="https://issues.apache.org/jira/browse/CALCITE-6156";>[CALCITE-6156]
    * Add ENDSWITH, STARTSWITH functions (enabled in Postgres, Snowflake 
libraries)</a>. */
diff --git a/site/_docs/reference.md b/site/_docs/reference.md
index 3f97448543..1ba228d312 100644
--- a/site/_docs/reference.md
+++ b/site/_docs/reference.md
@@ -2685,6 +2685,8 @@ In the following:
 | s | SORT_ARRAY(array [, ascendingOrder])           | Sorts the *array* in 
ascending or descending order according to the natural ordering of the array 
elements. The default order is ascending if *ascendingOrder* is not specified. 
Null elements will be placed at the beginning of the returned array in 
ascending order or at the end of the returned array in descending order
 | * | ASINH(numeric)                                 | Returns the inverse 
hyperbolic sine of *numeric*
 | * | ATANH(numeric)                                 | Returns the inverse 
hyperbolic tangent of *numeric*
+| f | BITAND_AGG(value)                              | Equivalent to 
`BIT_AND(value)`
+| f | BITOR_AGG(value)                               | Equivalent to 
`BIT_OR(value)`
 | s | BIT_LENGTH(binary)                             | Returns the bit length 
of *binary*
 | s | BIT_LENGTH(string)                             | Returns the bit length 
of *string*
 | s | BIT_GET(value, position)                       | Returns the bit (0 or 
1) value at the specified *position* of numeric *value*. The positions are 
numbered from right to left, starting at zero. The *position* argument cannot 
be negative
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 dabb16e752..24bf994591 100644
--- a/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java
+++ b/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java
@@ -13479,76 +13479,106 @@ public class SqlOperatorTest {
     f.checkAgg("logical_or(x)", values4, isNullValue());
   }
 
+  @Test void testBitAndAggFunc() {
+    final SqlOperatorFixture f = fixture();
+    f.setFor(SqlLibraryOperators.BITAND_AGG, VmName.EXPAND);
+    checkBitAnd(f, FunctionAlias.of(SqlLibraryOperators.BITAND_AGG));
+  }
+
   @Test void testBitAndFunc() {
     final SqlOperatorFixture f = fixture();
-    f.setFor(SqlStdOperatorTable.BIT_AND, VM_FENNEL, VM_JAVA);
-    f.checkFails("bit_and(^*^)", "Unknown identifier '\\*'", false);
-    f.checkType("bit_and(1)", "INTEGER");
-    f.checkType("bit_and(CAST(2 AS TINYINT))", "TINYINT");
-    f.checkType("bit_and(CAST(2 AS SMALLINT))", "SMALLINT");
-    f.checkType("bit_and(distinct CAST(2 AS BIGINT))", "BIGINT");
-    f.checkType("bit_and(CAST(x'02' AS BINARY(1)))", "BINARY(1)");
-    f.checkFails("^bit_and(1.2)^",
-        "Cannot apply 'BIT_AND' to arguments of type 'BIT_AND\\(<DECIMAL\\(2, 
1\\)>\\)'\\. Supported form\\(s\\): 'BIT_AND\\(<INTEGER>\\)'\n"
-            + "'BIT_AND\\(<BINARY>\\)'",
-        false);
-    f.checkFails("^bit_and()^",
-        "Invalid number of arguments to function 'BIT_AND'. Was expecting 1 
arguments",
-        false);
-    f.checkFails("^bit_and(1, 2)^",
-        "Invalid number of arguments to function 'BIT_AND'. Was expecting 1 
arguments",
-        false);
-    final String[] values = {"3", "2", "2"};
-    f.checkAgg("bit_and(x)", values, isSingle("2"));
-    final String[] binaryValues = {
-        "CAST(x'03' AS BINARY)",
-        "cast(x'02' as BINARY)",
-        "cast(x'02' AS BINARY)",
-        "cast(null AS BINARY)"};
-    f.checkAgg("bit_and(x)", binaryValues, isSingle("02"));
-    f.checkAgg("bit_and(x)", new String[]{"CAST(x'02' AS BINARY)"}, 
isSingle("02"));
+    f.setFor(SqlStdOperatorTable.BIT_AND, VmName.EXPAND);
+    checkBitAnd(f, FunctionAlias.of(SqlStdOperatorTable.BIT_AND));
+  }
+
+  /** Tests the {@code BIT_AND} and {@code BITAND_AGG} operators. */
+  void checkBitAnd(SqlOperatorFixture f0, FunctionAlias functionAlias) {
+    final SqlFunction function = functionAlias.function;
+    final String fn = function.getName();
+    final Consumer<SqlOperatorFixture> consumer = f -> {
+      f.checkFails(fn + "(^*^)", "Unknown identifier '\\*'", false);
+      f.checkType(fn + "(1)", "INTEGER");
+      f.checkType(fn + "(CAST(2 AS TINYINT))", "TINYINT");
+      f.checkType(fn + "(CAST(2 AS SMALLINT))", "SMALLINT");
+      f.checkType(fn + "(distinct CAST(2 AS BIGINT))", "BIGINT");
+      f.checkType(fn + "(CAST(x'02' AS BINARY(1)))", "BINARY(1)");
+      f.checkFails("^" + fn + "(1.2)^",
+          "Cannot apply '" + fn + "' to arguments of type '"
+          + fn + "\\(<DECIMAL\\(2, 1\\)>\\)'\\. Supported form\\(s\\): '"
+          + fn + "\\(<INTEGER>\\)'\n"
+          + "'" + fn + "\\(<BINARY>\\)'",
+          false);
+      f.checkFails("^" + fn + "()^",
+          "Invalid number of arguments to function '" + fn + "'. Was expecting 
1 arguments",
+          false);
+      f.checkFails("^" + fn + "(1, 2)^",
+          "Invalid number of arguments to function '" + fn + "'. Was expecting 
1 arguments",
+          false);
+      final String[] values = {"3", "2", "2"};
+      f.checkAgg(fn + "(x)", values, isSingle("2"));
+      final String[] binaryValues = {
+          "CAST(x'03' AS BINARY)",
+          "cast(x'02' as BINARY)",
+          "cast(x'02' AS BINARY)",
+          "cast(null AS BINARY)"};
+      f.checkAgg(fn + "(x)", binaryValues, isSingle("02"));
+      f.checkAgg(fn + "(x)", new String[]{"CAST(x'02' AS BINARY)"}, 
isSingle("02"));
+      f.checkAggFails(fn + "(x)",
+          new String[]{"CAST(x'0201' AS VARBINARY)", "CAST(x'02' AS 
VARBINARY)"},
+          "Error while executing SQL .*"
+              + " Different length for bitwise operands: the first: 2, the 
second: 1",
+          true);
+    };
+    f0.forEachLibrary(list(functionAlias.libraries), consumer);
   }
 
-  @Test void testBitAndFuncRuntimeFails() {
+  @Test void testBitOrAggFunc() {
     final SqlOperatorFixture f = fixture();
-    f.checkAggFails("bit_and(x)",
-        new String[]{"CAST(x'0201' AS VARBINARY)", "CAST(x'02' AS VARBINARY)"},
-        "Error while executing SQL .*"
-            + " Different length for bitwise operands: the first: 2, the 
second: 1",
-        true);
+    f.setFor(SqlLibraryOperators.BITOR_AGG, VmName.EXPAND);
+    checkBitOr(f, FunctionAlias.of(SqlLibraryOperators.BITOR_AGG));
   }
 
   @Test void testBitOrFunc() {
     final SqlOperatorFixture f = fixture();
-    f.setFor(SqlStdOperatorTable.BIT_OR, VM_FENNEL, VM_JAVA);
-    f.checkFails("bit_or(^*^)", "Unknown identifier '\\*'", false);
-    f.checkType("bit_or(1)", "INTEGER");
-    f.checkType("bit_or(CAST(2 AS TINYINT))", "TINYINT");
-    f.checkType("bit_or(CAST(2 AS SMALLINT))", "SMALLINT");
-    f.checkType("bit_or(distinct CAST(2 AS BIGINT))", "BIGINT");
-    f.checkType("bit_or(CAST(x'02' AS BINARY(1)))", "BINARY(1)");
-    f.checkFails("^bit_or(1.2)^",
-        "Cannot apply 'BIT_OR' to arguments of type "
-            + "'BIT_OR\\(<DECIMAL\\(2, 1\\)>\\)'\\. Supported form\\(s\\): "
-            + "'BIT_OR\\(<INTEGER>\\)'\n"
-            + "'BIT_OR\\(<BINARY>\\)'",
-        false);
-    f.checkFails("^bit_or()^",
-        "Invalid number of arguments to function 'BIT_OR'. Was expecting 1 
arguments",
-        false);
-    f.checkFails("^bit_or(1, 2)^",
-        "Invalid number of arguments to function 'BIT_OR'. Was expecting 1 
arguments",
-        false);
-    final String[] values = {"1", "2", "2"};
-    f.checkAgg("bit_or(x)", values, isSingle(3));
-    final String[] binaryValues = {
-        "CAST(x'01' AS BINARY)",
-        "cast(x'02' as BINARY)",
-        "cast(x'02' AS BINARY)",
-        "cast(null AS BINARY)"};
-    f.checkAgg("bit_or(x)", binaryValues, isSingle("03"));
-    f.checkAgg("bit_or(x)", new String[]{"CAST(x'02' AS BINARY)"},
-        isSingle("02"));
+    f.setFor(SqlStdOperatorTable.BIT_OR, VmName.EXPAND);
+    checkBitOr(f, FunctionAlias.of(SqlStdOperatorTable.BIT_OR));
+  }
+
+  /** Tests the {@code BIT_OR} and {@code BITOR_AGG} operators. */
+  void checkBitOr(SqlOperatorFixture f0, FunctionAlias functionAlias) {
+    final SqlFunction function = functionAlias.function;
+    final String fn = function.getName();
+    final Consumer<SqlOperatorFixture> consumer = f -> {
+      f.checkFails(fn + "(^*^)", "Unknown identifier '\\*'", false);
+      f.checkType(fn + "(1)", "INTEGER");
+      f.checkType(fn + "(CAST(2 AS TINYINT))", "TINYINT");
+      f.checkType(fn + "(CAST(2 AS SMALLINT))", "SMALLINT");
+      f.checkType(fn + "(distinct CAST(2 AS BIGINT))", "BIGINT");
+      f.checkType(fn + "(CAST(x'02' AS BINARY(1)))", "BINARY(1)");
+      f.checkFails("^" + fn + "(1.2)^",
+          "Cannot apply '" + fn + "' to arguments of type "
+              + "'" + fn + "\\(<DECIMAL\\(2, 1\\)>\\)'\\. Supported 
form\\(s\\): "
+              + "'" + fn + "\\(<INTEGER>\\)'\n"
+              + "'" + fn + "\\(<BINARY>\\)'",
+          false);
+      f.checkFails("^" + fn + "()^",
+          "Invalid number of arguments to function '" + fn + "'. Was expecting 
1 arguments",
+          false);
+      f.checkFails("^" + fn + "(1, 2)^",
+          "Invalid number of arguments to function '" + fn + "'. Was expecting 
1 arguments",
+          false);
+      final String[] values = {"1", "2", "2"};
+      f.checkAgg("bit_or(x)", values, isSingle(3));
+      final String[] binaryValues = {
+          "CAST(x'01' AS BINARY)",
+          "cast(x'02' as BINARY)",
+          "cast(x'02' AS BINARY)",
+          "cast(null AS BINARY)"};
+      f.checkAgg(fn + "(x)", binaryValues, isSingle("03"));
+      f.checkAgg(fn + "(x)", new String[]{"CAST(x'02' AS BINARY)"},
+          isSingle("02"));
+    };
+    f0.forEachLibrary(list(functionAlias.libraries), consumer);
   }
 
   @Test void testBitXorFunc() {

Reply via email to