681 lines
18 KiB
Go
681 lines
18 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 (
|
|
"bytes"
|
|
"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/model"
|
|
"github.com/pingcap/tidb/mysql"
|
|
"github.com/pingcap/tidb/plan"
|
|
"github.com/pingcap/tidb/privilege"
|
|
"github.com/pingcap/tidb/sessionctx"
|
|
"github.com/pingcap/tidb/sessionctx/variable"
|
|
"github.com/pingcap/tidb/stmt"
|
|
"github.com/pingcap/tidb/table"
|
|
"github.com/pingcap/tidb/terror"
|
|
"github.com/pingcap/tidb/util/charset"
|
|
"github.com/pingcap/tidb/util/format"
|
|
)
|
|
|
|
var (
|
|
_ plan.Plan = (*ShowPlan)(nil)
|
|
)
|
|
|
|
// ShowPlan is used for show statements
|
|
type ShowPlan struct {
|
|
Target int
|
|
DBName string
|
|
TableName string
|
|
ColumnName string
|
|
Flag int
|
|
Full bool
|
|
// Used by SHOW VARIABLES
|
|
GlobalScope bool
|
|
Pattern *expression.PatternLike
|
|
Where expression.Expression
|
|
rows []*plan.Row
|
|
cursor int
|
|
User string // ShowGrants need to know username.
|
|
}
|
|
|
|
func (s *ShowPlan) isColOK(c *column.Col) bool {
|
|
// support `desc tableName columnName`
|
|
// TODO: columnName can be a regular
|
|
if s.ColumnName == "" {
|
|
return true
|
|
}
|
|
|
|
if strings.EqualFold(s.ColumnName, c.Name.L) {
|
|
return true
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
// Explain implements plan.Plan Explain interface.
|
|
func (s *ShowPlan) Explain(w format.Formatter) {
|
|
// TODO: finish this
|
|
}
|
|
|
|
// GetFields implements plan.Plan GetFields interface.
|
|
func (s *ShowPlan) GetFields() []*field.ResultField {
|
|
var (
|
|
names []string
|
|
types []byte
|
|
)
|
|
|
|
switch s.Target {
|
|
case stmt.ShowEngines:
|
|
names = []string{"Engine", "Support", "Comment", "Transactions", "XA", "Savepoints"}
|
|
case stmt.ShowDatabases:
|
|
names = []string{"Database"}
|
|
case stmt.ShowTables:
|
|
names = []string{fmt.Sprintf("Tables_in_%s", s.DBName)}
|
|
if s.Full {
|
|
names = append(names, "Table_type")
|
|
}
|
|
case stmt.ShowTableStatus:
|
|
names = []string{"Name", "Engine", "Version", "Row_format", "Rows", "Avg_row_length",
|
|
"Data_length", "Max_data_length", "Index_length", "Data_free", "Auto_increment",
|
|
"Create_time", "Update_time", "Check_time", "Collation", "Checksum",
|
|
"Create_options", "Comment"}
|
|
types = []byte{mysql.TypeVarchar, mysql.TypeVarchar, mysql.TypeLonglong, mysql.TypeVarchar, mysql.TypeLonglong, mysql.TypeLonglong,
|
|
mysql.TypeLonglong, mysql.TypeLonglong, mysql.TypeLonglong, mysql.TypeLonglong, mysql.TypeLonglong,
|
|
mysql.TypeDatetime, mysql.TypeDatetime, mysql.TypeDatetime, mysql.TypeVarchar, mysql.TypeVarchar,
|
|
mysql.TypeVarchar, mysql.TypeVarchar}
|
|
case stmt.ShowColumns:
|
|
names = column.ColDescFieldNames(s.Full)
|
|
case stmt.ShowWarnings:
|
|
names = []string{"Level", "Code", "Message"}
|
|
types = []byte{mysql.TypeVarchar, mysql.TypeLong, mysql.TypeVarchar}
|
|
case stmt.ShowCharset:
|
|
names = []string{"Charset", "Description", "Default collation", "Maxlen"}
|
|
types = []byte{mysql.TypeVarchar, mysql.TypeVarchar, mysql.TypeVarchar, mysql.TypeLonglong}
|
|
case stmt.ShowVariables:
|
|
names = []string{"Variable_name", "Value"}
|
|
case stmt.ShowStatus:
|
|
names = []string{"Variable_name", "Value"}
|
|
case stmt.ShowCollation:
|
|
names = []string{"Collation", "Charset", "Id", "Default", "Compiled", "Sortlen"}
|
|
types = []byte{mysql.TypeVarchar, mysql.TypeVarchar, mysql.TypeLonglong,
|
|
mysql.TypeVarchar, mysql.TypeVarchar, mysql.TypeLonglong}
|
|
case stmt.ShowCreateTable:
|
|
names = []string{"Table", "Create Table"}
|
|
case stmt.ShowGrants:
|
|
names = []string{fmt.Sprintf("Grants for %s", s.User)}
|
|
case stmt.ShowTriggers:
|
|
names = []string{"Trigger", "Event", "Table", "Statement", "Timing", "Created",
|
|
"sql_mode", "Definer", "character_set_client", "collation_connection", "Database Collation"}
|
|
types = []byte{mysql.TypeVarchar, mysql.TypeVarchar, mysql.TypeVarchar, mysql.TypeVarchar, mysql.TypeVarchar, mysql.TypeVarchar,
|
|
mysql.TypeVarchar, mysql.TypeVarchar, mysql.TypeVarchar, mysql.TypeVarchar, mysql.TypeVarchar}
|
|
}
|
|
fields := make([]*field.ResultField, 0, len(names))
|
|
for i, name := range names {
|
|
f := &field.ResultField{Name: name}
|
|
if types == nil || types[i] == 0 {
|
|
// use varchar as the default return column type
|
|
f.Col.Tp = mysql.TypeVarchar
|
|
} else {
|
|
f.Col.Tp = types[i]
|
|
}
|
|
|
|
fields = append(fields, f)
|
|
}
|
|
|
|
return fields
|
|
}
|
|
|
|
// Filter implements plan.Plan Filter interface.
|
|
func (s *ShowPlan) Filter(ctx context.Context, expr expression.Expression) (plan.Plan, bool, error) {
|
|
return s, false, nil
|
|
}
|
|
|
|
// Next implements plan.Plan Next interface.
|
|
func (s *ShowPlan) Next(ctx context.Context) (row *plan.Row, err error) {
|
|
if s.rows == nil {
|
|
if err := s.fetchAll(ctx); err != nil {
|
|
return nil, errors.Trace(err)
|
|
}
|
|
}
|
|
if s.cursor == len(s.rows) {
|
|
return
|
|
}
|
|
row = s.rows[s.cursor]
|
|
s.cursor++
|
|
return
|
|
}
|
|
|
|
func (s *ShowPlan) fetchAll(ctx context.Context) error {
|
|
switch s.Target {
|
|
case stmt.ShowEngines:
|
|
return s.fetchShowEngines(ctx)
|
|
case stmt.ShowDatabases:
|
|
return s.fetchShowDatabases(ctx)
|
|
case stmt.ShowTables:
|
|
return s.fetchShowTables(ctx)
|
|
case stmt.ShowTableStatus:
|
|
return s.fetchShowTableStatus(ctx)
|
|
case stmt.ShowColumns:
|
|
return s.fetchShowColumns(ctx)
|
|
case stmt.ShowWarnings:
|
|
// empty result
|
|
case stmt.ShowCharset:
|
|
return s.fetchShowCharset(ctx)
|
|
case stmt.ShowVariables:
|
|
return s.fetchShowVariables(ctx)
|
|
case stmt.ShowStatus:
|
|
return s.fetchShowStatus(ctx)
|
|
case stmt.ShowCollation:
|
|
return s.fetchShowCollation(ctx)
|
|
case stmt.ShowCreateTable:
|
|
return s.fetchShowCreateTable(ctx)
|
|
case stmt.ShowGrants:
|
|
return s.fetchShowGrants(ctx)
|
|
case stmt.ShowTriggers:
|
|
return s.fetchShowTriggers(ctx)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Close implements plan.Plan Close interface.
|
|
func (s *ShowPlan) Close() error {
|
|
s.rows = nil
|
|
s.cursor = 0
|
|
return nil
|
|
}
|
|
|
|
func (s *ShowPlan) evalCondition(ctx context.Context, m map[interface{}]interface{}) (bool, error) {
|
|
var cond expression.Expression
|
|
if s.Pattern != nil {
|
|
cond = s.Pattern
|
|
} else if s.Where != nil {
|
|
cond = s.Where
|
|
}
|
|
|
|
if cond == nil {
|
|
return true, nil
|
|
}
|
|
return expression.EvalBoolExpr(ctx, cond, m)
|
|
}
|
|
|
|
func (s *ShowPlan) getTable(ctx context.Context) (table.Table, error) {
|
|
is := sessionctx.GetDomain(ctx).InfoSchema()
|
|
dbName := model.NewCIStr(s.DBName)
|
|
if !is.SchemaExists(dbName) {
|
|
// MySQL returns no such table here if database doesn't exist.
|
|
return nil, errors.Trace(mysql.NewErr(mysql.ErrNoSuchTable, s.DBName, s.TableName))
|
|
}
|
|
tbName := model.NewCIStr(s.TableName)
|
|
tb, err := is.TableByName(dbName, tbName)
|
|
if err != nil {
|
|
return nil, errors.Trace(mysql.NewErr(mysql.ErrNoSuchTable, s.DBName, s.TableName))
|
|
}
|
|
return tb, nil
|
|
}
|
|
|
|
func (s *ShowPlan) fetchShowColumns(ctx context.Context) error {
|
|
tb, err := s.getTable(ctx)
|
|
if err != nil {
|
|
return errors.Trace(err)
|
|
}
|
|
|
|
cols := tb.Cols()
|
|
|
|
for _, col := range cols {
|
|
if !s.isColOK(col) {
|
|
continue
|
|
}
|
|
|
|
desc := column.NewColDesc(col)
|
|
|
|
// The FULL keyword causes the output to include the column collation and comments,
|
|
// as well as the privileges you have for each column.
|
|
row := &plan.Row{}
|
|
if s.Full {
|
|
row.Data = []interface{}{
|
|
desc.Field,
|
|
desc.Type,
|
|
desc.Collation,
|
|
desc.Null,
|
|
desc.Key,
|
|
desc.DefaultValue,
|
|
desc.Extra,
|
|
desc.Privileges,
|
|
desc.Comment,
|
|
}
|
|
} else {
|
|
row.Data = []interface{}{
|
|
desc.Field,
|
|
desc.Type,
|
|
desc.Null,
|
|
desc.Key,
|
|
desc.DefaultValue,
|
|
desc.Extra,
|
|
}
|
|
}
|
|
s.rows = append(s.rows, row)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *ShowPlan) fetchShowCollation(ctx context.Context) error {
|
|
collations := charset.GetCollations()
|
|
m := map[interface{}]interface{}{}
|
|
|
|
for _, v := range collations {
|
|
if s.Pattern != nil {
|
|
s.Pattern.Expr = expression.Value{Val: v.Name}
|
|
} else if s.Where != nil {
|
|
m[expression.ExprEvalIdentFunc] = func(name string) (interface{}, error) {
|
|
switch {
|
|
case strings.EqualFold(name, "Collation"):
|
|
return v.Name, nil
|
|
case strings.EqualFold(name, "Charset"):
|
|
return v.CharsetName, nil
|
|
case strings.EqualFold(name, "Id"):
|
|
return v.ID, nil
|
|
case strings.EqualFold(name, "Default"):
|
|
if v.IsDefault {
|
|
return "Yes", nil
|
|
}
|
|
return "", nil
|
|
case strings.EqualFold(name, "Compiled"):
|
|
return "Yes", nil
|
|
case strings.EqualFold(name, "Sortlen"):
|
|
// TODO: add sort length in Collation
|
|
return 1, nil
|
|
default:
|
|
return nil, errors.Errorf("unknown field %s", name)
|
|
}
|
|
}
|
|
}
|
|
|
|
match, err := s.evalCondition(ctx, m)
|
|
if err != nil {
|
|
return errors.Trace(err)
|
|
}
|
|
if !match {
|
|
continue
|
|
}
|
|
|
|
isDefault := ""
|
|
if v.IsDefault {
|
|
isDefault = "Yes"
|
|
}
|
|
row := &plan.Row{Data: []interface{}{v.Name, v.CharsetName, v.ID, isDefault, "Yes", 1}}
|
|
s.rows = append(s.rows, row)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *ShowPlan) fetchShowTables(ctx context.Context) error {
|
|
is := sessionctx.GetDomain(ctx).InfoSchema()
|
|
dbName := model.NewCIStr(s.DBName)
|
|
if !is.SchemaExists(dbName) {
|
|
return errors.Errorf("Can not find DB: %s", dbName)
|
|
}
|
|
|
|
// sort for tables
|
|
var tableNames []string
|
|
for _, v := range is.SchemaTables(dbName) {
|
|
tableNames = append(tableNames, v.TableName().L)
|
|
}
|
|
|
|
sort.Strings(tableNames)
|
|
|
|
m := map[interface{}]interface{}{}
|
|
for _, v := range tableNames {
|
|
data := []interface{}{v}
|
|
if s.Full {
|
|
// TODO: support "VIEW" later if we have supported view feature.
|
|
// now, just use "BASE TABLE".
|
|
data = append(data, "BASE TABLE")
|
|
}
|
|
// Check like/where clause.
|
|
if s.Pattern != nil {
|
|
s.Pattern.Expr = expression.Value{Val: data[0]}
|
|
} else if s.Where != nil {
|
|
m[expression.ExprEvalIdentFunc] = func(name string) (interface{}, error) {
|
|
if s.Full && strings.EqualFold(name, "Table_type") {
|
|
return data[1], nil
|
|
}
|
|
return nil, errors.Errorf("unknown field %s", name)
|
|
}
|
|
}
|
|
match, err := s.evalCondition(ctx, m)
|
|
if err != nil {
|
|
return errors.Trace(err)
|
|
}
|
|
if !match {
|
|
continue
|
|
}
|
|
s.rows = append(s.rows, &plan.Row{Data: data})
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *ShowPlan) fetchShowTableStatus(ctx context.Context) error {
|
|
is := sessionctx.GetDomain(ctx).InfoSchema()
|
|
dbName := model.NewCIStr(s.DBName)
|
|
if !is.SchemaExists(dbName) {
|
|
return errors.Errorf("Can not find DB: %s", dbName)
|
|
}
|
|
|
|
// sort for tables
|
|
var tableNames []string
|
|
for _, v := range is.SchemaTables(dbName) {
|
|
tableNames = append(tableNames, v.TableName().L)
|
|
}
|
|
|
|
sort.Strings(tableNames)
|
|
|
|
m := map[interface{}]interface{}{}
|
|
for _, v := range tableNames {
|
|
// Check like/where clause.
|
|
if s.Pattern != nil {
|
|
s.Pattern.Expr = expression.Value{Val: v}
|
|
} else if s.Where != nil {
|
|
m[expression.ExprEvalIdentFunc] = func(name string) (interface{}, error) {
|
|
return nil, errors.Errorf("unknown field %s", name)
|
|
}
|
|
}
|
|
match, err := s.evalCondition(ctx, m)
|
|
if err != nil {
|
|
return errors.Trace(err)
|
|
}
|
|
if !match {
|
|
continue
|
|
}
|
|
now := mysql.CurrentTime(mysql.TypeDatetime)
|
|
data := []interface{}{
|
|
v, "InnoDB", "10", "Compact", 100, 100,
|
|
100, 100, 100, 100, 100,
|
|
now, now, now, "utf8_general_ci", "",
|
|
"", "",
|
|
}
|
|
s.rows = append(s.rows, &plan.Row{Data: data})
|
|
}
|
|
return nil
|
|
}
|
|
func (s *ShowPlan) fetchShowVariables(ctx context.Context) error {
|
|
sessionVars := variable.GetSessionVars(ctx)
|
|
globalVars := variable.GetGlobalVarAccessor(ctx)
|
|
m := map[interface{}]interface{}{}
|
|
|
|
for _, v := range variable.SysVars {
|
|
if s.Pattern != nil {
|
|
s.Pattern.Expr = expression.Value{Val: v.Name}
|
|
} else if s.Where != nil {
|
|
m[expression.ExprEvalIdentFunc] = func(name string) (interface{}, error) {
|
|
if strings.EqualFold(name, "Variable_name") {
|
|
return v.Name, nil
|
|
}
|
|
|
|
return nil, errors.Errorf("unknown field %s", name)
|
|
}
|
|
}
|
|
|
|
match, err := s.evalCondition(ctx, m)
|
|
if err != nil {
|
|
return errors.Trace(err)
|
|
}
|
|
if !match {
|
|
continue
|
|
}
|
|
|
|
var value string
|
|
if !s.GlobalScope {
|
|
// Try to get Session Scope variable value first.
|
|
sv, ok := sessionVars.Systems[v.Name]
|
|
if ok {
|
|
value = sv
|
|
} else {
|
|
// If session scope variable is not set, get the global scope value.
|
|
value, err = globalVars.GetGlobalSysVar(ctx, v.Name)
|
|
if err != nil {
|
|
return errors.Trace(err)
|
|
}
|
|
}
|
|
} else {
|
|
value, err = globalVars.GetGlobalSysVar(ctx, v.Name)
|
|
if err != nil {
|
|
return errors.Trace(err)
|
|
}
|
|
}
|
|
row := &plan.Row{Data: []interface{}{v.Name, value}}
|
|
s.rows = append(s.rows, row)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func getSessionStatusVar(ctx context.Context, sessionVars *variable.SessionVars,
|
|
globalVars variable.GlobalVarAccessor, name string) (string, error) {
|
|
sv, ok := sessionVars.StatusVars[name]
|
|
if ok {
|
|
return sv, nil
|
|
}
|
|
|
|
value, err := globalVars.GetGlobalStatusVar(ctx, name)
|
|
if err != nil && terror.UnknownStatusVar.Equal(err) {
|
|
return "", errors.Trace(err)
|
|
}
|
|
|
|
return value, nil
|
|
}
|
|
|
|
func getGlobalStatusVar(ctx context.Context, sessionVars *variable.SessionVars,
|
|
globalVars variable.GlobalVarAccessor, name string) (string, error) {
|
|
|
|
value, err := globalVars.GetGlobalStatusVar(ctx, name)
|
|
if err == nil {
|
|
return value, nil
|
|
}
|
|
|
|
if terror.UnknownStatusVar.Equal(err) {
|
|
return "", errors.Trace(err)
|
|
}
|
|
|
|
sv, _ := sessionVars.StatusVars[name]
|
|
|
|
return sv, nil
|
|
}
|
|
|
|
func (s *ShowPlan) fetchShowStatus(ctx context.Context) error {
|
|
sessionVars := variable.GetSessionVars(ctx)
|
|
globalVars := variable.GetGlobalVarAccessor(ctx)
|
|
m := map[interface{}]interface{}{}
|
|
|
|
for _, v := range variable.StatusVars {
|
|
if s.Pattern != nil {
|
|
s.Pattern.Expr = expression.Value{Val: v.Name}
|
|
} else if s.Where != nil {
|
|
m[expression.ExprEvalIdentFunc] = func(name string) (interface{}, error) {
|
|
if strings.EqualFold(name, "Variable_name") {
|
|
return v.Name, nil
|
|
}
|
|
|
|
return nil, errors.Errorf("unknown field %s", name)
|
|
}
|
|
}
|
|
|
|
match, err := s.evalCondition(ctx, m)
|
|
if err != nil {
|
|
return errors.Trace(err)
|
|
}
|
|
if !match {
|
|
continue
|
|
}
|
|
|
|
var value string
|
|
if !s.GlobalScope {
|
|
value, err = getSessionStatusVar(ctx, sessionVars, globalVars, v.Name)
|
|
if err != nil {
|
|
return errors.Trace(err)
|
|
}
|
|
} else if v.Scope != variable.ScopeSession {
|
|
value, err = getGlobalStatusVar(ctx, sessionVars, globalVars, v.Name)
|
|
if err != nil {
|
|
return errors.Trace(err)
|
|
}
|
|
} else {
|
|
continue
|
|
}
|
|
|
|
row := &plan.Row{Data: []interface{}{v.Name, value}}
|
|
s.rows = append(s.rows, row)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *ShowPlan) fetchShowCharset(ctx context.Context) error {
|
|
// See: http://dev.mysql.com/doc/refman/5.7/en/show-character-set.html
|
|
descs := charset.GetAllCharsets()
|
|
for _, desc := range descs {
|
|
row := &plan.Row{
|
|
Data: []interface{}{desc.Name, desc.Desc, desc.DefaultCollation, desc.Maxlen},
|
|
}
|
|
s.rows = append(s.rows, row)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *ShowPlan) fetchShowEngines(ctx context.Context) error {
|
|
// Mock data
|
|
row := &plan.Row{
|
|
Data: []interface{}{"InnoDB", "DEFAULT", "Supports transactions, row-level locking, and foreign keys", "YES", "YES", "YES"},
|
|
}
|
|
s.rows = append(s.rows, row)
|
|
return nil
|
|
}
|
|
|
|
func (s *ShowPlan) fetchShowDatabases(ctx context.Context) error {
|
|
dbs := sessionctx.GetDomain(ctx).InfoSchema().AllSchemaNames()
|
|
|
|
// TODO: let information_schema be the first database
|
|
sort.Strings(dbs)
|
|
|
|
for _, d := range dbs {
|
|
s.rows = append(s.rows, &plan.Row{Data: []interface{}{d}})
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *ShowPlan) fetchShowCreateTable(ctx context.Context) error {
|
|
tb, err := s.getTable(ctx)
|
|
if err != nil {
|
|
return errors.Trace(err)
|
|
}
|
|
|
|
// TODO: let the result more like MySQL.
|
|
var buf bytes.Buffer
|
|
buf.WriteString(fmt.Sprintf("CREATE TABLE `%s` (\n", tb.TableName().O))
|
|
for i, col := range tb.Cols() {
|
|
buf.WriteString(fmt.Sprintf(" `%s` %s", col.Name.O, col.GetTypeDesc()))
|
|
if mysql.HasAutoIncrementFlag(col.Flag) {
|
|
buf.WriteString(" NOT NULL AUTO_INCREMENT")
|
|
} else {
|
|
if mysql.HasNotNullFlag(col.Flag) {
|
|
buf.WriteString(" NOT NULL")
|
|
}
|
|
switch col.DefaultValue {
|
|
case nil:
|
|
buf.WriteString(" DEFAULT NULL")
|
|
case "CURRENT_TIMESTAMP":
|
|
buf.WriteString(" DEFAULT CURRENT_TIMESTAMP")
|
|
default:
|
|
buf.WriteString(fmt.Sprintf(" DEFAULT '%v'", col.DefaultValue))
|
|
}
|
|
|
|
if mysql.HasOnUpdateNowFlag(col.Flag) {
|
|
buf.WriteString(" ON UPDATE CURRENT_TIMESTAMP")
|
|
}
|
|
}
|
|
if i != len(tb.Cols())-1 {
|
|
buf.WriteString(",\n")
|
|
}
|
|
}
|
|
|
|
if len(tb.Indices()) > 0 {
|
|
buf.WriteString(",\n")
|
|
}
|
|
|
|
for i, idx := range tb.Indices() {
|
|
if idx.Primary {
|
|
buf.WriteString(" PRIMARY KEY ")
|
|
} else if idx.Unique {
|
|
buf.WriteString(fmt.Sprintf(" UNIQUE KEY `%s` ", idx.Name.O))
|
|
} else {
|
|
buf.WriteString(fmt.Sprintf(" KEY `%s` ", idx.Name.O))
|
|
}
|
|
|
|
cols := make([]string, 0, len(idx.Columns))
|
|
for _, c := range idx.Columns {
|
|
cols = append(cols, c.Name.O)
|
|
}
|
|
buf.WriteString(fmt.Sprintf("(`%s`)", strings.Join(cols, "`,`")))
|
|
if i != len(tb.Indices())-1 {
|
|
buf.WriteString(",\n")
|
|
}
|
|
}
|
|
|
|
buf.WriteString("\n")
|
|
|
|
buf.WriteString(") ENGINE=InnoDB")
|
|
if s := tb.Meta().Charset; len(s) > 0 {
|
|
buf.WriteString(fmt.Sprintf(" DEFAULT CHARSET=%s", s))
|
|
} else {
|
|
buf.WriteString(" DEFAULT CHARSET=latin1")
|
|
}
|
|
|
|
data := []interface{}{
|
|
tb.TableName().O,
|
|
buf.String(),
|
|
}
|
|
|
|
s.rows = append(s.rows, &plan.Row{Data: data})
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *ShowPlan) fetchShowGrants(ctx context.Context) error {
|
|
// Get checker
|
|
checker := privilege.GetPrivilegeChecker(ctx)
|
|
if checker == nil {
|
|
return errors.New("Miss privilege checker!")
|
|
}
|
|
gs, err := checker.ShowGrants(ctx, s.User)
|
|
if err != nil {
|
|
return errors.Trace(err)
|
|
}
|
|
for _, g := range gs {
|
|
data := []interface{}{g}
|
|
s.rows = append(s.rows, &plan.Row{Data: data})
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *ShowPlan) fetchShowTriggers(ctx context.Context) error {
|
|
return nil
|
|
}
|