github.com/treeverse/lakefs@v1.24.1-0.20240520134607-95648127bfb0/pkg/kv/kvtest/iterators.go (about) 1 package kvtest 2 3 import ( 4 "context" 5 "errors" 6 "sync/atomic" 7 "testing" 8 9 "github.com/go-test/deep" 10 "github.com/treeverse/lakefs/pkg/graveler" 11 "github.com/treeverse/lakefs/pkg/kv" 12 ) 13 14 const ( 15 firstPartitionKey = "m" 16 secondPartitionKey = "ma" 17 ) 18 19 type StoreWithCounter struct { 20 kv.Store 21 ScanCalls int64 22 } 23 24 // MaxPageSize is the maximum page size for pagination tests 25 const MaxPageSize = 10 26 27 func NewStoreWithCounter(store kv.Store) *StoreWithCounter { 28 return &StoreWithCounter{Store: store} 29 } 30 31 func (swc *StoreWithCounter) Scan(ctx context.Context, partitionKey []byte, options kv.ScanOptions) (kv.EntriesIterator, error) { 32 atomic.AddInt64(&swc.ScanCalls, 1) 33 return swc.Store.Scan(ctx, partitionKey, options) 34 } 35 36 func testPartitionIterator(t *testing.T, ms MakeStore) { 37 ctx := context.Background() 38 store := ms(t, ctx) 39 40 // prepare data 41 modelNames := []string{"a", "aa", "b", "c", "d"} 42 for _, name := range modelNames { 43 model := TestModel{Name: []byte(name)} 44 for _, partitionKey := range []string{firstPartitionKey, secondPartitionKey} { 45 err := kv.SetMsg(ctx, store, partitionKey, model.Name, &model) 46 if err != nil { 47 t.Fatalf("failed to set model (partition %s, name %s): %s", partitionKey, name, err) 48 } 49 } 50 } 51 52 t.Run("listing all values of partition", testPartitionIteratorListAll(ctx, store)) 53 t.Run("listing values SeekGE", listPartitionIteratorWithSeekGE(ctx, store)) 54 t.Run("count scans on successive SeekGE operations", testPartitionIteratorCountScansOnSeekGE(store, ctx)) 55 t.Run("failed SeekGE partition not found", testPartitionIteratorSeekGEOnPartitionNotFound(ctx, store)) 56 t.Run("SeekGE past end", testPartitionIteratorSeekGEPastEnd(ctx, store)) 57 t.Run("SeekGE seek back", testPartitionIteratorSeekGESeekBack(ctx, store)) 58 t.Run("listing values SeekGE after pagination", testPartitionIteratorSeekGEWithPagination(ctx, store)) 59 } 60 61 func testPartitionIteratorSeekGEWithPagination(ctx context.Context, store kv.Store) func(t *testing.T) { 62 return func(t *testing.T) { 63 // load much more data 64 moreModelNames := []string{ 65 "da", "db", "dc", "dd", "de", "df", "dg", "dh", "di", "dj", 66 "dk", "dl", "dm", "dn", "do", "dp", "dq", "dr", "ds", "dt", 67 "du", "dv", "dw", "dx", "dy", "dz", 68 "z", 69 } 70 for _, name := range moreModelNames { 71 model := TestModel{Name: []byte(name)} 72 for _, partitionKey := range []string{firstPartitionKey, secondPartitionKey} { 73 err := kv.SetMsg(ctx, store, partitionKey, model.Name, &model) 74 if err != nil { 75 t.Fatalf("failed to set model (partition %s, name %s): %s", partitionKey, name, err) 76 } 77 } 78 } 79 80 itr := kv.NewPartitionIterator(ctx, store, (&TestModel{}).ProtoReflect().Type(), secondPartitionKey, 0) 81 if itr == nil { 82 t.Fatalf("failed to create partition iterator") 83 } 84 defer itr.Close() 85 86 if !itr.Next() { 87 t.Fatal("expected Next to be true") 88 } 89 90 itr.SeekGE([]byte("b")) 91 for i := 0; i < MaxPageSize+1; i++ { 92 // force pagination 93 if !itr.Next() { 94 t.Fatal("expected Next to be true") 95 } 96 } 97 98 itr.SeekGE([]byte("z")) 99 if !itr.Next() { 100 t.Fatal("expected Next to be true") 101 } 102 103 itr.SeekGE([]byte("d1")) 104 names := scanPartitionIterator(t, itr, func(_ []byte, model *TestModel) string { return string(model.Name) }) 105 if diffs := deep.Equal(names, moreModelNames); diffs != nil { 106 t.Fatalf("got wrong list of names: %v", diffs) 107 } 108 } 109 } 110 111 func testPartitionIteratorSeekGESeekBack(ctx context.Context, store kv.Store) func(t *testing.T) { 112 return func(t *testing.T) { 113 itr := kv.NewPartitionIterator(ctx, store, (&TestModel{}).ProtoReflect().Type(), firstPartitionKey, 0) 114 if itr == nil { 115 t.Fatalf("failed to create partition iterator") 116 } 117 defer itr.Close() 118 itr.SeekGE([]byte("z")) 119 if itr.Next() { 120 t.Fatal("expected Next to be false") 121 } 122 if err := itr.Err(); err != nil { 123 t.Fatalf("unexpected error: %s", err) 124 } 125 itr.SeekGE([]byte("a")) 126 if !itr.Next() { 127 t.Fatalf("expected Next to be true") 128 } 129 if err := itr.Err(); err != nil { 130 t.Fatalf("unexpected error: %s", err) 131 } 132 e := itr.Entry() 133 model := e.Value.(*TestModel) 134 if string(model.Name) != "a" { 135 t.Fatalf("expected value a from iterator") 136 } 137 } 138 } 139 140 func testPartitionIteratorSeekGEPastEnd(ctx context.Context, store kv.Store) func(t *testing.T) { 141 return func(t *testing.T) { 142 itr := kv.NewPartitionIterator(ctx, store, (&TestModel{}).ProtoReflect().Type(), firstPartitionKey, 0) 143 if itr == nil { 144 t.Fatalf("failed to create partition iterator") 145 } 146 defer itr.Close() 147 itr.SeekGE([]byte("b")) 148 if !itr.Next() { 149 t.Fatal("expected Next to be true") 150 } 151 e := itr.Entry() 152 model := e.Value.(*TestModel) 153 if string(model.Name) != "b" { 154 t.Fatalf("expected value b from iterator") 155 } 156 itr.SeekGE(graveler.UpperBoundForPrefix([]byte("d1"))) 157 if itr.Next() { 158 t.Fatalf("expected Next to be false") 159 } 160 if err := itr.Err(); err != nil { 161 t.Fatalf("unexpected error: %v", err) 162 } 163 } 164 } 165 166 func testPartitionIteratorSeekGEOnPartitionNotFound(ctx context.Context, store kv.Store) func(t *testing.T) { 167 return func(t *testing.T) { 168 itr := kv.NewPartitionIterator(ctx, store, (&TestModel{}).ProtoReflect().Type(), "", 0) 169 if itr == nil { 170 t.Fatalf("failed to create partition iterator") 171 } 172 defer itr.Close() 173 itr.SeekGE([]byte("d")) 174 if itr.Next() { 175 t.Fatalf("next after seekGE expected to be false") 176 } 177 178 itr.SeekGE([]byte("d")) 179 if itr.Next() { 180 t.Fatalf("next after seekGE expected to be false") 181 } 182 183 if err := itr.Err(); !errors.Is(err, kv.ErrMissingPartitionKey) { 184 t.Fatalf("expected error: %s, got %v", kv.ErrMissingPartitionKey, err) 185 } 186 } 187 } 188 189 func testPartitionIteratorCountScansOnSeekGE(store kv.Store, ctx context.Context) func(t *testing.T) { 190 return func(t *testing.T) { 191 store := NewStoreWithCounter(store) 192 itr := kv.NewPartitionIterator(ctx, store, (&TestModel{}).ProtoReflect().Type(), secondPartitionKey, 0) 193 if itr == nil { 194 t.Fatalf("failed to create partition iterator") 195 } 196 defer itr.Close() 197 for _, seekValue := range []string{"b", "c", "d"} { 198 itr.SeekGE([]byte(seekValue)) 199 if !itr.Next() { 200 t.Fatalf("Expected iterator to have a value") 201 } 202 if err := itr.Err(); err != nil { 203 t.Fatalf("unexpected error: %v", err) 204 } 205 k := itr.Entry().Key 206 if string(k) != seekValue { 207 t.Fatalf("Expected to find value %s. Found %s", seekValue, k) 208 } 209 } 210 scanCalls := atomic.LoadInt64(&store.ScanCalls) 211 if scanCalls != 1 { 212 t.Fatalf("Expected exactly 1 call to Scan. got: %d", scanCalls) 213 } 214 } 215 } 216 217 func listPartitionIteratorWithSeekGE(ctx context.Context, store kv.Store) func(t *testing.T) { 218 return func(t *testing.T) { 219 itr := kv.NewPartitionIterator(ctx, store, (&TestModel{}).ProtoReflect().Type(), secondPartitionKey, 0) 220 if itr == nil { 221 t.Fatalf("failed to create partition iterator") 222 } 223 defer itr.Close() 224 for _, seekValue := range []string{"b", "aaa", "b"} { 225 itr.SeekGE([]byte(seekValue)) 226 names := scanPartitionIterator(t, itr, func(_ []byte, model *TestModel) string { return string(model.Name) }) 227 if diffs := deep.Equal(names, []string{"b", "c", "d"}); diffs != nil { 228 t.Fatalf("got wrong list of names: %v", diffs) 229 } 230 } 231 } 232 } 233 234 func testPartitionIteratorListAll(ctx context.Context, store kv.Store) func(t *testing.T) { 235 return func(t *testing.T) { 236 itr := kv.NewPartitionIterator(ctx, store, (&TestModel{}).ProtoReflect().Type(), firstPartitionKey, 0) 237 if itr == nil { 238 t.Fatalf("failed to create partition iterator") 239 } 240 defer itr.Close() 241 names := scanPartitionIterator(t, itr, func(_ []byte, model *TestModel) string { return string(model.Name) }) 242 if diffs := deep.Equal(names, []string{"a", "aa", "b", "c", "d"}); diffs != nil { 243 t.Fatalf("got wrong list of names: %v", diffs) 244 } 245 } 246 } 247 248 // scanPartitionIterator scans the iterator and returns a slice of the results of applying fn to each model. 249 // slice element type is based on the callback 'fn' function return type. 250 func scanPartitionIterator[E any](t *testing.T, itr kv.MessageIterator, fn func(key []byte, model *TestModel) E) []E { 251 t.Helper() 252 result := make([]E, 0) 253 for itr.Next() { 254 e := itr.Entry() 255 model := e.Value.(*TestModel) 256 result = append(result, fn(e.Key, model)) 257 } 258 if err := itr.Err(); err != nil { 259 t.Fatalf("While scan partition iterator unexpected error: %v", err) 260 } 261 return result 262 } 263 264 func testPrimaryIterator(t *testing.T, ms MakeStore) { 265 ctx := context.Background() 266 store := ms(t, ctx) 267 268 // prepare data 269 modelNames := []string{"a", "aa", "b", "c", "d"} 270 for _, name := range modelNames { 271 model := TestModel{Name: []byte(name)} 272 for _, partitionKey := range []string{firstPartitionKey, secondPartitionKey} { 273 err := kv.SetMsg(ctx, store, partitionKey, model.Name, &model) 274 if err != nil { 275 t.Fatalf("failed to set model (partition %s, name %s): %s", partitionKey, name, err) 276 } 277 } 278 } 279 280 t.Run("listing all values of partition", func(t *testing.T) { 281 itr, err := kv.NewPrimaryIterator(ctx, store, (&TestModel{}).ProtoReflect().Type(), 282 firstPartitionKey, []byte(""), kv.IteratorOptionsFrom([]byte(""))) 283 if err != nil { 284 t.Fatalf("failed to create primary iterator: %v", err) 285 } 286 defer itr.Close() 287 names := scanPartitionIterator(t, itr, func(_ []byte, model *TestModel) string { return string(model.Name) }) 288 if diffs := deep.Equal(names, []string{"a", "aa", "b", "c", "d"}); diffs != nil { 289 t.Fatalf("got wrong list of names: %v", diffs) 290 } 291 }) 292 293 t.Run("listing with prefix", func(t *testing.T) { 294 itr, err := kv.NewPrimaryIterator(ctx, store, (&TestModel{}).ProtoReflect().Type(), 295 secondPartitionKey, []byte("a"), kv.IteratorOptionsFrom([]byte(""))) 296 if err != nil { 297 t.Fatalf("failed to create primary iterator: %v", err) 298 } 299 defer itr.Close() 300 names := scanPartitionIterator(t, itr, func(_ []byte, model *TestModel) string { return string(model.Name) }) 301 if diffs := deep.Equal(names, []string{"a", "aa"}); diffs != nil { 302 t.Fatalf("got wrong list of names: %v", diffs) 303 } 304 }) 305 306 t.Run("listing empty value", func(t *testing.T) { 307 itr, err := kv.NewPrimaryIterator(ctx, store, (&TestModel{}).ProtoReflect().Type(), 308 secondPartitionKey, []byte(""), kv.IteratorOptionsAfter([]byte("d"))) 309 if err != nil { 310 t.Fatalf("failed to create primary iterator: %v", err) 311 } 312 defer itr.Close() 313 314 if itr.Next() { 315 t.Fatalf("next should return false") 316 } 317 if err := itr.Err(); err != nil { 318 t.Fatalf("unexpected error: %v", err) 319 } 320 }) 321 322 t.Run("listing from", func(t *testing.T) { 323 itr, err := kv.NewPrimaryIterator(ctx, store, (&TestModel{}).ProtoReflect().Type(), 324 firstPartitionKey, []byte(""), kv.IteratorOptionsFrom([]byte("b"))) 325 if err != nil { 326 t.Fatalf("failed to create primary iterator: %v", err) 327 } 328 defer itr.Close() 329 names := scanPartitionIterator(t, itr, func(_ []byte, model *TestModel) string { return string(model.Name) }) 330 if diffs := deep.Equal(names, []string{"b", "c", "d"}); diffs != nil { 331 t.Fatalf("got wrong list of names: %v", diffs) 332 } 333 }) 334 335 t.Run("listing after", func(t *testing.T) { 336 itr, err := kv.NewPrimaryIterator(ctx, store, (&TestModel{}).ProtoReflect().Type(), 337 firstPartitionKey, []byte(""), kv.IteratorOptionsAfter([]byte("b"))) 338 if err != nil { 339 t.Fatalf("failed to create primary iterator: %v", err) 340 } 341 defer itr.Close() 342 names := scanPartitionIterator(t, itr, func(_ []byte, model *TestModel) string { return string(model.Name) }) 343 if diffs := deep.Equal(names, []string{"c", "d"}); diffs != nil { 344 t.Fatalf("got wrong list of names: %v", diffs) 345 } 346 }) 347 } 348 349 func testSecondaryIterator(t *testing.T, ms MakeStore) { 350 ctx := context.Background() 351 store := ms(t, ctx) 352 353 // prepare data 354 m := []TestModel{ 355 {Name: []byte("a"), JustAString: "one"}, 356 {Name: []byte("b"), JustAString: "two"}, 357 {Name: []byte("c"), JustAString: "three"}, 358 {Name: []byte("d"), JustAString: "four"}, 359 {Name: []byte("e"), JustAString: "five"}, 360 } 361 for i := 0; i < len(m); i++ { 362 primaryKey := append([]byte("name/"), m[i].Name...) 363 err := kv.SetMsg(ctx, store, firstPartitionKey, primaryKey, &m[i]) 364 if err != nil { 365 t.Fatal("failed to set model (primary key)", err) 366 } 367 secondaryKey := append([]byte("just_a_string/"), m[i].JustAString...) 368 err = kv.SetMsg(ctx, store, firstPartitionKey, secondaryKey, &kv.SecondaryIndex{PrimaryKey: primaryKey}) 369 if err != nil { 370 t.Fatal("failed to set model (secondary key)", err) 371 } 372 } 373 err := kv.SetMsg(ctx, store, secondPartitionKey, []byte("just_a_string/e"), &kv.SecondaryIndex{}) 374 if err != nil { 375 t.Fatal("failed to set key", err) 376 } 377 err = kv.SetMsg(ctx, store, secondPartitionKey, []byte("just_a_string/f"), &kv.SecondaryIndex{PrimaryKey: []byte("name/no-name")}) 378 if err != nil { 379 t.Fatal("failed to set model", err) 380 } 381 382 t.Run("listing all values of partition", func(t *testing.T) { 383 itr, err := kv.NewSecondaryIterator(ctx, store, (&TestModel{}).ProtoReflect().Type(), 384 firstPartitionKey, []byte("just_a_string/"), []byte("")) 385 if err != nil { 386 t.Fatalf("failed to create secondary iterator: %v", err) 387 } 388 defer itr.Close() 389 390 type KeyNameValue struct { 391 Key []byte 392 Name []byte 393 Value string 394 } 395 msgs := scanPartitionIterator(t, itr, func(key []byte, model *TestModel) KeyNameValue { 396 return KeyNameValue{ 397 Key: key, 398 Name: model.Name, 399 Value: model.JustAString, 400 } 401 }) 402 expectedMsgs := []KeyNameValue{ 403 {Key: []byte("just_a_string/five"), Name: []byte("e"), Value: "five"}, 404 {Key: []byte("just_a_string/four"), Name: []byte("d"), Value: "four"}, 405 {Key: []byte("just_a_string/one"), Name: []byte("a"), Value: "one"}, 406 {Key: []byte("just_a_string/three"), Name: []byte("c"), Value: "three"}, 407 {Key: []byte("just_a_string/two"), Name: []byte("b"), Value: "two"}, 408 } 409 if diffs := deep.Equal(msgs, expectedMsgs); diffs != nil { 410 t.Fatalf("Diff expected result: %v", diffs) 411 } 412 }) 413 414 t.Run("listing with prefix", func(t *testing.T) { 415 itr, err := kv.NewSecondaryIterator(ctx, store, (&TestModel{}).ProtoReflect().Type(), 416 firstPartitionKey, []byte("just_a_string/f"), []byte("")) 417 if err != nil { 418 t.Fatalf("failed to create secondary iterator: %v", err) 419 } 420 defer itr.Close() 421 var msgs []*TestModel 422 for itr.Next() { 423 e := itr.Entry() 424 model := e.Value.(*TestModel) 425 msgs = append(msgs, model) 426 } 427 if err := itr.Err(); err != nil { 428 t.Fatalf("unexpected error: %v", err) 429 } 430 431 expectedMsgs := []*TestModel{ 432 {Name: []byte("e"), JustAString: "five"}, 433 {Name: []byte("d"), JustAString: "four"}, 434 } 435 if diffs := deep.Equal(msgs, expectedMsgs); diffs != nil { 436 t.Fatalf("Diff expected result: %v", diffs) 437 } 438 }) 439 440 t.Run("listing after", func(t *testing.T) { 441 itr, err := kv.NewSecondaryIterator(ctx, store, (&TestModel{}).ProtoReflect().Type(), 442 firstPartitionKey, []byte("just_a_string/"), []byte("just_a_string/four")) 443 if err != nil { 444 t.Fatalf("failed to create secondary iterator: %v", err) 445 } 446 defer itr.Close() 447 var msgs []*TestModel 448 for itr.Next() { 449 e := itr.Entry() 450 model := e.Value.(*TestModel) 451 msgs = append(msgs, model) 452 } 453 if err := itr.Err(); err != nil { 454 t.Fatalf("unexpected error: %v", err) 455 } 456 457 expectedMsgs := []*TestModel{ 458 {Name: []byte("a"), JustAString: "one"}, 459 {Name: []byte("c"), JustAString: "three"}, 460 {Name: []byte("b"), JustAString: "two"}, 461 } 462 if diffs := deep.Equal(msgs, expectedMsgs); diffs != nil { 463 t.Fatalf("Diff expected result: %v", diffs) 464 } 465 }) 466 467 t.Run("key not found", func(t *testing.T) { 468 itr, err := kv.NewSecondaryIterator(ctx, store, (&TestModel{}).ProtoReflect().Type(), 469 secondPartitionKey, []byte("just_a_string/e"), []byte("")) 470 if err != nil { 471 t.Fatalf("failed to create secondary iterator: %v", err) 472 } 473 defer itr.Close() 474 475 if itr.Next() { 476 t.Fatalf("Next() should return false") 477 } 478 479 expectedErr := kv.ErrMissingKey 480 if err := itr.Err(); !errors.Is(err, expectedErr) { 481 t.Fatalf("expected error: %s, got %v", expectedErr, err) 482 } 483 }) 484 485 t.Run("value not found", func(t *testing.T) { 486 itr, err := kv.NewSecondaryIterator(ctx, store, (&TestModel{}).ProtoReflect().Type(), 487 secondPartitionKey, []byte("just_a_string/f"), []byte("")) 488 if err != nil { 489 t.Fatalf("failed to create secondary iterator: %v", err) 490 } 491 defer itr.Close() 492 if itr.Next() { 493 t.Fatalf("next should return false") 494 } 495 if err := itr.Err(); err != nil { 496 t.Fatalf("unexpected error: %v", err) 497 } 498 }) 499 }