Skip to content

Commit 7a27bfe

Browse files
IGNITE-28570 SQL Calcite: Fix stack overflow exception with large scalar IN - Fixes #13049.
Signed-off-by: Aleksey Plekhanov <plehanov.alex@gmail.com>
1 parent 938452e commit 7a27bfe

2 files changed

Lines changed: 88 additions & 2 deletions

File tree

modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/util/RexUtils.java

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@
8080
import static java.util.Objects.requireNonNull;
8181
import static org.apache.calcite.rex.RexUtil.removeCast;
8282
import static org.apache.calcite.rex.RexUtil.sargRef;
83+
import static org.apache.calcite.sql.SqlKind.AND;
8384
import static org.apache.calcite.sql.SqlKind.EQUALS;
8485
import static org.apache.calcite.sql.SqlKind.GREATER_THAN;
8586
import static org.apache.calcite.sql.SqlKind.GREATER_THAN_OR_EQUAL;
@@ -97,6 +98,9 @@ public class RexUtils {
9798
/** Maximum amount of search bounds tuples per scan. */
9899
public static final int MAX_SEARCH_BOUNDS_COMPLEXITY = 100;
99100

101+
/** Operands limit per call when expanding SEARCH/SARG operator. */
102+
public static final int SEARCH_EXPAND_OPERANDS_LIMIT = 100;
103+
100104
/** */
101105
public static RexNode makeCast(RexBuilder builder, RexNode node, RelDataType type) {
102106
return TypeUtils.needCast(builder.getTypeFactory(), node.getType(), type) ? builder.makeCast(type, node) : node;
@@ -879,7 +883,7 @@ public static RexNode expandSearchNullable(RexBuilder rexBuilder, @Nullable RexP
879883
RexNode op1 = call.getOperands().get(1);
880884

881885
if (!op0.getType().isNullable())
882-
return RexUtil.expandSearch(rexBuilder, program, call);
886+
return expandSearch(rexBuilder, program, call);
883887

884888
while (op1 instanceof RexLocalRef) // Dereference local variable.
885889
op1 = requireNonNull(program, "program").getExprList().get(((RexSlot)op1).getIndex());
@@ -892,7 +896,7 @@ public static RexNode expandSearchNullable(RexBuilder rexBuilder, @Nullable RexP
892896
? rexBuilder.makeLiteral(arg.nullAs.toBoolean())
893897
: rexBuilder.makeNullLiteral(rexBuilder.getTypeFactory().createSqlType(SqlTypeName.BOOLEAN));
894898

895-
RexNode expandedSearch = RexUtil.expandSearch(rexBuilder, program,
899+
RexNode expandedSearch = expandSearch(rexBuilder, program,
896900
rexBuilder.makeCall(call.getOperator(), rexBuilder.makeNotNull(op0), op1));
897901

898902
return rexBuilder.makeCall(SqlStdOperatorTable.CASE,
@@ -902,6 +906,42 @@ public static RexNode expandSearchNullable(RexBuilder rexBuilder, @Nullable RexP
902906
);
903907
}
904908

909+
/**
910+
* Expand SEARCH/SARG with operands limit (to avoid too deep recursion on operands processing after compilation).
911+
*/
912+
private static RexNode expandSearch(RexBuilder rexBuilder, @Nullable RexProgram program, RexNode call) {
913+
RexNode expandedSearch = RexUtil.expandSearch(rexBuilder, program, call);
914+
915+
RexShuttle groupingShuttle = new RexShuttle() {
916+
@Override public RexNode visitCall(RexCall call) {
917+
if (call.getOperands().size() > SEARCH_EXPAND_OPERANDS_LIMIT
918+
&& (call.getOperator().getKind() == OR || call.getOperator().getKind() == AND)) {
919+
920+
List<RexNode> groupedOps = new ArrayList<>();
921+
List<RexNode> grpOps = new ArrayList<>(SEARCH_EXPAND_OPERANDS_LIMIT);
922+
923+
for (int i = 0; i < call.getOperands().size(); i++) {
924+
if (i > 0 && i % SEARCH_EXPAND_OPERANDS_LIMIT == 0) {
925+
groupedOps.add(rexBuilder.makeCall(call.getOperator(), grpOps));
926+
927+
grpOps = new ArrayList<>(SEARCH_EXPAND_OPERANDS_LIMIT);
928+
}
929+
930+
grpOps.add(call.getOperands().get(i));
931+
}
932+
933+
groupedOps.add(grpOps.size() == 1 ? grpOps.get(0) : rexBuilder.makeCall(call.getOperator(), grpOps));
934+
935+
return rexBuilder.makeCall(call.getOperator(), groupedOps);
936+
}
937+
938+
return super.visitCall(call);
939+
}
940+
};
941+
942+
return expandedSearch.accept(groupingShuttle);
943+
}
944+
905945
/**
906946
* Traverse {@code node} and expand all SEARCH/SARG operators (with preceding NULLs check).
907947
*

modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/RexSimplificationPlannerTest.java

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,24 @@
1717

1818
package org.apache.ignite.internal.processors.query.calcite.planner;
1919

20+
import java.util.function.Predicate;
21+
import java.util.stream.Collectors;
22+
import java.util.stream.IntStream;
2023
import org.apache.calcite.linq4j.tree.Types;
2124
import org.apache.calcite.rel.core.Join;
25+
import org.apache.calcite.rex.RexCall;
26+
import org.apache.calcite.rex.RexNode;
27+
import org.apache.calcite.rex.RexVisitor;
28+
import org.apache.calcite.rex.RexVisitorImpl;
2229
import org.apache.calcite.util.Util;
2330
import org.apache.ignite.internal.processors.query.calcite.exec.exp.IgniteScalarFunction;
31+
import org.apache.ignite.internal.processors.query.calcite.rel.IgniteTableScan;
2432
import org.apache.ignite.internal.processors.query.calcite.rel.IgniteValues;
2533
import org.apache.ignite.internal.processors.query.calcite.rel.ProjectableFilterableTableScan;
2634
import org.apache.ignite.internal.processors.query.calcite.schema.IgniteSchema;
2735
import org.apache.ignite.internal.processors.query.calcite.trait.IgniteDistributions;
36+
import org.apache.ignite.internal.processors.query.calcite.util.Commons;
37+
import org.apache.ignite.internal.processors.query.calcite.util.RexUtils;
2838
import org.junit.Test;
2939

3040
import static org.apache.calcite.sql.type.SqlTypeName.INTEGER;
@@ -240,6 +250,42 @@ public void testFunctionFilterReduction() throws Exception {
240250
.and(s -> "AND(=($t0, 0), =(ECHO(ECHO_ND(1)), 1))".equals(s.condition().toString())));
241251
}
242252

253+
/** */
254+
@Test
255+
public void testSearchOperandsGrouping() throws Exception {
256+
IgniteSchema publicSchema = createSchema(createTable("T", single(), "ID", INTEGER));
257+
258+
String in = IntStream.range(1, RexUtils.SEARCH_EXPAND_OPERANDS_LIMIT * 3)
259+
.mapToObj(Integer::toString).collect(Collectors.joining(", "));
260+
261+
assertPlan("SELECT * FROM t WHERE id IN (" + in + ")", publicSchema, isTableScan("T")
262+
.and(checkOperandsLimit()));
263+
264+
assertPlan("SELECT * FROM t WHERE id NOT IN (" + in + ")", publicSchema, isTableScan("T")
265+
.and(checkOperandsLimit()));
266+
}
267+
268+
/** */
269+
private Predicate<IgniteTableScan> checkOperandsLimit() {
270+
return n -> {
271+
RexNode expandedSearch = RexUtils.expandSearchNullableRecursive(Commons.emptyCluster().getRexBuilder(),
272+
null, n.condition());
273+
274+
RexVisitor<Void> operandsChecker = new RexVisitorImpl<>(true) {
275+
@Override public Void visitCall(RexCall call) {
276+
assertTrue("Unexpected operands count: " + call.getOperands().size(),
277+
call.getOperands().size() <= RexUtils.SEARCH_EXPAND_OPERANDS_LIMIT);
278+
279+
return super.visitCall(call);
280+
}
281+
};
282+
283+
expandedSearch.accept(operandsChecker);
284+
285+
return true;
286+
};
287+
}
288+
243289
/** */
244290
public static int echo(int val) {
245291
return val;

0 commit comments

Comments
 (0)