diff --git a/executor/show.go b/executor/show.go index 498825084093d..349a5ad307de5 100644 --- a/executor/show.go +++ b/executor/show.go @@ -426,14 +426,32 @@ func (e *ShowExec) fetchShowTables() error { return ErrBadDB.GenWithStackByArgs(e.DBName) } // sort for tables - tableNames := make([]string, 0, len(e.is.SchemaTables(e.DBName))) + schemaTables := e.is.SchemaTables(e.DBName) + tableNames := make([]string, 0, len(schemaTables)) activeRoles := e.ctx.GetSessionVars().ActiveRoles - var tableTypes = make(map[string]string) - for _, v := range e.is.SchemaTables(e.DBName) { + var ( + tableTypes = make(map[string]string) + fieldPatternsRegexp *regexp.Regexp + FieldFilterEnable bool + fieldFilter string + ) + if e.Extractor != nil { + extractor := (e.Extractor).(*plannercore.ShowTablesTableExtractor) + if extractor.FieldPatterns != "" { + fieldPatternsRegexp = regexp.MustCompile(extractor.FieldPatterns) + } + FieldFilterEnable = extractor.Field != "" + fieldFilter = extractor.Field + } + for _, v := range schemaTables { // Test with mysql.AllPrivMask means any privilege would be OK. // TODO: Should consider column privileges, which also make a table visible. if checker != nil && !checker.RequestVerification(activeRoles, e.DBName.O, v.Meta().Name.O, "", mysql.AllPrivMask) { continue + } else if FieldFilterEnable && v.Meta().Name.L != fieldFilter { + continue + } else if fieldPatternsRegexp != nil && !fieldPatternsRegexp.MatchString(v.Meta().Name.L) { + continue } tableNames = append(tableNames, v.Meta().Name.O) if v.Meta().IsView() { diff --git a/planner/core/memtable_predicate_extractor_test.go b/planner/core/memtable_predicate_extractor_test.go index 663da2cfb92d2..2e11ed2d2263d 100644 --- a/planner/core/memtable_predicate_extractor_test.go +++ b/planner/core/memtable_predicate_extractor_test.go @@ -1661,6 +1661,7 @@ func TestPredicateQuery(t *testing.T) { tk := testkit.NewTestKit(t, store) tk.MustExec("use test") tk.MustExec("create table t(id int, abclmn int);") + tk.MustExec("create table abclmn(a 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")) @@ -1693,4 +1694,13 @@ func TestPredicateQuery(t *testing.T) { tk.MustGetErrCode("show columns from t like id", errno.ErrBadField) tk.MustGetErrCode("show columns from t like `id`", errno.ErrBadField) + + tk.MustQuery("show tables like 't'").Check(testkit.Rows("t")) + tk.MustQuery("show tables like 'T'").Check(testkit.Rows("t")) + tk.MustQuery("show tables like 'ABCLMN'").Check(testkit.Rows("abclmn")) + tk.MustQuery("show tables like 'ABC%'").Check(testkit.Rows("abclmn")) + tk.MustQuery("show tables like '%lmn'").Check(testkit.Rows("abclmn")) + tk.MustQuery("show full tables like '%lmn'").Check(testkit.Rows("abclmn BASE TABLE")) + tk.MustGetErrCode("show tables like T", errno.ErrBadField) + tk.MustGetErrCode("show tables like `T`", errno.ErrBadField) } diff --git a/planner/core/planbuilder.go b/planner/core/planbuilder.go index fdfdf8057ce16..61246210f7c65 100644 --- a/planner/core/planbuilder.go +++ b/planner/core/planbuilder.go @@ -2876,7 +2876,17 @@ func (b *PlanBuilder) buildShow(ctx context.Context, show *ast.ShowStmt) (Plan, // avoid to build Selection. show.Pattern = nil } - case ast.ShowTables, ast.ShowTableStatus: + case ast.ShowTables: + if p.DBName == "" { + return nil, ErrNoDB + } + var extractor ShowTablesTableExtractor + if extractor.Extract(show) { + p.Extractor = &extractor + // Avoid building Selection. + show.Pattern = nil + } + case ast.ShowTableStatus: if p.DBName == "" { return nil, ErrNoDB } diff --git a/planner/core/show_predicate_extractor.go b/planner/core/show_predicate_extractor.go index 0be76a053fd6d..103c4107c7f5e 100644 --- a/planner/core/show_predicate_extractor.go +++ b/planner/core/show_predicate_extractor.go @@ -25,7 +25,10 @@ import ( "github.com/pingcap/tidb/util/stringutil" ) -var _ ShowPredicateExtractor = &ShowColumnsTableExtractor{} +var ( + _ ShowPredicateExtractor = &ShowColumnsTableExtractor{} + _ ShowPredicateExtractor = &ShowTablesTableExtractor{} +) // 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. @@ -75,7 +78,6 @@ func (e *ShowColumnsTableExtractor) Extract(show *ast.ShowStmt) bool { return true } return false - } func (e *ShowColumnsTableExtractor) explainInfo() string { @@ -95,3 +97,47 @@ func (e *ShowColumnsTableExtractor) explainInfo() string { } return s } + +// ShowTablesTableExtractor is used to extract some predicates of tables. +type ShowTablesTableExtractor struct { + ShowColumnsTableExtractor +} + +// Extract implements the ShowTablesTableExtractor Extract interface +func (e *ShowTablesTableExtractor) 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 TABLE in t LIKE `abc``. + ptn := pattern.Pattern.(*driver.ValueExpr).GetString() + patValue, patTypes := stringutil.CompilePattern(ptn, pattern.Escape) + if 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 + } + } + return false +} + +func (e *ShowTablesTableExtractor) explainInfo() string { + r := new(bytes.Buffer) + if len(e.Field) > 0 { + r.WriteString(fmt.Sprintf("table:[%s], ", e.Field)) + } + + if len(e.FieldPatterns) > 0 { + r.WriteString(fmt.Sprintf("table_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_test.go b/planner/core/stringer_test.go index 3ca5cbfc66568..e4163c7c29c8e 100644 --- a/planner/core/stringer_test.go +++ b/planner/core/stringer_test.go @@ -56,6 +56,22 @@ func TestPlanStringer(t *testing.T) { sql: "desc t a", plan: "Show(field:[a])", }, + { + sql: "show tables in test like 't'", + plan: "Show(table:[t])", + }, + { + sql: "show tables in test like 'T'", + plan: "Show(table:[t])", + }, + { + sql: "show tables in test like 't%'", + plan: "Show(table_pattern:[(?i)t.*])", + }, + { + sql: "show tables in test like '%T%'", + plan: "Show(table_pattern:[(?i).*T.*])", + }, } parser := parser.New() for _, tt := range tests {