[Enhancement] Optimize parsing predicates with large number of CompoundPredicates (backport #63139) (#63234)
Signed-off-by: shuming.li <ming.moriarty@gmail.com> Co-authored-by: shuming.li <ming.moriarty@gmail.com>
This commit is contained in:
parent
b464c2e4a5
commit
7a765d9f1a
|
|
@ -61,6 +61,7 @@ public class CompoundPredicate extends Predicate {
|
|||
if (e2 != null) {
|
||||
children.add(e2);
|
||||
}
|
||||
incrDepth();
|
||||
}
|
||||
|
||||
protected CompoundPredicate(CompoundPredicate other) {
|
||||
|
|
|
|||
|
|
@ -204,6 +204,9 @@ public abstract class Expr extends TreeNode<Expr> implements ParseNode, Cloneabl
|
|||
// passed to BE storage engine
|
||||
private boolean isIndexOnlyFilter = false;
|
||||
|
||||
// depth is used to indicate the depth of the same operator in the tree
|
||||
protected int depth = 0;
|
||||
|
||||
protected Expr() {
|
||||
pos = NodePosition.ZERO;
|
||||
type = Type.INVALID;
|
||||
|
|
@ -240,6 +243,7 @@ public abstract class Expr extends TreeNode<Expr> implements ParseNode, Cloneabl
|
|||
printSqlInParens = other.printSqlInParens;
|
||||
children = Expr.cloneList(other.children);
|
||||
hints = Lists.newArrayList(hints);
|
||||
depth = other.depth;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
|
|
@ -272,6 +276,15 @@ public abstract class Expr extends TreeNode<Expr> implements ParseNode, Cloneabl
|
|||
return originType;
|
||||
}
|
||||
|
||||
public int getDepth() {
|
||||
return depth;
|
||||
}
|
||||
|
||||
public void incrDepth() {
|
||||
int curDepth = children.stream().mapToInt(Expr::getDepth).max().orElse(0);
|
||||
this.depth = curDepth + 1;
|
||||
}
|
||||
|
||||
private Optional<Expr> replaceLargeStringLiteralImpl() {
|
||||
if (this instanceof LargeStringLiteral) {
|
||||
return Optional.of(new StringLiteral(((LargeStringLiteral) this).getValue()));
|
||||
|
|
|
|||
|
|
@ -3879,4 +3879,8 @@ public class Config extends ConfigBase {
|
|||
@ConfField(comment = "Enable case-insensitive catalog/database/table names. " +
|
||||
"Only configurable during cluster initialization, immutable once set.")
|
||||
public static boolean enable_table_name_case_insensitive = false;
|
||||
|
||||
@ConfField(mutable = true, comment = "The threshold to flatten compound predicate from deep tree to a balanced tree to " +
|
||||
"avoid stack over flow")
|
||||
public static int compound_predicate_flatten_threshold = 512;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -524,6 +524,7 @@ import com.starrocks.sql.ast.warehouse.cngroup.CreateCnGroupStmt;
|
|||
import com.starrocks.sql.ast.warehouse.cngroup.DropCnGroupStmt;
|
||||
import com.starrocks.sql.ast.warehouse.cngroup.EnableDisableCnGroupStmt;
|
||||
import com.starrocks.sql.common.PListCell;
|
||||
import com.starrocks.sql.parser.rewriter.CompoundPredicateExprRewriter;
|
||||
import com.starrocks.sql.util.EitherOr;
|
||||
import com.starrocks.statistic.StatsConstants;
|
||||
import com.starrocks.transaction.GtidGenerator;
|
||||
|
|
@ -548,6 +549,7 @@ import java.time.format.DateTimeFormatter;
|
|||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Deque;
|
||||
import java.util.HashMap;
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.Iterator;
|
||||
|
|
@ -597,6 +599,8 @@ public class AstBuilder extends StarRocksBaseVisitor<ParseNode> {
|
|||
Lists.newArrayList(FunctionSet.SUBSTR, FunctionSet.SUBSTRING,
|
||||
FunctionSet.FROM_UNIXTIME, FunctionSet.FROM_UNIXTIME_MS,
|
||||
FunctionSet.STR2DATE);
|
||||
// rewriter
|
||||
private static final CompoundPredicateExprRewriter COMPOUND_PREDICATE_EXPR_REWRITER = new CompoundPredicateExprRewriter();
|
||||
|
||||
protected AstBuilder(long sqlMode) {
|
||||
this(sqlMode, new IdentityHashMap<>());
|
||||
|
|
@ -7050,11 +7054,49 @@ public class AstBuilder extends StarRocksBaseVisitor<ParseNode> {
|
|||
null, createPos(context));
|
||||
}
|
||||
|
||||
private record LogicalBinaryNode(StarRocksParser.LogicalBinaryContext context,
|
||||
CompoundPredicate.Operator operator) {}
|
||||
|
||||
// Iteratively build a left-deep CompoundPredicate tree for LogicalBinaryContext,
|
||||
// allowing each node to have its own operator, using LogicalBinaryNode for clarity.
|
||||
// Corrected: Properly builds left-deep tree by pushing all contexts and operators,
|
||||
// and reconstructing from the bottom up, preserving associativity.
|
||||
private CompoundPredicate buildCompoundPredicateIterative(StarRocksParser.LogicalBinaryContext context) {
|
||||
// Stack to store all contexts and their operators from leftmost to root
|
||||
Deque<LogicalBinaryNode> nodeStack = new java.util.ArrayDeque<>();
|
||||
StarRocksParser.LogicalBinaryContext current = context;
|
||||
|
||||
// Traverse all the way down the left chain, pushing each context and operator
|
||||
while (true) {
|
||||
nodeStack.push(new LogicalBinaryNode(current, getLogicalBinaryOperator(current.operator)));
|
||||
if (current.left instanceof StarRocksParser.LogicalBinaryContext) {
|
||||
current = (StarRocksParser.LogicalBinaryContext) current.left;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// The leftmost leaf expression
|
||||
Expr result = (Expr) visit(current.left);
|
||||
// Rebuild the tree from the bottom up (leftmost to root)
|
||||
while (!nodeStack.isEmpty()) {
|
||||
LogicalBinaryNode node = nodeStack.pop();
|
||||
Expr right = (Expr) visit(node.context.right);
|
||||
result = new CompoundPredicate(node.operator(), result, right, createPos(node.context()));
|
||||
}
|
||||
return (CompoundPredicate) result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ParseNode visitLogicalBinary(StarRocksParser.LogicalBinaryContext context) {
|
||||
Expr left = (Expr) visit(context.left);
|
||||
Expr right = (Expr) visit(context.right);
|
||||
return new CompoundPredicate(getLogicalBinaryOperator(context.operator), left, right, createPos(context));
|
||||
if (Config.compound_predicate_flatten_threshold > 0) {
|
||||
CompoundPredicate result = buildCompoundPredicateIterative(context);
|
||||
return COMPOUND_PREDICATE_EXPR_REWRITER.rewrite(result);
|
||||
} else {
|
||||
Expr left = (Expr) visit(context.left);
|
||||
Expr right = (Expr) visit(context.right);
|
||||
return new CompoundPredicate(getLogicalBinaryOperator(context.operator), left, right, createPos(context));
|
||||
}
|
||||
}
|
||||
|
||||
private static CompoundPredicate.Operator getLogicalBinaryOperator(Token token) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,104 @@
|
|||
// Copyright 2021-present StarRocks, Inc. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package com.starrocks.sql.parser.rewriter;
|
||||
|
||||
import com.starrocks.analysis.CompoundPredicate;
|
||||
import com.starrocks.analysis.Expr;
|
||||
import com.starrocks.common.Config;
|
||||
import com.starrocks.sql.parser.NodePosition;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Deque;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Optimizes CompoundPredicate expressions by flattening and balancing long chains of
|
||||
* AND/OR predicates into a balanced binary tree. This transformation helps avoid
|
||||
* deep recursion and potential stack overflow for queries with very long AND/OR chains.
|
||||
* The optimization is only applied if the predicate tree exceeds a certain depth threshold.
|
||||
*
|
||||
* Additionally, optimizes OR predicates where all children are equality predicates
|
||||
* on the same column into an IN predicate for better performance.
|
||||
*/
|
||||
public class CompoundPredicateExprRewriter {
|
||||
/**
|
||||
* Rewrites a CompoundPredicate expression into a balanced binary tree if it is a long
|
||||
* chain of AND/OR predicates of the same operator and exceeds the flatten threshold.
|
||||
* Also rewrites OR chains of equality predicates on the same column into an IN predicate.
|
||||
* @param input the input expression
|
||||
* @return the rewritten (possibly balanced or IN-optimized) expression
|
||||
*/
|
||||
public Expr rewrite(Expr input) {
|
||||
if (Config.compound_predicate_flatten_threshold <= 0 ||
|
||||
input == null || !(input instanceof CompoundPredicate)) {
|
||||
return input;
|
||||
}
|
||||
CompoundPredicate compoundPredicate = (CompoundPredicate) input;
|
||||
if (compoundPredicate.getDepth() < Config.compound_predicate_flatten_threshold) {
|
||||
return input;
|
||||
}
|
||||
CompoundPredicate.Operator op = compoundPredicate.getOp();
|
||||
List<Expr> operands = new ArrayList<>();
|
||||
for (Expr child : compoundPredicate.getChildren()) {
|
||||
// Flatten left and right subtrees if they are the same operator
|
||||
if (child instanceof CompoundPredicate && ((CompoundPredicate) child).getOp() == op) {
|
||||
operands.addAll(flattenOperands(child, op));
|
||||
} else {
|
||||
Expr rewrittenLeft = rewrite(child);
|
||||
operands.add(rewrittenLeft);
|
||||
}
|
||||
}
|
||||
// TODO: Optimize OR predicates of the form: col = v1 OR col = v2 OR ... => col IN (v1, v2, ...)
|
||||
return buildBalanced(operands, op, 0, operands.size() - 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively builds a balanced binary tree from a list of operands.
|
||||
*/
|
||||
private Expr buildBalanced(List<Expr> ops, CompoundPredicate.Operator op, int l, int r) {
|
||||
if (l == r) {
|
||||
return ops.get(l);
|
||||
}
|
||||
int m = (l + r) >>> 1;
|
||||
Expr leftExpr = buildBalanced(ops, op, l, m);
|
||||
Expr rightExpr = buildBalanced(ops, op, m + 1, r);
|
||||
return new CompoundPredicate(op, leftExpr, rightExpr, NodePosition.ZERO);
|
||||
}
|
||||
|
||||
/**
|
||||
* Flattens a left- or right-deep tree of CompoundPredicates with the same operator
|
||||
* into a list of operands.
|
||||
*/
|
||||
private List<Expr> flattenOperands(Expr expr, CompoundPredicate.Operator op) {
|
||||
List<Expr> operands = new ArrayList<>();
|
||||
Deque<Expr> stack = new ArrayDeque<>();
|
||||
stack.push(expr);
|
||||
while (!stack.isEmpty()) {
|
||||
Expr current = stack.pop();
|
||||
if (current instanceof CompoundPredicate) {
|
||||
CompoundPredicate cp = (CompoundPredicate) current;
|
||||
if (cp.getOp() == op) {
|
||||
for (Expr child : cp.getChildren()) {
|
||||
stack.push(child);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
operands.add(current);
|
||||
}
|
||||
return operands;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,267 @@
|
|||
// Copyright 2021-present StarRocks, Inc. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package com.starrocks.sql.parser.rewriter;
|
||||
|
||||
import com.starrocks.analysis.BinaryPredicate;
|
||||
import com.starrocks.analysis.BinaryType;
|
||||
import com.starrocks.analysis.BoolLiteral;
|
||||
import com.starrocks.analysis.CompoundPredicate;
|
||||
import com.starrocks.analysis.Expr;
|
||||
import com.starrocks.analysis.IntLiteral;
|
||||
import com.starrocks.analysis.SlotRef;
|
||||
import com.starrocks.analysis.StringLiteral;
|
||||
import com.starrocks.analysis.TableName;
|
||||
import com.starrocks.common.Config;
|
||||
import com.starrocks.utframe.StarRocksTestBase;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertSame;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
public class CompoundPredicateExprRewriterTest extends StarRocksTestBase {
|
||||
|
||||
private CompoundPredicateExprRewriter rewriter;
|
||||
private int originalThreshold;
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
rewriter = new CompoundPredicateExprRewriter();
|
||||
// Save original threshold and set a lower value for testing
|
||||
originalThreshold = Config.compound_predicate_flatten_threshold;
|
||||
Config.compound_predicate_flatten_threshold = 3;
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void tearDown() {
|
||||
// Restore original threshold
|
||||
Config.compound_predicate_flatten_threshold = originalThreshold;
|
||||
}
|
||||
|
||||
// Helper methods to create test expressions
|
||||
private SlotRef createSlotRef(String columnName) {
|
||||
return new SlotRef(new TableName("test_db", "test_table"), columnName);
|
||||
}
|
||||
|
||||
private IntLiteral createIntLiteral(long value) {
|
||||
return new IntLiteral(value);
|
||||
}
|
||||
|
||||
private StringLiteral createStringLiteral(String value) {
|
||||
return new StringLiteral(value);
|
||||
}
|
||||
|
||||
private BoolLiteral createBoolLiteral(boolean value) {
|
||||
return new BoolLiteral(value);
|
||||
}
|
||||
|
||||
private BinaryPredicate createBinaryPredicate(Expr left, BinaryType op, Expr right) {
|
||||
return new BinaryPredicate(op, left, right);
|
||||
}
|
||||
|
||||
private CompoundPredicate createCompoundPredicate(CompoundPredicate.Operator op, Expr left, Expr right) {
|
||||
return new CompoundPredicate(op, left, right);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that null input returns null
|
||||
*/
|
||||
@Test
|
||||
public void testRewriteWithNullInput() {
|
||||
Expr result = rewriter.rewrite(null);
|
||||
assertNull(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that non-CompoundPredicate input returns unchanged
|
||||
*/
|
||||
@Test
|
||||
public void testRewriteWithNonCompoundPredicate() {
|
||||
SlotRef slotRef = createSlotRef("col1");
|
||||
Expr result = rewriter.rewrite(slotRef);
|
||||
assertSame(slotRef, result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that compound predicate below threshold returns unchanged
|
||||
*/
|
||||
@Test
|
||||
public void testRewriteWithShallowPredicate() {
|
||||
SlotRef col1 = createSlotRef("col1");
|
||||
IntLiteral val1 = createIntLiteral(1);
|
||||
IntLiteral val2 = createIntLiteral(2);
|
||||
|
||||
BinaryPredicate pred1 = createBinaryPredicate(col1, BinaryType.EQ, val1);
|
||||
BinaryPredicate pred2 = createBinaryPredicate(col1, BinaryType.EQ, val2);
|
||||
CompoundPredicate compound = createCompoundPredicate(CompoundPredicate.Operator.AND, pred1, pred2);
|
||||
|
||||
Expr result = rewriter.rewrite(compound);
|
||||
assertSame(compound, result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that compound predicate with threshold disabled (0 or negative) returns unchanged
|
||||
*/
|
||||
@Test
|
||||
public void testRewriteWithDisabledThreshold() {
|
||||
Config.compound_predicate_flatten_threshold = 0;
|
||||
|
||||
SlotRef col1 = createSlotRef("col1");
|
||||
IntLiteral val1 = createIntLiteral(1);
|
||||
IntLiteral val2 = createIntLiteral(2);
|
||||
IntLiteral val3 = createIntLiteral(3);
|
||||
IntLiteral val4 = createIntLiteral(4);
|
||||
|
||||
BinaryPredicate pred1 = createBinaryPredicate(col1, BinaryType.EQ, val1);
|
||||
BinaryPredicate pred2 = createBinaryPredicate(col1, BinaryType.EQ, val2);
|
||||
BinaryPredicate pred3 = createBinaryPredicate(col1, BinaryType.EQ, val3);
|
||||
BinaryPredicate pred4 = createBinaryPredicate(col1, BinaryType.EQ, val4);
|
||||
|
||||
CompoundPredicate compound = createCompoundPredicate(CompoundPredicate.Operator.AND,
|
||||
createCompoundPredicate(CompoundPredicate.Operator.AND, pred1, pred2),
|
||||
createCompoundPredicate(CompoundPredicate.Operator.AND, pred3, pred4));
|
||||
|
||||
Expr result = rewriter.rewrite(compound);
|
||||
assertSame(compound, result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that OR conversion fails when operators are not equality
|
||||
*/
|
||||
@Test
|
||||
public void testOrToInPredicateConversion_NonEqualityOperators() {
|
||||
SlotRef col1 = createSlotRef("col1");
|
||||
IntLiteral val1 = createIntLiteral(1);
|
||||
IntLiteral val2 = createIntLiteral(2);
|
||||
|
||||
BinaryPredicate pred1 = createBinaryPredicate(col1, BinaryType.LT, val1);
|
||||
BinaryPredicate pred2 = createBinaryPredicate(col1, BinaryType.GT, val2);
|
||||
|
||||
CompoundPredicate compound = createCompoundPredicate(CompoundPredicate.Operator.OR, pred1, pred2);
|
||||
|
||||
Expr result = rewriter.rewrite(compound);
|
||||
|
||||
// Should not convert to IN predicate, should return balanced tree
|
||||
assertTrue(result instanceof CompoundPredicate);
|
||||
CompoundPredicate resultCompound = (CompoundPredicate) result;
|
||||
assertEquals(CompoundPredicate.Operator.OR, resultCompound.getOp());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that OR conversion fails when operands are not BinaryPredicates
|
||||
*/
|
||||
@Test
|
||||
public void testOrToInPredicateConversion_NonBinaryPredicates() {
|
||||
SlotRef col1 = createSlotRef("col1");
|
||||
IntLiteral val1 = createIntLiteral(1);
|
||||
|
||||
BinaryPredicate pred1 = createBinaryPredicate(col1, BinaryType.EQ, val1);
|
||||
CompoundPredicate nestedOr = createCompoundPredicate(CompoundPredicate.Operator.OR,
|
||||
createBinaryPredicate(col1, BinaryType.EQ, val1),
|
||||
createBinaryPredicate(col1, BinaryType.EQ, val1));
|
||||
|
||||
CompoundPredicate compound = createCompoundPredicate(CompoundPredicate.Operator.OR, pred1, nestedOr);
|
||||
|
||||
Expr result = rewriter.rewrite(compound);
|
||||
|
||||
// Should not convert to IN predicate, should return balanced tree
|
||||
assertTrue(result instanceof CompoundPredicate);
|
||||
CompoundPredicate resultCompound = (CompoundPredicate) result;
|
||||
assertEquals(CompoundPredicate.Operator.OR, resultCompound.getOp());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test balanced tree building for AND predicates
|
||||
*/
|
||||
@Test
|
||||
public void testBalancedTreeBuilding_AndPredicates() {
|
||||
SlotRef col1 = createSlotRef("col1");
|
||||
IntLiteral val1 = createIntLiteral(1);
|
||||
IntLiteral val2 = createIntLiteral(2);
|
||||
IntLiteral val3 = createIntLiteral(3);
|
||||
IntLiteral val4 = createIntLiteral(4);
|
||||
|
||||
BinaryPredicate pred1 = createBinaryPredicate(col1, BinaryType.EQ, val1);
|
||||
BinaryPredicate pred2 = createBinaryPredicate(col1, BinaryType.EQ, val2);
|
||||
BinaryPredicate pred3 = createBinaryPredicate(col1, BinaryType.EQ, val3);
|
||||
BinaryPredicate pred4 = createBinaryPredicate(col1, BinaryType.EQ, val4);
|
||||
|
||||
// Create a deep AND chain that exceeds threshold
|
||||
CompoundPredicate compound = createCompoundPredicate(CompoundPredicate.Operator.AND,
|
||||
createCompoundPredicate(CompoundPredicate.Operator.AND, pred1, pred2),
|
||||
createCompoundPredicate(CompoundPredicate.Operator.AND, pred3, pred4));
|
||||
|
||||
Expr result = rewriter.rewrite(compound);
|
||||
|
||||
assertTrue(result instanceof CompoundPredicate);
|
||||
CompoundPredicate resultCompound = (CompoundPredicate) result;
|
||||
assertEquals(CompoundPredicate.Operator.AND, resultCompound.getOp());
|
||||
|
||||
// Verify the tree is balanced by checking depth
|
||||
assertTrue(resultCompound.getDepth() <= 3); // Should be balanced
|
||||
}
|
||||
|
||||
/**
|
||||
* Test balanced tree building for OR predicates (when IN conversion fails)
|
||||
*/
|
||||
@Test
|
||||
public void testBalancedTreeBuilding_OrPredicates() {
|
||||
SlotRef col1 = createSlotRef("col1");
|
||||
SlotRef col2 = createSlotRef("col2");
|
||||
IntLiteral val1 = createIntLiteral(1);
|
||||
IntLiteral val2 = createIntLiteral(2);
|
||||
IntLiteral val3 = createIntLiteral(3);
|
||||
IntLiteral val4 = createIntLiteral(4);
|
||||
|
||||
BinaryPredicate pred1 = createBinaryPredicate(col1, BinaryType.EQ, val1);
|
||||
BinaryPredicate pred2 = createBinaryPredicate(col2, BinaryType.EQ, val2);
|
||||
BinaryPredicate pred3 = createBinaryPredicate(col1, BinaryType.EQ, val3);
|
||||
BinaryPredicate pred4 = createBinaryPredicate(col2, BinaryType.EQ, val4);
|
||||
|
||||
// Create a deep OR chain that exceeds threshold but can't be converted to IN
|
||||
CompoundPredicate compound = createCompoundPredicate(CompoundPredicate.Operator.OR,
|
||||
createCompoundPredicate(CompoundPredicate.Operator.OR, pred1, pred2),
|
||||
createCompoundPredicate(CompoundPredicate.Operator.OR, pred3, pred4));
|
||||
|
||||
Expr result = rewriter.rewrite(compound);
|
||||
|
||||
assertTrue(result instanceof CompoundPredicate);
|
||||
CompoundPredicate resultCompound = (CompoundPredicate) result;
|
||||
assertEquals(CompoundPredicate.Operator.OR, resultCompound.getOp());
|
||||
|
||||
// Verify the tree is balanced by checking depth
|
||||
assertTrue(resultCompound.getDepth() <= 3); // Should be balanced
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that NOT operator is not processed for balancing
|
||||
*/
|
||||
@Test
|
||||
public void testNotOperatorNotProcessed() {
|
||||
SlotRef col1 = createSlotRef("col1");
|
||||
IntLiteral val1 = createIntLiteral(1);
|
||||
BinaryPredicate pred1 = createBinaryPredicate(col1, BinaryType.EQ, val1);
|
||||
|
||||
CompoundPredicate notPredicate = createCompoundPredicate(CompoundPredicate.Operator.NOT, pred1, null);
|
||||
|
||||
Expr result = rewriter.rewrite(notPredicate);
|
||||
|
||||
// NOT predicates should not be processed for balancing
|
||||
assertSame(notPredicate, result);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,89 @@
|
|||
// Copyright 2021-present StarRocks, Inc. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package com.starrocks.sql.plan;
|
||||
|
||||
import com.starrocks.common.FeConstants;
|
||||
import com.starrocks.sql.optimizer.rule.transformation.materialization.MVTestBase;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class InvalidPlanTest extends MVTestBase {
|
||||
@BeforeAll
|
||||
public static void beforeAll() throws Exception {
|
||||
MVTestBase.beforeClass();
|
||||
FeConstants.unitTestView = false;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWithLargeBinaryPredicates() throws Exception {
|
||||
starRocksAssert.withTable("CREATE TABLE `test_table` (\n" +
|
||||
" `id` bigint(20) NOT NULL,\n" +
|
||||
" `cargo_code` bigint(20) NOT NULL,\n" +
|
||||
" `cargo_upc` varchar(100) NULL,\n" +
|
||||
" `cargo_name` varchar(256) NULL,\n" +
|
||||
" `item1_desc` varchar(256) NULL,\n" +
|
||||
" `upc_nbr` varchar(64) NULL,\n" +
|
||||
" `vendor_nbr` varchar(64) NULL,\n" +
|
||||
" `division` varchar(64) NULL,\n" +
|
||||
" `sub_division` varchar(64) NULL,\n" +
|
||||
" `sub_division_en` varchar(200) NULL,\n" +
|
||||
" `category` varchar(200) NULL,\n" +
|
||||
" `category_cn` varchar(200) NULL,\n" +
|
||||
" `category_en` varchar(200) NULL,\n" +
|
||||
" `sub_category` varchar(200) NULL,\n" +
|
||||
" `sub_category_cn` varchar(200) NULL,\n" +
|
||||
" `sub_category_en` varchar(200) NULL,\n" +
|
||||
" `node_code` varchar(200) NOT NULL,\n" +
|
||||
" `node_type` varchar(200) NULL,\n" +
|
||||
" `storage_area` varchar(256) NULL,\n" +
|
||||
" `fragile_flag` int(11) NULL,\n" +
|
||||
" `storehouse_sell_date` int(11) NULL,\n" +
|
||||
" `custom_box_gauge` int(11) NULL,\n" +
|
||||
" `minimum_order_quantity` int(11) NULL,\n" +
|
||||
" `activation_status` int(11) NULL,\n" +
|
||||
" `day_sold_out_flag` int(11) NULL,\n" +
|
||||
" `new_category_flag` int(11) NULL,\n" +
|
||||
" `updated_time` datetime NULL,\n" +
|
||||
" `calc_node_code_cargo_code` varchar(200) NULL,\n" +
|
||||
" `sell_by_date` int(11) NULL,\n" +
|
||||
" `dc_storage_conditions_code` varchar(200) NULL,\n" +
|
||||
" `dc_storage_conditions` varchar(256) NULL,\n" +
|
||||
" `deleted_flag` int(11) NULL,\n" +
|
||||
" `specification` varchar(255) NULL,\n" +
|
||||
" `lead_time_day` int(11) NULL,\n" +
|
||||
" `lead_time_hour` int(11) NULL,\n" +
|
||||
" `next_status` int(11) NULL,\n" +
|
||||
" `next_switch_time` datetime NULL,\n" +
|
||||
" `cloud_status` int(11) NULL,\n" +
|
||||
" `latest_activate_date` datetime NULL,\n" +
|
||||
" `long_desc` varchar(256) NULL,\n" +
|
||||
" `activate_effect_time` datetime NULL,\n" +
|
||||
" `inactivate_effect_time` datetime NULL,\n" +
|
||||
" `ti` int(11) NULL,\n" +
|
||||
" `hi` int(11) NULL\n" +
|
||||
") ENGINE=OLAP \n" +
|
||||
"PRIMARY KEY(`id`)\n" +
|
||||
"DISTRIBUTED BY HASH(`id`) BUCKETS 20 \n" +
|
||||
"PROPERTIES (\n" +
|
||||
"\"replication_num\" = \"1\"\n" +
|
||||
");");
|
||||
String sql = ReplayFromDumpTestBase.geContentFromFile("bugs/large_binary_predicate1.sql");
|
||||
String plan = getFragmentPlan(sql);
|
||||
PlanTestBase.assertContains(plan, " 0:OlapScanNode\n" +
|
||||
" TABLE: test_table\n" +
|
||||
" PREAGGREGATION: ON\n" +
|
||||
" PREDICATES: 32: deleted_flag = 0, 2: cargo_code IN ");
|
||||
}
|
||||
}
|
||||
|
|
@ -147,8 +147,13 @@ public class ReplayFromDumpTestBase {
|
|||
}
|
||||
|
||||
protected static String getDumpInfoFromFile(String fileName) throws Exception {
|
||||
String completeFileName = fileName + ".json";
|
||||
return geContentFromFile(completeFileName);
|
||||
}
|
||||
|
||||
public static String geContentFromFile(String completeFileName) throws Exception {
|
||||
String path = Objects.requireNonNull(ClassLoader.getSystemClassLoader().getResource("sql")).getPath();
|
||||
File file = new File(path + "/" + fileName + ".json");
|
||||
File file = new File(path + "/" + completeFileName);
|
||||
StringBuilder sb = new StringBuilder();
|
||||
String tempStr;
|
||||
try (BufferedReader reader = new BufferedReader(new FileReader(file))) {
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue