github.com/dolthub/go-mysql-server@v0.18.0/sql/index_registry_test.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 "testing" 20 21 "github.com/stretchr/testify/require" 22 ) 23 24 func TestIndexesByTable(t *testing.T) { 25 var require = require.New(t) 26 27 var r = NewIndexRegistry() 28 r.indexOrder = []indexKey{ 29 {"foo", "bar_idx_1"}, 30 {"foo", "bar_idx_2"}, 31 {"foo", "bar_idx_3"}, 32 {"foo", "baz_idx_1"}, 33 {"oof", "rab_idx_1"}, 34 } 35 36 r.indexes = map[indexKey]DriverIndex{ 37 indexKey{"foo", "bar_idx_1"}: &dummyIdx{ 38 database: "foo", 39 table: "bar", 40 id: "bar_idx_1", 41 expr: []Expression{dummyExpr{1, "2"}}, 42 }, 43 indexKey{"foo", "bar_idx_2"}: &dummyIdx{ 44 database: "foo", 45 table: "bar", 46 id: "bar_idx_2", 47 expr: []Expression{dummyExpr{2, "3"}}, 48 }, 49 indexKey{"foo", "bar_idx_3"}: &dummyIdx{ 50 database: "foo", 51 table: "bar", 52 id: "bar_idx_3", 53 expr: []Expression{dummyExpr{3, "4"}}, 54 }, 55 indexKey{"foo", "baz_idx_1"}: &dummyIdx{ 56 database: "foo", 57 table: "baz", 58 id: "baz_idx_1", 59 expr: []Expression{dummyExpr{4, "5"}}, 60 }, 61 indexKey{"oof", "rab_idx_1"}: &dummyIdx{ 62 database: "oof", 63 table: "rab", 64 id: "rab_idx_1", 65 expr: []Expression{dummyExpr{5, "6"}}, 66 }, 67 } 68 69 r.statuses[indexKey{"foo", "bar_idx_1"}] = IndexReady 70 r.statuses[indexKey{"foo", "bar_idx_2"}] = IndexReady 71 r.statuses[indexKey{"foo", "bar_idx_3"}] = IndexNotReady 72 r.statuses[indexKey{"foo", "baz_idx_1"}] = IndexReady 73 r.statuses[indexKey{"oof", "rab_idx_1"}] = IndexReady 74 75 indexes := r.IndexesByTable("foo", "bar") 76 require.Len(indexes, 3) 77 78 for i, idx := range indexes { 79 expected := r.indexes[r.indexOrder[i]] 80 require.Equal(expected, idx) 81 r.ReleaseIndex(idx) 82 } 83 } 84 85 func TestIndexByExpression(t *testing.T) { 86 require := require.New(t) 87 88 r := NewIndexRegistry() 89 r.indexOrder = []indexKey{ 90 {"foo", ""}, 91 {"foo", "bar"}, 92 } 93 r.indexes = map[indexKey]DriverIndex{ 94 indexKey{"foo", ""}: &dummyIdx{ 95 database: "foo", 96 expr: []Expression{dummyExpr{1, "2"}}, 97 }, 98 indexKey{"foo", "bar"}: &dummyIdx{ 99 database: "foo", 100 id: "bar", 101 expr: []Expression{dummyExpr{2, "3"}}, 102 }, 103 } 104 r.statuses[indexKey{"foo", ""}] = IndexReady 105 106 ctx := NewEmptyContext() 107 idx, prefixCount, err := r.MatchingIndex(ctx, "bar", dummyExpr{1, "2"}) 108 require.NoError(err) 109 require.Equal(0, prefixCount) 110 require.Nil(idx) 111 112 idx, prefixCount, err = r.MatchingIndex(ctx, "foo", dummyExpr{1, "2"}) 113 require.NoError(err) 114 require.Equal(1, prefixCount) 115 require.NotNil(idx) 116 117 idx, prefixCount, err = r.MatchingIndex(ctx, "foo", dummyExpr{2, "3"}) 118 require.NoError(err) 119 require.Equal(0, prefixCount) 120 require.Nil(idx) 121 122 idx, prefixCount, err = r.MatchingIndex(ctx, "foo", dummyExpr{3, "4"}) 123 require.NoError(err) 124 require.Equal(0, prefixCount) 125 require.Nil(idx) 126 } 127 128 func TestAddIndex(t *testing.T) { 129 require := require.New(t) 130 r := NewIndexRegistry() 131 idx := &dummyIdx{ 132 id: "foo", 133 expr: []Expression{new(dummyExpr)}, 134 database: "foo", 135 table: "foo", 136 } 137 138 done, ready, err := r.AddIndex(idx) 139 require.NoError(err) 140 141 i := r.Index("foo", "foo") 142 require.False(r.CanUseIndex(i)) 143 144 done <- struct{}{} 145 146 <-ready 147 i = r.Index("foo", "foo") 148 require.True(r.CanUseIndex(i)) 149 150 _, _, err = r.AddIndex(idx) 151 require.Error(err) 152 require.True(ErrIndexIDAlreadyRegistered.Is(err)) 153 154 _, _, err = r.AddIndex(&dummyIdx{ 155 id: "another", 156 expr: []Expression{new(dummyExpr)}, 157 database: "foo", 158 table: "foo", 159 }) 160 require.Error(err) 161 require.True(ErrIndexExpressionAlreadyRegistered.Is(err)) 162 } 163 164 func TestDeleteIndex(t *testing.T) { 165 require := require.New(t) 166 r := NewIndexRegistry() 167 168 idx := &dummyIdx{"foo", nil, "foo", "foo"} 169 idx2 := &dummyIdx{"foo", nil, "foo", "bar"} 170 r.indexes[indexKey{"foo", "foo"}] = idx 171 r.indexes[indexKey{"foo", "bar"}] = idx2 172 173 _, err := r.DeleteIndex("foo", "foo", false) 174 require.Error(err) 175 require.True(ErrIndexDeleteInvalidStatus.Is(err)) 176 177 _, err = r.DeleteIndex("foo", "foo", true) 178 require.NoError(err) 179 180 r.setStatus(idx2, IndexReady) 181 182 _, err = r.DeleteIndex("foo", "foo", false) 183 require.NoError(err) 184 185 require.Len(r.indexes, 0) 186 } 187 188 func TestDeleteIndex_InUse(t *testing.T) { 189 require := require.New(t) 190 r := NewIndexRegistry() 191 idx := &dummyIdx{ 192 "foo", nil, "foo", "foo", 193 } 194 r.indexes[indexKey{"foo", "foo"}] = idx 195 r.setStatus(idx, IndexReady) 196 r.retainIndex("foo", "foo") 197 198 done, err := r.DeleteIndex("foo", "foo", false) 199 require.NoError(err) 200 201 require.Len(r.indexes, 1) 202 require.False(r.CanUseIndex(idx)) 203 204 go func() { 205 r.ReleaseIndex(idx) 206 }() 207 208 <-done 209 require.Len(r.indexes, 0) 210 } 211 212 func TestExpressionsWithIndexes(t *testing.T) { 213 require := require.New(t) 214 215 r := NewIndexRegistry() 216 217 var indexes = []*dummyIdx{ 218 { 219 "idx1", 220 []Expression{ 221 &dummyExpr{0, "foo"}, 222 &dummyExpr{1, "bar"}, 223 }, 224 "foo", 225 "foo", 226 }, 227 { 228 "idx2", 229 []Expression{ 230 &dummyExpr{0, "foo"}, 231 &dummyExpr{1, "bar"}, 232 &dummyExpr{3, "baz"}, 233 }, 234 "foo", 235 "foo", 236 }, 237 { 238 "idx3", 239 []Expression{ 240 &dummyExpr{0, "foo"}, 241 }, 242 "foo", 243 "foo", 244 }, 245 } 246 247 for _, idx := range indexes { 248 done, ready, err := r.AddIndex(idx) 249 require.NoError(err) 250 close(done) 251 <-ready 252 } 253 254 exprs := r.ExpressionsWithIndexes( 255 "foo", 256 &dummyExpr{0, "foo"}, 257 &dummyExpr{1, "bar"}, 258 &dummyExpr{3, "baz"}, 259 ) 260 261 expected := [][]Expression{ 262 { 263 &dummyExpr{0, "foo"}, 264 &dummyExpr{1, "bar"}, 265 &dummyExpr{3, "baz"}, 266 }, 267 { 268 &dummyExpr{0, "foo"}, 269 &dummyExpr{1, "bar"}, 270 }, 271 { 272 &dummyExpr{0, "foo"}, 273 }, 274 } 275 276 require.ElementsMatch(expected, exprs) 277 } 278 279 func TestLoadIndexes(t *testing.T) { 280 ctx := NewEmptyContext() 281 require := require.New(t) 282 283 d1 := &loadDriver{id: "d1", indexes: []DriverIndex{ 284 &dummyIdx{id: "idx1", database: "db1", table: "t1"}, 285 &dummyIdx{id: "idx2", database: "db2", table: "t3"}, 286 }} 287 288 d2 := &loadDriver{id: "d2", indexes: []DriverIndex{ 289 &dummyIdx{id: "idx3", database: "db1", table: "t2"}, 290 &dummyIdx{id: "idx4", database: "db2", table: "t4"}, 291 }} 292 293 registry := NewIndexRegistry() 294 registry.RegisterIndexDriver(d1) 295 registry.RegisterIndexDriver(d2) 296 297 dbs := []Database{ 298 dummyDB{ 299 name: "db1", 300 tables: map[string]Table{ 301 "t1": &dummyTable{name: "t1"}, 302 "t2": &dummyTable{name: "t2"}, 303 }, 304 }, 305 dummyDB{ 306 name: "db2", 307 tables: map[string]Table{ 308 "t3": &dummyTable{name: "t3"}, 309 "t4": &dummyTable{name: "t4"}, 310 }, 311 }, 312 } 313 314 require.NoError(registry.LoadIndexes(ctx, dbs)) 315 require.NoError(registry.registerIndexesForTable(ctx, "db1", "t1")) 316 require.NoError(registry.registerIndexesForTable(ctx, "db1", "t2")) 317 require.NoError(registry.registerIndexesForTable(ctx, "db2", "t3")) 318 require.NoError(registry.registerIndexesForTable(ctx, "db2", "t4")) 319 320 expected := append(d1.indexes[:], d2.indexes...) 321 var result []Index 322 for _, idx := range registry.indexes { 323 result = append(result, idx) 324 } 325 326 require.ElementsMatch(expected, result) 327 328 for _, idx := range expected { 329 require.Equal(registry.statuses[indexKey{idx.Database(), idx.ID()}], IndexReady) 330 } 331 } 332 333 func TestLoadOutdatedIndexes(t *testing.T) { 334 ctx := NewEmptyContext() 335 require := require.New(t) 336 337 d := &loadDriver{id: "d1", indexes: []DriverIndex{ 338 &checksumIndex{&dummyIdx{id: "idx1", database: "db1", table: "t1"}, "2"}, 339 &checksumIndex{&dummyIdx{id: "idx2", database: "db1", table: "t2"}, "2"}, 340 }} 341 342 registry := NewIndexRegistry() 343 registry.RegisterIndexDriver(d) 344 345 dbs := []Database{ 346 dummyDB{ 347 name: "db1", 348 tables: map[string]Table{ 349 "t1": &checksumTable{&dummyTable{name: "t1"}, "2"}, 350 "t2": &checksumTable{&dummyTable{name: "t2"}, "1"}, 351 }, 352 }, 353 } 354 355 require.NoError(registry.LoadIndexes(ctx, dbs)) 356 require.NoError(registry.registerIndexesForTable(ctx, "db1", "t1")) 357 require.NoError(registry.registerIndexesForTable(ctx, "db1", "t2")) 358 359 var result []Index 360 for _, idx := range registry.indexes { 361 result = append(result, idx) 362 } 363 364 require.ElementsMatch(d.indexes, result) 365 366 require.Equal(registry.statuses[indexKey{"db1", "idx1"}], IndexReady) 367 require.Equal(registry.statuses[indexKey{"db1", "idx2"}], IndexOutdated) 368 } 369 370 var _ Database = dummyDB{} 371 372 type dummyDB struct { 373 name string 374 tables map[string]Table 375 } 376 377 func (d dummyDB) Name() string { return d.name } 378 func (d dummyDB) Tables() map[string]Table { return d.tables } 379 func (d dummyDB) GetTableInsensitive(ctx *Context, tblName string) (Table, bool, error) { 380 tbl, ok := GetTableInsensitive(tblName, d.tables) 381 return tbl, ok, nil 382 } 383 func (d dummyDB) GetTableNames(ctx *Context) ([]string, error) { 384 tblNames := make([]string, 0, len(d.tables)) 385 for k := range d.tables { 386 tblNames = append(tblNames, k) 387 } 388 389 return tblNames, nil 390 } 391 392 type dummyTable struct { 393 Table 394 name string 395 } 396 397 func (t dummyTable) Name() string { return t.name } 398 399 type loadDriver struct { 400 indexes []DriverIndex 401 id string 402 } 403 404 func (d loadDriver) ID() string { return d.id } 405 func (loadDriver) Create(db, table, id string, expressions []Expression, config map[string]string) (DriverIndex, error) { 406 panic("create is a placeholder") 407 } 408 func (d loadDriver) LoadAll(ctx *Context, db, table string) ([]DriverIndex, error) { 409 var result []DriverIndex 410 for _, i := range d.indexes { 411 if i.Table() == table && i.Database() == db { 412 result = append(result, i) 413 } 414 } 415 return result, nil 416 } 417 func (loadDriver) Save(ctx *Context, index DriverIndex, iter PartitionIndexKeyValueIter) error { 418 return nil 419 } 420 func (loadDriver) Delete(DriverIndex, PartitionIter) error { return nil } 421 422 type dummyIdx struct { 423 id string 424 expr []Expression 425 database string 426 table string 427 } 428 429 var _ DriverIndex = (*dummyIdx)(nil) 430 431 func (i dummyIdx) CanSupport(r ...Range) bool { 432 return false 433 } 434 435 func (i dummyIdx) Expressions() []string { 436 var exprs []string 437 for _, e := range i.expr { 438 exprs = append(exprs, e.String()) 439 } 440 return exprs 441 } 442 func (i dummyIdx) ID() string { return i.id } 443 func (i dummyIdx) Database() string { return i.database } 444 func (i dummyIdx) Table() string { return i.table } 445 func (i dummyIdx) Driver() string { return "dummy" } 446 func (i dummyIdx) IsUnique() bool { return false } 447 func (i dummyIdx) IsSpatial() bool { return false } 448 func (i dummyIdx) IsFullText() bool { return false } 449 func (i dummyIdx) Comment() string { return "" } 450 func (i dummyIdx) IsGenerated() bool { return false } 451 func (i dummyIdx) IndexType() string { return "BTREE" } 452 func (i dummyIdx) PrefixLengths() []uint16 { return nil } 453 454 func (i dummyIdx) NewLookup(ctx *Context, ranges ...Range) (IndexLookup, error) { 455 panic("not implemented") 456 } 457 func (i dummyIdx) ColumnExpressionTypes() []ColumnExpressionType { 458 panic("not implemented") 459 } 460 461 type dummyExpr struct { 462 index int 463 colName string 464 } 465 466 var _ Expression = (*dummyExpr)(nil) 467 var _ CollationCoercible = (*dummyExpr)(nil) 468 469 func (dummyExpr) Children() []Expression { return nil } 470 func (dummyExpr) Eval(*Context, Row) (interface{}, error) { panic("not implemented") } 471 func (e dummyExpr) WithChildren(children ...Expression) (Expression, error) { 472 return e, nil 473 } 474 func (e dummyExpr) String() string { return fmt.Sprintf("dummyExpr{%d, %s}", e.index, e.colName) } 475 func (dummyExpr) IsNullable() bool { return false } 476 func (dummyExpr) Resolved() bool { return false } 477 func (dummyExpr) Type() Type { panic("not implemented") } 478 func (e dummyExpr) WithIndex(idx int) Expression { 479 return &dummyExpr{idx, e.colName} 480 } 481 func (dummyExpr) CollationCoercibility(ctx *Context) (collation CollationID, coercibility byte) { 482 return Collation_binary, 7 483 } 484 485 type checksumTable struct { 486 Table 487 checksum string 488 } 489 490 func (t *checksumTable) Checksum() (string, error) { 491 return t.checksum, nil 492 } 493 494 type checksumIndex struct { 495 DriverIndex 496 checksum string 497 } 498 499 func (idx *checksumIndex) Checksum() (string, error) { 500 return idx.checksum, nil 501 }