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  }