diff --git a/executor/builder.go b/executor/builder.go index a718577cea424..19353296e5e8a 100644 --- a/executor/builder.go +++ b/executor/builder.go @@ -764,6 +764,7 @@ func (b *executorBuilder) buildShow(v *plannercore.PhysicalShow) Executor { IfNotExists: v.IfNotExists, GlobalScope: v.GlobalScope, Extended: v.Extended, + Extractor: v.Extractor, } if e.Tp == ast.ShowMasterStatus { // show master status need start ts. diff --git a/executor/show.go b/executor/show.go index 8449483a3787c..5116ea81056e8 100644 --- a/executor/show.go +++ b/executor/show.go @@ -20,6 +20,7 @@ import ( gjson "encoding/json" "fmt" "reflect" + "regexp" "sort" "strconv" "strings" @@ -84,6 +85,7 @@ type ShowExec struct { Flag int // Some flag parsed from sql, such as FULL. Roles []*auth.RoleIdentity // Used for show grants. User *auth.UserIdentity // Used by show grants, show create user. + Extractor plannercore.ShowPredicateExtractor is infoschema.InfoSchema @@ -514,10 +516,23 @@ func (e *ShowExec) fetchShowTableStatus(ctx context.Context) error { func (e *ShowExec) fetchShowColumns(ctx context.Context) error { tb, err := e.getTable() - if err != nil { return errors.Trace(err) } + var ( + fieldPatternsRegexp *regexp.Regexp + FieldFilterEnable bool + fieldFilter string + ) + if e.Extractor != nil { + extractor := (e.Extractor).(*plannercore.ShowColumnsTableExtractor) + if extractor.FieldPatterns != "" { + fieldPatternsRegexp = regexp.MustCompile(extractor.FieldPatterns) + } + FieldFilterEnable = extractor.Field != "" + fieldFilter = extractor.Field + } + checker := privilege.GetPrivilegeManager(e.ctx) activeRoles := e.ctx.GetSessionVars().ActiveRoles if checker != nil && e.ctx.GetSessionVars().User != nil && !checker.RequestVerification(activeRoles, e.DBName.O, tb.Meta().Name.O, "", mysql.InsertPriv|mysql.SelectPriv|mysql.UpdatePriv|mysql.ReferencesPriv) { @@ -536,10 +551,11 @@ func (e *ShowExec) fetchShowColumns(ctx context.Context) error { return err } for _, col := range cols { - if e.Column != nil && e.Column.Name.L != col.Name.L { + if FieldFilterEnable && col.Name.L != fieldFilter { + continue + } else if fieldPatternsRegexp != nil && !fieldPatternsRegexp.MatchString(col.Name.L) { continue } - desc := table.NewColDesc(col) var columnDefault interface{} if desc.DefaultValue != nil { diff --git a/planner/cascades/implementation_rules.go b/planner/cascades/implementation_rules.go index b686d17d72469..7b30b67287202 100644 --- a/planner/cascades/implementation_rules.go +++ b/planner/cascades/implementation_rules.go @@ -242,12 +242,14 @@ func (r *ImplShow) Match(expr *memo.GroupExpr, prop *property.PhysicalProperty) func (r *ImplShow) OnImplement(expr *memo.GroupExpr, reqProp *property.PhysicalProperty) ([]memo.Implementation, error) { logicProp := expr.Group.Prop show := expr.ExprNode.(*plannercore.LogicalShow) - // TODO(zz-jason): unifying LogicalShow and PhysicalShow to a single // struct. So that we don't need to create a new PhysicalShow object, which // can help us to reduce the gc pressure of golang runtime and improve the // overall performance. - showPhys := plannercore.PhysicalShow{ShowContents: show.ShowContents}.Init(show.SCtx()) + showPhys := plannercore.PhysicalShow{ + ShowContents: show.ShowContents, + Extractor: show.Extractor, + }.Init(show.SCtx()) showPhys.SetSchema(logicProp.Schema) return []memo.Implementation{impl.NewShowImpl(showPhys)}, nil } diff --git a/planner/core/find_best_task.go b/planner/core/find_best_task.go index ff90a92b9b497..c2535efd48db4 100644 --- a/planner/core/find_best_task.go +++ b/planner/core/find_best_task.go @@ -155,7 +155,7 @@ func (p *LogicalShow) findBestTask(prop *property.PhysicalProperty, planCounter if !prop.IsEmpty() || planCounter.Empty() { return invalidTask, 0, nil } - pShow := PhysicalShow{ShowContents: p.ShowContents}.Init(p.ctx) + pShow := PhysicalShow{ShowContents: p.ShowContents, Extractor: p.Extractor}.Init(p.ctx) pShow.SetSchema(p.schema) planCounter.Dec(1) return &rootTask{p: pShow}, 1, nil diff --git a/planner/core/logical_plans.go b/planner/core/logical_plans.go index c322f7e9489c1..79eb82a76b47a 100644 --- a/planner/core/logical_plans.go +++ b/planner/core/logical_plans.go @@ -1261,6 +1261,8 @@ type ShowContents struct { type LogicalShow struct { logicalSchemaProducer ShowContents + + Extractor ShowPredicateExtractor } // LogicalShowDDLJobs is for showing DDL job list. diff --git a/planner/core/memtable_predicate_extractor_test.go b/planner/core/memtable_predicate_extractor_test.go index 311b9ae4a8838..663da2cfb92d2 100644 --- a/planner/core/memtable_predicate_extractor_test.go +++ b/planner/core/memtable_predicate_extractor_test.go @@ -18,57 +18,32 @@ import ( "context" "regexp" "sort" + "testing" "time" - . "github.com/pingcap/check" "github.com/pingcap/tidb/domain" - "github.com/pingcap/tidb/kv" + "github.com/pingcap/tidb/errno" "github.com/pingcap/tidb/parser" plannercore "github.com/pingcap/tidb/planner/core" "github.com/pingcap/tidb/session" - "github.com/pingcap/tidb/store/mockstore" + "github.com/pingcap/tidb/testkit" "github.com/pingcap/tidb/util/hint" "github.com/pingcap/tidb/util/set" + "github.com/pingcap/tidb/util/testutil" + "github.com/stretchr/testify/require" ) -var _ = Suite(&extractorSuite{}) - -type extractorSuite struct { - store kv.Storage - dom *domain.Domain -} - -func (s *extractorSuite) SetUpSuite(c *C) { - store, err := mockstore.NewMockStore() - c.Assert(err, IsNil) - c.Assert(store, NotNil) - - session.SetSchemaLease(0) - session.DisableStats4Test() - dom, err := session.BootstrapSession(store) - c.Assert(err, IsNil) - c.Assert(dom, NotNil) - - s.store = store - s.dom = dom -} - -func (s *extractorSuite) TearDownSuite(c *C) { - s.dom.Close() - s.store.Close() -} - -func (s *extractorSuite) getLogicalMemTable(c *C, se session.Session, parser *parser.Parser, sql string) *plannercore.LogicalMemTable { +func getLogicalMemTable(t *testing.T, dom *domain.Domain, se session.Session, parser *parser.Parser, sql string) *plannercore.LogicalMemTable { stmt, err := parser.ParseOneStmt(sql, "", "") - c.Assert(err, IsNil) + require.NoError(t, err) ctx := context.Background() - builder, _ := plannercore.NewPlanBuilder().Init(se, s.dom.InfoSchema(), &hint.BlockHintProcessor{}) + builder, _ := plannercore.NewPlanBuilder().Init(se, dom.InfoSchema(), &hint.BlockHintProcessor{}) plan, err := builder.Build(ctx, stmt) - c.Assert(err, IsNil) + require.NoError(t, err) logicalPlan, err := plannercore.LogicalOptimize(ctx, builder.GetOptFlag(), plan.(plannercore.LogicalPlan)) - c.Assert(err, IsNil) + require.NoError(t, err) // Obtain the leaf plan leafPlan := logicalPlan @@ -80,9 +55,12 @@ func (s *extractorSuite) getLogicalMemTable(c *C, se session.Session, parser *pa return logicalMemTable } -func (s *extractorSuite) TestClusterConfigTableExtractor(c *C) { - se, err := session.CreateSession4Test(s.store) - c.Assert(err, IsNil) +func TestClusterConfigTableExtractor(t *testing.T) { + store, dom, clean := testkit.CreateMockStoreAndDomain(t) + defer clean() + + se, err := session.CreateSession4Test(store) + require.NoError(t, err) parser := parser.New() var cases = []struct { @@ -236,25 +214,28 @@ func (s *extractorSuite) TestClusterConfigTableExtractor(c *C) { }, } for _, ca := range cases { - logicalMemTable := s.getLogicalMemTable(c, se, parser, ca.sql) - c.Assert(logicalMemTable.Extractor, NotNil) + logicalMemTable := getLogicalMemTable(t, dom, se, parser, ca.sql) + require.NotNil(t, logicalMemTable.Extractor) clusterConfigExtractor := logicalMemTable.Extractor.(*plannercore.ClusterTableExtractor) - c.Assert(clusterConfigExtractor.NodeTypes, DeepEquals, ca.nodeTypes, Commentf("SQL: %v", ca.sql)) - c.Assert(clusterConfigExtractor.Instances, DeepEquals, ca.instances, Commentf("SQL: %v", ca.sql)) - c.Assert(clusterConfigExtractor.SkipRequest, DeepEquals, ca.skipRequest, Commentf("SQL: %v", ca.sql)) + require.EqualValues(t, ca.nodeTypes, clusterConfigExtractor.NodeTypes, "SQL: %v", ca.sql) + require.EqualValues(t, ca.instances, clusterConfigExtractor.Instances, "SQL: %v", ca.sql) + require.EqualValues(t, ca.skipRequest, clusterConfigExtractor.SkipRequest, "SQL: %v", ca.sql) } } -func timestamp(c *C, s string) int64 { - t, err := time.ParseInLocation("2006-01-02 15:04:05.999", s, time.Local) - c.Assert(err, IsNil) - return t.UnixNano() / int64(time.Millisecond) +func timestamp(t *testing.T, s string) int64 { + tt, err := time.ParseInLocation("2006-01-02 15:04:05.999", s, time.Local) + require.NoError(t, err) + return tt.UnixNano() / int64(time.Millisecond) } -func (s *extractorSuite) TestClusterLogTableExtractor(c *C) { - se, err := session.CreateSession4Test(s.store) - c.Assert(err, IsNil) +func TestClusterLogTableExtractor(t *testing.T) { + store, dom, clean := testkit.CreateMockStoreAndDomain(t) + defer clean() + + se, err := session.CreateSession4Test(store) + require.NoError(t, err) parser := parser.New() var cases = []struct { @@ -414,77 +395,77 @@ func (s *extractorSuite) TestClusterLogTableExtractor(c *C) { sql: "select * from information_schema.cluster_log where time='2019-10-10 10:10:10'", nodeTypes: set.NewStringSet(), instances: set.NewStringSet(), - startTime: timestamp(c, "2019-10-10 10:10:10"), - endTime: timestamp(c, "2019-10-10 10:10:10"), + startTime: timestamp(t, "2019-10-10 10:10:10"), + endTime: timestamp(t, "2019-10-10 10:10:10"), }, { sql: "select * from information_schema.cluster_log where time>='2019-10-10 10:10:10' and time<='2019-10-11 10:10:10'", nodeTypes: set.NewStringSet(), instances: set.NewStringSet(), - startTime: timestamp(c, "2019-10-10 10:10:10"), - endTime: timestamp(c, "2019-10-11 10:10:10"), + startTime: timestamp(t, "2019-10-10 10:10:10"), + endTime: timestamp(t, "2019-10-11 10:10:10"), }, { sql: "select * from information_schema.cluster_log where time>'2019-10-10 10:10:10' and time<'2019-10-11 10:10:10'", nodeTypes: set.NewStringSet(), instances: set.NewStringSet(), - startTime: timestamp(c, "2019-10-10 10:10:10") + 1, - endTime: timestamp(c, "2019-10-11 10:10:10") - 1, + startTime: timestamp(t, "2019-10-10 10:10:10") + 1, + endTime: timestamp(t, "2019-10-11 10:10:10") - 1, }, { sql: "select * from information_schema.cluster_log where time>='2019-10-10 10:10:10' and time<'2019-10-11 10:10:10'", nodeTypes: set.NewStringSet(), instances: set.NewStringSet(), - startTime: timestamp(c, "2019-10-10 10:10:10"), - endTime: timestamp(c, "2019-10-11 10:10:10") - 1, + startTime: timestamp(t, "2019-10-10 10:10:10"), + endTime: timestamp(t, "2019-10-11 10:10:10") - 1, }, { sql: "select * from information_schema.cluster_log where time>='2019-10-12 10:10:10' and time<'2019-10-11 10:10:10'", nodeTypes: set.NewStringSet(), instances: set.NewStringSet(), - startTime: timestamp(c, "2019-10-12 10:10:10"), - endTime: timestamp(c, "2019-10-11 10:10:10") - 1, + startTime: timestamp(t, "2019-10-12 10:10:10"), + endTime: timestamp(t, "2019-10-11 10:10:10") - 1, skipRequest: true, }, { sql: "select * from information_schema.cluster_log where time>='2019-10-10 10:10:10'", nodeTypes: set.NewStringSet(), instances: set.NewStringSet(), - startTime: timestamp(c, "2019-10-10 10:10:10"), + startTime: timestamp(t, "2019-10-10 10:10:10"), }, { sql: "select * from information_schema.cluster_log where time>='2019-10-10 10:10:10' and time>='2019-10-11 10:10:10' and time>='2019-10-12 10:10:10'", nodeTypes: set.NewStringSet(), instances: set.NewStringSet(), - startTime: timestamp(c, "2019-10-12 10:10:10"), + startTime: timestamp(t, "2019-10-12 10:10:10"), }, { sql: "select * from information_schema.cluster_log where time>='2019-10-10 10:10:10' and time>='2019-10-11 10:10:10' and time>='2019-10-12 10:10:10' and time='2019-10-13 10:10:10'", nodeTypes: set.NewStringSet(), instances: set.NewStringSet(), - startTime: timestamp(c, "2019-10-13 10:10:10"), - endTime: timestamp(c, "2019-10-13 10:10:10"), + startTime: timestamp(t, "2019-10-13 10:10:10"), + endTime: timestamp(t, "2019-10-13 10:10:10"), }, { sql: "select * from information_schema.cluster_log where time<='2019-10-10 10:10:10' and time='2019-10-13 10:10:10'", nodeTypes: set.NewStringSet(), instances: set.NewStringSet(), - startTime: timestamp(c, "2019-10-13 10:10:10"), - endTime: timestamp(c, "2019-10-10 10:10:10"), + startTime: timestamp(t, "2019-10-13 10:10:10"), + endTime: timestamp(t, "2019-10-10 10:10:10"), skipRequest: true, }, { sql: "select * from information_schema.cluster_log where time='2019-10-10 10:10:10' and time<='2019-10-13 10:10:10'", nodeTypes: set.NewStringSet(), instances: set.NewStringSet(), - startTime: timestamp(c, "2019-10-10 10:10:10"), - endTime: timestamp(c, "2019-10-10 10:10:10"), + startTime: timestamp(t, "2019-10-10 10:10:10"), + endTime: timestamp(t, "2019-10-10 10:10:10"), }, { sql: "select * from information_schema.cluster_log where time>='2019-10-10 10:10:10' and message like '%a%'", nodeTypes: set.NewStringSet(), instances: set.NewStringSet(), - startTime: timestamp(c, "2019-10-10 10:10:10"), + startTime: timestamp(t, "2019-10-10 10:10:10"), patterns: []string{".*a.*"}, }, { @@ -531,34 +512,37 @@ func (s *extractorSuite) TestClusterLogTableExtractor(c *C) { }, } for _, ca := range cases { - logicalMemTable := s.getLogicalMemTable(c, se, parser, ca.sql) - c.Assert(logicalMemTable.Extractor, NotNil) + logicalMemTable := getLogicalMemTable(t, dom, se, parser, ca.sql) + require.NotNil(t, logicalMemTable.Extractor) clusterConfigExtractor := logicalMemTable.Extractor.(*plannercore.ClusterLogTableExtractor) - c.Assert(clusterConfigExtractor.NodeTypes, DeepEquals, ca.nodeTypes, Commentf("SQL: %v", ca.sql)) - c.Assert(clusterConfigExtractor.Instances, DeepEquals, ca.instances, Commentf("SQL: %v", ca.sql)) - c.Assert(clusterConfigExtractor.SkipRequest, DeepEquals, ca.skipRequest, Commentf("SQL: %v", ca.sql)) + require.EqualValues(t, ca.nodeTypes, clusterConfigExtractor.NodeTypes, "SQL: %v", ca.sql) + require.EqualValues(t, ca.instances, clusterConfigExtractor.Instances, "SQL: %v", ca.sql) + require.EqualValues(t, ca.skipRequest, clusterConfigExtractor.SkipRequest, "SQL: %v", ca.sql) if ca.startTime > 0 { - c.Assert(clusterConfigExtractor.StartTime, Equals, ca.startTime, Commentf("SQL: %v", ca.sql)) + require.Equal(t, ca.startTime, clusterConfigExtractor.StartTime, "SQL: %v", ca.sql) } if ca.endTime > 0 { - c.Assert(clusterConfigExtractor.EndTime, Equals, ca.endTime, Commentf("SQL: %v", ca.sql)) + require.Equal(t, ca.endTime, clusterConfigExtractor.EndTime, "SQL: %v", ca.sql) } - c.Assert(clusterConfigExtractor.Patterns, DeepEquals, ca.patterns, Commentf("SQL: %v", ca.sql)) + require.EqualValues(t, ca.patterns, clusterConfigExtractor.Patterns, "SQL: %v", ca.sql) if len(ca.level) > 0 { - c.Assert(clusterConfigExtractor.LogLevels, DeepEquals, ca.level, Commentf("SQL: %v", ca.sql)) + require.EqualValues(t, ca.level, clusterConfigExtractor.LogLevels, "SQL: %v", ca.sql) } } } -func (s *extractorSuite) TestMetricTableExtractor(c *C) { - se, err := session.CreateSession4Test(s.store) - c.Assert(err, IsNil) +func TestMetricTableExtractor(t *testing.T) { + store, dom, clean := testkit.CreateMockStoreAndDomain(t) + defer clean() + + se, err := session.CreateSession4Test(store) + require.NoError(t, err) - parseTime := func(c *C, s string) time.Time { - t, err := time.ParseInLocation(plannercore.MetricTableTimeFormat, s, time.Local) - c.Assert(err, IsNil) - return t + parseTime := func(t *testing.T, s string) time.Time { + tt, err := time.ParseInLocation(plannercore.MetricTableTimeFormat, s, time.Local) + require.NoError(t, err) + return tt } parser := parser.New() @@ -607,33 +591,33 @@ func (s *extractorSuite) TestMetricTableExtractor(c *C) { "instance": set.NewStringSet("127.0.0.1:10080"), "sql_type": set.NewStringSet("Update"), }, - startTime: parseTime(c, "2019-10-10 10:10:10"), - endTime: parseTime(c, "2019-10-10 10:10:10"), + startTime: parseTime(t, "2019-10-10 10:10:10"), + endTime: parseTime(t, "2019-10-10 10:10:10"), }, { sql: "select * from metrics_schema.tidb_query_duration where time>'2019-10-10 10:10:10' and time<'2019-10-11 10:10:10'", promQL: `histogram_quantile(0.9, sum(rate(tidb_server_handle_query_duration_seconds_bucket{}[60s])) by (le,sql_type,instance))`, - startTime: parseTime(c, "2019-10-10 10:10:10.001"), - endTime: parseTime(c, "2019-10-11 10:10:09.999"), + startTime: parseTime(t, "2019-10-10 10:10:10.001"), + endTime: parseTime(t, "2019-10-11 10:10:09.999"), }, { sql: "select * from metrics_schema.tidb_query_duration where time>='2019-10-10 10:10:10'", promQL: `histogram_quantile(0.9, sum(rate(tidb_server_handle_query_duration_seconds_bucket{}[60s])) by (le,sql_type,instance))`, - startTime: parseTime(c, "2019-10-10 10:10:10"), - endTime: parseTime(c, "2019-10-10 10:20:10"), + startTime: parseTime(t, "2019-10-10 10:10:10"), + endTime: parseTime(t, "2019-10-10 10:20:10"), }, { sql: "select * from metrics_schema.tidb_query_duration where time>='2019-10-10 10:10:10' and time<='2019-10-09 10:10:10'", promQL: "", - startTime: parseTime(c, "2019-10-10 10:10:10"), - endTime: parseTime(c, "2019-10-09 10:10:10"), + startTime: parseTime(t, "2019-10-10 10:10:10"), + endTime: parseTime(t, "2019-10-09 10:10:10"), skipRequest: true, }, { sql: "select * from metrics_schema.tidb_query_duration where time<='2019-10-09 10:10:10'", promQL: "histogram_quantile(0.9, sum(rate(tidb_server_handle_query_duration_seconds_bucket{}[60s])) by (le,sql_type,instance))", - startTime: parseTime(c, "2019-10-09 10:00:10"), - endTime: parseTime(c, "2019-10-09 10:10:10"), + startTime: parseTime(t, "2019-10-09 10:00:10"), + endTime: parseTime(t, "2019-10-09 10:10:10"), }, { sql: "select * from metrics_schema.tidb_query_duration where quantile=0.9 or quantile=0.8", @@ -649,34 +633,37 @@ func (s *extractorSuite) TestMetricTableExtractor(c *C) { } se.GetSessionVars().StmtCtx.TimeZone = time.Local for _, ca := range cases { - logicalMemTable := s.getLogicalMemTable(c, se, parser, ca.sql) - c.Assert(logicalMemTable.Extractor, NotNil) + logicalMemTable := getLogicalMemTable(t, dom, se, parser, ca.sql) + require.NotNil(t, logicalMemTable.Extractor) metricTableExtractor := logicalMemTable.Extractor.(*plannercore.MetricTableExtractor) if len(ca.labelConditions) > 0 { - c.Assert(metricTableExtractor.LabelConditions, DeepEquals, ca.labelConditions, Commentf("SQL: %v", ca.sql)) + require.EqualValues(t, ca.labelConditions, metricTableExtractor.LabelConditions, "SQL: %v", ca.sql) } - c.Assert(metricTableExtractor.SkipRequest, DeepEquals, ca.skipRequest, Commentf("SQL: %v", ca.sql)) + require.EqualValues(t, ca.skipRequest, metricTableExtractor.SkipRequest, "SQL: %v", ca.sql) if len(metricTableExtractor.Quantiles) > 0 { - c.Assert(metricTableExtractor.Quantiles, DeepEquals, ca.quantiles) + require.EqualValues(t, ca.quantiles, metricTableExtractor.Quantiles) } if !ca.skipRequest { promQL := metricTableExtractor.GetMetricTablePromQL(se, "tidb_query_duration") - c.Assert(promQL, DeepEquals, ca.promQL, Commentf("SQL: %v", ca.sql)) + require.EqualValues(t, promQL, ca.promQL, "SQL: %v", ca.sql) start, end := metricTableExtractor.StartTime, metricTableExtractor.EndTime - c.Assert(start.UnixNano() <= end.UnixNano(), IsTrue) + require.GreaterOrEqual(t, end.UnixNano(), start.UnixNano()) if ca.startTime.Unix() > 0 { - c.Assert(metricTableExtractor.StartTime, DeepEquals, ca.startTime, Commentf("SQL: %v, start_time: %v", ca.sql, metricTableExtractor.StartTime)) + require.EqualValues(t, ca.startTime, metricTableExtractor.StartTime, "SQL: %v, start_time: %v", ca.sql, metricTableExtractor.StartTime) } if ca.endTime.Unix() > 0 { - c.Assert(metricTableExtractor.EndTime, DeepEquals, ca.endTime, Commentf("SQL: %v, end_time: %v", ca.sql, metricTableExtractor.EndTime)) + require.EqualValues(t, ca.endTime, metricTableExtractor.EndTime, "SQL: %v, end_time: %v", ca.sql, metricTableExtractor.EndTime) } } } } -func (s *extractorSuite) TestMetricsSummaryTableExtractor(c *C) { - se, err := session.CreateSession4Test(s.store) - c.Assert(err, IsNil) +func TestMetricsSummaryTableExtractor(t *testing.T) { + store, dom, clean := testkit.CreateMockStoreAndDomain(t) + defer clean() + + se, err := session.CreateSession4Test(store) + require.NoError(t, err) var cases = []struct { sql string @@ -758,23 +745,26 @@ func (s *extractorSuite) TestMetricsSummaryTableExtractor(c *C) { for _, ca := range cases { sort.Float64s(ca.quantiles) - logicalMemTable := s.getLogicalMemTable(c, se, parser, ca.sql) - c.Assert(logicalMemTable.Extractor, NotNil) + logicalMemTable := getLogicalMemTable(t, dom, se, parser, ca.sql) + require.NotNil(t, logicalMemTable.Extractor) extractor := logicalMemTable.Extractor.(*plannercore.MetricSummaryTableExtractor) if len(ca.quantiles) > 0 { - c.Assert(extractor.Quantiles, DeepEquals, ca.quantiles, Commentf("SQL: %v", ca.sql)) + require.EqualValues(t, ca.quantiles, extractor.Quantiles, "SQL: %v", ca.sql) } if len(ca.names) > 0 { - c.Assert(extractor.MetricsNames, DeepEquals, ca.names, Commentf("SQL: %v", ca.sql)) + require.EqualValues(t, ca.names, extractor.MetricsNames, "SQL: %v", ca.sql) } - c.Assert(extractor.SkipRequest, Equals, ca.skipRequest, Commentf("SQL: %v", ca.sql)) + require.Equal(t, ca.skipRequest, extractor.SkipRequest, "SQL: %v", ca.sql) } } -func (s *extractorSuite) TestInspectionResultTableExtractor(c *C) { - se, err := session.CreateSession4Test(s.store) - c.Assert(err, IsNil) +func TestInspectionResultTableExtractor(t *testing.T) { + store, dom, clean := testkit.CreateMockStoreAndDomain(t) + defer clean() + + se, err := session.CreateSession4Test(store) + require.NoError(t, err) var cases = []struct { sql string @@ -897,23 +887,26 @@ func (s *extractorSuite) TestInspectionResultTableExtractor(c *C) { } parser := parser.New() for _, ca := range cases { - logicalMemTable := s.getLogicalMemTable(c, se, parser, ca.sql) - c.Assert(logicalMemTable.Extractor, NotNil) + logicalMemTable := getLogicalMemTable(t, dom, se, parser, ca.sql) + require.NotNil(t, logicalMemTable.Extractor) clusterConfigExtractor := logicalMemTable.Extractor.(*plannercore.InspectionResultTableExtractor) if len(ca.rules) > 0 { - c.Assert(clusterConfigExtractor.Rules, DeepEquals, ca.rules, Commentf("SQL: %v", ca.sql)) + require.EqualValues(t, ca.rules, clusterConfigExtractor.Rules, "SQL: %v", ca.sql) } if len(ca.items) > 0 { - c.Assert(clusterConfigExtractor.Items, DeepEquals, ca.items, Commentf("SQL: %v", ca.sql)) + require.EqualValues(t, ca.items, clusterConfigExtractor.Items, "SQL: %v", ca.sql) } - c.Assert(clusterConfigExtractor.SkipInspection, Equals, ca.skipInspection, Commentf("SQL: %v", ca.sql)) + require.Equal(t, ca.skipInspection, clusterConfigExtractor.SkipInspection, "SQL: %v", ca.sql) } } -func (s *extractorSuite) TestInspectionSummaryTableExtractor(c *C) { - se, err := session.CreateSession4Test(s.store) - c.Assert(err, IsNil) +func TestInspectionSummaryTableExtractor(t *testing.T) { + store, dom, clean := testkit.CreateMockStoreAndDomain(t) + defer clean() + + se, err := session.CreateSession4Test(store) + require.NoError(t, err) var cases = []struct { sql string @@ -996,23 +989,26 @@ func (s *extractorSuite) TestInspectionSummaryTableExtractor(c *C) { } parser := parser.New() for _, ca := range cases { - logicalMemTable := s.getLogicalMemTable(c, se, parser, ca.sql) - c.Assert(logicalMemTable.Extractor, NotNil) + logicalMemTable := getLogicalMemTable(t, dom, se, parser, ca.sql) + require.NotNil(t, logicalMemTable.Extractor) clusterConfigExtractor := logicalMemTable.Extractor.(*plannercore.InspectionSummaryTableExtractor) if len(ca.rules) > 0 { - c.Assert(clusterConfigExtractor.Rules, DeepEquals, ca.rules, Commentf("SQL: %v", ca.sql)) + require.EqualValues(t, ca.rules, clusterConfigExtractor.Rules, "SQL: %v", ca.sql) } if len(ca.names) > 0 { - c.Assert(clusterConfigExtractor.MetricNames, DeepEquals, ca.names, Commentf("SQL: %v", ca.sql)) + require.EqualValues(t, ca.names, clusterConfigExtractor.MetricNames, "SQL: %v", ca.sql) } - c.Assert(clusterConfigExtractor.SkipInspection, Equals, ca.skipInspection, Commentf("SQL: %v", ca.sql)) + require.Equal(t, ca.skipInspection, clusterConfigExtractor.SkipInspection, "SQL: %v", ca.sql) } } -func (s *extractorSuite) TestInspectionRuleTableExtractor(c *C) { - se, err := session.CreateSession4Test(s.store) - c.Assert(err, IsNil) +func TestInspectionRuleTableExtractor(t *testing.T) { + store, dom, clean := testkit.CreateMockStoreAndDomain(t) + defer clean() + + se, err := session.CreateSession4Test(store) + require.NoError(t, err) var cases = []struct { sql string @@ -1037,20 +1033,23 @@ func (s *extractorSuite) TestInspectionRuleTableExtractor(c *C) { } parser := parser.New() for _, ca := range cases { - logicalMemTable := s.getLogicalMemTable(c, se, parser, ca.sql) - c.Assert(logicalMemTable.Extractor, NotNil) + logicalMemTable := getLogicalMemTable(t, dom, se, parser, ca.sql) + require.NotNil(t, logicalMemTable.Extractor) clusterConfigExtractor := logicalMemTable.Extractor.(*plannercore.InspectionRuleTableExtractor) if len(ca.tps) > 0 { - c.Assert(clusterConfigExtractor.Types, DeepEquals, ca.tps, Commentf("SQL: %v", ca.sql)) + require.EqualValues(t, ca.tps, clusterConfigExtractor.Types, "SQL: %v", ca.sql) } - c.Assert(clusterConfigExtractor.SkipRequest, Equals, ca.skip, Commentf("SQL: %v", ca.sql)) + require.Equal(t, ca.skip, clusterConfigExtractor.SkipRequest, "SQL: %v", ca.sql) } } -func (s *extractorSuite) TestTiDBHotRegionsHistoryTableExtractor(c *C) { - se, err := session.CreateSession4Test(s.store) - c.Assert(err, IsNil) +func TestTiDBHotRegionsHistoryTableExtractor(t *testing.T) { + store, dom, clean := testkit.CreateMockStoreAndDomain(t) + defer clean() + + se, err := session.CreateSession4Test(store) + require.NoError(t, err) se.GetSessionVars().StmtCtx.TimeZone = time.Local var cases = []struct { @@ -1072,40 +1071,40 @@ func (s *extractorSuite) TestTiDBHotRegionsHistoryTableExtractor(c *C) { }, { sql: "select * from information_schema.tidb_hot_regions_history where update_time='2019-10-10 10:10:10'", - startTime: timestamp(c, "2019-10-10 10:10:10"), - endTime: timestamp(c, "2019-10-10 10:10:10"), + startTime: timestamp(t, "2019-10-10 10:10:10"), + endTime: timestamp(t, "2019-10-10 10:10:10"), isLearners: []bool{false, true}, isLeaders: []bool{false, true}, hotRegionTypes: set.NewStringSet(plannercore.HotRegionTypeRead, plannercore.HotRegionTypeWrite), }, { sql: "select * from information_schema.tidb_hot_regions_history where update_time>='2019-10-10 10:10:10' and update_time<='2019-10-11 10:10:10'", - startTime: timestamp(c, "2019-10-10 10:10:10"), - endTime: timestamp(c, "2019-10-11 10:10:10"), + startTime: timestamp(t, "2019-10-10 10:10:10"), + endTime: timestamp(t, "2019-10-11 10:10:10"), isLearners: []bool{false, true}, isLeaders: []bool{false, true}, hotRegionTypes: set.NewStringSet(plannercore.HotRegionTypeRead, plannercore.HotRegionTypeWrite), }, { sql: "select * from information_schema.tidb_hot_regions_history where update_time>'2019-10-10 10:10:10' and update_time<'2019-10-11 10:10:10'", - startTime: timestamp(c, "2019-10-10 10:10:10") + 1, - endTime: timestamp(c, "2019-10-11 10:10:10") - 1, + startTime: timestamp(t, "2019-10-10 10:10:10") + 1, + endTime: timestamp(t, "2019-10-11 10:10:10") - 1, isLearners: []bool{false, true}, isLeaders: []bool{false, true}, hotRegionTypes: set.NewStringSet(plannercore.HotRegionTypeRead, plannercore.HotRegionTypeWrite), }, { sql: "select * from information_schema.tidb_hot_regions_history where update_time>='2019-10-10 10:10:10' and update_time<'2019-10-11 10:10:10'", - startTime: timestamp(c, "2019-10-10 10:10:10"), - endTime: timestamp(c, "2019-10-11 10:10:10") - 1, + startTime: timestamp(t, "2019-10-10 10:10:10"), + endTime: timestamp(t, "2019-10-11 10:10:10") - 1, isLearners: []bool{false, true}, isLeaders: []bool{false, true}, hotRegionTypes: set.NewStringSet(plannercore.HotRegionTypeRead, plannercore.HotRegionTypeWrite), }, { sql: "select * from information_schema.tidb_hot_regions_history where update_time>='2019-10-12 10:10:10' and update_time<'2019-10-11 10:10:10'", - startTime: timestamp(c, "2019-10-12 10:10:10"), - endTime: timestamp(c, "2019-10-11 10:10:10") - 1, + startTime: timestamp(t, "2019-10-12 10:10:10"), + endTime: timestamp(t, "2019-10-11 10:10:10") - 1, isLearners: []bool{false, true}, isLeaders: []bool{false, true}, hotRegionTypes: set.NewStringSet(plannercore.HotRegionTypeRead, plannercore.HotRegionTypeWrite), @@ -1113,30 +1112,30 @@ func (s *extractorSuite) TestTiDBHotRegionsHistoryTableExtractor(c *C) { }, { sql: "select * from information_schema.tidb_hot_regions_history where update_time>='2019-10-10 10:10:10'", - startTime: timestamp(c, "2019-10-10 10:10:10"), + startTime: timestamp(t, "2019-10-10 10:10:10"), isLearners: []bool{false, true}, isLeaders: []bool{false, true}, hotRegionTypes: set.NewStringSet(plannercore.HotRegionTypeRead, plannercore.HotRegionTypeWrite), }, { sql: "select * from information_schema.tidb_hot_regions_history where update_time>='2019-10-10 10:10:10' and update_time>='2019-10-11 10:10:10' and update_time>='2019-10-12 10:10:10'", - startTime: timestamp(c, "2019-10-12 10:10:10"), + startTime: timestamp(t, "2019-10-12 10:10:10"), isLearners: []bool{false, true}, isLeaders: []bool{false, true}, hotRegionTypes: set.NewStringSet(plannercore.HotRegionTypeRead, plannercore.HotRegionTypeWrite), }, { sql: "select * from information_schema.tidb_hot_regions_history where update_time>='2019-10-10 10:10:10' and update_time>='2019-10-11 10:10:10' and update_time>='2019-10-12 10:10:10' and update_time='2019-10-13 10:10:10'", - startTime: timestamp(c, "2019-10-13 10:10:10"), - endTime: timestamp(c, "2019-10-13 10:10:10"), + startTime: timestamp(t, "2019-10-13 10:10:10"), + endTime: timestamp(t, "2019-10-13 10:10:10"), isLearners: []bool{false, true}, isLeaders: []bool{false, true}, hotRegionTypes: set.NewStringSet(plannercore.HotRegionTypeRead, plannercore.HotRegionTypeWrite), }, { sql: "select * from information_schema.tidb_hot_regions_history where update_time<='2019-10-10 10:10:10' and update_time='2019-10-13 10:10:10'", - startTime: timestamp(c, "2019-10-13 10:10:10"), - endTime: timestamp(c, "2019-10-10 10:10:10"), + startTime: timestamp(t, "2019-10-13 10:10:10"), + endTime: timestamp(t, "2019-10-10 10:10:10"), isLearners: []bool{false, true}, isLeaders: []bool{false, true}, hotRegionTypes: set.NewStringSet(plannercore.HotRegionTypeRead, plannercore.HotRegionTypeWrite), @@ -1144,8 +1143,8 @@ func (s *extractorSuite) TestTiDBHotRegionsHistoryTableExtractor(c *C) { }, { sql: "select * from information_schema.tidb_hot_regions_history where update_time='2019-10-10 10:10:10' and update_time<='2019-10-13 10:10:10'", - startTime: timestamp(c, "2019-10-10 10:10:10"), - endTime: timestamp(c, "2019-10-10 10:10:10"), + startTime: timestamp(t, "2019-10-10 10:10:10"), + endTime: timestamp(t, "2019-10-10 10:10:10"), isLearners: []bool{false, true}, isLeaders: []bool{false, true}, hotRegionTypes: set.NewStringSet(plannercore.HotRegionTypeRead, plannercore.HotRegionTypeWrite), @@ -1389,34 +1388,309 @@ func (s *extractorSuite) TestTiDBHotRegionsHistoryTableExtractor(c *C) { parser := parser.New() for _, ca := range cases { - logicalMemTable := s.getLogicalMemTable(c, se, parser, ca.sql) - c.Assert(logicalMemTable.Extractor, NotNil, Commentf("SQL: %v", ca.sql)) + logicalMemTable := getLogicalMemTable(t, dom, se, parser, ca.sql) + require.NotNil(t, logicalMemTable.Extractor, "SQL: %v", ca.sql) hotRegionsHistoryExtractor := logicalMemTable.Extractor.(*plannercore.HotRegionsHistoryTableExtractor) if ca.startTime > 0 { - c.Assert(hotRegionsHistoryExtractor.StartTime, Equals, ca.startTime, Commentf("SQL: %v", ca.sql)) + require.Equal(t, ca.startTime, hotRegionsHistoryExtractor.StartTime, "SQL: %v", ca.sql) } if ca.endTime > 0 { - c.Assert(hotRegionsHistoryExtractor.EndTime, Equals, ca.endTime, Commentf("SQL: %v", ca.sql)) + require.Equal(t, ca.endTime, hotRegionsHistoryExtractor.EndTime, "SQL: %v", ca.sql) } - c.Assert(hotRegionsHistoryExtractor.SkipRequest, DeepEquals, ca.skipRequest, Commentf("SQL: %v", ca.sql)) + require.EqualValues(t, ca.skipRequest, hotRegionsHistoryExtractor.SkipRequest, "SQL: %v", ca.sql) if len(ca.isLearners) > 0 { - c.Assert(hotRegionsHistoryExtractor.IsLearners, DeepEquals, ca.isLearners, Commentf("SQL: %v", ca.sql)) + require.EqualValues(t, ca.isLearners, hotRegionsHistoryExtractor.IsLearners, "SQL: %v", ca.sql) } if len(ca.isLeaders) > 0 { - c.Assert(hotRegionsHistoryExtractor.IsLeaders, DeepEquals, ca.isLeaders, Commentf("SQL: %v", ca.sql)) + require.EqualValues(t, ca.isLeaders, hotRegionsHistoryExtractor.IsLeaders, "SQL: %v", ca.sql) } if ca.hotRegionTypes.Count() > 0 { - c.Assert(hotRegionsHistoryExtractor.HotRegionTypes, DeepEquals, ca.hotRegionTypes, Commentf("SQL: %v", ca.sql)) + require.EqualValues(t, ca.hotRegionTypes, hotRegionsHistoryExtractor.HotRegionTypes, "SQL: %v", ca.sql) } if len(ca.regionIDs) > 0 { - c.Assert(hotRegionsHistoryExtractor.RegionIDs, DeepEquals, ca.regionIDs, Commentf("SQL: %v", ca.sql)) + require.EqualValues(t, ca.regionIDs, hotRegionsHistoryExtractor.RegionIDs, "SQL: %v", ca.sql) } if len(ca.storeIDs) > 0 { - c.Assert(hotRegionsHistoryExtractor.StoreIDs, DeepEquals, ca.storeIDs, Commentf("SQL: %v", ca.sql)) + require.EqualValues(t, ca.storeIDs, hotRegionsHistoryExtractor.StoreIDs, "SQL: %v", ca.sql) } if len(ca.peerIDs) > 0 { - c.Assert(hotRegionsHistoryExtractor.PeerIDs, DeepEquals, ca.peerIDs, Commentf("SQL: %v", ca.sql)) + require.EqualValues(t, ca.peerIDs, hotRegionsHistoryExtractor.PeerIDs, "SQL: %v", ca.sql) + } + } +} + +func TestTikvRegionPeersExtractor(t *testing.T) { + store, dom, clean := testkit.CreateMockStoreAndDomain(t) + defer clean() + + se, err := session.CreateSession4Test(store) + require.NoError(t, err) + + var cases = []struct { + sql string + regionIDs, storeIDs []uint64 + skipRequest bool + }{ + // Test `region_id`, `store_id` columns. + { + sql: "select * from information_schema.tikv_region_peers where region_id=100", + regionIDs: []uint64{100}, + }, + { + sql: "select * from information_schema.tikv_region_peers where 100=region_id", + regionIDs: []uint64{100}, + }, + { + sql: "select * from information_schema.tikv_region_peers where 100=region_id or region_id=101", + regionIDs: []uint64{100, 101}, + }, + { + sql: "select * from information_schema.tikv_region_peers where 100=region_id or region_id=101 or region_id=102 or 103 = region_id", + regionIDs: []uint64{100, 101, 102, 103}, + }, + { + sql: "select * from information_schema.tikv_region_peers where (region_id=100 or region_id=101) and (store_id=200 or store_id=201)", + regionIDs: []uint64{100, 101}, + storeIDs: []uint64{200, 201}, + }, + { + sql: "select * from information_schema.tikv_region_peers where region_id in (100, 101)", + regionIDs: []uint64{100, 101}, + }, + { + sql: "select * from information_schema.tikv_region_peers where region_id in (100, 101) and store_id=200", + regionIDs: []uint64{100, 101}, + storeIDs: []uint64{200}, + }, + { + sql: "select * from information_schema.tikv_region_peers where region_id in (100, 101) and store_id in (200, 201)", + regionIDs: []uint64{100, 101}, + storeIDs: []uint64{200, 201}, + }, + { + sql: "select * from information_schema.tikv_region_peers where region_id=100 and store_id in (200, 201)", + regionIDs: []uint64{100}, + storeIDs: []uint64{200, 201}, + }, + { + sql: "select * from information_schema.tikv_region_peers where region_id=100 and store_id=200", + regionIDs: []uint64{100}, + storeIDs: []uint64{200}, + }, + { + sql: "select * from information_schema.tikv_region_peers where region_id=100 and region_id=101", + skipRequest: true, + }, + { + sql: "select * from information_schema.tikv_region_peers where region_id=100 and region_id in (100,101)", + regionIDs: []uint64{100}, + }, + { + sql: "select * from information_schema.tikv_region_peers where region_id=100 and region_id in (100,101) and store_id=200 and store_id in (200,201)", + regionIDs: []uint64{100}, + storeIDs: []uint64{200}, + }, + { + sql: "select * from information_schema.tikv_region_peers where region_id=100 and region_id in (101,102)", + skipRequest: true, + }, + { + sql: "select * from information_schema.tikv_region_peers where region_id=100 and region_id in (101,102) and store_id=200 and store_id in (200,201)", + skipRequest: true, + }, + { + sql: "select * from information_schema.tikv_region_peers where region_id=100 and region_id in (100,101) and store_id=200 and store_id in (201,202)", + skipRequest: true, + }, + { + sql: `select * from information_schema.tikv_region_peers + where region_id=100 and region_id in (100,101) + and store_id=200 and store_id in (201,202)`, + skipRequest: true, + }, + { + sql: "select * from information_schema.tikv_region_peers where region_id in (100,101) and region_id in (101,102)", + regionIDs: []uint64{101}, + }, + { + sql: `select * from information_schema.tikv_region_peers + where region_id in (100,101) + and region_id in (101,102) + and store_id in (200,201) + and store_id in (201,202)`, + regionIDs: []uint64{101}, + storeIDs: []uint64{201}, + }, + { + sql: `select * from information_schema.tikv_region_peers + where region_id in (100,101) + and region_id in (100,102) + and region_id in (102,103) + and region_id in (103,104)`, + skipRequest: true, + }, + // Test columns that is not extracted by TikvRegionPeersExtractor + { + sql: `select * from information_schema.tikv_region_peers + where peer_id=100 + and is_learner=0 + and is_leader=1 + and status='NORMAL' + and down_seconds=1000`, + }, + } + parser := parser.New() + for _, ca := range cases { + logicalMemTable := getLogicalMemTable(t, dom, se, parser, ca.sql) + require.NotNil(t, logicalMemTable.Extractor) + + tikvRegionPeersExtractor := logicalMemTable.Extractor.(*plannercore.TikvRegionPeersExtractor) + if len(ca.regionIDs) > 0 { + require.EqualValues(t, ca.regionIDs, tikvRegionPeersExtractor.RegionIDs, "SQL: %v", ca.sql) + } + if len(ca.storeIDs) > 0 { + require.EqualValues(t, ca.storeIDs, tikvRegionPeersExtractor.StoreIDs, "SQL: %v", ca.sql) + } + } +} + +func TestColumns(t *testing.T) { + store, dom, clean := testkit.CreateMockStoreAndDomain(t) + defer clean() + + se, err := session.CreateSession4Test(store) + require.NoError(t, err) + + var cases = []struct { + sql string + columnName set.StringSet + tableSchema set.StringSet + tableName set.StringSet + columnNamePattern []string + tableSchemaPattern []string + tableNamePattern []string + skipRequest bool + }{ + { + sql: `select * from INFORMATION_SCHEMA.COLUMNS where column_name='T';`, + columnName: set.NewStringSet("t"), + }, + { + sql: `select * from INFORMATION_SCHEMA.COLUMNS where table_schema='TEST';`, + tableSchema: set.NewStringSet("test"), + }, + { + sql: `select * from INFORMATION_SCHEMA.COLUMNS where table_name='TEST';`, + tableName: set.NewStringSet("test"), + }, + { + sql: "select * from information_schema.COLUMNS where table_name in ('TEST','t') and column_name in ('A','b')", + columnName: set.NewStringSet("a", "b"), + tableName: set.NewStringSet("test", "t"), + }, + { + sql: `select * from information_schema.COLUMNS where table_name='a' and table_name in ('a', 'B');`, + tableName: set.NewStringSet("a"), + }, + { + sql: `select * from information_schema.COLUMNS where table_name='a' and table_name='B';`, + skipRequest: true, + }, + { + sql: `select * from information_schema.COLUMNS where table_name like 'T%';`, + tableNamePattern: []string{"(?i)T.*"}, + }, + { + sql: `select * from information_schema.COLUMNS where column_name like 'T%';`, + columnNamePattern: []string{"(?i)T.*"}, + }, + { + sql: `select * from information_schema.COLUMNS where column_name like 'i%';`, + columnNamePattern: []string{"(?i)i.*"}, + }, + { + sql: `select * from information_schema.COLUMNS where column_name like 'abc%' or column_name like "def%";`, + columnNamePattern: []string{"(?i)abc.*|def.*"}, + }, + { + sql: `select * from information_schema.COLUMNS where column_name like 'abc%' and column_name like "%def";`, + columnNamePattern: []string{"(?i)abc.*", "(?i).*def"}, + }, + } + parser := parser.New() + for _, ca := range cases { + logicalMemTable := getLogicalMemTable(t, dom, se, parser, ca.sql) + require.NotNil(t, logicalMemTable.Extractor) + + columnsTableExtractor := logicalMemTable.Extractor.(*plannercore.ColumnsTableExtractor) + require.Equal(t, ca.skipRequest, columnsTableExtractor.SkipRequest, "SQL: %v", ca.sql) + + require.Equal(t, ca.columnName.Count(), columnsTableExtractor.ColumnName.Count()) + if ca.columnName.Count() > 0 && columnsTableExtractor.ColumnName.Count() > 0 { + require.EqualValues(t, ca.columnName, columnsTableExtractor.ColumnName, "SQL: %v", ca.sql) + } + + require.Equal(t, ca.tableSchema.Count(), columnsTableExtractor.TableSchema.Count()) + if ca.tableSchema.Count() > 0 && columnsTableExtractor.TableSchema.Count() > 0 { + require.EqualValues(t, ca.tableSchema, columnsTableExtractor.TableSchema, "SQL: %v", ca.sql) + } + require.Equal(t, ca.tableName.Count(), columnsTableExtractor.TableName.Count()) + if ca.tableName.Count() > 0 && columnsTableExtractor.TableName.Count() > 0 { + require.EqualValues(t, ca.tableName, columnsTableExtractor.TableName, "SQL: %v", ca.sql) + } + require.Equal(t, len(ca.tableNamePattern), len(columnsTableExtractor.TableNamePatterns)) + if len(ca.tableNamePattern) > 0 && len(columnsTableExtractor.TableNamePatterns) > 0 { + require.EqualValues(t, ca.tableNamePattern, columnsTableExtractor.TableNamePatterns, "SQL: %v", ca.sql) + } + require.Equal(t, len(ca.columnNamePattern), len(columnsTableExtractor.ColumnNamePatterns)) + if len(ca.columnNamePattern) > 0 && len(columnsTableExtractor.ColumnNamePatterns) > 0 { + require.EqualValues(t, ca.columnNamePattern, columnsTableExtractor.ColumnNamePatterns, "SQL: %v", ca.sql) + } + require.Equal(t, len(ca.tableSchemaPattern), len(columnsTableExtractor.TableSchemaPatterns)) + if len(ca.tableSchemaPattern) > 0 && len(columnsTableExtractor.TableSchemaPatterns) > 0 { + require.EqualValues(t, ca.tableSchemaPattern, columnsTableExtractor.TableSchemaPatterns, "SQL: %v", ca.sql) } } } + +func TestPredicateQuery(t *testing.T) { + store, clean := testkit.CreateMockStore(t) + defer clean() + + tk := testkit.NewTestKit(t, store) + tk.MustExec("use test") + tk.MustExec("create table t(id int, abclmn int);") + tk.MustQuery("select TABLE_NAME from information_schema.columns where table_schema = 'test' and column_name like 'i%'").Check(testkit.Rows("t")) + tk.MustQuery("select TABLE_NAME from information_schema.columns where table_schema = 'TEST' and column_name like 'I%'").Check(testkit.Rows("t")) + tk.MustQuery("select TABLE_NAME from information_schema.columns where table_schema = 'TEST' and column_name like 'ID'").Check(testkit.Rows("t")) + tk.MustQuery("select TABLE_NAME from information_schema.columns where table_schema = 'TEST' and column_name like 'id'").Check(testkit.Rows("t")) + tk.MustQuery("select column_name from information_schema.columns where table_schema = 'TEST' and (column_name like 'I%' or column_name like '%D')").Check(testkit.Rows("id")) + tk.MustQuery("select column_name from information_schema.columns where table_schema = 'TEST' and (column_name like 'abc%' and column_name like '%lmn')").Check(testkit.Rows("abclmn")) + tk.MustQuery("describe t").Check(testkit.Rows("id int(11) YES ", "abclmn int(11) YES ")) + tk.MustQuery("describe t id").Check(testkit.Rows("id int(11) YES ")) + tk.MustQuery("describe t ID").Check(testkit.Rows("id int(11) YES ")) + tk.MustGetErrCode("describe t 'I%'", errno.ErrParse) + tk.MustGetErrCode("describe t I%", errno.ErrParse) + + tk.MustQuery("show columns from t like 'abclmn'").Check(testutil.RowsWithSep(",", "abclmn,int(11),YES,,,")) + tk.MustQuery("show columns from t like 'ABCLMN'").Check(testutil.RowsWithSep(",", "abclmn,int(11),YES,,,")) + tk.MustQuery("show columns from t like 'abc%'").Check(testutil.RowsWithSep(",", "abclmn,int(11),YES,,,")) + tk.MustQuery("show columns from t like 'ABC%'").Check(testutil.RowsWithSep(",", "abclmn,int(11),YES,,,")) + tk.MustQuery("show columns from t like '%lmn'").Check(testutil.RowsWithSep(",", "abclmn,int(11),YES,,,")) + tk.MustQuery("show columns from t like '%LMN'").Check(testutil.RowsWithSep(",", "abclmn,int(11),YES,,,")) + tk.MustQuery("show columns in t like '%lmn'").Check(testutil.RowsWithSep(",", "abclmn,int(11),YES,,,")) + tk.MustQuery("show columns in t like '%LMN'").Check(testutil.RowsWithSep(",", "abclmn,int(11),YES,,,")) + tk.MustQuery("show fields in t like '%lmn'").Check(testutil.RowsWithSep(",", "abclmn,int(11),YES,,,")) + tk.MustQuery("show fields in t like '%LMN'").Check(testutil.RowsWithSep(",", "abclmn,int(11),YES,,,")) + + tk.MustQuery("show columns from t where field like '%lmn'").Check(testutil.RowsWithSep(",", "abclmn,int(11),YES,,,")) + tk.MustQuery("show columns from t where field = 'abclmn'").Check(testutil.RowsWithSep(",", "abclmn,int(11),YES,,,")) + tk.MustQuery("show columns in t where field = 'abclmn'").Check(testutil.RowsWithSep(",", "abclmn,int(11),YES,,,")) + tk.MustQuery("show fields from t where field = 'abclmn'").Check(testutil.RowsWithSep(",", "abclmn,int(11),YES,,,")) + tk.MustQuery("show fields in t where field = 'abclmn'").Check(testutil.RowsWithSep(",", "abclmn,int(11),YES,,,")) + tk.MustQuery("explain t").Check(testkit.Rows("id int(11) YES ", "abclmn int(11) YES ")) + + tk.MustGetErrCode("show columns from t like id", errno.ErrBadField) + tk.MustGetErrCode("show columns from t like `id`", errno.ErrBadField) +} diff --git a/planner/core/physical_plans.go b/planner/core/physical_plans.go index 6293bba4b5073..876e71415b75e 100644 --- a/planner/core/physical_plans.go +++ b/planner/core/physical_plans.go @@ -1367,6 +1367,8 @@ type PhysicalShow struct { physicalSchemaProducer ShowContents + + Extractor ShowPredicateExtractor } // PhysicalShowDDLJobs is for showing DDL job list. diff --git a/planner/core/planbuilder.go b/planner/core/planbuilder.go index 0dfd7ea71c743..026162be967d6 100644 --- a/planner/core/planbuilder.go +++ b/planner/core/planbuilder.go @@ -2871,7 +2871,15 @@ func (b *PlanBuilder) buildShow(ctx context.Context, show *ast.ShowStmt) (Plan, }.Init(b.ctx) isView := false isSequence := false + switch show.Tp { + case ast.ShowColumns: + var extractor ShowColumnsTableExtractor + if extractor.Extract(show) { + p.Extractor = &extractor + // avoid to build Selection. + show.Pattern = nil + } case ast.ShowTables, ast.ShowTableStatus: if p.DBName == "" { return nil, ErrNoDB diff --git a/planner/core/show_predicate_extractor.go b/planner/core/show_predicate_extractor.go new file mode 100644 index 0000000000000..0be76a053fd6d --- /dev/null +++ b/planner/core/show_predicate_extractor.go @@ -0,0 +1,97 @@ +// Copyright 2022 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 + +import ( + "bytes" + "fmt" + "strings" + + "github.com/pingcap/tidb/parser/ast" + driver "github.com/pingcap/tidb/types/parser_driver" + "github.com/pingcap/tidb/util/collate" + "github.com/pingcap/tidb/util/stringutil" +) + +var _ ShowPredicateExtractor = &ShowColumnsTableExtractor{} + +// ShowPredicateExtractor is used to extract some predicates from `PatternLikeExpr` clause +// and push the predicates down to the data retrieving on reading memory table stage when use ShowStmt. +// +// e.g: +// SHOW COLUMNS FROM t LIKE '%abc%' +// We must request all components from the memory table, and filter the result by the PatternLikeExpr predicate. +// +// it is a way to fix https://github.com/pingcap/tidb/issues/29910. +type ShowPredicateExtractor interface { + // Extracts predicates which can be pushed down and returns whether the extractor can extract predicates. + Extract(show *ast.ShowStmt) bool + explainInfo() string +} + +// ShowColumnsTableExtractor is used to extract some predicates of tables table. +type ShowColumnsTableExtractor struct { + Field string + + FieldPatterns string +} + +// Extract implements the MemTablePredicateExtractor Extract interface +func (e *ShowColumnsTableExtractor) Extract(show *ast.ShowStmt) bool { + if show.Pattern != nil && show.Pattern.Pattern != nil { + pattern := show.Pattern + switch pattern.Pattern.(type) { + case *driver.ValueExpr: + // It is used in `SHOW COLUMNS FROM t LIKE `abc``. + ptn := pattern.Pattern.(*driver.ValueExpr).GetString() + patValue, patTypes := stringutil.CompilePattern(ptn, pattern.Escape) + if !collate.NewCollationEnabled() && stringutil.IsExactMatch(patTypes) { + e.Field = strings.ToLower(string(patValue)) + return true + } + // (?i) mean to be case-insensitive. + e.FieldPatterns = "(?i)" + stringutil.CompileLike2Regexp(string(patValue)) + return true + case *ast.ColumnNameExpr: + // It is used in `SHOW COLUMNS FROM t LIKE abc`. + // MySQL do not support this syntax and return the error. + return false + } + } else if show.Column != nil && show.Column.Name.L != "" { + // it is used in `DESCRIBE t COLUMN`. + e.Field = show.Column.Name.L + return true + } + return false + +} + +func (e *ShowColumnsTableExtractor) explainInfo() string { + r := new(bytes.Buffer) + if len(e.Field) > 0 { + r.WriteString(fmt.Sprintf("field:[%s], ", e.Field)) + } + + if len(e.FieldPatterns) > 0 { + r.WriteString(fmt.Sprintf("field_pattern:[%s], ", e.FieldPatterns)) + } + + // remove the last ", " in the message info + s := r.String() + if len(s) > 2 { + return s[:len(s)-2] + } + return s +} diff --git a/planner/core/stringer.go b/planner/core/stringer.go index ce41a53bf66c8..68eb94c584861 100644 --- a/planner/core/stringer.go +++ b/planner/core/stringer.go @@ -132,8 +132,16 @@ func toString(in Plan, strs []string, idxs []int) ([]string, []int) { str = "Lock" case *ShowDDL: str = "ShowDDL" - case *LogicalShow, *PhysicalShow: + case *LogicalShow: str = "Show" + if pl := in.(*LogicalShow); pl.Extractor != nil { + str = str + "(" + pl.Extractor.explainInfo() + ")" + } + case *PhysicalShow: + str = "Show" + if pl := in.(*PhysicalShow); pl.Extractor != nil { + str = str + "(" + pl.Extractor.explainInfo() + ")" + } case *LogicalShowDDLJobs, *PhysicalShowDDLJobs: str = "ShowDDLJobs" case *LogicalSort, *PhysicalSort: diff --git a/planner/core/stringer_test.go b/planner/core/stringer_test.go new file mode 100644 index 0000000000000..3ca5cbfc66568 --- /dev/null +++ b/planner/core/stringer_test.go @@ -0,0 +1,72 @@ +// Copyright 2022 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" + "testing" + + "github.com/pingcap/tidb/parser" + "github.com/pingcap/tidb/planner/core" + "github.com/pingcap/tidb/testkit" + "github.com/pingcap/tidb/util/hint" + "github.com/stretchr/testify/require" +) + +func TestPlanStringer(t *testing.T) { + store, clean := testkit.CreateMockStore(t) + defer clean() + tk := testkit.NewTestKit(t, store) + + tk.MustExec("use test") + tk.MustExec("create table t(a int, b int, c int, index idx(a))") + tests := []struct { + sql string + plan string + }{ + { + sql: "show columns from t like 'a'", + plan: "Show(field:[a])", + }, + { + sql: "show columns from t like 'a%'", + plan: "Show(field_pattern:[(?i)a.*])", + }, + { + sql: "show columns from t where field = 'a'", + plan: "Show->Sel([eq(Column#13, a)])->Projection", + }, + { + sql: "desc t", + plan: "Show", + }, + { + sql: "desc t a", + plan: "Show(field:[a])", + }, + } + parser := parser.New() + for _, tt := range tests { + stmt, err := parser.ParseOneStmt(tt.sql, "", "") + require.NoError(t, err, "for %s", tt.sql) + ret := &core.PreprocessorReturn{} + builder, _ := core.NewPlanBuilder().Init(tk.Session(), ret.InfoSchema, &hint.BlockHintProcessor{}) + p, err := builder.Build(context.TODO(), stmt) + require.NoError(t, err, "for %s", tt.sql) + p, err = core.LogicalOptimize(context.TODO(), builder.GetOptFlag(), p.(core.LogicalPlan)) + require.NoError(t, err, "for %s", tt.sql) + require.Equal(t, tt.plan, core.ToString(p)) + } +}