github.com/cilium/statedb@v0.3.2/db_test.go (about) 1 // SPDX-License-Identifier: Apache-2.0 2 // Copyright Authors of Cilium 3 4 package statedb 5 6 import ( 7 "bytes" 8 "context" 9 "expvar" 10 "fmt" 11 "log/slog" 12 "runtime" 13 "slices" 14 "strconv" 15 "strings" 16 "testing" 17 "time" 18 19 "github.com/stretchr/testify/assert" 20 "github.com/stretchr/testify/require" 21 "go.uber.org/goleak" 22 23 "github.com/cilium/hive" 24 "github.com/cilium/hive/cell" 25 "github.com/cilium/hive/hivetest" 26 "github.com/cilium/statedb/index" 27 "github.com/cilium/statedb/part" 28 "github.com/cilium/stream" 29 ) 30 31 // Amount of time to wait for the watch channel to close in tests 32 const watchCloseTimeout = 30 * time.Second 33 34 func TestMain(m *testing.M) { 35 // Catch any leaks of goroutines from these tests. 36 goleak.VerifyTestMain(m) 37 } 38 39 type testObject struct { 40 ID uint64 41 Tags part.Set[string] 42 } 43 44 func (t testObject) getID() uint64 { 45 return t.ID 46 } 47 48 func (t testObject) String() string { 49 return fmt.Sprintf("testObject{ID: %d, Tags: %v}", t.ID, t.Tags) 50 } 51 52 func (t testObject) TableHeader() []string { 53 return []string{"ID", "Tags"} 54 } 55 56 func (t testObject) TableRow() []string { 57 return []string{ 58 strconv.FormatUint(uint64(t.ID), 10), 59 strings.Join(slices.Collect(t.Tags.All()), ", "), 60 } 61 } 62 63 var ( 64 idIndex = Index[testObject, uint64]{ 65 Name: "id", 66 FromObject: func(t testObject) index.KeySet { 67 return index.NewKeySet(index.Uint64(t.ID)) 68 }, 69 FromKey: index.Uint64, 70 FromString: index.Uint64String, 71 Unique: true, 72 } 73 74 tagsIndex = Index[testObject, string]{ 75 Name: "tags", 76 FromObject: func(t testObject) index.KeySet { 77 return index.Set(t.Tags) 78 }, 79 FromKey: index.String, 80 FromString: index.FromString, 81 Unique: false, 82 } 83 ) 84 85 func newTestObjectTable(t testing.TB, name string, secondaryIndexers ...Indexer[testObject]) RWTable[testObject] { 86 table, err := NewTable( 87 name, 88 idIndex, 89 secondaryIndexers..., 90 ) 91 require.NoError(t, err, "NewTable[testObject]") 92 return table 93 } 94 95 const ( 96 INDEX_TAGS = true 97 NO_INDEX_TAGS = false 98 ) 99 100 func newTestDB(t testing.TB, secondaryIndexers ...Indexer[testObject]) (*DB, RWTable[testObject], *ExpVarMetrics) { 101 metrics := NewExpVarMetrics(false) 102 db, table := newTestDBWithMetrics(t, metrics, secondaryIndexers...) 103 return db, table, metrics 104 } 105 106 func newTestDBWithMetrics(t testing.TB, metrics Metrics, secondaryIndexers ...Indexer[testObject]) (*DB, RWTable[testObject]) { 107 var ( 108 db *DB 109 ) 110 table := newTestObjectTable(t, "test", secondaryIndexers...) 111 112 h := hive.New( 113 cell.Provide(func() Metrics { return metrics }), 114 Cell, // DB 115 cell.Invoke(func(db_ *DB) { 116 err := db_.RegisterTable(table) 117 require.NoError(t, err, "RegisterTable failed") 118 119 // Use a short GC interval. 120 db_.setGCRateLimitInterval(50 * time.Millisecond) 121 122 db = db_ 123 }), 124 ) 125 126 log := hivetest.Logger(t, hivetest.LogLevel(slog.LevelError)) 127 require.NoError(t, h.Start(log, context.TODO())) 128 t.Cleanup(func() { 129 assert.NoError(t, h.Stop(log, context.TODO())) 130 }) 131 return db, table 132 } 133 134 func TestDB_Insert_SamePointer(t *testing.T) { 135 db := New() 136 require.NoError(t, db.Start(), "Start") 137 defer func() { require.NoError(t, db.Stop(), "Stop") }() 138 139 idIndex := Index[*testObject, uint64]{ 140 Name: "id", 141 FromObject: func(t *testObject) index.KeySet { 142 return index.NewKeySet(index.Uint64(t.ID)) 143 }, 144 FromKey: index.Uint64, 145 Unique: true, 146 } 147 table, _ := NewTable("test", idIndex) 148 require.NoError(t, db.RegisterTable(table), "RegisterTable") 149 150 txn := db.WriteTxn(table) 151 obj := &testObject{ID: 1} 152 _, _, err := table.Insert(txn, obj) 153 require.NoError(t, err, "Insert failed") 154 txn.Commit() 155 156 defer func() { 157 txn.Abort() 158 if err := recover(); err == nil { 159 t.Fatalf("Inserting the same object again didn't fatal") 160 } 161 }() 162 163 // Try to insert the same again. This will panic. 164 txn = db.WriteTxn(table) 165 _, _, err = table.Insert(txn, obj) 166 require.NoError(t, err, "Insert failed") 167 } 168 169 func TestDB_LowerBound_ByRevision(t *testing.T) { 170 t.Parallel() 171 172 db, table := newTestDBWithMetrics(t, &NopMetrics{}, tagsIndex) 173 174 { 175 txn := db.WriteTxn(table) 176 _, _, err := table.Insert(txn, testObject{ID: 42, Tags: part.NewSet("hello", "world")}) 177 require.NoError(t, err, "Insert failed") 178 txn.Commit() 179 180 txn = db.WriteTxn(table) 181 _, _, err = table.Insert(txn, testObject{ID: 71, Tags: part.NewSet("foo")}) 182 require.NoError(t, err, "Insert failed") 183 txn.Commit() 184 } 185 186 txn := db.ReadTxn() 187 188 seq, watch := table.LowerBoundWatch(txn, ByRevision[testObject](0)) 189 expected := []uint64{42, 71} 190 revs := map[uint64]Revision{} 191 var prevRev Revision 192 for obj, rev := range seq { 193 require.NotEmpty(t, expected) 194 require.EqualValues(t, expected[0], obj.ID) 195 require.Greater(t, rev, prevRev) 196 expected = expected[1:] 197 prevRev = rev 198 revs[obj.ID] = rev 199 } 200 201 expected = []uint64{71} 202 seq = table.LowerBound(txn, ByRevision[testObject](revs[42]+1)) 203 for obj, rev := range seq { 204 require.NotEmpty(t, expected) 205 require.EqualValues(t, expected[0], obj.ID) 206 require.EqualValues(t, revs[obj.ID], rev) 207 expected = expected[1:] 208 } 209 require.Empty(t, expected) 210 211 select { 212 case <-watch: 213 t.Fatalf("expected LowerBound watch to not be closed before changes") 214 default: 215 } 216 217 { 218 txn := db.WriteTxn(table) 219 _, _, err := table.Insert(txn, testObject{ID: 71, Tags: part.NewSet("foo", "modified")}) 220 require.NoError(t, err, "Insert failed") 221 txn.Commit() 222 } 223 224 select { 225 case <-watch: 226 case <-time.After(watchCloseTimeout): 227 t.Fatalf("expected LowerBound watch to close after changes") 228 } 229 230 txn = db.ReadTxn() 231 seq = table.LowerBound(txn, ByRevision[testObject](revs[42]+1)) 232 expected = []uint64{71} 233 for obj, _ := range seq { 234 require.NotEmpty(t, expected) 235 require.EqualValues(t, expected[0], obj.ID) 236 expected = expected[1:] 237 } 238 require.Empty(t, expected) 239 } 240 241 func TestDB_Prefix(t *testing.T) { 242 t.Parallel() 243 244 db, table := newTestDBWithMetrics(t, &NopMetrics{}, tagsIndex) 245 246 { 247 txn := db.WriteTxn(table) 248 _, _, err := table.Insert(txn, testObject{ID: 42, Tags: part.NewSet("a", "b")}) 249 require.NoError(t, err, "Insert failed") 250 _, _, err = table.Insert(txn, testObject{ID: 82, Tags: part.NewSet("abc")}) 251 require.NoError(t, err, "Insert failed") 252 _, _, err = table.Insert(txn, testObject{ID: 71, Tags: part.NewSet("ab")}) 253 require.NoError(t, err, "Insert failed") 254 txn.Commit() 255 } 256 257 txn := db.ReadTxn() 258 259 iter, watch := table.PrefixWatch(txn, tagsIndex.Query("ab")) 260 require.Equal(t, []uint64{71, 82}, Collect(Map(iter, testObject.getID))) 261 262 select { 263 case <-watch: 264 t.Fatalf("expected Prefix watch to not be closed before any changes") 265 default: 266 } 267 268 { 269 txn := db.WriteTxn(table) 270 _, _, err := table.Insert(txn, testObject{ID: 12, Tags: part.NewSet("bc")}) 271 require.NoError(t, err, "Insert failed") 272 txn.Commit() 273 } 274 275 select { 276 case <-watch: 277 t.Fatalf("expected Prefix watch to not be closed before relevant changes") 278 default: 279 } 280 281 { 282 txn := db.WriteTxn(table) 283 _, _, err := table.Insert(txn, testObject{ID: 99, Tags: part.NewSet("abcd")}) 284 require.NoError(t, err, "Insert failed") 285 txn.Commit() 286 } 287 288 select { 289 case <-watch: 290 case <-time.After(watchCloseTimeout): 291 t.Fatalf("expected Prefix watch to close after relevant changes") 292 } 293 294 txn = db.ReadTxn() 295 iter = table.Prefix(txn, tagsIndex.Query("ab")) 296 require.Equal(t, Collect(Map(iter, testObject.getID)), []uint64{71, 82, 99}) 297 } 298 299 func TestDB_Changes(t *testing.T) { 300 t.Parallel() 301 302 db, table, metrics := newTestDB(t, tagsIndex) 303 304 { 305 txn := db.WriteTxn(table) 306 _, _, err := table.Insert(txn, testObject{ID: 42, Tags: part.NewSet("hello", "world")}) 307 require.NoError(t, err, "Insert failed") 308 _, _, err = table.Insert(txn, testObject{ID: 71, Tags: part.NewSet("foo")}) 309 require.NoError(t, err, "Insert failed") 310 _, _, err = table.Insert(txn, testObject{ID: 83, Tags: part.NewSet("bar")}) 311 require.NoError(t, err, "Insert failed") 312 txn.Commit() 313 } 314 315 assert.EqualValues(t, table.Revision(db.ReadTxn()), expvarInt(metrics.RevisionVar.Get("test")), "Revision") 316 assert.EqualValues(t, 3, expvarInt(metrics.ObjectCountVar.Get("test")), "ObjectCount") 317 assert.EqualValues(t, 0, expvarInt(metrics.GraveyardObjectCountVar.Get("test")), "GraveyardObjectCount") 318 319 // Create two change iterators 320 wtxn := db.WriteTxn(table) 321 iter, err := table.Changes(wtxn) 322 require.NoError(t, err, "failed to create ChangeIterator") 323 iter2, err := table.Changes(wtxn) 324 require.NoError(t, err, "failed to create ChangeIterator") 325 txn0 := wtxn.Commit() 326 327 assert.EqualValues(t, 2, expvarInt(metrics.DeleteTrackerCountVar.Get("test")), "DeleteTrackerCount") 328 329 // The initial watch channel is closed, so users can either iterate first or watch first. 330 changes, watch := iter.Next(db.ReadTxn()) 331 332 // Delete 2/3 objects 333 { 334 txn := db.WriteTxn(table) 335 old, deleted, err := table.Delete(txn, testObject{ID: 42}) 336 require.True(t, deleted) 337 require.EqualValues(t, 42, old.ID) 338 require.NoError(t, err) 339 old, deleted, err = table.Delete(txn, testObject{ID: 71}) 340 require.True(t, deleted) 341 require.EqualValues(t, 71, old.ID) 342 require.NoError(t, err) 343 txn.Commit() 344 345 // Reinsert and redelete to test updating graveyard with existing object. 346 txn = db.WriteTxn(table) 347 _, _, err = table.Insert(txn, testObject{ID: 71, Tags: part.NewSet("foo")}) 348 require.NoError(t, err, "Insert failed") 349 txn.Commit() 350 351 txn = db.WriteTxn(table) 352 _, deleted, err = table.Delete(txn, testObject{ID: 71}) 353 require.True(t, deleted) 354 require.NoError(t, err, "Delete failed") 355 txn.Commit() 356 } 357 358 // 1 object should exist. 359 txn := db.ReadTxn() 360 iterAll := table.All(txn) 361 objs := Collect(iterAll) 362 require.Len(t, objs, 1) 363 364 assert.EqualValues(t, 1, expvarInt(metrics.ObjectCountVar.Get("test")), "ObjectCount") 365 assert.EqualValues(t, 2, expvarInt(metrics.GraveyardObjectCountVar.Get("test")), "GraveyardObjectCount") 366 367 // Consume the deletions using the first delete tracker. 368 nExist := 0 369 nDeleted := 0 370 371 // Observe the objects that existed when the tracker was created. 372 <-watch 373 changes, watch = iter.Next(txn0) 374 for change := range changes { 375 if change.Deleted { 376 nDeleted++ 377 } else { 378 nExist++ 379 } 380 } 381 assert.Equal(t, 0, nDeleted) 382 assert.Equal(t, 3, nExist) 383 384 // Wait for the new changes. 385 <-watch 386 387 changes, watch = iter.Next(txn) 388 389 // Consume one change, leaving a partially consumed sequence. 390 for change := range changes { 391 if change.Deleted { 392 nDeleted++ 393 nExist-- 394 } else { 395 nExist++ 396 } 397 break 398 } 399 400 // The iterator can be refreshed with new snapshot without having consumed 401 // the previous sequence fully. No changes are missed. 402 changes, watch = iter.Next(db.ReadTxn()) 403 for change := range changes { 404 if change.Deleted { 405 nDeleted++ 406 nExist-- 407 } else { 408 nExist++ 409 } 410 } 411 412 assert.Equal(t, 2, nDeleted) 413 assert.Equal(t, 1, nExist) 414 415 // Since the second iterator has not processed the deletions, 416 // the graveyard index should still hold them. 417 require.False(t, db.graveyardIsEmpty()) 418 419 // Consume the deletions using the second iterator. 420 nExist = 0 421 nDeleted = 0 422 423 changes, watch = iter2.Next(txn) 424 for change := range changes { 425 if change.Deleted { 426 nDeleted++ 427 } else { 428 nExist++ 429 } 430 } 431 432 assert.Equal(t, 1, nExist) 433 assert.Equal(t, 2, nDeleted) 434 435 // Refreshing with the same snapshot yields no new changes. 436 changes, watch = iter2.Next(txn) 437 for change := range changes { 438 t.Fatalf("unexpected change: %v", change) 439 } 440 441 // Graveyard will now be GCd. 442 eventuallyGraveyardIsEmpty(t, db) 443 444 assert.EqualValues(t, table.Revision(db.ReadTxn()), expvarInt(metrics.RevisionVar.Get("test")), "Revision") 445 assert.EqualValues(t, 1, expvarInt(metrics.ObjectCountVar.Get("test")), "ObjectCount") 446 assert.EqualValues(t, 0, expvarInt(metrics.GraveyardObjectCountVar.Get("test")), "GraveyardObjectCount") 447 448 // Insert a new object and consume the event 449 { 450 wtxn := db.WriteTxn(table) 451 _, _, err := table.Insert(wtxn, testObject{ID: 88, Tags: part.NewSet("foo")}) 452 require.NoError(t, err, "Insert failed") 453 wtxn.Commit() 454 } 455 456 <-watch 457 458 txn = db.ReadTxn() 459 changes, watch = iter.Next(txn) 460 changes1 := Collect(changes) 461 changes, _ = iter2.Next(txn) 462 changes2 := Collect(changes) 463 464 assert.Equal(t, len(changes1), len(changes2), 465 "expected same number of changes from both iterators") 466 467 if assert.Len(t, changes1, 1, "expected one change") { 468 change := changes1[0] 469 change2 := changes2[0] 470 assert.EqualValues(t, 88, change.Object.ID) 471 assert.EqualValues(t, 88, change2.Object.ID) 472 assert.False(t, change.Deleted) 473 assert.False(t, change2.Deleted) 474 } 475 476 // After dropping the first iterator, deletes are still tracked for second one. 477 // Delete the remaining objects. 478 iter = nil 479 { 480 txn := db.WriteTxn(table) 481 require.NoError(t, table.DeleteAll(txn), "DeleteAll failed") 482 txn.Commit() 483 } 484 485 require.False(t, db.graveyardIsEmpty()) 486 487 assert.EqualValues(t, 0, expvarInt(metrics.ObjectCountVar.Get("test")), "ObjectCount") 488 assert.EqualValues(t, 2, expvarInt(metrics.GraveyardObjectCountVar.Get("test")), "GraveyardObjectCount") 489 490 // Consume the deletions using the second iterator. 491 txn = db.ReadTxn() 492 493 <-watch 494 changes, watch = iter2.Next(txn) 495 496 count := 0 497 for change := range changes { 498 count++ 499 assert.True(t, change.Deleted, "expected object %d to be deleted", change.Object.ID) 500 } 501 assert.Equal(t, 2, count) 502 503 eventuallyGraveyardIsEmpty(t, db) 504 505 assert.EqualValues(t, 0, expvarInt(metrics.ObjectCountVar.Get("test")), "ObjectCount") 506 assert.EqualValues(t, 0, expvarInt(metrics.GraveyardObjectCountVar.Get("test")), "GraveyardObjectCount") 507 508 // After dropping the second iterator the deletions no longer go into graveyard. 509 iter2 = nil 510 { 511 txn := db.WriteTxn(table) 512 _, _, err := table.Insert(txn, testObject{ID: 78, Tags: part.NewSet("world")}) 513 require.NoError(t, err, "Insert failed") 514 txn.Commit() 515 txn = db.WriteTxn(table) 516 require.NoError(t, table.DeleteAll(txn), "DeleteAll failed") 517 txn.Commit() 518 } 519 // Eventually GC drops the second iterator and the delete tracker is closed. 520 eventuallyGraveyardIsEmpty(t, db) 521 522 assert.EqualValues(t, 0, expvarInt(metrics.ObjectCountVar.Get("test")), "ObjectCount") 523 assert.EqualValues(t, 0, expvarInt(metrics.GraveyardObjectCountVar.Get("test")), "GraveyardObjectCount") 524 525 // Create another iterator and test observing changes using a WriteTxn 526 // that is mutating the table. 527 wtxn = db.WriteTxn(table) 528 iter3, err := table.Changes(wtxn) 529 require.NoError(t, err, "failed to create ChangeIterator") 530 _, _, err = table.Insert(wtxn, testObject{ID: 1}) 531 require.NoError(t, err, "Insert failed") 532 wtxn.Commit() 533 534 wtxn = db.WriteTxn(table) 535 _, _, err = table.Insert(wtxn, testObject{ID: 2}) 536 require.NoError(t, err, "Insert failed") 537 changes, _ = iter3.Next(wtxn) 538 // We don't observe the insert of ID 2 539 count = 0 540 for change := range changes { 541 require.EqualValues(t, 1, change.Object.ID) 542 count++ 543 } 544 require.Equal(t, 1, count) 545 wtxn.Abort() 546 } 547 548 func TestDB_Observable(t *testing.T) { 549 t.Parallel() 550 551 db, table, _ := newTestDB(t) 552 ctx, cancel := context.WithCancel(context.Background()) 553 events := stream.ToChannel(ctx, Observable(db, table)) 554 555 txn := db.WriteTxn(table) 556 _, hadOld, err := table.Insert(txn, testObject{ID: uint64(1)}) 557 require.False(t, hadOld, "Expected no prior object") 558 require.NoError(t, err, "Insert failed") 559 _, hadOld, err = table.Insert(txn, testObject{ID: uint64(2)}) 560 require.False(t, hadOld, "Expected no prior object") 561 require.NoError(t, err, "Insert failed") 562 txn.Commit() 563 564 event := <-events 565 require.False(t, event.Deleted, "expected insert") 566 require.Equal(t, uint64(1), event.Object.ID) 567 event = <-events 568 require.False(t, event.Deleted, "expected insert") 569 require.Equal(t, uint64(2), event.Object.ID) 570 571 txn = db.WriteTxn(table) 572 _, hadOld, err = table.Delete(txn, testObject{ID: uint64(1)}) 573 require.True(t, hadOld, "Expected that object was deleted") 574 require.NoError(t, err, "Delete failed") 575 _, hadOld, err = table.Delete(txn, testObject{ID: uint64(2)}) 576 require.True(t, hadOld, "Expected that object was deleted") 577 require.NoError(t, err, "Delete failed") 578 txn.Commit() 579 580 event = <-events 581 require.True(t, event.Deleted, "expected delete") 582 require.Equal(t, uint64(1), event.Object.ID) 583 event = <-events 584 require.True(t, event.Deleted, "expected delete") 585 require.Equal(t, uint64(2), event.Object.ID) 586 587 cancel() 588 ev, ok := <-events 589 require.False(t, ok, "expected channel to close, got event: %+v", ev) 590 } 591 592 func TestDB_NumObjects(t *testing.T) { 593 t.Parallel() 594 595 db, table, _ := newTestDB(t) 596 rtxn := db.ReadTxn() 597 assert.Equal(t, 0, table.NumObjects(rtxn)) 598 599 txn := db.WriteTxn(table) 600 assert.Equal(t, 0, table.NumObjects(txn)) 601 table.Insert(txn, testObject{ID: uint64(1)}) 602 assert.Equal(t, 1, table.NumObjects(txn)) 603 table.Insert(txn, testObject{ID: uint64(1)}) 604 table.Insert(txn, testObject{ID: uint64(2)}) 605 assert.Equal(t, 2, table.NumObjects(txn)) 606 607 assert.Equal(t, 0, table.NumObjects(rtxn)) 608 txn.Commit() 609 assert.Equal(t, 0, table.NumObjects(rtxn)) 610 611 rtxn = db.ReadTxn() 612 assert.Equal(t, 2, table.NumObjects(rtxn)) 613 } 614 615 func TestDB_All(t *testing.T) { 616 t.Parallel() 617 618 db, table, _ := newTestDB(t, tagsIndex) 619 620 { 621 txn := db.WriteTxn(table) 622 _, _, err := table.Insert(txn, testObject{ID: uint64(1)}) 623 require.NoError(t, err, "Insert failed") 624 _, _, err = table.Insert(txn, testObject{ID: uint64(2)}) 625 require.NoError(t, err, "Insert failed") 626 _, _, err = table.Insert(txn, testObject{ID: uint64(3)}) 627 require.NoError(t, err, "Insert failed") 628 iter := table.All(txn) 629 objs := Collect(iter) 630 require.Len(t, objs, 3) 631 require.EqualValues(t, 1, objs[0].ID) 632 require.EqualValues(t, 2, objs[1].ID) 633 require.EqualValues(t, 3, objs[2].ID) 634 txn.Commit() 635 } 636 637 txn := db.ReadTxn() 638 iter, watch := table.AllWatch(txn) 639 objs := Collect(iter) 640 require.Len(t, objs, 3) 641 require.EqualValues(t, 1, objs[0].ID) 642 require.EqualValues(t, 2, objs[1].ID) 643 require.EqualValues(t, 3, objs[2].ID) 644 645 select { 646 case <-watch: 647 t.Fatalf("expected All() watch channel to not close before delete") 648 default: 649 } 650 651 { 652 txn := db.WriteTxn(table) 653 _, hadOld, err := table.Delete(txn, testObject{ID: uint64(1)}) 654 require.True(t, hadOld, "expected object to be deleted") 655 require.NoError(t, err, "Delete failed") 656 txn.Commit() 657 } 658 659 // Prior read transaction not affected by delete. 660 iter = table.All(txn) 661 objs = Collect(iter) 662 require.Len(t, objs, 3) 663 664 select { 665 case <-watch: 666 case <-time.After(watchCloseTimeout): 667 t.Fatalf("expected All() watch channel to close after delete") 668 } 669 } 670 671 func TestDB_Modify(t *testing.T) { 672 t.Parallel() 673 674 db, table, _ := newTestDB(t, tagsIndex) 675 676 txn := db.WriteTxn(table) 677 678 // Modifying a non-existing object is effectively an Insert. 679 _, hadOld, err := table.Modify(txn, testObject{ID: uint64(1), Tags: part.NewSet("foo")}, func(old, new testObject) testObject { 680 t.Fatalf("merge unepectedly called") 681 return new 682 }) 683 require.NoError(t, err, "Modify failed") 684 require.False(t, hadOld, "expected hadOld to be false") 685 686 mergeCalled := false 687 _, hadOld, err = table.Modify(txn, testObject{ID: uint64(1)}, func(old, new testObject) testObject { 688 mergeCalled = true 689 // Merge the old and new tags. 690 new.Tags = old.Tags.Set("bar") 691 return new 692 }) 693 require.NoError(t, err, "Modify failed") 694 require.True(t, hadOld, "expected hadOld to be true") 695 require.True(t, mergeCalled, "expected merge() to be called") 696 697 obj, _, found := table.Get(txn, idIndex.Query(1)) 698 require.True(t, found) 699 require.True(t, obj.Tags.Has("foo")) 700 require.True(t, obj.Tags.Has("bar")) 701 702 txn.Commit() 703 704 objs := Collect(table.All(db.ReadTxn())) 705 require.Len(t, objs, 1) 706 require.EqualValues(t, 1, objs[0].ID) 707 } 708 709 func TestDB_Revision(t *testing.T) { 710 t.Parallel() 711 712 db, table, _ := newTestDB(t, tagsIndex) 713 714 startRevision := table.Revision(db.ReadTxn()) 715 716 // On aborted write transactions the revision remains unchanged. 717 txn := db.WriteTxn(table) 718 _, _, err := table.Insert(txn, testObject{ID: 1}) 719 require.NoError(t, err) 720 writeRevision := table.Revision(txn) // Returns new, but uncommitted revision 721 txn.Abort() 722 require.Equal(t, writeRevision, startRevision+1, "revision incremented on Insert") 723 readRevision := table.Revision(db.ReadTxn()) 724 require.Equal(t, startRevision, readRevision, "aborted transaction does not change revision") 725 726 // Committed write transactions increment the revision 727 txn = db.WriteTxn(table) 728 _, _, err = table.Insert(txn, testObject{ID: 1}) 729 require.NoError(t, err) 730 writeRevision = table.Revision(txn) 731 txn.Commit() 732 require.Equal(t, writeRevision, startRevision+1, "revision incremented on Insert") 733 readRevision = table.Revision(db.ReadTxn()) 734 require.Equal(t, writeRevision, readRevision, "committed transaction changed revision") 735 } 736 737 func TestDB_GetList(t *testing.T) { 738 t.Parallel() 739 740 db, table, _ := newTestDB(t, tagsIndex) 741 742 // Write test objects 1..10 to table with odd/even/odd/... tags. 743 { 744 txn := db.WriteTxn(table) 745 for i := 1; i <= 10; i++ { 746 tag := "odd" 747 if i%2 == 0 { 748 tag = "even" 749 } 750 _, _, err := table.Insert(txn, testObject{ID: uint64(i), Tags: part.NewSet(tag)}) 751 require.NoError(t, err) 752 } 753 // Check that we can query the not-yet-committed write transaction. 754 obj, rev, ok := table.Get(txn, idIndex.Query(1)) 755 require.True(t, ok, "expected Get(1) to return result") 756 require.NotZero(t, rev, "expected non-zero revision") 757 require.EqualValues(t, obj.ID, 1, "expected first obj.ID to equal 1") 758 txn.Commit() 759 } 760 761 txn := db.ReadTxn() 762 763 // Test List against the ID index. 764 iter := table.List(txn, idIndex.Query(0)) 765 items := Collect(iter) 766 require.Len(t, items, 0, "expected Get(0) to not return results") 767 768 iter = table.List(txn, idIndex.Query(1)) 769 items = Collect(iter) 770 require.Len(t, items, 1, "expected Get(1) to return result") 771 require.EqualValues(t, items[0].ID, 1, "expected items[0].ID to equal 1") 772 773 iter, listWatch := table.ListWatch(txn, idIndex.Query(2)) 774 items = Collect(iter) 775 require.Len(t, items, 1, "expected Get(2) to return result") 776 require.EqualValues(t, items[0].ID, 2, "expected items[0].ID to equal 2") 777 778 // Test Get/GetWatch against the ID index. 779 _, _, ok := table.Get(txn, idIndex.Query(0)) 780 require.False(t, ok, "expected Get(0) to not return result") 781 782 obj, rev, ok := table.Get(txn, idIndex.Query(1)) 783 require.True(t, ok, "expected Get(1) to return result") 784 require.NotZero(t, rev, "expected non-zero revision") 785 require.EqualValues(t, obj.ID, 1, "expected first obj.ID to equal 1") 786 787 obj, rev, getWatch, ok := table.GetWatch(txn, idIndex.Query(2)) 788 require.True(t, ok, "expected GetWatch(2) to return result") 789 require.NotZero(t, rev, "expected non-zero revision") 790 require.EqualValues(t, obj.ID, 2, "expected obj.ID to equal 2") 791 792 select { 793 case <-getWatch: 794 t.Fatalf("GetWatch channel closed before changes") 795 case <-listWatch: 796 t.Fatalf("List channel closed before changes") 797 default: 798 } 799 800 // Modify the testObject(2) to trigger closing of the watch channels. 801 wtxn := db.WriteTxn(table) 802 _, hadOld, err := table.Insert(wtxn, testObject{ID: uint64(2), Tags: part.NewSet("even", "modified")}) 803 require.True(t, hadOld) 804 require.NoError(t, err) 805 wtxn.Commit() 806 807 select { 808 case <-getWatch: 809 case <-time.After(watchCloseTimeout): 810 t.Fatalf("GetWatch channel not closed after change") 811 } 812 select { 813 case <-listWatch: 814 case <-time.After(watchCloseTimeout): 815 t.Fatalf("List channel not closed after change") 816 } 817 818 // Since we modified the database, grab a fresh read transaction. 819 txn = db.ReadTxn() 820 821 // Test Get and Last against the tags multi-index which will 822 // return multiple results. 823 obj, rev, _, ok = table.GetWatch(txn, tagsIndex.Query("even")) 824 require.True(t, ok, "expected Get(even) to return result") 825 require.NotZero(t, rev, "expected non-zero revision") 826 require.ElementsMatch(t, slices.Collect(obj.Tags.All()), []string{"even", "modified"}) 827 require.EqualValues(t, 2, obj.ID) 828 829 iter = table.List(txn, tagsIndex.Query("odd")) 830 items = Collect(iter) 831 require.Len(t, items, 5, "expected Get(odd) to return 5 items") 832 for i, item := range items { 833 require.EqualValues(t, item.ID, i*2+1, "expected items[%d].ID to equal %d", i, i*2+1) 834 } 835 } 836 837 func TestDB_CommitAbort(t *testing.T) { 838 t.Parallel() 839 840 dbX, table, metrics := newTestDB(t, tagsIndex) 841 db := dbX.NewHandle("test-handle") 842 843 txn := db.WriteTxn(table) 844 _, _, err := table.Insert(txn, testObject{ID: 123}) 845 require.NoError(t, err) 846 txn.Commit() 847 848 assert.EqualValues(t, table.Revision(db.ReadTxn()), expvarInt(metrics.RevisionVar.Get("test")), "Revision") 849 assert.EqualValues(t, 1, expvarInt(metrics.ObjectCountVar.Get("test")), "ObjectCount") 850 assert.Greater(t, expvarFloat(metrics.WriteTxnAcquisitionVar.Get("test-handle/test")), 0.0, "WriteTxnAcquisition") 851 assert.Greater(t, expvarFloat(metrics.WriteTxnDurationVar.Get("test-handle/test")), 0.0, "WriteTxnDuration") 852 853 obj, rev, ok := table.Get(db.ReadTxn(), idIndex.Query(123)) 854 require.True(t, ok, "expected Get(1) to return result") 855 require.NotZero(t, rev, "expected non-zero revision") 856 require.EqualValues(t, obj.ID, 123, "expected obj.ID to equal 123") 857 require.Zero(t, obj.Tags.Len(), "expected no tags") 858 859 _, _, err = table.Insert(txn, testObject{ID: 123, Tags: part.NewSet("insert-after-commit")}) 860 require.ErrorIs(t, err, ErrTransactionClosed) 861 txn.Commit() // should be no-op 862 863 txn = db.WriteTxn(table) 864 txn.Abort() 865 866 _, _, err = table.Insert(txn, testObject{ID: 123, Tags: part.NewSet("insert-after-abort")}) 867 require.ErrorIs(t, err, ErrTransactionClosed) 868 txn.Commit() // should be no-op 869 870 // Check that insert after commit and insert after abort do not change the 871 // table. 872 obj, newRev, ok := table.Get(db.ReadTxn(), idIndex.Query(123)) 873 require.True(t, ok, "expected object to exist") 874 require.Equal(t, rev, newRev, "expected unchanged revision") 875 require.EqualValues(t, obj.ID, 123, "expected obj.ID to equal 123") 876 require.Zero(t, obj.Tags.Len(), "expected no tags") 877 } 878 879 func TestDB_CompareAndSwap_CompareAndDelete(t *testing.T) { 880 t.Parallel() 881 882 db, table, _ := newTestDB(t, tagsIndex) 883 884 // Updating a non-existing object fails and nothing is inserted. 885 wtxn := db.WriteTxn(table) 886 { 887 _, hadOld, err := table.CompareAndSwap(wtxn, 1, testObject{ID: 1}) 888 require.ErrorIs(t, ErrObjectNotFound, err) 889 require.False(t, hadOld) 890 891 objs := table.All(wtxn) 892 require.Len(t, Collect(objs), 0) 893 894 wtxn.Abort() 895 } 896 897 // Insert a test object and retrieve it. 898 wtxn = db.WriteTxn(table) 899 _, hadOld, err := table.Insert(wtxn, testObject{ID: 1}) 900 require.False(t, hadOld, "expected Insert to not replace object") 901 require.NoError(t, err, "Insert failed") 902 wtxn.Commit() 903 904 obj, rev1, ok := table.Get(db.ReadTxn(), idIndex.Query(1)) 905 require.True(t, ok) 906 907 // Updating an object with matching revision number works 908 wtxn = db.WriteTxn(table) 909 obj.Tags = part.NewSet("updated") // NOTE: testObject stored by value so no explicit copy needed. 910 oldObj, hadOld, err := table.CompareAndSwap(wtxn, rev1, obj) 911 require.NoError(t, err) 912 require.True(t, hadOld) 913 require.EqualValues(t, 1, oldObj.ID) 914 wtxn.Commit() 915 916 obj, _, ok = table.Get(db.ReadTxn(), idIndex.Query(1)) 917 require.True(t, ok) 918 require.Equal(t, 1, obj.Tags.Len()) 919 v := slices.Collect(obj.Tags.All())[0] 920 require.Equal(t, "updated", v) 921 922 // Updating an object with mismatching revision number fails 923 wtxn = db.WriteTxn(table) 924 obj.Tags = part.NewSet("mismatch") 925 oldObj, hadOld, err = table.CompareAndSwap(wtxn, rev1, obj) 926 require.ErrorIs(t, ErrRevisionNotEqual, err) 927 require.True(t, hadOld) 928 require.EqualValues(t, 1, oldObj.ID) 929 wtxn.Commit() 930 931 obj, _, ok = table.Get(db.ReadTxn(), idIndex.Query(1)) 932 require.True(t, ok) 933 require.Equal(t, 1, obj.Tags.Len()) 934 v = slices.Collect(obj.Tags.All())[0] 935 require.Equal(t, "updated", v) 936 937 // Deleting an object with mismatching revision number fails 938 wtxn = db.WriteTxn(table) 939 obj.Tags = part.NewSet("mismatch") 940 oldObj, hadOld, err = table.CompareAndDelete(wtxn, rev1, obj) 941 require.ErrorIs(t, ErrRevisionNotEqual, err) 942 require.True(t, hadOld) 943 require.EqualValues(t, 1, oldObj.ID) 944 wtxn.Commit() 945 946 obj, rev2, ok := table.Get(db.ReadTxn(), idIndex.Query(1)) 947 require.True(t, ok) 948 require.Equal(t, 1, obj.Tags.Len()) 949 v = slices.Collect(obj.Tags.All())[0] 950 require.Equal(t, "updated", v) 951 952 // Deleting with matching revision number works 953 wtxn = db.WriteTxn(table) 954 obj.Tags = part.NewSet("mismatch") 955 oldObj, hadOld, err = table.CompareAndDelete(wtxn, rev2, obj) 956 require.NoError(t, err) 957 require.True(t, hadOld) 958 require.EqualValues(t, 1, oldObj.ID) 959 wtxn.Commit() 960 961 _, _, ok = table.Get(db.ReadTxn(), idIndex.Query(1)) 962 require.False(t, ok) 963 964 // Deleting non-existing object yields not found 965 wtxn = db.WriteTxn(table) 966 _, hadOld, err = table.CompareAndDelete(wtxn, rev2, obj) 967 require.NoError(t, err) 968 require.False(t, hadOld) 969 wtxn.Abort() 970 } 971 972 func TestDB_ReadAfterWrite(t *testing.T) { 973 t.Parallel() 974 975 db, table, _ := newTestDB(t, tagsIndex) 976 977 txn := db.WriteTxn(table) 978 979 require.Len(t, Collect(table.All(txn)), 0) 980 981 _, _, err := table.Insert(txn, testObject{ID: 1}) 982 require.NoError(t, err, "Insert failed") 983 984 require.Len(t, Collect(table.All(txn)), 1) 985 986 _, hadOld, _ := table.Delete(txn, testObject{ID: 1}) 987 require.True(t, hadOld) 988 require.Len(t, Collect(table.All(txn)), 0) 989 990 _, _, err = table.Insert(txn, testObject{ID: 2}) 991 require.NoError(t, err, "Insert failed") 992 require.Len(t, Collect(table.All(txn)), 1) 993 994 txn.Commit() 995 996 require.Len(t, Collect(table.All(db.ReadTxn())), 1) 997 } 998 999 func TestDB_Initialization(t *testing.T) { 1000 t.Parallel() 1001 1002 db, table, _ := newTestDB(t, tagsIndex) 1003 1004 // Using Initialized() before any initializers are registered 1005 // will return true and a closed channel. 1006 init, initWatch := table.Initialized(db.ReadTxn()) 1007 require.True(t, init, "Initialized should be true") 1008 select { 1009 case <-initWatch: 1010 default: 1011 t.Fatalf("Initialized watch channel should be closed") 1012 } 1013 1014 wtxn := db.WriteTxn(table) 1015 done1 := table.RegisterInitializer(wtxn, "test1") 1016 done2 := table.RegisterInitializer(wtxn, "test2") 1017 wtxn.Commit() 1018 1019 txn := db.ReadTxn() 1020 init, initWatch = table.Initialized(txn) 1021 require.False(t, init, "Initialized should be false") 1022 require.Equal(t, []string{"test1", "test2"}, table.PendingInitializers(txn), "test1, test2 should be pending") 1023 1024 wtxn = db.WriteTxn(table) 1025 done1(wtxn) 1026 init, _ = table.Initialized(txn) 1027 require.False(t, init, "Initialized should be false") 1028 wtxn.Commit() 1029 1030 // Old read transaction unaffected. 1031 init, _ = table.Initialized(txn) 1032 require.False(t, init, "Initialized should be false") 1033 require.Equal(t, []string{"test1", "test2"}, table.PendingInitializers(txn), "test1, test2 should be pending") 1034 1035 txn = db.ReadTxn() 1036 init, _ = table.Initialized(txn) 1037 require.False(t, init, "Initialized should be false") 1038 require.Equal(t, []string{"test2"}, table.PendingInitializers(txn), "test2 should be pending") 1039 1040 wtxn = db.WriteTxn(table) 1041 done2(wtxn) 1042 init, _ = table.Initialized(wtxn) 1043 assert.True(t, init, "Initialized should be true") 1044 wtxn.Commit() 1045 1046 txn = db.ReadTxn() 1047 init, _ = table.Initialized(txn) 1048 require.True(t, init, "Initialized should be true") 1049 require.Empty(t, table.PendingInitializers(txn), "There should be no pending initializers") 1050 1051 select { 1052 case <-initWatch: 1053 default: 1054 t.Fatalf("Initialized() watch channel was not closed") 1055 } 1056 } 1057 1058 func TestWriteJSON(t *testing.T) { 1059 t.Parallel() 1060 1061 db, table, _ := newTestDB(t, tagsIndex) 1062 1063 buf := new(bytes.Buffer) 1064 err := db.ReadTxn().WriteJSON(buf) 1065 require.NoError(t, err) 1066 1067 txn := db.WriteTxn(table) 1068 for i := 1; i <= 10; i++ { 1069 _, _, err := table.Insert(txn, testObject{ID: uint64(i)}) 1070 require.NoError(t, err) 1071 } 1072 txn.Commit() 1073 } 1074 1075 func Test_nonUniqueKey(t *testing.T) { 1076 // empty keys 1077 key := encodeNonUniqueKey(nil, nil) 1078 secondary, _ := decodeNonUniqueKey(key) 1079 assert.Len(t, secondary, 0) 1080 1081 // empty primary 1082 key = encodeNonUniqueKey(nil, []byte("foo")) 1083 secondary, _ = decodeNonUniqueKey(key) 1084 assert.Equal(t, string(secondary), "foo") 1085 1086 // empty secondary 1087 key = encodeNonUniqueKey([]byte("quux"), []byte{}) 1088 secondary, _ = decodeNonUniqueKey(key) 1089 assert.Len(t, secondary, 0) 1090 1091 // non-empty 1092 key = encodeNonUniqueKey([]byte("foo"), []byte("quux")) 1093 secondary, primary := decodeNonUniqueKey(key) 1094 assert.EqualValues(t, secondary, "quux") 1095 assert.EqualValues(t, primary, "foo") 1096 1097 // non-empty, primary with substitutions: 1098 // 0x0 => 0xfe, 0xfe => 0xfd01, 0xfd => 0xfd00 1099 key = encodeNonUniqueKey([]byte{0x0, 0xfd, 0xfe}, []byte("quux")) 1100 secondary, primary = decodeNonUniqueKey(key) 1101 assert.EqualValues(t, secondary, "quux") 1102 assert.EqualValues(t, primary, []byte{0xfe, 0xfd, 0x01, 0xfd, 0x00}) 1103 } 1104 1105 func Test_validateTableName(t *testing.T) { 1106 validNames := []string{ 1107 "a", 1108 "abc123", 1109 "a1_bc", 1110 "a-b", 1111 } 1112 invalidNames := []string{ 1113 "", 1114 "123", 1115 "ABC", 1116 "loooooooooooooooooooooooooooooooooooooooooooooooong", 1117 "a^*%", 1118 } 1119 1120 for _, name := range validNames { 1121 _, err := NewTable(name, idIndex) 1122 require.NoError(t, err, "NewTable(%s)", name) 1123 } 1124 1125 for _, name := range invalidNames { 1126 _, err := NewTable(name, idIndex) 1127 require.Error(t, err, "NewTable(%s)", name) 1128 } 1129 } 1130 1131 func eventuallyGraveyardIsEmpty(t testing.TB, db *DB) { 1132 require.Eventually(t, 1133 func() bool { 1134 runtime.GC() // force changeIterator finalizers 1135 return db.graveyardIsEmpty() 1136 }, 1137 5*time.Second, 1138 100*time.Millisecond, 1139 "graveyard not garbage collected") 1140 } 1141 1142 func expvarInt(v expvar.Var) int64 { 1143 if v, ok := v.(*expvar.Int); ok && v != nil { 1144 return v.Value() 1145 } 1146 return -1 1147 } 1148 1149 func expvarFloat(v expvar.Var) float64 { 1150 if v, ok := v.(*expvar.Float); ok && v != nil { 1151 return v.Value() 1152 } 1153 return -1 1154 }