Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

planner: change more conditions that are always false to dual | tidb-test=pr/2466 #59199

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pkg/executor/prepared_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ func TestPreparedNullParam(t *testing.T) {
ps := []*util.ProcessInfo{tkProcess}
tk.Session().SetSessionManager(&testkit.MockSessionManager{PS: ps})
tk.MustQuery(fmt.Sprintf("explain for connection %d", tkProcess.ID)).Check(testkit.Rows(
"TableDual_5 0.00 root rows:0"))
"TableDual_6 0.00 root rows:0"))
}
}

Expand Down
13 changes: 13 additions & 0 deletions pkg/expression/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -2162,3 +2162,16 @@ func binaryDurationWithMS(pos int, paramValues []byte,
pos += 4
return pos, fmt.Sprintf("%s.%06d", dur, microSecond)
}

// IsConstFalse is used to check whether the expression is a constant false expression.
func IsConstFalse(expr Expression) bool {
if e, ok := expr.(*ScalarFunction); ok {
switch e.FuncName.L {
case ast.LT, ast.LE, ast.GT, ast.GE, ast.EQ, ast.NE:
if constExpr, ok := e.GetArgs()[1].(*Constant); ok && constExpr.Value.IsNull() && constExpr.DeferredExpr == nil {
return true
}
}
}
return false
}
3 changes: 2 additions & 1 deletion pkg/planner/core/casetest/rule/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ go_test(
name = "rule_test",
timeout = "short",
srcs = [
"dual_test.go",
"main_test.go",
"rule_derive_topn_from_window_test.go",
"rule_inject_extra_projection_test.go",
Expand All @@ -13,7 +14,7 @@ go_test(
],
data = glob(["testdata/**"]),
flaky = True,
shard_count = 8,
shard_count = 9,
deps = [
"//pkg/domain",
"//pkg/expression",
Expand Down
47 changes: 47 additions & 0 deletions pkg/planner/core/casetest/rule/dual_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Copyright 2023 PingCAP, Inc.
//
// 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
//
// http://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 rule

import (
"testing"

"github.com/pingcap/tidb/pkg/testkit"
)

func TestDual(t *testing.T) {
store := testkit.CreateMockStore(t)
tk := testkit.NewTestKit(t, store)
tk.MustExec("use test")
tk.MustExec("CREATE TABLE t (id INT PRIMARY KEY AUTO_INCREMENT,d INT);")
tk.MustQuery("explain select a from (select d as a from t where d = 0) k where k.a = 5").Check(testkit.Rows(
"TableDual_8 0.00 root rows:0"))
tk.MustQuery("select a from (select d as a from t where d = 0) k where k.a = 5").Check(testkit.Rows())
tk.MustQuery("explain select a from (select 1+2 as a from t where d = 0) k where k.a = 5").Check(testkit.Rows(
"Projection_8 0.00 root 3->Column#3",
"└─TableDual_9 0.00 root rows:0"))
tk.MustQuery("select a from (select 1+2 as a from t where d = 0) k where k.a = 5").Check(testkit.Rows())
tk.MustQuery("explain select * from t where d != null;").Check(testkit.Rows(
"TableDual_6 0.00 root rows:0"))
tk.MustQuery("explain select * from t where d > null;").Check(testkit.Rows(
"TableDual_6 0.00 root rows:0"))
tk.MustQuery("explain select * from t where d >= null;").Check(testkit.Rows(
"TableDual_6 0.00 root rows:0"))
tk.MustQuery("explain select * from t where d < null;").Check(testkit.Rows(
"TableDual_6 0.00 root rows:0"))
tk.MustQuery("explain select * from t where d <= null;").Check(testkit.Rows(
"TableDual_6 0.00 root rows:0"))
tk.MustQuery("explain select * from t where d = null;").Check(testkit.Rows(
"TableDual_6 0.00 root rows:0"))
}
17 changes: 2 additions & 15 deletions pkg/planner/core/casetest/rule/testdata/outer2inner_out.json
Original file line number Diff line number Diff line change
Expand Up @@ -161,14 +161,7 @@
{
"SQL": "select * from t3 as t1 left join t3 as t2 on t1.c3 = t2.c3 where t2.b3 != NULL; -- self join",
"Plan": [
"Projection 0.00 root test.t3.a3, test.t3.b3, test.t3.c3, test.t3.a3, test.t3.b3, test.t3.c3",
"└─HashJoin 0.00 root inner join, equal:[eq(test.t3.c3, test.t3.c3)]",
" ├─TableReader(Build) 0.00 root data:Selection",
" │ └─Selection 0.00 cop[tikv] ne(test.t3.b3, NULL), not(isnull(test.t3.c3))",
" │ └─TableFullScan 10000.00 cop[tikv] table:t2 keep order:false, stats:pseudo",
" └─TableReader(Probe) 9990.00 root data:Selection",
" └─Selection 9990.00 cop[tikv] not(isnull(test.t3.c3))",
" └─TableFullScan 10000.00 cop[tikv] table:t1 keep order:false, stats:pseudo"
"TableDual 0.00 root rows:0"
]
},
{
Expand Down Expand Up @@ -434,13 +427,7 @@
{
"SQL": "select * from t3 as t1 left join t3 as t2 on t1.c3 = t2.c3 where t1.b3 != NULL -- negative case with self join",
"Plan": [
"HashJoin 0.00 root left outer join, left side:TableReader, equal:[eq(test.t3.c3, test.t3.c3)]",
"├─TableReader(Build) 0.00 root data:Selection",
"│ └─Selection 0.00 cop[tikv] ne(test.t3.b3, NULL)",
"│ └─TableFullScan 10000.00 cop[tikv] table:t1 keep order:false, stats:pseudo",
"└─TableReader(Probe) 9990.00 root data:Selection",
" └─Selection 9990.00 cop[tikv] not(isnull(test.t3.c3))",
" └─TableFullScan 10000.00 cop[tikv] table:t2 keep order:false, stats:pseudo"
"TableDual 0.00 root rows:0"
]
},
{
Expand Down
1 change: 1 addition & 0 deletions pkg/planner/core/issuetest/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ func TestMain(m *testing.M) {
testsetup.SetupForCommonTest()
flag.Parse()
opts := []goleak.Option{
goleak.IgnoreTopFunction("go.opencensus.io/stats/view.(*worker).start"),
goleak.IgnoreTopFunction("github.com/golang/glog.(*fileSink).flushDaemon"),
goleak.IgnoreTopFunction("github.com/bazelbuild/rules_go/go/tools/bzltestutil.RegisterTimeoutHandler.func1"),
goleak.IgnoreTopFunction("github.com/lestrrat-go/httprc.runFetchWorker"),
Expand Down
2 changes: 1 addition & 1 deletion pkg/planner/core/logical_plans_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -330,7 +330,7 @@ func TestAntiSemiJoinConstFalse(t *testing.T) {
}{
{
sql: "select a from t t1 where not exists (select a from t t2 where t1.a = t2.a and t2.b = 1 and t2.b = 2)",
best: "Join{DataScan(t1)->DataScan(t2)}(test.t.a,test.t.a)->Projection",
best: "Join{DataScan(t1)->Dual}(test.t.a,test.t.a)->Projection",
joinType: "anti semi join",
},
}
Expand Down
1 change: 1 addition & 0 deletions pkg/planner/core/operator/logicalop/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ go_library(
name = "logicalop",
srcs = [
"base_logical_plan.go",
"expression_util.go",
"hash64_equals_generated.go",
"logical_aggregation.go",
"logical_apply.go",
Expand Down
52 changes: 52 additions & 0 deletions pkg/planner/core/operator/logicalop/expression_util.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// Copyright 2025 PingCAP, Inc.
//
// 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
//
// http://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,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how about putting it into constraint pkg

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it cannot put it constraint pkg.it will happen import cycle.

        imports github.com/pingcap/tidb/pkg/planner/core/operator/logicalop
        imports github.com/pingcap/tidb/pkg/planner/core/constraint
        imports github.com/pingcap/tidb/pkg/planner/core/operator/logicalop: import cycle not allowed

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also try to move this into planner/core. but it still raise the import cycle.

// 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 logicalop

import (
"github.com/pingcap/tidb/pkg/expression"
"github.com/pingcap/tidb/pkg/planner/core/base"
)

// Conds2TableDual builds a LogicalTableDual if cond is constant false or null.
func Conds2TableDual(p base.LogicalPlan, conds []expression.Expression) base.LogicalPlan {
for _, cond := range conds {
if expression.IsConstFalse(cond) {
if expression.MaybeOverOptimized4PlanCache(p.SCtx().GetExprCtx(), conds) {
return nil
}
dual := LogicalTableDual{}.Init(p.SCtx(), p.QueryBlockOffset())
dual.SetSchema(p.Schema())
return dual
}
}
if len(conds) != 1 {
return nil
}

con, ok := conds[0].(*expression.Constant)
if !ok {
return nil
}
sc := p.SCtx().GetSessionVars().StmtCtx
if expression.MaybeOverOptimized4PlanCache(p.SCtx().GetExprCtx(), []expression.Expression{con}) {
return nil
}
if isTrue, err := con.Value.ToBool(sc.TypeCtxOrDefault()); (err == nil && isTrue == 0) || con.Value.IsNull() {
dual := LogicalTableDual{}.Init(p.SCtx(), p.QueryBlockOffset())
dual.SetSchema(p.Schema())
return dual
}
return nil
}
5 changes: 5 additions & 0 deletions pkg/planner/core/operator/logicalop/logical_datasource.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,11 @@ func (ds *DataSource) PredicatePushDown(predicates []expression.Expression, opt
// TODO: remove it to the place building logical plan
predicates = utilfuncp.AddPrefix4ShardIndexes(ds, ds.SCtx(), predicates)
ds.AllConds = predicates
dual := Conds2TableDual(ds, ds.AllConds)
if dual != nil {
AppendTableDualTraceStep(ds, dual, predicates, opt)
return nil, dual
}
ds.PushedDownConds, predicates = expression.PushDownExprs(util.GetPushDownCtx(ds.SCtx()), predicates, kv.UnSpecified)
appendDataSourcePredicatePushDownTraceStep(ds, opt)
return predicates, ds
Expand Down
21 changes: 0 additions & 21 deletions pkg/planner/core/operator/logicalop/logical_join.go
Original file line number Diff line number Diff line change
Expand Up @@ -1868,27 +1868,6 @@ func deriveNotNullExpr(ctx base.PlanContext, expr expression.Expression, schema
return nil
}

// Conds2TableDual builds a LogicalTableDual if cond is constant false or null.
func Conds2TableDual(p base.LogicalPlan, conds []expression.Expression) base.LogicalPlan {
if len(conds) != 1 {
return nil
}
con, ok := conds[0].(*expression.Constant)
if !ok {
return nil
}
sc := p.SCtx().GetSessionVars().StmtCtx
if expression.MaybeOverOptimized4PlanCache(p.SCtx().GetExprCtx(), []expression.Expression{con}) {
return nil
}
if isTrue, err := con.Value.ToBool(sc.TypeCtxOrDefault()); (err == nil && isTrue == 0) || con.Value.IsNull() {
dual := LogicalTableDual{}.Init(p.SCtx(), p.QueryBlockOffset())
dual.SetSchema(p.Schema())
return dual
}
return nil
}

// BuildLogicalJoinSchema builds the schema for join operator.
func BuildLogicalJoinSchema(joinType JoinType, join base.LogicalPlan) *expression.Schema {
leftSchema := join.Children()[0].Schema()
Expand Down
4 changes: 2 additions & 2 deletions pkg/planner/core/testdata/plan_suite_unexported_out.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,8 @@
"Join{DataScan(a)->DataScan(b)}(test.t.a,test.t.a)->Aggr(count(1))->Projection",
"DataScan(t)->Projection->Projection",
"DataScan(t)->Projection->Projection",
"DataScan(t)->Projection->Projection",
"DataScan(t)->Projection->Projection",
"Dual->Projection->Projection",
"Dual->Projection->Projection",
"Join{DataScan(ta)->DataScan(tb)}(test.t.d,test.t.b)(test.t.a,test.t.c)->Projection",
"Join{DataScan(t1)->DataScan(t2)}(test.t.a,test.t.b)(test.t.d,test.t.d)->Projection",
"Join{DataScan(ta)->DataScan(tb)}(test.t.d,test.t.d)->Projection",
Expand Down
24 changes: 6 additions & 18 deletions tests/integrationtest/r/executor/executor.result
Original file line number Diff line number Diff line change
Expand Up @@ -2765,47 +2765,35 @@ create table t (c1 year(4), c2 int, key(c1));
insert into t values(2001, 1);
explain format = 'brief' select t1.c1, t2.c1 from t as t1 inner join t as t2 on t1.c1 = t2.c1 where t1.c1 != NULL;
id estRows task access object operator info
MergeJoin 0.00 root inner join, left key:executor__executor.t.c1, right key:executor__executor.t.c1
├─TableDual(Build) 0.00 root rows:0
└─TableDual(Probe) 0.00 root rows:0
TableDual 0.00 root rows:0
select t1.c1, t2.c1 from t as t1 inner join t as t2 on t1.c1 = t2.c1 where t1.c1 != NULL;
c1 c1
explain format = 'brief' select * from t as t1 inner join t as t2 on t1.c1 = t2.c1 where t1.c1 != NULL;
id estRows task access object operator info
MergeJoin 0.00 root inner join, left key:executor__executor.t.c1, right key:executor__executor.t.c1
├─TableDual(Build) 0.00 root rows:0
└─TableDual(Probe) 0.00 root rows:0
TableDual 0.00 root rows:0
select * from t as t1 inner join t as t2 on t1.c1 = t2.c1 where t1.c1 != NULL;
c1 c2 c1 c2
explain format = 'brief' select count(*) from t as t1 inner join t as t2 on t1.c1 = t2.c1 where t1.c1 != NULL;
id estRows task access object operator info
StreamAgg 1.00 root funcs:count(1)->Column#7
└─MergeJoin 0.00 root inner join, left key:executor__executor.t.c1, right key:executor__executor.t.c1
├─TableDual(Build) 0.00 root rows:0
└─TableDual(Probe) 0.00 root rows:0
└─TableDual 0.00 root rows:0
select count(*) from t as t1 inner join t as t2 on t1.c1 = t2.c1 where t1.c1 != NULL;
count(*)
0
explain format = 'brief' select t1.c1, t2.c1 from t as t1 left join t as t2 on t1.c1 = t2.c1 where t1.c1 != NULL;
id estRows task access object operator info
MergeJoin 0.00 root left outer join, left side:TableDual, left key:executor__executor.t.c1, right key:executor__executor.t.c1
├─TableDual(Build) 0.00 root rows:0
└─TableDual(Probe) 0.00 root rows:0
TableDual 0.00 root rows:0
select t1.c1, t2.c1 from t as t1 left join t as t2 on t1.c1 = t2.c1 where t1.c1 != NULL;
c1 c1
explain format = 'brief' select * from t as t1 left join t as t2 on t1.c1 = t2.c1 where t1.c1 != NULL;
id estRows task access object operator info
MergeJoin 0.00 root left outer join, left side:TableDual, left key:executor__executor.t.c1, right key:executor__executor.t.c1
├─TableDual(Build) 0.00 root rows:0
└─TableDual(Probe) 0.00 root rows:0
TableDual 0.00 root rows:0
select * from t as t1 left join t as t2 on t1.c1 = t2.c1 where t1.c1 != NULL;
c1 c2 c1 c2
explain format = 'brief' select count(*) from t as t1 left join t as t2 on t1.c1 = t2.c1 where t1.c1 != NULL;
id estRows task access object operator info
StreamAgg 1.00 root funcs:count(1)->Column#7
└─MergeJoin 0.00 root left outer join, left side:TableDual, left key:executor__executor.t.c1, right key:executor__executor.t.c1
├─TableDual(Build) 0.00 root rows:0
└─TableDual(Probe) 0.00 root rows:0
└─TableDual 0.00 root rows:0
select count(*) from t as t1 left join t as t2 on t1.c1 = t2.c1 where t1.c1 != NULL;
count(*)
0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ Selection_5 0.00 root json_overlaps(planner__core__casetest__index__index.t2.a,
└─TableRowIDScan_8(Probe) 0.00 cop[tikv] table:t2 keep order:false, stats:pseudo
explain select /*+ use_index_merge(t2, idx2, idx) */ * from t2 where 1 member of (a) and c=1 and c=2; -- 6: AND index merge from multi complicated mv indexes (empty range);
id estRows task access object operator info
TableDual_5 0.00 root rows:0
TableDual_6 0.00 root rows:0
drop table if exists t;
create table t(a int, b int, c int, unique index(a), unique index(b), primary key(c));
explain format = 'brief' select /*+ USE_INDEX_MERGE(t, a, b) */ * from t where a = 1 or b = 2;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,14 +122,10 @@ Selection 12.80 root gt(Column#4, 100)
└─TableFullScan 10000.00 cop[tikv] table:ts keep order:false, stats:pseudo
explain format = 'brief' select f from t where f <> NULL and f in (1,2,3) -- Special case of NULL with no simplification.;
id estRows task access object operator info
TableReader 0.00 root data:Selection
└─Selection 0.00 cop[tikv] in(planner__core__casetest__predicate_simplification.t.f, 1, 2, 3), ne(planner__core__casetest__predicate_simplification.t.f, NULL)
└─TableFullScan 10000.00 cop[tikv] table:t keep order:false, stats:pseudo
TableDual 0.00 root rows:0
explain format = 'brief' select f from t where f != NULL and f in (NULL,2,3) -- Special case of NULL with no simplification.;
id estRows task access object operator info
TableReader 0.00 root data:Selection
└─Selection 0.00 cop[tikv] in(planner__core__casetest__predicate_simplification.t.f, NULL, 2, 3), ne(planner__core__casetest__predicate_simplification.t.f, NULL)
└─TableFullScan 10000.00 cop[tikv] table:t keep order:false, stats:pseudo
TableDual 0.00 root rows:0
drop table if exists dt;
drop table if exists it;
CREATE TABLE `dt` (
Expand Down
25 changes: 4 additions & 21 deletions tests/integrationtest/r/tpch.result
Original file line number Diff line number Diff line change
Expand Up @@ -710,20 +710,8 @@ and n_name = 'MOZAMBIQUE'
order by
value desc;
id estRows task access object operator info
Projection 1283496.34 root tpch50.partsupp.ps_partkey, Column#35->Column#58
└─Sort 1283496.34 root Column#35:desc
└─Selection 1283496.34 root gt(Column#35, NULL)
└─HashAgg 1604370.43 root group by:Column#60, funcs:sum(Column#59)->Column#35, funcs:firstrow(Column#60)->tpch50.partsupp.ps_partkey
└─Projection 1604370.43 root mul(tpch50.partsupp.ps_supplycost, cast(tpch50.partsupp.ps_availqty, decimal(10,0) BINARY))->Column#59, tpch50.partsupp.ps_partkey->Column#60
└─HashJoin 1604370.43 root inner join, equal:[eq(tpch50.supplier.s_suppkey, tpch50.partsupp.ps_suppkey)]
├─HashJoin(Build) 19999.44 root inner join, equal:[eq(tpch50.nation.n_nationkey, tpch50.supplier.s_nationkey)]
│ ├─TableReader(Build) 1.00 root data:Selection
│ │ └─Selection 1.00 cop[tikv] eq(tpch50.nation.n_name, "MOZAMBIQUE")
│ │ └─TableFullScan 25.00 cop[tikv] table:nation keep order:false
│ └─TableReader(Probe) 500000.00 root data:TableFullScan
│ └─TableFullScan 500000.00 cop[tikv] table:supplier keep order:false
└─TableReader(Probe) 40000000.00 root data:TableFullScan
└─TableFullScan 40000000.00 cop[tikv] table:partsupp keep order:false
Projection 0.00 root tpch50.partsupp.ps_partkey, Column#35->Column#58
└─TableDual 0.00 root rows:0
/*
Q12 Shipping Modes and Order Priority Query
This query determines whether selecting less expensive modes of shipping is negatively affecting the critical-priority
Expand Down Expand Up @@ -1292,10 +1280,5 @@ id estRows task access object operator info
Sort 1.00 root Column#31
└─Projection 1.00 root Column#31, Column#32, Column#33
└─HashAgg 1.00 root group by:Column#36, funcs:count(1)->Column#32, funcs:sum(Column#35)->Column#33, funcs:firstrow(Column#36)->Column#31
└─Projection 0.64 root tpch50.customer.c_acctbal->Column#35, substring(tpch50.customer.c_phone, 1, 2)->Column#36
└─HashJoin 0.64 root anti semi join, left side:TableReader, equal:[eq(tpch50.customer.c_custkey, tpch50.orders.o_custkey)]
├─TableReader(Build) 0.80 root data:Selection
│ └─Selection 0.80 cop[tikv] gt(tpch50.customer.c_acctbal, NULL), in(substring(tpch50.customer.c_phone, 1, 2), "20", "40", "22", "30", "39", "42", "21")
│ └─TableFullScan 7500000.00 cop[tikv] table:customer keep order:false
└─TableReader(Probe) 75000000.00 root data:TableFullScan
└─TableFullScan 75000000.00 cop[tikv] table:orders keep order:false
└─Projection 0.00 root tpch50.customer.c_acctbal->Column#35, substring(tpch50.customer.c_phone, 1, 2)->Column#36
└─TableDual 0.00 root rows:0