github.com/cosmos/cosmos-sdk@v0.50.10/x/group/internal/orm/index_test.go (about)

     1  package orm
     2  
     3  import (
     4  	"testing"
     5  
     6  	"github.com/stretchr/testify/assert"
     7  	"github.com/stretchr/testify/require"
     8  
     9  	errorsmod "cosmossdk.io/errors"
    10  	storetypes "cosmossdk.io/store/types"
    11  
    12  	"github.com/cosmos/cosmos-sdk/codec"
    13  	"github.com/cosmos/cosmos-sdk/codec/types"
    14  	"github.com/cosmos/cosmos-sdk/testutil/testdata"
    15  	"github.com/cosmos/cosmos-sdk/types/query"
    16  	"github.com/cosmos/cosmos-sdk/x/group/errors"
    17  )
    18  
    19  var _ Indexable = &nilRowGetterBuilder{}
    20  
    21  type nilRowGetterBuilder struct{}
    22  
    23  func (b *nilRowGetterBuilder) RowGetter() RowGetter {
    24  	return nil
    25  }
    26  func (b *nilRowGetterBuilder) AddAfterSetInterceptor(AfterSetInterceptor)       {}
    27  func (b *nilRowGetterBuilder) AddAfterDeleteInterceptor(AfterDeleteInterceptor) {}
    28  
    29  func TestNewIndex(t *testing.T) {
    30  	interfaceRegistry := types.NewInterfaceRegistry()
    31  	cdc := codec.NewProtoCodec(interfaceRegistry)
    32  
    33  	myTable, err := NewAutoUInt64Table(AutoUInt64TablePrefix, AutoUInt64TableSeqPrefix, &testdata.TableModel{}, cdc)
    34  	require.NoError(t, err)
    35  	indexer := func(val interface{}) ([]interface{}, error) {
    36  		return []interface{}{val.(*testdata.TableModel).Metadata}, nil
    37  	}
    38  
    39  	testCases := []struct {
    40  		name        string
    41  		table       Indexable
    42  		expectErr   bool
    43  		expectedErr string
    44  		indexKey    interface{}
    45  	}{
    46  		{
    47  			name:        "nil indexKey",
    48  			table:       myTable,
    49  			expectErr:   true,
    50  			expectedErr: "indexKey must not be nil",
    51  			indexKey:    nil,
    52  		},
    53  		{
    54  			name:        "nil rowGetter",
    55  			table:       &nilRowGetterBuilder{},
    56  			expectErr:   true,
    57  			expectedErr: "rowGetter must not be nil",
    58  			indexKey:    []byte{},
    59  		},
    60  		{
    61  			name:      "all not nil",
    62  			table:     myTable,
    63  			expectErr: false,
    64  			indexKey:  []byte{},
    65  		},
    66  		{
    67  			name:      "index key type not allowed",
    68  			table:     myTable,
    69  			expectErr: true,
    70  			indexKey:  1,
    71  		},
    72  	}
    73  	for _, tc := range testCases {
    74  		t.Run(tc.name, func(t *testing.T) {
    75  			index, err := NewIndex(tc.table, AutoUInt64TableSeqPrefix, indexer, tc.indexKey)
    76  			if tc.expectErr {
    77  				require.Error(t, err)
    78  				require.Contains(t, err.Error(), tc.expectedErr)
    79  			} else {
    80  				require.NoError(t, err)
    81  				require.NotEmpty(t, index)
    82  			}
    83  		})
    84  	}
    85  }
    86  
    87  func TestIndexPrefixScan(t *testing.T) {
    88  	interfaceRegistry := types.NewInterfaceRegistry()
    89  	cdc := codec.NewProtoCodec(interfaceRegistry)
    90  
    91  	tb, err := NewAutoUInt64Table(AutoUInt64TablePrefix, AutoUInt64TableSeqPrefix, &testdata.TableModel{}, cdc)
    92  	require.NoError(t, err)
    93  	idx, err := NewIndex(tb, AutoUInt64TableModelByMetadataPrefix, func(val interface{}) ([]interface{}, error) {
    94  		i := []interface{}{val.(*testdata.TableModel).Metadata}
    95  		return i, nil
    96  	}, testdata.TableModel{}.Metadata)
    97  	require.NoError(t, err)
    98  	strIdx, err := NewIndex(tb, 0x1, func(val interface{}) ([]interface{}, error) {
    99  		i := []interface{}{val.(*testdata.TableModel).Name}
   100  		return i, nil
   101  	}, testdata.TableModel{}.Name)
   102  	require.NoError(t, err)
   103  
   104  	ctx := NewMockContext()
   105  	store := ctx.KVStore(storetypes.NewKVStoreKey("test"))
   106  
   107  	g1 := testdata.TableModel{
   108  		Id:       1,
   109  		Name:     "my test 1",
   110  		Metadata: []byte("metadata-a"),
   111  	}
   112  	g2 := testdata.TableModel{
   113  		Id:       2,
   114  		Name:     "my test 2",
   115  		Metadata: []byte("metadata-b"),
   116  	}
   117  	g3 := testdata.TableModel{
   118  		Id:       3,
   119  		Name:     "my test 3",
   120  		Metadata: []byte("metadata-b"),
   121  	}
   122  	for _, g := range []testdata.TableModel{g1, g2, g3} {
   123  		g := g
   124  		_, err := tb.Create(store, &g)
   125  		require.NoError(t, err)
   126  	}
   127  
   128  	specs := map[string]struct {
   129  		start, end interface{}
   130  		expResult  []testdata.TableModel
   131  		expRowIDs  []RowID
   132  		expError   *errorsmod.Error
   133  		method     func(store storetypes.KVStore, start, end interface{}) (Iterator, error)
   134  	}{
   135  		"exact match with a single result": {
   136  			start:     []byte("metadata-a"),
   137  			end:       []byte("metadata-b"),
   138  			method:    idx.PrefixScan,
   139  			expResult: []testdata.TableModel{g1},
   140  			expRowIDs: []RowID{EncodeSequence(1)},
   141  		},
   142  		"one result by prefix": {
   143  			start:     []byte("metadata"),
   144  			end:       []byte("metadata-b"),
   145  			method:    idx.PrefixScan,
   146  			expResult: []testdata.TableModel{g1},
   147  			expRowIDs: []RowID{EncodeSequence(1)},
   148  		},
   149  		"multi key elements by exact match": {
   150  			start:     []byte("metadata-b"),
   151  			end:       []byte("metadata-c"),
   152  			method:    idx.PrefixScan,
   153  			expResult: []testdata.TableModel{g2, g3},
   154  			expRowIDs: []RowID{EncodeSequence(2), EncodeSequence(3)},
   155  		},
   156  		"open end query": {
   157  			start:     []byte("metadata-b"),
   158  			end:       nil,
   159  			method:    idx.PrefixScan,
   160  			expResult: []testdata.TableModel{g2, g3},
   161  			expRowIDs: []RowID{EncodeSequence(2), EncodeSequence(3)},
   162  		},
   163  		"open start query": {
   164  			start:     nil,
   165  			end:       []byte("metadata-b"),
   166  			method:    idx.PrefixScan,
   167  			expResult: []testdata.TableModel{g1},
   168  			expRowIDs: []RowID{EncodeSequence(1)},
   169  		},
   170  		"open start and end query": {
   171  			start:     nil,
   172  			end:       nil,
   173  			method:    idx.PrefixScan,
   174  			expResult: []testdata.TableModel{g1, g2, g3},
   175  			expRowIDs: []RowID{EncodeSequence(1), EncodeSequence(2), EncodeSequence(3)},
   176  		},
   177  		"all matching prefix": {
   178  			start:     []byte("admin"),
   179  			end:       nil,
   180  			method:    idx.PrefixScan,
   181  			expResult: []testdata.TableModel{g1, g2, g3},
   182  			expRowIDs: []RowID{EncodeSequence(1), EncodeSequence(2), EncodeSequence(3)},
   183  		},
   184  		"non matching prefix": {
   185  			start:     []byte("metadata-c"),
   186  			end:       nil,
   187  			method:    idx.PrefixScan,
   188  			expResult: []testdata.TableModel{},
   189  		},
   190  		"start equals end": {
   191  			start:    []byte("any"),
   192  			end:      []byte("any"),
   193  			method:   idx.PrefixScan,
   194  			expError: errors.ErrORMInvalidArgument,
   195  		},
   196  		"start after end": {
   197  			start:    []byte("b"),
   198  			end:      []byte("a"),
   199  			method:   idx.PrefixScan,
   200  			expError: errors.ErrORMInvalidArgument,
   201  		},
   202  		"reverse: exact match with a single result": {
   203  			start:     []byte("metadata-a"),
   204  			end:       []byte("metadata-b"),
   205  			method:    idx.ReversePrefixScan,
   206  			expResult: []testdata.TableModel{g1},
   207  			expRowIDs: []RowID{EncodeSequence(1)},
   208  		},
   209  		"reverse: one result by prefix": {
   210  			start:     []byte("metadata"),
   211  			end:       []byte("metadata-b"),
   212  			method:    idx.ReversePrefixScan,
   213  			expResult: []testdata.TableModel{g1},
   214  			expRowIDs: []RowID{EncodeSequence(1)},
   215  		},
   216  		"reverse: multi key elements by exact match": {
   217  			start:     []byte("metadata-b"),
   218  			end:       []byte("metadata-c"),
   219  			method:    idx.ReversePrefixScan,
   220  			expResult: []testdata.TableModel{g3, g2},
   221  			expRowIDs: []RowID{EncodeSequence(3), EncodeSequence(2)},
   222  		},
   223  		"reverse: open end query": {
   224  			start:     []byte("metadata-b"),
   225  			end:       nil,
   226  			method:    idx.ReversePrefixScan,
   227  			expResult: []testdata.TableModel{g3, g2},
   228  			expRowIDs: []RowID{EncodeSequence(3), EncodeSequence(2)},
   229  		},
   230  		"reverse: open start query": {
   231  			start:     nil,
   232  			end:       []byte("metadata-b"),
   233  			method:    idx.ReversePrefixScan,
   234  			expResult: []testdata.TableModel{g1},
   235  			expRowIDs: []RowID{EncodeSequence(1)},
   236  		},
   237  		"reverse: open start and end query": {
   238  			start:     nil,
   239  			end:       nil,
   240  			method:    idx.ReversePrefixScan,
   241  			expResult: []testdata.TableModel{g3, g2, g1},
   242  			expRowIDs: []RowID{EncodeSequence(3), EncodeSequence(2), EncodeSequence(1)},
   243  		},
   244  		"reverse: all matching prefix": {
   245  			start:     []byte("admin"),
   246  			end:       nil,
   247  			method:    idx.ReversePrefixScan,
   248  			expResult: []testdata.TableModel{g3, g2, g1},
   249  			expRowIDs: []RowID{EncodeSequence(3), EncodeSequence(2), EncodeSequence(1)},
   250  		},
   251  		"reverse: non matching prefix": {
   252  			start:     []byte("metadata-c"),
   253  			end:       nil,
   254  			method:    idx.ReversePrefixScan,
   255  			expResult: []testdata.TableModel{},
   256  		},
   257  		"reverse: start equals end": {
   258  			start:    []byte("any"),
   259  			end:      []byte("any"),
   260  			method:   idx.ReversePrefixScan,
   261  			expError: errors.ErrORMInvalidArgument,
   262  		},
   263  		"reverse: start after end": {
   264  			start:    []byte("b"),
   265  			end:      []byte("a"),
   266  			method:   idx.ReversePrefixScan,
   267  			expError: errors.ErrORMInvalidArgument,
   268  		},
   269  		"exact match with a single result using string based index": {
   270  			start:     "my test 1",
   271  			end:       "my test 2",
   272  			method:    strIdx.PrefixScan,
   273  			expResult: []testdata.TableModel{g1},
   274  			expRowIDs: []RowID{EncodeSequence(1)},
   275  		},
   276  	}
   277  	for msg, spec := range specs {
   278  		t.Run(msg, func(t *testing.T) {
   279  			it, err := spec.method(store, spec.start, spec.end)
   280  			require.True(t, spec.expError.Is(err), "expected #+v but got #+v", spec.expError, err)
   281  			if spec.expError != nil {
   282  				return
   283  			}
   284  			var loaded []testdata.TableModel
   285  			rowIDs, err := ReadAll(it, &loaded)
   286  			require.NoError(t, err)
   287  			assert.Equal(t, spec.expResult, loaded)
   288  			assert.Equal(t, spec.expRowIDs, rowIDs)
   289  		})
   290  	}
   291  }
   292  
   293  func TestUniqueIndex(t *testing.T) {
   294  	interfaceRegistry := types.NewInterfaceRegistry()
   295  	cdc := codec.NewProtoCodec(interfaceRegistry)
   296  
   297  	myTable, err := NewPrimaryKeyTable(PrimaryKeyTablePrefix, &testdata.TableModel{}, cdc)
   298  	require.NoError(t, err)
   299  	uniqueIdx, err := NewUniqueIndex(myTable, 0x10, func(val interface{}) (interface{}, error) {
   300  		return []byte{val.(*testdata.TableModel).Metadata[0]}, nil
   301  	}, []byte{})
   302  	require.NoError(t, err)
   303  
   304  	ctx := NewMockContext()
   305  	store := ctx.KVStore(storetypes.NewKVStoreKey("test"))
   306  
   307  	m := testdata.TableModel{
   308  		Id:       1,
   309  		Name:     "my test",
   310  		Metadata: []byte("metadata"),
   311  	}
   312  	err = myTable.Create(store, &m)
   313  	require.NoError(t, err)
   314  
   315  	indexedKey := []byte{'m'}
   316  
   317  	// Has
   318  	exists, err := uniqueIdx.Has(store, indexedKey)
   319  	require.NoError(t, err)
   320  	assert.True(t, exists)
   321  
   322  	// Get
   323  	it, err := uniqueIdx.Get(store, indexedKey)
   324  	require.NoError(t, err)
   325  	var loaded testdata.TableModel
   326  	rowID, err := it.LoadNext(&loaded)
   327  	require.NoError(t, err)
   328  	require.Equal(t, RowID(PrimaryKey(&m)), rowID)
   329  	require.Equal(t, m, loaded)
   330  
   331  	// GetPaginated
   332  	cases := map[string]struct {
   333  		pageReq *query.PageRequest
   334  		expErr  bool
   335  	}{
   336  		"nil key": {
   337  			pageReq: &query.PageRequest{Key: nil},
   338  			expErr:  false,
   339  		},
   340  		"after indexed key": {
   341  			pageReq: &query.PageRequest{Key: indexedKey},
   342  			expErr:  true,
   343  		},
   344  	}
   345  
   346  	for testName, tc := range cases {
   347  		t.Run(testName, func(t *testing.T) {
   348  			it, err := uniqueIdx.GetPaginated(store, indexedKey, tc.pageReq)
   349  			require.NoError(t, err)
   350  			rowID, err := it.LoadNext(&loaded)
   351  			if tc.expErr { // iterator done
   352  				require.Error(t, err)
   353  			} else {
   354  				require.NoError(t, err)
   355  				require.Equal(t, RowID(PrimaryKey(&m)), rowID)
   356  				require.Equal(t, m, loaded)
   357  			}
   358  		})
   359  	}
   360  
   361  	// PrefixScan match
   362  	it, err = uniqueIdx.PrefixScan(store, indexedKey, nil)
   363  	require.NoError(t, err)
   364  	rowID, err = it.LoadNext(&loaded)
   365  	require.NoError(t, err)
   366  	require.Equal(t, RowID(PrimaryKey(&m)), rowID)
   367  	require.Equal(t, m, loaded)
   368  
   369  	// PrefixScan no match
   370  	it, err = uniqueIdx.PrefixScan(store, []byte{byte('n')}, nil)
   371  	require.NoError(t, err)
   372  	_, err = it.LoadNext(&loaded)
   373  	require.Error(t, errors.ErrORMIteratorDone, err)
   374  
   375  	// ReversePrefixScan match
   376  	it, err = uniqueIdx.ReversePrefixScan(store, indexedKey, nil)
   377  	require.NoError(t, err)
   378  	rowID, err = it.LoadNext(&loaded)
   379  	require.NoError(t, err)
   380  	require.Equal(t, RowID(PrimaryKey(&m)), rowID)
   381  	require.Equal(t, m, loaded)
   382  
   383  	// ReversePrefixScan no match
   384  	it, err = uniqueIdx.ReversePrefixScan(store, []byte{byte('l')}, nil)
   385  	require.NoError(t, err)
   386  	_, err = it.LoadNext(&loaded)
   387  	require.Error(t, errors.ErrORMIteratorDone, err)
   388  	// create with same index key should fail
   389  	new := testdata.TableModel{
   390  		Id:       1,
   391  		Name:     "my test",
   392  		Metadata: []byte("my-metadata"),
   393  	}
   394  	err = myTable.Create(store, &new)
   395  	require.Error(t, errors.ErrORMUniqueConstraint, err)
   396  
   397  	// and when delete
   398  	err = myTable.Delete(store, &m)
   399  	require.NoError(t, err)
   400  
   401  	// then no persistent element
   402  	exists, err = uniqueIdx.Has(store, indexedKey)
   403  	require.NoError(t, err)
   404  	assert.False(t, exists)
   405  }
   406  
   407  func TestPrefixRange(t *testing.T) {
   408  	cases := map[string]struct {
   409  		src      []byte
   410  		expStart []byte
   411  		expEnd   []byte
   412  		expPanic bool
   413  	}{
   414  		"normal":                 {src: []byte{1, 3, 4}, expStart: []byte{1, 3, 4}, expEnd: []byte{1, 3, 5}},
   415  		"normal short":           {src: []byte{79}, expStart: []byte{79}, expEnd: []byte{80}},
   416  		"empty case":             {src: []byte{}},
   417  		"roll-over example 1":    {src: []byte{17, 28, 255}, expStart: []byte{17, 28, 255}, expEnd: []byte{17, 29, 0}},
   418  		"roll-over example 2":    {src: []byte{15, 42, 255, 255}, expStart: []byte{15, 42, 255, 255}, expEnd: []byte{15, 43, 0, 0}},
   419  		"pathological roll-over": {src: []byte{255, 255, 255, 255}, expStart: []byte{255, 255, 255, 255}},
   420  		"nil prohibited":         {expPanic: true},
   421  	}
   422  
   423  	for testName, tc := range cases {
   424  		t.Run(testName, func(t *testing.T) {
   425  			if tc.expPanic {
   426  				require.Panics(t, func() {
   427  					PrefixRange(tc.src)
   428  				})
   429  				return
   430  			}
   431  			start, end := PrefixRange(tc.src)
   432  			assert.Equal(t, tc.expStart, start)
   433  			assert.Equal(t, tc.expEnd, end)
   434  		})
   435  	}
   436  }