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

[hotfix]: cherry-pick 33946, 34538 and 35367 to v5.2.4 #36898

Merged
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 72 additions & 37 deletions executor/infoschema_reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ import (
"github.com/pingcap/tidb/util/chunk"
"github.com/pingcap/tidb/util/collate"
"github.com/pingcap/tidb/util/deadlockhistory"
"github.com/pingcap/tidb/util/hint"
"github.com/pingcap/tidb/util/keydecoder"
"github.com/pingcap/tidb/util/logutil"
"github.com/pingcap/tidb/util/pdapi"
Expand Down Expand Up @@ -615,85 +616,119 @@ func (e *hugeMemTableRetriever) dataForColumnsInTable(ctx context.Context, sctx
sctx.GetSessionVars().StmtCtx.AppendWarning(err)
return
}
is := sctx.GetInfoSchema().(infoschema.InfoSchema)
var viewSchema *expression.Schema
var viewOutputNames types.NameSlice
if tbl.IsView() {
var viewLogicalPlan plannercore.Plan
// Build plan is not thread safe, there will be concurrency on sessionctx.
if err := runWithSystemSession(sctx, func(s sessionctx.Context) error {
planBuilder, _ := plannercore.NewPlanBuilder().Init(s, is, &hint.BlockHintProcessor{})
var err error
viewLogicalPlan, err = planBuilder.BuildDataSourceFromView(ctx, schema.Name, tbl)
if err != nil {
return errors.Trace(err)
}
return nil
}); err != nil {
sctx.GetSessionVars().StmtCtx.AppendWarning(err)
return
}

viewSchema = viewLogicalPlan.Schema()
viewOutputNames = viewLogicalPlan.OutputNames()
}

for i, col := range tbl.Columns {
if col.Hidden {
continue
}

ft := &col.FieldType
if viewSchema != nil {
// If this is a view, replace the column with the view column.
idx := expression.FindFieldNameIdxByColName(viewOutputNames, col.Name.L)
if idx >= 0 {
col1 := viewSchema.Columns[idx]
ft = col1.GetType()
}
}

var charMaxLen, charOctLen, numericPrecision, numericScale, datetimePrecision interface{}
colLen, decimal := col.Flen, col.Decimal
defaultFlen, defaultDecimal := mysql.GetDefaultFieldLengthAndDecimal(col.Tp)
colLen, decimal := ft.Flen, ft.Decimal
defaultFlen, defaultDecimal := mysql.GetDefaultFieldLengthAndDecimal(ft.Tp)
if decimal == types.UnspecifiedLength {
decimal = defaultDecimal
}
if colLen == types.UnspecifiedLength {
colLen = defaultFlen
}
if col.Tp == mysql.TypeSet {
if ft.Tp == mysql.TypeSet {
// Example: In MySQL set('a','bc','def','ghij') has length 13, because
// len('a')+len('bc')+len('def')+len('ghij')+len(ThreeComma)=13
// Reference link: https://bugs.mysql.com/bug.php?id=22613
colLen = 0
for _, ele := range col.Elems {
for _, ele := range ft.Elems {
colLen += len(ele)
}
if len(col.Elems) != 0 {
colLen += (len(col.Elems) - 1)
if len(ft.Elems) != 0 {
colLen += (len(ft.Elems) - 1)
}
charMaxLen = colLen
charOctLen = calcCharOctLength(colLen, col.Charset)
} else if col.Tp == mysql.TypeEnum {
charOctLen = calcCharOctLength(colLen, ft.Charset)
} else if ft.Tp == mysql.TypeEnum {
// Example: In MySQL enum('a', 'ab', 'cdef') has length 4, because
// the longest string in the enum is 'cdef'
// Reference link: https://bugs.mysql.com/bug.php?id=22613
colLen = 0
for _, ele := range col.Elems {
for _, ele := range ft.Elems {
if len(ele) > colLen {
colLen = len(ele)
}
}
charMaxLen = colLen
charOctLen = calcCharOctLength(colLen, col.Charset)
} else if types.IsString(col.Tp) {
charOctLen = calcCharOctLength(colLen, ft.Charset)
} else if types.IsString(ft.Tp) {
charMaxLen = colLen
charOctLen = calcCharOctLength(colLen, col.Charset)
} else if types.IsTypeFractionable(col.Tp) {
charOctLen = calcCharOctLength(colLen, ft.Charset)
} else if types.IsTypeFractionable(ft.Tp) {
datetimePrecision = decimal
} else if types.IsTypeNumeric(col.Tp) {
} else if types.IsTypeNumeric(ft.Tp) {
numericPrecision = colLen
if col.Tp != mysql.TypeFloat && col.Tp != mysql.TypeDouble {
if ft.Tp != mysql.TypeFloat && ft.Tp != mysql.TypeDouble {
numericScale = decimal
} else if decimal != -1 {
numericScale = decimal
}
}
columnType := col.FieldType.InfoSchemaStr()
columnType := ft.InfoSchemaStr()
columnDesc := table.NewColDesc(table.ToColumn(col))
var columnDefault interface{}
if columnDesc.DefaultValue != nil {
columnDefault = fmt.Sprintf("%v", columnDesc.DefaultValue)
}
record := types.MakeDatums(
infoschema.CatalogVal, // TABLE_CATALOG
schema.Name.O, // TABLE_SCHEMA
tbl.Name.O, // TABLE_NAME
col.Name.O, // COLUMN_NAME
i+1, // ORIGINAL_POSITION
columnDefault, // COLUMN_DEFAULT
columnDesc.Null, // IS_NULLABLE
types.TypeToStr(col.Tp, col.Charset), // DATA_TYPE
charMaxLen, // CHARACTER_MAXIMUM_LENGTH
charOctLen, // CHARACTER_OCTET_LENGTH
numericPrecision, // NUMERIC_PRECISION
numericScale, // NUMERIC_SCALE
datetimePrecision, // DATETIME_PRECISION
columnDesc.Charset, // CHARACTER_SET_NAME
columnDesc.Collation, // COLLATION_NAME
columnType, // COLUMN_TYPE
columnDesc.Key, // COLUMN_KEY
columnDesc.Extra, // EXTRA
"select,insert,update,references", // PRIVILEGES
columnDesc.Comment, // COLUMN_COMMENT
col.GeneratedExprString, // GENERATION_EXPRESSION
infoschema.CatalogVal, // TABLE_CATALOG
schema.Name.O, // TABLE_SCHEMA
tbl.Name.O, // TABLE_NAME
col.Name.O, // COLUMN_NAME
i+1, // ORIGINAL_POSITION
columnDefault, // COLUMN_DEFAULT
columnDesc.Null, // IS_NULLABLE
types.TypeToStr(ft.Tp, ft.Charset), // DATA_TYPE
charMaxLen, // CHARACTER_MAXIMUM_LENGTH
charOctLen, // CHARACTER_OCTET_LENGTH
numericPrecision, // NUMERIC_PRECISION
numericScale, // NUMERIC_SCALE
datetimePrecision, // DATETIME_PRECISION
columnDesc.Charset, // CHARACTER_SET_NAME
columnDesc.Collation, // COLLATION_NAME
columnType, // COLUMN_TYPE
columnDesc.Key, // COLUMN_KEY
columnDesc.Extra, // EXTRA
"select,insert,update,references", // PRIVILEGES
columnDesc.Comment, // COLUMN_COMMENT
col.GeneratedExprString, // GENERATION_EXPRESSION
)
e.rows = append(e.rows, record)
}
Expand Down
56 changes: 56 additions & 0 deletions executor/infoschema_reader_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -981,3 +981,59 @@ func (s *testInfoschemaTableSuite) TestTablesPKType(c *C) {
tk.MustQuery("SELECT TIDB_PK_TYPE FROM information_schema.tables where table_schema = 'test' and table_name = 't_common'").Check(testkit.Rows("CLUSTERED"))
tk.MustQuery("SELECT TIDB_PK_TYPE FROM information_schema.tables where table_schema = 'INFORMATION_SCHEMA' and table_name = 'TABLES'").Check(testkit.Rows("NONCLUSTERED"))
}

// https://github.com/pingcap/tidb/issues/32459.
func (s *testInfoschemaTableSuite) TestJoinSystemTableContainsView(c *C) {
tk := testkit.NewTestKitWithInit(c, s.store)
tk.MustExec("use test")
tk.MustExec("create table t (a timestamp, b int);")
tk.MustExec("insert into t values (null, 100);")
tk.MustExec("create view v as select * from t;")
// This is used by grafana when TiDB is specified as the data source.
// See https://github.com/grafana/grafana/blob/e86b6662a187c77656f72bef3b0022bf5ced8b98/public/app/plugins/datasource/mysql/meta_query.ts#L31
for i := 0; i < 10; i++ {
tk.MustQuery(`
SELECT
table_name as table_name,
( SELECT
column_name as column_name
FROM information_schema.columns c
WHERE
c.table_schema = t.table_schema AND
c.table_name = t.table_name AND
c.data_type IN ('timestamp', 'datetime')
ORDER BY ordinal_position LIMIT 1
) AS time_column,
( SELECT
column_name AS column_name
FROM information_schema.columns c
WHERE
c.table_schema = t.table_schema AND
c.table_name = t.table_name AND
c.data_type IN('float', 'int', 'bigint')
ORDER BY ordinal_position LIMIT 1
) AS value_column
FROM information_schema.tables t
WHERE
t.table_schema = database() AND
EXISTS
( SELECT 1
FROM information_schema.columns c
WHERE
c.table_schema = t.table_schema AND
c.table_name = t.table_name AND
c.data_type IN ('timestamp', 'datetime')
) AND
EXISTS
( SELECT 1
FROM information_schema.columns c
WHERE
c.table_schema = t.table_schema AND
c.table_name = t.table_name AND
c.data_type IN('float', 'int', 'bigint')
)
LIMIT 1
;
`).Check(testkit.Rows("t a b"))
}
}
29 changes: 26 additions & 3 deletions executor/show.go
Original file line number Diff line number Diff line change
Expand Up @@ -1761,9 +1761,15 @@ func (e *ShowExec) fetchShowBuiltins() error {
// Because view's underlying table's column could change or recreate, so view's column type may change over time.
// To avoid this situation we need to generate a logical plan and extract current column types from Schema.
func tryFillViewColumnType(ctx context.Context, sctx sessionctx.Context, is infoschema.InfoSchema, dbName model.CIStr, tbl *model.TableInfo) error {
if tbl.IsView() {
if !tbl.IsView() {
return nil
}
// We need to run the build plan process in another session because there may be
// multiple goroutines running at the same time while session is not goroutine-safe.
// Take joining system table as an example, `fetchBuildSideRows` and `fetchProbeSideChunks` can be run concurrently.
return runWithSystemSession(sctx, func(s sessionctx.Context) error {
// Retrieve view columns info.
planBuilder, _ := plannercore.NewPlanBuilder().Init(sctx, is, &hint.BlockHintProcessor{})
planBuilder, _ := plannercore.NewPlanBuilder().Init(s, is, &hint.BlockHintProcessor{})
if viewLogicalPlan, err := planBuilder.BuildDataSourceFromView(ctx, dbName, tbl); err == nil {
viewSchema := viewLogicalPlan.Schema()
viewOutputNames := viewLogicalPlan.OutputNames()
Expand All @@ -1779,6 +1785,23 @@ func tryFillViewColumnType(ctx context.Context, sctx sessionctx.Context, is info
} else {
return err
}
return nil
})
}

func runWithSystemSession(sctx sessionctx.Context, fn func(sessionctx.Context) error) error {
b := &baseExecutor{ctx: sctx}
sysCtx, err := b.getSysSession()
if err != nil {
return err
}
return nil
// TODO(tangenta): remove the CurrentDB assignment after
// https://github.com/pingcap/tidb/issues/34090 is fixed.
originDB := sysCtx.GetSessionVars().CurrentDB
sysCtx.GetSessionVars().CurrentDB = sctx.GetSessionVars().CurrentDB
defer func() {
sysCtx.GetSessionVars().CurrentDB = originDB
}()
defer b.releaseSysSession(sysCtx)
return fn(sysCtx)
}
79 changes: 79 additions & 0 deletions infoschema/cluster_tables_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// 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,
// 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 infoschema_test

import (
"fmt"
"net/http/httptest"
"strconv"
"sync"
"testing"
"time"

"github.com/pingcap/parser/auth"
"github.com/pingcap/tidb/domain"
"github.com/pingcap/tidb/kv"
"github.com/pingcap/tidb/testkit"
"github.com/stretchr/testify/require"
"google.golang.org/grpc"
)

type clusterTablesSuite struct {
store kv.Storage
dom *domain.Domain
rpcserver *grpc.Server
httpServer *httptest.Server
mockAddr string
listenAddr string
startTime time.Time
}

func TestStmtSummaryIssue35340(t *testing.T) {
s := new(clusterTablesSuite)
s.store, s.dom = testkit.CreateMockStoreAndDomain(t)

tk := s.newTestKitWithRoot(t)
tk.MustExec("set global tidb_stmt_summary_refresh_interval=1800")
tk.MustExec("set global tidb_stmt_summary_max_stmt_count = 3000")
for i := 0; i < 100; i++ {
user := "user" + strconv.Itoa(i)
tk.MustExec(fmt.Sprintf("create user '%v'@'localhost'", user))
}
tk.MustExec("flush privileges")
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go func() {
defer wg.Done()
tk := s.newTestKitWithRoot(t)
for j := 0; j < 100; j++ {
user := "user" + strconv.Itoa(j)
require.True(t, tk.Session().Auth(&auth.UserIdentity{
Username: user,
Hostname: "localhost",
}, nil, nil))
tk.MustQuery("select count(*) from information_schema.statements_summary;")
}
}()
}
wg.Wait()
}

func (s *clusterTablesSuite) newTestKitWithRoot(t *testing.T) *testkit.TestKit {
tk := testkit.NewTestKit(t, s.store)
tk.MustExec("use test")
require.True(t, tk.Session().Auth(&auth.UserIdentity{Username: "root", Hostname: "%"}, nil, nil))
return tk
}
26 changes: 26 additions & 0 deletions testkit/mockstore.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,16 @@
// See the License for the specific language governing permissions and
// limitations under the License.

//go:build !codes
// +build !codes

package testkit

import (
"testing"
"time"

"github.com/pingcap/tidb/domain"
"github.com/pingcap/tidb/kv"
"github.com/pingcap/tidb/session"
"github.com/pingcap/tidb/store/mockstore"
Expand All @@ -44,3 +47,26 @@ func CreateMockStore(t *testing.T) (store kv.Storage, clean func()) {

return
}

// CreateMockStoreAndDomain return a new mock kv.Storage and *domain.Domain.
func CreateMockStoreAndDomain(t testing.TB, opts ...mockstore.MockTiKVStoreOption) (kv.Storage, *domain.Domain) {
store, err := mockstore.NewMockStore(opts...)
require.NoError(t, err)
return store, bootstrap(t, store, 0)
}

func bootstrap(t testing.TB, store kv.Storage, lease time.Duration) *domain.Domain {
session.SetSchemaLease(lease)
session.DisableStats4Test()
dom, err := session.BootstrapSession(store)
require.NoError(t, err)

dom.SetStatsUpdating(true)

t.Cleanup(func() {
dom.Close()
err := store.Close()
require.NoError(t, err)
})
return dom
}
Loading