github.com/dolthub/go-mysql-server@v0.18.0/sql/plan/alter_index.go (about)

     1  // Copyright 2020-2021 Dolthub, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package plan
    16  
    17  import (
    18  	"fmt"
    19  	"strings"
    20  
    21  	"gopkg.in/src-d/go-errors.v1"
    22  
    23  	"github.com/dolthub/go-mysql-server/sql"
    24  	"github.com/dolthub/go-mysql-server/sql/expression"
    25  	"github.com/dolthub/go-mysql-server/sql/types"
    26  )
    27  
    28  var (
    29  	// ErrIndexActionNotImplemented is returned when the action has not been implemented
    30  	ErrIndexActionNotImplemented = errors.NewKind("alter table index action is not implemented: %v")
    31  	// ErrCreateIndexMissingColumns is returned when a CREATE INDEX statement does not provide any columns
    32  	ErrCreateIndexMissingColumns = errors.NewKind("cannot create an index without columns")
    33  	// ErrCreateIndexNonExistentColumn is returned when a key is provided in the index that isn't in the table
    34  	ErrCreateIndexNonExistentColumn = errors.NewKind("column `%v` does not exist in the table")
    35  	// ErrCreateIndexDuplicateColumn is returned when a CREATE INDEX statement has the same column multiple times
    36  	ErrCreateIndexDuplicateColumn = errors.NewKind("cannot have duplicates of columns in an index: `%v`")
    37  )
    38  
    39  type IndexAction byte
    40  
    41  const (
    42  	IndexAction_Create IndexAction = iota
    43  	IndexAction_Drop
    44  	IndexAction_Rename
    45  	IndexAction_DisableEnableKeys
    46  )
    47  
    48  type AlterIndex struct {
    49  	// Action states whether it's a CREATE, DROP, or RENAME
    50  	Action IndexAction
    51  	// ddlNode references to the database that is being operated on
    52  	ddlNode
    53  	// Table is the table that is being referenced
    54  	Table sql.Node
    55  	// IndexName is the index name, and in the case of a RENAME it represents the new name
    56  	IndexName string
    57  	// PreviousIndexName states the old name when renaming an index
    58  	PreviousIndexName string
    59  	// Using states whether you're using BTREE, HASH, or none
    60  	Using sql.IndexUsing
    61  	// Constraint specifies whether this is UNIQUE, FULLTEXT, SPATIAL, or none
    62  	Constraint sql.IndexConstraint
    63  	// Columns contains the column names (and possibly lengths) when creating an index
    64  	Columns []sql.IndexColumn
    65  	// Comment is the comment that was left at index creation, if any
    66  	Comment string
    67  	// DisableKeys determines whether to DISABLE KEYS if true or ENABLE KEYS if false
    68  	DisableKeys bool
    69  	// TargetSchema Analyzer state.
    70  	targetSchema sql.Schema
    71  }
    72  
    73  var _ sql.SchemaTarget = (*AlterIndex)(nil)
    74  var _ sql.Expressioner = (*AlterIndex)(nil)
    75  var _ sql.Node = (*AlterIndex)(nil)
    76  var _ sql.CollationCoercible = (*AlterIndex)(nil)
    77  
    78  func NewAlterCreateIndex(db sql.Database, table sql.Node, indexName string, using sql.IndexUsing, constraint sql.IndexConstraint, columns []sql.IndexColumn, comment string) *AlterIndex {
    79  	return &AlterIndex{
    80  		Action:     IndexAction_Create,
    81  		ddlNode:    ddlNode{Db: db},
    82  		Table:      table,
    83  		IndexName:  indexName,
    84  		Using:      using,
    85  		Constraint: constraint,
    86  		Columns:    columns,
    87  		Comment:    comment,
    88  	}
    89  }
    90  
    91  func NewAlterDropIndex(db sql.Database, table sql.Node, indexName string) *AlterIndex {
    92  	return &AlterIndex{
    93  		Action:    IndexAction_Drop,
    94  		ddlNode:   ddlNode{Db: db},
    95  		Table:     table,
    96  		IndexName: indexName,
    97  	}
    98  }
    99  
   100  func NewAlterRenameIndex(db sql.Database, table sql.Node, fromIndexName, toIndexName string) *AlterIndex {
   101  	return &AlterIndex{
   102  		Action:            IndexAction_Rename,
   103  		ddlNode:           ddlNode{Db: db},
   104  		Table:             table,
   105  		IndexName:         toIndexName,
   106  		PreviousIndexName: fromIndexName,
   107  	}
   108  }
   109  
   110  func NewAlterDisableEnableKeys(db sql.Database, table sql.Node, disableKeys bool) *AlterIndex {
   111  	return &AlterIndex{
   112  		Action:      IndexAction_DisableEnableKeys,
   113  		ddlNode:     ddlNode{Db: db},
   114  		Table:       table,
   115  		DisableKeys: disableKeys,
   116  	}
   117  }
   118  
   119  // Schema implements the Node interface.
   120  func (p *AlterIndex) Schema() sql.Schema {
   121  	return types.OkResultSchema
   122  }
   123  
   124  // WithChildren implements the Node interface. For AlterIndex, the only appropriate input is
   125  // a single child - The Table.
   126  func (p AlterIndex) WithChildren(children ...sql.Node) (sql.Node, error) {
   127  	if len(children) != 1 {
   128  		return nil, sql.ErrInvalidChildrenNumber.New(p, len(children), 1)
   129  	}
   130  
   131  	switch p.Action {
   132  	case IndexAction_Create, IndexAction_Drop, IndexAction_Rename, IndexAction_DisableEnableKeys:
   133  		p.Table = children[0]
   134  		return &p, nil
   135  	default:
   136  		return nil, ErrIndexActionNotImplemented.New(p.Action)
   137  	}
   138  }
   139  
   140  func (p AlterIndex) WithTargetSchema(schema sql.Schema) (sql.Node, error) {
   141  	p.targetSchema = schema
   142  	return &p, nil
   143  }
   144  
   145  func (p *AlterIndex) TargetSchema() sql.Schema {
   146  	return p.targetSchema
   147  }
   148  
   149  // Expressions on the AlterIndex object are specifically column default expresions, Nothing else.
   150  func (p *AlterIndex) Expressions() []sql.Expression {
   151  	newExprs := make([]sql.Expression, len(p.TargetSchema()))
   152  	for i, col := range p.TargetSchema() {
   153  		newExprs[i] = expression.WrapExpression(col.Default)
   154  	}
   155  
   156  	return newExprs
   157  }
   158  
   159  // WithExpressions implements the Node Interface. For AlterIndex, expressions represent  column defaults on the
   160  // targetSchema instance - required to be the same number of columns on the target schema.
   161  func (p AlterIndex) WithExpressions(expressions ...sql.Expression) (sql.Node, error) {
   162  	columns := p.TargetSchema().Copy()
   163  
   164  	if len(columns) != len(expressions) {
   165  		return nil, fmt.Errorf("invariant failure: column count does not match expression count")
   166  	}
   167  
   168  	for i, expr := range expressions {
   169  		wrapper, ok := expr.(*expression.Wrapper)
   170  		if !ok {
   171  			return nil, fmt.Errorf("*expression.Wrapper cast failure unexpected: %v", expr)
   172  		}
   173  
   174  		wrapped := wrapper.Unwrap()
   175  		if wrapped == nil {
   176  			continue // No default for this column
   177  		}
   178  
   179  		newColDef, ok := wrapped.(*sql.ColumnDefaultValue)
   180  		if !ok {
   181  			return nil, fmt.Errorf("*sql.ColumnDefaultValue cast failure unexptected: %v", wrapped)
   182  		}
   183  
   184  		columns[i].Default = newColDef
   185  	}
   186  
   187  	newIdx, err := p.WithTargetSchema(columns)
   188  	if err != nil {
   189  		return nil, err
   190  	}
   191  	return newIdx, nil
   192  }
   193  
   194  // CheckPrivileges implements the interface sql.Node.
   195  func (p *AlterIndex) CheckPrivileges(ctx *sql.Context, opChecker sql.PrivilegedOperationChecker) bool {
   196  	subject := sql.PrivilegeCheckSubject{
   197  		Database: CheckPrivilegeNameForDatabase(p.ddlNode.Database()),
   198  		Table:    getTableName(p.Table),
   199  	}
   200  	return opChecker.UserHasPrivileges(ctx,
   201  		sql.NewPrivilegedOperation(subject, sql.PrivilegeType_Index))
   202  }
   203  
   204  // CollationCoercibility implements the interface sql.CollationCoercible.
   205  func (*AlterIndex) CollationCoercibility(ctx *sql.Context) (collation sql.CollationID, coercibility byte) {
   206  	return sql.Collation_binary, 7
   207  }
   208  
   209  // WithDatabase implements the sql.Databaser interface.
   210  func (p *AlterIndex) WithDatabase(database sql.Database) (sql.Node, error) {
   211  	np := *p
   212  	np.Db = database
   213  	return &np, nil
   214  }
   215  
   216  func (p AlterIndex) String() string {
   217  	pr := sql.NewTreePrinter()
   218  	switch p.Action {
   219  	case IndexAction_Create:
   220  		_ = pr.WriteNode("CreateIndex(%s)", p.IndexName)
   221  		children := []string{fmt.Sprintf("Table(%s)", p.Table.String())}
   222  		switch p.Constraint {
   223  		case sql.IndexConstraint_Unique:
   224  			children = append(children, "Constraint(UNIQUE)")
   225  		case sql.IndexConstraint_Spatial:
   226  			children = append(children, "Constraint(SPATIAL)")
   227  		case sql.IndexConstraint_Fulltext:
   228  			children = append(children, "Constraint(FULLTEXT)")
   229  		}
   230  		switch p.Using {
   231  		case sql.IndexUsing_BTree, sql.IndexUsing_Default:
   232  			children = append(children, "Using(BTREE)")
   233  		case sql.IndexUsing_Hash:
   234  			children = append(children, "Using(HASH)")
   235  		}
   236  		cols := make([]string, len(p.Columns))
   237  		for i, col := range p.Columns {
   238  			if col.Length == 0 {
   239  				cols[i] = col.Name
   240  			} else {
   241  				cols[i] = fmt.Sprintf("%s(%v)", col.Name, col.Length)
   242  			}
   243  		}
   244  		children = append(children, fmt.Sprintf("Columns(%s)", strings.Join(cols, ", ")))
   245  		children = append(children, fmt.Sprintf("Comment(%s)", p.Comment))
   246  		_ = pr.WriteChildren(children...)
   247  	case IndexAction_Drop:
   248  		_ = pr.WriteNode("DropIndex(%s)", p.IndexName)
   249  		_ = pr.WriteChildren(fmt.Sprintf("Table(%s)", p.Table.String()))
   250  	case IndexAction_Rename:
   251  		_ = pr.WriteNode("RenameIndex")
   252  		_ = pr.WriteChildren(
   253  			fmt.Sprintf("Table(%s)", p.Table.String()),
   254  			fmt.Sprintf("FromIndex(%s)", p.PreviousIndexName),
   255  			fmt.Sprintf("ToIndex(%s)", p.IndexName),
   256  		)
   257  	default:
   258  		_ = pr.WriteNode("Unknown_Index_Action(%v)", p.Action)
   259  	}
   260  	return pr.String()
   261  }
   262  
   263  func (p *AlterIndex) Resolved() bool {
   264  	return p.Table.Resolved() && p.ddlNode.Resolved() && p.targetSchema.Resolved()
   265  }
   266  
   267  func (p *AlterIndex) IsReadOnly() bool {
   268  	return false
   269  }
   270  
   271  // Children implements the sql.Node interface.
   272  func (p *AlterIndex) Children() []sql.Node {
   273  	return []sql.Node{p.Table}
   274  }
   275  
   276  // ColumnNames returns each column's name without the length property.
   277  func (p *AlterIndex) ColumnNames() []string {
   278  	colNames := make([]string, len(p.Columns))
   279  	for i, col := range p.Columns {
   280  		colNames[i] = col.Name
   281  	}
   282  	return colNames
   283  }