[improvement](planner) eliminating useless sort node (#14377)

This commit is contained in:
yinzhijian
2022-11-22 15:13:25 +08:00
committed by GitHub
parent b9f017ebb1
commit 663f7dddcc
3 changed files with 218 additions and 0 deletions

View File

@ -20,6 +20,7 @@
package org.apache.doris.analysis;
import org.apache.doris.analysis.CompoundPredicate.Operator;
import org.apache.doris.catalog.AggregateFunction;
import org.apache.doris.catalog.Column;
import org.apache.doris.catalog.DatabaseIf;
@ -36,6 +37,7 @@ import org.apache.doris.common.ColumnAliasGenerator;
import org.apache.doris.common.ErrorCode;
import org.apache.doris.common.ErrorReport;
import org.apache.doris.common.Pair;
import org.apache.doris.common.Reference;
import org.apache.doris.common.TableAliasGenerator;
import org.apache.doris.common.TreeNode;
import org.apache.doris.common.UserException;
@ -559,6 +561,7 @@ public class SelectStmt extends QueryStmt {
}
analyzeAggregation(analyzer);
createAnalyticInfo(analyzer);
eliminatingSortNode();
if (evaluateOrderBy) {
createSortTupleInfo(analyzer);
}
@ -1734,6 +1737,79 @@ public class SelectStmt extends QueryStmt {
return expr;
}
public void eliminatingSortNode() {
// initial sql: select * from t1 where k1 = 1 order by k1
// optimized sql: select * from t1 where k1 = 1
if (ConnectContext.get() == null || !ConnectContext.get().getSessionVariable().enableEliminateSortNode) {
return;
}
if (!evaluateOrderBy() || getSortInfo() == null || getWhereClause() == null) {
return;
}
List<SlotRef> sortSlots = new ArrayList<>();
// get source slot ref from order by clause
for (Expr expr : getSortInfo().getOrderingExprs()) {
SlotRef source = expr.getSrcSlotRef();
if (source == null) {
return;
}
sortSlots.add(source);
}
if (sortSlots.isEmpty()) {
return;
}
if (checkSortNodeEliminable(getWhereClause(), sortSlots) && sortSlots.isEmpty()) {
evaluateOrderBy = false;
}
}
private boolean checkSortNodeEliminable(Expr expr, List<SlotRef> sortSlotRefs) {
// 1. Check that the CompoundPredicates in the whereClause are all AndCompound
if (expr instanceof CompoundPredicate) {
if (((CompoundPredicate) expr).getOp() != Operator.AND) {
// fail to eliminate
return false;
}
}
// 2. Check that all sort slots have:
// 2.1 at least one BinaryPredicate expression equal to a constant
// 2.2 OR at least one InPredicate expression containing only one constant
// in the whereClause
if (expr instanceof BinaryPredicate) {
Reference<SlotRef> slotRefRef = new Reference<>();
BinaryPredicate binaryPredicate = (BinaryPredicate) expr;
if (binaryPredicate.isSingleColumnPredicate(slotRefRef, null)) {
if (binaryPredicate.getOp() != BinaryPredicate.Operator.EQ) {
// it's ok, try to check next expr
return true;
}
// remove it
sortSlotRefs.remove(slotRefRef.getRef());
}
} else if (expr instanceof InPredicate) {
if (((InPredicate) expr).isNotIn()) {
return true;
}
// there can only be two child nodes, one is a slotref and the other is a constant
if (expr.getChildren().size() != 2) {
// it's ok, try to check next expr
return true;
}
if (!expr.getChild(1).isConstant()) {
// it's ok, try to check next expr
return true;
}
// remove it
sortSlotRefs.remove(expr.getChild(0).unwrapSlotRef());
}
for (Expr child : expr.getChildren()) {
if (!checkSortNodeEliminable(child, sortSlotRefs)) {
return false;
}
}
return true;
}
@Override
public String toSql() {
if (sqlString != null) {

View File

@ -225,6 +225,8 @@ public class SessionVariable implements Serializable, Writable {
public static final String ENABLE_NEREIDS_STATS_DERIVE_V2 = "enable_nereids_stats_derive_v2";
public static final String ENABLE_ELIMINATE_SORT_NODE = "enable_eliminate_sort_node";
public static final String INTERNAL_SESSION = "internal_session";
// session origin value
@ -593,6 +595,9 @@ public class SessionVariable implements Serializable, Writable {
@VariableMgr.VarAttr(name = ENABLE_NEREIDS_STATS_DERIVE_V2)
public boolean enableNereidsStatsDeriveV2 = false;
@VariableMgr.VarAttr(name = ENABLE_ELIMINATE_SORT_NODE)
public boolean enableEliminateSortNode = true;
@VariableMgr.VarAttr(name = INTERNAL_SESSION)
public boolean internalSession = false;

View File

@ -526,4 +526,141 @@ public class PlannerTest extends TestWithFeService {
Assertions.assertFalse(plan2.contains("SORT INFO:"));
Assertions.assertFalse(plan2.contains("SORT LIMIT:"));
}
@Test
public void testEliminatingSortNode() throws Exception {
// fail case 1
{
String sql1 = "explain select k1 from db1.tbl1 where k1 = 1 order by k1, k2";
StmtExecutor stmtExecutor1 = new StmtExecutor(connectContext, sql1);
stmtExecutor1.execute();
Planner planner1 = stmtExecutor1.planner();
String plan1 = planner1.getExplainString(new ExplainOptions(false, false));
Assertions.assertTrue(plan1.contains("SORT INFO:\n `k1`\n `k2`"));
Assertions.assertTrue(plan1.contains("SORT LIMIT:"));
}
// fail case 2
{
String sql1 = "explain select k1 from db1.tbl1 where k1 = 1 and k3 = 2 order by k1, k2";
StmtExecutor stmtExecutor1 = new StmtExecutor(connectContext, sql1);
stmtExecutor1.execute();
Planner planner1 = stmtExecutor1.planner();
String plan1 = planner1.getExplainString(new ExplainOptions(false, false));
Assertions.assertTrue(plan1.contains("SORT INFO:\n `k1`\n `k2`"));
Assertions.assertTrue(plan1.contains("SORT LIMIT:"));
}
// fail case 3
{
String sql1 = "explain select k1 from db1.tbl1 where k1 = 1 and k2 != 2 order by k1, k2";
StmtExecutor stmtExecutor1 = new StmtExecutor(connectContext, sql1);
stmtExecutor1.execute();
Planner planner1 = stmtExecutor1.planner();
String plan1 = planner1.getExplainString(new ExplainOptions(false, false));
Assertions.assertTrue(plan1.contains("SORT INFO:\n `k1`\n `k2`"));
Assertions.assertTrue(plan1.contains("SORT LIMIT:"));
}
// fail case 4
{
String sql1 = "explain select k1 from db1.tbl1 where k1 = 1 or k2 = 2 order by k1, k2";
StmtExecutor stmtExecutor1 = new StmtExecutor(connectContext, sql1);
stmtExecutor1.execute();
Planner planner1 = stmtExecutor1.planner();
String plan1 = planner1.getExplainString(new ExplainOptions(false, false));
Assertions.assertTrue(plan1.contains("SORT INFO:\n `k1`\n `k2`"));
Assertions.assertTrue(plan1.contains("SORT LIMIT:"));
}
// fail case 5
{
String sql1 = "explain select k1 from db1.tbl1 where k1 = 1 and k2 = 2 or k3 = 3 order by k1, k2";
StmtExecutor stmtExecutor1 = new StmtExecutor(connectContext, sql1);
stmtExecutor1.execute();
Planner planner1 = stmtExecutor1.planner();
String plan1 = planner1.getExplainString(new ExplainOptions(false, false));
Assertions.assertTrue(plan1.contains("SORT INFO:\n `k1`\n `k2`"));
Assertions.assertTrue(plan1.contains("SORT LIMIT:"));
}
// fail case 6
// TODO, support: in (select 1)
{
String sql1 = "explain select k1 from db1.tbl1 where k1 in (select 1) and k2 = 2 order by k1, k2";
StmtExecutor stmtExecutor1 = new StmtExecutor(connectContext, sql1);
stmtExecutor1.execute();
Planner planner1 = stmtExecutor1.planner();
String plan1 = planner1.getExplainString(new ExplainOptions(false, false));
Assertions.assertTrue(plan1.contains("order by:"));
}
// fail case 7
{
String sql1 = "explain select k1 from db1.tbl1 where k1 not in (1) and k2 = 2 order by k1, k2";
StmtExecutor stmtExecutor1 = new StmtExecutor(connectContext, sql1);
stmtExecutor1.execute();
Planner planner1 = stmtExecutor1.planner();
String plan1 = planner1.getExplainString(new ExplainOptions(false, false));
Assertions.assertTrue(plan1.contains("order by:"));
}
// success case 1
{
String sql1 = "explain select k1 from db1.tbl1 where k1 = 1 and k2 = 2 order by k1, k2";
StmtExecutor stmtExecutor1 = new StmtExecutor(connectContext, sql1);
stmtExecutor1.execute();
Planner planner1 = stmtExecutor1.planner();
String plan1 = planner1.getExplainString(new ExplainOptions(false, false));
Assertions.assertFalse(plan1.contains("SORT INFO:\n `k1`\n `k2`"));
Assertions.assertFalse(plan1.contains("SORT LIMIT:"));
}
// success case 2
{
String sql1 = "explain select k1 from db1.tbl1 where k3 = 3 and k2 = 2 and k1 = 1 order by k1, k2";
StmtExecutor stmtExecutor1 = new StmtExecutor(connectContext, sql1);
stmtExecutor1.execute();
Planner planner1 = stmtExecutor1.planner();
String plan1 = planner1.getExplainString(new ExplainOptions(false, false));
Assertions.assertFalse(plan1.contains("SORT INFO:\n `k1`\n `k2`"));
Assertions.assertFalse(plan1.contains("SORT LIMIT:"));
}
// success case 3
{
String sql1 = "explain select k1 from db1.tbl1 where k1 in (1) and k2 in (2) and k2 !=2 order by k1, k2";
StmtExecutor stmtExecutor1 = new StmtExecutor(connectContext, sql1);
stmtExecutor1.execute();
Planner planner1 = stmtExecutor1.planner();
String plan1 = planner1.getExplainString(new ExplainOptions(false, false));
Assertions.assertFalse(plan1.contains("SORT INFO:\n `k1`\n `k2`"));
Assertions.assertFalse(plan1.contains("SORT LIMIT:"));
}
// success case 4
{
String sql1 = "explain select k1 from db1.tbl1 where k1 in (concat('1','2')) and k2 = 2 order by k1, k2";
StmtExecutor stmtExecutor1 = new StmtExecutor(connectContext, sql1);
stmtExecutor1.execute();
Planner planner1 = stmtExecutor1.planner();
String plan1 = planner1.getExplainString(new ExplainOptions(false, false));
Assertions.assertFalse(plan1.contains("SORT INFO:\n `k1`\n `k2`"));
Assertions.assertFalse(plan1.contains("SORT LIMIT:"));
}
// success case 5
{
String sql1 = "explain select tbl1.k1 from db1.tbl1 join db1.tbl2 on tbl1.k1 = tbl2.k1"
+ " where tbl1.k1 = 1 and tbl2.k1 = 2 and tbl1.k2 = 3 order by tbl1.k1, tbl2.k1";
StmtExecutor stmtExecutor1 = new StmtExecutor(connectContext, sql1);
stmtExecutor1.execute();
Planner planner1 = stmtExecutor1.planner();
String plan1 = planner1.getExplainString(new ExplainOptions(false, false));
Assertions.assertFalse(plan1.contains("SORT INFO:"));
Assertions.assertFalse(plan1.contains("SORT LIMIT:"));
}
}
}