From c133e7478cbdc1b7f21ef952812fb946575a10ee Mon Sep 17 00:00:00 2001 From: likzn <1020193211@qq.com> Date: Wed, 1 Jun 2022 18:14:27 +0800 Subject: [PATCH] *: support `SHOW TABLE STATUS` with case insensitivity (#35086) close pingcap/tidb#7518 --- executor/show.go | 80 +++++------ executor/show_test.go | 23 +++ planner/core/planbuilder.go | 43 +----- planner/core/show_predicate_extractor.go | 175 +++++++---------------- planner/core/stringer_test.go | 8 ++ 5 files changed, 121 insertions(+), 208 deletions(-) diff --git a/executor/show.go b/executor/show.go index a83c6bbb5108b..2271ee1188efe 100644 --- a/executor/show.go +++ b/executor/show.go @@ -404,25 +404,19 @@ func (e *ShowExec) fetchShowDatabases() error { sort.Strings(dbs) var ( fieldPatternsLike collate.WildcardPattern - FieldFilterEnable bool fieldFilter string ) if e.Extractor != nil { - extractor := (e.Extractor).(*plannercore.ShowDatabaseExtractor) - if extractor.FieldPatterns != "" { - fieldPatternsLike = collate.GetCollatorByID(collate.CollationName2ID(mysql.UTF8MB4DefaultCollation)).Pattern() - fieldPatternsLike.Compile(extractor.FieldPatterns, byte('\\')) - } - FieldFilterEnable = extractor.Field != "" - fieldFilter = extractor.Field + fieldFilter = e.Extractor.Field() + fieldPatternsLike = e.Extractor.FieldPatternLike() } // let information_schema be the first database moveInfoSchemaToFront(dbs) for _, d := range dbs { if checker != nil && !checker.DBIsVisible(e.ctx.GetSessionVars().ActiveRoles, d) { continue - } else if FieldFilterEnable && strings.ToLower(d) != fieldFilter { + } else if fieldFilter != "" && strings.ToLower(d) != fieldFilter { continue } else if fieldPatternsLike != nil && !fieldPatternsLike.DoMatch(strings.ToLower(d)) { continue @@ -484,24 +478,19 @@ func (e *ShowExec) fetchShowTables() error { var ( tableTypes = make(map[string]string) fieldPatternsLike collate.WildcardPattern - FieldFilterEnable bool fieldFilter string ) + if e.Extractor != nil { - extractor := (e.Extractor).(*plannercore.ShowTablesTableExtractor) - if extractor.FieldPatterns != "" { - fieldPatternsLike = collate.GetCollatorByID(collate.CollationName2ID(mysql.UTF8MB4DefaultCollation)).Pattern() - fieldPatternsLike.Compile(extractor.FieldPatterns, byte('\\')) - } - FieldFilterEnable = extractor.Field != "" - fieldFilter = extractor.Field + fieldFilter = e.Extractor.Field() + fieldPatternsLike = e.Extractor.FieldPatternLike() } 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 { + } else if fieldFilter != "" && v.Meta().Name.L != fieldFilter { continue } else if fieldPatternsLike != nil && !fieldPatternsLike.DoMatch(v.Meta().Name.L) { continue @@ -563,10 +552,23 @@ func (e *ShowExec) fetchShowTableStatus(ctx context.Context) error { if err != nil { return errors.Trace(err) } + var ( + fieldPatternsLike collate.WildcardPattern + fieldFilter string + ) + if e.Extractor != nil { + fieldFilter = e.Extractor.Field() + fieldPatternsLike = e.Extractor.FieldPatternLike() + } activeRoles := e.ctx.GetSessionVars().ActiveRoles for _, row := range rows { - if checker != nil && !checker.RequestVerification(activeRoles, e.DBName.O, row.GetString(0), "", mysql.AllPrivMask) { + tableName := row.GetString(0) + if checker != nil && !checker.RequestVerification(activeRoles, e.DBName.O, tableName, "", mysql.AllPrivMask) { + continue + } else if fieldFilter != "" && strings.ToLower(tableName) != fieldFilter { + continue + } else if fieldPatternsLike != nil && !fieldPatternsLike.DoMatch(strings.ToLower(tableName)) { continue } e.result.AppendRow(row) @@ -582,17 +584,12 @@ func (e *ShowExec) fetchShowColumns(ctx context.Context) error { } var ( fieldPatternsLike collate.WildcardPattern - FieldFilterEnable bool fieldFilter string ) + if e.Extractor != nil { - extractor := (e.Extractor).(*plannercore.ShowColumnsTableExtractor) - if extractor.FieldPatterns != "" { - fieldPatternsLike = collate.GetCollatorByID(collate.CollationName2ID(mysql.UTF8MB4DefaultCollation)).Pattern() - fieldPatternsLike.Compile(extractor.FieldPatterns, byte('\\')) - } - FieldFilterEnable = extractor.Field != "" - fieldFilter = extractor.Field + fieldFilter = e.Extractor.Field() + fieldPatternsLike = e.Extractor.FieldPatternLike() } checker := privilege.GetPrivilegeManager(e.ctx) @@ -613,7 +610,7 @@ func (e *ShowExec) fetchShowColumns(ctx context.Context) error { return err } for _, col := range cols { - if FieldFilterEnable && col.Name.L != fieldFilter { + if fieldFilter != "" && col.Name.L != fieldFilter { continue } else if fieldPatternsLike != nil && !fieldPatternsLike.DoMatch(col.Name.L) { continue @@ -806,17 +803,12 @@ func (e *ShowExec) fetchShowVariables() (err error) { ) var ( fieldPatternsLike collate.WildcardPattern - FieldFilterEnable bool fieldFilter string ) + if e.Extractor != nil { - extractor := (e.Extractor).(*plannercore.ShowVariablesExtractor) - if extractor.FieldPatterns != "" { - fieldPatternsLike = collate.GetCollatorByID(collate.CollationName2ID(mysql.UTF8MB4DefaultCollation)).Pattern() - fieldPatternsLike.Compile(extractor.FieldPatterns, byte('\\')) - } - FieldFilterEnable = extractor.Field != "" - fieldFilter = extractor.Field + fieldFilter = e.Extractor.Field() + fieldPatternsLike = e.Extractor.FieldPatternLike() } if e.GlobalScope { // Collect global scope variables, @@ -825,7 +817,7 @@ func (e *ShowExec) fetchShowVariables() (err error) { // otherwise, fetch the value from table `mysql.Global_Variables`. for _, v := range variable.GetSysVars() { if v.Scope != variable.ScopeSession { - if FieldFilterEnable && v.Name != fieldFilter { + if fieldFilter != "" && v.Name != fieldFilter { continue } else if fieldPatternsLike != nil && !fieldPatternsLike.DoMatch(v.Name) { continue @@ -847,7 +839,7 @@ func (e *ShowExec) fetchShowVariables() (err error) { // If it is a session only variable, use the default value defined in code, // otherwise, fetch the value from table `mysql.Global_Variables`. for _, v := range variable.GetSysVars() { - if FieldFilterEnable && v.Name != fieldFilter { + if fieldFilter != "" && v.Name != fieldFilter { continue } else if fieldPatternsLike != nil && !fieldPatternsLike.DoMatch(v.Name) { continue @@ -1476,17 +1468,11 @@ func (e *ShowExec) fetchShowCreatePlacementPolicy() error { func (e *ShowExec) fetchShowCollation() error { var ( fieldPatternsLike collate.WildcardPattern - FieldFilterEnable bool fieldFilter string ) if e.Extractor != nil { - extractor := (e.Extractor).(*plannercore.ShowCollationExtractor) - if extractor.FieldPatterns != "" { - fieldPatternsLike = collate.GetCollatorByID(collate.CollationName2ID(mysql.UTF8MB4DefaultCollation)).Pattern() - fieldPatternsLike.Compile(extractor.FieldPatterns, byte('\\')) - } - FieldFilterEnable = extractor.Field != "" - fieldFilter = extractor.Field + fieldPatternsLike = e.Extractor.FieldPatternLike() + fieldFilter = e.Extractor.Field() } collations := collate.GetSupportedCollations() @@ -1495,7 +1481,7 @@ func (e *ShowExec) fetchShowCollation() error { if v.IsDefault { isDefault = "Yes" } - if FieldFilterEnable && strings.ToLower(v.Name) != fieldFilter { + if fieldFilter != "" && strings.ToLower(v.Name) != fieldFilter { continue } else if fieldPatternsLike != nil && !fieldPatternsLike.DoMatch(v.Name) { continue diff --git a/executor/show_test.go b/executor/show_test.go index 251dd35558292..2963022c21567 100644 --- a/executor/show_test.go +++ b/executor/show_test.go @@ -1849,6 +1849,29 @@ func TestShowDatabasesLike(t *testing.T) { tk.MustQuery("SHOW DATABASES LIKE 'test_%'").Check(testkit.Rows("TEST_$1", "test_$2")) } +func TestShowTableStatusLike(t *testing.T) { + store, clean := testkit.CreateMockStore(t) + defer clean() + + tk := testkit.NewTestKit(t, store) + tk.MustExec("use test") + tk.MustExec("DROP table IF EXISTS `T1`") + tk.MustExec("CREATE table `T1` (a int);") + rows := tk.MustQuery("SHOW table status LIKE 't1'").Rows() + require.Equal(t, "T1", rows[0][0]) + + tk.MustExec("DROP table IF EXISTS `Li_1`") + tk.MustExec("DROP table IF EXISTS `li_2`") + + tk.MustExec("CREATE table `Li_1` (a int);") + tk.MustExec("CREATE table `li_2` (a int);") + + rows = tk.MustQuery("SHOW table status LIKE 'li%'").Rows() + require.Equal(t, "Li_1", rows[0][0]) + require.Equal(t, "li_2", rows[1][0]) + +} + func TestShowCollationsLike(t *testing.T) { store, clean := testkit.CreateMockStore(t) defer clean() diff --git a/planner/core/planbuilder.go b/planner/core/planbuilder.go index d6d1c6f7b7bbc..7a11f48fa5812 100644 --- a/planner/core/planbuilder.go +++ b/planner/core/planbuilder.go @@ -2933,27 +2933,16 @@ func (b *PlanBuilder) buildShow(ctx context.Context, show *ast.ShowStmt) (Plan, 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: - if p.DBName == "" { + case ast.ShowDatabases, ast.ShowVariables, ast.ShowTables, ast.ShowColumns, ast.ShowTableStatus, ast.ShowCollation: + if (show.Tp == ast.ShowTables || show.Tp == ast.ShowTableStatus) && p.DBName == "" { return nil, ErrNoDB } - var extractor ShowTablesTableExtractor - if extractor.Extract(show) { - p.Extractor = &extractor + extractor := newShowBaseExtractor(*show) + if extractor.Extract() { + p.Extractor = extractor // Avoid building Selection. show.Pattern = nil } - case ast.ShowTableStatus: - if p.DBName == "" { - return nil, ErrNoDB - } case ast.ShowCreateTable, ast.ShowCreateSequence, ast.ShowPlacementForTable, ast.ShowPlacementForPartition: var err error if table, err := b.is.TableByName(show.Table.Schema, show.Table.Name); err == nil { @@ -3012,28 +3001,8 @@ func (b *PlanBuilder) buildShow(ctx context.Context, show *ast.ShowStmt) (Plan, if tableInfo.Meta().TempTableType != model.TempTableNone { return nil, ErrOptOnTemporaryTable.GenWithStackByArgs("show table regions") } - case ast.ShowDatabases: - var extractor ShowDatabaseExtractor - if extractor.Extract(show) { - p.Extractor = &extractor - // Avoid building Selection. - show.Pattern = nil - } - case ast.ShowCollation: - var extractor ShowCollationExtractor - if extractor.Extract(show) { - p.Extractor = &extractor - show.Pattern = nil - } - } - if show.Tp == ast.ShowVariables { - var extractor ShowVariablesExtractor - if extractor.Extract(show) { - p.Extractor = &extractor - // Avoid building Selection. - show.Pattern = nil - } } + schema, names := buildShowSchema(show, isView, isSequence) p.SetSchema(schema) p.names = names diff --git a/planner/core/show_predicate_extractor.go b/planner/core/show_predicate_extractor.go index ae44335ff4c2b..f5742e9f34dbe 100644 --- a/planner/core/show_predicate_extractor.go +++ b/planner/core/show_predicate_extractor.go @@ -20,16 +20,21 @@ import ( "strings" "github.com/pingcap/tidb/parser/ast" + "github.com/pingcap/tidb/parser/mysql" driver "github.com/pingcap/tidb/types/parser_driver" + "github.com/pingcap/tidb/util/collate" "github.com/pingcap/tidb/util/stringutil" ) +const ( + fieldKey = "field" + tableKey = "table" + databaseKey = "database" + collationKey = "collation" +) + var ( - _ ShowPredicateExtractor = &ShowColumnsTableExtractor{} - _ ShowPredicateExtractor = &ShowTablesTableExtractor{} - _ ShowPredicateExtractor = &ShowVariablesExtractor{} - _ ShowPredicateExtractor = &ShowDatabaseExtractor{} - _ ShowPredicateExtractor = &ShowCollationExtractor{} + _ ShowPredicateExtractor = &ShowBaseExtractor{} ) // ShowPredicateExtractor is used to extract some predicates from `PatternLikeExpr` clause @@ -42,56 +47,40 @@ var ( // it is a way to fix https://github.com/pingcap/tidb/issues/29910. type ShowPredicateExtractor interface { // Extract predicates which can be pushed down and returns whether the extractor can extract predicates. - Extract(show *ast.ShowStmt) bool + Extract() bool explainInfo() string + Field() string + FieldPatternLike() collate.WildcardPattern } // ShowBaseExtractor is the definition of base extractor for derived predicates. type ShowBaseExtractor struct { - Field string + ast.ShowStmt - FieldPatterns string -} + field string -// Extract implements the ShowPredicateExtractor interface. -func (e *ShowBaseExtractor) 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 XXXX 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 - } - e.FieldPatterns = strings.ToLower(string(patValue)) - return true - } - } - return false + fieldPattern string } -// ShowColumnsTableExtractor is used to extract some predicates of tables table. -type ShowColumnsTableExtractor struct { - ShowBaseExtractor +func newShowBaseExtractor(showStatement ast.ShowStmt) ShowPredicateExtractor { + return &ShowBaseExtractor{ShowStmt: showStatement} } -// Extract implements the MemTablePredicateExtractor Extract interface -func (e *ShowColumnsTableExtractor) Extract(show *ast.ShowStmt) bool { +// Extract implements the ShowPredicateExtractor interface. +func (e *ShowBaseExtractor) Extract() bool { + show := e.ShowStmt 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``. + // It is used in `SHOW XXXX 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)) + e.field = strings.ToLower(string(patValue)) return true } - e.FieldPatterns = strings.ToLower(string(patValue)) + e.fieldPattern = strings.ToLower(string(patValue)) return true case *ast.ColumnNameExpr: // It is used in `SHOW COLUMNS FROM t LIKE abc`. @@ -100,43 +89,33 @@ func (e *ShowColumnsTableExtractor) Extract(show *ast.ShowStmt) bool { } } else if show.Column != nil && show.Column.Name.L != "" { // it is used in `DESCRIBE t COLUMN`. - e.Field = show.Column.Name.L + 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] +// explainInfo implements the ShowPredicateExtractor interface. +func (e *ShowBaseExtractor) explainInfo() string { + key := "" + switch e.ShowStmt.Tp { + case ast.ShowVariables, ast.ShowColumns: + key = fieldKey + case ast.ShowTables, ast.ShowTableStatus: + key = tableKey + case ast.ShowDatabases: + key = databaseKey + case ast.ShowCollation: + key = collationKey } - return s -} -// ShowTablesTableExtractor is used to extract some predicates of tables. -type ShowTablesTableExtractor struct { - ShowBaseExtractor -} - -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.field) > 0 { + r.WriteString(fmt.Sprintf("%s:[%s], ", key, e.field)) } - if len(e.FieldPatterns) > 0 { - r.WriteString(fmt.Sprintf("table_pattern:[%s], ", e.FieldPatterns)) + if len(e.fieldPattern) > 0 { + r.WriteString(fmt.Sprintf("%s_pattern:[%s], ", key, e.fieldPattern)) } // remove the last ", " in the message info @@ -147,69 +126,17 @@ func (e *ShowTablesTableExtractor) explainInfo() string { return s } -// ShowVariablesExtractor is used to extract some predicates of variables. -type ShowVariablesExtractor struct { - ShowBaseExtractor +// Field will return the variable `field` in ShowBaseExtractor +func (e *ShowBaseExtractor) Field() string { + return e.field } -func (e *ShowVariablesExtractor) explainInfo() string { - r := new(bytes.Buffer) - if len(e.Field) > 0 { - r.WriteString(fmt.Sprintf("variable:[%s], ", e.Field)) - } - - if len(e.FieldPatterns) > 0 { - r.WriteString(fmt.Sprintf("variable_pattern:[%s], ", e.FieldPatterns)) +// FieldPatternLike will return compiled collate.WildcardPattern +func (e *ShowBaseExtractor) FieldPatternLike() collate.WildcardPattern { + if e.fieldPattern == "" { + return nil } - - // remove the last ", " in the message info - s := r.String() - if len(s) > 2 { - return s[:len(s)-2] - } - return s -} - -// ShowDatabaseExtractor is used to extract some predicates of databases. -type ShowDatabaseExtractor struct { - ShowBaseExtractor -} - -func (e *ShowDatabaseExtractor) explainInfo() string { - r := new(bytes.Buffer) - if len(e.Field) > 0 { - r.WriteString(fmt.Sprintf("database:[%s], ", e.Field)) - } - if len(e.FieldPatterns) > 0 { - r.WriteString(fmt.Sprintf("database_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 -} - -// ShowCollationExtractor is used to extract some predicates of collations. -type ShowCollationExtractor struct { - ShowBaseExtractor -} - -func (e *ShowCollationExtractor) explainInfo() string { - r := new(bytes.Buffer) - if len(e.Field) > 0 { - r.WriteString(fmt.Sprintf("collation:[%s], ", e.Field)) - } - if len(e.FieldPatterns) > 0 { - r.WriteString(fmt.Sprintf("collation_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 + fieldPatternsLike := collate.GetCollatorByID(collate.CollationName2ID(mysql.UTF8MB4DefaultCollation)).Pattern() + fieldPatternsLike.Compile(e.fieldPattern, byte('\\')) + return fieldPatternsLike } diff --git a/planner/core/stringer_test.go b/planner/core/stringer_test.go index 8e4124038112d..50f15ae9e9c37 100644 --- a/planner/core/stringer_test.go +++ b/planner/core/stringer_test.go @@ -88,6 +88,14 @@ func TestPlanStringer(t *testing.T) { sql: "show databases like '%T%'", plan: "Show(database_pattern:[%t%])", }, + { + sql: "show table status in test like 'T%'", + plan: "Show(table_pattern:[t%])", + }, + { + sql: "show table status in test like '%T%'", + plan: "Show(table_pattern:[%t%])", + }, { sql: "show collation like 't'", plan: "Show(collation:[t])",