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(¤tMaximumID) 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