github.com/authzed/spicedb@v1.32.1-0.20240520085336-ebda56537386/internal/datastore/postgres/postgres_shared_test.go (about)

     1  //go:build ci && docker
     2  // +build ci,docker
     3  
     4  package postgres
     5  
     6  import (
     7  	"context"
     8  	"fmt"
     9  	"math/rand"
    10  	"strings"
    11  	"sync"
    12  	"testing"
    13  	"time"
    14  
    15  	sq "github.com/Masterminds/squirrel"
    16  	"github.com/authzed/spicedb/internal/datastore/common"
    17  	pgcommon "github.com/authzed/spicedb/internal/datastore/postgres/common"
    18  	pgversion "github.com/authzed/spicedb/internal/datastore/postgres/version"
    19  	"github.com/authzed/spicedb/internal/testfixtures"
    20  	testdatastore "github.com/authzed/spicedb/internal/testserver/datastore"
    21  	"github.com/authzed/spicedb/pkg/datastore"
    22  	"github.com/authzed/spicedb/pkg/datastore/test"
    23  	"github.com/authzed/spicedb/pkg/migrate"
    24  	"github.com/authzed/spicedb/pkg/namespace"
    25  	core "github.com/authzed/spicedb/pkg/proto/core/v1"
    26  	"github.com/authzed/spicedb/pkg/tuple"
    27  	"github.com/jackc/pgx/v5"
    28  	"github.com/jackc/pgx/v5/pgconn"
    29  	"github.com/samber/lo"
    30  	"github.com/stretchr/testify/require"
    31  	"go.opentelemetry.io/otel"
    32  	"go.opentelemetry.io/otel/sdk/trace"
    33  	"go.opentelemetry.io/otel/sdk/trace/tracetest"
    34  	"golang.org/x/sync/errgroup"
    35  )
    36  
    37  const pgSerializationFailure = "40001"
    38  
    39  const (
    40  	veryLargeGCInterval = 90000 * time.Second
    41  )
    42  
    43  // Implement the TestableDatastore interface
    44  func (pgd *pgDatastore) ExampleRetryableError() error {
    45  	return &pgconn.PgError{
    46  		Code: pgSerializationFailure,
    47  	}
    48  }
    49  
    50  type postgresConfig struct {
    51  	targetMigration string
    52  	migrationPhase  string
    53  	pgVersion       string
    54  	pgbouncer       bool
    55  }
    56  
    57  // the global OTel tracer is used everywhere, so we synchronize tests over a global test tracer
    58  var (
    59  	otelMutex         = sync.Mutex{}
    60  	testTraceProvider *trace.TracerProvider
    61  	postgresConfigs   = lo.Map(
    62  		[]string{pgversion.MinimumSupportedPostgresVersion, "14", "15", "16"},
    63  		func(postgresVersion string, _ int) postgresConfig {
    64  			return postgresConfig{"head", "", postgresVersion, false}
    65  		},
    66  	)
    67  )
    68  
    69  func init() {
    70  	testTraceProvider = trace.NewTracerProvider(
    71  		trace.WithSampler(trace.AlwaysSample()),
    72  	)
    73  	otel.SetTracerProvider(testTraceProvider)
    74  }
    75  
    76  func testPostgresDatastore(t *testing.T, pc []postgresConfig) {
    77  	for _, config := range pc {
    78  		pgbouncerStr := ""
    79  		if config.pgbouncer {
    80  			pgbouncerStr = "pgbouncer-"
    81  		}
    82  		t.Run(fmt.Sprintf("%spostgres-%s-%s-%s", pgbouncerStr, config.pgVersion, config.targetMigration, config.migrationPhase), func(t *testing.T) {
    83  			t.Parallel()
    84  			b := testdatastore.RunPostgresForTesting(t, "", config.targetMigration, config.pgVersion, config.pgbouncer)
    85  			ctx := context.Background()
    86  
    87  			test.All(t, test.DatastoreTesterFunc(func(revisionQuantization, gcInterval, gcWindow time.Duration, watchBufferLength uint16) (datastore.Datastore, error) {
    88  				ds := b.NewDatastore(t, func(engine, uri string) datastore.Datastore {
    89  					ds, err := newPostgresDatastore(ctx, uri,
    90  						RevisionQuantization(revisionQuantization),
    91  						GCWindow(gcWindow),
    92  						GCInterval(gcInterval),
    93  						WatchBufferLength(watchBufferLength),
    94  						DebugAnalyzeBeforeStatistics(),
    95  						MigrationPhase(config.migrationPhase),
    96  					)
    97  					require.NoError(t, err)
    98  					return ds
    99  				})
   100  				return ds, nil
   101  			}))
   102  
   103  			t.Run("TransactionTimestamps", createDatastoreTest(
   104  				b,
   105  				TransactionTimestampsTest,
   106  				RevisionQuantization(0),
   107  				GCWindow(1*time.Millisecond),
   108  				GCInterval(veryLargeGCInterval),
   109  				WatchBufferLength(1),
   110  				MigrationPhase(config.migrationPhase),
   111  			))
   112  
   113  			t.Run("QuantizedRevisions", func(t *testing.T) {
   114  				QuantizedRevisionTest(t, b)
   115  			})
   116  
   117  			t.Run("WatchNotEnabled", func(t *testing.T) {
   118  				WatchNotEnabledTest(t, b, config.pgVersion)
   119  			})
   120  
   121  			t.Run("GCQueriesServedByExpectedIndexes", func(t *testing.T) {
   122  				GCQueriesServedByExpectedIndexes(t, b, config.pgVersion)
   123  			})
   124  
   125  			if config.migrationPhase == "" {
   126  				t.Run("RevisionInversion", createDatastoreTest(
   127  					b,
   128  					RevisionInversionTest,
   129  					RevisionQuantization(0),
   130  					GCWindow(1*time.Millisecond),
   131  					WatchBufferLength(1),
   132  					MigrationPhase(config.migrationPhase),
   133  				))
   134  
   135  				t.Run("ConcurrentRevisionHead", createDatastoreTest(
   136  					b,
   137  					ConcurrentRevisionHeadTest,
   138  					RevisionQuantization(0),
   139  					GCWindow(1*time.Millisecond),
   140  					WatchBufferLength(1),
   141  					MigrationPhase(config.migrationPhase),
   142  				))
   143  
   144  				t.Run("ConcurrentRevisionWatch", createDatastoreTest(
   145  					b,
   146  					ConcurrentRevisionWatchTest,
   147  					RevisionQuantization(0),
   148  					GCWindow(1*time.Millisecond),
   149  					WatchBufferLength(50),
   150  					MigrationPhase(config.migrationPhase),
   151  				))
   152  
   153  				t.Run("OverlappingRevisionWatch", createDatastoreTest(
   154  					b,
   155  					OverlappingRevisionWatchTest,
   156  					RevisionQuantization(0),
   157  					GCWindow(1*time.Millisecond),
   158  					WatchBufferLength(50),
   159  					MigrationPhase(config.migrationPhase),
   160  				))
   161  
   162  				t.Run("RepairTransactionsTest", createDatastoreTest(
   163  					b,
   164  					RepairTransactionsTest,
   165  					RevisionQuantization(0),
   166  					GCWindow(1*time.Millisecond),
   167  					WatchBufferLength(1),
   168  					MigrationPhase(config.migrationPhase),
   169  				))
   170  
   171  				t.Run("TestNullCaveatWatch", createDatastoreTest(
   172  					b,
   173  					NullCaveatWatchTest,
   174  					RevisionQuantization(0),
   175  					GCWindow(1*time.Millisecond),
   176  					WatchBufferLength(50),
   177  					MigrationPhase(config.migrationPhase),
   178  				))
   179  			}
   180  
   181  			t.Run("OTelTracing", createDatastoreTest(
   182  				b,
   183  				OTelTracingTest,
   184  				RevisionQuantization(0),
   185  				GCWindow(1*time.Millisecond),
   186  				WatchBufferLength(1),
   187  				MigrationPhase(config.migrationPhase),
   188  			))
   189  		})
   190  	}
   191  }
   192  
   193  func testPostgresDatastoreWithoutCommitTimestamps(t *testing.T, pc []postgresConfig) {
   194  	for _, config := range pc {
   195  		pgVersion := config.pgVersion
   196  		enablePgbouncer := config.pgbouncer
   197  		t.Run(fmt.Sprintf("postgres-%s", pgVersion), func(t *testing.T) {
   198  			t.Parallel()
   199  
   200  			ctx := context.Background()
   201  			b := testdatastore.RunPostgresForTestingWithCommitTimestamps(t, "", "head", false, pgVersion, enablePgbouncer)
   202  
   203  			// NOTE: watch API requires the commit timestamps, so we skip those tests here.
   204  			test.AllWithExceptions(t, test.DatastoreTesterFunc(func(revisionQuantization, gcInterval, gcWindow time.Duration, watchBufferLength uint16) (datastore.Datastore, error) {
   205  				ds := b.NewDatastore(t, func(engine, uri string) datastore.Datastore {
   206  					ds, err := newPostgresDatastore(ctx, uri,
   207  						RevisionQuantization(revisionQuantization),
   208  						GCWindow(gcWindow),
   209  						GCInterval(gcInterval),
   210  						WatchBufferLength(watchBufferLength),
   211  						DebugAnalyzeBeforeStatistics(),
   212  					)
   213  					require.NoError(t, err)
   214  					return ds
   215  				})
   216  				return ds, nil
   217  			}), test.WithCategories(test.WatchCategory))
   218  		})
   219  	}
   220  }
   221  
   222  type datastoreTestFunc func(t *testing.T, ds datastore.Datastore)
   223  
   224  func createDatastoreTest(b testdatastore.RunningEngineForTest, tf datastoreTestFunc, options ...Option) func(*testing.T) {
   225  	return func(t *testing.T) {
   226  		ctx := context.Background()
   227  		ds := b.NewDatastore(t, func(engine, uri string) datastore.Datastore {
   228  			ds, err := newPostgresDatastore(ctx, uri, options...)
   229  			require.NoError(t, err)
   230  			return ds
   231  		})
   232  		defer ds.Close()
   233  
   234  		tf(t, ds)
   235  	}
   236  }
   237  
   238  func GarbageCollectionTest(t *testing.T, ds datastore.Datastore) {
   239  	require := require.New(t)
   240  
   241  	ctx := context.Background()
   242  	r, err := ds.ReadyState(ctx)
   243  	require.NoError(err)
   244  	require.True(r.IsReady)
   245  	firstWrite, err := ds.ReadWriteTx(ctx, func(ctx context.Context, rwt datastore.ReadWriteTransaction) error {
   246  		// Write basic namespaces.
   247  		return rwt.WriteNamespaces(ctx, namespace.Namespace(
   248  			"resource",
   249  			namespace.MustRelation("reader", nil),
   250  		), namespace.Namespace("user"))
   251  	})
   252  	require.NoError(err)
   253  
   254  	// Run GC at the transaction and ensure no relationships are removed.
   255  	pds := ds.(*pgDatastore)
   256  
   257  	// Nothing to GC
   258  	removed, err := pds.DeleteBeforeTx(ctx, firstWrite)
   259  	require.NoError(err)
   260  	require.Zero(removed.Relationships)
   261  	require.Zero(removed.Namespaces)
   262  
   263  	// Replace the namespace with a new one.
   264  	updateTwoNamespaces, err := ds.ReadWriteTx(ctx, func(ctx context.Context, rwt datastore.ReadWriteTransaction) error {
   265  		return rwt.WriteNamespaces(
   266  			ctx,
   267  			namespace.Namespace(
   268  				"resource",
   269  				namespace.MustRelation("reader", nil),
   270  				namespace.MustRelation("unused", nil),
   271  			),
   272  			namespace.Namespace("user"),
   273  		)
   274  	})
   275  	require.NoError(err)
   276  
   277  	// Run GC to remove the old transaction
   278  	removed, err = pds.DeleteBeforeTx(ctx, updateTwoNamespaces)
   279  	require.NoError(err)
   280  	require.Zero(removed.Relationships)
   281  	require.Equal(int64(1), removed.Transactions) // firstWrite
   282  	require.Equal(int64(2), removed.Namespaces)   // resource, user
   283  
   284  	// Write a relationship.
   285  	tpl := tuple.Parse("resource:someresource#reader@user:someuser#...")
   286  
   287  	wroteOneRelationship, err := common.WriteTuples(ctx, ds, core.RelationTupleUpdate_CREATE, tpl)
   288  	require.NoError(err)
   289  
   290  	// Run GC at the transaction and ensure no relationships are removed, but 1 transaction (the previous write namespace) is.
   291  	removed, err = pds.DeleteBeforeTx(ctx, wroteOneRelationship)
   292  	require.NoError(err)
   293  	require.Zero(removed.Relationships)
   294  	require.Equal(int64(1), removed.Transactions) // updateTwoNamespaces
   295  	require.Zero(removed.Namespaces)
   296  
   297  	// Run GC again and ensure there are no changes.
   298  	removed, err = pds.DeleteBeforeTx(ctx, wroteOneRelationship)
   299  	require.NoError(err)
   300  	require.Zero(removed.Relationships)
   301  	require.Zero(removed.Transactions)
   302  	require.Zero(removed.Namespaces)
   303  
   304  	// Ensure the relationship is still present.
   305  	tRequire := testfixtures.TupleChecker{Require: require, DS: ds}
   306  	tRequire.TupleExists(ctx, tpl, wroteOneRelationship)
   307  
   308  	// Overwrite the relationship by changing its caveat.
   309  	tpl = tuple.MustWithCaveat(tpl, "somecaveat")
   310  	relOverwrittenAt, err := common.WriteTuples(ctx, ds, core.RelationTupleUpdate_TOUCH, tpl)
   311  	require.NoError(err)
   312  
   313  	// Run GC, which won't clean anything because we're dropping the write transaction only
   314  	removed, err = pds.DeleteBeforeTx(ctx, relOverwrittenAt)
   315  	require.NoError(err)
   316  	require.Equal(int64(1), removed.Relationships) // wroteOneRelationship
   317  	require.Equal(int64(1), removed.Transactions)  // wroteOneRelationship
   318  	require.Zero(removed.Namespaces)
   319  
   320  	// Run GC again and ensure there are no changes.
   321  	removed, err = pds.DeleteBeforeTx(ctx, relOverwrittenAt)
   322  	require.NoError(err)
   323  	require.Zero(removed.Relationships)
   324  	require.Zero(removed.Transactions)
   325  	require.Zero(removed.Namespaces)
   326  
   327  	// Ensure the relationship is still present.
   328  	tRequire.TupleExists(ctx, tpl, relOverwrittenAt)
   329  
   330  	// Delete the relationship.
   331  	relDeletedAt, err := common.WriteTuples(ctx, ds, core.RelationTupleUpdate_DELETE, tpl)
   332  	require.NoError(err)
   333  
   334  	// Ensure the relationship is gone.
   335  	tRequire.NoTupleExists(ctx, tpl, relDeletedAt)
   336  
   337  	// Run GC, which will now drop the overwrite transaction only and the first tpl revision
   338  	removed, err = pds.DeleteBeforeTx(ctx, relDeletedAt)
   339  	require.NoError(err)
   340  	require.Equal(int64(1), removed.Relationships)
   341  	require.Equal(int64(1), removed.Transactions) // relOverwrittenAt
   342  	require.Zero(removed.Namespaces)
   343  
   344  	// Run GC again and ensure there are no changes.
   345  	removed, err = pds.DeleteBeforeTx(ctx, relDeletedAt)
   346  	require.NoError(err)
   347  	require.Zero(removed.Relationships)
   348  	require.Zero(removed.Transactions)
   349  	require.Zero(removed.Namespaces)
   350  
   351  	// Write a the relationship a few times.
   352  	var relLastWriteAt datastore.Revision
   353  	for i := 0; i < 3; i++ {
   354  		tpl = tuple.MustWithCaveat(tpl, fmt.Sprintf("somecaveat%d", i))
   355  
   356  		var err error
   357  		relLastWriteAt, err = common.WriteTuples(ctx, ds, core.RelationTupleUpdate_TOUCH, tpl)
   358  		require.NoError(err)
   359  	}
   360  
   361  	// Run GC at the transaction and ensure the older copies of the relationships are removed,
   362  	// as well as the 2 older write transactions and the older delete transaction.
   363  	removed, err = pds.DeleteBeforeTx(ctx, relLastWriteAt)
   364  	require.NoError(err)
   365  	require.Equal(int64(2), removed.Relationships) // delete, old1
   366  	require.Equal(int64(3), removed.Transactions)  // removed, write1, write2
   367  	require.Zero(removed.Namespaces)
   368  
   369  	// Ensure the relationship is still present.
   370  	tRequire.TupleExists(ctx, tpl, relLastWriteAt)
   371  
   372  	// Inject a transaction to clean up the last write
   373  	lastRev, err := pds.ReadWriteTx(ctx, func(ctx context.Context, rwt datastore.ReadWriteTransaction) error {
   374  		return nil
   375  	})
   376  	require.NoError(err)
   377  
   378  	// Run GC to clean up the last write
   379  	removed, err = pds.DeleteBeforeTx(ctx, lastRev)
   380  	require.NoError(err)
   381  	require.Zero(removed.Relationships)           // write3
   382  	require.Equal(int64(1), removed.Transactions) // write3
   383  	require.Zero(removed.Namespaces)
   384  }
   385  
   386  func TransactionTimestampsTest(t *testing.T, ds datastore.Datastore) {
   387  	require := require.New(t)
   388  
   389  	ctx := context.Background()
   390  	r, err := ds.ReadyState(ctx)
   391  	require.NoError(err)
   392  	require.True(r.IsReady)
   393  
   394  	// Setting db default time zone to before UTC
   395  	pgd := ds.(*pgDatastore)
   396  	_, err = pgd.writePool.Exec(ctx, "SET TIME ZONE 'America/New_York';")
   397  	require.NoError(err)
   398  
   399  	// Get timestamp in UTC as reference
   400  	startTimeUTC, err := pgd.Now(ctx)
   401  	require.NoError(err)
   402  
   403  	// Transaction timestamp should not be stored in system time zone
   404  	tx, err := pgd.writePool.Begin(ctx)
   405  	require.NoError(err)
   406  
   407  	txXID, _, err := createNewTransaction(ctx, tx)
   408  	require.NoError(err)
   409  
   410  	err = tx.Commit(ctx)
   411  	require.NoError(err)
   412  
   413  	var ts time.Time
   414  	sql, args, err := psql.Select("timestamp").From(tableTransaction).Where(sq.Eq{"xid": txXID}).ToSql()
   415  	require.NoError(err)
   416  	err = pgd.readPool.QueryRow(ctx, sql, args...).Scan(&ts)
   417  	require.NoError(err)
   418  
   419  	// Transaction timestamp will be before the reference time if it was stored
   420  	// in the default time zone and reinterpreted
   421  	require.True(startTimeUTC.Before(ts))
   422  }
   423  
   424  func GarbageCollectionByTimeTest(t *testing.T, ds datastore.Datastore) {
   425  	require := require.New(t)
   426  
   427  	ctx := context.Background()
   428  	r, err := ds.ReadyState(ctx)
   429  	require.NoError(err)
   430  	require.True(r.IsReady)
   431  	// Write basic namespaces.
   432  	_, err = ds.ReadWriteTx(ctx, func(ctx context.Context, rwt datastore.ReadWriteTransaction) error {
   433  		return rwt.WriteNamespaces(ctx, namespace.Namespace(
   434  			"resource",
   435  			namespace.MustRelation("reader", nil),
   436  		), namespace.Namespace("user"))
   437  	})
   438  	require.NoError(err)
   439  
   440  	pds := ds.(*pgDatastore)
   441  
   442  	// Sleep 1ms to ensure GC will delete the previous transaction.
   443  	time.Sleep(1 * time.Millisecond)
   444  
   445  	// Write a relationship.
   446  	tpl := tuple.Parse("resource:someresource#reader@user:someuser#...")
   447  	relLastWriteAt, err := common.WriteTuples(ctx, ds, core.RelationTupleUpdate_CREATE, tpl)
   448  	require.NoError(err)
   449  
   450  	// Run GC and ensure only transactions were removed.
   451  	afterWrite, err := pds.Now(ctx)
   452  	require.NoError(err)
   453  
   454  	afterWriteTx, err := pds.TxIDBefore(ctx, afterWrite)
   455  	require.NoError(err)
   456  
   457  	removed, err := pds.DeleteBeforeTx(ctx, afterWriteTx)
   458  	require.NoError(err)
   459  	require.Zero(removed.Relationships)
   460  	require.True(removed.Transactions > 0)
   461  	require.Zero(removed.Namespaces)
   462  
   463  	// Ensure the relationship is still present.
   464  	tRequire := testfixtures.TupleChecker{Require: require, DS: ds}
   465  	tRequire.TupleExists(ctx, tpl, relLastWriteAt)
   466  
   467  	// Sleep 1ms to ensure GC will delete the previous write.
   468  	time.Sleep(1 * time.Millisecond)
   469  
   470  	// Delete the relationship.
   471  	relDeletedAt, err := common.WriteTuples(ctx, ds, core.RelationTupleUpdate_DELETE, tpl)
   472  	require.NoError(err)
   473  
   474  	// Inject a revision to sweep up the last revision
   475  	_, err = pds.ReadWriteTx(ctx, func(ctx context.Context, rwt datastore.ReadWriteTransaction) error {
   476  		return nil
   477  	})
   478  	require.NoError(err)
   479  
   480  	// Run GC and ensure the relationship is not removed.
   481  	afterDelete, err := pds.Now(ctx)
   482  	require.NoError(err)
   483  
   484  	afterDeleteTx, err := pds.TxIDBefore(ctx, afterDelete)
   485  	require.NoError(err)
   486  
   487  	removed, err = pds.DeleteBeforeTx(ctx, afterDeleteTx)
   488  	require.NoError(err)
   489  	require.Equal(int64(1), removed.Relationships)
   490  	require.Equal(int64(2), removed.Transactions) // relDeletedAt, injected
   491  	require.Zero(removed.Namespaces)
   492  
   493  	// Ensure the relationship is still not present.
   494  	tRequire.NoTupleExists(ctx, tpl, relDeletedAt)
   495  }
   496  
   497  const chunkRelationshipCount = 2000
   498  
   499  func ChunkedGarbageCollectionTest(t *testing.T, ds datastore.Datastore) {
   500  	require := require.New(t)
   501  
   502  	ctx := context.Background()
   503  	r, err := ds.ReadyState(ctx)
   504  	require.NoError(err)
   505  	require.True(r.IsReady)
   506  	// Write basic namespaces.
   507  	_, err = ds.ReadWriteTx(ctx, func(ctx context.Context, rwt datastore.ReadWriteTransaction) error {
   508  		return rwt.WriteNamespaces(ctx, namespace.Namespace(
   509  			"resource",
   510  			namespace.MustRelation("reader", nil),
   511  		), namespace.Namespace("user"))
   512  	})
   513  	require.NoError(err)
   514  
   515  	pds := ds.(*pgDatastore)
   516  
   517  	// Prepare relationships to write.
   518  	var tpls []*core.RelationTuple
   519  	for i := 0; i < chunkRelationshipCount; i++ {
   520  		tpl := tuple.Parse(fmt.Sprintf("resource:resource-%d#reader@user:someuser#...", i))
   521  		tpls = append(tpls, tpl)
   522  	}
   523  
   524  	// Write a large number of relationships.
   525  	writtenAt, err := common.WriteTuples(ctx, ds, core.RelationTupleUpdate_CREATE, tpls...)
   526  	require.NoError(err)
   527  
   528  	// Ensure the relationships were written.
   529  	tRequire := testfixtures.TupleChecker{Require: require, DS: ds}
   530  	for _, tpl := range tpls {
   531  		tRequire.TupleExists(ctx, tpl, writtenAt)
   532  	}
   533  
   534  	// Run GC and ensure only transactions were removed.
   535  	afterWrite, err := pds.Now(ctx)
   536  	require.NoError(err)
   537  
   538  	afterWriteTx, err := pds.TxIDBefore(ctx, afterWrite)
   539  	require.NoError(err)
   540  
   541  	removed, err := pds.DeleteBeforeTx(ctx, afterWriteTx)
   542  	require.NoError(err)
   543  	require.Zero(removed.Relationships)
   544  	require.True(removed.Transactions > 0)
   545  	require.Zero(removed.Namespaces)
   546  
   547  	// Sleep to ensure the relationships will GC.
   548  	time.Sleep(1 * time.Millisecond)
   549  
   550  	// Delete all the relationships.
   551  	deletedAt, err := common.WriteTuples(ctx, ds, core.RelationTupleUpdate_DELETE, tpls...)
   552  	require.NoError(err)
   553  
   554  	// Inject a revision to sweep up the last revision
   555  	_, err = pds.ReadWriteTx(ctx, func(ctx context.Context, rwt datastore.ReadWriteTransaction) error {
   556  		return nil
   557  	})
   558  	require.NoError(err)
   559  
   560  	// Ensure the relationships were deleted.
   561  	for _, tpl := range tpls {
   562  		tRequire.NoTupleExists(ctx, tpl, deletedAt)
   563  	}
   564  
   565  	// Sleep to ensure GC.
   566  	time.Sleep(1 * time.Millisecond)
   567  
   568  	// Run GC and ensure all the stale relationships are removed.
   569  	afterDelete, err := pds.Now(ctx)
   570  	require.NoError(err)
   571  
   572  	afterDeleteTx, err := pds.TxIDBefore(ctx, afterDelete)
   573  	require.NoError(err)
   574  
   575  	removed, err = pds.DeleteBeforeTx(ctx, afterDeleteTx)
   576  	require.NoError(err)
   577  	require.Equal(int64(chunkRelationshipCount), removed.Relationships)
   578  	require.Equal(int64(2), removed.Transactions)
   579  	require.Zero(removed.Namespaces)
   580  }
   581  
   582  func QuantizedRevisionTest(t *testing.T, b testdatastore.RunningEngineForTest) {
   583  	testCases := []struct {
   584  		testName      string
   585  		quantization  time.Duration
   586  		relativeTimes []time.Duration
   587  		numLower      uint64
   588  		numHigher     uint64
   589  	}{
   590  		{
   591  			"DefaultRevision",
   592  			1 * time.Second,
   593  			[]time.Duration{},
   594  			0, 0,
   595  		},
   596  		{
   597  			"OnlyPastRevisions",
   598  			1 * time.Second,
   599  			[]time.Duration{-2 * time.Second},
   600  			1, 0,
   601  		},
   602  		{
   603  			"OnlyFutureRevisions",
   604  			1 * time.Second,
   605  			[]time.Duration{2 * time.Second},
   606  			0, 1,
   607  		},
   608  		{
   609  			"QuantizedLower",
   610  			2 * time.Second,
   611  			[]time.Duration{-4 * time.Second, -1 * time.Nanosecond, 0},
   612  			1, 2,
   613  		},
   614  		{
   615  			"QuantizationDisabled",
   616  			1 * time.Nanosecond,
   617  			[]time.Duration{-2 * time.Second, -1 * time.Nanosecond, 0},
   618  			3, 0,
   619  		},
   620  	}
   621  
   622  	for _, tc := range testCases {
   623  		t.Run(tc.testName, func(t *testing.T) {
   624  			require := require.New(t)
   625  			ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
   626  			defer cancel()
   627  
   628  			var conn *pgx.Conn
   629  			ds := b.NewDatastore(t, func(engine, uri string) datastore.Datastore {
   630  				var err error
   631  				conn, err = pgx.Connect(ctx, uri)
   632  				RegisterTypes(conn.TypeMap())
   633  				require.NoError(err)
   634  
   635  				ds, err := newPostgresDatastore(
   636  					ctx,
   637  					uri,
   638  					RevisionQuantization(5*time.Second),
   639  					GCWindow(24*time.Hour),
   640  					WatchBufferLength(1),
   641  				)
   642  				require.NoError(err)
   643  
   644  				return ds
   645  			})
   646  			defer ds.Close()
   647  
   648  			// set a random time zone to ensure the queries are unaffected by tz
   649  			_, err := conn.Exec(ctx, fmt.Sprintf("SET TIME ZONE -%d", rand.Intn(8)+1))
   650  			require.NoError(err)
   651  
   652  			var dbNow time.Time
   653  			err = conn.QueryRow(ctx, "SELECT (NOW() AT TIME ZONE 'utc')").Scan(&dbNow)
   654  			require.NoError(err)
   655  
   656  			if len(tc.relativeTimes) > 0 {
   657  				psql := sq.StatementBuilder.PlaceholderFormat(sq.Dollar)
   658  				insertTxn := psql.Insert(tableTransaction).Columns(colTimestamp)
   659  
   660  				for _, offset := range tc.relativeTimes {
   661  					sql, args, err := insertTxn.Values(dbNow.Add(offset)).ToSql()
   662  					require.NoError(err)
   663  
   664  					_, err = conn.Exec(ctx, sql, args...)
   665  					require.NoError(err)
   666  				}
   667  			}
   668  
   669  			queryRevision := fmt.Sprintf(
   670  				querySelectRevision,
   671  				colXID,
   672  				tableTransaction,
   673  				colTimestamp,
   674  				tc.quantization.Nanoseconds(),
   675  				colSnapshot,
   676  			)
   677  
   678  			var revision xid8
   679  			var snapshot pgSnapshot
   680  			var validFor time.Duration
   681  			err = conn.QueryRow(ctx, queryRevision).Scan(&revision, &snapshot, &validFor)
   682  			require.NoError(err)
   683  
   684  			queryFmt := "SELECT COUNT(%[1]s) FROM %[2]s WHERE pg_visible_in_snapshot(%[1]s, $1) = %[3]s;"
   685  			numLowerQuery := fmt.Sprintf(queryFmt, colXID, tableTransaction, "true")
   686  			numHigherQuery := fmt.Sprintf(queryFmt, colXID, tableTransaction, "false")
   687  
   688  			var numLower, numHigher uint64
   689  			require.NoError(conn.QueryRow(ctx, numLowerQuery, snapshot).Scan(&numLower), "%s - %s", revision, snapshot)
   690  			require.NoError(conn.QueryRow(ctx, numHigherQuery, snapshot).Scan(&numHigher), "%s - %s", revision, snapshot)
   691  
   692  			// Subtract one from numLower because of the artificially injected first transaction row
   693  			require.Equal(tc.numLower, numLower-1)
   694  			require.Equal(tc.numHigher, numHigher)
   695  		})
   696  	}
   697  }
   698  
   699  // ConcurrentRevisionHeadTest uses goroutines and channels to intentionally set up a pair of
   700  // revisions that are concurrently applied and then ensures a call to HeadRevision reflects
   701  // the changes found in *both* revisions.
   702  func ConcurrentRevisionHeadTest(t *testing.T, ds datastore.Datastore) {
   703  	require := require.New(t)
   704  
   705  	ctx := context.Background()
   706  	r, err := ds.ReadyState(ctx)
   707  	require.NoError(err)
   708  	require.True(r.IsReady)
   709  	// Write basic namespaces.
   710  	_, err = ds.ReadWriteTx(ctx, func(ctx context.Context, rwt datastore.ReadWriteTransaction) error {
   711  		return rwt.WriteNamespaces(ctx, namespace.Namespace(
   712  			"resource",
   713  			namespace.MustRelation("reader", nil),
   714  		), namespace.Namespace("user"))
   715  	})
   716  	require.NoError(err)
   717  
   718  	g := errgroup.Group{}
   719  
   720  	waitToStart := make(chan struct{})
   721  	waitToFinish := make(chan struct{})
   722  
   723  	var commitLastRev, commitFirstRev datastore.Revision
   724  	g.Go(func() error {
   725  		var err error
   726  		commitLastRev, err = ds.ReadWriteTx(ctx, func(ctx context.Context, rwt datastore.ReadWriteTransaction) error {
   727  			rtu := tuple.Touch(&core.RelationTuple{
   728  				ResourceAndRelation: &core.ObjectAndRelation{
   729  					Namespace: "resource",
   730  					ObjectId:  "123",
   731  					Relation:  "reader",
   732  				},
   733  				Subject: &core.ObjectAndRelation{
   734  					Namespace: "user",
   735  					ObjectId:  "456",
   736  					Relation:  "...",
   737  				},
   738  			})
   739  			err = rwt.WriteRelationships(ctx, []*core.RelationTupleUpdate{rtu})
   740  			require.NoError(err)
   741  
   742  			close(waitToStart)
   743  			<-waitToFinish
   744  
   745  			return err
   746  		})
   747  		require.NoError(err)
   748  		return nil
   749  	})
   750  
   751  	<-waitToStart
   752  
   753  	commitFirstRev, err = ds.ReadWriteTx(ctx, func(ctx context.Context, rwt datastore.ReadWriteTransaction) error {
   754  		rtu := tuple.Touch(&core.RelationTuple{
   755  			ResourceAndRelation: &core.ObjectAndRelation{
   756  				Namespace: "resource",
   757  				ObjectId:  "789",
   758  				Relation:  "reader",
   759  			},
   760  			Subject: &core.ObjectAndRelation{
   761  				Namespace: "user",
   762  				ObjectId:  "456",
   763  				Relation:  "...",
   764  			},
   765  		})
   766  		return rwt.WriteRelationships(ctx, []*core.RelationTupleUpdate{rtu})
   767  	})
   768  	close(waitToFinish)
   769  
   770  	require.NoError(err)
   771  	require.NoError(g.Wait())
   772  
   773  	// Ensure the revisions do not compare.
   774  	require.False(commitFirstRev.GreaterThan(commitLastRev))
   775  	require.False(commitFirstRev.Equal(commitLastRev))
   776  
   777  	// Ensure a call to HeadRevision now reflects both sets of data applied.
   778  	headRev, err := ds.HeadRevision(ctx)
   779  	require.NoError(err)
   780  
   781  	reader := ds.SnapshotReader(headRev)
   782  	it, err := reader.QueryRelationships(ctx, datastore.RelationshipsFilter{
   783  		OptionalResourceType: "resource",
   784  	})
   785  	require.NoError(err)
   786  	defer it.Close()
   787  
   788  	found := []*core.RelationTuple{}
   789  	for tpl := it.Next(); tpl != nil; tpl = it.Next() {
   790  		require.NoError(it.Err())
   791  		found = append(found, tpl)
   792  	}
   793  
   794  	require.Equal(2, len(found), "missing relationships in %v", found)
   795  }
   796  
   797  // ConcurrentRevisionWatchTest uses goroutines and channels to intentionally set up a pair of
   798  // revisions that are concurrently applied and then ensures that a Watch call does not end up
   799  // in a loop.
   800  func ConcurrentRevisionWatchTest(t *testing.T, ds datastore.Datastore) {
   801  	require := require.New(t)
   802  
   803  	ctx := context.Background()
   804  	withCancel, cancel := context.WithCancel(ctx)
   805  	defer cancel()
   806  
   807  	r, err := ds.ReadyState(ctx)
   808  	require.NoError(err)
   809  	require.True(r.IsReady)
   810  
   811  	// Write basic namespaces.
   812  	rev, err := ds.ReadWriteTx(ctx, func(ctx context.Context, rwt datastore.ReadWriteTransaction) error {
   813  		return rwt.WriteNamespaces(ctx, namespace.Namespace(
   814  			"resource",
   815  			namespace.MustRelation("reader", nil),
   816  		), namespace.Namespace("user"))
   817  	})
   818  	require.NoError(err)
   819  
   820  	// Start a watch loop.
   821  	waitForWatch := make(chan struct{})
   822  	seenWatchRevisions := make([]datastore.Revision, 0)
   823  	seenWatchRevisionsLock := sync.Mutex{}
   824  
   825  	go func() {
   826  		changes, _ := ds.Watch(withCancel, rev, datastore.WatchJustRelationships())
   827  
   828  		waitForWatch <- struct{}{}
   829  
   830  		for {
   831  			select {
   832  			case change, ok := <-changes:
   833  				if !ok {
   834  					return
   835  				}
   836  
   837  				seenWatchRevisionsLock.Lock()
   838  				seenWatchRevisions = append(seenWatchRevisions, change.Revision)
   839  				seenWatchRevisionsLock.Unlock()
   840  
   841  				time.Sleep(1 * time.Millisecond)
   842  			case <-withCancel.Done():
   843  				return
   844  			}
   845  		}
   846  	}()
   847  
   848  	<-waitForWatch
   849  
   850  	// Write the two concurrent transactions, while watching for changes.
   851  	g := errgroup.Group{}
   852  
   853  	waitToStart := make(chan struct{})
   854  	waitToFinish := make(chan struct{})
   855  
   856  	var commitLastRev, commitFirstRev datastore.Revision
   857  	g.Go(func() error {
   858  		var err error
   859  		commitLastRev, err = ds.ReadWriteTx(ctx, func(ctx context.Context, rwt datastore.ReadWriteTransaction) error {
   860  			err = rwt.WriteRelationships(ctx, []*core.RelationTupleUpdate{
   861  				tuple.Touch(tuple.MustParse("something:001#viewer@user:123")),
   862  				tuple.Touch(tuple.MustParse("something:002#viewer@user:123")),
   863  				tuple.Touch(tuple.MustParse("something:003#viewer@user:123")),
   864  			})
   865  			require.NoError(err)
   866  
   867  			close(waitToStart)
   868  			<-waitToFinish
   869  
   870  			return err
   871  		})
   872  		require.NoError(err)
   873  		return nil
   874  	})
   875  
   876  	<-waitToStart
   877  
   878  	commitFirstRev, err = ds.ReadWriteTx(ctx, func(ctx context.Context, rwt datastore.ReadWriteTransaction) error {
   879  		return rwt.WriteRelationships(ctx, []*core.RelationTupleUpdate{
   880  			tuple.Touch(tuple.MustParse("resource:1001#reader@user:456")),
   881  			tuple.Touch(tuple.MustParse("resource:1002#reader@user:456")),
   882  			tuple.Touch(tuple.MustParse("resource:1003#reader@user:456")),
   883  		})
   884  	})
   885  	close(waitToFinish)
   886  
   887  	require.NoError(err)
   888  	require.NoError(g.Wait())
   889  
   890  	// Ensure the revisions do not compare.
   891  	require.False(commitFirstRev.GreaterThan(commitLastRev))
   892  	require.False(commitLastRev.GreaterThan(commitFirstRev))
   893  	require.False(commitFirstRev.Equal(commitLastRev))
   894  
   895  	// Write another revision.
   896  	afterRev, err := ds.ReadWriteTx(ctx, func(ctx context.Context, rwt datastore.ReadWriteTransaction) error {
   897  		rtu := tuple.Touch(&core.RelationTuple{
   898  			ResourceAndRelation: &core.ObjectAndRelation{
   899  				Namespace: "resource",
   900  				ObjectId:  "2345",
   901  				Relation:  "reader",
   902  			},
   903  			Subject: &core.ObjectAndRelation{
   904  				Namespace: "user",
   905  				ObjectId:  "456",
   906  				Relation:  "...",
   907  			},
   908  		})
   909  		return rwt.WriteRelationships(ctx, []*core.RelationTupleUpdate{rtu})
   910  	})
   911  	require.NoError(err)
   912  	require.True(afterRev.GreaterThan(commitFirstRev))
   913  	require.True(afterRev.GreaterThan(commitLastRev))
   914  
   915  	// Ensure that the last revision is eventually seen from the watch.
   916  	require.Eventually(func() bool {
   917  		seenWatchRevisionsLock.Lock()
   918  		defer seenWatchRevisionsLock.Unlock()
   919  		return len(seenWatchRevisions) == 3 && seenWatchRevisions[len(seenWatchRevisions)-1].String() == afterRev.String()
   920  	}, 2*time.Second, 5*time.Millisecond)
   921  }
   922  
   923  func OverlappingRevisionWatchTest(t *testing.T, ds datastore.Datastore) {
   924  	require := require.New(t)
   925  
   926  	ctx := context.Background()
   927  	r, err := ds.ReadyState(ctx)
   928  	require.NoError(err)
   929  	require.True(r.IsReady)
   930  
   931  	rev, err := ds.HeadRevision(ctx)
   932  	require.NoError(err)
   933  
   934  	pds := ds.(*pgDatastore)
   935  	require.True(pds.watchEnabled)
   936  
   937  	prev := rev.(postgresRevision)
   938  	nexttx := prev.snapshot.xmax + 1
   939  
   940  	// Manually construct an equivalent of overlapping transactions in the database, from the repro
   941  	// information (See: https://github.com/authzed/spicedb/issues/1272)
   942  	err = pgx.BeginTxFunc(ctx, pds.writePool, pgx.TxOptions{IsoLevel: pgx.Serializable}, func(tx pgx.Tx) error {
   943  		_, err := tx.Exec(ctx, fmt.Sprintf(
   944  			`INSERT INTO %s ("%s", "%s") VALUES ('%d', '%d:%d:')`,
   945  			tableTransaction,
   946  			colXID,
   947  			colSnapshot,
   948  			nexttx,
   949  			nexttx,
   950  			nexttx,
   951  		))
   952  		if err != nil {
   953  			return err
   954  		}
   955  
   956  		_, err = tx.Exec(ctx, fmt.Sprintf(
   957  			`INSERT INTO %s ("%s", "%s", "%s", "%s", "%s", "%s", "%s", "%s", "%s") VALUES ('somenamespace', '123', 'viewer', 'user', '456', '...', '', null, '%d'::xid8)`,
   958  			tableTuple,
   959  			colNamespace,
   960  			colObjectID,
   961  			colRelation,
   962  			colUsersetNamespace,
   963  			colUsersetObjectID,
   964  			colUsersetRelation,
   965  			colCaveatContextName,
   966  			colCaveatContext,
   967  			colCreatedXid,
   968  			nexttx,
   969  		))
   970  		if err != nil {
   971  			return err
   972  		}
   973  
   974  		_, err = tx.Exec(ctx, fmt.Sprintf(
   975  			`INSERT INTO %s ("xid", "snapshot") VALUES ('%d', '%d:%d:')`,
   976  			tableTransaction,
   977  			nexttx+1,
   978  			nexttx,
   979  			nexttx,
   980  		))
   981  		if err != nil {
   982  			return err
   983  		}
   984  
   985  		_, err = tx.Exec(ctx, fmt.Sprintf(
   986  			`INSERT INTO %s ("%s", "%s", "%s", "%s", "%s", "%s", "%s", "%s", "%s") VALUES ('somenamespace', '456', 'viewer', 'user', '456', '...', '', null, '%d'::xid8)`,
   987  			tableTuple,
   988  			colNamespace,
   989  			colObjectID,
   990  			colRelation,
   991  			colUsersetNamespace,
   992  			colUsersetObjectID,
   993  			colUsersetRelation,
   994  			colCaveatContextName,
   995  			colCaveatContext,
   996  			colCreatedXid,
   997  			nexttx+1,
   998  		))
   999  
  1000  		return err
  1001  	})
  1002  	require.NoError(err)
  1003  
  1004  	// Call watch and ensure it terminates with having only read the two expected sets of changes.
  1005  	changes, errChan := ds.Watch(ctx, rev, datastore.WatchJustRelationships())
  1006  	transactionCount := 0
  1007  loop:
  1008  	for {
  1009  		select {
  1010  		case _, ok := <-changes:
  1011  			if !ok {
  1012  				err := <-errChan
  1013  				require.NoError(err)
  1014  				return
  1015  			}
  1016  
  1017  			transactionCount++
  1018  			time.Sleep(10 * time.Millisecond)
  1019  		case <-time.NewTimer(1 * time.Second).C:
  1020  			break loop
  1021  		}
  1022  	}
  1023  
  1024  	require.Equal(2, transactionCount)
  1025  }
  1026  
  1027  // RevisionInversionTest uses goroutines and channels to intentionally set up a pair of
  1028  // revisions that might compare incorrectly.
  1029  func RevisionInversionTest(t *testing.T, ds datastore.Datastore) {
  1030  	require := require.New(t)
  1031  
  1032  	ctx := context.Background()
  1033  	r, err := ds.ReadyState(ctx)
  1034  	require.NoError(err)
  1035  	require.True(r.IsReady)
  1036  	// Write basic namespaces.
  1037  	_, err = ds.ReadWriteTx(ctx, func(ctx context.Context, rwt datastore.ReadWriteTransaction) error {
  1038  		return rwt.WriteNamespaces(ctx, namespace.Namespace(
  1039  			"resource",
  1040  			namespace.MustRelation("reader", nil),
  1041  		), namespace.Namespace("user"))
  1042  	})
  1043  	require.NoError(err)
  1044  
  1045  	g := errgroup.Group{}
  1046  
  1047  	waitToStart := make(chan struct{})
  1048  	waitToFinish := make(chan struct{})
  1049  
  1050  	var commitLastRev, commitFirstRev datastore.Revision
  1051  	g.Go(func() error {
  1052  		var err error
  1053  		commitLastRev, err = ds.ReadWriteTx(ctx, func(ctx context.Context, rwt datastore.ReadWriteTransaction) error {
  1054  			rtu := tuple.Touch(&core.RelationTuple{
  1055  				ResourceAndRelation: &core.ObjectAndRelation{
  1056  					Namespace: "resource",
  1057  					ObjectId:  "123",
  1058  					Relation:  "reader",
  1059  				},
  1060  				Subject: &core.ObjectAndRelation{
  1061  					Namespace: "user",
  1062  					ObjectId:  "456",
  1063  					Relation:  "...",
  1064  				},
  1065  			})
  1066  			err = rwt.WriteRelationships(ctx, []*core.RelationTupleUpdate{rtu})
  1067  			require.NoError(err)
  1068  
  1069  			close(waitToStart)
  1070  			<-waitToFinish
  1071  
  1072  			return err
  1073  		})
  1074  		require.NoError(err)
  1075  		return nil
  1076  	})
  1077  
  1078  	<-waitToStart
  1079  
  1080  	commitFirstRev, err = ds.ReadWriteTx(ctx, func(ctx context.Context, rwt datastore.ReadWriteTransaction) error {
  1081  		rtu := tuple.Touch(&core.RelationTuple{
  1082  			ResourceAndRelation: &core.ObjectAndRelation{
  1083  				Namespace: "resource",
  1084  				ObjectId:  "789",
  1085  				Relation:  "reader",
  1086  			},
  1087  			Subject: &core.ObjectAndRelation{
  1088  				Namespace: "user",
  1089  				ObjectId:  "ten",
  1090  				Relation:  "...",
  1091  			},
  1092  		})
  1093  		return rwt.WriteRelationships(ctx, []*core.RelationTupleUpdate{rtu})
  1094  	})
  1095  	close(waitToFinish)
  1096  
  1097  	require.NoError(err)
  1098  	require.NoError(g.Wait())
  1099  	require.False(commitFirstRev.GreaterThan(commitLastRev))
  1100  	require.False(commitFirstRev.Equal(commitLastRev))
  1101  }
  1102  
  1103  func OTelTracingTest(t *testing.T, ds datastore.Datastore) {
  1104  	otelMutex.Lock()
  1105  	defer otelMutex.Unlock()
  1106  
  1107  	require := require.New(t)
  1108  
  1109  	ctx := context.Background()
  1110  	r, err := ds.ReadyState(ctx)
  1111  	require.NoError(err)
  1112  	require.True(r.IsReady)
  1113  
  1114  	spanrecorder := tracetest.NewSpanRecorder()
  1115  	testTraceProvider.RegisterSpanProcessor(spanrecorder)
  1116  
  1117  	// Perform basic operation
  1118  	_, err = ds.ReadWriteTx(ctx, func(ctx context.Context, rwt datastore.ReadWriteTransaction) error {
  1119  		return rwt.WriteNamespaces(ctx, namespace.Namespace("resource"))
  1120  	})
  1121  	require.NoError(err)
  1122  
  1123  	ended := spanrecorder.Ended()
  1124  	var present bool
  1125  	for _, span := range ended {
  1126  		if span.Name() == "query INSERT" {
  1127  			present = true
  1128  		}
  1129  	}
  1130  	require.True(present, "missing trace for Streaming gRPC call")
  1131  }
  1132  
  1133  func WatchNotEnabledTest(t *testing.T, _ testdatastore.RunningEngineForTest, pgVersion string) {
  1134  	require := require.New(t)
  1135  
  1136  	ds := testdatastore.RunPostgresForTestingWithCommitTimestamps(t, "", migrate.Head, false, pgVersion, false).NewDatastore(t, func(engine, uri string) datastore.Datastore {
  1137  		ctx := context.Background()
  1138  		ds, err := newPostgresDatastore(ctx, uri,
  1139  			RevisionQuantization(0),
  1140  			GCWindow(time.Millisecond*1),
  1141  			WatchBufferLength(1),
  1142  		)
  1143  		require.NoError(err)
  1144  		return ds
  1145  	})
  1146  	defer ds.Close()
  1147  
  1148  	ds, revision := testfixtures.StandardDatastoreWithData(ds, require)
  1149  	_, errChan := ds.Watch(
  1150  		context.Background(),
  1151  		revision,
  1152  		datastore.WatchJustRelationships(),
  1153  	)
  1154  	err := <-errChan
  1155  	require.NotNil(err)
  1156  	require.Contains(err.Error(), "track_commit_timestamp=on")
  1157  }
  1158  
  1159  func BenchmarkPostgresQuery(b *testing.B) {
  1160  	req := require.New(b)
  1161  
  1162  	ds := testdatastore.RunPostgresForTesting(b, "", migrate.Head, pgversion.MinimumSupportedPostgresVersion, false).NewDatastore(b, func(engine, uri string) datastore.Datastore {
  1163  		ctx := context.Background()
  1164  		ds, err := newPostgresDatastore(ctx, uri,
  1165  			RevisionQuantization(0),
  1166  			GCWindow(time.Millisecond*1),
  1167  			WatchBufferLength(1),
  1168  		)
  1169  		require.NoError(b, err)
  1170  		return ds
  1171  	})
  1172  	defer ds.Close()
  1173  	ds, revision := testfixtures.StandardDatastoreWithData(ds, req)
  1174  
  1175  	b.Run("benchmark checks", func(b *testing.B) {
  1176  		require := require.New(b)
  1177  
  1178  		for i := 0; i < b.N; i++ {
  1179  			iter, err := ds.SnapshotReader(revision).QueryRelationships(context.Background(), datastore.RelationshipsFilter{
  1180  				OptionalResourceType: testfixtures.DocumentNS.Name,
  1181  			})
  1182  			require.NoError(err)
  1183  
  1184  			defer iter.Close()
  1185  
  1186  			for tpl := iter.Next(); tpl != nil; tpl = iter.Next() {
  1187  				require.Equal(testfixtures.DocumentNS.Name, tpl.ResourceAndRelation.Namespace)
  1188  			}
  1189  			require.NoError(iter.Err())
  1190  		}
  1191  	})
  1192  }
  1193  
  1194  func datastoreWithInterceptorAndTestData(t *testing.T, interceptor pgcommon.QueryInterceptor, pgVersion string) datastore.Datastore {
  1195  	require := require.New(t)
  1196  
  1197  	ds := testdatastore.RunPostgresForTestingWithCommitTimestamps(t, "", migrate.Head, false, pgVersion, false).NewDatastore(t, func(engine, uri string) datastore.Datastore {
  1198  		ctx := context.Background()
  1199  		ds, err := newPostgresDatastore(ctx, uri,
  1200  			RevisionQuantization(0),
  1201  			GCWindow(time.Millisecond*1),
  1202  			WatchBufferLength(1),
  1203  			WithQueryInterceptor(interceptor),
  1204  		)
  1205  		require.NoError(err)
  1206  		return ds
  1207  	})
  1208  	t.Cleanup(func() {
  1209  		ds.Close()
  1210  	})
  1211  
  1212  	ds, _ = testfixtures.StandardDatastoreWithData(ds, require)
  1213  
  1214  	// Write namespaces and a few thousand relationships.
  1215  	ctx := context.Background()
  1216  	for i := 0; i < 1000; i++ {
  1217  		_, err := ds.ReadWriteTx(ctx, func(ctx context.Context, rwt datastore.ReadWriteTransaction) error {
  1218  			err := rwt.WriteNamespaces(ctx, namespace.Namespace(
  1219  				fmt.Sprintf("resource%d", i),
  1220  				namespace.MustRelation("reader", nil)))
  1221  			if err != nil {
  1222  				return err
  1223  			}
  1224  
  1225  			// Write some relationships.
  1226  			rtu := tuple.Touch(&core.RelationTuple{
  1227  				ResourceAndRelation: &core.ObjectAndRelation{
  1228  					Namespace: testfixtures.DocumentNS.Name,
  1229  					ObjectId:  fmt.Sprintf("doc%d", i),
  1230  					Relation:  "reader",
  1231  				},
  1232  				Subject: &core.ObjectAndRelation{
  1233  					Namespace: "user",
  1234  					ObjectId:  "456",
  1235  					Relation:  "...",
  1236  				},
  1237  			})
  1238  
  1239  			rtu2 := tuple.Touch(&core.RelationTuple{
  1240  				ResourceAndRelation: &core.ObjectAndRelation{
  1241  					Namespace: fmt.Sprintf("resource%d", i),
  1242  					ObjectId:  "123",
  1243  					Relation:  "reader",
  1244  				},
  1245  				Subject: &core.ObjectAndRelation{
  1246  					Namespace: "user",
  1247  					ObjectId:  "456",
  1248  					Relation:  "...",
  1249  				},
  1250  			})
  1251  
  1252  			rtu3 := tuple.Touch(&core.RelationTuple{
  1253  				ResourceAndRelation: &core.ObjectAndRelation{
  1254  					Namespace: fmt.Sprintf("resource%d", i),
  1255  					ObjectId:  "123",
  1256  					Relation:  "writer",
  1257  				},
  1258  				Subject: &core.ObjectAndRelation{
  1259  					Namespace: "user",
  1260  					ObjectId:  "456",
  1261  					Relation:  "...",
  1262  				},
  1263  			})
  1264  
  1265  			return rwt.WriteRelationships(ctx, []*core.RelationTupleUpdate{rtu, rtu2, rtu3})
  1266  		})
  1267  		require.NoError(err)
  1268  	}
  1269  
  1270  	// Delete some relationships.
  1271  	for i := 990; i < 1000; i++ {
  1272  		_, err := ds.ReadWriteTx(ctx, func(ctx context.Context, rwt datastore.ReadWriteTransaction) error {
  1273  			rtu := tuple.Delete(&core.RelationTuple{
  1274  				ResourceAndRelation: &core.ObjectAndRelation{
  1275  					Namespace: testfixtures.DocumentNS.Name,
  1276  					ObjectId:  fmt.Sprintf("doc%d", i),
  1277  					Relation:  "reader",
  1278  				},
  1279  				Subject: &core.ObjectAndRelation{
  1280  					Namespace: "user",
  1281  					ObjectId:  "456",
  1282  					Relation:  "...",
  1283  				},
  1284  			})
  1285  
  1286  			rtu2 := tuple.Delete(&core.RelationTuple{
  1287  				ResourceAndRelation: &core.ObjectAndRelation{
  1288  					Namespace: fmt.Sprintf("resource%d", i),
  1289  					ObjectId:  "123",
  1290  					Relation:  "reader",
  1291  				},
  1292  				Subject: &core.ObjectAndRelation{
  1293  					Namespace: "user",
  1294  					ObjectId:  "456",
  1295  					Relation:  "...",
  1296  				},
  1297  			})
  1298  			return rwt.WriteRelationships(ctx, []*core.RelationTupleUpdate{rtu, rtu2})
  1299  		})
  1300  		require.NoError(err)
  1301  	}
  1302  
  1303  	// Write some more relationships.
  1304  	for i := 1000; i < 1100; i++ {
  1305  		_, err := ds.ReadWriteTx(ctx, func(ctx context.Context, rwt datastore.ReadWriteTransaction) error {
  1306  			// Write some relationships.
  1307  			rtu := tuple.Touch(&core.RelationTuple{
  1308  				ResourceAndRelation: &core.ObjectAndRelation{
  1309  					Namespace: testfixtures.DocumentNS.Name,
  1310  					ObjectId:  fmt.Sprintf("doc%d", i),
  1311  					Relation:  "reader",
  1312  				},
  1313  				Subject: &core.ObjectAndRelation{
  1314  					Namespace: "user",
  1315  					ObjectId:  "456",
  1316  					Relation:  "...",
  1317  				},
  1318  			})
  1319  
  1320  			rtu2 := tuple.Touch(&core.RelationTuple{
  1321  				ResourceAndRelation: &core.ObjectAndRelation{
  1322  					Namespace: fmt.Sprintf("resource%d", i),
  1323  					ObjectId:  "123",
  1324  					Relation:  "reader",
  1325  				},
  1326  				Subject: &core.ObjectAndRelation{
  1327  					Namespace: "user",
  1328  					ObjectId:  "456",
  1329  					Relation:  "...",
  1330  				},
  1331  			})
  1332  			return rwt.WriteRelationships(ctx, []*core.RelationTupleUpdate{rtu, rtu2})
  1333  		})
  1334  		require.NoError(err)
  1335  	}
  1336  
  1337  	return ds
  1338  }
  1339  
  1340  func GCQueriesServedByExpectedIndexes(t *testing.T, _ testdatastore.RunningEngineForTest, pgVersion string) {
  1341  	require := require.New(t)
  1342  	interceptor := &withQueryInterceptor{explanations: make(map[string]string, 0)}
  1343  	ds := datastoreWithInterceptorAndTestData(t, interceptor, pgVersion)
  1344  
  1345  	// Get the head revision.
  1346  	ctx := context.Background()
  1347  	revision, err := ds.HeadRevision(ctx)
  1348  	require.NoError(err)
  1349  
  1350  	casted := datastore.UnwrapAs[common.GarbageCollector](ds)
  1351  	require.NotNil(casted)
  1352  
  1353  	_, err = casted.DeleteBeforeTx(context.Background(), revision)
  1354  	require.NoError(err)
  1355  
  1356  	require.NotEmpty(interceptor.explanations, "expected queries to be executed")
  1357  
  1358  	// Ensure we have indexes representing each query in the GC workflow.
  1359  	for _, explanation := range interceptor.explanations {
  1360  		switch {
  1361  		case strings.HasPrefix(explanation, "Delete on relation_tuple_transaction"):
  1362  			fallthrough
  1363  
  1364  		case strings.HasPrefix(explanation, "Delete on namespace_config"):
  1365  			fallthrough
  1366  
  1367  		case strings.HasPrefix(explanation, "Delete on relation_tuple"):
  1368  			require.Contains(explanation, "Index Scan")
  1369  
  1370  		default:
  1371  			require.Failf("unknown GC query: %s", explanation)
  1372  		}
  1373  	}
  1374  }
  1375  
  1376  func RepairTransactionsTest(t *testing.T, ds datastore.Datastore) {
  1377  	// Break the datastore by adding a transaction entry with an XID greater the current one.
  1378  	pds := ds.(*pgDatastore)
  1379  
  1380  	getVersionQuery := fmt.Sprintf("SELECT version()")
  1381  	var version string
  1382  	err := pds.writePool.QueryRow(context.Background(), getVersionQuery).Scan(&version)
  1383  	require.NoError(t, err)
  1384  
  1385  	if strings.HasPrefix(version, "PostgreSQL 13.") || strings.HasPrefix(version, "PostgreSQL 14.") {
  1386  		t.Skip("Skipping test on PostgreSQL 13 and 14 as they do not support xid8 max")
  1387  		return
  1388  	}
  1389  
  1390  	createLaterTxn := fmt.Sprintf(
  1391  		"INSERT INTO %s (\"xid\") VALUES (12345::text::xid8)",
  1392  		tableTransaction,
  1393  	)
  1394  
  1395  	_, err = pds.writePool.Exec(context.Background(), createLaterTxn)
  1396  	require.NoError(t, err)
  1397  
  1398  	// Run the repair code.
  1399  	err = pds.repairTransactionIDs(context.Background(), false)
  1400  	require.NoError(t, err)
  1401  
  1402  	// Ensure the current transaction ID is greater than the max specified in the transactions table.
  1403  	currentMaximumID := 0
  1404  	err = pds.writePool.QueryRow(context.Background(), queryCurrentTransactionID).Scan(&currentMaximumID)
  1405  	require.NoError(t, err)
  1406  	require.Greater(t, currentMaximumID, 12345)
  1407  }
  1408  
  1409  func NullCaveatWatchTest(t *testing.T, ds datastore.Datastore) {
  1410  	require := require.New(t)
  1411  
  1412  	ctx, cancel := context.WithCancel(context.Background())
  1413  	defer cancel()
  1414  
  1415  	lowestRevision, err := ds.HeadRevision(ctx)
  1416  	require.NoError(err)
  1417  
  1418  	// Run the watch API.
  1419  	changes, errchan := ds.Watch(ctx, lowestRevision, datastore.WatchJustRelationships())
  1420  	require.Zero(len(errchan))
  1421  
  1422  	// Manually insert a relationship with a NULL caveat. This is allowed, but can only happen due to
  1423  	// bulk import (normal write rels will make it empty instead)
  1424  	pds := ds.(*pgDatastore)
  1425  	_, err = pds.ReadWriteTx(ctx, func(ctx context.Context, drwt datastore.ReadWriteTransaction) error {
  1426  		rwt := drwt.(*pgReadWriteTXN)
  1427  
  1428  		createInserts := writeTuple
  1429  		valuesToWrite := []interface{}{
  1430  			"resource",
  1431  			"someresourceid",
  1432  			"somerelation",
  1433  			"subject",
  1434  			"somesubject",
  1435  			"...",
  1436  			nil, // set explicitly to null
  1437  			nil, // set explicitly to null
  1438  		}
  1439  
  1440  		query := createInserts.Values(valuesToWrite...)
  1441  		sql, args, err := query.ToSql()
  1442  		if err != nil {
  1443  			return fmt.Errorf(errUnableToWriteRelationships, err)
  1444  		}
  1445  
  1446  		_, err = rwt.tx.Exec(ctx, sql, args...)
  1447  		return err
  1448  	})
  1449  	require.NoError(err)
  1450  
  1451  	// Verify the relationship create was tracked by the watch.
  1452  	test.VerifyUpdates(require, [][]*core.RelationTupleUpdate{
  1453  		{
  1454  			tuple.Touch(tuple.Parse("resource:someresourceid#somerelation@subject:somesubject")),
  1455  		},
  1456  	},
  1457  		changes,
  1458  		errchan,
  1459  		false,
  1460  	)
  1461  
  1462  	// Delete the relationship and ensure it does not raise an error in watch.
  1463  	deleteUpdate := tuple.Delete(tuple.Parse("resource:someresourceid#somerelation@subject:somesubject"))
  1464  	_, err = common.UpdateTuplesInDatastore(ctx, ds, deleteUpdate)
  1465  	require.NoError(err)
  1466  
  1467  	// Verify the delete.
  1468  	test.VerifyUpdates(require, [][]*core.RelationTupleUpdate{
  1469  		{
  1470  			tuple.Delete(tuple.Parse("resource:someresourceid#somerelation@subject:somesubject")),
  1471  		},
  1472  	},
  1473  		changes,
  1474  		errchan,
  1475  		false,
  1476  	)
  1477  }
  1478  
  1479  const waitForChangesTimeout = 5 * time.Second