github.com/dolthub/go-mysql-server@v0.18.0/sql/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 sql
    16  
    17  import (
    18  	"fmt"
    19  	"strings"
    20  )
    21  
    22  type IndexDef struct {
    23  	Name       string
    24  	Columns    []IndexColumn
    25  	Constraint IndexConstraint
    26  	Storage    IndexUsing
    27  	Comment    string
    28  }
    29  
    30  // IndexColumn is the column by which to add to an index.
    31  type IndexColumn struct {
    32  	Name string
    33  	// Length represents the index prefix length. If zero, then no length was specified.
    34  	Length int64
    35  }
    36  
    37  // IndexConstraint represents any constraints that should be applied to the index.
    38  type IndexConstraint byte
    39  
    40  const (
    41  	IndexConstraint_None IndexConstraint = iota
    42  	IndexConstraint_Unique
    43  	IndexConstraint_Fulltext
    44  	IndexConstraint_Spatial
    45  	IndexConstraint_Primary
    46  )
    47  
    48  // IndexUsing is the desired storage type.
    49  type IndexUsing byte
    50  
    51  const (
    52  	IndexUsing_Default IndexUsing = iota
    53  	IndexUsing_BTree
    54  	IndexUsing_Hash
    55  )
    56  
    57  // Index is the representation of an index, and also creates an IndexLookup when given a collection of ranges.
    58  type Index interface {
    59  	// ID returns the identifier of the index.
    60  	ID() string
    61  	// Database returns the database name this index belongs to.
    62  	Database() string
    63  	// Table returns the table name this index belongs to.
    64  	Table() string
    65  	// Expressions returns the indexed expressions. If the result is more than
    66  	// one expression, it means the index has multiple columns indexed. If it's
    67  	// just one, it means it may be an expression or a column.
    68  	Expressions() []string
    69  	// IsUnique returns whether this index is unique
    70  	IsUnique() bool
    71  	// IsSpatial returns whether this index is a spatial index
    72  	IsSpatial() bool
    73  	// IsFullText returns whether this index is a Full-Text index
    74  	IsFullText() bool
    75  	// Comment returns the comment for this index
    76  	Comment() string
    77  	// IndexType returns the type of this index, e.g. BTREE
    78  	IndexType() string
    79  	// IsGenerated returns whether this index was generated. Generated indexes
    80  	// are used for index access, but are not displayed (such as with SHOW INDEXES).
    81  	IsGenerated() bool
    82  	// ColumnExpressionTypes returns each expression and its associated Type.
    83  	// Each expression string should exactly match the string returned from
    84  	// Index.Expressions().
    85  	ColumnExpressionTypes() []ColumnExpressionType
    86  	// CanSupport returns whether this index supports lookups on the given
    87  	// range filters.
    88  	CanSupport(...Range) bool
    89  	// PrefixLengths returns the prefix lengths for each column in this index
    90  	PrefixLengths() []uint16
    91  }
    92  
    93  // ExtendedIndex is an extension of Index, that allows access to appended primary keys. MySQL internally represents an
    94  // index as the collection of all explicitly referenced columns, while appending any unreferenced primary keys to the
    95  // end (in order of their declaration). For full MySQL compatibility, integrators are encouraged to mimic this, however
    96  // not all implementations may define their indexes (on tables with primary keys) in this way, therefore this interface
    97  // is optional.
    98  type ExtendedIndex interface {
    99  	Index
   100  	// ExtendedExpressions returns the same result as Expressions, but appends any primary keys that are implicitly in
   101  	// the index. The appended primary keys are in declaration order.
   102  	ExtendedExpressions() []string
   103  	// ExtendedColumnExpressionTypes returns the same result as ColumnExpressionTypes, but appends the type of any
   104  	// primary keys that are implicitly in the index. The appended primary keys are in declaration order.
   105  	ExtendedColumnExpressionTypes() []ColumnExpressionType
   106  }
   107  
   108  // IndexLookup is the implementation-specific definition of an index lookup. The IndexLookup must contain all necessary
   109  // information to retrieve exactly the rows in the table as specified by the ranges given to their parent index.
   110  // Implementors are responsible for all semantics of correctly returning rows that match an index lookup.
   111  type IndexLookup struct {
   112  	Index  Index
   113  	Ranges RangeCollection
   114  	// IsPointLookup is true if the lookup will return one or zero
   115  	// values; the range is null safe, the index is unique, every index
   116  	// column has a range expression, and every range expression is an
   117  	// exact equality.
   118  	IsPointLookup   bool
   119  	IsEmptyRange    bool
   120  	IsSpatialLookup bool
   121  	IsReverse       bool
   122  }
   123  
   124  var emptyLookup = IndexLookup{}
   125  
   126  func NewIndexLookup(idx Index, ranges RangeCollection, isPointLookup, isEmptyRange, isSpatialLookup, isReverse bool) IndexLookup {
   127  	if isReverse {
   128  		for i, j := 0, len(ranges)-1; i < j; i, j = i+1, j-1 {
   129  			ranges[i], ranges[j] = ranges[j], ranges[i]
   130  		}
   131  	}
   132  	return IndexLookup{
   133  		Index:           idx,
   134  		Ranges:          ranges,
   135  		IsPointLookup:   isPointLookup,
   136  		IsEmptyRange:    isEmptyRange,
   137  		IsSpatialLookup: isSpatialLookup,
   138  		IsReverse:       isReverse,
   139  	}
   140  }
   141  
   142  func (il IndexLookup) IsEmpty() bool {
   143  	return il.Index == nil
   144  }
   145  
   146  func (il IndexLookup) String() string {
   147  	pr := NewTreePrinter()
   148  	_ = pr.WriteNode("IndexLookup")
   149  	pr.WriteChildren(fmt.Sprintf("index: %s", il.Index), fmt.Sprintf("ranges: %s", il.Ranges.String()))
   150  	return pr.String()
   151  }
   152  
   153  func (il IndexLookup) DebugString() string {
   154  	pr := NewTreePrinter()
   155  	_ = pr.WriteNode("IndexLookup")
   156  	pr.WriteChildren(fmt.Sprintf("index: %s", il.Index), fmt.Sprintf("ranges: %s", il.Ranges.DebugString()))
   157  	return pr.String()
   158  }
   159  
   160  // FilteredIndex is an extension of |Index| that allows an index to declare certain filter predicates handled,
   161  // allowing them to be removed from the overall plan for greater execution efficiency
   162  type FilteredIndex interface {
   163  	Index
   164  	// HandledFilters returns a subset of |filters| that are satisfied
   165  	// by index lookups to this index.
   166  	HandledFilters(filters []Expression) (handled []Expression)
   167  }
   168  
   169  type IndexOrder byte
   170  
   171  const (
   172  	IndexOrderNone IndexOrder = iota
   173  	IndexOrderAsc
   174  	IndexOrderDesc
   175  )
   176  
   177  // OrderedIndex is an extension of |Index| that allows indexes to declare their return order. The query engine can
   178  // optimize certain queries if the order of an index is guaranteed, e.g. removing a sort operation.
   179  type OrderedIndex interface {
   180  	Index
   181  	// Order returns the order of results for reads from this index
   182  	Order() IndexOrder
   183  	// Reversible returns whether or not this index can be iterated on backwards
   184  	Reversible() bool
   185  }
   186  
   187  // ColumnExpressionType returns a column expression along with its Type.
   188  type ColumnExpressionType struct {
   189  	Expression string
   190  	Type       Type
   191  }
   192  
   193  // ValidatePrimaryKeyDrop validates that a primary key may be dropped. If any validation error is returned, then it
   194  // means it is not valid to drop this table's primary key. Validation includes checking for PK columns with the
   195  // auto_increment property, in which case, MySQL requires that another index exists on the table where the first
   196  // column in the index is the auto_increment column from the primary key.
   197  // https://dev.mysql.com/doc/refman/8.0/en/innodb-auto-increment-handling.html
   198  func ValidatePrimaryKeyDrop(ctx *Context, t IndexAddressableTable, oldSchema PrimaryKeySchema) error {
   199  	// If the primary key doesn't have an auto_increment option set, then we don't validate anything else
   200  	autoIncrementColumn := findPrimaryKeyAutoIncrementColumn(oldSchema)
   201  	if autoIncrementColumn == nil {
   202  		return nil
   203  	}
   204  
   205  	// If there is an auto_increment option set, then we need to verify that there is still a supporting index,
   206  	// meaning the index is prefixed with the primary key column that contains the auto_increment option.
   207  	indexes, err := t.GetIndexes(ctx)
   208  	if err != nil {
   209  		return err
   210  	}
   211  
   212  	for _, idx := range indexes {
   213  		// Don't bother considering FullText or Spatial indexes, since these aren't valid
   214  		// on auto_increment int columns anyway.
   215  		if idx.IsFullText() || idx.IsSpatial() {
   216  			continue
   217  		}
   218  
   219  		// Skip the primary key index, since we're trying to delete it
   220  		if strings.ToLower(idx.ID()) == "primary" {
   221  			continue
   222  		}
   223  
   224  		if idx.Expressions()[0] == autoIncrementColumn.Source+"."+autoIncrementColumn.Name {
   225  			// By this point, we've verified that it's valid to drop the table's primary key
   226  			return nil
   227  		}
   228  	}
   229  
   230  	// We've searched all indexes and couldn't find one supporting the auto_increment column, so we error out.
   231  	return ErrWrongAutoKey.New()
   232  }
   233  
   234  // findPrimaryKeyAutoIncrementColumn returns the first column in the primary key that has the auto_increment option,
   235  // otherwise it returns null if no primary key columns are defined with the auto_increment option.
   236  func findPrimaryKeyAutoIncrementColumn(schema PrimaryKeySchema) *Column {
   237  	for _, ordinal := range schema.PkOrdinals {
   238  		if schema.Schema[ordinal].AutoIncrement {
   239  			return schema.Schema[ordinal]
   240  		}
   241  	}
   242  	return nil
   243  }