github.com/dolthub/dolt/go@v0.40.5-0.20240520175717-68db7794bea6/libraries/doltcore/schema/index.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 "context" 19 "io" 20 21 "github.com/dolthub/dolt/go/store/types" 22 ) 23 24 type Index interface { 25 // AllTags returns the tags of the columns in the entire index, including the primary keys. 26 // If we imagined a dolt index as being a standard dolt table, then the tags would represent the schema columns. 27 AllTags() []uint64 28 // ColumnNames returns the names of the columns in the index. 29 ColumnNames() []string 30 // Comment returns the comment that was provided upon index creation. 31 Comment() string 32 // Count returns the number of indexed columns in this index. 33 Count() int 34 // DeepEquals returns whether this Index is equivalent to another. This function is similar to Equals, however it 35 // does take the table's primary keys into consideration. 36 DeepEquals(other Index) bool 37 // Equals returns whether this Index is equivalent to another. This does not check for column names, thus those may 38 // be renamed and the index equivalence will be preserved. It also does not depend on the table's primary keys. 39 Equals(other Index) bool 40 // GetColumn returns the column for the given tag and whether the column was found or not. 41 GetColumn(tag uint64) (Column, bool) 42 // IndexedColumnTags returns the tags of the columns in the index. 43 IndexedColumnTags() []uint64 44 // IsUnique returns whether the given index has the UNIQUE constraint. 45 IsUnique() bool 46 // IsSpatial returns whether the given index has the SPATIAL constraint. 47 IsSpatial() bool 48 // IsFullText returns whether the given index has the FULLTEXT constraint. 49 IsFullText() bool 50 // IsUserDefined returns whether the given index was created by a user or automatically generated. 51 IsUserDefined() bool 52 // Name returns the name of the index. 53 Name() string 54 // PrimaryKeyTags returns the primary keys of the indexed table, in the order that they're stored for that table. 55 PrimaryKeyTags() []uint64 56 // Schema returns the schema for the internal index map. Can be used for table operations. 57 Schema() Schema 58 // ToTableTuple returns a tuple that may be used to retrieve the original row from the indexed table when given 59 // a full index key (and not a partial index key). 60 ToTableTuple(ctx context.Context, fullKey types.Tuple, format *types.NomsBinFormat) (types.Tuple, error) 61 // PrefixLengths returns the prefix lengths for the index 62 PrefixLengths() []uint16 63 // FullTextProperties returns all properties belonging to a Full-Text index. 64 FullTextProperties() FullTextProperties 65 } 66 67 var _ Index = (*indexImpl)(nil) 68 69 type indexImpl struct { 70 name string 71 tags []uint64 72 allTags []uint64 73 indexColl *indexCollectionImpl 74 isUnique bool 75 isSpatial bool 76 isFullText bool 77 isUserDefined bool 78 comment string 79 prefixLengths []uint16 80 fullTextProps FullTextProperties 81 } 82 83 func NewIndex(name string, tags, allTags []uint64, indexColl IndexCollection, props IndexProperties) Index { 84 var indexCollImpl *indexCollectionImpl 85 if indexColl != nil { 86 indexCollImpl = indexColl.(*indexCollectionImpl) 87 } 88 89 return &indexImpl{ 90 name: name, 91 tags: tags, 92 allTags: allTags, 93 indexColl: indexCollImpl, 94 isUnique: props.IsUnique, 95 isSpatial: props.IsSpatial, 96 isFullText: props.IsFullText, 97 isUserDefined: props.IsUserDefined, 98 comment: props.Comment, 99 fullTextProps: props.FullTextProperties, 100 } 101 } 102 103 // AllTags implements Index. 104 func (ix *indexImpl) AllTags() []uint64 { 105 return ix.allTags 106 } 107 108 // ColumnNames implements Index. 109 func (ix *indexImpl) ColumnNames() []string { 110 colNames := make([]string, len(ix.tags)) 111 for i, tag := range ix.tags { 112 colNames[i] = ix.indexColl.colColl.TagToCol[tag].Name 113 } 114 return colNames 115 } 116 117 // Comment implements Index. 118 func (ix *indexImpl) Comment() string { 119 return ix.comment 120 } 121 122 // Count implements Index. 123 func (ix *indexImpl) Count() int { 124 return len(ix.tags) 125 } 126 127 // Equals implements Index. 128 func (ix *indexImpl) Equals(other Index) bool { 129 if ix.Count() != other.Count() { 130 return false 131 } 132 133 // we're only interested in columns the index is defined over, not the table's primary keys 134 tt := ix.IndexedColumnTags() 135 ot := other.IndexedColumnTags() 136 for i := range tt { 137 if tt[i] != ot[i] { 138 return false 139 } 140 } 141 142 return ix.IsUnique() == other.IsUnique() && 143 ix.IsSpatial() == other.IsSpatial() && 144 compareUint16Slices(ix.PrefixLengths(), other.PrefixLengths()) && 145 ix.Comment() == other.Comment() && 146 ix.Name() == other.Name() 147 } 148 149 // DeepEquals implements Index. 150 func (ix *indexImpl) DeepEquals(other Index) bool { 151 if ix.Count() != other.Count() { 152 return false 153 } 154 155 // DeepEquals compares all tags used in this index, as well as the tags from the table's primary key 156 tt := ix.AllTags() 157 ot := other.AllTags() 158 for i := range tt { 159 if tt[i] != ot[i] { 160 return false 161 } 162 } 163 164 return ix.IsUnique() == other.IsUnique() && 165 ix.IsSpatial() == other.IsSpatial() && 166 compareUint16Slices(ix.PrefixLengths(), other.PrefixLengths()) && 167 ix.Comment() == other.Comment() && 168 ix.Name() == other.Name() 169 } 170 171 // compareUint16Slices returns true if |a| and |b| contain the exact same uint16 values, in the same order; otherwise 172 // it returns false. 173 func compareUint16Slices(a, b []uint16) bool { 174 if len(a) != len(b) { 175 return false 176 } 177 178 for i := range a { 179 if a[i] != b[i] { 180 return false 181 } 182 } 183 184 return true 185 } 186 187 // GetColumn implements Index. 188 func (ix *indexImpl) GetColumn(tag uint64) (Column, bool) { 189 return ix.indexColl.colColl.GetByTag(tag) 190 } 191 192 // IndexedColumnTags implements Index. 193 func (ix *indexImpl) IndexedColumnTags() []uint64 { 194 return ix.tags 195 } 196 197 // IsUnique implements Index. 198 func (ix *indexImpl) IsUnique() bool { 199 return ix.isUnique 200 } 201 202 // IsSpatial implements Index. 203 func (ix *indexImpl) IsSpatial() bool { 204 return ix.isSpatial 205 } 206 207 // IsFullText implements Index. 208 func (ix *indexImpl) IsFullText() bool { 209 return ix.isFullText 210 } 211 212 // IsUserDefined implements Index. 213 func (ix *indexImpl) IsUserDefined() bool { 214 return ix.isUserDefined 215 } 216 217 // Name implements Index. 218 func (ix *indexImpl) Name() string { 219 return ix.name 220 } 221 222 // PrimaryKeyTags implements Index. 223 func (ix *indexImpl) PrimaryKeyTags() []uint64 { 224 return ix.indexColl.pks 225 } 226 227 // Schema implements Index. 228 func (ix *indexImpl) Schema() Schema { 229 contentHashedFields := make([]uint64, 0) 230 cols := make([]Column, len(ix.allTags)) 231 for i, tag := range ix.allTags { 232 col := ix.indexColl.colColl.TagToCol[tag] 233 cols[i] = Column{ 234 Name: col.Name, 235 Tag: tag, 236 Kind: col.Kind, 237 IsPartOfPK: true, 238 TypeInfo: col.TypeInfo, 239 Constraints: nil, 240 } 241 242 // contentHashedFields is the collection of column tags for columns in a unique index that do 243 // not have a prefix length specified and should be stored as a content hash. This information 244 // is needed to later identify that an index is using content-hashed encoding. 245 prefixLength := uint16(0) 246 if len(ix.PrefixLengths()) > i { 247 prefixLength = ix.PrefixLengths()[i] 248 } 249 if ix.IsUnique() && prefixLength == 0 { 250 contentHashedFields = append(contentHashedFields, tag) 251 } 252 } 253 allCols := NewColCollection(cols...) 254 nonPkCols := NewColCollection() 255 return &schemaImpl{ 256 pkCols: allCols, 257 nonPKCols: nonPkCols, 258 allCols: allCols, 259 indexCollection: NewIndexCollection(nil, nil), 260 checkCollection: NewCheckCollection(), 261 contentHashedFields: contentHashedFields, 262 } 263 } 264 265 // ToTableTuple implements Index. 266 func (ix *indexImpl) ToTableTuple(ctx context.Context, fullKey types.Tuple, format *types.NomsBinFormat) (types.Tuple, error) { 267 pkTags := make(map[uint64]int) 268 for i, tag := range ix.indexColl.pks { 269 pkTags[tag] = i 270 } 271 tplItr, err := fullKey.Iterator() 272 if err != nil { 273 return types.Tuple{}, err 274 } 275 resVals := make([]types.Value, len(pkTags)*2) 276 for { 277 _, tag, err := tplItr.NextUint64() 278 if err != nil { 279 if err == io.EOF { 280 break 281 } 282 return types.Tuple{}, err 283 } 284 idx, inPK := pkTags[tag] 285 if inPK { 286 _, valVal, err := tplItr.Next() 287 if err != nil { 288 return types.Tuple{}, err 289 } 290 resVals[idx*2] = types.Uint(tag) 291 resVals[idx*2+1] = valVal 292 } else { 293 err := tplItr.Skip() 294 if err != nil { 295 return types.Tuple{}, err 296 } 297 } 298 } 299 return types.NewTuple(format, resVals...) 300 } 301 302 // PrefixLengths implements Index. 303 func (ix *indexImpl) PrefixLengths() []uint16 { 304 return ix.prefixLengths 305 } 306 307 // FullTextProperties implements Index. 308 func (ix *indexImpl) FullTextProperties() FullTextProperties { 309 return ix.fullTextProps 310 } 311 312 // copy returns an exact copy of the calling index. 313 func (ix *indexImpl) copy() *indexImpl { 314 newIx := *ix 315 newIx.tags = make([]uint64, len(ix.tags)) 316 _ = copy(newIx.tags, ix.tags) 317 newIx.allTags = make([]uint64, len(ix.allTags)) 318 _ = copy(newIx.allTags, ix.allTags) 319 if len(ix.prefixLengths) > 0 { 320 newIx.prefixLengths = make([]uint16, len(ix.prefixLengths)) 321 _ = copy(newIx.prefixLengths, ix.prefixLengths) 322 } 323 if len(newIx.fullTextProps.KeyPositions) > 0 { 324 newIx.fullTextProps.KeyPositions = make([]uint16, len(ix.fullTextProps.KeyPositions)) 325 _ = copy(newIx.fullTextProps.KeyPositions, ix.fullTextProps.KeyPositions) 326 } 327 return &newIx 328 }