456 lines
17 KiB
Go
456 lines
17 KiB
Go
// Copyright 2015 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 plans
|
|
|
|
import (
|
|
"fmt"
|
|
"sort"
|
|
"strings"
|
|
|
|
"github.com/juju/errors"
|
|
"github.com/pingcap/tidb/column"
|
|
"github.com/pingcap/tidb/context"
|
|
"github.com/pingcap/tidb/expression"
|
|
"github.com/pingcap/tidb/field"
|
|
"github.com/pingcap/tidb/infoschema"
|
|
"github.com/pingcap/tidb/model"
|
|
"github.com/pingcap/tidb/mysql"
|
|
"github.com/pingcap/tidb/plan"
|
|
"github.com/pingcap/tidb/sessionctx"
|
|
"github.com/pingcap/tidb/util/charset"
|
|
"github.com/pingcap/tidb/util/format"
|
|
"github.com/pingcap/tidb/util/types"
|
|
)
|
|
|
|
var _ = (*InfoSchemaPlan)(nil)
|
|
|
|
// InfoSchemaPlan handles information_schema query, simulates the behavior of
|
|
// MySQL.
|
|
type InfoSchemaPlan struct {
|
|
TableName string
|
|
rows []*plan.Row
|
|
cursor int
|
|
}
|
|
|
|
var (
|
|
schemataFields = buildResultFieldsForSchemata()
|
|
tablesFields = buildResultFieldsForTables()
|
|
columnsFields = buildResultFieldsForColumns()
|
|
statisticsFields = buildResultFieldsForStatistics()
|
|
characterSetsFields = buildResultFieldsForCharacterSets()
|
|
characterSetsRecords = buildCharacterSetsRecords()
|
|
collationsFields = buildResultFieldsForCollations()
|
|
collationsRecords = buildColltionsRecords()
|
|
)
|
|
|
|
const (
|
|
tableSchemata = "SCHEMATA"
|
|
tableTables = "TABLES"
|
|
tableColumns = "COLUMNS"
|
|
tableStatistics = "STATISTICS"
|
|
tableCharacterSets = "CHARACTER_SETS"
|
|
tableCollations = "COLLATIONS"
|
|
catalogVal = "def"
|
|
)
|
|
|
|
// NewInfoSchemaPlan returns new InfoSchemaPlan instance, and checks if the
|
|
// given table name is valid.
|
|
func NewInfoSchemaPlan(tableName string) (isp *InfoSchemaPlan, err error) {
|
|
switch strings.ToUpper(tableName) {
|
|
case tableSchemata:
|
|
case tableTables:
|
|
case tableColumns:
|
|
case tableStatistics:
|
|
case tableCharacterSets:
|
|
case tableCollations:
|
|
default:
|
|
return nil, errors.Errorf("table INFORMATION_SCHEMA.%s does not exist", tableName)
|
|
}
|
|
isp = &InfoSchemaPlan{
|
|
TableName: strings.ToUpper(tableName),
|
|
}
|
|
return
|
|
}
|
|
|
|
func buildResultFieldsForSchemata() (rfs []*field.ResultField) {
|
|
tbName := tableSchemata
|
|
rfs = append(rfs, buildResultField(tbName, "CATALOG_NAME", mysql.TypeVarchar, 512))
|
|
rfs = append(rfs, buildResultField(tbName, "SCHEMA_NAME", mysql.TypeVarchar, 64))
|
|
rfs = append(rfs, buildResultField(tbName, "DEFAULT_CHARACTER_SET_NAME", mysql.TypeVarchar, 64))
|
|
rfs = append(rfs, buildResultField(tbName, "DEFAULT_COLLATION_NAME", mysql.TypeVarchar, 32))
|
|
rfs = append(rfs, buildResultField(tbName, "SQL_PATH", mysql.TypeVarchar, 512))
|
|
return rfs
|
|
}
|
|
|
|
func buildResultFieldsForTables() (rfs []*field.ResultField) {
|
|
tbName := tableTables
|
|
rfs = append(rfs, buildResultField(tbName, "TABLE_CATALOG", mysql.TypeVarchar, 512))
|
|
rfs = append(rfs, buildResultField(tbName, "TABLE_SCHEMA", mysql.TypeVarchar, 64))
|
|
rfs = append(rfs, buildResultField(tbName, "TABLE_NAME", mysql.TypeVarchar, 64))
|
|
rfs = append(rfs, buildResultField(tbName, "TABLE_TYPE", mysql.TypeVarchar, 64))
|
|
rfs = append(rfs, buildResultField(tbName, "ENGINE", mysql.TypeVarchar, 64))
|
|
rfs = append(rfs, buildResultField(tbName, "VERSION", mysql.TypeLonglong, 21))
|
|
rfs = append(rfs, buildResultField(tbName, "ROW_FORMAT", mysql.TypeVarchar, 10))
|
|
rfs = append(rfs, buildResultField(tbName, "TABLE_ROWS", mysql.TypeLonglong, 21))
|
|
rfs = append(rfs, buildResultField(tbName, "AVG_ROW_LENGTH", mysql.TypeLonglong, 21))
|
|
rfs = append(rfs, buildResultField(tbName, "DATA_LENGTH", mysql.TypeLonglong, 21))
|
|
rfs = append(rfs, buildResultField(tbName, "MAX_DATA_LENGTH", mysql.TypeLonglong, 21))
|
|
rfs = append(rfs, buildResultField(tbName, "INDEX_LENGTH", mysql.TypeLonglong, 21))
|
|
rfs = append(rfs, buildResultField(tbName, "DATA_FREE", mysql.TypeLonglong, 21))
|
|
rfs = append(rfs, buildResultField(tbName, "AUTO_INCREMENT", mysql.TypeLonglong, 21))
|
|
rfs = append(rfs, buildResultField(tbName, "CREATE_TIME", mysql.TypeDatetime, 19))
|
|
rfs = append(rfs, buildResultField(tbName, "UPDATE_TIME", mysql.TypeDatetime, 19))
|
|
rfs = append(rfs, buildResultField(tbName, "CHECK_TIME", mysql.TypeDatetime, 19))
|
|
rfs = append(rfs, buildResultField(tbName, "TABLE_COLLATION", mysql.TypeVarchar, 32))
|
|
rfs = append(rfs, buildResultField(tbName, "CHECK_SUM", mysql.TypeLonglong, 21))
|
|
rfs = append(rfs, buildResultField(tbName, "CREATE_OPTIONS", mysql.TypeVarchar, 255))
|
|
rfs = append(rfs, buildResultField(tbName, "TABLE_COMMENT", mysql.TypeVarchar, 2048))
|
|
for i, f := range rfs {
|
|
f.Offset = i
|
|
}
|
|
return
|
|
}
|
|
|
|
func buildResultFieldsForColumns() (rfs []*field.ResultField) {
|
|
tbName := tableColumns
|
|
rfs = append(rfs, buildResultField(tbName, "TABLE_CATALOG", mysql.TypeVarchar, 512))
|
|
rfs = append(rfs, buildResultField(tbName, "TABLE_SCHEMA", mysql.TypeVarchar, 64))
|
|
rfs = append(rfs, buildResultField(tbName, "TABLE_NAME", mysql.TypeVarchar, 64))
|
|
rfs = append(rfs, buildResultField(tbName, "COLUMN_NAME", mysql.TypeVarchar, 64))
|
|
rfs = append(rfs, buildResultField(tbName, "ORIGINAL_POSITION", mysql.TypeLonglong, 64))
|
|
rfs = append(rfs, buildResultField(tbName, "COLUMN_DEFAULT", mysql.TypeBlob, 196606))
|
|
rfs = append(rfs, buildResultField(tbName, "IS_NULLABLE", mysql.TypeVarchar, 3))
|
|
rfs = append(rfs, buildResultField(tbName, "DATA_TYPE", mysql.TypeVarchar, 64))
|
|
rfs = append(rfs, buildResultField(tbName, "CHARACTER_MAXIMUM_LENGTH", mysql.TypeLonglong, 21))
|
|
rfs = append(rfs, buildResultField(tbName, "CHARACTOR_OCTET_LENGTH", mysql.TypeLonglong, 21))
|
|
rfs = append(rfs, buildResultField(tbName, "NUMERIC_PRECISION", mysql.TypeLonglong, 21))
|
|
rfs = append(rfs, buildResultField(tbName, "NUMERIC_SCALE", mysql.TypeLonglong, 21))
|
|
rfs = append(rfs, buildResultField(tbName, "DATETIME_PRECISION", mysql.TypeLonglong, 21))
|
|
rfs = append(rfs, buildResultField(tbName, "CHARACTER_SET_NAME", mysql.TypeVarchar, 32))
|
|
rfs = append(rfs, buildResultField(tbName, "COLLATION_NAME", mysql.TypeVarchar, 32))
|
|
rfs = append(rfs, buildResultField(tbName, "COLUMN_TYPE", mysql.TypeBlob, 196606))
|
|
rfs = append(rfs, buildResultField(tbName, "COLUMN_KEY", mysql.TypeVarchar, 3))
|
|
rfs = append(rfs, buildResultField(tbName, "EXTRA", mysql.TypeVarchar, 30))
|
|
rfs = append(rfs, buildResultField(tbName, "PRIVILEGES", mysql.TypeVarchar, 80))
|
|
rfs = append(rfs, buildResultField(tbName, "COLUMN_COMMENT", mysql.TypeVarchar, 1024))
|
|
for i, f := range rfs {
|
|
f.Offset = i
|
|
}
|
|
return
|
|
}
|
|
|
|
func buildResultFieldsForStatistics() (rfs []*field.ResultField) {
|
|
tbName := tableStatistics
|
|
rfs = append(rfs, buildResultField(tbName, "TABLE_CATALOG", mysql.TypeVarchar, 512))
|
|
rfs = append(rfs, buildResultField(tbName, "TABLE_SCHEMA", mysql.TypeVarchar, 64))
|
|
rfs = append(rfs, buildResultField(tbName, "TABLE_NAME", mysql.TypeVarchar, 64))
|
|
rfs = append(rfs, buildResultField(tbName, "NON_UNIQUE", mysql.TypeVarchar, 1))
|
|
rfs = append(rfs, buildResultField(tbName, "INDEX_SCHEMA", mysql.TypeVarchar, 64))
|
|
rfs = append(rfs, buildResultField(tbName, "INDEX_NAME", mysql.TypeVarchar, 64))
|
|
rfs = append(rfs, buildResultField(tbName, "SEQ_IN_INDEX", mysql.TypeLonglong, 2))
|
|
rfs = append(rfs, buildResultField(tbName, "COLUMN_NAME", mysql.TypeVarchar, 21))
|
|
rfs = append(rfs, buildResultField(tbName, "COLLATION", mysql.TypeVarchar, 1))
|
|
rfs = append(rfs, buildResultField(tbName, "CARDINALITY", mysql.TypeLonglong, 21))
|
|
rfs = append(rfs, buildResultField(tbName, "SUB_PART", mysql.TypeLonglong, 3))
|
|
rfs = append(rfs, buildResultField(tbName, "PACKED", mysql.TypeVarchar, 10))
|
|
rfs = append(rfs, buildResultField(tbName, "NULLABLE", mysql.TypeVarchar, 3))
|
|
rfs = append(rfs, buildResultField(tbName, "INDEX_TYPE", mysql.TypeVarchar, 16))
|
|
rfs = append(rfs, buildResultField(tbName, "COMMENT", mysql.TypeVarchar, 16))
|
|
rfs = append(rfs, buildResultField(tbName, "INDEX_COMMENT", mysql.TypeVarchar, 1024))
|
|
for i, f := range rfs {
|
|
f.Offset = i
|
|
}
|
|
return
|
|
}
|
|
|
|
func buildResultFieldsForCharacterSets() (rfs []*field.ResultField) {
|
|
tbName := tableCharacterSets
|
|
rfs = append(rfs, buildResultField(tbName, "CHARACTER_SET_NAME", mysql.TypeVarchar, 32))
|
|
rfs = append(rfs, buildResultField(tbName, "DEFAULT_COLLATE_NAME", mysql.TypeVarchar, 32))
|
|
rfs = append(rfs, buildResultField(tbName, "DESCRIPTION", mysql.TypeVarchar, 60))
|
|
rfs = append(rfs, buildResultField(tbName, "MAXLEN", mysql.TypeLonglong, 3))
|
|
return rfs
|
|
}
|
|
|
|
func buildCharacterSetsRecords() (records [][]interface{}) {
|
|
records = append(records,
|
|
[]interface{}{"ascii", "ascii_general_ci", "US ASCII", 1},
|
|
[]interface{}{"binary", "binary", "Binary pseudo charset", 1},
|
|
[]interface{}{"latin1", "latin1_swedish_ci", "cp1252 West European", 1},
|
|
[]interface{}{"utf8", "utf8_general_ci", "UTF-8 Unicode", 3},
|
|
[]interface{}{"utf8mb4", "utf8mb4_general_ci", "UTF-8 Unicode", 4},
|
|
)
|
|
return records
|
|
}
|
|
|
|
func buildResultFieldsForCollations() (rfs []*field.ResultField) {
|
|
tbName := tableCollations
|
|
rfs = append(rfs, buildResultField(tbName, "COLLATION_NAME", mysql.TypeVarchar, 32))
|
|
rfs = append(rfs, buildResultField(tbName, "CHARACTER_SET_NAME", mysql.TypeVarchar, 32))
|
|
rfs = append(rfs, buildResultField(tbName, "ID", mysql.TypeLonglong, 11))
|
|
rfs = append(rfs, buildResultField(tbName, "IS_DEFAULT", mysql.TypeVarchar, 3))
|
|
rfs = append(rfs, buildResultField(tbName, "IS_COMPILED", mysql.TypeVarchar, 3))
|
|
rfs = append(rfs, buildResultField(tbName, "SORTLEN", mysql.TypeLonglong, 3))
|
|
return rfs
|
|
}
|
|
|
|
func buildColltionsRecords() (records [][]interface{}) {
|
|
records = append(records,
|
|
[]interface{}{"ascii_general_ci", "ascii", 1, "Yes", "Yes", 1},
|
|
[]interface{}{"binary", "binary", 2, "Yes", "Yes", 1},
|
|
[]interface{}{"latin1_swedish_ci", "latin1", 3, "Yes", "Yes", 1},
|
|
[]interface{}{"utf8_general_ci", "utf8", 4, "Yes", "Yes", 1},
|
|
[]interface{}{"utf8mb4_general_ci", "utf8mb4", 5, "Yes", "Yes", 1},
|
|
)
|
|
return records
|
|
}
|
|
|
|
// Explain implements plan.Plan Explain interface.
|
|
func (isp *InfoSchemaPlan) Explain(w format.Formatter) {}
|
|
|
|
// Filter implements plan.Plan Filter interface.
|
|
func (isp *InfoSchemaPlan) Filter(ctx context.Context, expr expression.Expression) (p plan.Plan, filtered bool, err error) {
|
|
return isp, false, nil
|
|
}
|
|
|
|
// GetFields implements plan.Plan GetFields interface, simulates MySQL's output.
|
|
func (isp *InfoSchemaPlan) GetFields() []*field.ResultField {
|
|
switch isp.TableName {
|
|
case tableSchemata:
|
|
return schemataFields
|
|
case tableTables:
|
|
return tablesFields
|
|
case tableColumns:
|
|
return columnsFields
|
|
case tableStatistics:
|
|
return statisticsFields
|
|
case tableCharacterSets:
|
|
return characterSetsFields
|
|
case tableCollations:
|
|
return collationsFields
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func buildResultField(tableName, name string, tp byte, size int) *field.ResultField {
|
|
mCharset := charset.CharsetBin
|
|
mCollation := charset.CharsetBin
|
|
mFlag := mysql.UnsignedFlag
|
|
if tp == mysql.TypeVarchar || tp == mysql.TypeBlob {
|
|
mCharset = mysql.DefaultCharset
|
|
mCollation = mysql.DefaultCollationName
|
|
mFlag = 0
|
|
}
|
|
fieldType := types.FieldType{
|
|
Charset: mCharset,
|
|
Collate: mCollation,
|
|
Tp: tp,
|
|
Flen: size,
|
|
Flag: uint(mFlag),
|
|
}
|
|
colInfo := model.ColumnInfo{
|
|
Name: model.NewCIStr(name),
|
|
FieldType: fieldType,
|
|
}
|
|
field := &field.ResultField{
|
|
Col: column.Col{ColumnInfo: colInfo},
|
|
DBName: infoschema.Name,
|
|
TableName: tableName,
|
|
Name: colInfo.Name.O,
|
|
}
|
|
return field
|
|
}
|
|
|
|
// Next implements plan.Plan Next interface.
|
|
func (isp *InfoSchemaPlan) Next(ctx context.Context) (row *plan.Row, err error) {
|
|
if isp.rows == nil {
|
|
isp.fetchAll(ctx)
|
|
}
|
|
if isp.cursor == len(isp.rows) {
|
|
return
|
|
}
|
|
row = isp.rows[isp.cursor]
|
|
isp.cursor++
|
|
return
|
|
}
|
|
|
|
func (isp *InfoSchemaPlan) fetchAll(ctx context.Context) {
|
|
is := sessionctx.GetDomain(ctx).InfoSchema()
|
|
schemas := is.AllSchemas()
|
|
switch isp.TableName {
|
|
case tableSchemata:
|
|
isp.fetchSchemata(is.AllSchemaNames())
|
|
case tableTables:
|
|
isp.fetchTables(schemas)
|
|
case tableColumns:
|
|
isp.fetchColumns(schemas)
|
|
case tableStatistics:
|
|
isp.fetchStatistics(is, schemas)
|
|
case tableCharacterSets:
|
|
isp.fetchCharacterSets()
|
|
case tableCollations:
|
|
isp.fetchCollations()
|
|
}
|
|
}
|
|
|
|
func (isp *InfoSchemaPlan) fetchSchemata(schemas []string) {
|
|
sort.Strings(schemas)
|
|
for _, schema := range schemas {
|
|
record := []interface{}{
|
|
catalogVal, // CATALOG_NAME
|
|
schema, // SCHEMA_NAME
|
|
mysql.DefaultCharset, // DEFAULT_CHARACTER_SET_NAME
|
|
mysql.DefaultCollationName, // DEFAULT_COLLATION_NAME
|
|
nil,
|
|
}
|
|
isp.rows = append(isp.rows, &plan.Row{Data: record})
|
|
}
|
|
}
|
|
|
|
func (isp *InfoSchemaPlan) fetchTables(schemas []*model.DBInfo) {
|
|
for _, schema := range schemas {
|
|
for _, table := range schema.Tables {
|
|
record := []interface{}{
|
|
catalogVal, // TABLE_CATALOG
|
|
schema.Name.O, // TABLE_SCHEMA
|
|
table.Name.O, // TABLE_NAME
|
|
"BASE_TABLE", // TABLE_TYPE
|
|
"InnoDB", // ENGINE
|
|
uint64(10), // VERSION
|
|
"Compact", // ROW_FORMAT
|
|
uint64(0), // TABLE_ROWS
|
|
uint64(0), // AVG_ROW_LENGTH
|
|
uint64(16384), // DATA_LENGTH
|
|
uint64(0), // MAX_DATA_LENGTH
|
|
uint64(0), // INDEX_LENGTH
|
|
uint64(0), // DATA_FREE
|
|
nil, // AUTO_INCREMENT
|
|
nil, // CREATE_TIME
|
|
nil, // UPDATE_TIME
|
|
nil, // CHECK_TIME
|
|
"latin1_swedish_ci", // TABLE_COLLATION
|
|
nil, // CHECKSUM
|
|
"", // CREATE_OPTIONS
|
|
"", // TABLE_COMMENT
|
|
}
|
|
isp.rows = append(isp.rows, &plan.Row{Data: record})
|
|
}
|
|
}
|
|
}
|
|
|
|
func (isp *InfoSchemaPlan) fetchColumns(schemas []*model.DBInfo) {
|
|
for _, schema := range schemas {
|
|
for _, table := range schema.Tables {
|
|
for i, col := range table.Columns {
|
|
colLen := col.Flen
|
|
if colLen == types.UnspecifiedLength {
|
|
colLen = mysql.GetDefaultFieldLength(col.Tp)
|
|
}
|
|
decimal := col.Decimal
|
|
if decimal == types.UnspecifiedLength {
|
|
decimal = 0
|
|
}
|
|
columnType := col.FieldType.CompactStr()
|
|
columnDesc := column.NewColDesc(&column.Col{ColumnInfo: *col})
|
|
var columnDefault interface{}
|
|
if columnDesc.DefaultValue != nil {
|
|
columnDefault = fmt.Sprintf("%v", columnDesc.DefaultValue)
|
|
}
|
|
record := []interface{}{
|
|
catalogVal, // TABLE_CATALOG
|
|
schema.Name.O, // TABLE_SCHEMA
|
|
table.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
|
|
colLen, // CHARACTER_MAXIMUM_LENGTH
|
|
colLen, // CHARACTOR_OCTET_LENGTH
|
|
decimal, // NUMERIC_PRECISION
|
|
0, // NUMERIC_SCALE
|
|
0, // DATETIME_PRECISION
|
|
col.Charset, // CHARACTER_SET_NAME
|
|
col.Collate, // COLLATION_NAME
|
|
columnType, // COLUMN_TYPE
|
|
columnDesc.Key, // COLUMN_KEY
|
|
columnDesc.Extra, // EXTRA
|
|
"select,insert,update,references", // PRIVILEGES
|
|
"", // COLUMN_COMMENT
|
|
}
|
|
isp.rows = append(isp.rows, &plan.Row{Data: record})
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (isp *InfoSchemaPlan) fetchStatistics(is infoschema.InfoSchema, schemas []*model.DBInfo) {
|
|
for _, schema := range schemas {
|
|
for _, table := range schema.Tables {
|
|
for _, index := range table.Indices {
|
|
nonUnique := "1"
|
|
if index.Unique {
|
|
nonUnique = "0"
|
|
}
|
|
for i, key := range index.Columns {
|
|
col, _ := is.ColumnByName(schema.Name, table.Name, key.Name)
|
|
nullable := "YES"
|
|
if mysql.HasNotNullFlag(col.Flag) {
|
|
nullable = ""
|
|
}
|
|
record := []interface{}{
|
|
catalogVal, // TABLE_CATALOG
|
|
schema.Name.O, // TABLE_SCHEMA
|
|
table.Name.O, // TABLE_NAME
|
|
nonUnique, // NON_UNIQUE
|
|
schema.Name.O, // INDEX_SCHEMA
|
|
index.Name.O, // INDEX_NAME
|
|
i + 1, // SEQ_IN_INDEX
|
|
key.Name.O, // COLUMN_NAME
|
|
"A", // COLLATION
|
|
0, // CARDINALITY
|
|
nil, // SUB_PART
|
|
nil, // PACKED
|
|
nullable, // NULLABLE
|
|
"BTREE", // INDEX_TYPE
|
|
"", // COMMENT
|
|
"", // INDEX_COMMENT
|
|
}
|
|
isp.rows = append(isp.rows, &plan.Row{Data: record})
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (isp *InfoSchemaPlan) fetchCharacterSets() {
|
|
for _, record := range characterSetsRecords {
|
|
isp.rows = append(isp.rows, &plan.Row{Data: record})
|
|
}
|
|
}
|
|
|
|
func (isp *InfoSchemaPlan) fetchCollations() error {
|
|
for _, record := range collationsRecords {
|
|
isp.rows = append(isp.rows, &plan.Row{Data: record})
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Close implements plan.Plan Close interface.
|
|
func (isp *InfoSchemaPlan) Close() error {
|
|
isp.rows = nil
|
|
isp.cursor = 0
|
|
return nil
|
|
}
|