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 }