// 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, // See the License for the specific language governing permissions and // limitations under the License. package core_test import ( "context" "fmt" . "github.com/pingcap/check" "github.com/pingcap/parser" "github.com/pingcap/parser/model" "github.com/pingcap/parser/terror" "github.com/pingcap/tidb/domain" "github.com/pingcap/tidb/executor" "github.com/pingcap/tidb/infoschema" "github.com/pingcap/tidb/kv" "github.com/pingcap/tidb/planner" "github.com/pingcap/tidb/planner/core" "github.com/pingcap/tidb/session" "github.com/pingcap/tidb/sessionctx" "github.com/pingcap/tidb/sessionctx/stmtctx" "github.com/pingcap/tidb/sessionctx/variable" "github.com/pingcap/tidb/util/hint" "github.com/pingcap/tidb/util/testkit" "github.com/pingcap/tidb/util/testleak" "github.com/pingcap/tidb/util/testutil" ) var _ = Suite(&testPlanSuite{}) var _ = SerialSuites(&testPlanSerialSuite{}) type testPlanSuiteBase struct { *parser.Parser is infoschema.InfoSchema } func (s *testPlanSuiteBase) SetUpSuite(c *C) { s.is = infoschema.MockInfoSchema([]*model.TableInfo{core.MockSignedTable(), core.MockUnsignedTable()}) s.Parser = parser.New() s.Parser.EnableWindowFunc(true) } type testPlanSerialSuite struct { testPlanSuiteBase } type testPlanSuite struct { testPlanSuiteBase testData testutil.TestData } func (s *testPlanSuite) SetUpSuite(c *C) { s.testPlanSuiteBase.SetUpSuite(c) var err error s.testData, err = testutil.LoadTestSuiteData("testdata", "plan_suite") c.Assert(err, IsNil) } func (s *testPlanSuite) TearDownSuite(c *C) { c.Assert(s.testData.GenerateOutputIfNeeded(), IsNil) } func (s *testPlanSuite) TestDAGPlanBuilderSimpleCase(c *C) { defer testleak.AfterTest(c)() store, dom, err := newStoreWithBootstrap() c.Assert(err, IsNil) defer func() { dom.Close() store.Close() }() se, err := session.CreateSession4Test(store) c.Assert(err, IsNil) _, err = se.Execute(context.Background(), "use test") c.Assert(err, IsNil) var input []string var output []struct { SQL string Best string } s.testData.GetTestCases(c, &input, &output) for i, tt := range input { comment := Commentf("case:%v sql:%s", i, tt) stmt, err := s.ParseOneStmt(tt, "", "") c.Assert(err, IsNil, comment) err = se.NewTxn(context.Background()) c.Assert(err, IsNil) p, _, err := planner.Optimize(context.TODO(), se, stmt, s.is) c.Assert(err, IsNil) s.testData.OnRecord(func() { output[i].SQL = tt output[i].Best = core.ToString(p) }) c.Assert(core.ToString(p), Equals, output[i].Best, comment) } } func (s *testPlanSuite) TestDAGPlanBuilderJoin(c *C) { defer testleak.AfterTest(c)() store, dom, err := newStoreWithBootstrap() c.Assert(err, IsNil) defer func() { dom.Close() store.Close() }() se, err := session.CreateSession4Test(store) c.Assert(err, IsNil) _, err = se.Execute(context.Background(), "use test") c.Assert(err, IsNil) var input []string var output []struct { SQL string Best string } s.testData.GetTestCases(c, &input, &output) for i, tt := range input { comment := Commentf("case:%v sql:%s", i, tt) stmt, err := s.ParseOneStmt(tt, "", "") c.Assert(err, IsNil, comment) p, _, err := planner.Optimize(context.TODO(), se, stmt, s.is) c.Assert(err, IsNil) s.testData.OnRecord(func() { output[i].SQL = tt output[i].Best = core.ToString(p) }) c.Assert(core.ToString(p), Equals, output[i].Best, comment) } } func (s *testPlanSuite) TestDAGPlanBuilderSubquery(c *C) { defer testleak.AfterTest(c)() store, dom, err := newStoreWithBootstrap() c.Assert(err, IsNil) defer func() { dom.Close() store.Close() }() se, err := session.CreateSession4Test(store) c.Assert(err, IsNil) _, err = se.Execute(context.Background(), "use test") c.Assert(err, IsNil) se.Execute(context.Background(), "set sql_mode='STRICT_TRANS_TABLES'") // disable only full group by ctx := se.(sessionctx.Context) sessionVars := ctx.GetSessionVars() sessionVars.HashAggFinalConcurrency = 1 sessionVars.HashAggPartialConcurrency = 1 var input []string var output []struct { SQL string Best string } s.testData.GetTestCases(c, &input, &output) for i, tt := range input { comment := Commentf("for %s", tt) stmt, err := s.ParseOneStmt(tt, "", "") c.Assert(err, IsNil, comment) p, _, err := planner.Optimize(context.TODO(), se, stmt, s.is) c.Assert(err, IsNil) s.testData.OnRecord(func() { output[i].SQL = tt output[i].Best = core.ToString(p) }) c.Assert(core.ToString(p), Equals, output[i].Best, Commentf("for %s", tt)) } } func (s *testPlanSuite) TestDAGPlanTopN(c *C) { defer testleak.AfterTest(c)() store, dom, err := newStoreWithBootstrap() c.Assert(err, IsNil) defer func() { dom.Close() store.Close() }() se, err := session.CreateSession4Test(store) c.Assert(err, IsNil) _, err = se.Execute(context.Background(), "use test") c.Assert(err, IsNil) var input []string var output []struct { SQL string Best string } s.testData.GetTestCases(c, &input, &output) for i, tt := range input { comment := Commentf("case:%v sql:%s", i, tt) stmt, err := s.ParseOneStmt(tt, "", "") c.Assert(err, IsNil, comment) p, _, err := planner.Optimize(context.TODO(), se, stmt, s.is) c.Assert(err, IsNil) s.testData.OnRecord(func() { output[i].SQL = tt output[i].Best = core.ToString(p) }) c.Assert(core.ToString(p), Equals, output[i].Best, comment) } } func (s *testPlanSuite) TestDAGPlanBuilderBasePhysicalPlan(c *C) { defer testleak.AfterTest(c)() store, dom, err := newStoreWithBootstrap() c.Assert(err, IsNil) defer func() { dom.Close() store.Close() }() se, err := session.CreateSession4Test(store) c.Assert(err, IsNil) _, err = se.Execute(context.Background(), "use test") c.Assert(err, IsNil) var input []string var output []struct { SQL string Best string Hints string } s.testData.GetTestCases(c, &input, &output) for i, tt := range input { comment := Commentf("for %s", tt) stmt, err := s.ParseOneStmt(tt, "", "") c.Assert(err, IsNil, comment) core.Preprocess(se, stmt, s.is) p, _, err := planner.Optimize(context.TODO(), se, stmt, s.is) c.Assert(err, IsNil) s.testData.OnRecord(func() { output[i].SQL = tt output[i].Best = core.ToString(p) output[i].Hints = hint.RestoreOptimizerHints(core.GenHintsFromPhysicalPlan(p)) }) c.Assert(core.ToString(p), Equals, output[i].Best, Commentf("for %s", tt)) c.Assert(hint.RestoreOptimizerHints(core.GenHintsFromPhysicalPlan(p)), Equals, output[i].Hints, Commentf("for %s", tt)) } } func (s *testPlanSuite) TestDAGPlanBuilderUnion(c *C) { defer testleak.AfterTest(c)() store, dom, err := newStoreWithBootstrap() c.Assert(err, IsNil) defer func() { dom.Close() store.Close() }() se, err := session.CreateSession4Test(store) c.Assert(err, IsNil) _, err = se.Execute(context.Background(), "use test") c.Assert(err, IsNil) var input []string var output []struct { SQL string Best string } s.testData.GetTestCases(c, &input, &output) for i, tt := range input { comment := Commentf("case:%v sql:%s", i, tt) stmt, err := s.ParseOneStmt(tt, "", "") c.Assert(err, IsNil, comment) p, _, err := planner.Optimize(context.TODO(), se, stmt, s.is) c.Assert(err, IsNil) s.testData.OnRecord(func() { output[i].SQL = tt output[i].Best = core.ToString(p) }) c.Assert(core.ToString(p), Equals, output[i].Best, comment) } } func (s *testPlanSuite) TestDAGPlanBuilderUnionScan(c *C) { defer testleak.AfterTest(c)() store, dom, err := newStoreWithBootstrap() c.Assert(err, IsNil) defer func() { dom.Close() store.Close() }() se, err := session.CreateSession4Test(store) c.Assert(err, IsNil) _, err = se.Execute(context.Background(), "use test") c.Assert(err, IsNil) var input []string var output []struct { SQL string Best string } for i, tt := range input { comment := Commentf("for %s", tt) stmt, err := s.ParseOneStmt(tt, "", "") c.Assert(err, IsNil, comment) err = se.NewTxn(context.Background()) c.Assert(err, IsNil) // Make txn not read only. txn, err := se.Txn(true) c.Assert(err, IsNil) txn.Set(kv.Key("AAA"), []byte("BBB")) c.Assert(se.StmtCommit(nil), IsNil) p, _, err := planner.Optimize(context.TODO(), se, stmt, s.is) c.Assert(err, IsNil) s.testData.OnRecord(func() { output[i].SQL = tt output[i].Best = core.ToString(p) }) c.Assert(core.ToString(p), Equals, output[i].Best, Commentf("for %s", tt)) } } func (s *testPlanSuite) TestDAGPlanBuilderAgg(c *C) { defer testleak.AfterTest(c)() store, dom, err := newStoreWithBootstrap() c.Assert(err, IsNil) defer func() { dom.Close() store.Close() }() se, err := session.CreateSession4Test(store) c.Assert(err, IsNil) se.Execute(context.Background(), "use test") se.Execute(context.Background(), "set sql_mode='STRICT_TRANS_TABLES'") // disable only full group by ctx := se.(sessionctx.Context) sessionVars := ctx.GetSessionVars() sessionVars.HashAggFinalConcurrency = 1 sessionVars.HashAggPartialConcurrency = 1 var input []string var output []struct { SQL string Best string } s.testData.GetTestCases(c, &input, &output) for i, tt := range input { comment := Commentf("for %s", tt) stmt, err := s.ParseOneStmt(tt, "", "") c.Assert(err, IsNil, comment) p, _, err := planner.Optimize(context.TODO(), se, stmt, s.is) c.Assert(err, IsNil) s.testData.OnRecord(func() { output[i].SQL = tt output[i].Best = core.ToString(p) }) c.Assert(core.ToString(p), Equals, output[i].Best, Commentf("for %s", tt)) } } func (s *testPlanSuite) TestRefine(c *C) { defer testleak.AfterTest(c)() store, dom, err := newStoreWithBootstrap() c.Assert(err, IsNil) defer func() { dom.Close() store.Close() }() se, err := session.CreateSession4Test(store) c.Assert(err, IsNil) _, err = se.Execute(context.Background(), "use test") c.Assert(err, IsNil) var input []string var output []struct { SQL string Best string } s.testData.GetTestCases(c, &input, &output) for i, tt := range input { comment := Commentf("for %s", tt) stmt, err := s.ParseOneStmt(tt, "", "") c.Assert(err, IsNil, comment) sc := se.(sessionctx.Context).GetSessionVars().StmtCtx sc.IgnoreTruncate = false p, _, err := planner.Optimize(context.TODO(), se, stmt, s.is) c.Assert(err, IsNil, comment) s.testData.OnRecord(func() { output[i].SQL = tt output[i].Best = core.ToString(p) }) c.Assert(core.ToString(p), Equals, output[i].Best, comment) } } func (s *testPlanSuite) TestAggEliminator(c *C) { defer testleak.AfterTest(c)() store, dom, err := newStoreWithBootstrap() c.Assert(err, IsNil) defer func() { dom.Close() store.Close() }() se, err := session.CreateSession4Test(store) c.Assert(err, IsNil) _, err = se.Execute(context.Background(), "use test") c.Assert(err, IsNil) se.Execute(context.Background(), "set sql_mode='STRICT_TRANS_TABLES'") // disable only full group by var input []string var output []struct { SQL string Best string } s.testData.GetTestCases(c, &input, &output) for i, tt := range input { comment := Commentf("for %s", tt) stmt, err := s.ParseOneStmt(tt, "", "") c.Assert(err, IsNil, comment) sc := se.(sessionctx.Context).GetSessionVars().StmtCtx sc.IgnoreTruncate = false p, _, err := planner.Optimize(context.TODO(), se, stmt, s.is) c.Assert(err, IsNil) s.testData.OnRecord(func() { output[i].SQL = tt output[i].Best = core.ToString(p) }) c.Assert(core.ToString(p), Equals, output[i].Best, Commentf("for %s", tt)) } } 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(reqType, subType int64) bool { return false } func (s *testPlanSuite) TestRequestTypeSupportedOff(c *C) { defer testleak.AfterTest(c)() store, dom, err := newStoreWithBootstrap() c.Assert(err, IsNil) defer func() { dom.Close() store.Close() }() se, err := session.CreateSession4Test(overrideStore{store}) c.Assert(err, IsNil) _, err = se.Execute(context.Background(), "use test") c.Assert(err, IsNil) sql := "select * from t where a in (1, 10, 20)" expect := "TableReader(Table(t))->Sel([in(test.t.a, 1, 10, 20)])" stmt, err := s.ParseOneStmt(sql, "", "") c.Assert(err, IsNil) p, _, err := planner.Optimize(context.TODO(), se, stmt, s.is) c.Assert(err, IsNil) c.Assert(core.ToString(p), Equals, expect, Commentf("for %s", sql)) } func (s *testPlanSuite) TestIndexJoinUnionScan(c *C) { defer testleak.AfterTest(c)() store, dom, err := newStoreWithBootstrap() c.Assert(err, IsNil) tk := testkit.NewTestKit(c, store) defer func() { dom.Close() store.Close() }() tk.MustExec("use test") var input [][]string var output []struct { SQL []string Plan []string } tk.MustExec("create table t (a int primary key, b int, index idx(a))") tk.MustExec("create table tt (a int primary key) partition by range (a) (partition p0 values less than (100), partition p1 values less than (200))") s.testData.GetTestCases(c, &input, &output) for i, ts := range input { tk.MustExec("begin") for j, tt := range ts { if j != len(ts)-1 { tk.MustExec(tt) } s.testData.OnRecord(func() { output[i].SQL = ts if j == len(ts)-1 { output[i].Plan = s.testData.ConvertRowsToStrings(tk.MustQuery(tt).Rows()) } }) if j == len(ts)-1 { tk.MustQuery(tt).Check(testkit.Rows(output[i].Plan...)) } } tk.MustExec("rollback") } } func (s *testPlanSuite) TestDoSubquery(c *C) { defer testleak.AfterTest(c)() store, dom, err := newStoreWithBootstrap() c.Assert(err, IsNil) defer func() { dom.Close() store.Close() }() se, err := session.CreateSession4Test(store) c.Assert(err, IsNil) _, err = se.Execute(context.Background(), "use test") c.Assert(err, IsNil) tests := []struct { sql string best string }{ { sql: "do 1 in (select a from t)", best: "LeftHashJoin{Dual->PointGet(Handle(t.a)1)}->Projection", }, } for _, tt := range tests { comment := Commentf("for %s", tt.sql) stmt, err := s.ParseOneStmt(tt.sql, "", "") c.Assert(err, IsNil, comment) p, _, err := planner.Optimize(context.TODO(), se, stmt, s.is) c.Assert(err, IsNil) c.Assert(core.ToString(p), Equals, tt.best, comment) } } func (s *testPlanSuite) TestIndexLookupCartesianJoin(c *C) { defer testleak.AfterTest(c)() store, dom, err := newStoreWithBootstrap() c.Assert(err, IsNil) defer func() { dom.Close() store.Close() }() se, err := session.CreateSession4Test(store) c.Assert(err, IsNil) _, err = se.Execute(context.Background(), "use test") c.Assert(err, IsNil) sql := "select /*+ TIDB_INLJ(t1, t2) */ * from t t1 join t t2" stmt, err := s.ParseOneStmt(sql, "", "") c.Assert(err, IsNil) p, _, err := planner.Optimize(context.TODO(), se, stmt, s.is) c.Assert(err, IsNil) c.Assert(core.ToString(p), Equals, "LeftHashJoin{TableReader(Table(t))->TableReader(Table(t))}") warnings := se.GetSessionVars().StmtCtx.GetWarnings() lastWarn := warnings[len(warnings)-1] err = core.ErrInternal.GenWithStack("TIDB_INLJ hint is inapplicable without column equal ON condition") c.Assert(terror.ErrorEqual(err, lastWarn.Err), IsTrue) } func (s *testPlanSuite) TestSemiJoinToInner(c *C) { defer testleak.AfterTest(c)() store, dom, err := newStoreWithBootstrap() c.Assert(err, IsNil) defer func() { dom.Close() store.Close() }() se, err := session.CreateSession4Test(store) c.Assert(err, IsNil) _, err = se.Execute(context.Background(), "use test") c.Assert(err, IsNil) var input []string var output []struct { SQL string Best string } s.testData.GetTestCases(c, &input, &output) for i, tt := range input { stmt, err := s.ParseOneStmt(tt, "", "") c.Assert(err, IsNil) p, _, err := planner.Optimize(context.TODO(), se, stmt, s.is) c.Assert(err, IsNil) s.testData.OnRecord(func() { output[i].SQL = tt output[i].Best = core.ToString(p) }) c.Assert(core.ToString(p), Equals, output[i].Best) } } func (s *testPlanSuite) TestUnmatchedTableInHint(c *C) { defer testleak.AfterTest(c)() store, dom, err := newStoreWithBootstrap() c.Assert(err, IsNil) defer func() { dom.Close() store.Close() }() se, err := session.CreateSession4Test(store) c.Assert(err, IsNil) _, err = se.Execute(context.Background(), "use test") c.Assert(err, IsNil) var input []string var output []struct { SQL string Warning string } s.testData.GetTestCases(c, &input, &output) for i, test := range input { se.GetSessionVars().StmtCtx.SetWarnings(nil) stmt, err := s.ParseOneStmt(test, "", "") c.Assert(err, IsNil) _, _, err = planner.Optimize(context.TODO(), se, stmt, s.is) c.Assert(err, IsNil) warnings := se.GetSessionVars().StmtCtx.GetWarnings() s.testData.OnRecord(func() { output[i].SQL = test if len(warnings) > 0 { output[i].Warning = warnings[0].Err.Error() } }) if output[i].Warning == "" { c.Assert(len(warnings), Equals, 0) } else { c.Assert(len(warnings), Equals, 1) c.Assert(warnings[0].Level, Equals, stmtctx.WarnLevelWarning) c.Assert(warnings[0].Err.Error(), Equals, output[i].Warning) } } } func (s *testPlanSuite) TestHintScope(c *C) { defer testleak.AfterTest(c)() store, dom, err := newStoreWithBootstrap() c.Assert(err, IsNil) defer func() { dom.Close() store.Close() }() se, err := session.CreateSession4Test(store) c.Assert(err, IsNil) _, err = se.Execute(context.Background(), "use test") c.Assert(err, IsNil) var input []string var output []struct { SQL string Best string } s.testData.GetTestCases(c, &input, &output) for i, test := range input { comment := Commentf("case:%v sql:%s", i, test) stmt, err := s.ParseOneStmt(test, "", "") c.Assert(err, IsNil, comment) p, _, err := planner.Optimize(context.Background(), se, stmt, s.is) c.Assert(err, IsNil) s.testData.OnRecord(func() { output[i].SQL = test output[i].Best = core.ToString(p) }) c.Assert(core.ToString(p), Equals, output[i].Best) warnings := se.GetSessionVars().StmtCtx.GetWarnings() c.Assert(warnings, HasLen, 0, comment) } } func (s *testPlanSuite) TestJoinHints(c *C) { defer testleak.AfterTest(c)() store, dom, err := newStoreWithBootstrap() c.Assert(err, IsNil) defer func() { dom.Close() store.Close() }() se, err := session.CreateSession4Test(store) c.Assert(err, IsNil) _, err = se.Execute(context.Background(), "use test") c.Assert(err, IsNil) var input []string var output []struct { SQL string Best string Warning string Hints string } s.testData.GetTestCases(c, &input, &output) ctx := context.Background() for i, test := range input { comment := Commentf("case:%v sql:%s", i, test) stmt, err := s.ParseOneStmt(test, "", "") c.Assert(err, IsNil, comment) se.GetSessionVars().StmtCtx.SetWarnings(nil) p, _, err := planner.Optimize(ctx, se, stmt, s.is) c.Assert(err, IsNil) warnings := se.GetSessionVars().StmtCtx.GetWarnings() s.testData.OnRecord(func() { output[i].SQL = test output[i].Best = core.ToString(p) if len(warnings) > 0 { output[i].Warning = warnings[0].Err.Error() } output[i].Hints = hint.RestoreOptimizerHints(core.GenHintsFromPhysicalPlan(p)) }) c.Assert(core.ToString(p), Equals, output[i].Best) if output[i].Warning == "" { c.Assert(len(warnings), Equals, 0) } else { c.Assert(len(warnings), Equals, 1, Commentf("%v", warnings)) c.Assert(warnings[0].Level, Equals, stmtctx.WarnLevelWarning) c.Assert(warnings[0].Err.Error(), Equals, output[i].Warning) } c.Assert(hint.RestoreOptimizerHints(core.GenHintsFromPhysicalPlan(p)), Equals, output[i].Hints, comment) } } func (s *testPlanSuite) TestAggregationHints(c *C) { defer testleak.AfterTest(c)() store, dom, err := newStoreWithBootstrap() c.Assert(err, IsNil) defer func() { dom.Close() store.Close() }() se, err := session.CreateSession4Test(store) c.Assert(err, IsNil) _, err = se.Execute(context.Background(), "use test") c.Assert(err, IsNil) sessionVars := se.(sessionctx.Context).GetSessionVars() sessionVars.HashAggFinalConcurrency = 1 sessionVars.HashAggPartialConcurrency = 1 var input []struct { SQL string AggPushDown bool } var output []struct { SQL string Best string Warning string } s.testData.GetTestCases(c, &input, &output) ctx := context.Background() for i, test := range input { comment := Commentf("case:%v sql:%s", i, test) se.GetSessionVars().StmtCtx.SetWarnings(nil) se.GetSessionVars().AllowAggPushDown = test.AggPushDown stmt, err := s.ParseOneStmt(test.SQL, "", "") c.Assert(err, IsNil, comment) p, _, err := planner.Optimize(ctx, se, stmt, s.is) c.Assert(err, IsNil) warnings := se.GetSessionVars().StmtCtx.GetWarnings() s.testData.OnRecord(func() { output[i].SQL = test.SQL output[i].Best = core.ToString(p) if len(warnings) > 0 { output[i].Warning = warnings[0].Err.Error() } }) c.Assert(core.ToString(p), Equals, output[i].Best, comment) if output[i].Warning == "" { c.Assert(len(warnings), Equals, 0, comment) } else { c.Assert(len(warnings), Equals, 1, comment) c.Assert(warnings[0].Level, Equals, stmtctx.WarnLevelWarning, comment) c.Assert(warnings[0].Err.Error(), Equals, output[i].Warning, comment) } } } func (s *testPlanSuite) TestExplainJoinHints(c *C) { defer testleak.AfterTest(c)() store, dom, err := newStoreWithBootstrap() c.Assert(err, IsNil) defer func() { dom.Close() store.Close() }() tk := testkit.NewTestKit(c, store) tk.MustExec("use test") tk.MustExec("drop table if exists t") tk.MustExec("create table t(a int, b int, c int, key(b), key(c))") tk.MustQuery("explain format='hint' select /*+ inl_hash_join(t2) */ * from t t1 inner join t t2 on t1.b = t2.b and t1.c = 1").Check(testkit.Rows( "use_index(@`sel_1` `test`.`t1` `c`), use_index(@`sel_1` `test`.`t2` `b`), inl_hash_join(@`sel_1` `test`.`t2`), inl_hash_join(`t2`)", )) } func (s *testPlanSuite) TestAggToCopHint(c *C) { defer testleak.AfterTest(c)() store, dom, err := newStoreWithBootstrap() c.Assert(err, IsNil) defer func() { dom.Close() store.Close() }() tk := testkit.NewTestKit(c, store) tk.MustExec("use test") tk.MustExec("drop table if exists ta") tk.MustExec("create table ta(a int, b int, index(a))") var ( input []string output []struct { SQL string Best string Warning string } ) s.testData.GetTestCases(c, &input, &output) ctx := context.Background() is := domain.GetDomain(tk.Se).InfoSchema() for i, test := range input { comment := Commentf("case:%v sql:%s", i, test) s.testData.OnRecord(func() { output[i].SQL = test }) c.Assert(test, Equals, output[i].SQL, comment) tk.Se.GetSessionVars().StmtCtx.SetWarnings(nil) stmt, err := s.ParseOneStmt(test, "", "") c.Assert(err, IsNil, comment) p, _, err := planner.Optimize(ctx, tk.Se, stmt, is) c.Assert(err, IsNil) planString := core.ToString(p) s.testData.OnRecord(func() { output[i].Best = planString }) c.Assert(planString, Equals, output[i].Best, comment) warnings := tk.Se.GetSessionVars().StmtCtx.GetWarnings() s.testData.OnRecord(func() { if len(warnings) > 0 { output[i].Warning = warnings[0].Err.Error() } }) if output[i].Warning == "" { c.Assert(len(warnings), Equals, 0, comment) } else { c.Assert(len(warnings), Equals, 1, comment) c.Assert(warnings[0].Level, Equals, stmtctx.WarnLevelWarning, comment) c.Assert(warnings[0].Err.Error(), Equals, output[i].Warning, comment) } } } func (s *testPlanSuite) TestLimitToCopHint(c *C) { defer testleak.AfterTest(c)() store, dom, err := newStoreWithBootstrap() c.Assert(err, IsNil) defer func() { dom.Close() store.Close() }() tk := testkit.NewTestKit(c, store) tk.MustExec("use test") tk.MustExec("drop table if exists tn") tk.MustExec("create table tn(a int, b int, c int, d int, key (a, b, c, d))") var ( input []string output []struct { SQL string Plan []string Warning string } ) s.testData.GetTestCases(c, &input, &output) for i, ts := range input { s.testData.OnRecord(func() { output[i].SQL = ts output[i].Plan = s.testData.ConvertRowsToStrings(tk.MustQuery("explain " + ts).Rows()) }) tk.MustQuery("explain " + ts).Check(testkit.Rows(output[i].Plan...)) comment := Commentf("case:%v sql:%s", i, ts) warnings := tk.Se.GetSessionVars().StmtCtx.GetWarnings() s.testData.OnRecord(func() { if len(warnings) > 0 { output[i].Warning = warnings[0].Err.Error() } }) if output[i].Warning == "" { c.Assert(len(warnings), Equals, 0, comment) } else { c.Assert(len(warnings), Equals, 1, comment) c.Assert(warnings[0].Level, Equals, stmtctx.WarnLevelWarning, comment) c.Assert(warnings[0].Err.Error(), Equals, output[i].Warning, comment) } } } func (s *testPlanSuite) TestPushdownDistinctEnable(c *C) { defer testleak.AfterTest(c)() var ( input []string output []struct { SQL string Plan []string Result []string } ) s.testData.GetTestCases(c, &input, &output) vars := []string{ fmt.Sprintf("set @@session.%s = 1", variable.TiDBOptDistinctAggPushDown), "set session tidb_opt_agg_push_down = 1", } s.doTestPushdownDistinct(c, vars, input, output) } func (s *testPlanSuite) TestPushdownDistinctDisable(c *C) { defer testleak.AfterTest(c)() var ( input []string output []struct { SQL string Plan []string Result []string } ) s.testData.GetTestCases(c, &input, &output) vars := []string{ fmt.Sprintf("set @@session.%s = 0", variable.TiDBOptDistinctAggPushDown), "set session tidb_opt_agg_push_down = 1", } s.doTestPushdownDistinct(c, vars, input, output) } func (s *testPlanSuite) TestPushdownDistinctEnableAggPushDownDisable(c *C) { var ( input []string output []struct { SQL string Plan []string Result []string } ) s.testData.GetTestCases(c, &input, &output) vars := []string{ fmt.Sprintf("set @@session.%s = 1", variable.TiDBOptDistinctAggPushDown), "set session tidb_opt_agg_push_down = 0", } s.doTestPushdownDistinct(c, vars, input, output) } func (s *testPlanSuite) doTestPushdownDistinct(c *C, vars, input []string, output []struct { SQL string Plan []string Result []string }) { store, dom, err := newStoreWithBootstrap() c.Assert(err, IsNil) defer func() { dom.Close() store.Close() }() tk := testkit.NewTestKit(c, store) tk.MustExec("use test") tk.MustExec("drop table if exists t") tk.MustExec("create table t(a int, b int, c int, index(c))") tk.MustExec("insert into t values (1, 1, 1), (1, 1, 3), (1, 2, 3), (2, 1, 3), (1, 2, NULL);") tk.MustExec("drop table if exists pt") tk.MustExec(`CREATE TABLE pt (a int, b int) PARTITION BY RANGE (a) ( PARTITION p0 VALUES LESS THAN (2), PARTITION p1 VALUES LESS THAN (100) );`) tk.MustExec("drop table if exists ta") tk.MustExec("create table ta(a int);") tk.MustExec("insert into ta values(1), (1);") tk.MustExec("drop table if exists tb") tk.MustExec("create table tb(a int);") tk.MustExec("insert into tb values(1), (1);") tk.MustExec("set session sql_mode=''") tk.MustExec(fmt.Sprintf("set session %s=1", variable.TiDBHashAggPartialConcurrency)) tk.MustExec(fmt.Sprintf("set session %s=1", variable.TiDBHashAggFinalConcurrency)) for _, v := range vars { tk.MustExec(v) } for i, ts := range input { s.testData.OnRecord(func() { output[i].SQL = ts output[i].Plan = s.testData.ConvertRowsToStrings(tk.MustQuery("explain " + ts).Rows()) output[i].Result = s.testData.ConvertRowsToStrings(tk.MustQuery(ts).Sort().Rows()) }) tk.MustQuery("explain " + ts).Check(testkit.Rows(output[i].Plan...)) tk.MustQuery(ts).Sort().Check(testkit.Rows(output[i].Result...)) } } func (s *testPlanSuite) TestGroupConcatOrderby(c *C) { var ( input []string output []struct { SQL string Plan []string Result []string } ) s.testData.GetTestCases(c, &input, &output) store, dom, err := newStoreWithBootstrap() c.Assert(err, IsNil) defer func() { dom.Close() store.Close() }() tk := testkit.NewTestKit(c, store) tk.MustExec("use test") tk.MustExec("drop table if exists test;") tk.MustExec("create table test(id int, name int)") tk.MustExec("insert into test values(1, 10);") tk.MustExec("insert into test values(1, 20);") tk.MustExec("insert into test values(1, 30);") tk.MustExec("insert into test values(2, 20);") tk.MustExec("insert into test values(3, 200);") tk.MustExec("insert into test values(3, 500);") tk.MustExec("drop table if exists ptest;") tk.MustExec("CREATE TABLE ptest (id int,name int) PARTITION BY RANGE ( id ) " + "(PARTITION `p0` VALUES LESS THAN (2), PARTITION `p1` VALUES LESS THAN (11))") tk.MustExec("insert into ptest select * from test;") tk.MustExec(fmt.Sprintf("set session tidb_opt_distinct_agg_push_down = %v", 1)) tk.MustExec(fmt.Sprintf("set session tidb_opt_agg_push_down = %v", 1)) for i, ts := range input { s.testData.OnRecord(func() { output[i].SQL = ts output[i].Plan = s.testData.ConvertRowsToStrings(tk.MustQuery("explain " + ts).Rows()) output[i].Result = s.testData.ConvertRowsToStrings(tk.MustQuery(ts).Sort().Rows()) }) tk.MustQuery("explain " + ts).Check(testkit.Rows(output[i].Plan...)) tk.MustQuery(ts).Check(testkit.Rows(output[i].Result...)) } } func (s *testPlanSuite) TestHintAlias(c *C) { defer testleak.AfterTest(c)() store, dom, err := newStoreWithBootstrap() c.Assert(err, IsNil) defer func() { dom.Close() store.Close() }() se, err := session.CreateSession4Test(store) c.Assert(err, IsNil) _, err = se.Execute(context.Background(), "use test") c.Assert(err, IsNil) 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() for i, tt := range tests { comment := Commentf("case:%v sql1:%s sql2:%s", i, tt.sql1, tt.sql2) stmt1, err := s.ParseOneStmt(tt.sql1, "", "") c.Assert(err, IsNil, comment) stmt2, err := s.ParseOneStmt(tt.sql2, "", "") c.Assert(err, IsNil, comment) p1, _, err := planner.Optimize(ctx, se, stmt1, s.is) c.Assert(err, IsNil) p2, _, err := planner.Optimize(ctx, se, stmt2, s.is) c.Assert(err, IsNil) c.Assert(core.ToString(p1), Equals, core.ToString(p2)) } } func (s *testPlanSuite) TestIndexHint(c *C) { defer testleak.AfterTest(c)() store, dom, err := newStoreWithBootstrap() c.Assert(err, IsNil) defer func() { dom.Close() store.Close() }() se, err := session.CreateSession4Test(store) c.Assert(err, IsNil) _, err = se.Execute(context.Background(), "use test") c.Assert(err, IsNil) var input []string var output []struct { SQL string Best string HasWarn bool Hints string } s.testData.GetTestCases(c, &input, &output) ctx := context.Background() for i, test := range input { comment := Commentf("case:%v sql:%s", i, test) se.GetSessionVars().StmtCtx.SetWarnings(nil) stmt, err := s.ParseOneStmt(test, "", "") c.Assert(err, IsNil, comment) p, _, err := planner.Optimize(ctx, se, stmt, s.is) c.Assert(err, IsNil) s.testData.OnRecord(func() { output[i].SQL = test output[i].Best = core.ToString(p) output[i].HasWarn = len(se.GetSessionVars().StmtCtx.GetWarnings()) > 0 output[i].Hints = hint.RestoreOptimizerHints(core.GenHintsFromPhysicalPlan(p)) }) c.Assert(core.ToString(p), Equals, output[i].Best, comment) warnings := se.GetSessionVars().StmtCtx.GetWarnings() if output[i].HasWarn { c.Assert(warnings, HasLen, 1, comment) } else { c.Assert(warnings, HasLen, 0, comment) } c.Assert(hint.RestoreOptimizerHints(core.GenHintsFromPhysicalPlan(p)), Equals, output[i].Hints, comment) } } func (s *testPlanSuite) TestIndexMergeHint(c *C) { defer testleak.AfterTest(c)() store, dom, err := newStoreWithBootstrap() c.Assert(err, IsNil) defer func() { dom.Close() store.Close() }() se, err := session.CreateSession4Test(store) c.Assert(err, IsNil) _, err = se.Execute(context.Background(), "use test") c.Assert(err, IsNil) var input []string var output []struct { SQL string Best string HasWarn bool Hints string } s.testData.GetTestCases(c, &input, &output) ctx := context.Background() for i, test := range input { comment := Commentf("case:%v sql:%s", i, test) se.GetSessionVars().StmtCtx.SetWarnings(nil) stmt, err := s.ParseOneStmt(test, "", "") c.Assert(err, IsNil, comment) sctx := se.(sessionctx.Context) err = executor.ResetContextOfStmt(sctx, stmt) c.Assert(err, IsNil) p, _, err := planner.Optimize(ctx, se, stmt, s.is) c.Assert(err, IsNil) s.testData.OnRecord(func() { output[i].SQL = test output[i].Best = core.ToString(p) output[i].HasWarn = len(se.GetSessionVars().StmtCtx.GetWarnings()) > 0 output[i].Hints = hint.RestoreOptimizerHints(core.GenHintsFromPhysicalPlan(p)) }) c.Assert(core.ToString(p), Equals, output[i].Best, comment) warnings := se.GetSessionVars().StmtCtx.GetWarnings() if output[i].HasWarn { c.Assert(warnings, HasLen, 1, comment) } else { c.Assert(warnings, HasLen, 0, comment) } c.Assert(hint.RestoreOptimizerHints(core.GenHintsFromPhysicalPlan(p)), Equals, output[i].Hints, comment) } } func (s *testPlanSuite) TestQueryBlockHint(c *C) { defer testleak.AfterTest(c)() store, dom, err := newStoreWithBootstrap() c.Assert(err, IsNil) defer func() { dom.Close() store.Close() }() se, err := session.CreateSession4Test(store) c.Assert(err, IsNil) _, err = se.Execute(context.Background(), "use test") c.Assert(err, IsNil) var input []string var output []struct { SQL string Plan string Hints string } s.testData.GetTestCases(c, &input, &output) ctx := context.TODO() for i, tt := range input { comment := Commentf("case:%v sql: %s", i, tt) stmt, err := s.ParseOneStmt(tt, "", "") c.Assert(err, IsNil, comment) p, _, err := planner.Optimize(ctx, se, stmt, s.is) c.Assert(err, IsNil, comment) s.testData.OnRecord(func() { output[i].SQL = tt output[i].Plan = core.ToString(p) output[i].Hints = hint.RestoreOptimizerHints(core.GenHintsFromPhysicalPlan(p)) }) c.Assert(core.ToString(p), Equals, output[i].Plan, comment) c.Assert(hint.RestoreOptimizerHints(core.GenHintsFromPhysicalPlan(p)), Equals, output[i].Hints, comment) } } func (s *testPlanSuite) TestInlineProjection(c *C) { defer testleak.AfterTest(c)() store, dom, err := newStoreWithBootstrap() c.Assert(err, IsNil) defer func() { dom.Close() store.Close() }() se, err := session.CreateSession4Test(store) c.Assert(err, IsNil) ctx := context.Background() _, err = se.Execute(ctx, "use test") c.Assert(err, IsNil) _, err = se.Execute(ctx, `drop table if exists test.t1, test.t2;`) c.Assert(err, IsNil) _, err = se.Execute(ctx, `create table test.t1(a bigint, b bigint, index idx_a(a), index idx_b(b));`) c.Assert(err, IsNil) _, err = se.Execute(ctx, `create table test.t2(a bigint, b bigint, index idx_a(a), index idx_b(b));`) c.Assert(err, IsNil) var input []string var output []struct { SQL string Plan string Hints string } is := domain.GetDomain(se).InfoSchema() s.testData.GetTestCases(c, &input, &output) for i, tt := range input { comment := Commentf("case:%v sql: %s", i, tt) stmt, err := s.ParseOneStmt(tt, "", "") c.Assert(err, IsNil, comment) p, _, err := planner.Optimize(ctx, se, stmt, is) c.Assert(err, IsNil, comment) s.testData.OnRecord(func() { output[i].SQL = tt output[i].Plan = core.ToString(p) output[i].Hints = hint.RestoreOptimizerHints(core.GenHintsFromPhysicalPlan(p)) }) c.Assert(core.ToString(p), Equals, output[i].Plan, comment) c.Assert(hint.RestoreOptimizerHints(core.GenHintsFromPhysicalPlan(p)), Equals, output[i].Hints, comment) } } func (s *testPlanSuite) TestDAGPlanBuilderSplitAvg(c *C) { defer testleak.AfterTest(c)() store, dom, err := newStoreWithBootstrap() c.Assert(err, IsNil) defer func() { dom.Close() store.Close() }() se, err := session.CreateSession4Test(store) c.Assert(err, IsNil) _, err = se.Execute(context.Background(), "use test") c.Assert(err, IsNil) tests := []struct { sql string plan string }{ { sql: "select avg(a),avg(b),avg(c) from t", plan: "TableReader(Table(t)->StreamAgg)->StreamAgg", }, { sql: "select /*+ HASH_AGG() */ avg(a),avg(b),avg(c) from t", plan: "TableReader(Table(t)->HashAgg)->HashAgg", }, } for _, tt := range tests { comment := Commentf("for %s", tt.sql) stmt, err := s.ParseOneStmt(tt.sql, "", "") c.Assert(err, IsNil, comment) core.Preprocess(se, stmt, s.is) p, _, err := planner.Optimize(context.TODO(), se, stmt, s.is) c.Assert(err, IsNil, comment) c.Assert(core.ToString(p), Equals, tt.plan, comment) root, ok := p.(core.PhysicalPlan) if !ok { continue } testDAGPlanBuilderSplitAvg(c, root) } } func testDAGPlanBuilderSplitAvg(c *C, root core.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 { c.Assert(agg.Schema().Columns[i].RetType, Equals, aggfunc.RetTp) } } if agg, ok := baseAgg.(*core.PhysicalStreamAgg); ok { for i, aggfunc := range agg.AggFuncs { c.Assert(agg.Schema().Columns[i].RetType, Equals, aggfunc.RetTp) } } } } childs := root.Children() if childs == nil { return } for _, son := range childs { testDAGPlanBuilderSplitAvg(c, son) } } func (s *testPlanSuite) TestIndexJoinHint(c *C) { defer testleak.AfterTest(c)() store, dom, err := newStoreWithBootstrap() c.Assert(err, IsNil) defer func() { dom.Close() store.Close() }() se, err := session.CreateSession4Test(store) c.Assert(err, IsNil) ctx := context.Background() _, err = se.Execute(ctx, "use test") c.Assert(err, IsNil) _, err = se.Execute(ctx, `drop table if exists test.t1, test.t2, test.t;`) c.Assert(err, IsNil) _, err = se.Execute(ctx, `create table test.t1(a bigint, b bigint, index idx_a(a), index idx_b(b));`) c.Assert(err, IsNil) _, err = se.Execute(ctx, `create table test.t2(a bigint, b bigint, index idx_a(a), index idx_b(b));`) c.Assert(err, IsNil) _, err = se.Execute(ctx, "CREATE TABLE `t` ( `a` bigint(20) NOT NULL, `b` tinyint(1) DEFAULT NULL, `c` datetime DEFAULT NULL, `d` int(10) unsigned DEFAULT NULL, `e` varchar(20) DEFAULT NULL, `f` double DEFAULT NULL, `g` decimal(30,5) DEFAULT NULL, `h` float DEFAULT NULL, `i` date DEFAULT NULL, `j` timestamp NULL DEFAULT NULL, PRIMARY KEY (`a`), UNIQUE KEY `b` (`b`), KEY `c` (`c`,`d`,`e`), KEY `f` (`f`), KEY `g` (`g`,`h`), KEY `g_2` (`g`), UNIQUE KEY `g_3` (`g`), KEY `i` (`i`) );") c.Assert(err, IsNil) var input []string var output []struct { SQL string Plan string } is := domain.GetDomain(se).InfoSchema() s.testData.GetTestCases(c, &input, &output) for i, tt := range input { comment := Commentf("case:%v sql: %s", i, tt) stmt, err := s.ParseOneStmt(tt, "", "") c.Assert(err, IsNil, comment) p, _, err := planner.Optimize(ctx, se, stmt, is) c.Assert(err, IsNil, comment) s.testData.OnRecord(func() { output[i].SQL = tt output[i].Plan = core.ToString(p) }) c.Assert(core.ToString(p), Equals, output[i].Plan, comment) } } func (s *testPlanSuite) TestDAGPlanBuilderWindow(c *C) { defer testleak.AfterTest(c)() var input []string var output []struct { SQL string Best string } s.testData.GetTestCases(c, &input, &output) vars := []string{ "set @@session.tidb_window_concurrency = 1", } s.doTestDAGPlanBuilderWindow(c, vars, input, output) } func (s *testPlanSuite) TestDAGPlanBuilderWindowParallel(c *C) { defer testleak.AfterTest(c)() var input []string var output []struct { SQL string Best string } s.testData.GetTestCases(c, &input, &output) vars := []string{ "set @@session.tidb_window_concurrency = 4", } s.doTestDAGPlanBuilderWindow(c, vars, input, output) } func (s *testPlanSuite) doTestDAGPlanBuilderWindow(c *C, vars, input []string, output []struct { SQL string Best string }) { store, dom, err := newStoreWithBootstrap() c.Assert(err, IsNil) defer func() { dom.Close() store.Close() }() se, err := session.CreateSession4Test(store) c.Assert(err, IsNil) ctx := context.Background() _, err = se.Execute(ctx, "use test") c.Assert(err, IsNil) for _, v := range vars { _, err = se.Execute(ctx, v) c.Assert(err, IsNil) } for i, tt := range input { comment := Commentf("case:%v sql:%s", i, tt) stmt, err := s.ParseOneStmt(tt, "", "") c.Assert(err, IsNil, comment) err = se.NewTxn(context.Background()) c.Assert(err, IsNil) p, _, err := planner.Optimize(context.TODO(), se, stmt, s.is) c.Assert(err, IsNil) s.testData.OnRecord(func() { output[i].SQL = tt output[i].Best = core.ToString(p) }) c.Assert(core.ToString(p), Equals, output[i].Best, comment) } } func (s *testPlanSuite) TestNominalSort(c *C) { defer testleak.AfterTest(c)() store, dom, err := newStoreWithBootstrap() c.Assert(err, IsNil) tk := testkit.NewTestKit(c, store) defer func() { dom.Close() store.Close() }() tk.MustExec("use test") var input []string var output []struct { SQL string Plan []string Result []string } tk.MustExec("create table t (a int, b int, index idx_a(a), index idx_b(b))") tk.MustExec("insert into t values(1, 1)") tk.MustExec("insert into t values(1, 2)") tk.MustExec("insert into t values(2, 4)") tk.MustExec("insert into t values(3, 5)") s.testData.GetTestCases(c, &input, &output) for i, ts := range input { s.testData.OnRecord(func() { output[i].SQL = ts output[i].Plan = s.testData.ConvertRowsToStrings(tk.MustQuery("explain " + ts).Rows()) output[i].Result = s.testData.ConvertRowsToStrings(tk.MustQuery(ts).Rows()) }) tk.MustQuery("explain " + ts).Check(testkit.Rows(output[i].Plan...)) tk.MustQuery(ts).Check(testkit.Rows(output[i].Result...)) } } func (s *testPlanSuite) TestHintFromDiffDatabase(c *C) { defer testleak.AfterTest(c)() store, dom, err := newStoreWithBootstrap() c.Assert(err, IsNil) defer func() { dom.Close() store.Close() }() se, err := session.CreateSession4Test(store) c.Assert(err, IsNil) ctx := context.Background() _, err = se.Execute(ctx, "use test") c.Assert(err, IsNil) _, err = se.Execute(ctx, `drop table if exists test.t1`) c.Assert(err, IsNil) _, err = se.Execute(ctx, `create table test.t1(a bigint, index idx_a(a));`) c.Assert(err, IsNil) _, err = se.Execute(ctx, `create table test.t2(a bigint, index idx_a(a));`) c.Assert(err, IsNil) _, err = se.Execute(ctx, "drop database if exists test2") c.Assert(err, IsNil) _, err = se.Execute(ctx, "create database test2") c.Assert(err, IsNil) _, err = se.Execute(ctx, "use test2") c.Assert(err, IsNil) var input []string var output []struct { SQL string Plan string } is := domain.GetDomain(se).InfoSchema() s.testData.GetTestCases(c, &input, &output) for i, tt := range input { comment := Commentf("case:%v sql: %s", i, tt) stmt, err := s.ParseOneStmt(tt, "", "") c.Assert(err, IsNil, comment) p, _, err := planner.Optimize(ctx, se, stmt, is) c.Assert(err, IsNil, comment) s.testData.OnRecord(func() { output[i].SQL = tt output[i].Plan = core.ToString(p) }) c.Assert(core.ToString(p), Equals, output[i].Plan, comment) } }