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 }