This is an automated email from the ASF dual-hosted git repository.
asf-gitbox-commits pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ignite.git
The following commit(s) were added to refs/heads/master by this push:
new 7a27bfe1487 IGNITE-28570 SQL Calcite: Fix stack overflow exception
with large scalar IN - Fixes #13049.
7a27bfe1487 is described below
commit 7a27bfe148758050e615e1f8f353216426347887
Author: Aleksey Plekhanov <[email protected]>
AuthorDate: Thu Apr 23 16:38:00 2026 +0300
IGNITE-28570 SQL Calcite: Fix stack overflow exception with large scalar IN
- Fixes #13049.
Signed-off-by: Aleksey Plekhanov <[email protected]>
---
.../processors/query/calcite/util/RexUtils.java | 44 ++++++++++++++++++++-
.../planner/RexSimplificationPlannerTest.java | 46 ++++++++++++++++++++++
2 files changed, 88 insertions(+), 2 deletions(-)
diff --git
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/util/RexUtils.java
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/util/RexUtils.java
index 4e0930ce827..180090141d3 100644
---
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/util/RexUtils.java
+++
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/util/RexUtils.java
@@ -80,6 +80,7 @@ import org.jetbrains.annotations.Nullable;
import static java.util.Objects.requireNonNull;
import static org.apache.calcite.rex.RexUtil.removeCast;
import static org.apache.calcite.rex.RexUtil.sargRef;
+import static org.apache.calcite.sql.SqlKind.AND;
import static org.apache.calcite.sql.SqlKind.EQUALS;
import static org.apache.calcite.sql.SqlKind.GREATER_THAN;
import static org.apache.calcite.sql.SqlKind.GREATER_THAN_OR_EQUAL;
@@ -97,6 +98,9 @@ public class RexUtils {
/** Maximum amount of search bounds tuples per scan. */
public static final int MAX_SEARCH_BOUNDS_COMPLEXITY = 100;
+ /** Operands limit per call when expanding SEARCH/SARG operator. */
+ public static final int SEARCH_EXPAND_OPERANDS_LIMIT = 100;
+
/** */
public static RexNode makeCast(RexBuilder builder, RexNode node,
RelDataType type) {
return TypeUtils.needCast(builder.getTypeFactory(), node.getType(),
type) ? builder.makeCast(type, node) : node;
@@ -879,7 +883,7 @@ public class RexUtils {
RexNode op1 = call.getOperands().get(1);
if (!op0.getType().isNullable())
- return RexUtil.expandSearch(rexBuilder, program, call);
+ return expandSearch(rexBuilder, program, call);
while (op1 instanceof RexLocalRef) // Dereference local variable.
op1 = requireNonNull(program,
"program").getExprList().get(((RexSlot)op1).getIndex());
@@ -892,7 +896,7 @@ public class RexUtils {
? rexBuilder.makeLiteral(arg.nullAs.toBoolean())
:
rexBuilder.makeNullLiteral(rexBuilder.getTypeFactory().createSqlType(SqlTypeName.BOOLEAN));
- RexNode expandedSearch = RexUtil.expandSearch(rexBuilder, program,
+ RexNode expandedSearch = expandSearch(rexBuilder, program,
rexBuilder.makeCall(call.getOperator(),
rexBuilder.makeNotNull(op0), op1));
return rexBuilder.makeCall(SqlStdOperatorTable.CASE,
@@ -902,6 +906,42 @@ public class RexUtils {
);
}
+ /**
+ * Expand SEARCH/SARG with operands limit (to avoid too deep recursion on
operands processing after compilation).
+ */
+ private static RexNode expandSearch(RexBuilder rexBuilder, @Nullable
RexProgram program, RexNode call) {
+ RexNode expandedSearch = RexUtil.expandSearch(rexBuilder, program,
call);
+
+ RexShuttle groupingShuttle = new RexShuttle() {
+ @Override public RexNode visitCall(RexCall call) {
+ if (call.getOperands().size() > SEARCH_EXPAND_OPERANDS_LIMIT
+ && (call.getOperator().getKind() == OR ||
call.getOperator().getKind() == AND)) {
+
+ List<RexNode> groupedOps = new ArrayList<>();
+ List<RexNode> grpOps = new
ArrayList<>(SEARCH_EXPAND_OPERANDS_LIMIT);
+
+ for (int i = 0; i < call.getOperands().size(); i++) {
+ if (i > 0 && i % SEARCH_EXPAND_OPERANDS_LIMIT == 0) {
+
groupedOps.add(rexBuilder.makeCall(call.getOperator(), grpOps));
+
+ grpOps = new
ArrayList<>(SEARCH_EXPAND_OPERANDS_LIMIT);
+ }
+
+ grpOps.add(call.getOperands().get(i));
+ }
+
+ groupedOps.add(grpOps.size() == 1 ? grpOps.get(0) :
rexBuilder.makeCall(call.getOperator(), grpOps));
+
+ return rexBuilder.makeCall(call.getOperator(), groupedOps);
+ }
+
+ return super.visitCall(call);
+ }
+ };
+
+ return expandedSearch.accept(groupingShuttle);
+ }
+
/**
* Traverse {@code node} and expand all SEARCH/SARG operators (with
preceding NULLs check).
*
diff --git
a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/RexSimplificationPlannerTest.java
b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/RexSimplificationPlannerTest.java
index 6e242bf8d1e..f3da6391600 100644
---
a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/RexSimplificationPlannerTest.java
+++
b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/RexSimplificationPlannerTest.java
@@ -17,14 +17,24 @@
package org.apache.ignite.internal.processors.query.calcite.planner;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
import org.apache.calcite.linq4j.tree.Types;
import org.apache.calcite.rel.core.Join;
+import org.apache.calcite.rex.RexCall;
+import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.rex.RexVisitor;
+import org.apache.calcite.rex.RexVisitorImpl;
import org.apache.calcite.util.Util;
import
org.apache.ignite.internal.processors.query.calcite.exec.exp.IgniteScalarFunction;
+import org.apache.ignite.internal.processors.query.calcite.rel.IgniteTableScan;
import org.apache.ignite.internal.processors.query.calcite.rel.IgniteValues;
import
org.apache.ignite.internal.processors.query.calcite.rel.ProjectableFilterableTableScan;
import org.apache.ignite.internal.processors.query.calcite.schema.IgniteSchema;
import
org.apache.ignite.internal.processors.query.calcite.trait.IgniteDistributions;
+import org.apache.ignite.internal.processors.query.calcite.util.Commons;
+import org.apache.ignite.internal.processors.query.calcite.util.RexUtils;
import org.junit.Test;
import static org.apache.calcite.sql.type.SqlTypeName.INTEGER;
@@ -240,6 +250,42 @@ public class RexSimplificationPlannerTest extends
AbstractPlannerTest {
.and(s -> "AND(=($t0, 0), =(ECHO(ECHO_ND(1)),
1))".equals(s.condition().toString())));
}
+ /** */
+ @Test
+ public void testSearchOperandsGrouping() throws Exception {
+ IgniteSchema publicSchema = createSchema(createTable("T", single(),
"ID", INTEGER));
+
+ String in = IntStream.range(1, RexUtils.SEARCH_EXPAND_OPERANDS_LIMIT *
3)
+ .mapToObj(Integer::toString).collect(Collectors.joining(", "));
+
+ assertPlan("SELECT * FROM t WHERE id IN (" + in + ")", publicSchema,
isTableScan("T")
+ .and(checkOperandsLimit()));
+
+ assertPlan("SELECT * FROM t WHERE id NOT IN (" + in + ")",
publicSchema, isTableScan("T")
+ .and(checkOperandsLimit()));
+ }
+
+ /** */
+ private Predicate<IgniteTableScan> checkOperandsLimit() {
+ return n -> {
+ RexNode expandedSearch =
RexUtils.expandSearchNullableRecursive(Commons.emptyCluster().getRexBuilder(),
+ null, n.condition());
+
+ RexVisitor<Void> operandsChecker = new RexVisitorImpl<>(true) {
+ @Override public Void visitCall(RexCall call) {
+ assertTrue("Unexpected operands count: " +
call.getOperands().size(),
+ call.getOperands().size() <=
RexUtils.SEARCH_EXPAND_OPERANDS_LIMIT);
+
+ return super.visitCall(call);
+ }
+ };
+
+ expandedSearch.accept(operandsChecker);
+
+ return true;
+ };
+ }
+
/** */
public static int echo(int val) {
return val;