github.com/dolthub/dolt/go@v0.40.5-0.20240520175717-68db7794bea6/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, prefixLengths []uint16, 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, prefixLengths []uint16, props IndexProperties) (Index, error) 31 // todo: this method is trash, clean up this interface 32 UnsafeAddIndexByColTags(indexName string, tags []uint64, prefixLengths []uint16, 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 // Note that if an index collection contains multiple indexes that cover the same column tags (e.g. different index 50 // types) then this method will return one of them, but it is not guaranteed which one and can easily result in a 51 // race condition. 52 GetIndexByTags(tags ...uint64) (Index, bool) 53 // GetIndexesByTags returns all indexes from this collection that cover the same columns identified by |tags|, in the 54 // same order specified. This method is preferred over GetIndexByTags. 55 GetIndexesByTags(tags ...uint64) []Index 56 // IndexesWithColumn returns all indexes that index the given column. 57 IndexesWithColumn(columnName string) []Index 58 // IndexesWithTag returns all indexes that index the given tag. 59 IndexesWithTag(tag uint64) []Index 60 // Iter iterated over the indexes in the collection, calling the cb function on each. 61 Iter(cb func(index Index) (stop bool, err error)) error 62 // Merge adds the given index if it does not already exist. Indexed columns are referenced by column name, 63 // rather than by tag number, which allows an index from a different table to be added as long as they have matching 64 // column names. If an index with the same name or column structure already exists, or the index contains different 65 // columns, then it is skipped. 66 Merge(indexes ...Index) 67 // RemoveIndex removes an index from the table metadata. 68 RemoveIndex(indexName string) (Index, error) 69 // RenameIndex renames an index in the table metadata. 70 RenameIndex(oldName, newName string) (Index, error) 71 //SetPks changes the pks or pk ordinals 72 SetPks([]uint64) error 73 // ContainsFullTextIndex returns whether the collection contains at least one Full-Text index. 74 ContainsFullTextIndex() bool 75 // Copy returns a copy of this index collection that can be modified without affecting the original. 76 Copy() IndexCollection 77 } 78 79 type IndexProperties struct { 80 IsUnique bool 81 IsSpatial bool 82 IsFullText bool 83 IsUserDefined bool 84 Comment string 85 FullTextProperties 86 } 87 88 type FullTextProperties struct { 89 ConfigTable string 90 PositionTable string 91 DocCountTable string 92 GlobalCountTable string 93 RowCountTable string 94 KeyType uint8 95 KeyName string 96 KeyPositions []uint16 97 } 98 99 type indexCollectionImpl struct { 100 colColl *ColCollection 101 indexes map[string]*indexImpl 102 colTagToIndex map[uint64][]*indexImpl 103 pks []uint64 104 } 105 106 func NewIndexCollection(cols *ColCollection, pkCols *ColCollection) IndexCollection { 107 ixc := &indexCollectionImpl{ 108 colColl: cols, 109 indexes: make(map[string]*indexImpl), 110 colTagToIndex: make(map[uint64][]*indexImpl), 111 } 112 if cols != nil { 113 for _, col := range cols.cols { 114 ixc.colTagToIndex[col.Tag] = nil 115 if col.IsPartOfPK { 116 ixc.pks = append(ixc.pks, col.Tag) 117 } 118 } 119 } 120 if pkCols != nil { 121 for i, col := range pkCols.cols { 122 ixc.pks[i] = col.Tag 123 } 124 } 125 return ixc 126 } 127 128 func (ixc indexCollectionImpl) Copy() IndexCollection { 129 if ixc.pks != nil { 130 pks := make([]uint64, len(ixc.pks)) 131 copy(pks, ixc.pks) 132 ixc.pks = pks 133 } 134 135 if ixc.indexes != nil { 136 indexes := make(map[string]*indexImpl, len(ixc.indexes)) 137 for name, index := range ixc.indexes { 138 indexes[name] = index.copy() 139 } 140 ixc.indexes = indexes 141 } 142 143 if ixc.colTagToIndex != nil { 144 colTagToIndex := make(map[uint64][]*indexImpl, len(ixc.colTagToIndex)) 145 for tag, indexes := range ixc.colTagToIndex { 146 var indexesCopy []*indexImpl 147 if indexes != nil { 148 indexesCopy = make([]*indexImpl, len(indexes)) 149 for i, index := range indexes { 150 indexesCopy[i] = index.copy() 151 } 152 } 153 colTagToIndex[tag] = indexesCopy 154 } 155 ixc.colTagToIndex = colTagToIndex 156 } 157 158 // no need to copy the colColl, it's immutable 159 return &ixc 160 } 161 162 func (ixc *indexCollectionImpl) AddIndex(indexes ...Index) { 163 for _, indexInterface := range indexes { 164 index, ok := indexInterface.(*indexImpl) 165 if !ok { 166 panic(fmt.Errorf("unknown index type: %T", indexInterface)) 167 } 168 index = index.copy() 169 index.indexColl = ixc 170 index.allTags = combineAllTags(index.tags, ixc.pks) 171 lowerName := strings.ToLower(index.name) 172 oldNamedIndex, ok := ixc.indexes[lowerName] 173 if ok { 174 ixc.removeIndex(oldNamedIndex) 175 } 176 oldTaggedIndex := ixc.containsColumnTagCollection(index.tags...) 177 if oldTaggedIndex != nil { 178 ixc.removeIndex(oldTaggedIndex) 179 } 180 ixc.indexes[lowerName] = index 181 for _, tag := range index.tags { 182 ixc.colTagToIndex[tag] = append(ixc.colTagToIndex[tag], index) 183 } 184 } 185 } 186 187 func (ixc *indexCollectionImpl) AddIndexByColNames(indexName string, cols []string, prefixLengths []uint16, props IndexProperties) (Index, error) { 188 tags, ok := ixc.columnNamesToTags(cols) 189 if !ok { 190 return nil, fmt.Errorf("the table does not contain at least one of the following columns: `%v`", cols) 191 } 192 return ixc.AddIndexByColTags(indexName, tags, prefixLengths, props) 193 } 194 195 func (ixc *indexCollectionImpl) AddIndexByColTags(indexName string, tags []uint64, prefixLengths []uint16, props IndexProperties) (Index, error) { 196 lowerName := strings.ToLower(indexName) 197 if strings.HasPrefix(lowerName, "dolt_") { 198 return nil, fmt.Errorf("indexes cannot be prefixed with `dolt_`") 199 } 200 if ixc.Contains(lowerName) { 201 return nil, fmt.Errorf("`%s` already exists as an index for this table", lowerName) 202 } 203 if !ixc.tagsExist(tags...) { 204 return nil, fmt.Errorf("tags %v do not exist on this table", tags) 205 } 206 207 for _, tag := range tags { 208 // we already validated the tag exists 209 c, _ := ixc.colColl.GetByTag(tag) 210 err := validateColumnIndexable(c) 211 if err != nil { 212 return nil, err 213 } 214 } 215 216 index := &indexImpl{ 217 indexColl: ixc, 218 name: indexName, 219 tags: tags, 220 allTags: combineAllTags(tags, ixc.pks), 221 isUnique: props.IsUnique, 222 isSpatial: props.IsSpatial, 223 isFullText: props.IsFullText, 224 isUserDefined: props.IsUserDefined, 225 comment: props.Comment, 226 prefixLengths: prefixLengths, 227 fullTextProps: props.FullTextProperties, 228 } 229 ixc.indexes[lowerName] = index 230 for _, tag := range tags { 231 ixc.colTagToIndex[tag] = append(ixc.colTagToIndex[tag], index) 232 } 233 return index, nil 234 } 235 236 // validateColumnIndexable returns an error if the column given cannot be used in an index 237 func validateColumnIndexable(c Column) error { 238 return nil 239 } 240 241 func (ixc *indexCollectionImpl) UnsafeAddIndexByColTags(indexName string, tags []uint64, prefixLengths []uint16, props IndexProperties) (Index, error) { 242 index := &indexImpl{ 243 indexColl: ixc, 244 name: indexName, 245 tags: tags, 246 allTags: combineAllTags(tags, ixc.pks), 247 isUnique: props.IsUnique, 248 isSpatial: props.IsSpatial, 249 isFullText: props.IsFullText, 250 isUserDefined: props.IsUserDefined, 251 comment: props.Comment, 252 prefixLengths: prefixLengths, 253 fullTextProps: props.FullTextProperties, 254 } 255 ixc.indexes[strings.ToLower(indexName)] = index 256 for _, tag := range tags { 257 ixc.colTagToIndex[tag] = append(ixc.colTagToIndex[tag], index) 258 } 259 return index, nil 260 } 261 262 func (ixc *indexCollectionImpl) AllIndexes() []Index { 263 indexes := make([]Index, len(ixc.indexes)) 264 i := 0 265 for _, index := range ixc.indexes { 266 indexes[i] = index 267 i++ 268 } 269 sort.Slice(indexes, func(i, j int) bool { 270 return indexes[i].Name() < indexes[j].Name() 271 }) 272 return indexes 273 } 274 275 func (ixc *indexCollectionImpl) Contains(indexName string) bool { 276 _, ok := ixc.indexes[strings.ToLower(indexName)] 277 return ok 278 } 279 280 func (ixc *indexCollectionImpl) Count() int { 281 return len(ixc.indexes) 282 } 283 284 func (ixc *indexCollectionImpl) Equals(other IndexCollection) bool { 285 otherIxc, ok := other.(*indexCollectionImpl) 286 if !ok || len(ixc.indexes) != len(otherIxc.indexes) { 287 // if the lengths don't match then we can quickly return 288 return false 289 } 290 for _, index := range ixc.indexes { 291 otherIndex := otherIxc.containsColumnTagCollection(index.tags...) 292 if otherIndex == nil || !index.Equals(otherIndex) { 293 return false 294 } 295 } 296 return true 297 } 298 299 func (ixc *indexCollectionImpl) GetByName(indexName string) Index { 300 ix, ok := ixc.indexes[strings.ToLower(indexName)] 301 if ok { 302 return ix 303 } 304 return nil 305 } 306 307 func (ixc *indexCollectionImpl) GetByNameCaseInsensitive(indexName string) (Index, bool) { 308 for name, ix := range ixc.indexes { 309 if strings.ToLower(name) == strings.ToLower(indexName) { 310 return ix, true 311 } 312 } 313 return nil, false 314 } 315 316 func (ixc *indexCollectionImpl) hasIndexOnColumns(cols ...string) bool { 317 tags := make([]uint64, len(cols)) 318 for i, col := range cols { 319 col, ok := ixc.colColl.NameToCol[col] 320 if !ok { 321 return false 322 } 323 tags[i] = col.Tag 324 } 325 return ixc.hasIndexOnTags(tags...) 326 } 327 328 func (ixc *indexCollectionImpl) GetIndexByColumnNames(cols ...string) (Index, bool) { 329 tags := make([]uint64, len(cols)) 330 for i, col := range cols { 331 col, ok := ixc.colColl.NameToCol[col] 332 if !ok { 333 return nil, false 334 } 335 tags[i] = col.Tag 336 } 337 return ixc.GetIndexByTags(tags...) 338 } 339 340 func (ixc *indexCollectionImpl) GetIndexByTags(tags ...uint64) (Index, bool) { 341 idx := ixc.containsColumnTagCollection(tags...) 342 if idx == nil { 343 return nil, false 344 } 345 return idx, true 346 } 347 348 // GetIndexesByTags implements the schema.Index interface 349 func (ixc *indexCollectionImpl) GetIndexesByTags(tags ...uint64) []Index { 350 var result []Index 351 352 tagCount := len(tags) 353 for _, idx := range ixc.indexes { 354 if tagCount != len(idx.tags) { 355 continue 356 } 357 358 allMatch := true 359 for i, idxTag := range idx.tags { 360 if tags[i] != idxTag { 361 allMatch = false 362 break 363 } 364 } 365 if allMatch { 366 result = append(result, idx) 367 } 368 } 369 return result 370 } 371 372 func (ixc *indexCollectionImpl) hasIndexOnTags(tags ...uint64) bool { 373 _, ok := ixc.GetIndexByTags(tags...) 374 return ok 375 } 376 377 func (ixc *indexCollectionImpl) IndexesWithColumn(columnName string) []Index { 378 col, ok := ixc.colColl.NameToCol[columnName] 379 if !ok { 380 return nil 381 } 382 return ixc.IndexesWithTag(col.Tag) 383 } 384 385 func (ixc *indexCollectionImpl) IndexesWithTag(tag uint64) []Index { 386 indexImpls := ixc.colTagToIndex[tag] 387 indexes := make([]Index, len(indexImpls)) 388 for i, idx := range indexImpls { 389 indexes[i] = idx 390 } 391 return indexes 392 } 393 394 func (ixc *indexCollectionImpl) Iter(cb func(index Index) (stop bool, err error)) error { 395 for _, ind := range ixc.indexes { 396 stop, err := cb(ind) 397 if err != nil { 398 return err 399 } 400 if stop { 401 break 402 } 403 } 404 return nil 405 } 406 407 func (ixc *indexCollectionImpl) Merge(indexes ...Index) { 408 for _, index := range indexes { 409 if tags, ok := ixc.columnNamesToTags(index.ColumnNames()); ok && !ixc.Contains(index.Name()) { 410 newIndex := &indexImpl{ 411 name: index.Name(), 412 tags: tags, 413 indexColl: ixc, 414 isUnique: index.IsUnique(), 415 isSpatial: index.IsSpatial(), 416 isFullText: index.IsFullText(), 417 isUserDefined: index.IsUserDefined(), 418 comment: index.Comment(), 419 prefixLengths: index.PrefixLengths(), 420 fullTextProps: index.FullTextProperties(), 421 } 422 ixc.AddIndex(newIndex) 423 } 424 } 425 } 426 427 func (ixc *indexCollectionImpl) RemoveIndex(indexName string) (Index, error) { 428 lowerName := strings.ToLower(indexName) 429 if !ixc.Contains(lowerName) { 430 return nil, fmt.Errorf("`%s` does not exist as an index for this table", lowerName) 431 } 432 index := ixc.indexes[lowerName] 433 delete(ixc.indexes, lowerName) 434 for _, tag := range index.tags { 435 indexesRefThisCol := ixc.colTagToIndex[tag] 436 for i, comparisonIndex := range indexesRefThisCol { 437 if comparisonIndex == index { 438 ixc.colTagToIndex[tag] = append(indexesRefThisCol[:i], indexesRefThisCol[i+1:]...) 439 break 440 } 441 } 442 } 443 return index, nil 444 } 445 446 func (ixc *indexCollectionImpl) RenameIndex(oldName, newName string) (Index, error) { 447 if !ixc.Contains(oldName) { 448 return nil, fmt.Errorf("`%s` does not exist as an index for this table", oldName) 449 } 450 if ixc.Contains(newName) { 451 return nil, fmt.Errorf("`%s` already exists as an index for this table", newName) 452 } 453 index := ixc.indexes[oldName] 454 delete(ixc.indexes, oldName) 455 index.name = newName 456 ixc.indexes[newName] = index 457 return index, nil 458 } 459 460 func (ixc *indexCollectionImpl) columnNamesToTags(cols []string) ([]uint64, bool) { 461 tags := make([]uint64, len(cols)) 462 for i, colName := range cols { 463 col, ok := ixc.colColl.NameToCol[colName] 464 if !ok { 465 return nil, false 466 } 467 tags[i] = col.Tag 468 } 469 return tags, true 470 } 471 472 func (ixc *indexCollectionImpl) containsColumnTagCollection(tags ...uint64) *indexImpl { 473 tagCount := len(tags) 474 for _, idx := range ixc.indexes { 475 if tagCount == len(idx.tags) { 476 allMatch := true 477 for i, idxTag := range idx.tags { 478 if tags[i] != idxTag { 479 allMatch = false 480 break 481 } 482 } 483 if allMatch { 484 return idx 485 } 486 } 487 } 488 return nil 489 } 490 491 func (ixc *indexCollectionImpl) removeIndex(index *indexImpl) { 492 delete(ixc.indexes, strings.ToLower(index.name)) 493 for _, tag := range index.tags { 494 var newReferences []*indexImpl 495 for _, referencedIndex := range ixc.colTagToIndex[tag] { 496 if referencedIndex != index { 497 newReferences = append(newReferences, referencedIndex) 498 } 499 } 500 ixc.colTagToIndex[tag] = newReferences 501 } 502 } 503 504 func (ixc *indexCollectionImpl) tagsExist(tags ...uint64) bool { 505 if len(tags) == 0 { 506 return false 507 } 508 tagToCol := ixc.colColl.TagToCol 509 for _, tag := range tags { 510 if _, ok := tagToCol[tag]; !ok { 511 return false 512 } 513 } 514 return true 515 } 516 517 func (ixc *indexCollectionImpl) SetPks(tags []uint64) error { 518 if len(tags) != len(ixc.pks) { 519 return ErrInvalidPkOrdinals 520 } 521 ixc.pks = tags 522 return nil 523 } 524 525 func (ixc *indexCollectionImpl) ContainsFullTextIndex() bool { 526 for _, idx := range ixc.indexes { 527 if idx.isFullText { 528 return true 529 } 530 } 531 return false 532 } 533 534 // TableNameSlice returns the table names as a slice, which may be used to easily grab all of the tables using a for loop. 535 func (props FullTextProperties) TableNameSlice() []string { 536 return []string{ 537 props.ConfigTable, 538 props.PositionTable, 539 props.DocCountTable, 540 props.GlobalCountTable, 541 props.RowCountTable, 542 } 543 } 544 545 func combineAllTags(tags []uint64, pks []uint64) []uint64 { 546 allTags := make([]uint64, len(tags)) 547 _ = copy(allTags, tags) 548 foundCols := make(map[uint64]struct{}) 549 for _, tag := range tags { 550 foundCols[tag] = struct{}{} 551 } 552 for _, pk := range pks { 553 if _, ok := foundCols[pk]; !ok { 554 allTags = append(allTags, pk) 555 } 556 } 557 return allTags 558 }