Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions babel/src/main/codegen/config.fmpp
Original file line number Diff line number Diff line change
Expand Up @@ -582,6 +582,9 @@ data: {
binaryOperatorsTokens: [
"< INFIX_CAST: \"::\" >"
"< NULL_SAFE_EQUAL: \"<=>\" >"
"< CONTAINS_OP: \"@>\" >"
"< CONTAINED_BY_OP: \"<@\" >"
"< OVERLAP_OP: \"&&\" >"
]

# Custom identifier token.
Expand All @@ -601,6 +604,9 @@ data: {
extraBinaryExpressions: [
"InfixCast"
"NullSafeEqual"
"ContainsOp"
"ContainedByOp"
"OverlapOp"
]

# List of files in @includes directory that have parser method
Expand Down
39 changes: 39 additions & 0 deletions babel/src/main/codegen/includes/parserImpls.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -221,3 +221,42 @@ void NullSafeEqual(List<Object> list, ExprContext exprContext, Span s) :
}
AddExpression2b(list, ExprContext.ACCEPT_SUB_QUERY)
}

/** Parses the contains operator. */
void ContainsOp(List<Object> list, ExprContext exprContext, Span s) :
{
}
{
<CONTAINS_OP> {
checkNonQueryExpression(exprContext);
list.add(new SqlParserUtil.ToTreeListItem(
SqlLibraryOperators.CONTAINS_OP, getPos()));
}
AddExpression2b(list, ExprContext.ACCEPT_SUB_QUERY)
}

/** Parses the contained-by operator. */
void ContainedByOp(List<Object> list, ExprContext exprContext, Span s) :
{
}
{
<CONTAINED_BY_OP> {
checkNonQueryExpression(exprContext);
list.add(new SqlParserUtil.ToTreeListItem(
SqlLibraryOperators.CONTAINED_BY_OP, getPos()));
}
AddExpression2b(list, ExprContext.ACCEPT_SUB_QUERY)
}

/** Parses the overlap operator. */
void OverlapOp(List<Object> list, ExprContext exprContext, Span s) :
{
}
{
<OVERLAP_OP> {
checkNonQueryExpression(exprContext);
list.add(new SqlParserUtil.ToTreeListItem(
SqlLibraryOperators.OVERLAP_OP, getPos()));
}
AddExpression2b(list, ExprContext.ACCEPT_SUB_QUERY)
}
46 changes: 46 additions & 0 deletions babel/src/test/java/org/apache/calcite/test/BabelTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -564,4 +564,50 @@ private void checkSqlResult(String funLibrary, String query, String result) {
.query("SELECT AGE(timestamp '2023-12-25') FROM (VALUES (1)) t")
.runs();
}

/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-7512">[CALCITE-7512]
* Support containment and overlap operators for PostgreSQL</a>. */
@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");

// 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");

// 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");
}
}
34 changes: 34 additions & 0 deletions babel/src/test/resources/sql/postgresql.iq
Original file line number Diff line number Diff line change
Expand Up @@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -189,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;
Expand Down Expand Up @@ -263,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;
Expand Down Expand Up @@ -1118,6 +1121,14 @@ void populate2() {
defineMethod(SUBSTRING_INDEX, BuiltInMethod.SUBSTRING_INDEX.method, NullPolicy.STRICT);
define(ARRAY_CONCAT, new ArrayConcatImplementor());
define(SORT_ARRAY, new SortArrayImplementor());
// 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);
Expand Down Expand Up @@ -4689,6 +4700,26 @@ protected MethodCallExpression implementSafe(Method method,
}
}

/** Implementor for PostgreSQL containment and overlap operators.
*
* <p>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<Expression> argValueList) {
return new MethodImplementor(method, nullPolicy, false)
.implementSafe(translator, call, argValueList);
}
}

/** Implementor for the {@code PI} operator. */
private static class PiImplementor extends AbstractRexCallImplementor {
PiImplementor() {
Expand Down
30 changes: 30 additions & 0 deletions core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java
Original file line number Diff line number Diff line change
Expand Up @@ -6971,6 +6971,36 @@
return new ArrayList<>(result);
}

/** Support for the PostgreSQL {@code @>} (contains) operator. */
public static @Nullable Boolean arrayContainsOp(List list1, List list2) {

Check warning on line 6975 in core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Provide the parametrized type for this generic.

See more on https://sonarcloud.io/project/issues?id=apache_calcite&issues=AZ4LoWf8BOAiju0VXwxD&open=AZ4LoWf8BOAiju0VXwxD&pullRequest=4930

Check warning on line 6975 in core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Provide the parametrized type for this generic.

See more on https://sonarcloud.io/project/issues?id=apache_calcite&issues=AZ4LoWf8BOAiju0VXwxC&open=AZ4LoWf8BOAiju0VXwxC&pullRequest=4930
if (list1 == null || list2 == null) {
return null;
}
return new HashSet<>(list1).containsAll(list2);
}

/** Support for the PostgreSQL {@code <@} (contained by) operator. */
public static @Nullable Boolean arrayContainedByOp(List list1, List list2) {

Check warning on line 6983 in core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Provide the parametrized type for this generic.

See more on https://sonarcloud.io/project/issues?id=apache_calcite&issues=AZ4LoWf8BOAiju0VXwxF&open=AZ4LoWf8BOAiju0VXwxF&pullRequest=4930

Check warning on line 6983 in core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Provide the parametrized type for this generic.

See more on https://sonarcloud.io/project/issues?id=apache_calcite&issues=AZ4LoWf8BOAiju0VXwxE&open=AZ4LoWf8BOAiju0VXwxE&pullRequest=4930
if (list1 == null || list2 == null) {
return null;
}
return new HashSet<>(list2).containsAll(list1);
}

/** Support for the PostgreSQL {@code &&} (overlap) operator. */
public static @Nullable Boolean arrayOverlapOp(List list1, List list2) {

Check warning on line 6991 in core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Provide the parametrized type for this generic.

See more on https://sonarcloud.io/project/issues?id=apache_calcite&issues=AZ4LoWf8BOAiju0VXwxH&open=AZ4LoWf8BOAiju0VXwxH&pullRequest=4930

Check warning on line 6991 in core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Provide the parametrized type for this generic.

See more on https://sonarcloud.io/project/issues?id=apache_calcite&issues=AZ4LoWf8BOAiju0VXwxG&open=AZ4LoWf8BOAiju0VXwxG&pullRequest=4930
if (list1 == null || list2 == null) {
return null;
}
final Set set = new HashSet<>(list1);

Check warning on line 6995 in core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Provide the parametrized type for this generic.

See more on https://sonarcloud.io/project/issues?id=apache_calcite&issues=AZ4LoWf8BOAiju0VXwxI&open=AZ4LoWf8BOAiju0VXwxI&pullRequest=4930
for (Object item : list2) {
if (set.contains(item)) {
return true;
}
}
return false;
}

/** Transforms a list, applying a function to each element. */
public static <F, T> List<T> transform(List<? extends F> list,
Function1<? super F, ? extends T> function) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2800,4 +2800,54 @@ private static RelDataType deriveTypeMapFromEntries(SqlOperatorBinding opBinding
OperandTypes.family(SqlTypeFamily.TIMESTAMP),
OperandTypes.family(SqlTypeFamily.TIMESTAMP, SqlTypeFamily.TIMESTAMP)),
SqlFunctionCategory.TIMEDATE);

/**
* The PostgreSQL {@code @>} (contains) operator.
*
* <p>This operator is polymorphic in PostgreSQL:
* <ul>
* <li>ARRAY: checks if left array contains all elements of right array</li>
* <li>RANGE: checks if left range contains the right value/range</li>
* <li>JSONB: checks if left JSON document contains the right JSON document</li>
* </ul>
*
* @see <a href="https://www.postgresql.org/docs/current/functions-array.html">
* PostgreSQL Array Functions</a>
* @see <a href="https://www.postgresql.org/docs/current/rangetypes.html">
* PostgreSQL Range Types</a>
* @see <a href="https://www.postgresql.org/docs/current/functions-json.html">
* PostgreSQL JSON Functions</a>
*/
@LibraryOperator(libraries = {POSTGRESQL})
public static final SqlBinaryOperator CONTAINS_OP =
new SqlBinaryOperator("@>", SqlKind.OTHER, 30, true,
ReturnTypes.BOOLEAN_NULLABLE, null, OperandTypes.ANY_ANY);

/**
* The PostgreSQL {@code <@} (contained by) operator.
*
* <p>This operator is polymorphic in PostgreSQL:
* <ul>
* <li>ARRAY: checks if left array is contained by right array</li>
* <li>RANGE: checks if left range is contained by right range</li>
* </ul>
*/
@LibraryOperator(libraries = {POSTGRESQL})
public static final SqlBinaryOperator CONTAINED_BY_OP =
new SqlBinaryOperator("<@", SqlKind.OTHER, 30, true,
ReturnTypes.BOOLEAN_NULLABLE, null, OperandTypes.ANY_ANY);

/**
* The PostgreSQL {@code &&} (overlap) operator.
*
* <p>This operator is polymorphic in PostgreSQL:
* <ul>
* <li>ARRAY: checks if two arrays have elements in common</li>
* <li>RANGE: checks if two ranges overlap</li>
* </ul>
*/
@LibraryOperator(libraries = {POSTGRESQL})
public static final SqlBinaryOperator OVERLAP_OP =
new SqlBinaryOperator("&&", SqlKind.OTHER, 30, true,
ReturnTypes.BOOLEAN_NULLABLE, null, OperandTypes.ANY_ANY);
}
3 changes: 3 additions & 0 deletions core/src/main/java/org/apache/calcite/util/BuiltInMethod.java
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
Loading