From a7edd0c6e95bf7be8d262d5cc669cd13a5877551 Mon Sep 17 00:00:00 2001 From: Terran Date: Sat, 9 May 2026 14:46:42 +0800 Subject: [PATCH 1/3] Support array operators for PostgreSQL --- babel/src/main/codegen/config.fmpp | 6 +++ .../src/main/codegen/includes/parserImpls.ftl | 39 +++++++++++++++++++ .../org/apache/calcite/test/BabelTest.java | 29 ++++++++++++++ .../adapter/enumerable/RexImpTable.java | 7 ++++ .../apache/calcite/runtime/SqlFunctions.java | 30 ++++++++++++++ .../calcite/sql/fun/SqlLibraryOperators.java | 18 +++++++++ .../apache/calcite/util/BuiltInMethod.java | 3 ++ 7 files changed, 132 insertions(+) diff --git a/babel/src/main/codegen/config.fmpp b/babel/src/main/codegen/config.fmpp index 001bdf2e1034..2d25c13e84da 100644 --- a/babel/src/main/codegen/config.fmpp +++ b/babel/src/main/codegen/config.fmpp @@ -582,6 +582,9 @@ data: { binaryOperatorsTokens: [ "< INFIX_CAST: \"::\" >" "< NULL_SAFE_EQUAL: \"<=>\" >" + "< ARRAY_CONTAINS_OP: \"@>\" >" + "< ARRAY_CONTAINED_BY_OP: \"<@\" >" + "< ARRAY_OVERLAP_OP: \"&&\" >" ] # Custom identifier token. @@ -601,6 +604,9 @@ data: { extraBinaryExpressions: [ "InfixCast" "NullSafeEqual" + "ArrayContainsOp" + "ArrayContainedByOp" + "ArrayOverlapOp" ] # List of files in @includes directory that have parser method diff --git a/babel/src/main/codegen/includes/parserImpls.ftl b/babel/src/main/codegen/includes/parserImpls.ftl index 7565303fa008..fdb6f28c03a7 100644 --- a/babel/src/main/codegen/includes/parserImpls.ftl +++ b/babel/src/main/codegen/includes/parserImpls.ftl @@ -221,3 +221,42 @@ void NullSafeEqual(List list, ExprContext exprContext, Span s) : } AddExpression2b(list, ExprContext.ACCEPT_SUB_QUERY) } + +/** Parses the array contains operator. */ +void ArrayContainsOp(List list, ExprContext exprContext, Span s) : +{ +} +{ + { + checkNonQueryExpression(exprContext); + list.add(new SqlParserUtil.ToTreeListItem( + SqlLibraryOperators.ARRAY_CONTAINS_OP, getPos())); + } + AddExpression2b(list, ExprContext.ACCEPT_SUB_QUERY) +} + +/** Parses the array contained-by operator. */ +void ArrayContainedByOp(List list, ExprContext exprContext, Span s) : +{ +} +{ + { + checkNonQueryExpression(exprContext); + list.add(new SqlParserUtil.ToTreeListItem( + SqlLibraryOperators.ARRAY_CONTAINED_BY_OP, getPos())); + } + AddExpression2b(list, ExprContext.ACCEPT_SUB_QUERY) +} + +/** Parses the array "&&" (overlap) operator. */ +void ArrayOverlapOp(List list, ExprContext exprContext, Span s) : +{ +} +{ + { + checkNonQueryExpression(exprContext); + list.add(new SqlParserUtil.ToTreeListItem( + SqlLibraryOperators.ARRAY_OVERLAP_OP, getPos())); + } + AddExpression2b(list, ExprContext.ACCEPT_SUB_QUERY) +} diff --git a/babel/src/test/java/org/apache/calcite/test/BabelTest.java b/babel/src/test/java/org/apache/calcite/test/BabelTest.java index aff1aee6604b..d19d481202ae 100644 --- a/babel/src/test/java/org/apache/calcite/test/BabelTest.java +++ b/babel/src/test/java/org/apache/calcite/test/BabelTest.java @@ -564,4 +564,33 @@ private void checkSqlResult(String funLibrary, String query, String result) { .query("SELECT AGE(timestamp '2023-12-25') FROM (VALUES (1)) t") .runs(); } + @Test void testArrayContainsOp() { + // @> operator: array contains + checkSqlResult("standard,postgresql", + "SELECT ARRAY[1,2,3] @> ARRAY[1,2]", + "EXPR$0=true\n"); + checkSqlResult("standard,postgresql", + "SELECT ARRAY[1,2,3] @> ARRAY[4]", + "EXPR$0=false\n"); + } + + @Test void testArrayContainedByOp() { + // <@ operator: array is contained by + checkSqlResult("standard,postgresql", + "SELECT ARRAY[1,2] <@ ARRAY[1,2,3]", + "EXPR$0=true\n"); + checkSqlResult("standard,postgresql", + "SELECT ARRAY[4] <@ ARRAY[1,2,3]", + "EXPR$0=false\n"); + } + + @Test void testArrayOverlapOp() { + // && operator: array overlap + checkSqlResult("standard,postgresql", + "SELECT ARRAY[1,2] && ARRAY[2,3]", + "EXPR$0=true\n"); + checkSqlResult("standard,postgresql", + "SELECT ARRAY[1,2] && ARRAY[3,4]", + "EXPR$0=false\n"); + } } 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 549bddbf724d..6ef93b9283bc 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 @@ -142,7 +142,9 @@ import static org.apache.calcite.sql.fun.SqlLibraryOperators.ARRAY_COMPACT; import static org.apache.calcite.sql.fun.SqlLibraryOperators.ARRAY_CONCAT; import static org.apache.calcite.sql.fun.SqlLibraryOperators.ARRAY_CONCAT_AGG; +import static org.apache.calcite.sql.fun.SqlLibraryOperators.ARRAY_CONTAINED_BY_OP; import static org.apache.calcite.sql.fun.SqlLibraryOperators.ARRAY_CONTAINS; +import static org.apache.calcite.sql.fun.SqlLibraryOperators.ARRAY_CONTAINS_OP; import static org.apache.calcite.sql.fun.SqlLibraryOperators.ARRAY_DISTINCT; import static org.apache.calcite.sql.fun.SqlLibraryOperators.ARRAY_EXCEPT; import static org.apache.calcite.sql.fun.SqlLibraryOperators.ARRAY_INSERT; @@ -151,6 +153,7 @@ import static org.apache.calcite.sql.fun.SqlLibraryOperators.ARRAY_LENGTH; import static org.apache.calcite.sql.fun.SqlLibraryOperators.ARRAY_MAX; import static org.apache.calcite.sql.fun.SqlLibraryOperators.ARRAY_MIN; +import static org.apache.calcite.sql.fun.SqlLibraryOperators.ARRAY_OVERLAP_OP; import static org.apache.calcite.sql.fun.SqlLibraryOperators.ARRAY_POSITION; import static org.apache.calcite.sql.fun.SqlLibraryOperators.ARRAY_PREPEND; import static org.apache.calcite.sql.fun.SqlLibraryOperators.ARRAY_REMOVE; @@ -1118,6 +1121,10 @@ void populate2() { defineMethod(SUBSTRING_INDEX, BuiltInMethod.SUBSTRING_INDEX.method, NullPolicy.STRICT); define(ARRAY_CONCAT, new ArrayConcatImplementor()); define(SORT_ARRAY, new SortArrayImplementor()); + defineMethod(ARRAY_CONTAINS_OP, BuiltInMethod.ARRAY_CONTAINS_OP.method, NullPolicy.NONE); + defineMethod(ARRAY_CONTAINED_BY_OP, BuiltInMethod.ARRAY_CONTAINED_BY_OP.method, + NullPolicy.NONE); + defineMethod(ARRAY_OVERLAP_OP, BuiltInMethod.ARRAY_OVERLAP_OP.method, NullPolicy.NONE); final MethodImplementor isEmptyImplementor = new MethodImplementor(BuiltInMethod.IS_EMPTY.method, NullPolicy.STRICT, false); 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 c1ebafe8edd4..046d123cba02 100644 --- a/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java +++ b/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java @@ -6971,6 +6971,36 @@ public static List arrayUnion(List list1, List list2) { return new ArrayList<>(result); } + /** Support for the PostgreSQL array {@code @>} (contains) operator. */ + public static @Nullable Boolean arrayContainsOp(List list1, List list2) { + if (list1 == null || list2 == null) { + return null; + } + return new HashSet<>(list1).containsAll(list2); + } + + /** Support for the PostgreSQL array {@code <@} (contained by) operator. */ + public static @Nullable Boolean arrayContainedByOp(List list1, List list2) { + if (list1 == null || list2 == null) { + return null; + } + return new HashSet<>(list2).containsAll(list1); + } + + /** Support for the PostgreSQL array {@code &&} (overlap) operator. */ + public static @Nullable Boolean arrayOverlapOp(List list1, List list2) { + if (list1 == null || list2 == null) { + return null; + } + final Set set = new HashSet<>(list1); + for (Object item : list2) { + if (set.contains(item)) { + return true; + } + } + return false; + } + /** Transforms a list, applying a function to each element. */ public static List transform(List list, Function1 function) { 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 2479a92ba247..86189abe10ab 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 @@ -2800,4 +2800,22 @@ private static RelDataType deriveTypeMapFromEntries(SqlOperatorBinding opBinding OperandTypes.family(SqlTypeFamily.TIMESTAMP), OperandTypes.family(SqlTypeFamily.TIMESTAMP, SqlTypeFamily.TIMESTAMP)), SqlFunctionCategory.TIMEDATE); + + /** PostgreSQL array {@code @>} operator: array contains. */ + @LibraryOperator(libraries = {POSTGRESQL}) + public static final SqlBinaryOperator ARRAY_CONTAINS_OP = + new SqlBinaryOperator("@>", SqlKind.OTHER, 30, true, + ReturnTypes.BOOLEAN_NULLABLE, null, OperandTypes.ANY_ANY); + + /** PostgreSQL array {@code <@} operator: array is contained by. */ + @LibraryOperator(libraries = {POSTGRESQL}) + public static final SqlBinaryOperator ARRAY_CONTAINED_BY_OP = + new SqlBinaryOperator("<@", SqlKind.OTHER, 30, true, + ReturnTypes.BOOLEAN_NULLABLE, null, OperandTypes.ANY_ANY); + + /** PostgreSQL array {@code &&} operator: array overlap. */ + @LibraryOperator(libraries = {POSTGRESQL}) + public static final SqlBinaryOperator ARRAY_OVERLAP_OP = + new SqlBinaryOperator("&&", SqlKind.OTHER, 30, true, + ReturnTypes.BOOLEAN_NULLABLE, null, OperandTypes.ANY_ANY); } 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 98b67a02bbf3..d01dbae630ba 100644 --- a/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java +++ b/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java @@ -532,6 +532,9 @@ public enum BuiltInMethod { IS_JSON_OBJECT(JsonFunctions.class, "isJsonObject", String.class), IS_JSON_ARRAY(JsonFunctions.class, "isJsonArray", String.class), IS_JSON_SCALAR(JsonFunctions.class, "isJsonScalar", String.class), + ARRAY_CONTAINS_OP(SqlFunctions.class, "arrayContainsOp", List.class, List.class), + ARRAY_CONTAINED_BY_OP(SqlFunctions.class, "arrayContainedByOp", List.class, List.class), + ARRAY_OVERLAP_OP(SqlFunctions.class, "arrayOverlapOp", List.class, List.class), ST_GEOM_FROM_EWKT(SpatialTypeFunctions.class, "ST_GeomFromEWKT", String.class), UUID_FROM_STRING(UUID.class, "fromString", String.class), UUID_TO_STRING(SqlFunctions.class, "uuidToString", UUID.class), From daf0cb5f61215dd62de655f38b0d360dbfb49a46 Mon Sep 17 00:00:00 2001 From: Terran Date: Sat, 9 May 2026 15:11:53 +0800 Subject: [PATCH 2/3] [CALCITE-7512] Add comments --- .../src/test/java/org/apache/calcite/test/BabelTest.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/babel/src/test/java/org/apache/calcite/test/BabelTest.java b/babel/src/test/java/org/apache/calcite/test/BabelTest.java index d19d481202ae..ba17a1c7bda5 100644 --- a/babel/src/test/java/org/apache/calcite/test/BabelTest.java +++ b/babel/src/test/java/org/apache/calcite/test/BabelTest.java @@ -564,6 +564,10 @@ private void checkSqlResult(String funLibrary, String query, String result) { .query("SELECT AGE(timestamp '2023-12-25') FROM (VALUES (1)) t") .runs(); } + + /** Test case for + * [CALCITE-7512] + * Support array operators for PostgreSQL. */ @Test void testArrayContainsOp() { // @> operator: array contains checkSqlResult("standard,postgresql", @@ -572,9 +576,7 @@ private void checkSqlResult(String funLibrary, String query, String result) { checkSqlResult("standard,postgresql", "SELECT ARRAY[1,2,3] @> ARRAY[4]", "EXPR$0=false\n"); - } - @Test void testArrayContainedByOp() { // <@ operator: array is contained by checkSqlResult("standard,postgresql", "SELECT ARRAY[1,2] <@ ARRAY[1,2,3]", @@ -582,9 +584,7 @@ private void checkSqlResult(String funLibrary, String query, String result) { checkSqlResult("standard,postgresql", "SELECT ARRAY[4] <@ ARRAY[1,2,3]", "EXPR$0=false\n"); - } - @Test void testArrayOverlapOp() { // && operator: array overlap checkSqlResult("standard,postgresql", "SELECT ARRAY[1,2] && ARRAY[2,3]", From 5fba83a28fefa9a1d22560897a20d593d6057a8b Mon Sep 17 00:00:00 2001 From: Terran Date: Wed, 13 May 2026 19:00:18 +0800 Subject: [PATCH 3/3] [CALCITE-7512] Optimize the naming of array function operation symbols --- babel/src/main/codegen/config.fmpp | 12 ++--- .../src/main/codegen/includes/parserImpls.ftl | 24 +++++----- .../org/apache/calcite/test/BabelTest.java | 27 +++++++++--- babel/src/test/resources/sql/postgresql.iq | 34 ++++++++++++++ .../adapter/enumerable/RexImpTable.java | 38 +++++++++++++--- .../apache/calcite/runtime/SqlFunctions.java | 6 +-- .../calcite/sql/fun/SqlLibraryOperators.java | 44 ++++++++++++++++--- 7 files changed, 146 insertions(+), 39 deletions(-) diff --git a/babel/src/main/codegen/config.fmpp b/babel/src/main/codegen/config.fmpp index 2d25c13e84da..8920488dfed5 100644 --- a/babel/src/main/codegen/config.fmpp +++ b/babel/src/main/codegen/config.fmpp @@ -582,9 +582,9 @@ data: { binaryOperatorsTokens: [ "< INFIX_CAST: \"::\" >" "< NULL_SAFE_EQUAL: \"<=>\" >" - "< ARRAY_CONTAINS_OP: \"@>\" >" - "< ARRAY_CONTAINED_BY_OP: \"<@\" >" - "< ARRAY_OVERLAP_OP: \"&&\" >" + "< CONTAINS_OP: \"@>\" >" + "< CONTAINED_BY_OP: \"<@\" >" + "< OVERLAP_OP: \"&&\" >" ] # Custom identifier token. @@ -604,9 +604,9 @@ data: { extraBinaryExpressions: [ "InfixCast" "NullSafeEqual" - "ArrayContainsOp" - "ArrayContainedByOp" - "ArrayOverlapOp" + "ContainsOp" + "ContainedByOp" + "OverlapOp" ] # List of files in @includes directory that have parser method diff --git a/babel/src/main/codegen/includes/parserImpls.ftl b/babel/src/main/codegen/includes/parserImpls.ftl index fdb6f28c03a7..3f7fa640288b 100644 --- a/babel/src/main/codegen/includes/parserImpls.ftl +++ b/babel/src/main/codegen/includes/parserImpls.ftl @@ -222,41 +222,41 @@ void NullSafeEqual(List list, ExprContext exprContext, Span s) : AddExpression2b(list, ExprContext.ACCEPT_SUB_QUERY) } -/** Parses the array contains operator. */ -void ArrayContainsOp(List list, ExprContext exprContext, Span s) : +/** Parses the contains operator. */ +void ContainsOp(List list, ExprContext exprContext, Span s) : { } { - { + { checkNonQueryExpression(exprContext); list.add(new SqlParserUtil.ToTreeListItem( - SqlLibraryOperators.ARRAY_CONTAINS_OP, getPos())); + SqlLibraryOperators.CONTAINS_OP, getPos())); } AddExpression2b(list, ExprContext.ACCEPT_SUB_QUERY) } -/** Parses the array contained-by operator. */ -void ArrayContainedByOp(List list, ExprContext exprContext, Span s) : +/** Parses the contained-by operator. */ +void ContainedByOp(List list, ExprContext exprContext, Span s) : { } { - { + { checkNonQueryExpression(exprContext); list.add(new SqlParserUtil.ToTreeListItem( - SqlLibraryOperators.ARRAY_CONTAINED_BY_OP, getPos())); + SqlLibraryOperators.CONTAINED_BY_OP, getPos())); } AddExpression2b(list, ExprContext.ACCEPT_SUB_QUERY) } -/** Parses the array "&&" (overlap) operator. */ -void ArrayOverlapOp(List list, ExprContext exprContext, Span s) : +/** Parses the overlap operator. */ +void OverlapOp(List list, ExprContext exprContext, Span s) : { } { - { + { checkNonQueryExpression(exprContext); list.add(new SqlParserUtil.ToTreeListItem( - SqlLibraryOperators.ARRAY_OVERLAP_OP, getPos())); + SqlLibraryOperators.OVERLAP_OP, getPos())); } AddExpression2b(list, ExprContext.ACCEPT_SUB_QUERY) } diff --git a/babel/src/test/java/org/apache/calcite/test/BabelTest.java b/babel/src/test/java/org/apache/calcite/test/BabelTest.java index ba17a1c7bda5..97ae6ccbddb1 100644 --- a/babel/src/test/java/org/apache/calcite/test/BabelTest.java +++ b/babel/src/test/java/org/apache/calcite/test/BabelTest.java @@ -567,30 +567,47 @@ private void checkSqlResult(String funLibrary, String query, String result) { /** Test case for * [CALCITE-7512] - * Support array operators for PostgreSQL. */ - @Test void testArrayContainsOp() { - // @> operator: array contains + * Support containment and overlap operators for PostgreSQL. */ + @Test void testPostgresContainmentAndOverlapOperators() { + // Test @> operator: contains checkSqlResult("standard,postgresql", "SELECT ARRAY[1,2,3] @> ARRAY[1,2]", "EXPR$0=true\n"); checkSqlResult("standard,postgresql", "SELECT ARRAY[1,2,3] @> ARRAY[4]", "EXPR$0=false\n"); + checkSqlResult("standard,postgresql", + "SELECT ARRAY[1,2,3] @> ARRAY[1,2,3]", + "EXPR$0=true\n"); - // <@ operator: array is contained by + // Test <@ operator: contained by checkSqlResult("standard,postgresql", "SELECT ARRAY[1,2] <@ ARRAY[1,2,3]", "EXPR$0=true\n"); checkSqlResult("standard,postgresql", "SELECT ARRAY[4] <@ ARRAY[1,2,3]", "EXPR$0=false\n"); + checkSqlResult("standard,postgresql", + "SELECT ARRAY[1,2,3] <@ ARRAY[1,2,3]", + "EXPR$0=true\n"); - // && operator: array overlap + // Test && operator: overlap checkSqlResult("standard,postgresql", "SELECT ARRAY[1,2] && ARRAY[2,3]", "EXPR$0=true\n"); checkSqlResult("standard,postgresql", "SELECT ARRAY[1,2] && ARRAY[3,4]", "EXPR$0=false\n"); + checkSqlResult("standard,postgresql", + "SELECT ARRAY[1,2] && ARRAY[1,3]", + "EXPR$0=true\n"); + + // Test NULL handling + checkSqlResult("standard,postgresql", + "SELECT ARRAY[1,2] @> NULL", + "EXPR$0=null\n"); + checkSqlResult("standard,postgresql", + "SELECT NULL <@ ARRAY[1,2,3]", + "EXPR$0=null\n"); } } diff --git a/babel/src/test/resources/sql/postgresql.iq b/babel/src/test/resources/sql/postgresql.iq index af230bd21bd6..56d990c65dcb 100644 --- a/babel/src/test/resources/sql/postgresql.iq +++ b/babel/src/test/resources/sql/postgresql.iq @@ -1369,4 +1369,38 @@ X ABC !ok +# Test PostgreSQL containment and overlap operators +# @> operator: contains +select array[1,2,3] @> array[1,2]; +EXPR$0 +true +!ok + +select array[1,2,3] @> array[4]; +EXPR$0 +false +!ok + +# <@ operator: contained by +select array[1,2] <@ array[1,2,3]; +EXPR$0 +true +!ok + +select array[4] <@ array[1,2,3]; +EXPR$0 +false +!ok + +# && operator: overlap +select array[1,2] && array[2,3]; +EXPR$0 +true +!ok + +select array[1,2] && array[3,4]; +EXPR$0 +false +!ok + # End postgresql.iq 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 6ef93b9283bc..628eda8a3203 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 @@ -142,9 +142,7 @@ import static org.apache.calcite.sql.fun.SqlLibraryOperators.ARRAY_COMPACT; import static org.apache.calcite.sql.fun.SqlLibraryOperators.ARRAY_CONCAT; import static org.apache.calcite.sql.fun.SqlLibraryOperators.ARRAY_CONCAT_AGG; -import static org.apache.calcite.sql.fun.SqlLibraryOperators.ARRAY_CONTAINED_BY_OP; import static org.apache.calcite.sql.fun.SqlLibraryOperators.ARRAY_CONTAINS; -import static org.apache.calcite.sql.fun.SqlLibraryOperators.ARRAY_CONTAINS_OP; import static org.apache.calcite.sql.fun.SqlLibraryOperators.ARRAY_DISTINCT; import static org.apache.calcite.sql.fun.SqlLibraryOperators.ARRAY_EXCEPT; import static org.apache.calcite.sql.fun.SqlLibraryOperators.ARRAY_INSERT; @@ -153,7 +151,6 @@ import static org.apache.calcite.sql.fun.SqlLibraryOperators.ARRAY_LENGTH; import static org.apache.calcite.sql.fun.SqlLibraryOperators.ARRAY_MAX; import static org.apache.calcite.sql.fun.SqlLibraryOperators.ARRAY_MIN; -import static org.apache.calcite.sql.fun.SqlLibraryOperators.ARRAY_OVERLAP_OP; import static org.apache.calcite.sql.fun.SqlLibraryOperators.ARRAY_POSITION; import static org.apache.calcite.sql.fun.SqlLibraryOperators.ARRAY_PREPEND; import static org.apache.calcite.sql.fun.SqlLibraryOperators.ARRAY_REMOVE; @@ -192,6 +189,8 @@ import static org.apache.calcite.sql.fun.SqlLibraryOperators.CONCAT_WS_MSSQL; import static org.apache.calcite.sql.fun.SqlLibraryOperators.CONCAT_WS_POSTGRESQL; import static org.apache.calcite.sql.fun.SqlLibraryOperators.CONCAT_WS_SPARK; +import static org.apache.calcite.sql.fun.SqlLibraryOperators.CONTAINED_BY_OP; +import static org.apache.calcite.sql.fun.SqlLibraryOperators.CONTAINS_OP; import static org.apache.calcite.sql.fun.SqlLibraryOperators.CONTAINS_SUBSTR; import static org.apache.calcite.sql.fun.SqlLibraryOperators.CONVERT_ORACLE; import static org.apache.calcite.sql.fun.SqlLibraryOperators.COSD; @@ -266,6 +265,7 @@ import static org.apache.calcite.sql.fun.SqlLibraryOperators.MONTHNAME; import static org.apache.calcite.sql.fun.SqlLibraryOperators.OFFSET; import static org.apache.calcite.sql.fun.SqlLibraryOperators.ORDINAL; +import static org.apache.calcite.sql.fun.SqlLibraryOperators.OVERLAP_OP; import static org.apache.calcite.sql.fun.SqlLibraryOperators.PARSE_DATE; import static org.apache.calcite.sql.fun.SqlLibraryOperators.PARSE_DATETIME; import static org.apache.calcite.sql.fun.SqlLibraryOperators.PARSE_TIME; @@ -1121,10 +1121,14 @@ void populate2() { defineMethod(SUBSTRING_INDEX, BuiltInMethod.SUBSTRING_INDEX.method, NullPolicy.STRICT); define(ARRAY_CONCAT, new ArrayConcatImplementor()); define(SORT_ARRAY, new SortArrayImplementor()); - defineMethod(ARRAY_CONTAINS_OP, BuiltInMethod.ARRAY_CONTAINS_OP.method, NullPolicy.NONE); - defineMethod(ARRAY_CONTAINED_BY_OP, BuiltInMethod.ARRAY_CONTAINED_BY_OP.method, - NullPolicy.NONE); - defineMethod(ARRAY_OVERLAP_OP, BuiltInMethod.ARRAY_OVERLAP_OP.method, NullPolicy.NONE); + // PostgreSQL containment and overlap operators + // Uses type dispatch to support multiple operand types (ARRAY, RANGE, JSONB) + define(CONTAINS_OP, + new ContainmentOpImplementor(BuiltInMethod.ARRAY_CONTAINS_OP.method)); + define(CONTAINED_BY_OP, + new ContainmentOpImplementor(BuiltInMethod.ARRAY_CONTAINED_BY_OP.method)); + define(OVERLAP_OP, + new ContainmentOpImplementor(BuiltInMethod.ARRAY_OVERLAP_OP.method)); final MethodImplementor isEmptyImplementor = new MethodImplementor(BuiltInMethod.IS_EMPTY.method, NullPolicy.STRICT, false); @@ -4696,6 +4700,26 @@ protected MethodCallExpression implementSafe(Method method, } } + /** Implementor for PostgreSQL containment and overlap operators. + * + *

Currently supports ARRAY types only. + */ + private static class ContainmentOpImplementor extends AbstractRexCallImplementor { + + private final Method method; + + ContainmentOpImplementor(Method method) { + super("containment_op", NullPolicy.NONE, false); + this.method = method; + } + + @Override Expression implementSafe(final RexToLixTranslator translator, + final RexCall call, final List argValueList) { + return new MethodImplementor(method, nullPolicy, false) + .implementSafe(translator, call, argValueList); + } + } + /** Implementor for the {@code PI} operator. */ private static class PiImplementor extends AbstractRexCallImplementor { PiImplementor() { 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 046d123cba02..2ecf7a71263f 100644 --- a/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java +++ b/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java @@ -6971,7 +6971,7 @@ public static List arrayUnion(List list1, List list2) { return new ArrayList<>(result); } - /** Support for the PostgreSQL array {@code @>} (contains) operator. */ + /** Support for the PostgreSQL {@code @>} (contains) operator. */ public static @Nullable Boolean arrayContainsOp(List list1, List list2) { if (list1 == null || list2 == null) { return null; @@ -6979,7 +6979,7 @@ public static List arrayUnion(List list1, List list2) { return new HashSet<>(list1).containsAll(list2); } - /** Support for the PostgreSQL array {@code <@} (contained by) operator. */ + /** Support for the PostgreSQL {@code <@} (contained by) operator. */ public static @Nullable Boolean arrayContainedByOp(List list1, List list2) { if (list1 == null || list2 == null) { return null; @@ -6987,7 +6987,7 @@ public static List arrayUnion(List list1, List list2) { return new HashSet<>(list2).containsAll(list1); } - /** Support for the PostgreSQL array {@code &&} (overlap) operator. */ + /** Support for the PostgreSQL {@code &&} (overlap) operator. */ public static @Nullable Boolean arrayOverlapOp(List list1, List list2) { if (list1 == null || list2 == null) { return null; 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 86189abe10ab..0294ce811525 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 @@ -2801,21 +2801,53 @@ private static RelDataType deriveTypeMapFromEntries(SqlOperatorBinding opBinding OperandTypes.family(SqlTypeFamily.TIMESTAMP, SqlTypeFamily.TIMESTAMP)), SqlFunctionCategory.TIMEDATE); - /** PostgreSQL array {@code @>} operator: array contains. */ + /** + * The PostgreSQL {@code @>} (contains) operator. + * + *

This operator is polymorphic in PostgreSQL: + *

    + *
  • ARRAY: checks if left array contains all elements of right array
  • + *
  • RANGE: checks if left range contains the right value/range
  • + *
  • JSONB: checks if left JSON document contains the right JSON document
  • + *
+ * + * @see + * PostgreSQL Array Functions + * @see + * PostgreSQL Range Types + * @see + * PostgreSQL JSON Functions + */ @LibraryOperator(libraries = {POSTGRESQL}) - public static final SqlBinaryOperator ARRAY_CONTAINS_OP = + public static final SqlBinaryOperator CONTAINS_OP = new SqlBinaryOperator("@>", SqlKind.OTHER, 30, true, ReturnTypes.BOOLEAN_NULLABLE, null, OperandTypes.ANY_ANY); - /** PostgreSQL array {@code <@} operator: array is contained by. */ + /** + * The PostgreSQL {@code <@} (contained by) operator. + * + *

This operator is polymorphic in PostgreSQL: + *

    + *
  • ARRAY: checks if left array is contained by right array
  • + *
  • RANGE: checks if left range is contained by right range
  • + *
+ */ @LibraryOperator(libraries = {POSTGRESQL}) - public static final SqlBinaryOperator ARRAY_CONTAINED_BY_OP = + public static final SqlBinaryOperator CONTAINED_BY_OP = new SqlBinaryOperator("<@", SqlKind.OTHER, 30, true, ReturnTypes.BOOLEAN_NULLABLE, null, OperandTypes.ANY_ANY); - /** PostgreSQL array {@code &&} operator: array overlap. */ + /** + * The PostgreSQL {@code &&} (overlap) operator. + * + *

This operator is polymorphic in PostgreSQL: + *

    + *
  • ARRAY: checks if two arrays have elements in common
  • + *
  • RANGE: checks if two ranges overlap
  • + *
+ */ @LibraryOperator(libraries = {POSTGRESQL}) - public static final SqlBinaryOperator ARRAY_OVERLAP_OP = + public static final SqlBinaryOperator OVERLAP_OP = new SqlBinaryOperator("&&", SqlKind.OTHER, 30, true, ReturnTypes.BOOLEAN_NULLABLE, null, OperandTypes.ANY_ANY); }