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  }