github.com/hasnat/dolt/go@v0.0.0-20210628190320-9eb5d843fbb7/libraries/doltcore/schema/index_coll.go (about)

     1  // Copyright 2020 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 schema
    16  
    17  import (
    18  	"fmt"
    19  	"sort"
    20  	"strings"
    21  )
    22  
    23  type IndexCollection interface {
    24  	// AddIndex adds the given index, overwriting any current indexes with the same name or columns.
    25  	// It does not perform any kind of checking, and is intended for schema modifications.
    26  	AddIndex(indexes ...Index)
    27  	// AddIndexByColNames adds an index with the given name and columns (in index order).
    28  	AddIndexByColNames(indexName string, cols []string, props IndexProperties) (Index, error)
    29  	// AddIndexByColTags adds an index with the given name and column tags (in index order).
    30  	AddIndexByColTags(indexName string, tags []uint64, props IndexProperties) (Index, error)
    31  	// todo: this method is trash, clean up this interface
    32  	UnsafeAddIndexByColTags(indexName string, tags []uint64, props IndexProperties) (Index, error)
    33  	// AllIndexes returns a slice containing all of the indexes in this collection.
    34  	AllIndexes() []Index
    35  	// Contains returns whether the given index name already exists for this table.
    36  	Contains(indexName string) bool
    37  	// Count returns the number of indexes in this collection.
    38  	Count() int
    39  	// Equals returns whether this index collection is equivalent to another. Indexes are compared by everything except
    40  	// for their name, the names of all columns, and anything relating to the parent table's primary keys.
    41  	Equals(other IndexCollection) bool
    42  	// GetByName returns the index with the given name, or nil if it does not exist.
    43  	GetByName(indexName string) Index
    44  	// GetByName returns the index with a matching case-insensitive name, the bool return value indicates if a match was found.
    45  	GetByNameCaseInsensitive(indexName string) (Index, bool)
    46  	// GetIndexByColumnNames returns whether the collection contains an index that has this exact collection and ordering of columns.
    47  	GetIndexByColumnNames(cols ...string) (Index, bool)
    48  	// GetIndexByTags returns whether the collection contains an index that has this exact collection and ordering of columns.
    49  	GetIndexByTags(tags ...uint64) (Index, bool)
    50  	// IndexesWithColumn returns all indexes that index the given column.
    51  	IndexesWithColumn(columnName string) []Index
    52  	// IndexesWithTag returns all indexes that index the given tag.
    53  	IndexesWithTag(tag uint64) []Index
    54  	// Iter iterated over the indexes in the collection, calling the cb function on each.
    55  	Iter(cb func(index Index) (stop bool, err error)) error
    56  	// Merge adds the given index if it does not already exist. Indexed columns are referenced by column name,
    57  	// rather than by tag number, which allows an index from a different table to be added as long as they have matching
    58  	// column names. If an index with the same name or column structure already exists, or the index contains different
    59  	// columns, then it is skipped.
    60  	Merge(indexes ...Index)
    61  	// RemoveIndex removes an index from the table metadata.
    62  	RemoveIndex(indexName string) (Index, error)
    63  	// RenameIndex renames an index in the table metadata.
    64  	RenameIndex(oldName, newName string) (Index, error)
    65  }
    66  
    67  type IndexProperties struct {
    68  	IsUnique      bool
    69  	IsUserDefined bool
    70  	Comment       string
    71  }
    72  
    73  type indexCollectionImpl struct {
    74  	colColl       *ColCollection
    75  	indexes       map[string]*indexImpl
    76  	colTagToIndex map[uint64][]*indexImpl
    77  	pks           []uint64
    78  }
    79  
    80  func NewIndexCollection(cols *ColCollection) IndexCollection {
    81  	ixc := &indexCollectionImpl{
    82  		colColl:       cols,
    83  		indexes:       make(map[string]*indexImpl),
    84  		colTagToIndex: make(map[uint64][]*indexImpl),
    85  	}
    86  	if cols != nil {
    87  		for _, col := range cols.cols {
    88  			ixc.colTagToIndex[col.Tag] = nil
    89  			if col.IsPartOfPK {
    90  				ixc.pks = append(ixc.pks, col.Tag)
    91  			}
    92  		}
    93  	}
    94  	return ixc
    95  }
    96  
    97  func (ixc *indexCollectionImpl) AddIndex(indexes ...Index) {
    98  	for _, indexInterface := range indexes {
    99  		index, ok := indexInterface.(*indexImpl)
   100  		if !ok {
   101  			panic(fmt.Errorf("unknown index type: %T", indexInterface))
   102  		}
   103  		index = index.copy()
   104  		index.indexColl = ixc
   105  		index.allTags = combineAllTags(index.tags, ixc.pks)
   106  		oldNamedIndex, ok := ixc.indexes[index.name]
   107  		if ok {
   108  			ixc.removeIndex(oldNamedIndex)
   109  		}
   110  		oldTaggedIndex := ixc.containsColumnTagCollection(index.tags...)
   111  		if oldTaggedIndex != nil {
   112  			ixc.removeIndex(oldTaggedIndex)
   113  		}
   114  		ixc.indexes[index.name] = index
   115  		for _, tag := range index.tags {
   116  			ixc.colTagToIndex[tag] = append(ixc.colTagToIndex[tag], index)
   117  		}
   118  	}
   119  }
   120  
   121  func (ixc *indexCollectionImpl) AddIndexByColNames(indexName string, cols []string, props IndexProperties) (Index, error) {
   122  	tags, ok := ixc.columnNamesToTags(cols)
   123  	if !ok {
   124  		return nil, fmt.Errorf("the table does not contain at least one of the following columns: `%v`", cols)
   125  	}
   126  	return ixc.AddIndexByColTags(indexName, tags, props)
   127  }
   128  
   129  func (ixc *indexCollectionImpl) AddIndexByColTags(indexName string, tags []uint64, props IndexProperties) (Index, error) {
   130  	if strings.HasPrefix(indexName, "dolt_") {
   131  		return nil, fmt.Errorf("indexes cannot be prefixed with `dolt_`")
   132  	}
   133  	if ixc.Contains(indexName) {
   134  		return nil, fmt.Errorf("`%s` already exists as an index for this table", indexName)
   135  	}
   136  	if !ixc.tagsExist(tags...) {
   137  		return nil, fmt.Errorf("tags %v do not exist on this table", tags)
   138  	}
   139  	if ixc.hasIndexOnTags(tags...) {
   140  		return nil, fmt.Errorf("cannot create a duplicate index on this table")
   141  	}
   142  	index := &indexImpl{
   143  		indexColl:     ixc,
   144  		name:          indexName,
   145  		tags:          tags,
   146  		allTags:       combineAllTags(tags, ixc.pks),
   147  		isUnique:      props.IsUnique,
   148  		isUserDefined: props.IsUserDefined,
   149  		comment:       props.Comment,
   150  	}
   151  	ixc.indexes[indexName] = index
   152  	for _, tag := range tags {
   153  		ixc.colTagToIndex[tag] = append(ixc.colTagToIndex[tag], index)
   154  	}
   155  	return index, nil
   156  }
   157  
   158  func (ixc *indexCollectionImpl) UnsafeAddIndexByColTags(indexName string, tags []uint64, props IndexProperties) (Index, error) {
   159  	index := &indexImpl{
   160  		indexColl:     ixc,
   161  		name:          indexName,
   162  		tags:          tags,
   163  		allTags:       combineAllTags(tags, ixc.pks),
   164  		isUnique:      props.IsUnique,
   165  		isUserDefined: props.IsUserDefined,
   166  		comment:       props.Comment,
   167  	}
   168  	ixc.indexes[indexName] = index
   169  	for _, tag := range tags {
   170  		ixc.colTagToIndex[tag] = append(ixc.colTagToIndex[tag], index)
   171  	}
   172  	return index, nil
   173  }
   174  
   175  func (ixc *indexCollectionImpl) AllIndexes() []Index {
   176  	indexes := make([]Index, len(ixc.indexes))
   177  	i := 0
   178  	for _, index := range ixc.indexes {
   179  		indexes[i] = index
   180  		i++
   181  	}
   182  	sort.Slice(indexes, func(i, j int) bool {
   183  		return indexes[i].Name() < indexes[j].Name()
   184  	})
   185  	return indexes
   186  }
   187  
   188  func (ixc *indexCollectionImpl) Contains(indexName string) bool {
   189  	_, ok := ixc.indexes[indexName]
   190  	return ok
   191  }
   192  
   193  func (ixc *indexCollectionImpl) Count() int {
   194  	return len(ixc.indexes)
   195  }
   196  
   197  func (ixc *indexCollectionImpl) Equals(other IndexCollection) bool {
   198  	otherIxc, ok := other.(*indexCollectionImpl)
   199  	if !ok || len(ixc.indexes) != len(otherIxc.indexes) {
   200  		// if the lengths don't match then we can quickly return
   201  		return false
   202  	}
   203  	for _, index := range ixc.indexes {
   204  		otherIndex := otherIxc.containsColumnTagCollection(index.tags...)
   205  		if otherIndex == nil || !index.Equals(otherIndex) {
   206  			return false
   207  		}
   208  	}
   209  	return true
   210  }
   211  
   212  func (ixc *indexCollectionImpl) GetByName(indexName string) Index {
   213  	ix, ok := ixc.indexes[indexName]
   214  	if ok {
   215  		return ix
   216  	}
   217  	return nil
   218  }
   219  
   220  func (ixc *indexCollectionImpl) GetByNameCaseInsensitive(indexName string) (Index, bool) {
   221  	for name, ix := range ixc.indexes {
   222  		if strings.ToLower(name) == strings.ToLower(indexName) {
   223  			return ix, true
   224  		}
   225  	}
   226  	return nil, false
   227  }
   228  
   229  func (ixc *indexCollectionImpl) hasIndexOnColumns(cols ...string) bool {
   230  	tags := make([]uint64, len(cols))
   231  	for i, col := range cols {
   232  		col, ok := ixc.colColl.NameToCol[col]
   233  		if !ok {
   234  			return false
   235  		}
   236  		tags[i] = col.Tag
   237  	}
   238  	return ixc.hasIndexOnTags(tags...)
   239  }
   240  
   241  func (ixc *indexCollectionImpl) GetIndexByColumnNames(cols ...string) (Index, bool) {
   242  	tags := make([]uint64, len(cols))
   243  	for i, col := range cols {
   244  		col, ok := ixc.colColl.NameToCol[col]
   245  		if !ok {
   246  			return nil, false
   247  		}
   248  		tags[i] = col.Tag
   249  	}
   250  	return ixc.GetIndexByTags(tags...)
   251  }
   252  
   253  func (ixc *indexCollectionImpl) GetIndexByTags(tags ...uint64) (Index, bool) {
   254  	idx := ixc.containsColumnTagCollection(tags...)
   255  	if idx == nil {
   256  		return nil, false
   257  	}
   258  	return idx, true
   259  }
   260  
   261  func (ixc *indexCollectionImpl) hasIndexOnTags(tags ...uint64) bool {
   262  	_, ok := ixc.GetIndexByTags(tags...)
   263  	return ok
   264  }
   265  
   266  func (ixc *indexCollectionImpl) IndexesWithColumn(columnName string) []Index {
   267  	col, ok := ixc.colColl.NameToCol[columnName]
   268  	if !ok {
   269  		return nil
   270  	}
   271  	return ixc.IndexesWithTag(col.Tag)
   272  }
   273  
   274  func (ixc *indexCollectionImpl) IndexesWithTag(tag uint64) []Index {
   275  	indexImpls := ixc.colTagToIndex[tag]
   276  	indexes := make([]Index, len(indexImpls))
   277  	for i, idx := range indexImpls {
   278  		indexes[i] = idx
   279  	}
   280  	return indexes
   281  }
   282  
   283  func (ixc *indexCollectionImpl) Iter(cb func(index Index) (stop bool, err error)) error {
   284  	for _, ind := range ixc.indexes {
   285  		stop, err := cb(ind)
   286  		if err != nil {
   287  			return err
   288  		}
   289  		if stop {
   290  			break
   291  		}
   292  	}
   293  	return nil
   294  }
   295  
   296  func (ixc *indexCollectionImpl) Merge(indexes ...Index) {
   297  	for _, index := range indexes {
   298  		if tags, ok := ixc.columnNamesToTags(index.ColumnNames()); ok && !ixc.Contains(index.Name()) {
   299  			newIndex := &indexImpl{
   300  				name:          index.Name(),
   301  				tags:          tags,
   302  				indexColl:     ixc,
   303  				isUnique:      index.IsUnique(),
   304  				isUserDefined: index.IsUserDefined(),
   305  				comment:       index.Comment(),
   306  			}
   307  			ixc.AddIndex(newIndex)
   308  		}
   309  	}
   310  }
   311  
   312  func (ixc *indexCollectionImpl) RemoveIndex(indexName string) (Index, error) {
   313  	if !ixc.Contains(indexName) {
   314  		return nil, fmt.Errorf("`%s` does not exist as an index for this table", indexName)
   315  	}
   316  	index := ixc.indexes[indexName]
   317  	delete(ixc.indexes, indexName)
   318  	for _, tag := range index.tags {
   319  		indexesRefThisCol := ixc.colTagToIndex[tag]
   320  		for i, comparisonIndex := range indexesRefThisCol {
   321  			if comparisonIndex == index {
   322  				ixc.colTagToIndex[tag] = append(indexesRefThisCol[:i], indexesRefThisCol[i+1:]...)
   323  				break
   324  			}
   325  		}
   326  	}
   327  	return index, nil
   328  }
   329  
   330  func (ixc *indexCollectionImpl) RenameIndex(oldName, newName string) (Index, error) {
   331  	if !ixc.Contains(oldName) {
   332  		return nil, fmt.Errorf("`%s` does not exist as an index for this table", oldName)
   333  	}
   334  	if ixc.Contains(newName) {
   335  		return nil, fmt.Errorf("`%s` already exists as an index for this table", newName)
   336  	}
   337  	index := ixc.indexes[oldName]
   338  	delete(ixc.indexes, oldName)
   339  	index.name = newName
   340  	ixc.indexes[newName] = index
   341  	return index, nil
   342  }
   343  
   344  func (ixc *indexCollectionImpl) columnNamesToTags(cols []string) ([]uint64, bool) {
   345  	tags := make([]uint64, len(cols))
   346  	for i, colName := range cols {
   347  		col, ok := ixc.colColl.NameToCol[colName]
   348  		if !ok {
   349  			return nil, false
   350  		}
   351  		tags[i] = col.Tag
   352  	}
   353  	return tags, true
   354  }
   355  
   356  func (ixc *indexCollectionImpl) containsColumnTagCollection(tags ...uint64) *indexImpl {
   357  	tagCount := len(tags)
   358  	for _, idx := range ixc.indexes {
   359  		if tagCount == len(idx.tags) {
   360  			allMatch := true
   361  			for i, idxTag := range idx.tags {
   362  				if tags[i] != idxTag {
   363  					allMatch = false
   364  					break
   365  				}
   366  			}
   367  			if allMatch {
   368  				return idx
   369  			}
   370  		}
   371  	}
   372  	return nil
   373  }
   374  
   375  func (ixc *indexCollectionImpl) removeIndex(index *indexImpl) {
   376  	delete(ixc.indexes, index.name)
   377  	for _, tag := range index.tags {
   378  		var newReferences []*indexImpl
   379  		for _, referencedIndex := range ixc.colTagToIndex[tag] {
   380  			if referencedIndex != index {
   381  				newReferences = append(newReferences, referencedIndex)
   382  			}
   383  		}
   384  		ixc.colTagToIndex[tag] = newReferences
   385  	}
   386  }
   387  
   388  func (ixc *indexCollectionImpl) tagsExist(tags ...uint64) bool {
   389  	if len(tags) == 0 {
   390  		return false
   391  	}
   392  	tagToCol := ixc.colColl.TagToCol
   393  	for _, tag := range tags {
   394  		if _, ok := tagToCol[tag]; !ok {
   395  			return false
   396  		}
   397  	}
   398  	return true
   399  }
   400  
   401  func combineAllTags(tags []uint64, pks []uint64) []uint64 {
   402  	allTags := make([]uint64, len(tags))
   403  	_ = copy(allTags, tags)
   404  	foundCols := make(map[uint64]struct{})
   405  	for _, tag := range tags {
   406  		foundCols[tag] = struct{}{}
   407  	}
   408  	for _, pk := range pks {
   409  		if _, ok := foundCols[pk]; !ok {
   410  			allTags = append(allTags, pk)
   411  		}
   412  	}
   413  	return allTags
   414  }