Skip to content

Commit a20f006

Browse files
committed
Prevents pushing down isnull(nested) in PredicateAnalyzer to correct pushdown of complex & partial filters containing isnull(nested)
Signed-off-by: Yuanchun Shen <yuanchu@amazon.com>
1 parent a3249bf commit a20f006

3 files changed

Lines changed: 35 additions & 43 deletions

File tree

opensearch/src/main/java/org/opensearch/sql/opensearch/planner/physical/OpenSearchFilterIndexScanRule.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,6 @@ public interface Config extends RelRule.Config {
5454
.withOperandSupplier(
5555
b0 ->
5656
b0.operand(LogicalFilter.class)
57-
.predicate(Predicate.not(OpenSearchIndexScanRule::isConditionExcluded))
5857
.oneInput(
5958
b1 ->
6059
b1.operand(CalciteLogicalIndexScan.class)

opensearch/src/main/java/org/opensearch/sql/opensearch/planner/physical/OpenSearchIndexScanRule.java

Lines changed: 0 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -6,20 +6,13 @@
66
package org.opensearch.sql.opensearch.planner.physical;
77

88
import java.util.HashSet;
9-
import java.util.List;
109
import java.util.Set;
1110
import org.apache.calcite.plan.RelOptTable;
12-
import org.apache.calcite.rel.core.Filter;
1311
import org.apache.calcite.rel.core.Sort;
1412
import org.apache.calcite.rel.logical.LogicalProject;
1513
import org.apache.calcite.rel.logical.LogicalSort;
16-
import org.apache.calcite.rel.type.RelDataTypeField;
17-
import org.apache.calcite.rex.RexCall;
18-
import org.apache.calcite.rex.RexInputRef;
1914
import org.apache.calcite.rex.RexNode;
2015
import org.apache.calcite.rex.RexOver;
21-
import org.apache.calcite.sql.SqlKind;
22-
import org.apache.calcite.sql.type.ArraySqlType;
2316
import org.opensearch.sql.opensearch.storage.OpenSearchIndex;
2417
import org.opensearch.sql.opensearch.storage.scan.AbstractCalciteIndexScan;
2518

@@ -69,39 +62,4 @@ static boolean isLogicalSortLimit(LogicalSort sort) {
6962
static boolean sortByFieldsOnly(Sort sort) {
7063
return !sort.getCollation().getFieldCollations().isEmpty() && sort.fetch == null;
7164
}
72-
73-
/**
74-
* Filters with conditions of these kinds should not be pushed down since DSL does not handle such
75-
* cases correctly
76-
*/
77-
Set<SqlKind> FILTER_PUSHDOWN_EXCLUDED_FUNCTION = Set.of(SqlKind.IS_NULL, SqlKind.IS_NOT_NULL);
78-
79-
/**
80-
* Determines whether a filter condition should be excluded from pushdown to OpenSearch.
81-
* Currently, this method only excludes IS NULL and IS NOT NULL operations on nested fields, as
82-
* OpenSearch DSL does not handle these conditions correctly.
83-
*
84-
* @param filter The filter operation to check
85-
* @return true if the condition should be excluded from pushdown, false otherwise
86-
*/
87-
static boolean isConditionExcluded(Filter filter) {
88-
RexNode condition = filter.getCondition();
89-
if (condition instanceof RexCall conditionCall) {
90-
return FILTER_PUSHDOWN_EXCLUDED_FUNCTION.contains(conditionCall.getKind())
91-
&& conditionContainsNestedField(condition, filter.getRowType().getFieldList());
92-
}
93-
return false;
94-
}
95-
96-
private static boolean conditionContainsNestedField(
97-
RexNode condition, List<RelDataTypeField> fields) {
98-
if (condition instanceof RexCall conditionCall) {
99-
return conditionCall.getOperands().stream()
100-
.filter(operand -> operand instanceof RexInputRef)
101-
.map(operand -> (RexInputRef) operand)
102-
// Nested fields are of type ArraySqlType
103-
.anyMatch(inputRef -> fields.get(inputRef.getIndex()).getType() instanceof ArraySqlType);
104-
}
105-
return false;
106-
}
10765
}

opensearch/src/main/java/org/opensearch/sql/opensearch/request/PredicateAnalyzer.java

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@
7272
import org.apache.calcite.sql.SqlOperator;
7373
import org.apache.calcite.sql.SqlSyntax;
7474
import org.apache.calcite.sql.fun.SqlStdOperatorTable;
75+
import org.apache.calcite.sql.type.ArraySqlType;
7576
import org.apache.calcite.sql.type.SqlTypeFamily;
7677
import org.apache.calcite.sql.type.SqlTypeName;
7778
import org.apache.calcite.util.NlsString;
@@ -485,6 +486,10 @@ private QueryExpression postfix(RexCall call) {
485486
String message = format(Locale.ROOT, "Unsupported operator: [%s]", call);
486487
throw new PredicateAnalyzerException(message);
487488
}
489+
490+
// OpenSearch DSL does not handle IS_NULL / IS_NOT_NULL on nested fields correctly
491+
checkForNestedFieldOperands(call);
492+
488493
Expression a = call.getOperands().get(0).accept(this);
489494
// OpenSearch does not want is null/is not null (exists query)
490495
// for _id and _index, although it supports for all other metadata column
@@ -1357,6 +1362,11 @@ public ScriptQueryExpression(
13571362
RelDataType rowType,
13581363
Map<String, ExprType> fieldTypes,
13591364
RelOptCluster cluster) {
1365+
if (rexNode instanceof RexCall
1366+
&& (rexNode.getKind().equals(SqlKind.IS_NULL)
1367+
|| rexNode.getKind().equals(SqlKind.IS_NOT_NULL))) {
1368+
checkForNestedFieldOperands((RexCall) rexNode);
1369+
}
13601370
ReferenceFieldVisitor validator = new ReferenceFieldVisitor(rowType, fieldTypes, true);
13611371
// Dry run visitInputRef to make sure the input reference ExprType is valid for script
13621372
// pushdown
@@ -1645,4 +1655,29 @@ private static void checkForIncompatibleDateTimeOperands(RexCall call) {
16451655
"Cannot handle " + call.getKind() + " expression for _id field, " + call);
16461656
}
16471657
}
1658+
1659+
/**
1660+
* Examines the operands of a RexCall to check for nested fields and throws an exception if any
1661+
* are found.
1662+
*
1663+
* <p>This check prevents operations (IS_NULL, IS_NOT_NULL) that would produce incorrect results
1664+
* in OpenSearch when pushed down as either DSL queries or scripts.
1665+
*
1666+
* @param call The RexCall to check for nested field operands
1667+
* @throws PredicateAnalyzerException if any nested fields are detected in the operands
1668+
*/
1669+
private static void checkForNestedFieldOperands(RexCall call) throws PredicateAnalyzerException {
1670+
boolean conditionContainsNestedField =
1671+
call.getOperands().stream()
1672+
.map(RexNode::getType)
1673+
// Nested fields are of type ArraySqlType
1674+
.anyMatch(type -> type instanceof ArraySqlType);
1675+
if (conditionContainsNestedField) {
1676+
throw new PredicateAnalyzerException(
1677+
format(
1678+
Locale.ROOT,
1679+
"OpenSearch DSL does not handle %s on nested fields correctly",
1680+
call.getKind()));
1681+
}
1682+
}
16481683
}

0 commit comments

Comments
 (0)