603 lines
23 KiB
Go
603 lines
23 KiB
Go
// Copyright 2017 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 core_test
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"math"
|
|
"testing"
|
|
|
|
"github.com/pingcap/errors"
|
|
"github.com/pingcap/tidb/pkg/domain"
|
|
"github.com/pingcap/tidb/pkg/expression"
|
|
"github.com/pingcap/tidb/pkg/infoschema"
|
|
"github.com/pingcap/tidb/pkg/kv"
|
|
"github.com/pingcap/tidb/pkg/meta/model"
|
|
"github.com/pingcap/tidb/pkg/parser"
|
|
"github.com/pingcap/tidb/pkg/parser/ast"
|
|
"github.com/pingcap/tidb/pkg/parser/terror"
|
|
"github.com/pingcap/tidb/pkg/planner"
|
|
"github.com/pingcap/tidb/pkg/planner/core"
|
|
"github.com/pingcap/tidb/pkg/planner/core/base"
|
|
"github.com/pingcap/tidb/pkg/planner/core/resolve"
|
|
"github.com/pingcap/tidb/pkg/planner/property"
|
|
"github.com/pingcap/tidb/pkg/planner/util"
|
|
"github.com/pingcap/tidb/pkg/planner/util/coretestsdk"
|
|
"github.com/pingcap/tidb/pkg/session"
|
|
"github.com/pingcap/tidb/pkg/testkit"
|
|
"github.com/pingcap/tidb/pkg/testkit/external"
|
|
"github.com/pingcap/tidb/pkg/util/dbterror/plannererrors"
|
|
"github.com/pingcap/tipb/go-tipb"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestAnalyzeBuildSucc(t *testing.T) {
|
|
store := testkit.CreateMockStore(t)
|
|
tk := testkit.NewTestKit(t, store)
|
|
tk.MustExec("use test")
|
|
tk.MustExec("create table t(a int)")
|
|
tests := []struct {
|
|
sql string
|
|
succ bool
|
|
statsVer int
|
|
}{
|
|
{
|
|
sql: "analyze table t with 0.1 samplerate",
|
|
succ: true,
|
|
statsVer: 2,
|
|
},
|
|
{
|
|
sql: "analyze table t with 0.1 samplerate",
|
|
succ: false,
|
|
statsVer: 1,
|
|
},
|
|
{
|
|
sql: "analyze table t with 10 samplerate",
|
|
succ: false,
|
|
statsVer: 2,
|
|
},
|
|
{
|
|
sql: "analyze table t with 0.1 samplerate, 100000 samples",
|
|
succ: false,
|
|
statsVer: 2,
|
|
},
|
|
{
|
|
sql: "analyze table t with 0.1 samplerate, 100000 samples",
|
|
succ: false,
|
|
statsVer: 1,
|
|
},
|
|
}
|
|
|
|
p := parser.New()
|
|
is := infoschema.MockInfoSchema([]*model.TableInfo{core.MockSignedTable(), core.MockUnsignedTable()})
|
|
for i, tt := range tests {
|
|
comment := fmt.Sprintf("The %v-th test failed", i)
|
|
tk.MustExec(fmt.Sprintf("set @@tidb_analyze_version=%v", tt.statsVer))
|
|
|
|
stmt, err := p.ParseOneStmt(tt.sql, "", "")
|
|
if tt.succ {
|
|
require.NoError(t, err, comment)
|
|
} else if err != nil {
|
|
continue
|
|
}
|
|
nodeW := resolve.NewNodeW(stmt)
|
|
err = core.Preprocess(context.Background(), tk.Session(), nodeW, core.WithPreprocessorReturn(&core.PreprocessorReturn{InfoSchema: is}))
|
|
require.NoError(t, err)
|
|
_, _, err = planner.Optimize(context.Background(), tk.Session(), nodeW, is)
|
|
if tt.succ {
|
|
require.NoError(t, err, comment)
|
|
} else {
|
|
require.Error(t, err, comment)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestAnalyzeSetRate(t *testing.T) {
|
|
store := testkit.CreateMockStore(t)
|
|
|
|
tk := testkit.NewTestKit(t, store)
|
|
tk.MustExec("use test")
|
|
tk.MustExec("create table t(a int)")
|
|
tests := []struct {
|
|
sql string
|
|
rate float64
|
|
}{
|
|
{
|
|
sql: "analyze table t",
|
|
rate: -1,
|
|
},
|
|
{
|
|
sql: "analyze table t with 0.1 samplerate",
|
|
rate: 0.1,
|
|
},
|
|
{
|
|
sql: "analyze table t with 10000 samples",
|
|
rate: -1,
|
|
},
|
|
}
|
|
|
|
p := parser.New()
|
|
is := infoschema.MockInfoSchema([]*model.TableInfo{core.MockSignedTable(), core.MockUnsignedTable()})
|
|
for i, tt := range tests {
|
|
comment := fmt.Sprintf("The %v-th test failed", i)
|
|
stmt, err := p.ParseOneStmt(tt.sql, "", "")
|
|
require.NoError(t, err, comment)
|
|
|
|
nodeW := resolve.NewNodeW(stmt)
|
|
err = core.Preprocess(context.Background(), tk.Session(), nodeW, core.WithPreprocessorReturn(&core.PreprocessorReturn{InfoSchema: is}))
|
|
require.NoError(t, err, comment)
|
|
p, _, err := planner.Optimize(context.Background(), tk.Session(), nodeW, is)
|
|
require.NoError(t, err, comment)
|
|
ana := p.(*core.Analyze)
|
|
require.Equal(t, tt.rate, math.Float64frombits(ana.Opts[ast.AnalyzeOptSampleRate]))
|
|
}
|
|
}
|
|
|
|
type overrideStore struct{ kv.Storage }
|
|
|
|
func (store overrideStore) GetClient() kv.Client {
|
|
cli := store.Storage.GetClient()
|
|
return overrideClient{cli}
|
|
}
|
|
|
|
type overrideClient struct{ kv.Client }
|
|
|
|
func (cli overrideClient) IsRequestTypeSupported(_, _ int64) bool {
|
|
return false
|
|
}
|
|
|
|
func TestRequestTypeSupportedOff(t *testing.T) {
|
|
store := testkit.CreateMockStore(t)
|
|
se, err := session.CreateSession4Test(overrideStore{store})
|
|
require.NoError(t, err)
|
|
_, err = se.Execute(context.Background(), "use test")
|
|
require.NoError(t, err)
|
|
|
|
sql := "select * from t where a in (1, 10, 20)"
|
|
expect := "TableReader(Table(t))->Sel([in(test.t.a, 1, 10, 20)])"
|
|
|
|
is := infoschema.MockInfoSchema([]*model.TableInfo{core.MockSignedTable(), core.MockUnsignedTable()})
|
|
stmt, err := parser.New().ParseOneStmt(sql, "", "")
|
|
require.NoError(t, err)
|
|
nodeW := resolve.NewNodeW(stmt)
|
|
p, _, err := planner.Optimize(context.TODO(), se, nodeW, is)
|
|
require.NoError(t, err)
|
|
require.Equal(t, expect, core.ToString(p), fmt.Sprintf("sql: %s", sql))
|
|
}
|
|
|
|
func TestDoSubQuery(t *testing.T) {
|
|
store := testkit.CreateMockStore(t)
|
|
|
|
tk := testkit.NewTestKit(t, store)
|
|
tk.MustExec("use test")
|
|
|
|
tests := []struct {
|
|
sql string
|
|
best string
|
|
}{
|
|
{
|
|
sql: "do 1 in (select a from t)",
|
|
best: "LeftHashJoin{Dual->PointGet(Handle(t.a)1)}->Projection",
|
|
},
|
|
}
|
|
|
|
p := parser.New()
|
|
is := infoschema.MockInfoSchema([]*model.TableInfo{core.MockSignedTable(), core.MockUnsignedTable()})
|
|
|
|
for _, tt := range tests {
|
|
comment := fmt.Sprintf("for %s", tt.sql)
|
|
stmt, err := p.ParseOneStmt(tt.sql, "", "")
|
|
require.NoError(t, err, comment)
|
|
nodeW := resolve.NewNodeW(stmt)
|
|
p, _, err := planner.Optimize(context.TODO(), tk.Session(), nodeW, is)
|
|
require.NoError(t, err)
|
|
require.Equal(t, tt.best, core.ToString(p), comment)
|
|
}
|
|
}
|
|
|
|
func TestIndexLookupCartesianJoin(t *testing.T) {
|
|
store := testkit.CreateMockStore(t)
|
|
tk := testkit.NewTestKit(t, store)
|
|
tk.MustExec("use test")
|
|
|
|
stmt, err := parser.New().ParseOneStmt("select /*+ TIDB_INLJ(t1, t2) */ * from t t1 join t t2", "", "")
|
|
require.NoError(t, err)
|
|
|
|
is := infoschema.MockInfoSchema([]*model.TableInfo{core.MockSignedTable(), core.MockUnsignedTable()})
|
|
nodeW := resolve.NewNodeW(stmt)
|
|
p, _, err := planner.Optimize(context.TODO(), tk.Session(), nodeW, is)
|
|
require.NoError(t, err)
|
|
require.Equal(t, "LeftHashJoin{TableReader(Table(t))->TableReader(Table(t))}", core.ToString(p))
|
|
|
|
warnings := tk.Session().GetSessionVars().StmtCtx.GetWarnings()
|
|
lastWarn := warnings[len(warnings)-1]
|
|
err = plannererrors.ErrInternal.GenWithStack("TIDB_INLJ hint is inapplicable without column equal ON condition")
|
|
require.True(t, terror.ErrorEqual(err, lastWarn.Err))
|
|
}
|
|
|
|
func TestMPPHintsWithBinding(t *testing.T) {
|
|
store := testkit.CreateMockStore(t, coretestsdk.WithMockTiFlash(2))
|
|
tk := testkit.NewTestKit(t, store)
|
|
tk.MustExec("use test")
|
|
tk.MustExec("set tidb_cost_model_version=2")
|
|
tk.MustExec("create table t (a int, b int, c int)")
|
|
tk.MustExec("alter table t set tiflash replica 1")
|
|
tk.MustExec("set @@session.tidb_allow_mpp=ON")
|
|
tb := external.GetTableByName(t, tk, "test", "t")
|
|
err := domain.GetDomain(tk.Session()).DDLExecutor().UpdateTableReplicaInfo(tk.Session(), tb.Meta().ID, true)
|
|
require.NoError(t, err)
|
|
|
|
tk.MustExec("explain select a, sum(b) from t group by a, c")
|
|
tk.MustQuery("select @@last_plan_from_binding").Check(testkit.Rows("0"))
|
|
tk.MustExec("create global binding for select a, sum(b) from t group by a, c using select /*+ read_from_storage(tiflash[t]), MPP_1PHASE_AGG() */ a, sum(b) from t group by a, c;")
|
|
tk.MustExec("explain select a, sum(b) from t group by a, c")
|
|
tk.MustQuery("select @@last_plan_from_binding").Check(testkit.Rows("1"))
|
|
res := tk.MustQuery("show global bindings").Rows()
|
|
require.Equal(t, res[0][0], "select `a` , sum ( `b` ) from `test` . `t` group by `a` , `c`")
|
|
require.Equal(t, res[0][1], "SELECT /*+ read_from_storage(tiflash[`t`]) MPP_1PHASE_AGG()*/ `a`,sum(`b`) FROM `test`.`t` GROUP BY `a`,`c`")
|
|
tk.MustExec("create global binding for select a, sum(b) from t group by a, c using select /*+ read_from_storage(tiflash[t]), MPP_2PHASE_AGG() */ a, sum(b) from t group by a, c;")
|
|
tk.MustExec("explain select a, sum(b) from t group by a, c")
|
|
tk.MustQuery("select @@last_plan_from_binding").Check(testkit.Rows("1"))
|
|
res = tk.MustQuery("show global bindings").Rows()
|
|
require.Equal(t, res[0][0], "select `a` , sum ( `b` ) from `test` . `t` group by `a` , `c`")
|
|
require.Equal(t, res[0][1], "SELECT /*+ read_from_storage(tiflash[`t`]) MPP_2PHASE_AGG()*/ `a`,sum(`b`) FROM `test`.`t` GROUP BY `a`,`c`")
|
|
tk.MustExec("drop global binding for select a, sum(b) from t group by a, c;")
|
|
res = tk.MustQuery("show global bindings").Rows()
|
|
require.Equal(t, len(res), 0)
|
|
|
|
tk.MustExec("explain select * from t t1, t t2 where t1.a=t2.a")
|
|
tk.MustQuery("select @@last_plan_from_binding").Check(testkit.Rows("0"))
|
|
tk.MustExec("create global binding for select * from t t1, t t2 where t1.a=t2.a using select /*+ read_from_storage(tiflash[t1, t2]), shuffle_join(t1, t2) */ * from t t1, t t2 where t1.a=t2.a")
|
|
tk.MustExec("explain select * from t t1, t t2 where t1.a=t2.a")
|
|
tk.MustQuery("select @@last_plan_from_binding").Check(testkit.Rows("1"))
|
|
res = tk.MustQuery("show global bindings").Rows()
|
|
require.Equal(t, res[0][0], "select * from ( `test` . `t` as `t1` ) join `test` . `t` as `t2` where `t1` . `a` = `t2` . `a`")
|
|
require.Equal(t, res[0][1], "SELECT /*+ read_from_storage(tiflash[`t1`, `t2`]) shuffle_join(`t1`, `t2`)*/ * FROM (`test`.`t` AS `t1`) JOIN `test`.`t` AS `t2` WHERE `t1`.`a` = `t2`.`a`")
|
|
tk.MustExec("create global binding for select * from t t1, t t2 where t1.a=t2.a using select /*+ read_from_storage(tiflash[t1, t2]), broadcast_join(t1, t2) */ * from t t1, t t2 where t1.a=t2.a;")
|
|
tk.MustExec("explain select * from t t1, t t2 where t1.a=t2.a")
|
|
tk.MustQuery("select @@last_plan_from_binding").Check(testkit.Rows("1"))
|
|
res = tk.MustQuery("show global bindings").Rows()
|
|
require.Equal(t, res[0][0], "select * from ( `test` . `t` as `t1` ) join `test` . `t` as `t2` where `t1` . `a` = `t2` . `a`")
|
|
require.Equal(t, res[0][1], "SELECT /*+ read_from_storage(tiflash[`t1`, `t2`]) broadcast_join(`t1`, `t2`)*/ * FROM (`test`.`t` AS `t1`) JOIN `test`.`t` AS `t2` WHERE `t1`.`a` = `t2`.`a`")
|
|
tk.MustExec("drop global binding for select * from t t1, t t2 where t1.a=t2.a;")
|
|
res = tk.MustQuery("show global bindings").Rows()
|
|
require.Equal(t, len(res), 0)
|
|
}
|
|
|
|
func TestJoinHintCompatibilityWithBinding(t *testing.T) {
|
|
store := testkit.CreateMockStore(t, coretestsdk.WithMockTiFlash(2))
|
|
tk := testkit.NewTestKit(t, store)
|
|
tk.MustExec("use test")
|
|
tk.MustExec("set tidb_cost_model_version=2")
|
|
tk.MustExec("create table t (a int, b int, c int, index idx_a(a), index idx_b(b))")
|
|
tb := external.GetTableByName(t, tk, "test", "t")
|
|
err := domain.GetDomain(tk.Session()).DDLExecutor().UpdateTableReplicaInfo(tk.Session(), tb.Meta().ID, true)
|
|
require.NoError(t, err)
|
|
|
|
tk.MustExec("select * from t t1 join t t2 join t t3 where t1.a = t2.a and t2.b = t3.b;")
|
|
tk.MustQuery("select @@last_plan_from_binding").Check(testkit.Rows("0"))
|
|
tk.MustExec("select /*+ leading(t2), hash_join(t2) */ * from t t1 join t t2 join t t3 where t1.a = t2.a and t2.b = t3.b;")
|
|
tk.MustQuery("show warnings").Check(testkit.Rows())
|
|
tk.MustExec("create global binding for select * from t t1 join t t2 join t t3 where t1.a = t2.a and t2.b = t3.b using select /*+ leading(t2), hash_join(t2) */ * from t t1 join t t2 join t t3 where t1.a = t2.a and t2.b = t3.b;")
|
|
tk.MustExec("select * from t t1 join t t2 join t t3 where t1.a = t2.a and t2.b = t3.b;")
|
|
tk.MustQuery("select @@last_plan_from_binding").Check(testkit.Rows("1"))
|
|
res := tk.MustQuery("show global bindings").Rows()
|
|
require.Equal(t, res[0][0], "select * from ( `test` . `t` as `t1` join `test` . `t` as `t2` ) join `test` . `t` as `t3` where `t1` . `a` = `t2` . `a` and `t2` . `b` = `t3` . `b`")
|
|
require.Equal(t, res[0][1], "SELECT /*+ leading(`t2`) hash_join(`t2`)*/ * FROM (`test`.`t` AS `t1` JOIN `test`.`t` AS `t2`) JOIN `test`.`t` AS `t3` WHERE `t1`.`a` = `t2`.`a` AND `t2`.`b` = `t3`.`b`")
|
|
tk.MustExec("select * from t t1 join t t2 join t t3 where t1.a = t2.a and t2.b = t3.b;")
|
|
tk.MustQuery("show warnings").Check(testkit.Rows())
|
|
tk.MustExec("drop global binding for select * from t t1 join t t2 join t t3 where t1.a = t2.a and t2.b = t3.b;")
|
|
res = tk.MustQuery("show global bindings").Rows()
|
|
require.Equal(t, len(res), 0)
|
|
}
|
|
|
|
func TestJoinHintCompatibilityWithVariable(t *testing.T) {
|
|
store := testkit.CreateMockStore(t)
|
|
tk := testkit.NewTestKit(t, store)
|
|
tk.MustExec("use test")
|
|
tk.MustExec("set tidb_cost_model_version=2")
|
|
tk.MustExec("create table t (a int, b int, c int, index idx_a(a), index idx_b(b))")
|
|
tb := external.GetTableByName(t, tk, "test", "t")
|
|
err := domain.GetDomain(tk.Session()).DDLExecutor().UpdateTableReplicaInfo(tk.Session(), tb.Meta().ID, true)
|
|
require.NoError(t, err)
|
|
|
|
tk.MustExec("select /*+ leading(t2), hash_join(t2) */ * from t t1 join t t2 join t t3 where t1.a = t2.a and t2.b = t3.b;")
|
|
tk.MustQuery("show warnings").Check(testkit.Rows())
|
|
|
|
tk.MustExec("set @@session.tidb_opt_advanced_join_hint=0")
|
|
tk.MustExec("select /*+ leading(t2), hash_join(t2) */ * from t t1 join t t2 join t t3 where t1.a = t2.a and t2.b = t3.b;")
|
|
res := tk.MustQuery("show warnings").Rows()
|
|
require.Equal(t, len(res) > 0, true)
|
|
}
|
|
|
|
func TestHintAlias(t *testing.T) {
|
|
store := testkit.CreateMockStore(t)
|
|
|
|
tk := testkit.NewTestKit(t, store)
|
|
tk.MustExec("use test")
|
|
|
|
tests := []struct {
|
|
sql1 string
|
|
sql2 string
|
|
}{
|
|
{
|
|
sql1: "select /*+ TIDB_SMJ(t1) */ t1.a, t1.b from t t1, (select /*+ TIDB_INLJ(t3) */ t2.a from t t2, t t3 where t2.a = t3.c) s where t1.a=s.a",
|
|
sql2: "select /*+ MERGE_JOIN(t1) */ t1.a, t1.b from t t1, (select /*+ INL_JOIN(t3) */ t2.a from t t2, t t3 where t2.a = t3.c) s where t1.a=s.a",
|
|
},
|
|
{
|
|
sql1: "select /*+ TIDB_HJ(t1) */ t1.a, t1.b from t t1, (select /*+ TIDB_SMJ(t2) */ t2.a from t t2, t t3 where t2.a = t3.c) s where t1.a=s.a",
|
|
sql2: "select /*+ HASH_JOIN(t1) */ t1.a, t1.b from t t1, (select /*+ MERGE_JOIN(t2) */ t2.a from t t2, t t3 where t2.a = t3.c) s where t1.a=s.a",
|
|
},
|
|
{
|
|
sql1: "select /*+ TIDB_INLJ(t1) */ t1.a, t1.b from t t1, (select /*+ TIDB_HJ(t2) */ t2.a from t t2, t t3 where t2.a = t3.c) s where t1.a=s.a",
|
|
sql2: "select /*+ INL_JOIN(t1) */ t1.a, t1.b from t t1, (select /*+ HASH_JOIN(t2) */ t2.a from t t2, t t3 where t2.a = t3.c) s where t1.a=s.a",
|
|
},
|
|
}
|
|
ctx := context.TODO()
|
|
p := parser.New()
|
|
is := infoschema.MockInfoSchema([]*model.TableInfo{core.MockSignedTable(), core.MockUnsignedTable()})
|
|
|
|
for i, tt := range tests {
|
|
comment := fmt.Sprintf("case:%v sql1:%s sql2:%s", i, tt.sql1, tt.sql2)
|
|
stmt1, err := p.ParseOneStmt(tt.sql1, "", "")
|
|
require.NoError(t, err, comment)
|
|
stmt2, err := p.ParseOneStmt(tt.sql2, "", "")
|
|
require.NoError(t, err, comment)
|
|
|
|
nodeW1 := resolve.NewNodeW(stmt1)
|
|
p1, _, err := planner.Optimize(ctx, tk.Session(), nodeW1, is)
|
|
require.NoError(t, err)
|
|
nodeW2 := resolve.NewNodeW(stmt2)
|
|
p2, _, err := planner.Optimize(ctx, tk.Session(), nodeW2, is)
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, core.ToString(p2), core.ToString(p1))
|
|
}
|
|
}
|
|
|
|
func TestDAGPlanBuilderSplitAvg(t *testing.T) {
|
|
store := testkit.CreateMockStore(t)
|
|
|
|
tk := testkit.NewTestKit(t, store)
|
|
tk.MustExec("use test")
|
|
tk.MustExec("set tidb_cost_model_version=2")
|
|
tests := []struct {
|
|
sql string
|
|
plan string
|
|
}{
|
|
{
|
|
sql: "select avg(a),avg(b),avg(c) from t",
|
|
plan: "TableReader(Table(t)->HashAgg)->HashAgg",
|
|
},
|
|
{
|
|
sql: "select /*+ HASH_AGG() */ avg(a),avg(b),avg(c) from t",
|
|
plan: "TableReader(Table(t)->HashAgg)->HashAgg",
|
|
},
|
|
}
|
|
|
|
p := parser.New()
|
|
is := infoschema.MockInfoSchema([]*model.TableInfo{core.MockSignedTable(), core.MockUnsignedTable()})
|
|
|
|
for _, tt := range tests {
|
|
comment := fmt.Sprintf("for %s", tt.sql)
|
|
stmt, err := p.ParseOneStmt(tt.sql, "", "")
|
|
require.NoError(t, err, comment)
|
|
|
|
nodeW := resolve.NewNodeW(stmt)
|
|
err = core.Preprocess(context.Background(), tk.Session(), nodeW, core.WithPreprocessorReturn(&core.PreprocessorReturn{InfoSchema: is}))
|
|
require.NoError(t, err)
|
|
p, _, err := planner.Optimize(context.TODO(), tk.Session(), nodeW, is)
|
|
require.NoError(t, err, comment)
|
|
|
|
require.Equal(t, tt.plan, core.ToString(p), comment)
|
|
root, ok := p.(base.PhysicalPlan)
|
|
if !ok {
|
|
continue
|
|
}
|
|
testDAGPlanBuilderSplitAvg(t, root)
|
|
}
|
|
}
|
|
|
|
func testDAGPlanBuilderSplitAvg(t *testing.T, root base.PhysicalPlan) {
|
|
if p, ok := root.(*core.PhysicalTableReader); ok {
|
|
if p.TablePlans != nil {
|
|
baseAgg := p.TablePlans[len(p.TablePlans)-1]
|
|
if agg, ok := baseAgg.(*core.PhysicalHashAgg); ok {
|
|
for i, aggfunc := range agg.AggFuncs {
|
|
require.Equal(t, aggfunc.RetTp, agg.Schema().Columns[i].RetType)
|
|
}
|
|
}
|
|
if agg, ok := baseAgg.(*core.PhysicalStreamAgg); ok {
|
|
for i, aggfunc := range agg.AggFuncs {
|
|
require.Equal(t, aggfunc.RetTp, agg.Schema().Columns[i].RetType)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
childs := root.Children()
|
|
if childs == nil {
|
|
return
|
|
}
|
|
for _, son := range childs {
|
|
testDAGPlanBuilderSplitAvg(t, son)
|
|
}
|
|
}
|
|
|
|
func TestPhysicalPlanMemoryTrace(t *testing.T) {
|
|
// PhysicalSort
|
|
ls := core.PhysicalSort{}
|
|
size := ls.MemoryUsage()
|
|
ls.ByItems = append(ls.ByItems, &util.ByItems{})
|
|
require.Greater(t, ls.MemoryUsage(), size)
|
|
|
|
// PhysicalProperty
|
|
pp := property.PhysicalProperty{}
|
|
size = pp.MemoryUsage()
|
|
pp.MPPPartitionCols = append(pp.MPPPartitionCols, &property.MPPPartitionColumn{})
|
|
require.Greater(t, pp.MemoryUsage(), size)
|
|
}
|
|
|
|
func TestPhysicalTableScanExtractCorrelatedCols(t *testing.T) {
|
|
store := testkit.CreateMockStore(t, coretestsdk.WithMockTiFlash(2))
|
|
tk := testkit.NewTestKit(t, store)
|
|
tk.MustExec("use test")
|
|
tk.MustExec("create table t1 (id int, client_type tinyint, client_no char(18), taxpayer_no varchar(50), status tinyint, update_time datetime)")
|
|
tk.MustExec("alter table t1 set tiflash replica 1")
|
|
tb := external.GetTableByName(t, tk, "test", "t1")
|
|
err := domain.GetDomain(tk.Session()).DDLExecutor().UpdateTableReplicaInfo(tk.Session(), tb.Meta().ID, true)
|
|
require.NoError(t, err)
|
|
tk.MustExec("create table t2 (id int, company_no char(18), name varchar(200), tax_registry_no varchar(30))")
|
|
tk.MustExec("insert into t1(id, taxpayer_no, client_no, client_type, status, update_time) values (1, 'TAX001', 'Z9005', 1, 1, '2024-02-18 10:00:00'), (2, 'TAX002', 'Z9005', 1, 0, '2024-02-18 09:00:00'), (3, 'TAX003', 'Z9005', 2, 1, '2024-02-18 08:00:00'), (4, 'TAX004', 'Z9006', 1, 1, '2024-02-18 12:00:00')")
|
|
tk.MustExec("insert into t2(id, company_no, name, tax_registry_no) values (1, 'Z9005', 'AA', 'aaa'), (2, 'Z9006', 'BB', 'bbb'), (3, 'Z9007', 'CC', 'ccc')")
|
|
|
|
sql := "select company_no, ifnull((select /*+ read_from_storage(tiflash[test.t1]) */ taxpayer_no from test.t1 where client_no = c.company_no and client_type = 1 and status = 1 order by update_time desc limit 1), tax_registry_no) as tax_registry_no from test.t2 c where company_no = 'Z9005' limit 1"
|
|
tk.MustExec(sql)
|
|
info := tk.Session().ShowProcess()
|
|
require.NotNil(t, info)
|
|
p, ok := info.Plan.(base.Plan)
|
|
require.True(t, ok)
|
|
|
|
var findSelection func(p base.Plan) *core.PhysicalSelection
|
|
findSelection = func(p base.Plan) *core.PhysicalSelection {
|
|
if p == nil {
|
|
return nil
|
|
}
|
|
switch v := p.(type) {
|
|
case *core.PhysicalSelection:
|
|
if len(v.Children()) == 1 {
|
|
if ts, ok := v.Children()[0].(*core.PhysicalTableScan); ok && ts.Table.Name.L == "t1" {
|
|
return v
|
|
}
|
|
}
|
|
return nil
|
|
case *core.PhysicalTableReader:
|
|
for _, child := range v.TablePlans {
|
|
if sel := findSelection(child); sel != nil {
|
|
return sel
|
|
}
|
|
}
|
|
return nil
|
|
default:
|
|
physicayPlan := p.(base.PhysicalPlan)
|
|
for _, child := range physicayPlan.Children() {
|
|
if sel := findSelection(child); sel != nil {
|
|
return sel
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
}
|
|
sel := findSelection(p)
|
|
require.NotNil(t, sel)
|
|
ts := sel.Children()[0].(*core.PhysicalTableScan)
|
|
require.NotNil(t, ts)
|
|
// manually push down the condition `client_no = c.company_no`
|
|
var selected expression.Expression
|
|
for _, cond := range sel.Conditions {
|
|
if sf, ok := cond.(*expression.ScalarFunction); ok && sf.Function.PbCode() == tipb.ScalarFuncSig_EQString {
|
|
selected = cond
|
|
break
|
|
}
|
|
}
|
|
if selected != nil {
|
|
core.PushedDown(sel, ts, []expression.Expression{selected}, 0.1)
|
|
}
|
|
|
|
pb, err := ts.ToPB(tk.Session().GetBuildPBCtx(), kv.TiFlash)
|
|
require.NoError(t, err)
|
|
// make sure the pushed down filter condition is correct
|
|
require.Equal(t, 1, len(pb.TblScan.PushedDownFilterConditions))
|
|
require.Equal(t, tipb.ExprType_ColumnRef, pb.TblScan.PushedDownFilterConditions[0].Children[0].Tp)
|
|
// make sure the correlated columns are extracted correctly
|
|
correlated := ts.ExtractCorrelatedCols()
|
|
require.Equal(t, 1, len(correlated))
|
|
require.Equal(t, "test.t2.company_no", correlated[0].StringWithCtx(tk.Session().GetExprCtx().GetEvalCtx(), errors.RedactLogDisable))
|
|
}
|
|
|
|
func TestAvoidColumnEvaluatorForProjBelowUnion(t *testing.T) {
|
|
store := testkit.CreateMockStore(t)
|
|
tk := testkit.NewTestKit(t, store)
|
|
|
|
getPhysicalPlan := func(sql string) base.Plan {
|
|
tk.MustExec(sql)
|
|
info := tk.Session().ShowProcess()
|
|
require.NotNil(t, info)
|
|
p, ok := info.Plan.(base.Plan)
|
|
require.True(t, ok)
|
|
return p
|
|
}
|
|
|
|
var findProjBelowUnion func(p base.Plan) (projsBelowUnion, normalProjs []*core.PhysicalProjection)
|
|
findProjBelowUnion = func(p base.Plan) (projsBelowUnion, normalProjs []*core.PhysicalProjection) {
|
|
if p == nil {
|
|
return projsBelowUnion, normalProjs
|
|
}
|
|
switch v := p.(type) {
|
|
case *core.PhysicalUnionAll:
|
|
for _, child := range v.Children() {
|
|
if proj, ok := child.(*core.PhysicalProjection); ok {
|
|
projsBelowUnion = append(projsBelowUnion, proj)
|
|
}
|
|
}
|
|
default:
|
|
for _, child := range p.(base.PhysicalPlan).Children() {
|
|
if proj, ok := child.(*core.PhysicalProjection); ok {
|
|
normalProjs = append(normalProjs, proj)
|
|
}
|
|
subProjsBelowUnion, subNormalProjs := findProjBelowUnion(child)
|
|
projsBelowUnion = append(projsBelowUnion, subProjsBelowUnion...)
|
|
normalProjs = append(normalProjs, subNormalProjs...)
|
|
}
|
|
}
|
|
return projsBelowUnion, normalProjs
|
|
}
|
|
|
|
checkResult := func(sql string) {
|
|
p := getPhysicalPlan(sql)
|
|
projsBelowUnion, normalProjs := findProjBelowUnion(p)
|
|
if proj, ok := p.(*core.PhysicalProjection); ok {
|
|
normalProjs = append(normalProjs, proj)
|
|
}
|
|
require.NotEmpty(t, projsBelowUnion)
|
|
for _, proj := range projsBelowUnion {
|
|
require.True(t, proj.AvoidColumnEvaluator)
|
|
}
|
|
for _, proj := range normalProjs {
|
|
require.False(t, proj.AvoidColumnEvaluator)
|
|
}
|
|
}
|
|
|
|
// Test setup
|
|
tk.MustExec("use test")
|
|
tk.MustExec(`drop table if exists t1, t2;`)
|
|
tk.MustExec(`create table t1 (cc1 int, cc2 text);`)
|
|
tk.MustExec(`insert into t1 values (1, 'aaaa'), (2, 'bbbb'), (3, 'cccc');`)
|
|
tk.MustExec(`create table t2 (cc1 int, cc2 text, primary key(cc1));`)
|
|
tk.MustExec(`insert into t2 values (2, '2');`)
|
|
tk.MustExec(`set tidb_executor_concurrency = 1;`)
|
|
tk.MustExec(`set tidb_window_concurrency = 100;`)
|
|
|
|
testCases := []string{
|
|
`select * from (SELECT DISTINCT cc2 as a, cc2 as b, cc1 as c FROM t2 UNION ALL SELECT count(1) over (partition by cc1), cc2, cc1 FROM t1) order by a, b, c;`,
|
|
`select a+1, b+1 from (select cc1 as a, cc2 as b from t1 union select cc2, cc1 from t1) tmp`,
|
|
}
|
|
|
|
for _, sql := range testCases {
|
|
checkResult(sql)
|
|
}
|
|
}
|