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;

Reply via email to