Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

*: add sys schema, sys.SCHEMA_UNUSED_INDEXES view and sys.SCHEMA_INDEX_USAGE view #22126

Open
wants to merge 15 commits into
base: master
Choose a base branch
from
46 changes: 46 additions & 0 deletions ddl/ddl_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -1923,6 +1923,52 @@ func (d *ddl) RecoverTable(ctx sessionctx.Context, recoverInfo *RecoverInfo) (er
return errors.Trace(err)
}

// BuildTableInfoFromCreateViewAST builds model.TableInfo from a CreateViewStmt statement.
func BuildTableInfoFromCreateViewAST(s *ast.CreateViewStmt) (*model.TableInfo, error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about renaming it to BuildViewInfoFromAST to correspond to BuildTableInfoFromAST?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don’t think this is a good name, because this function does not build ViewInfo, but TableInfo.

ctx := mock.NewContext()
viewInfo, err := buildViewInfo(ctx, s)
if err != nil {
return nil, err
}

var schemaCols = s.Select.(*ast.SelectStmt).Fields.Fields
viewInfo.Cols = make([]model.CIStr, len(schemaCols))
for i, v := range schemaCols {
viewInfo.Cols[i] = v.AsName
}

var cols = make([]*table.Column, len(schemaCols))
if s.Cols == nil {
for i, v := range schemaCols {
cols[i] = table.ToColumn(&model.ColumnInfo{
Name: v.AsName,
ID: int64(i),
Offset: i,
State: model.StatePublic,
})
}
} else {
for i, v := range s.Cols {
cols[i] = table.ToColumn(&model.ColumnInfo{
Name: v,
ID: int64(i),
Offset: i,
State: model.StatePublic,
})
}
}
tblInfo, err := buildTableInfo(ctx, s.ViewName.Name, cols, nil, mysql.DefaultCharset, "")
if err != nil {
return nil, err
}
tblInfo.View = viewInfo

if err = checkTableInfoValidExtra(tblInfo); err != nil {
return nil, err
}
return tblInfo, nil
}

func (d *ddl) CreateView(ctx sessionctx.Context, s *ast.CreateViewStmt) (err error) {
viewInfo, err := buildViewInfo(ctx, s)
if err != nil {
Expand Down
2 changes: 2 additions & 0 deletions domain/domain.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import (
"github.com/pingcap/tidb/errno"
"github.com/pingcap/tidb/infoschema"
"github.com/pingcap/tidb/infoschema/perfschema"
"github.com/pingcap/tidb/infoschema/sysschema"
"github.com/pingcap/tidb/kv"
"github.com/pingcap/tidb/meta"
"github.com/pingcap/tidb/metrics"
Expand Down Expand Up @@ -700,6 +701,7 @@ const serverIDForStandalone = 1 // serverID for standalone deployment.
// Init initializes a domain.
func (do *Domain) Init(ddlLease time.Duration, sysFactory func(*Domain) (pools.Resource, error)) error {
perfschema.Init()
sysschema.Init()
if ebd, ok := do.store.(tikv.EtcdBackend); ok {
var addrs []string
var err error
Expand Down
2 changes: 1 addition & 1 deletion executor/explain_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ func (s *testSuite1) TestExplainPrivileges(c *C) {
tk1.Se = se

tk.MustExec(`grant select on explaindatabase.v to 'explain'@'%'`)
tk1.MustQuery("show databases").Check(testkit.Rows("INFORMATION_SCHEMA", "explaindatabase"))
tk1.MustQuery("show databases").Check(testkit.Rows("INFORMATION_SCHEMA", "explaindatabase", "sys"))

tk1.MustExec("use explaindatabase")
tk1.MustQuery("select * from v")
Expand Down
6 changes: 3 additions & 3 deletions executor/infoschema_reader_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ func (s *testInfoschemaTableSuite) TestSchemataTables(c *C) {
Username: "schemata_tester",
Hostname: "127.0.0.1",
}, nil, nil), IsTrue)
schemataTester.MustQuery("select count(*) from information_schema.SCHEMATA;").Check(testkit.Rows("1"))
schemataTester.MustQuery("select count(*) from information_schema.SCHEMATA;").Check(testkit.Rows("2"))
schemataTester.MustQuery("select * from information_schema.SCHEMATA where schema_name='mysql';").Check(
[][]interface{}{})
schemataTester.MustQuery("select * from information_schema.SCHEMATA where schema_name='INFORMATION_SCHEMA';").Check(
Expand All @@ -180,9 +180,9 @@ func (s *testInfoschemaTableSuite) TestSchemataTables(c *C) {
tk.MustExec("GRANT ALL PRIVILEGES ON mysql.* TO r_mysql_priv;")
tk.MustExec("GRANT r_mysql_priv TO schemata_tester;")
schemataTester.MustExec("set role r_mysql_priv")
schemataTester.MustQuery("select count(*) from information_schema.SCHEMATA;").Check(testkit.Rows("2"))
schemataTester.MustQuery("select count(*) from information_schema.SCHEMATA;").Check(testkit.Rows("3"))
schemataTester.MustQuery("select * from information_schema.SCHEMATA;").Check(
testkit.Rows("def INFORMATION_SCHEMA utf8mb4 utf8mb4_bin <nil>", "def mysql utf8mb4 utf8mb4_bin <nil>"))
testkit.Rows("def INFORMATION_SCHEMA utf8mb4 utf8mb4_bin <nil>", "def mysql utf8mb4 utf8mb4_bin <nil>", "def sys utf8mb4 utf8mb4_bin <nil>"))
}

func (s *testInfoschemaTableSuite) TestTableIDAndIndexID(c *C) {
Expand Down
18 changes: 9 additions & 9 deletions executor/show_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,33 +51,33 @@ func (s *testSuite5) TestShowVisibility(c *C) {
c.Assert(se.Auth(&auth.UserIdentity{Username: "show", Hostname: "%"}, nil, nil), IsTrue)
tk1.Se = se

// No ShowDatabases privilege, this user would see nothing except INFORMATION_SCHEMA.
tk.MustQuery("show databases").Check(testkit.Rows("INFORMATION_SCHEMA"))
// No ShowDatabases privilege, this user would see nothing except INFORMATION_SCHEMA and sys.
tk.MustQuery("show databases").Check(testkit.Rows("INFORMATION_SCHEMA", "sys"))

// After grant, the user can see the database.
tk.MustExec(`grant select on showdatabase.t1 to 'show'@'%'`)
tk1.MustQuery("show databases").Check(testkit.Rows("INFORMATION_SCHEMA", "showdatabase"))
tk1.MustQuery("show databases").Check(testkit.Rows("INFORMATION_SCHEMA", "showdatabase", "sys"))

// The user can see t1 but not t2.
tk1.MustExec("use showdatabase")
tk1.MustQuery("show tables").Check(testkit.Rows("t1"))

// After revoke, show database result should be just except INFORMATION_SCHEMA.
// After revoke, show database result should be just except INFORMATION_SCHEMA and sys.
tk.MustExec(`revoke select on showdatabase.t1 from 'show'@'%'`)
tk1.MustQuery("show databases").Check(testkit.Rows("INFORMATION_SCHEMA"))
tk1.MustQuery("show databases").Check(testkit.Rows("INFORMATION_SCHEMA", "sys"))

// Grant any global privilege would make show databases available.
tk.MustExec(`grant CREATE on *.* to 'show'@'%'`)
rows := tk1.MustQuery("show databases").Rows()
c.Assert(len(rows), GreaterEqual, 2) // At least INFORMATION_SCHEMA and showdatabase
c.Assert(len(rows), GreaterEqual, 3) // At least INFORMATION_SCHEMA, showdatabase and sys

tk.MustExec(`drop user 'show'@'%'`)
tk.MustExec("drop database showdatabase")
}

func (s *testSuite5) TestShowDatabasesInfoSchemaFirst(c *C) {
tk := testkit.NewTestKit(c, s.store)
tk.MustQuery("show databases").Check(testkit.Rows("INFORMATION_SCHEMA"))
tk.MustQuery("show databases").Check(testkit.Rows("INFORMATION_SCHEMA", "sys"))
tk.MustExec(`create user 'show'@'%'`)

tk.MustExec(`create database AAAA`)
Expand All @@ -90,7 +90,7 @@ func (s *testSuite5) TestShowDatabasesInfoSchemaFirst(c *C) {
c.Assert(err, IsNil)
c.Assert(se.Auth(&auth.UserIdentity{Username: "show", Hostname: "%"}, nil, nil), IsTrue)
tk1.Se = se
tk1.MustQuery("show databases").Check(testkit.Rows("INFORMATION_SCHEMA", "AAAA", "BBBB"))
tk1.MustQuery("show databases").Check(testkit.Rows("INFORMATION_SCHEMA", "AAAA", "BBBB", "sys"))

tk.MustExec(`drop user 'show'@'%'`)
tk.MustExec(`drop database AAAA`)
Expand Down Expand Up @@ -240,7 +240,7 @@ func (s *testSuite5) TestIssue10549(c *C) {
tk.MustExec("SET DEFAULT ROLE app_developer TO 'dev';")

c.Assert(tk.Se.Auth(&auth.UserIdentity{Username: "dev", Hostname: "%", AuthUsername: "dev", AuthHostname: "%"}, nil, nil), IsTrue)
tk.MustQuery("SHOW DATABASES;").Check(testkit.Rows("INFORMATION_SCHEMA", "newdb"))
tk.MustQuery("SHOW DATABASES;").Check(testkit.Rows("INFORMATION_SCHEMA", "newdb", "sys"))
tk.MustQuery("SHOW GRANTS;").Check(testkit.Rows("GRANT USAGE ON *.* TO 'dev'@'%'", "GRANT ALL PRIVILEGES ON newdb.* TO 'dev'@'%'", "GRANT 'app_developer'@'%' TO 'dev'@'%'"))
tk.MustQuery("SHOW GRANTS FOR CURRENT_USER").Check(testkit.Rows("GRANT USAGE ON *.* TO 'dev'@'%'", "GRANT 'app_developer'@'%' TO 'dev'@'%'"))
}
Expand Down
8 changes: 4 additions & 4 deletions infoschema/infoschema_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,13 +124,13 @@ func (*testSuite) TestT(c *C) {
is := handle.Get()

schemaNames := is.AllSchemaNames()
c.Assert(schemaNames, HasLen, 4)
c.Assert(testutil.CompareUnorderedStringSlice(schemaNames, []string{util.InformationSchemaName.O, util.MetricSchemaName.O, util.PerformanceSchemaName.O, "Test"}), IsTrue)
c.Assert(schemaNames, HasLen, 5)
c.Assert(testutil.CompareUnorderedStringSlice(schemaNames, []string{util.InformationSchemaName.O, util.MetricSchemaName.O, util.PerformanceSchemaName.O, util.SysSchemaName.O, "Test"}), IsTrue)

schemas := is.AllSchemas()
c.Assert(schemas, HasLen, 4)
c.Assert(schemas, HasLen, 5)
schemas = is.Clone()
c.Assert(schemas, HasLen, 4)
c.Assert(schemas, HasLen, 5)

c.Assert(is.SchemaExists(dbName), IsTrue)
c.Assert(is.SchemaExists(noexist), IsFalse)
Expand Down
49 changes: 49 additions & 0 deletions infoschema/sysschema/const.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// Copyright 2021 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 sysschema

var sysSchemaTables = []string{
viewUnusedIndexes,
viewIndexUsage,
}

const viewUnusedIndexes = "CREATE VIEW sys." + viewNameUnusedIndexes +
` AS SELECT DISTINCT i.table_schema AS table_schema, i.table_name AS table_name, i.key_name AS index_name
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why use DISTINCT in these two SQLs?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider a situation, table t has an index idx_ab(a, b). There will be two rows of data in the table information_schema.tidb_indexes because this index covers two columns. So there will be two lines of the same (table_schema, t, idx_ab). DISTINCT is to deal with this situation.

FROM information_schema.tidb_indexes AS i
LEFT JOIN information_schema.tables AS t
ON t.table_schema = i.table_schema
AND t.table_name = i.table_name
LEFT JOIN mysql.schema_index_usage AS u
ON u.table_id = t.tidb_table_id
AND u.index_id = i.index_id
WHERE (u.query_count = 0 OR u.query_count is null)
AND i.key_name != 'PRIMARY'
AND i.table_schema not in ('mysql', 'PERFORMANCE_SCHEMA', 'INFORMATION_SCHEMA');`

const viewIndexUsage = "CREATE VIEW sys." + viewNameIndexUsage +
` AS SELECT DISTINCT i.table_schema AS table_schema,
i.table_name AS table_name,
i.key_name AS index_name,
IFNULL(u.query_count, 0) AS query_count,
IFNULL(u.rows_selected, 0) AS rows_selected,
u.last_used_at AS last_used_at
FROM information_schema.tidb_indexes AS i
LEFT JOIN information_schema.tables AS t
ON t.table_schema = i.table_schema
AND t.table_name = i.table_name
LEFT JOIN mysql.schema_index_usage AS u
ON u.table_id = t.tidb_table_id
AND u.index_id = i.index_id
WHERE i.key_name != 'PRIMARY'
AND i.table_schema not in ('mysql', 'PERFORMANCE_SCHEMA', 'INFORMATION_SCHEMA');`
76 changes: 76 additions & 0 deletions infoschema/sysschema/init.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// Copyright 2021 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 sysschema

import (
"fmt"
"sync"

"github.com/pingcap/parser"
"github.com/pingcap/parser/ast"
"github.com/pingcap/parser/model"
"github.com/pingcap/parser/mysql"
"github.com/pingcap/tidb/ddl"
"github.com/pingcap/tidb/expression"
"github.com/pingcap/tidb/infoschema"
"github.com/pingcap/tidb/meta/autoid"
"github.com/pingcap/tidb/util"
)

var once sync.Once

// Init register the `sys` views.
// It should be init(), and the ideal usage should be:
//
// import _ "github.com/pingcap/tidb/sysschema"
//
// This function depends on plan/core.init(), which initialize the expression.EvalAstExpr function.
// The initialize order is a problem if init() is used as the function name.
func Init() {
initOnce := func() {
p := parser.New()
tbls := make([]*model.TableInfo, 0)
dbID := autoid.SysSchemaDBID
for _, sql := range sysSchemaTables {
stmt, err := p.ParseOneStmt(sql, "", "")
if err != nil {
panic(err)
}
meta, err := ddl.BuildTableInfoFromCreateViewAST(stmt.(*ast.CreateViewStmt))
if err != nil {
panic(err)
}
tbls = append(tbls, meta)
var ok bool
meta.ID, ok = tableIDMap[meta.Name.O]
if !ok {
panic(fmt.Sprintf("get sys table id failed, unknown system table `%v`", meta.Name.O))
}
for i, c := range meta.Columns {
c.ID = int64(i) + 1
}
}
dbInfo := &model.DBInfo{
ID: dbID,
Name: util.SysSchemaName,
Charset: mysql.DefaultCharset,
Collate: mysql.DefaultCollationName,
Tables: tbls,
}
infoschema.RegisterVirtualTable(dbInfo, tableFromMeta)
}
if expression.EvalAstExpr != nil {
once.Do(initOnce)
}
}
Loading