Files
tidb/plan/planbuilder.go

1450 lines
46 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 plan
import (
"fmt"
"strings"
"github.com/juju/errors"
"github.com/pingcap/tidb/ast"
"github.com/pingcap/tidb/expression"
"github.com/pingcap/tidb/infoschema"
"github.com/pingcap/tidb/model"
"github.com/pingcap/tidb/mysql"
"github.com/pingcap/tidb/parser/opcode"
"github.com/pingcap/tidb/sessionctx"
"github.com/pingcap/tidb/table"
"github.com/pingcap/tidb/terror"
"github.com/pingcap/tidb/types"
"github.com/pingcap/tidb/util/ranger"
)
// Error instances.
var (
ErrUnsupportedType = terror.ClassOptimizerPlan.New(CodeUnsupportedType, "Unsupported type %T")
SystemInternalErrorType = terror.ClassOptimizerPlan.New(SystemInternalError, "System internal error")
ErrUnknownColumn = terror.ClassOptimizerPlan.New(CodeUnknownColumn, mysql.MySQLErrName[mysql.ErrBadField])
ErrUnknownTable = terror.ClassOptimizerPlan.New(CodeUnknownTable, mysql.MySQLErrName[mysql.ErrUnknownTable])
ErrWrongArguments = terror.ClassOptimizerPlan.New(CodeWrongArguments, "Incorrect arguments to EXECUTE")
ErrAmbiguous = terror.ClassOptimizerPlan.New(CodeAmbiguous, "Column '%s' in field list is ambiguous")
ErrAnalyzeMissIndex = terror.ClassOptimizerPlan.New(CodeAnalyzeMissIndex, "Index '%s' in field list does not exist in table '%s'")
ErrAlterAutoID = terror.ClassAutoid.New(CodeAlterAutoID, "No support for setting auto_increment using alter_table")
ErrBadGeneratedColumn = terror.ClassOptimizerPlan.New(CodeBadGeneratedColumn, mysql.MySQLErrName[mysql.ErrBadGeneratedColumn])
ErrFieldNotInGroupBy = terror.ClassOptimizerPlan.New(CodeFieldNotInGroupBy, mysql.MySQLErrName[mysql.ErrFieldNotInGroupBy])
ErrBadTable = terror.ClassOptimizerPlan.New(CodeBadTable, mysql.MySQLErrName[mysql.ErrBadTable])
ErrKeyDoesNotExist = terror.ClassOptimizerPlan.New(CodeKeyDoesNotExist, mysql.MySQLErrName[mysql.ErrKeyDoesNotExist])
)
// Error codes.
const (
CodeUnsupportedType terror.ErrCode = 1
SystemInternalError = 2
CodeAlterAutoID = 3
CodeAnalyzeMissIndex = 4
CodeAmbiguous = 1052
CodeUnknownColumn = mysql.ErrBadField
CodeUnknownTable = mysql.ErrUnknownTable
CodeWrongArguments = 1210
CodeBadGeneratedColumn = mysql.ErrBadGeneratedColumn
CodeFieldNotInGroupBy = mysql.ErrFieldNotInGroupBy
CodeBadTable = mysql.ErrBadTable
CodeKeyDoesNotExist = mysql.ErrKeyDoesNotExist
)
func init() {
tableMySQLErrCodes := map[terror.ErrCode]uint16{
CodeUnknownColumn: mysql.ErrBadField,
CodeUnknownTable: mysql.ErrBadTable,
CodeAmbiguous: mysql.ErrNonUniq,
CodeWrongArguments: mysql.ErrWrongArguments,
CodeBadGeneratedColumn: mysql.ErrBadGeneratedColumn,
CodeFieldNotInGroupBy: mysql.ErrFieldNotInGroupBy,
CodeBadTable: mysql.ErrBadTable,
CodeKeyDoesNotExist: mysql.ErrKeyDoesNotExist,
}
terror.ErrClassToMySQLCodes[terror.ClassOptimizerPlan] = tableMySQLErrCodes
}
type visitInfo struct {
privilege mysql.PrivilegeType
db string
table string
column string
}
type tableHintInfo struct {
indexNestedLoopJoinTables []model.CIStr
sortMergeJoinTables []model.CIStr
hashJoinTables []model.CIStr
}
func (info *tableHintInfo) ifPreferMergeJoin(tableNames ...*model.CIStr) bool {
return info.matchTableName(tableNames, info.sortMergeJoinTables)
}
func (info *tableHintInfo) ifPreferHashJoin(tableNames ...*model.CIStr) bool {
return info.matchTableName(tableNames, info.hashJoinTables)
}
func (info *tableHintInfo) ifPreferINLJ(tableNames ...*model.CIStr) bool {
return info.matchTableName(tableNames, info.indexNestedLoopJoinTables)
}
// matchTableName checks whether the hint hit the need.
// Only need either side matches one on the list.
// Even though you can put 2 tables on the list,
// it doesn't mean optimizer will reorder to make them
// join directly.
// Which it joins on with depend on sequence of traverse
// and without reorder, user might adjust themselves.
// This is similar to MySQL hints.
func (info *tableHintInfo) matchTableName(tables []*model.CIStr, tablesInHints []model.CIStr) bool {
for _, tableName := range tables {
if tableName == nil {
continue
}
for _, curEntry := range tablesInHints {
if curEntry.L == tableName.L {
return true
}
}
}
return false
}
// clauseCode indicates in which clause the column is currently.
type clauseCode int
const (
unknowClause clauseCode = iota
fieldList
havingClause
onClause
orderByClause
whereClause
groupByClause
showStatement
)
var clauseMsg = map[clauseCode]string{
unknowClause: "",
fieldList: "field list",
havingClause: "having clause",
onClause: "on clause",
orderByClause: "order clause",
whereClause: "where clause",
groupByClause: "group statement",
showStatement: "show statement",
}
// planBuilder builds Plan from an ast.Node.
// It just builds the ast node straightforwardly.
type planBuilder struct {
err error
ctx sessionctx.Context
is infoschema.InfoSchema
outerSchemas []*expression.Schema
inUpdateStmt bool
// colMapper stores the column that must be pre-resolved.
colMapper map[*ast.ColumnNameExpr]int
// Collect the visit information for privilege check.
visitInfo []visitInfo
tableHintInfo []tableHintInfo
optFlag uint64
curClause clauseCode
// rewriterPool stores the expressionRewriter we have created to reuse it if it has been released.
// rewriterCounter counts how many rewriter is being used.
rewriterPool []*expressionRewriter
rewriterCounter int
// inStraightJoin represents whether the current "SELECT" statement has
// "STRAIGHT_JOIN" option.
inStraightJoin bool
}
func (b *planBuilder) build(node ast.Node) Plan {
b.optFlag = flagPrunColumns
switch x := node.(type) {
case *ast.AdminStmt:
return b.buildAdmin(x)
case *ast.DeallocateStmt:
return &Deallocate{Name: x.Name}
case *ast.DeleteStmt:
return b.buildDelete(x)
case *ast.ExecuteStmt:
return b.buildExecute(x)
case *ast.ExplainStmt:
return b.buildExplain(x)
case *ast.InsertStmt:
return b.buildInsert(x)
case *ast.LoadDataStmt:
return b.buildLoadData(x)
case *ast.LoadStatsStmt:
return b.buildLoadStats(x)
case *ast.PrepareStmt:
return b.buildPrepare(x)
case *ast.SelectStmt:
return b.buildSelect(x)
case *ast.UnionStmt:
return b.buildUnion(x)
case *ast.UpdateStmt:
return b.buildUpdate(x)
case *ast.ShowStmt:
return b.buildShow(x)
case *ast.DoStmt:
return b.buildDo(x)
case *ast.SetStmt:
return b.buildSet(x)
case *ast.AnalyzeTableStmt:
return b.buildAnalyze(x)
case *ast.BinlogStmt, *ast.FlushStmt, *ast.UseStmt,
*ast.BeginStmt, *ast.CommitStmt, *ast.RollbackStmt, *ast.CreateUserStmt, *ast.SetPwdStmt,
*ast.GrantStmt, *ast.DropUserStmt, *ast.AlterUserStmt, *ast.RevokeStmt, *ast.KillStmt, *ast.DropStatsStmt:
return b.buildSimple(node.(ast.StmtNode))
case ast.DDLNode:
return b.buildDDL(x)
}
b.err = ErrUnsupportedType.Gen("Unsupported type %T", node)
return nil
}
func (b *planBuilder) buildExecute(v *ast.ExecuteStmt) Plan {
vars := make([]expression.Expression, 0, len(v.UsingVars))
for _, expr := range v.UsingVars {
newExpr, _, err := b.rewrite(expr, nil, nil, true)
if err != nil {
b.err = errors.Trace(err)
}
vars = append(vars, newExpr)
}
exe := &Execute{Name: v.Name, UsingVars: vars, ExecID: v.ExecID}
return exe
}
func (b *planBuilder) buildDo(v *ast.DoStmt) Plan {
dual := LogicalTableDual{RowCount: 1}.init(b.ctx)
p := LogicalProjection{Exprs: make([]expression.Expression, 0, len(v.Exprs))}.init(b.ctx)
schema := expression.NewSchema(make([]*expression.Column, 0, len(v.Exprs))...)
for _, astExpr := range v.Exprs {
expr, _, err := b.rewrite(astExpr, dual, nil, true)
if err != nil {
b.err = errors.Trace(err)
return nil
}
p.Exprs = append(p.Exprs, expr)
schema.Append(&expression.Column{
FromID: p.id,
Position: schema.Len() + 1,
RetType: expr.GetType(),
})
}
p.SetChildren(dual)
p.self = p
p.SetSchema(schema)
p.calculateNoDelay = true
return p
}
func (b *planBuilder) buildSet(v *ast.SetStmt) Plan {
p := &Set{}
for _, vars := range v.Variables {
assign := &expression.VarAssignment{
Name: vars.Name,
IsGlobal: vars.IsGlobal,
IsSystem: vars.IsSystem,
}
if _, ok := vars.Value.(*ast.DefaultExpr); !ok {
if cn, ok2 := vars.Value.(*ast.ColumnNameExpr); ok2 && cn.Name.Table.L == "" {
// Convert column name expression to string value expression.
vars.Value = ast.NewValueExpr(cn.Name.Name.O)
}
mockTablePlan := LogicalTableDual{}.init(b.ctx)
assign.Expr, _, b.err = b.rewrite(vars.Value, mockTablePlan, nil, true)
if b.err != nil {
return nil
}
} else {
assign.IsDefault = true
}
if vars.ExtendValue != nil {
assign.ExtendValue = &expression.Constant{
Value: vars.ExtendValue.Datum,
RetType: &vars.ExtendValue.Type,
}
}
p.VarAssigns = append(p.VarAssigns, assign)
}
return p
}
// Detect aggregate function or groupby clause.
func (b *planBuilder) detectSelectAgg(sel *ast.SelectStmt) bool {
if sel.GroupBy != nil {
return true
}
for _, f := range sel.Fields.Fields {
if ast.HasAggFlag(f.Expr) {
return true
}
}
if sel.Having != nil {
if ast.HasAggFlag(sel.Having.Expr) {
return true
}
}
if sel.OrderBy != nil {
for _, item := range sel.OrderBy.Items {
if ast.HasAggFlag(item.Expr) {
return true
}
}
}
return false
}
func getPathByIndexName(paths []*accessPath, idxName model.CIStr) *accessPath {
for _, path := range paths {
if path.index.Name.L == idxName.L {
return path
}
}
return nil
}
func getPossibleAccessPaths(indexHints []*ast.IndexHint, tblInfo *model.TableInfo) ([]*accessPath, error) {
publicPaths := make([]*accessPath, 0, len(tblInfo.Indices)+1)
publicPaths = append(publicPaths, &accessPath{isTablePath: true})
for _, index := range tblInfo.Indices {
if index.State == model.StatePublic {
publicPaths = append(publicPaths, &accessPath{index: index})
}
}
hasScanHint, hasUseOrForce := false, false
available := make([]*accessPath, 0, len(publicPaths))
ignored := make([]*accessPath, 0, len(publicPaths))
for _, hint := range indexHints {
if hint.HintScope != ast.HintForScan {
continue
}
hasScanHint = true
for _, idxName := range hint.IndexNames {
path := getPathByIndexName(publicPaths[1:], idxName)
if path == nil {
return nil, ErrKeyDoesNotExist.GenByArgs(idxName, tblInfo.Name)
}
if hint.HintType == ast.HintIgnore {
// Collect all the ignored index hints.
ignored = append(ignored, path)
continue
}
// Currently we don't distinguish between "FORCE" and "USE" because
// our cost estimation is not reliable.
hasUseOrForce = true
path.forced = true
available = append(available, path)
}
}
if !hasScanHint || !hasUseOrForce {
available = publicPaths
}
available = removeIgnoredPaths(available, ignored)
// If we have got "FORCE" or "USE" index hint but got no available index,
// we have to use table scan.
if len(available) == 0 {
available = append(available, &accessPath{isTablePath: true})
}
return available, nil
}
func removeIgnoredPaths(paths, ignoredPaths []*accessPath) []*accessPath {
if len(ignoredPaths) == 0 {
return paths
}
remainedPaths := make([]*accessPath, 0, len(paths))
for _, path := range paths {
if path.isTablePath || getPathByIndexName(ignoredPaths, path.index.Name) == nil {
remainedPaths = append(remainedPaths, path)
}
}
return remainedPaths
}
func findIndexByName(indices []*model.IndexInfo, name model.CIStr) *model.IndexInfo {
for _, idx := range indices {
if idx.Name.L == name.L {
return idx
}
}
return nil
}
func (b *planBuilder) buildSelectLock(src LogicalPlan, lock ast.SelectLockType) *LogicalLock {
selectLock := LogicalLock{Lock: lock}.init(b.ctx)
selectLock.SetChildren(src)
return selectLock
}
func (b *planBuilder) buildPrepare(x *ast.PrepareStmt) Plan {
p := &Prepare{
Name: x.Name,
}
if x.SQLVar != nil {
p.SQLText, _ = x.SQLVar.GetValue().(string)
} else {
p.SQLText = x.SQLText
}
return p
}
func (b *planBuilder) buildCheckIndex(dbName model.CIStr, as *ast.AdminStmt) Plan {
tblName := as.Tables[0]
tbl, err := b.is.TableByName(dbName, tblName.Name)
if err != nil {
b.err = errors.Trace(err)
return nil
}
tblInfo := tbl.Meta()
// get index information
var idx *model.IndexInfo
for _, index := range tblInfo.Indices {
if index.Name.L == strings.ToLower(as.Index) {
idx = index
break
}
}
if idx == nil {
b.err = errors.Errorf("index %s do not exist", as.Index)
return nil
}
if idx.State != model.StatePublic {
b.err = errors.Errorf("index %s state %s isn't public", as.Index, idx.State)
return nil
}
id := 1
columns := make([]*model.ColumnInfo, 0, len(idx.Columns))
schema := expression.NewSchema(make([]*expression.Column, 0, len(idx.Columns))...)
for _, idxCol := range idx.Columns {
for _, col := range tblInfo.Columns {
if idxCol.Name.L == col.Name.L {
columns = append(columns, col)
schema.Append(&expression.Column{
FromID: id,
Position: schema.Len() + 1,
RetType: &col.FieldType,
})
}
}
}
is := PhysicalIndexScan{
Table: tblInfo,
TableAsName: &tblName.Name,
DBName: dbName,
Columns: columns,
Index: idx,
dataSourceSchema: schema,
Ranges: ranger.FullRange(),
KeepOrder: false,
}.init(b.ctx)
is.stats = &statsInfo{}
cop := &copTask{indexPlan: is}
// It's double read case.
ts := PhysicalTableScan{Columns: columns, Table: is.Table}.init(b.ctx)
ts.SetSchema(is.dataSourceSchema)
cop.tablePlan = ts
is.initSchema(id, idx, true)
t := finishCopTask(b.ctx, cop)
rootT := t.(*rootTask)
return rootT.p
}
func (b *planBuilder) buildAdmin(as *ast.AdminStmt) Plan {
var ret Plan
switch as.Tp {
case ast.AdminCheckTable:
p := &CheckTable{Tables: as.Tables}
ret = p
case ast.AdminCheckIndex:
dbName := as.Tables[0].Schema
readerPlan := b.buildCheckIndex(dbName, as)
if b.err != nil {
return ret
}
ret = &CheckIndex{
DBName: dbName.L,
IdxName: as.Index,
IndexLookUpReader: readerPlan.(*PhysicalIndexLookUpReader),
}
case ast.AdminRecoverIndex:
p := &RecoverIndex{Table: as.Tables[0], IndexName: as.Index}
p.SetSchema(buildRecoverIndexFields())
ret = p
case ast.AdminCleanupIndex:
p := &CleanupIndex{Table: as.Tables[0], IndexName: as.Index}
p.SetSchema(buildCleanupIndexFields())
ret = p
case ast.AdminChecksumTable:
p := &ChecksumTable{Tables: as.Tables}
p.SetSchema(buildChecksumTableSchema())
ret = p
case ast.AdminShowDDL:
p := &ShowDDL{}
p.SetSchema(buildShowDDLFields())
ret = p
case ast.AdminShowDDLJobs:
p := &ShowDDLJobs{}
p.SetSchema(buildShowDDLJobsFields())
ret = p
case ast.AdminCancelDDLJobs:
p := &CancelDDLJobs{JobIDs: as.JobIDs}
p.SetSchema(buildCancelDDLJobsFields())
ret = p
case ast.AdminCheckIndexRange:
p := &CheckIndexRange{Table: as.Tables[0], IndexName: as.Index, HandleRanges: as.HandleRanges}
schema, err := buildCheckIndexSchema(as.Tables[0], as.Index)
if err != nil {
b.err = errors.Trace(err)
break
}
p.SetSchema(schema)
ret = p
case ast.AdminShowDDLJobQueries:
p := &ShowDDLJobQueries{JobIDs: as.JobIDs}
p.SetSchema(buildShowDDLJobQueriesFields())
ret = p
default:
b.err = ErrUnsupportedType.Gen("Unsupported type %T", as)
}
return ret
}
func buildCheckIndexSchema(tn *ast.TableName, indexName string) (*expression.Schema, error) {
schema := expression.NewSchema()
indexName = strings.ToLower(indexName)
indicesInfo := tn.TableInfo.Indices
cols := tn.TableInfo.Cols()
for _, idxInfo := range indicesInfo {
if idxInfo.Name.L != indexName {
continue
}
for i, idxCol := range idxInfo.Columns {
col := cols[idxCol.Offset]
schema.Append(&expression.Column{
FromID: 1,
ColName: idxCol.Name,
TblName: tn.Name,
DBName: tn.Schema,
RetType: &col.FieldType,
Position: i,
ID: col.ID})
}
schema.Append(&expression.Column{
FromID: 1,
ColName: model.NewCIStr("extra_handle"),
TblName: tn.Name,
DBName: tn.Schema,
RetType: types.NewFieldType(mysql.TypeLonglong),
Position: len(idxInfo.Columns),
ID: -1,
})
}
if schema.Len() == 0 {
return nil, errors.Errorf("index %s not found", indexName)
}
return schema, nil
}
// getColsInfo returns the info of index columns, normal columns and primary key.
func getColsInfo(tn *ast.TableName) (indicesInfo []*model.IndexInfo, colsInfo []*model.ColumnInfo, pkCol *model.ColumnInfo) {
tbl := tn.TableInfo
// idxNames contains all the normal columns that can be analyzed more effectively, because those columns occur as index
// columns or primary key columns with integer type.
var idxNames []string
if tbl.PKIsHandle {
for _, col := range tbl.Columns {
if mysql.HasPriKeyFlag(col.Flag) {
idxNames = append(idxNames, col.Name.L)
pkCol = col
}
}
}
for _, idx := range tn.TableInfo.Indices {
if idx.State == model.StatePublic {
indicesInfo = append(indicesInfo, idx)
if len(idx.Columns) == 1 {
idxNames = append(idxNames, idx.Columns[0].Name.L)
}
}
}
for _, col := range tbl.Columns {
isIndexCol := false
for _, idx := range idxNames {
if idx == col.Name.L {
isIndexCol = true
break
}
}
if !isIndexCol {
colsInfo = append(colsInfo, col)
}
}
return
}
func (b *planBuilder) buildAnalyzeTable(as *ast.AnalyzeTableStmt) Plan {
p := &Analyze{}
for _, tbl := range as.TableNames {
idxInfo, colInfo, pkInfo := getColsInfo(tbl)
for _, idx := range idxInfo {
p.IdxTasks = append(p.IdxTasks, AnalyzeIndexTask{TableInfo: tbl.TableInfo, IndexInfo: idx})
}
if len(colInfo) > 0 || pkInfo != nil {
p.ColTasks = append(p.ColTasks, AnalyzeColumnsTask{TableInfo: tbl.TableInfo, PKInfo: pkInfo, ColsInfo: colInfo})
}
}
return p
}
func (b *planBuilder) buildAnalyzeIndex(as *ast.AnalyzeTableStmt) Plan {
p := &Analyze{}
tblInfo := as.TableNames[0].TableInfo
for _, idxName := range as.IndexNames {
idx := findIndexByName(tblInfo.Indices, idxName)
if idx == nil || idx.State != model.StatePublic {
b.err = ErrAnalyzeMissIndex.GenByArgs(idxName.O, tblInfo.Name.O)
break
}
p.IdxTasks = append(p.IdxTasks, AnalyzeIndexTask{TableInfo: tblInfo, IndexInfo: idx})
}
return p
}
func (b *planBuilder) buildAnalyzeAllIndex(as *ast.AnalyzeTableStmt) Plan {
p := &Analyze{}
tblInfo := as.TableNames[0].TableInfo
for _, idx := range tblInfo.Indices {
if idx.State == model.StatePublic {
p.IdxTasks = append(p.IdxTasks, AnalyzeIndexTask{TableInfo: tblInfo, IndexInfo: idx})
}
}
return p
}
func (b *planBuilder) buildAnalyze(as *ast.AnalyzeTableStmt) Plan {
if as.IndexFlag {
if len(as.IndexNames) == 0 {
return b.buildAnalyzeAllIndex(as)
}
return b.buildAnalyzeIndex(as)
}
return b.buildAnalyzeTable(as)
}
func buildShowDDLFields() *expression.Schema {
schema := expression.NewSchema(make([]*expression.Column, 0, 4)...)
schema.Append(buildColumn("", "SCHEMA_VER", mysql.TypeLonglong, 4))
schema.Append(buildColumn("", "OWNER", mysql.TypeVarchar, 64))
schema.Append(buildColumn("", "JOB", mysql.TypeVarchar, 128))
schema.Append(buildColumn("", "SELF_ID", mysql.TypeVarchar, 64))
return schema
}
func buildRecoverIndexFields() *expression.Schema {
schema := expression.NewSchema(make([]*expression.Column, 0, 2)...)
schema.Append(buildColumn("", "ADDED_COUNT", mysql.TypeLonglong, 4))
schema.Append(buildColumn("", "SCAN_COUNT", mysql.TypeLonglong, 4))
return schema
}
func buildCleanupIndexFields() *expression.Schema {
schema := expression.NewSchema(make([]*expression.Column, 0, 1)...)
schema.Append(buildColumn("", "REMOVED_COUNT", mysql.TypeLonglong, 4))
return schema
}
func buildShowDDLJobsFields() *expression.Schema {
schema := expression.NewSchema(make([]*expression.Column, 0, 10)...)
schema.Append(buildColumn("", "JOB_ID", mysql.TypeLonglong, 4))
schema.Append(buildColumn("", "DB_NAME", mysql.TypeVarchar, 64))
schema.Append(buildColumn("", "TABLE_NAME", mysql.TypeVarchar, 64))
schema.Append(buildColumn("", "JOB_TYPE", mysql.TypeVarchar, 64))
schema.Append(buildColumn("", "SCHEMA_STATE", mysql.TypeVarchar, 64))
schema.Append(buildColumn("", "SCHEMA_ID", mysql.TypeLonglong, 4))
schema.Append(buildColumn("", "TABLE_ID", mysql.TypeLonglong, 4))
schema.Append(buildColumn("", "ROW_COUNT", mysql.TypeLonglong, 4))
schema.Append(buildColumn("", "START_TIME", mysql.TypeVarchar, 64))
schema.Append(buildColumn("", "STATE", mysql.TypeVarchar, 64))
return schema
}
func buildShowDDLJobQueriesFields() *expression.Schema {
schema := expression.NewSchema(make([]*expression.Column, 0, 1)...)
schema.Append(buildColumn("", "QUERY", mysql.TypeVarchar, 256))
return schema
}
func buildCancelDDLJobsFields() *expression.Schema {
schema := expression.NewSchema(make([]*expression.Column, 0, 2)...)
schema.Append(buildColumn("", "JOB_ID", mysql.TypeVarchar, 64))
schema.Append(buildColumn("", "RESULT", mysql.TypeVarchar, 128))
return schema
}
func buildColumn(tableName, name string, tp byte, size int) *expression.Column {
cs, cl := types.DefaultCharsetForType(tp)
flag := mysql.UnsignedFlag
if tp == mysql.TypeVarchar || tp == mysql.TypeBlob {
cs = mysql.DefaultCharset
cl = mysql.DefaultCollationName
flag = 0
}
fieldType := &types.FieldType{
Charset: cs,
Collate: cl,
Tp: tp,
Flen: size,
Flag: flag,
}
return &expression.Column{
ColName: model.NewCIStr(name),
TblName: model.NewCIStr(tableName),
DBName: model.NewCIStr(infoschema.Name),
RetType: fieldType,
}
}
// splitWhere split a where expression to a list of AND conditions.
func splitWhere(where ast.ExprNode) []ast.ExprNode {
var conditions []ast.ExprNode
switch x := where.(type) {
case nil:
case *ast.BinaryOperationExpr:
if x.Op == opcode.LogicAnd {
conditions = append(conditions, splitWhere(x.L)...)
conditions = append(conditions, splitWhere(x.R)...)
} else {
conditions = append(conditions, x)
}
case *ast.ParenthesesExpr:
conditions = append(conditions, splitWhere(x.Expr)...)
default:
conditions = append(conditions, where)
}
return conditions
}
func (b *planBuilder) buildShow(show *ast.ShowStmt) Plan {
p := Show{
Tp: show.Tp,
DBName: show.DBName,
Table: show.Table,
Column: show.Column,
Flag: show.Flag,
Full: show.Full,
User: show.User,
GlobalScope: show.GlobalScope,
}.init(b.ctx)
switch showTp := show.Tp; showTp {
case ast.ShowProcedureStatus:
p.SetSchema(buildShowProcedureSchema())
case ast.ShowTriggers:
p.SetSchema(buildShowTriggerSchema())
case ast.ShowEvents:
p.SetSchema(buildShowEventsSchema())
case ast.ShowWarnings:
p.SetSchema(buildShowWarningsSchema())
default:
switch showTp {
case ast.ShowTables, ast.ShowTableStatus:
if p.DBName == "" {
b.err = ErrNoDB
return nil
}
case ast.ShowCreateTable:
b.visitInfo = appendVisitInfo(b.visitInfo, mysql.AllPrivMask, show.Table.Schema.L, show.Table.Name.L, "")
}
p.SetSchema(buildShowSchema(show))
}
for i, col := range p.schema.Columns {
col.Position = i
}
mockTablePlan := LogicalTableDual{}.init(b.ctx)
mockTablePlan.SetSchema(p.schema)
if show.Pattern != nil {
show.Pattern.Expr = &ast.ColumnNameExpr{
Name: &ast.ColumnName{Name: p.Schema().Columns[0].ColName},
}
expr, _, err := b.rewrite(show.Pattern, mockTablePlan, nil, false)
if err != nil {
b.err = errors.Trace(err)
return nil
}
p.Conditions = append(p.Conditions, expr)
}
if show.Where != nil {
conds := splitWhere(show.Where)
for _, cond := range conds {
expr, _, err := b.rewrite(cond, mockTablePlan, nil, false)
if err != nil {
b.err = errors.Trace(err)
return nil
}
p.Conditions = append(p.Conditions, expr)
}
p.ResolveIndices()
}
return p
}
func (b *planBuilder) buildSimple(node ast.StmtNode) Plan {
p := &Simple{Statement: node}
switch raw := node.(type) {
case *ast.CreateUserStmt, *ast.DropUserStmt, *ast.AlterUserStmt:
b.visitInfo = appendVisitInfo(b.visitInfo, mysql.CreateUserPriv, "", "", "")
case *ast.GrantStmt:
b.visitInfo = collectVisitInfoFromGrantStmt(b.visitInfo, raw)
case *ast.SetPwdStmt, *ast.RevokeStmt, *ast.KillStmt:
b.visitInfo = appendVisitInfo(b.visitInfo, mysql.SuperPriv, "", "", "")
}
return p
}
func collectVisitInfoFromGrantStmt(vi []visitInfo, stmt *ast.GrantStmt) []visitInfo {
// To use GRANT, you must have the GRANT OPTION privilege,
// and you must have the privileges that you are granting.
dbName := stmt.Level.DBName
tableName := stmt.Level.TableName
vi = appendVisitInfo(vi, mysql.GrantPriv, dbName, tableName, "")
var allPrivs []mysql.PrivilegeType
for _, item := range stmt.Privs {
if item.Priv == mysql.AllPriv {
switch stmt.Level.Level {
case ast.GrantLevelGlobal:
allPrivs = mysql.AllGlobalPrivs
case ast.GrantLevelDB:
allPrivs = mysql.AllDBPrivs
case ast.GrantLevelTable:
allPrivs = mysql.AllTablePrivs
}
break
}
vi = appendVisitInfo(vi, item.Priv, dbName, tableName, "")
}
for _, priv := range allPrivs {
vi = appendVisitInfo(vi, priv, dbName, tableName, "")
}
return vi
}
func (b *planBuilder) getDefaultValue(col *table.Column) (*expression.Constant, error) {
value, err := table.GetColDefaultValue(b.ctx, col.ToInfo())
if err != nil {
return nil, errors.Trace(err)
}
return &expression.Constant{Value: value, RetType: &col.FieldType}, nil
}
func (b *planBuilder) findDefaultValue(cols []*table.Column, name *ast.ColumnName) (*expression.Constant, error) {
for _, col := range cols {
if col.Name.L == name.Name.L {
return b.getDefaultValue(col)
}
}
return nil, ErrUnknownColumn.GenByArgs(name.Name.O, "field_list")
}
// resolveGeneratedColumns resolves generated columns with their generation
// expressions respectively. onDups indicates which columns are in on-duplicate list.
func (b *planBuilder) resolveGeneratedColumns(columns []*table.Column, onDups map[string]struct{}, mockPlan LogicalPlan) (igc InsertGeneratedColumns) {
for _, column := range columns {
if !column.IsGenerated() {
continue
}
columnName := &ast.ColumnName{Name: column.Name}
columnName.SetText(column.Name.O)
colExpr, _, err := mockPlan.findColumn(columnName)
if err != nil {
b.err = errors.Trace(err)
return
}
expr, _, err := b.rewrite(column.GeneratedExpr, mockPlan, nil, true)
if err != nil {
b.err = errors.Trace(err)
return
}
expr = expression.BuildCastFunction(b.ctx, expr, colExpr.GetType())
igc.Columns = append(igc.Columns, columnName)
igc.Exprs = append(igc.Exprs, expr)
if onDups == nil {
continue
}
for dep := range column.Dependences {
if _, ok := onDups[dep]; ok {
assign := &expression.Assignment{Col: colExpr, Expr: expr.Clone()}
igc.OnDuplicates = append(igc.OnDuplicates, assign)
break
}
}
}
return
}
func (b *planBuilder) buildInsert(insert *ast.InsertStmt) Plan {
ts, ok := insert.Table.TableRefs.Left.(*ast.TableSource)
if !ok {
b.err = infoschema.ErrTableNotExists.GenByArgs()
return nil
}
tn, ok := ts.Source.(*ast.TableName)
if !ok {
b.err = infoschema.ErrTableNotExists.GenByArgs()
return nil
}
tableInfo := tn.TableInfo
// Build Schema with DBName otherwise ColumnRef with DBName cannot match any Column in Schema.
schema := expression.TableInfo2SchemaWithDBName(tn.Schema, tableInfo)
tableInPlan, ok := b.is.TableByID(tableInfo.ID)
if !ok {
b.err = errors.Errorf("Can't get table %s.", tableInfo.Name.O)
return nil
}
insertPlan := Insert{
Table: tableInPlan,
Columns: insert.Columns,
tableSchema: schema,
IsReplace: insert.IsReplace,
}.init(b.ctx)
b.visitInfo = append(b.visitInfo, visitInfo{
privilege: mysql.InsertPriv,
db: tn.DBInfo.Name.L,
table: tableInfo.Name.L,
})
columnByName := make(map[string]*table.Column, len(insertPlan.Table.Cols()))
for _, col := range insertPlan.Table.Cols() {
columnByName[col.Name.L] = col
}
// Check insert.Columns contains generated columns or not.
// It's for INSERT INTO t (...) VALUES (...)
if len(insert.Columns) > 0 {
for _, col := range insert.Columns {
if column, ok := columnByName[col.Name.L]; ok {
if column.IsGenerated() {
b.err = ErrBadGeneratedColumn.GenByArgs(col.Name.O, tableInfo.Name.O)
return nil
}
}
}
}
mockTablePlan := LogicalTableDual{}.init(b.ctx)
mockTablePlan.SetSchema(schema)
checkRefColumn := func(n ast.Node) ast.Node {
if insertPlan.NeedFillDefaultValue {
return n
}
switch n.(type) {
case *ast.ColumnName, *ast.ColumnNameExpr:
insertPlan.NeedFillDefaultValue = true
}
return n
}
cols := insertPlan.Table.Cols()
maxValuesItemLength := 0 // the max length of items in VALUES list.
for _, valuesItem := range insert.Lists {
exprList := make([]expression.Expression, 0, len(valuesItem))
for i, valueItem := range valuesItem {
var expr expression.Expression
var err error
if dft, ok := valueItem.(*ast.DefaultExpr); ok {
if dft.Name != nil {
expr, err = b.findDefaultValue(cols, dft.Name)
} else {
expr, err = b.getDefaultValue(cols[i])
}
} else if val, ok := valueItem.(*ast.ValueExpr); ok {
expr = &expression.Constant{
Value: val.Datum,
RetType: &val.Type,
}
} else {
expr, _, err = b.rewriteWithPreprocess(valueItem, mockTablePlan, nil, true, checkRefColumn)
}
if err != nil {
b.err = errors.Trace(err)
}
exprList = append(exprList, expr)
}
if len(valuesItem) > maxValuesItemLength {
maxValuesItemLength = len(valuesItem)
}
insertPlan.Lists = append(insertPlan.Lists, exprList)
}
// It's for INSERT INTO t VALUES (...)
if len(insert.Columns) == 0 {
// The length of VALUES list maybe exceed table width,
// we ignore this here but do checking in executor.
var effectiveValuesLen int
if maxValuesItemLength <= len(tableInfo.Columns) {
effectiveValuesLen = maxValuesItemLength
} else {
effectiveValuesLen = len(tableInfo.Columns)
}
for i := 0; i < effectiveValuesLen; i++ {
col := tableInfo.Columns[i]
if col.IsGenerated() {
b.err = ErrBadGeneratedColumn.GenByArgs(col.Name.O, tableInfo.Name.O)
return nil
}
}
}
for _, assign := range insert.Setlist {
col, err := schema.FindColumn(assign.Column)
if err != nil {
b.err = errors.Trace(err)
return nil
}
if col == nil {
b.err = errors.Errorf("Can't find column %s", assign.Column)
return nil
}
// Check set list contains generated column or not.
if columnByName[assign.Column.Name.L].IsGenerated() {
b.err = ErrBadGeneratedColumn.GenByArgs(assign.Column.Name.O, tableInfo.Name.O)
return nil
}
expr, _, err := b.rewriteWithPreprocess(assign.Expr, mockTablePlan, nil, true, checkRefColumn)
if err != nil {
b.err = errors.Trace(err)
return nil
}
insertPlan.Setlist = append(insertPlan.Setlist, &expression.Assignment{
Col: col,
Expr: expr,
})
}
onDupCols := make(map[string]struct{}, len(insert.OnDuplicate))
for _, assign := range insert.OnDuplicate {
col, _, err := mockTablePlan.findColumn(assign.Column)
if err != nil {
b.err = errors.Trace(err)
return nil
}
column := columnByName[assign.Column.Name.L]
// Check "on duplicate set list" contains generated column or not.
if column.IsGenerated() {
b.err = ErrBadGeneratedColumn.GenByArgs(assign.Column.Name.O, tableInfo.Name.O)
return nil
}
expr, _, err := b.rewrite(assign.Expr, mockTablePlan, nil, true)
if err != nil {
b.err = errors.Trace(err)
return nil
}
insertPlan.OnDuplicate = append(insertPlan.OnDuplicate, &expression.Assignment{
Col: col,
Expr: expr,
})
onDupCols[column.Name.L] = struct{}{}
}
if insert.Select != nil {
selectPlan := b.build(insert.Select)
if b.err != nil {
return nil
}
// If the schema of selectPlan contains any generated column, raises error.
var effectiveSelectLen int
if selectPlan.Schema().Len() <= len(tableInfo.Columns) {
effectiveSelectLen = selectPlan.Schema().Len()
} else {
effectiveSelectLen = len(tableInfo.Columns)
}
for i := 0; i < effectiveSelectLen; i++ {
col := tableInfo.Columns[i]
if col.IsGenerated() {
b.err = ErrBadGeneratedColumn.GenByArgs(col.Name.O, tableInfo.Name.O)
return nil
}
}
insertPlan.SelectPlan, b.err = doOptimize(b.optFlag, selectPlan.(LogicalPlan))
if b.err != nil {
return nil
}
}
// Calculate generated columns.
insertPlan.GenCols = b.resolveGeneratedColumns(insertPlan.Table.Cols(), onDupCols, mockTablePlan)
if b.err != nil {
return nil
}
insertPlan.ResolveIndices()
return insertPlan
}
func (b *planBuilder) buildLoadData(ld *ast.LoadDataStmt) Plan {
p := &LoadData{
IsLocal: ld.IsLocal,
Path: ld.Path,
Table: ld.Table,
Columns: ld.Columns,
FieldsInfo: ld.FieldsInfo,
LinesInfo: ld.LinesInfo,
}
tableInfo := p.Table.TableInfo
tableInPlan, ok := b.is.TableByID(tableInfo.ID)
if !ok {
db := b.ctx.GetSessionVars().CurrentDB
b.err = infoschema.ErrTableNotExists.GenByArgs(db, tableInfo.Name.O)
return nil
}
schema := expression.TableInfo2Schema(tableInfo)
mockTablePlan := LogicalTableDual{}.init(b.ctx)
mockTablePlan.SetSchema(schema)
p.GenCols = b.resolveGeneratedColumns(tableInPlan.Cols(), nil, mockTablePlan)
return p
}
func (b *planBuilder) buildLoadStats(ld *ast.LoadStatsStmt) Plan {
p := &LoadStats{Path: ld.Path}
return p
}
func (b *planBuilder) buildDDL(node ast.DDLNode) Plan {
switch v := node.(type) {
case *ast.AlterTableStmt:
b.visitInfo = append(b.visitInfo, visitInfo{
privilege: mysql.AlterPriv,
db: v.Table.Schema.L,
table: v.Table.Name.L,
})
case *ast.CreateDatabaseStmt:
b.visitInfo = append(b.visitInfo, visitInfo{
privilege: mysql.CreatePriv,
db: v.Name,
})
case *ast.CreateIndexStmt:
b.visitInfo = append(b.visitInfo, visitInfo{
privilege: mysql.IndexPriv,
db: v.Table.Schema.L,
table: v.Table.Name.L,
})
case *ast.CreateTableStmt:
b.visitInfo = append(b.visitInfo, visitInfo{
privilege: mysql.CreatePriv,
db: v.Table.Schema.L,
table: v.Table.Name.L,
})
if v.ReferTable != nil {
b.visitInfo = append(b.visitInfo, visitInfo{
privilege: mysql.SelectPriv,
db: v.ReferTable.Schema.L,
table: v.ReferTable.Name.L,
})
}
case *ast.DropDatabaseStmt:
b.visitInfo = append(b.visitInfo, visitInfo{
privilege: mysql.DropPriv,
db: v.Name,
})
case *ast.DropIndexStmt:
b.visitInfo = append(b.visitInfo, visitInfo{
privilege: mysql.IndexPriv,
db: v.Table.Schema.L,
table: v.Table.Name.L,
})
case *ast.DropTableStmt:
for _, tableVal := range v.Tables {
b.visitInfo = append(b.visitInfo, visitInfo{
privilege: mysql.DropPriv,
db: tableVal.Schema.L,
table: tableVal.Name.L,
})
}
case *ast.TruncateTableStmt:
b.visitInfo = append(b.visitInfo, visitInfo{
privilege: mysql.DeletePriv,
db: v.Table.Schema.L,
table: v.Table.Name.L,
})
case *ast.RenameTableStmt:
b.visitInfo = append(b.visitInfo, visitInfo{
privilege: mysql.AlterPriv,
db: v.OldTable.Schema.L,
table: v.OldTable.Name.L,
})
b.visitInfo = append(b.visitInfo, visitInfo{
privilege: mysql.AlterPriv,
db: v.NewTable.Schema.L,
table: v.NewTable.Name.L,
})
}
p := &DDL{Statement: node}
return p
}
func (b *planBuilder) buildExplain(explain *ast.ExplainStmt) Plan {
if show, ok := explain.Stmt.(*ast.ShowStmt); ok {
return b.buildShow(show)
}
targetPlan, err := Optimize(b.ctx, explain.Stmt, b.is)
if err != nil {
b.err = errors.Trace(err)
return nil
}
pp, ok := targetPlan.(PhysicalPlan)
if !ok {
switch x := targetPlan.(type) {
case *Delete:
pp = x.SelectPlan
case *Update:
pp = x.SelectPlan
case *Insert:
if x.SelectPlan != nil {
pp = x.SelectPlan
}
}
if pp == nil {
b.err = ErrUnsupportedType.GenByArgs(targetPlan)
return nil
}
}
p := &Explain{StmtPlan: pp}
switch strings.ToLower(explain.Format) {
case ast.ExplainFormatROW:
retFields := []string{"id", "parents", "children", "task", "operator info", "count"}
schema := expression.NewSchema(make([]*expression.Column, 0, len(retFields))...)
for _, fieldName := range retFields {
schema.Append(buildColumn("", fieldName, mysql.TypeString, mysql.MaxBlobWidth))
}
p.SetSchema(schema)
p.explainedPlans = map[int]bool{}
p.prepareRootTaskInfo(p.StmtPlan.(PhysicalPlan), "")
case ast.ExplainFormatDOT:
retFields := []string{"dot contents"}
schema := expression.NewSchema(make([]*expression.Column, 0, len(retFields))...)
for _, fieldName := range retFields {
schema.Append(buildColumn("", fieldName, mysql.TypeString, mysql.MaxBlobWidth))
}
p.SetSchema(schema)
p.prepareDotInfo(p.StmtPlan.(PhysicalPlan))
default:
b.err = errors.Errorf("explain format '%s' is not supported now", explain.Format)
}
return p
}
func buildShowProcedureSchema() *expression.Schema {
tblName := "ROUTINES"
schema := expression.NewSchema(make([]*expression.Column, 0, 11)...)
schema.Append(buildColumn(tblName, "Db", mysql.TypeVarchar, 128))
schema.Append(buildColumn(tblName, "Name", mysql.TypeVarchar, 128))
schema.Append(buildColumn(tblName, "Type", mysql.TypeVarchar, 128))
schema.Append(buildColumn(tblName, "Definer", mysql.TypeVarchar, 128))
schema.Append(buildColumn(tblName, "Modified", mysql.TypeDatetime, 19))
schema.Append(buildColumn(tblName, "Created", mysql.TypeDatetime, 19))
schema.Append(buildColumn(tblName, "Security_type", mysql.TypeVarchar, 128))
schema.Append(buildColumn(tblName, "Comment", mysql.TypeBlob, 196605))
schema.Append(buildColumn(tblName, "character_set_client", mysql.TypeVarchar, 32))
schema.Append(buildColumn(tblName, "collation_connection", mysql.TypeVarchar, 32))
schema.Append(buildColumn(tblName, "Database Collation", mysql.TypeVarchar, 32))
return schema
}
func buildShowTriggerSchema() *expression.Schema {
tblName := "TRIGGERS"
schema := expression.NewSchema(make([]*expression.Column, 0, 11)...)
schema.Append(buildColumn(tblName, "Trigger", mysql.TypeVarchar, 128))
schema.Append(buildColumn(tblName, "Event", mysql.TypeVarchar, 128))
schema.Append(buildColumn(tblName, "Table", mysql.TypeVarchar, 128))
schema.Append(buildColumn(tblName, "Statement", mysql.TypeBlob, 196605))
schema.Append(buildColumn(tblName, "Timing", mysql.TypeVarchar, 128))
schema.Append(buildColumn(tblName, "Created", mysql.TypeDatetime, 19))
schema.Append(buildColumn(tblName, "sql_mode", mysql.TypeBlob, 8192))
schema.Append(buildColumn(tblName, "Definer", mysql.TypeVarchar, 128))
schema.Append(buildColumn(tblName, "character_set_client", mysql.TypeVarchar, 32))
schema.Append(buildColumn(tblName, "collation_connection", mysql.TypeVarchar, 32))
schema.Append(buildColumn(tblName, "Database Collation", mysql.TypeVarchar, 32))
return schema
}
func buildShowEventsSchema() *expression.Schema {
tblName := "EVENTS"
schema := expression.NewSchema(make([]*expression.Column, 0, 15)...)
schema.Append(buildColumn(tblName, "Db", mysql.TypeVarchar, 128))
schema.Append(buildColumn(tblName, "Name", mysql.TypeVarchar, 128))
schema.Append(buildColumn(tblName, "Time zone", mysql.TypeVarchar, 32))
schema.Append(buildColumn(tblName, "Definer", mysql.TypeVarchar, 128))
schema.Append(buildColumn(tblName, "Type", mysql.TypeVarchar, 128))
schema.Append(buildColumn(tblName, "Execute At", mysql.TypeDatetime, 19))
schema.Append(buildColumn(tblName, "Interval Value", mysql.TypeVarchar, 128))
schema.Append(buildColumn(tblName, "Interval Field", mysql.TypeVarchar, 128))
schema.Append(buildColumn(tblName, "Starts", mysql.TypeDatetime, 19))
schema.Append(buildColumn(tblName, "Ends", mysql.TypeDatetime, 19))
schema.Append(buildColumn(tblName, "Status", mysql.TypeVarchar, 32))
schema.Append(buildColumn(tblName, "Originator", mysql.TypeInt24, 4))
schema.Append(buildColumn(tblName, "character_set_client", mysql.TypeVarchar, 32))
schema.Append(buildColumn(tblName, "collation_connection", mysql.TypeVarchar, 32))
schema.Append(buildColumn(tblName, "Database Collation", mysql.TypeVarchar, 32))
return schema
}
func buildShowWarningsSchema() *expression.Schema {
tblName := "WARNINGS"
schema := expression.NewSchema(make([]*expression.Column, 0, 3)...)
schema.Append(buildColumn(tblName, "Level", mysql.TypeVarchar, 64))
schema.Append(buildColumn(tblName, "Code", mysql.TypeLong, 19))
schema.Append(buildColumn(tblName, "Message", mysql.TypeVarchar, 64))
return schema
}
// buildShowSchema builds column info for ShowStmt including column name and type.
func buildShowSchema(s *ast.ShowStmt) (schema *expression.Schema) {
var names []string
var ftypes []byte
switch s.Tp {
case ast.ShowEngines:
names = []string{"Engine", "Support", "Comment", "Transactions", "XA", "Savepoints"}
case ast.ShowDatabases:
names = []string{"Database"}
case ast.ShowTables:
names = []string{fmt.Sprintf("Tables_in_%s", s.DBName)}
if s.Full {
names = append(names, "Table_type")
}
case ast.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"}
ftypes = []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 ast.ShowColumns:
names = table.ColDescFieldNames(s.Full)
case ast.ShowWarnings:
names = []string{"Level", "Code", "Message"}
ftypes = []byte{mysql.TypeVarchar, mysql.TypeLong, mysql.TypeVarchar}
case ast.ShowCharset:
names = []string{"Charset", "Description", "Default collation", "Maxlen"}
ftypes = []byte{mysql.TypeVarchar, mysql.TypeVarchar, mysql.TypeVarchar, mysql.TypeLonglong}
case ast.ShowVariables, ast.ShowStatus:
names = []string{"Variable_name", "Value"}
case ast.ShowCollation:
names = []string{"Collation", "Charset", "Id", "Default", "Compiled", "Sortlen"}
ftypes = []byte{mysql.TypeVarchar, mysql.TypeVarchar, mysql.TypeLonglong,
mysql.TypeVarchar, mysql.TypeVarchar, mysql.TypeLonglong}
case ast.ShowCreateTable:
names = []string{"Table", "Create Table"}
case ast.ShowCreateDatabase:
names = []string{"Database", "Create Database"}
case ast.ShowGrants:
names = []string{fmt.Sprintf("Grants for %s", s.User)}
case ast.ShowIndex:
names = []string{"Table", "Non_unique", "Key_name", "Seq_in_index",
"Column_name", "Collation", "Cardinality", "Sub_part", "Packed",
"Null", "Index_type", "Comment", "Index_comment"}
ftypes = []byte{mysql.TypeVarchar, mysql.TypeLonglong, mysql.TypeVarchar, mysql.TypeLonglong,
mysql.TypeVarchar, mysql.TypeVarchar, mysql.TypeLonglong, mysql.TypeLonglong,
mysql.TypeVarchar, mysql.TypeVarchar, mysql.TypeVarchar, mysql.TypeVarchar, mysql.TypeVarchar}
case ast.ShowPlugins:
names = []string{"Name", "Status", "Type", "Library", "License"}
ftypes = []byte{
mysql.TypeVarchar, mysql.TypeVarchar, mysql.TypeVarchar, mysql.TypeVarchar, mysql.TypeVarchar,
}
case ast.ShowProcessList:
names = []string{"Id", "User", "Host", "db", "Command", "Time", "State", "Info", "Mem"}
ftypes = []byte{mysql.TypeLonglong, mysql.TypeVarchar, mysql.TypeVarchar,
mysql.TypeVarchar, mysql.TypeVarchar, mysql.TypeLong, mysql.TypeVarchar, mysql.TypeString, mysql.TypeLonglong}
case ast.ShowStatsMeta:
names = []string{"Db_name", "Table_name", "Update_time", "Modify_count", "Row_count"}
ftypes = []byte{mysql.TypeVarchar, mysql.TypeVarchar, mysql.TypeDatetime, mysql.TypeLonglong, mysql.TypeLonglong}
case ast.ShowStatsHistograms:
names = []string{"Db_name", "Table_name", "Column_name", "Is_index", "Update_time", "Distinct_count", "Null_count", "Avg_col_size"}
ftypes = []byte{mysql.TypeVarchar, mysql.TypeVarchar, mysql.TypeVarchar, mysql.TypeTiny, mysql.TypeDatetime,
mysql.TypeLonglong, mysql.TypeLonglong, mysql.TypeDouble}
case ast.ShowStatsBuckets:
names = []string{"Db_name", "Table_name", "Column_name", "Is_index", "Bucket_id", "Count",
"Repeats", "Lower_Bound", "Upper_Bound"}
ftypes = []byte{mysql.TypeVarchar, mysql.TypeVarchar, mysql.TypeVarchar, mysql.TypeTiny, mysql.TypeLonglong,
mysql.TypeLonglong, mysql.TypeLonglong, mysql.TypeVarchar, mysql.TypeVarchar}
case ast.ShowStatsHealthy:
names = []string{"Db_name", "Table_name", "Healthy"}
ftypes = []byte{mysql.TypeVarchar, mysql.TypeVarchar, mysql.TypeLonglong}
case ast.ShowProfiles: // ShowProfiles is deprecated.
names = []string{"Query_ID", "Duration", "Query"}
ftypes = []byte{mysql.TypeLong, mysql.TypeDouble, mysql.TypeVarchar}
}
schema = expression.NewSchema(make([]*expression.Column, 0, len(names))...)
for i := range names {
col := &expression.Column{
ColName: model.NewCIStr(names[i]),
}
// User varchar as the default return column type.
tp := mysql.TypeVarchar
if len(ftypes) != 0 && ftypes[i] != mysql.TypeUnspecified {
tp = ftypes[i]
}
fieldType := types.NewFieldType(tp)
fieldType.Flen, fieldType.Decimal = mysql.GetDefaultFieldLengthAndDecimal(tp)
fieldType.Charset, fieldType.Collate = types.DefaultCharsetForType(tp)
col.RetType = fieldType
schema.Append(col)
}
return schema
}
func buildChecksumTableSchema() *expression.Schema {
schema := expression.NewSchema(make([]*expression.Column, 0, 5)...)
schema.Append(buildColumn("", "Db_name", mysql.TypeVarchar, 128))
schema.Append(buildColumn("", "Table_name", mysql.TypeVarchar, 128))
schema.Append(buildColumn("", "Checksum_crc64_xor", mysql.TypeLonglong, 22))
schema.Append(buildColumn("", "Total_kvs", mysql.TypeLonglong, 22))
schema.Append(buildColumn("", "Total_bytes", mysql.TypeLonglong, 22))
return schema
}