github.com/authzed/spicedb@v1.32.1-0.20240520085336-ebda56537386/pkg/datastore/test/tuples.go (about)

     1  package test
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"strconv"
     8  	"sync"
     9  	"testing"
    10  	"time"
    11  
    12  	v1 "github.com/authzed/authzed-go/proto/authzed/api/v1"
    13  	"github.com/stretchr/testify/require"
    14  	"golang.org/x/sync/errgroup"
    15  	"google.golang.org/grpc/codes"
    16  
    17  	"github.com/authzed/grpcutil"
    18  
    19  	"github.com/authzed/spicedb/internal/datastore/common"
    20  	"github.com/authzed/spicedb/internal/testfixtures"
    21  	"github.com/authzed/spicedb/pkg/datastore"
    22  	"github.com/authzed/spicedb/pkg/datastore/options"
    23  	"github.com/authzed/spicedb/pkg/genutil/mapz"
    24  	core "github.com/authzed/spicedb/pkg/proto/core/v1"
    25  	"github.com/authzed/spicedb/pkg/tuple"
    26  )
    27  
    28  const (
    29  	testUserNamespace     = "test/user"
    30  	testResourceNamespace = "test/resource"
    31  	testGroupNamespace    = "test/group"
    32  	testReaderRelation    = "reader"
    33  	testEditorRelation    = "editor"
    34  	testMemberRelation    = "member"
    35  	ellipsis              = "..."
    36  )
    37  
    38  // SimpleTest tests whether or not the requirements for simple reading and
    39  // writing of relationships hold for a particular datastore.
    40  func SimpleTest(t *testing.T, tester DatastoreTester) {
    41  	testCases := []int{1, 2, 4, 32, 256}
    42  
    43  	for _, numTuples := range testCases {
    44  		numTuples := numTuples
    45  		t.Run(strconv.Itoa(numTuples), func(t *testing.T) {
    46  			require := require.New(t)
    47  
    48  			ds, err := tester.New(0, veryLargeGCInterval, veryLargeGCWindow, 1)
    49  			require.NoError(err)
    50  			defer ds.Close()
    51  
    52  			ctx := context.Background()
    53  
    54  			ok, err := ds.ReadyState(ctx)
    55  			require.NoError(err)
    56  			require.True(ok.IsReady)
    57  
    58  			setupDatastore(ds, require)
    59  
    60  			tRequire := testfixtures.TupleChecker{Require: require, DS: ds}
    61  
    62  			var testTuples []*core.RelationTuple
    63  			for i := 0; i < numTuples; i++ {
    64  				resourceName := fmt.Sprintf("resource%d", i)
    65  				userName := fmt.Sprintf("user%d", i)
    66  
    67  				newTuple := makeTestTuple(resourceName, userName)
    68  				testTuples = append(testTuples, newTuple)
    69  			}
    70  
    71  			lastRevision, err := common.WriteTuples(ctx, ds, core.RelationTupleUpdate_CREATE, testTuples...)
    72  			require.NoError(err)
    73  
    74  			for _, toCheck := range testTuples {
    75  				tRequire.TupleExists(ctx, toCheck, lastRevision)
    76  			}
    77  
    78  			// Write a duplicate tuple to make sure the datastore rejects it
    79  			_, err = common.WriteTuples(ctx, ds, core.RelationTupleUpdate_CREATE, testTuples...)
    80  			require.Error(err)
    81  
    82  			dsReader := ds.SnapshotReader(lastRevision)
    83  			for _, tupleToFind := range testTuples {
    84  				tupleSubject := tupleToFind.Subject
    85  
    86  				// Check that we can find the tuple a number of ways
    87  				iter, err := dsReader.QueryRelationships(ctx, datastore.RelationshipsFilter{
    88  					OptionalResourceType: tupleToFind.ResourceAndRelation.Namespace,
    89  					OptionalResourceIds:  []string{tupleToFind.ResourceAndRelation.ObjectId},
    90  				})
    91  				require.NoError(err)
    92  				tRequire.VerifyIteratorResults(iter, tupleToFind)
    93  
    94  				// Check without a resource type.
    95  				iter, err = dsReader.QueryRelationships(ctx, datastore.RelationshipsFilter{
    96  					OptionalResourceIds: []string{tupleToFind.ResourceAndRelation.ObjectId},
    97  				})
    98  				require.NoError(err)
    99  				tRequire.VerifyIteratorResults(iter, tupleToFind)
   100  
   101  				iter, err = dsReader.QueryRelationships(ctx, datastore.RelationshipsFilter{
   102  					OptionalResourceType:     tupleToFind.ResourceAndRelation.Namespace,
   103  					OptionalResourceIds:      []string{tupleToFind.ResourceAndRelation.ObjectId},
   104  					OptionalResourceRelation: tupleToFind.ResourceAndRelation.Relation,
   105  				})
   106  				require.NoError(err)
   107  				tRequire.VerifyIteratorResults(iter, tupleToFind)
   108  
   109  				iter, err = dsReader.ReverseQueryRelationships(
   110  					ctx,
   111  					onrToSubjectsFilter(tupleSubject),
   112  					options.WithResRelation(&options.ResourceRelation{
   113  						Namespace: tupleToFind.ResourceAndRelation.Namespace,
   114  						Relation:  tupleToFind.ResourceAndRelation.Relation,
   115  					}),
   116  				)
   117  				require.NoError(err)
   118  				tRequire.VerifyIteratorResults(iter, tupleToFind)
   119  
   120  				iter, err = dsReader.ReverseQueryRelationships(
   121  					ctx,
   122  					onrToSubjectsFilter(tupleSubject),
   123  					options.WithResRelation(&options.ResourceRelation{
   124  						Namespace: tupleToFind.ResourceAndRelation.Namespace,
   125  						Relation:  tupleToFind.ResourceAndRelation.Relation,
   126  					}),
   127  					options.WithLimitForReverse(options.LimitOne),
   128  				)
   129  				require.NoError(err)
   130  				tRequire.VerifyIteratorResults(iter, tupleToFind)
   131  
   132  				// Check that we fail to find the tuple with the wrong filters
   133  				iter, err = dsReader.QueryRelationships(ctx, datastore.RelationshipsFilter{
   134  					OptionalResourceType:     tupleToFind.ResourceAndRelation.Namespace,
   135  					OptionalResourceIds:      []string{tupleToFind.ResourceAndRelation.ObjectId},
   136  					OptionalResourceRelation: "fake",
   137  				})
   138  				require.NoError(err)
   139  				tRequire.VerifyIteratorResults(iter)
   140  
   141  				incorrectUserset := &core.ObjectAndRelation{
   142  					Namespace: tupleSubject.Namespace,
   143  					ObjectId:  tupleSubject.ObjectId,
   144  					Relation:  "fake",
   145  				}
   146  
   147  				iter, err = dsReader.ReverseQueryRelationships(
   148  					ctx,
   149  					onrToSubjectsFilter(incorrectUserset),
   150  					options.WithResRelation(&options.ResourceRelation{
   151  						Namespace: tupleToFind.ResourceAndRelation.Namespace,
   152  						Relation:  tupleToFind.ResourceAndRelation.Relation,
   153  					}),
   154  				)
   155  				require.NoError(err)
   156  				tRequire.VerifyIteratorResults(iter)
   157  			}
   158  
   159  			// Check a query that returns a number of tuples
   160  			iter, err := dsReader.QueryRelationships(ctx, datastore.RelationshipsFilter{
   161  				OptionalResourceType: testResourceNamespace,
   162  			})
   163  			require.NoError(err)
   164  			tRequire.VerifyIteratorResults(iter, testTuples...)
   165  
   166  			// Filter it down to a single tuple with a userset
   167  			iter, err = dsReader.QueryRelationships(ctx, datastore.RelationshipsFilter{
   168  				OptionalResourceType: testResourceNamespace,
   169  				OptionalSubjectsSelectors: []datastore.SubjectsSelector{
   170  					{
   171  						OptionalSubjectType: testUserNamespace,
   172  						OptionalSubjectIds:  []string{"user0"},
   173  					},
   174  				},
   175  			})
   176  			require.NoError(err)
   177  			tRequire.VerifyIteratorResults(iter, testTuples[0])
   178  
   179  			// Check for larger reverse queries.
   180  			iter, err = dsReader.ReverseQueryRelationships(ctx, datastore.SubjectsFilter{
   181  				SubjectType: testUserNamespace,
   182  			})
   183  			require.NoError(err)
   184  			tRequire.VerifyIteratorResults(iter, testTuples...)
   185  
   186  			// Check limit.
   187  			if len(testTuples) > 1 {
   188  				limit := uint64(len(testTuples) - 1)
   189  				iter, err := dsReader.ReverseQueryRelationships(ctx, datastore.SubjectsFilter{
   190  					SubjectType: testUserNamespace,
   191  				}, options.WithLimitForReverse(&limit))
   192  				require.NoError(err)
   193  				defer iter.Close()
   194  				tRequire.VerifyIteratorCount(iter, len(testTuples)-1)
   195  			}
   196  
   197  			// Check that we can find the group of tuples too
   198  			iter, err = dsReader.QueryRelationships(ctx, datastore.RelationshipsFilter{
   199  				OptionalResourceType: testTuples[0].ResourceAndRelation.Namespace,
   200  			})
   201  			require.NoError(err)
   202  			tRequire.VerifyIteratorResults(iter, testTuples...)
   203  
   204  			iter, err = dsReader.QueryRelationships(ctx, datastore.RelationshipsFilter{
   205  				OptionalResourceType:     testTuples[0].ResourceAndRelation.Namespace,
   206  				OptionalResourceRelation: testTuples[0].ResourceAndRelation.Relation,
   207  			})
   208  			require.NoError(err)
   209  			tRequire.VerifyIteratorResults(iter, testTuples...)
   210  
   211  			// Try some bad queries
   212  			iter, err = dsReader.QueryRelationships(ctx, datastore.RelationshipsFilter{
   213  				OptionalResourceType: testTuples[0].ResourceAndRelation.Namespace,
   214  				OptionalResourceIds:  []string{"fakeobectid"},
   215  			})
   216  			require.NoError(err)
   217  			tRequire.VerifyIteratorResults(iter)
   218  
   219  			// Delete the first tuple
   220  			deletedAt, err := common.WriteTuples(ctx, ds, core.RelationTupleUpdate_DELETE, testTuples[0])
   221  			require.NoError(err)
   222  
   223  			// Delete it AGAIN (idempotent delete) and make sure there's no error
   224  			_, err = common.WriteTuples(ctx, ds, core.RelationTupleUpdate_DELETE, testTuples[0])
   225  			require.NoError(err)
   226  
   227  			// Verify it can still be read at the old revision
   228  			tRequire.TupleExists(ctx, testTuples[0], lastRevision)
   229  
   230  			// Verify that it does not show up at the new revision
   231  			tRequire.NoTupleExists(ctx, testTuples[0], deletedAt)
   232  			alreadyDeletedIter, err := ds.SnapshotReader(deletedAt).QueryRelationships(
   233  				ctx,
   234  				datastore.RelationshipsFilter{
   235  					OptionalResourceType: testTuples[0].ResourceAndRelation.Namespace,
   236  				},
   237  			)
   238  			require.NoError(err)
   239  			tRequire.VerifyIteratorResults(alreadyDeletedIter, testTuples[1:]...)
   240  
   241  			// Write it back
   242  			returnedAt, err := common.WriteTuples(ctx, ds, core.RelationTupleUpdate_CREATE, testTuples[0])
   243  			require.NoError(err)
   244  			tRequire.TupleExists(ctx, testTuples[0], returnedAt)
   245  
   246  			// Delete with DeleteRelationship
   247  			deletedAt, err = ds.ReadWriteTx(ctx, func(ctx context.Context, rwt datastore.ReadWriteTransaction) error {
   248  				_, err := rwt.DeleteRelationships(ctx, &v1.RelationshipFilter{
   249  					ResourceType: testResourceNamespace,
   250  				})
   251  				require.NoError(err)
   252  				return err
   253  			})
   254  			require.NoError(err)
   255  			tRequire.NoTupleExists(ctx, testTuples[0], deletedAt)
   256  		})
   257  	}
   258  }
   259  
   260  func ObjectIDsTest(t *testing.T, tester DatastoreTester) {
   261  	testCases := []string{
   262  		"simple",
   263  		"google|123123123123",
   264  		"--=base64YWZzZGZh-ZHNmZHPwn5iK8J+YivC/fmIrwn5iK==",
   265  		"veryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryveryverylong",
   266  	}
   267  
   268  	for _, tc := range testCases {
   269  		t.Run(tc, func(t *testing.T) {
   270  			ctx := context.Background()
   271  			require := require.New(t)
   272  
   273  			ds, err := tester.New(0, veryLargeGCInterval, veryLargeGCWindow, 1)
   274  			require.NoError(err)
   275  			defer ds.Close()
   276  
   277  			tpl := makeTestTuple(tc, tc)
   278  			require.NoError(tpl.Validate())
   279  
   280  			// Write the test tuple
   281  			_, err = ds.ReadWriteTx(ctx, func(ctx context.Context, rwt datastore.ReadWriteTransaction) error {
   282  				return rwt.WriteRelationships(ctx, []*core.RelationTupleUpdate{
   283  					{
   284  						Operation: core.RelationTupleUpdate_CREATE,
   285  						Tuple:     tpl,
   286  					},
   287  				})
   288  			})
   289  			require.NoError(err)
   290  
   291  			// Read it back
   292  			rev, err := ds.HeadRevision(ctx)
   293  			require.NoError(err)
   294  			iter, err := ds.SnapshotReader(rev).QueryRelationships(ctx, datastore.RelationshipsFilter{
   295  				OptionalResourceType: testResourceNamespace,
   296  				OptionalResourceIds:  []string{tc},
   297  			})
   298  			require.NoError(err)
   299  			defer iter.Close()
   300  
   301  			first := iter.Next()
   302  			require.NotNil(first)
   303  			require.Equal(tc, first.ResourceAndRelation.ObjectId)
   304  			require.Equal(tc, first.Subject.ObjectId)
   305  
   306  			shouldBeNil := iter.Next()
   307  			require.Nil(shouldBeNil)
   308  			require.NoError(iter.Err())
   309  		})
   310  	}
   311  }
   312  
   313  // DeleteRelationshipsTest tests whether or not the requirements for deleting
   314  // relationships hold for a particular datastore.
   315  func DeleteRelationshipsTest(t *testing.T, tester DatastoreTester) {
   316  	var testTuples []*core.RelationTuple
   317  	for i := 0; i < 10; i++ {
   318  		newTuple := makeTestTuple(fmt.Sprintf("resource%d", i), fmt.Sprintf("user%d", i%2))
   319  		testTuples = append(testTuples, newTuple)
   320  	}
   321  	testTuples[len(testTuples)-1].ResourceAndRelation.Relation = "writer"
   322  
   323  	table := []struct {
   324  		name                      string
   325  		inputTuples               []*core.RelationTuple
   326  		filter                    *v1.RelationshipFilter
   327  		expectedExistingTuples    []*core.RelationTuple
   328  		expectedNonExistingTuples []*core.RelationTuple
   329  	}{
   330  		{
   331  			"resourceID",
   332  			testTuples,
   333  			&v1.RelationshipFilter{
   334  				ResourceType:       testResourceNamespace,
   335  				OptionalResourceId: "resource0",
   336  			},
   337  			testTuples[1:],
   338  			testTuples[:1],
   339  		},
   340  		{
   341  			"only resourceID",
   342  			testTuples,
   343  			&v1.RelationshipFilter{
   344  				OptionalResourceId: "resource0",
   345  			},
   346  			testTuples[1:],
   347  			testTuples[:1],
   348  		},
   349  		{
   350  			"only relation",
   351  			testTuples,
   352  			&v1.RelationshipFilter{
   353  				OptionalRelation: "writer",
   354  			},
   355  			testTuples[:len(testTuples)-1],
   356  			[]*core.RelationTuple{testTuples[len(testTuples)-1]},
   357  		},
   358  		{
   359  			"relation",
   360  			testTuples,
   361  			&v1.RelationshipFilter{
   362  				ResourceType:     testResourceNamespace,
   363  				OptionalRelation: "writer",
   364  			},
   365  			testTuples[:len(testTuples)-1],
   366  			[]*core.RelationTuple{testTuples[len(testTuples)-1]},
   367  		},
   368  		{
   369  			"subjectID",
   370  			testTuples,
   371  			&v1.RelationshipFilter{
   372  				ResourceType:          testResourceNamespace,
   373  				OptionalSubjectFilter: &v1.SubjectFilter{SubjectType: testUserNamespace, OptionalSubjectId: "user0"},
   374  			},
   375  			[]*core.RelationTuple{testTuples[1], testTuples[3], testTuples[5], testTuples[7], testTuples[9]},
   376  			[]*core.RelationTuple{testTuples[0], testTuples[2], testTuples[4], testTuples[6], testTuples[8]},
   377  		},
   378  		{
   379  			"subjectID without resource type",
   380  			testTuples,
   381  			&v1.RelationshipFilter{
   382  				OptionalSubjectFilter: &v1.SubjectFilter{SubjectType: testUserNamespace, OptionalSubjectId: "user0"},
   383  			},
   384  			[]*core.RelationTuple{testTuples[1], testTuples[3], testTuples[5], testTuples[7], testTuples[9]},
   385  			[]*core.RelationTuple{testTuples[0], testTuples[2], testTuples[4], testTuples[6], testTuples[8]},
   386  		},
   387  		{
   388  			"subjectRelation",
   389  			testTuples,
   390  			&v1.RelationshipFilter{
   391  				ResourceType:          testResourceNamespace,
   392  				OptionalSubjectFilter: &v1.SubjectFilter{SubjectType: testUserNamespace, OptionalRelation: &v1.SubjectFilter_RelationFilter{Relation: ""}},
   393  			},
   394  			nil,
   395  			testTuples,
   396  		},
   397  		{
   398  			"duplicates",
   399  			append(testTuples, testTuples[0]),
   400  			&v1.RelationshipFilter{
   401  				ResourceType:       testResourceNamespace,
   402  				OptionalResourceId: "resource0",
   403  			},
   404  			testTuples[1:],
   405  			testTuples[:1],
   406  		},
   407  	}
   408  
   409  	for _, tt := range table {
   410  		tt := tt
   411  		t.Run(tt.name, func(t *testing.T) {
   412  			require := require.New(t)
   413  			ctx := context.Background()
   414  
   415  			ds, err := tester.New(0, veryLargeGCInterval, veryLargeGCWindow, 1)
   416  			require.NoError(err)
   417  			defer ds.Close()
   418  
   419  			setupDatastore(ds, require)
   420  
   421  			tRequire := testfixtures.TupleChecker{Require: require, DS: ds}
   422  
   423  			// NOTE: we write tuples in multiple calls to ReadWriteTransaction because it is not allowed to change
   424  			// the same tuple in the same transaction.
   425  			_, err = ds.ReadWriteTx(ctx, func(ctx context.Context, rwt datastore.ReadWriteTransaction) error {
   426  				for _, tpl := range tt.inputTuples {
   427  					update := tuple.Touch(tpl)
   428  					err := rwt.WriteRelationships(ctx, []*core.RelationTupleUpdate{update})
   429  					if err != nil {
   430  						return err
   431  					}
   432  				}
   433  				return nil
   434  			})
   435  			require.NoError(err)
   436  
   437  			deletedAt, err := ds.ReadWriteTx(ctx, func(ctx context.Context, rwt datastore.ReadWriteTransaction) error {
   438  				_, err := rwt.DeleteRelationships(ctx, tt.filter)
   439  				require.NoError(err)
   440  				return err
   441  			})
   442  			require.NoError(err)
   443  
   444  			for _, tpl := range tt.expectedExistingTuples {
   445  				tRequire.TupleExists(ctx, tpl, deletedAt)
   446  			}
   447  
   448  			for _, tpl := range tt.expectedNonExistingTuples {
   449  				tRequire.NoTupleExists(ctx, tpl, deletedAt)
   450  			}
   451  		})
   452  	}
   453  }
   454  
   455  // InvalidReadsTest tests whether or not the requirements for reading via
   456  // invalid revisions hold for a particular datastore.
   457  func InvalidReadsTest(t *testing.T, tester DatastoreTester) {
   458  	t.Run("revision expiration", func(t *testing.T) {
   459  		testGCDuration := 600 * time.Millisecond
   460  
   461  		require := require.New(t)
   462  
   463  		ds, err := tester.New(0, veryLargeGCInterval, testGCDuration, 1)
   464  		require.NoError(err)
   465  		defer ds.Close()
   466  
   467  		setupDatastore(ds, require)
   468  
   469  		ctx := context.Background()
   470  
   471  		// Check that we get an error when there are no revisions
   472  		err = ds.CheckRevision(ctx, datastore.NoRevision)
   473  
   474  		revisionErr := datastore.ErrInvalidRevision{}
   475  		require.True(errors.As(err, &revisionErr))
   476  
   477  		newTuple := makeTestTuple("one", "one")
   478  		firstWrite, err := common.WriteTuples(ctx, ds, core.RelationTupleUpdate_CREATE, newTuple)
   479  		require.NoError(err)
   480  
   481  		// Check that we can read at the just written revision
   482  		err = ds.CheckRevision(ctx, firstWrite)
   483  		require.NoError(err)
   484  
   485  		// Wait the duration required to allow the revision to expire
   486  		time.Sleep(testGCDuration * 2)
   487  
   488  		// Write another tuple which will allow the first revision to expire
   489  		nextWrite, err := common.WriteTuples(ctx, ds, core.RelationTupleUpdate_TOUCH, newTuple)
   490  		require.NoError(err)
   491  
   492  		// Check that we can read at the just written revision
   493  		err = ds.CheckRevision(ctx, nextWrite)
   494  		require.NoError(err)
   495  
   496  		// Check that we can no longer read the old revision (now allowed to expire)
   497  		err = ds.CheckRevision(ctx, firstWrite)
   498  		require.True(errors.As(err, &revisionErr))
   499  		require.Equal(datastore.RevisionStale, revisionErr.Reason())
   500  	})
   501  }
   502  
   503  // DeleteNotExistantTest tests the deletion of a non-existant relationship.
   504  func DeleteNotExistantTest(t *testing.T, tester DatastoreTester) {
   505  	require := require.New(t)
   506  
   507  	rawDS, err := tester.New(0, veryLargeGCInterval, veryLargeGCWindow, 1)
   508  	require.NoError(err)
   509  
   510  	ds, _ := testfixtures.StandardDatastoreWithData(rawDS, require)
   511  	ctx := context.Background()
   512  
   513  	_, err = ds.ReadWriteTx(ctx, func(ctx context.Context, rwt datastore.ReadWriteTransaction) error {
   514  		err := rwt.WriteRelationships(ctx, []*core.RelationTupleUpdate{
   515  			tuple.Delete(tuple.MustParse("document:foo#viewer@user:tom#...")),
   516  		})
   517  		require.NoError(err)
   518  
   519  		return nil
   520  	})
   521  	require.NoError(err)
   522  }
   523  
   524  // DeleteAlreadyDeletedTest tests the deletion of an already-deleted relationship.
   525  func DeleteAlreadyDeletedTest(t *testing.T, tester DatastoreTester) {
   526  	require := require.New(t)
   527  
   528  	rawDS, err := tester.New(0, veryLargeGCInterval, veryLargeGCWindow, 1)
   529  	require.NoError(err)
   530  
   531  	ds, _ := testfixtures.StandardDatastoreWithData(rawDS, require)
   532  	ctx := context.Background()
   533  
   534  	_, err = ds.ReadWriteTx(ctx, func(ctx context.Context, rwt datastore.ReadWriteTransaction) error {
   535  		// Write the relationship.
   536  		return rwt.WriteRelationships(ctx, []*core.RelationTupleUpdate{
   537  			tuple.Create(tuple.MustParse("document:foo#viewer@user:tom#...")),
   538  		})
   539  	})
   540  	require.NoError(err)
   541  
   542  	_, err = ds.ReadWriteTx(ctx, func(ctx context.Context, rwt datastore.ReadWriteTransaction) error {
   543  		// Delete the relationship.
   544  		return rwt.WriteRelationships(ctx, []*core.RelationTupleUpdate{
   545  			tuple.Delete(tuple.MustParse("document:foo#viewer@user:tom#...")),
   546  		})
   547  	})
   548  	require.NoError(err)
   549  
   550  	_, err = ds.ReadWriteTx(ctx, func(ctx context.Context, rwt datastore.ReadWriteTransaction) error {
   551  		// Delete the relationship again.
   552  		return rwt.WriteRelationships(ctx, []*core.RelationTupleUpdate{
   553  			tuple.Delete(tuple.MustParse("document:foo#viewer@user:tom#...")),
   554  		})
   555  	})
   556  	require.NoError(err)
   557  }
   558  
   559  // WriteDeleteWriteTest tests writing a relationship, deleting it, and then writing it again.
   560  func WriteDeleteWriteTest(t *testing.T, tester DatastoreTester) {
   561  	require := require.New(t)
   562  
   563  	rawDS, err := tester.New(0, veryLargeGCInterval, veryLargeGCWindow, 1)
   564  	require.NoError(err)
   565  
   566  	ds, _ := testfixtures.StandardDatastoreWithData(rawDS, require)
   567  	ctx := context.Background()
   568  
   569  	tpl := makeTestTuple("foo", "tom")
   570  	_, err = common.WriteTuples(ctx, ds, core.RelationTupleUpdate_CREATE, tpl)
   571  	require.NoError(err)
   572  
   573  	ensureTuples(ctx, require, ds, tpl)
   574  
   575  	_, err = common.WriteTuples(ctx, ds, core.RelationTupleUpdate_DELETE, tpl)
   576  	require.NoError(err)
   577  
   578  	ensureNotTuples(ctx, require, ds, tpl)
   579  
   580  	_, err = common.WriteTuples(ctx, ds, core.RelationTupleUpdate_CREATE, tpl)
   581  	require.NoError(err)
   582  
   583  	ensureTuples(ctx, require, ds, tpl)
   584  }
   585  
   586  // CreateAlreadyExistingTest tests creating a relationship twice.
   587  func CreateAlreadyExistingTest(t *testing.T, tester DatastoreTester) {
   588  	require := require.New(t)
   589  
   590  	rawDS, err := tester.New(0, veryLargeGCInterval, veryLargeGCWindow, 1)
   591  	require.NoError(err)
   592  
   593  	ds, _ := testfixtures.StandardDatastoreWithData(rawDS, require)
   594  	ctx := context.Background()
   595  
   596  	tpl1 := makeTestTuple("foo", "tom")
   597  	tpl2 := makeTestTuple("foo", "sarah")
   598  	_, err = common.WriteTuples(ctx, ds, core.RelationTupleUpdate_CREATE, tpl1, tpl2)
   599  	require.NoError(err)
   600  
   601  	_, err = common.WriteTuples(ctx, ds, core.RelationTupleUpdate_CREATE, tpl1)
   602  	require.ErrorAs(err, &common.CreateRelationshipExistsError{})
   603  	require.Contains(err.Error(), "could not CREATE relationship ")
   604  	grpcutil.RequireStatus(t, codes.AlreadyExists, err)
   605  
   606  	f := func(ctx context.Context, rwt datastore.ReadWriteTransaction) error {
   607  		_, err := rwt.BulkLoad(ctx, testfixtures.NewBulkTupleGenerator(testResourceNamespace, testReaderRelation, testUserNamespace, 1, t))
   608  		return err
   609  	}
   610  	_, _ = ds.ReadWriteTx(ctx, f)
   611  	_, err = ds.ReadWriteTx(ctx, f)
   612  	grpcutil.RequireStatus(t, codes.AlreadyExists, err)
   613  }
   614  
   615  // TouchAlreadyExistingTest tests touching a relationship twice.
   616  func TouchAlreadyExistingTest(t *testing.T, tester DatastoreTester) {
   617  	require := require.New(t)
   618  
   619  	rawDS, err := tester.New(0, veryLargeGCInterval, veryLargeGCWindow, 1)
   620  	require.NoError(err)
   621  
   622  	ds, _ := testfixtures.StandardDatastoreWithData(rawDS, require)
   623  	ctx := context.Background()
   624  
   625  	tpl1 := makeTestTuple("foo", "tom")
   626  	tpl2 := makeTestTuple("foo", "sarah")
   627  
   628  	_, err = common.WriteTuples(ctx, ds, core.RelationTupleUpdate_CREATE, tpl1, tpl2)
   629  	require.NoError(err)
   630  
   631  	ensureTuples(ctx, require, ds, tpl1, tpl2)
   632  
   633  	_, err = common.WriteTuples(ctx, ds, core.RelationTupleUpdate_TOUCH, tpl1, tpl2)
   634  	require.NoError(err)
   635  
   636  	ensureTuples(ctx, require, ds, tpl1, tpl2)
   637  
   638  	tpl3 := makeTestTuple("foo", "fred")
   639  	_, err = common.WriteTuples(ctx, ds, core.RelationTupleUpdate_TOUCH, tpl1, tpl3)
   640  	require.NoError(err)
   641  
   642  	ensureTuples(ctx, require, ds, tpl1, tpl2, tpl3)
   643  }
   644  
   645  // CreateDeleteTouchTest tests writing a relationship, deleting it, and then touching it.
   646  func CreateDeleteTouchTest(t *testing.T, tester DatastoreTester) {
   647  	require := require.New(t)
   648  
   649  	rawDS, err := tester.New(0, veryLargeGCInterval, veryLargeGCWindow, 1)
   650  	require.NoError(err)
   651  
   652  	ds, _ := testfixtures.StandardDatastoreWithData(rawDS, require)
   653  	ctx := context.Background()
   654  
   655  	tpl1 := makeTestTuple("foo", "tom")
   656  	tpl2 := makeTestTuple("foo", "sarah")
   657  
   658  	_, err = common.WriteTuples(ctx, ds, core.RelationTupleUpdate_CREATE, tpl1, tpl2)
   659  	require.NoError(err)
   660  
   661  	ensureTuples(ctx, require, ds, tpl1, tpl2)
   662  
   663  	_, err = common.WriteTuples(ctx, ds, core.RelationTupleUpdate_DELETE, tpl1, tpl2)
   664  	require.NoError(err)
   665  
   666  	ensureNotTuples(ctx, require, ds, tpl1, tpl2)
   667  
   668  	_, err = common.WriteTuples(ctx, ds, core.RelationTupleUpdate_TOUCH, tpl1, tpl2)
   669  	require.NoError(err)
   670  
   671  	ensureTuples(ctx, require, ds, tpl1, tpl2)
   672  }
   673  
   674  // DeleteOneThousandIndividualInOneCallTest tests deleting 1000 relationships, individually.
   675  func DeleteOneThousandIndividualInOneCallTest(t *testing.T, tester DatastoreTester) {
   676  	require := require.New(t)
   677  
   678  	rawDS, err := tester.New(0, veryLargeGCInterval, veryLargeGCWindow, 1)
   679  	require.NoError(err)
   680  
   681  	ds, _ := testfixtures.StandardDatastoreWithData(rawDS, require)
   682  	ctx := context.Background()
   683  
   684  	// Write the 1000 relationships.
   685  	tuples := make([]*core.RelationTuple, 0, 1000)
   686  	for i := 0; i < 1000; i++ {
   687  		tpl := makeTestTuple("foo", fmt.Sprintf("user%d", i))
   688  		tuples = append(tuples, tpl)
   689  	}
   690  
   691  	_, err = common.WriteTuples(ctx, ds, core.RelationTupleUpdate_CREATE, tuples...)
   692  	require.NoError(err)
   693  	ensureTuples(ctx, require, ds, tuples...)
   694  
   695  	// Add an extra tuple.
   696  	_, err = common.WriteTuples(ctx, ds, core.RelationTupleUpdate_CREATE, makeTestTuple("foo", "extra"))
   697  	require.NoError(err)
   698  	ensureTuples(ctx, require, ds, makeTestTuple("foo", "extra"))
   699  
   700  	// Delete the first 1000 tuples.
   701  	_, err = common.WriteTuples(ctx, ds, core.RelationTupleUpdate_DELETE, tuples...)
   702  	require.NoError(err)
   703  	ensureNotTuples(ctx, require, ds, tuples...)
   704  
   705  	// Ensure the extra tuple is still present.
   706  	ensureTuples(ctx, require, ds, makeTestTuple("foo", "extra"))
   707  }
   708  
   709  // DeleteWithLimitTest tests deleting relationships with a limit.
   710  func DeleteWithLimitTest(t *testing.T, tester DatastoreTester) {
   711  	require := require.New(t)
   712  
   713  	rawDS, err := tester.New(0, veryLargeGCInterval, veryLargeGCWindow, 1)
   714  	require.NoError(err)
   715  
   716  	ds, _ := testfixtures.StandardDatastoreWithSchema(rawDS, require)
   717  	ctx := context.Background()
   718  
   719  	// Write the 1000 relationships.
   720  	tuples := make([]*core.RelationTuple, 0, 1000)
   721  	for i := 0; i < 1000; i++ {
   722  		tpl := makeTestTuple("foo", fmt.Sprintf("user%d", i))
   723  		tuples = append(tuples, tpl)
   724  	}
   725  
   726  	_, err = common.WriteTuples(ctx, ds, core.RelationTupleUpdate_CREATE, tuples...)
   727  	require.NoError(err)
   728  	ensureTuples(ctx, require, ds, tuples...)
   729  
   730  	// Delete 100 tuples.
   731  	var deleteLimit uint64 = 100
   732  	_, err = ds.ReadWriteTx(ctx, func(ctx context.Context, rwt datastore.ReadWriteTransaction) error {
   733  		limitReached, err := rwt.DeleteRelationships(ctx, &v1.RelationshipFilter{
   734  			ResourceType: testResourceNamespace,
   735  		}, options.WithDeleteLimit(&deleteLimit))
   736  		require.NoError(err)
   737  		require.True(limitReached)
   738  		return nil
   739  	})
   740  	require.NoError(err)
   741  
   742  	// Ensure 900 tuples remain.
   743  	found := countTuples(ctx, require, ds, testResourceNamespace)
   744  	require.Equal(900, found)
   745  
   746  	// Delete the remainder.
   747  	deleteLimit = 1000
   748  	_, err = ds.ReadWriteTx(ctx, func(ctx context.Context, rwt datastore.ReadWriteTransaction) error {
   749  		limitReached, err := rwt.DeleteRelationships(ctx, &v1.RelationshipFilter{
   750  			ResourceType: testResourceNamespace,
   751  		}, options.WithDeleteLimit(&deleteLimit))
   752  		require.NoError(err)
   753  		require.False(limitReached)
   754  		return nil
   755  	})
   756  	require.NoError(err)
   757  
   758  	found = countTuples(ctx, require, ds, testResourceNamespace)
   759  	require.Equal(0, found)
   760  }
   761  
   762  // DeleteCaveatedTupleTest tests deleting a relationship with a caveat.
   763  func DeleteCaveatedTupleTest(t *testing.T, tester DatastoreTester) {
   764  	require := require.New(t)
   765  
   766  	rawDS, err := tester.New(0, veryLargeGCInterval, veryLargeGCWindow, 1)
   767  	require.NoError(err)
   768  
   769  	ds, _ := testfixtures.StandardDatastoreWithData(rawDS, require)
   770  	ctx := context.Background()
   771  
   772  	tpl := tuple.Parse("test/resource:someresource#viewer@test/user:someuser[somecaveat]")
   773  
   774  	_, err = common.WriteTuples(ctx, ds, core.RelationTupleUpdate_CREATE, tpl)
   775  	require.NoError(err)
   776  	ensureTuples(ctx, require, ds, tpl)
   777  
   778  	// Delete the tuple.
   779  	withoutCaveat := tuple.Parse("test/resource:someresource#viewer@test/user:someuser")
   780  
   781  	_, err = common.WriteTuples(ctx, ds, core.RelationTupleUpdate_DELETE, withoutCaveat)
   782  	require.NoError(err)
   783  	ensureNotTuples(ctx, require, ds, tpl, withoutCaveat)
   784  }
   785  
   786  // DeleteRelationshipsWithVariousFiltersTest tests deleting relationships with various filters.
   787  func DeleteRelationshipsWithVariousFiltersTest(t *testing.T, tester DatastoreTester) {
   788  	tcs := []struct {
   789  		name            string
   790  		filter          *v1.RelationshipFilter
   791  		relationships   []string
   792  		expectedDeleted []string
   793  	}{
   794  		{
   795  			name: "resource type",
   796  			filter: &v1.RelationshipFilter{
   797  				ResourceType: "document",
   798  			},
   799  			relationships: []string{
   800  				"document:first#viewer@user:tom",
   801  				"document:second#viewer@user:tom",
   802  				"folder:secondfolder#viewer@user:tom",
   803  				"folder:someotherfolder#viewer@user:tom",
   804  			},
   805  			expectedDeleted: []string{"document:first#viewer@user:tom", "document:second#viewer@user:tom"},
   806  		},
   807  		{
   808  			name: "resource id",
   809  			filter: &v1.RelationshipFilter{
   810  				ResourceType:       "document",
   811  				OptionalResourceId: "first",
   812  			},
   813  			relationships:   []string{"document:first#viewer@user:tom", "document:second#viewer@user:tom"},
   814  			expectedDeleted: []string{"document:first#viewer@user:tom"},
   815  		},
   816  		{
   817  			name: "resource id without resource type",
   818  			filter: &v1.RelationshipFilter{
   819  				OptionalResourceId: "first",
   820  			},
   821  			relationships:   []string{"document:first#viewer@user:tom", "document:second#viewer@user:tom"},
   822  			expectedDeleted: []string{"document:first#viewer@user:tom"},
   823  		},
   824  		{
   825  			name: "resource id prefix with resource type",
   826  			filter: &v1.RelationshipFilter{
   827  				ResourceType:             "document",
   828  				OptionalResourceIdPrefix: "f",
   829  			},
   830  			relationships:   []string{"document:first#viewer@user:tom", "document:second#viewer@user:tom", "document:fourth#viewer@user:tom", "folder:fsomething#viewer@user:tom"},
   831  			expectedDeleted: []string{"document:first#viewer@user:tom", "document:fourth#viewer@user:tom"},
   832  		},
   833  		{
   834  			name: "resource id prefix without resource type",
   835  			filter: &v1.RelationshipFilter{
   836  				OptionalResourceIdPrefix: "f",
   837  			},
   838  			relationships:   []string{"document:first#viewer@user:tom", "document:second#viewer@user:tom", "document:fourth#viewer@user:tom", "folder:fsomething#viewer@user:tom"},
   839  			expectedDeleted: []string{"document:first#viewer@user:tom", "document:fourth#viewer@user:tom", "folder:fsomething#viewer@user:tom"},
   840  		},
   841  		{
   842  			name: "resource relation",
   843  			filter: &v1.RelationshipFilter{
   844  				OptionalRelation: "viewer",
   845  			},
   846  			relationships:   []string{"document:first#viewer@user:tom", "document:second#viewer@user:tom", "document:third#editor@user:tom", "folder:fsomething#viewer@user:tom"},
   847  			expectedDeleted: []string{"document:first#viewer@user:tom", "document:second#viewer@user:tom", "folder:fsomething#viewer@user:tom"},
   848  		},
   849  		{
   850  			name: "subject id",
   851  			filter: &v1.RelationshipFilter{
   852  				OptionalSubjectFilter: &v1.SubjectFilter{SubjectType: "user", OptionalSubjectId: "tom"},
   853  			},
   854  			relationships:   []string{"document:first#viewer@user:tom", "document:second#viewer@user:tom", "document:third#editor@user:tom", "document:first#viewer@user:alice"},
   855  			expectedDeleted: []string{"document:first#viewer@user:tom", "document:second#viewer@user:tom", "document:third#editor@user:tom"},
   856  		},
   857  		{
   858  			name: "subject filter with relation",
   859  			filter: &v1.RelationshipFilter{
   860  				OptionalSubjectFilter: &v1.SubjectFilter{SubjectType: "user", OptionalRelation: &v1.SubjectFilter_RelationFilter{Relation: "something"}},
   861  			},
   862  			relationships:   []string{"document:first#viewer@user:tom", "document:second#viewer@user:tom#something"},
   863  			expectedDeleted: []string{"document:second#viewer@user:tom#something"},
   864  		},
   865  		{
   866  			name: "full match",
   867  			filter: &v1.RelationshipFilter{
   868  				ResourceType:          "document",
   869  				OptionalResourceId:    "first",
   870  				OptionalRelation:      "viewer",
   871  				OptionalSubjectFilter: &v1.SubjectFilter{SubjectType: "user", OptionalSubjectId: "tom"},
   872  			},
   873  			relationships:   []string{"document:first#viewer@user:tom", "document:second#viewer@user:tom", "document:first#editor@user:tom", "document:firster#viewer@user:tom"},
   874  			expectedDeleted: []string{"document:first#viewer@user:tom"},
   875  		},
   876  	}
   877  
   878  	for _, tc := range tcs {
   879  		tc := tc
   880  		t.Run(tc.name, func(t *testing.T) {
   881  			for _, withLimit := range []bool{false, true} {
   882  				t.Run(fmt.Sprintf("withLimit=%v", withLimit), func(t *testing.T) {
   883  					require := require.New(t)
   884  
   885  					rawDS, err := tester.New(0, veryLargeGCInterval, veryLargeGCWindow, 1)
   886  					require.NoError(err)
   887  
   888  					// Write the initial relationships.
   889  					ds, _ := testfixtures.StandardDatastoreWithSchema(rawDS, require)
   890  					ctx := context.Background()
   891  
   892  					allRelationships := mapz.NewSet[string]()
   893  					for _, rel := range tc.relationships {
   894  						allRelationships.Add(rel)
   895  
   896  						tpl := tuple.MustParse(rel)
   897  						_, err = common.WriteTuples(ctx, ds, core.RelationTupleUpdate_CREATE, tpl)
   898  						require.NoError(err)
   899  					}
   900  
   901  					writtenRev, err := ds.HeadRevision(ctx)
   902  					require.NoError(err)
   903  
   904  					var delLimit *uint64
   905  					if withLimit {
   906  						limit := uint64(len(tc.expectedDeleted))
   907  						delLimit = &limit
   908  					}
   909  
   910  					// Delete the relationships and ensure matching are no longer found.
   911  					_, err = ds.ReadWriteTx(ctx, func(ctx context.Context, rwt datastore.ReadWriteTransaction) error {
   912  						_, err := rwt.DeleteRelationships(ctx, tc.filter, options.WithDeleteLimit(delLimit))
   913  						return err
   914  					})
   915  					require.NoError(err)
   916  
   917  					// Read the updated relationships and ensure no matching relationships are found.
   918  					headRev, err := ds.HeadRevision(ctx)
   919  					require.NoError(err)
   920  
   921  					filter, err := datastore.RelationshipsFilterFromPublicFilter(tc.filter)
   922  					require.NoError(err)
   923  
   924  					reader := ds.SnapshotReader(headRev)
   925  					iter, err := reader.QueryRelationships(ctx, filter)
   926  					require.NoError(err)
   927  					t.Cleanup(iter.Close)
   928  
   929  					found := iter.Next()
   930  					if found != nil {
   931  						require.Nil(found, "got relationship: %s", tuple.MustString(found))
   932  					}
   933  					iter.Close()
   934  
   935  					// Ensure the expected relationships were deleted.
   936  					resourceTypes := mapz.NewSet[string]()
   937  					for _, rel := range tc.relationships {
   938  						tpl := tuple.MustParse(rel)
   939  						resourceTypes.Add(tpl.ResourceAndRelation.Namespace)
   940  					}
   941  
   942  					allRemainingRelationships := mapz.NewSet[string]()
   943  					for _, resourceType := range resourceTypes.AsSlice() {
   944  						iter, err := reader.QueryRelationships(ctx, datastore.RelationshipsFilter{
   945  							OptionalResourceType: resourceType,
   946  						})
   947  						require.NoError(err)
   948  						t.Cleanup(iter.Close)
   949  
   950  						for {
   951  							rel := iter.Next()
   952  							if rel == nil {
   953  								break
   954  							}
   955  							allRemainingRelationships.Add(tuple.MustString(rel))
   956  						}
   957  						iter.Close()
   958  					}
   959  
   960  					deletedRelationships := allRelationships.Subtract(allRemainingRelationships).AsSlice()
   961  					require.ElementsMatch(tc.expectedDeleted, deletedRelationships)
   962  
   963  					// Ensure the initial relationships are still present at the previous revision.
   964  					allInitialRelationships := mapz.NewSet[string]()
   965  					olderReader := ds.SnapshotReader(writtenRev)
   966  					for _, resourceType := range resourceTypes.AsSlice() {
   967  						iter, err := olderReader.QueryRelationships(ctx, datastore.RelationshipsFilter{
   968  							OptionalResourceType: resourceType,
   969  						})
   970  						require.NoError(err)
   971  						t.Cleanup(iter.Close)
   972  
   973  						for {
   974  							rel := iter.Next()
   975  							if rel == nil {
   976  								break
   977  							}
   978  							allInitialRelationships.Add(tuple.MustString(rel))
   979  						}
   980  						iter.Close()
   981  					}
   982  
   983  					require.ElementsMatch(tc.relationships, allInitialRelationships.AsSlice())
   984  				})
   985  			}
   986  		})
   987  	}
   988  }
   989  
   990  func RecreateRelationshipsAfterDeleteWithFilter(t *testing.T, tester DatastoreTester) {
   991  	require := require.New(t)
   992  
   993  	rawDS, err := tester.New(0, veryLargeGCInterval, veryLargeGCWindow, 1)
   994  	require.NoError(err)
   995  
   996  	ds, _ := testfixtures.StandardDatastoreWithSchema(rawDS, require)
   997  	ctx := context.Background()
   998  
   999  	relationships := make([]*core.RelationTuple, 100)
  1000  	for i := 0; i < 100; i++ {
  1001  		relationships[i] = tuple.MustParse(fmt.Sprintf("document:%d#owner@user:first", i))
  1002  	}
  1003  
  1004  	writeRelationships := func() error {
  1005  		_, err := common.WriteTuples(ctx, ds, core.RelationTupleUpdate_CREATE, relationships...)
  1006  		return err
  1007  	}
  1008  
  1009  	deleteRelationships := func() error {
  1010  		_, err := ds.ReadWriteTx(ctx, func(ctx context.Context, rwt datastore.ReadWriteTransaction) error {
  1011  			delLimit := uint64(100)
  1012  			_, err := rwt.DeleteRelationships(ctx, &v1.RelationshipFilter{
  1013  				OptionalRelation: "owner",
  1014  				OptionalSubjectFilter: &v1.SubjectFilter{
  1015  					SubjectType:       "user",
  1016  					OptionalSubjectId: "first",
  1017  				},
  1018  			}, options.WithDeleteLimit(&delLimit))
  1019  			return err
  1020  		})
  1021  		return err
  1022  	}
  1023  
  1024  	// Write the initial relationships.
  1025  	require.NoError(writeRelationships())
  1026  
  1027  	// Delete the relationships.
  1028  	require.NoError(deleteRelationships())
  1029  
  1030  	// Write the same relationships again.
  1031  	require.NoError(writeRelationships())
  1032  }
  1033  
  1034  // QueryRelationshipsWithVariousFiltersTest tests various relationship filters for query relationships.
  1035  func QueryRelationshipsWithVariousFiltersTest(t *testing.T, tester DatastoreTester) {
  1036  	tcs := []struct {
  1037  		name          string
  1038  		filter        datastore.RelationshipsFilter
  1039  		relationships []string
  1040  		expected      []string
  1041  	}{
  1042  		{
  1043  			name: "resource type",
  1044  			filter: datastore.RelationshipsFilter{
  1045  				OptionalResourceType: "document",
  1046  			},
  1047  			relationships: []string{
  1048  				"document:first#viewer@user:tom",
  1049  				"document:second#viewer@user:tom",
  1050  				"folder:secondfolder#viewer@user:tom",
  1051  				"folder:someotherfolder#viewer@user:tom",
  1052  			},
  1053  			expected: []string{"document:first#viewer@user:tom", "document:second#viewer@user:tom"},
  1054  		},
  1055  		{
  1056  			name: "resource id",
  1057  			filter: datastore.RelationshipsFilter{
  1058  				OptionalResourceIds: []string{"first"},
  1059  			},
  1060  			relationships: []string{"document:first#viewer@user:tom", "document:second#viewer@user:tom"},
  1061  			expected:      []string{"document:first#viewer@user:tom"},
  1062  		},
  1063  		{
  1064  			name: "resource id prefix",
  1065  			filter: datastore.RelationshipsFilter{
  1066  				OptionalResourceIDPrefix: "first",
  1067  			},
  1068  			relationships: []string{"document:first#viewer@user:tom", "document:second#viewer@user:tom"},
  1069  			expected:      []string{"document:first#viewer@user:tom"},
  1070  		},
  1071  		{
  1072  			name: "resource id different prefix",
  1073  			filter: datastore.RelationshipsFilter{
  1074  				OptionalResourceIDPrefix: "s",
  1075  			},
  1076  			relationships: []string{
  1077  				"document:first#viewer@user:tom",
  1078  				"document:second#viewer@user:tom",
  1079  				"folder:secondfolder#viewer@user:tom",
  1080  				"folder:someotherfolder#viewer@user:tom",
  1081  			},
  1082  			expected: []string{
  1083  				"document:second#viewer@user:tom",
  1084  				"folder:secondfolder#viewer@user:tom",
  1085  				"folder:someotherfolder#viewer@user:tom",
  1086  			},
  1087  		},
  1088  		{
  1089  			name: "resource id different longer prefix",
  1090  			filter: datastore.RelationshipsFilter{
  1091  				OptionalResourceIDPrefix: "se",
  1092  			},
  1093  			relationships: []string{
  1094  				"document:first#viewer@user:tom",
  1095  				"document:second#viewer@user:tom",
  1096  				"folder:secondfolder#viewer@user:tom",
  1097  				"folder:someotherfolder#viewer@user:tom",
  1098  			},
  1099  			expected: []string{
  1100  				"document:second#viewer@user:tom",
  1101  				"folder:secondfolder#viewer@user:tom",
  1102  			},
  1103  		},
  1104  		{
  1105  			name: "resource type and resource id different prefix",
  1106  			filter: datastore.RelationshipsFilter{
  1107  				OptionalResourceType:     "folder",
  1108  				OptionalResourceIDPrefix: "s",
  1109  			},
  1110  			relationships: []string{
  1111  				"document:first#viewer@user:tom",
  1112  				"document:second#viewer@user:tom",
  1113  				"folder:secondfolder#viewer@user:tom",
  1114  				"folder:someotherfolder#viewer@user:tom",
  1115  			},
  1116  			expected: []string{
  1117  				"folder:secondfolder#viewer@user:tom",
  1118  				"folder:someotherfolder#viewer@user:tom",
  1119  			},
  1120  		},
  1121  		{
  1122  			name: "full resource",
  1123  			filter: datastore.RelationshipsFilter{
  1124  				OptionalResourceType:     "folder",
  1125  				OptionalResourceIDPrefix: "s",
  1126  				OptionalResourceRelation: "viewer",
  1127  			},
  1128  			relationships: []string{
  1129  				"document:first#viewer@user:tom",
  1130  				"document:second#viewer@user:tom",
  1131  				"folder:secondfolder#viewer@user:tom",
  1132  				"folder:someotherfolder#viewer@user:tom",
  1133  			},
  1134  			expected: []string{
  1135  				"folder:secondfolder#viewer@user:tom",
  1136  				"folder:someotherfolder#viewer@user:tom",
  1137  			},
  1138  		},
  1139  		{
  1140  			name: "full resource mismatch",
  1141  			filter: datastore.RelationshipsFilter{
  1142  				OptionalResourceType:     "folder",
  1143  				OptionalResourceIDPrefix: "s",
  1144  				OptionalResourceRelation: "someotherelation",
  1145  			},
  1146  			relationships: []string{
  1147  				"document:first#viewer@user:tom",
  1148  				"document:second#viewer@user:tom",
  1149  				"folder:secondfolder#viewer@user:tom",
  1150  				"folder:someotherfolder#viewer@user:tom",
  1151  			},
  1152  			expected: []string{},
  1153  		},
  1154  		{
  1155  			name: "subject type",
  1156  			filter: datastore.RelationshipsFilter{
  1157  				OptionalSubjectsSelectors: []datastore.SubjectsSelector{
  1158  					{
  1159  						OptionalSubjectType: "user",
  1160  					},
  1161  				},
  1162  			},
  1163  			relationships: []string{
  1164  				"document:first#viewer@user:tom",
  1165  				"document:second#viewer@anotheruser:tom",
  1166  				"folder:secondfolder#viewer@anotheruser:tom",
  1167  				"folder:someotherfolder#viewer@user:tom",
  1168  			},
  1169  			expected: []string{
  1170  				"document:first#viewer@user:tom",
  1171  				"folder:someotherfolder#viewer@user:tom",
  1172  			},
  1173  		},
  1174  		{
  1175  			name: "subject ids",
  1176  			filter: datastore.RelationshipsFilter{
  1177  				OptionalSubjectsSelectors: []datastore.SubjectsSelector{
  1178  					{
  1179  						OptionalSubjectIds: []string{"tom"},
  1180  					},
  1181  				},
  1182  			},
  1183  			relationships: []string{
  1184  				"document:first#viewer@user:tom",
  1185  				"document:second#viewer@anotheruser:fred",
  1186  				"folder:secondfolder#viewer@anotheruser:fred",
  1187  				"folder:someotherfolder#viewer@user:tom",
  1188  			},
  1189  			expected: []string{
  1190  				"document:first#viewer@user:tom",
  1191  				"folder:someotherfolder#viewer@user:tom",
  1192  			},
  1193  		},
  1194  		{
  1195  			name: "multiple subject ids",
  1196  			filter: datastore.RelationshipsFilter{
  1197  				OptionalSubjectsSelectors: []datastore.SubjectsSelector{
  1198  					{
  1199  						OptionalSubjectIds: []string{"tom", "fred"},
  1200  					},
  1201  				},
  1202  			},
  1203  			relationships: []string{
  1204  				"document:first#viewer@user:tom",
  1205  				"document:second#viewer@anotheruser:fred",
  1206  				"folder:secondfolder#viewer@anotheruser:sarah",
  1207  				"folder:someotherfolder#viewer@user:tom",
  1208  			},
  1209  			expected: []string{
  1210  				"document:first#viewer@user:tom",
  1211  				"document:second#viewer@anotheruser:fred",
  1212  				"folder:someotherfolder#viewer@user:tom",
  1213  			},
  1214  		},
  1215  		{
  1216  			name: "non ellipsis subject relation",
  1217  			filter: datastore.RelationshipsFilter{
  1218  				OptionalSubjectsSelectors: []datastore.SubjectsSelector{
  1219  					{
  1220  						RelationFilter: datastore.SubjectRelationFilter{
  1221  							NonEllipsisRelation: "something",
  1222  						},
  1223  					},
  1224  				},
  1225  			},
  1226  			relationships: []string{
  1227  				"document:first#viewer@user:tom#...",
  1228  				"document:second#viewer@anotheruser:fred#something",
  1229  				"folder:secondfolder#viewer@anotheruser:sarah",
  1230  				"folder:someotherfolder#viewer@user:tom",
  1231  			},
  1232  			expected: []string{
  1233  				"document:second#viewer@anotheruser:fred#something",
  1234  			},
  1235  		},
  1236  		{
  1237  			name: "specific subject relation and ellipsis",
  1238  			filter: datastore.RelationshipsFilter{
  1239  				OptionalSubjectsSelectors: []datastore.SubjectsSelector{
  1240  					{
  1241  						RelationFilter: datastore.SubjectRelationFilter{
  1242  							NonEllipsisRelation:     "something",
  1243  							IncludeEllipsisRelation: true,
  1244  						},
  1245  					},
  1246  				},
  1247  			},
  1248  			relationships: []string{
  1249  				"document:first#viewer@user:tom",
  1250  				"document:second#viewer@anotheruser:fred#something",
  1251  				"folder:secondfolder#viewer@anotheruser:sarah",
  1252  				"folder:someotherfolder#viewer@user:tom",
  1253  			},
  1254  			expected: []string{
  1255  				"document:first#viewer@user:tom",
  1256  				"document:second#viewer@anotheruser:fred#something",
  1257  				"folder:secondfolder#viewer@anotheruser:sarah",
  1258  				"folder:someotherfolder#viewer@user:tom",
  1259  			},
  1260  		},
  1261  		{
  1262  			name: "specific subject relation and no non-ellipsis",
  1263  			filter: datastore.RelationshipsFilter{
  1264  				OptionalSubjectsSelectors: []datastore.SubjectsSelector{
  1265  					{
  1266  						RelationFilter: datastore.SubjectRelationFilter{
  1267  							NonEllipsisRelation:      "something",
  1268  							OnlyNonEllipsisRelations: true,
  1269  						},
  1270  					},
  1271  				},
  1272  			},
  1273  			relationships: []string{
  1274  				"document:first#viewer@user:tom",
  1275  				"document:second#viewer@anotheruser:fred#something",
  1276  				"folder:secondfolder#viewer@anotheruser:sarah",
  1277  				"folder:someotherfolder#viewer@user:tom",
  1278  			},
  1279  			expected: []string{
  1280  				"document:second#viewer@anotheruser:fred#something",
  1281  			},
  1282  		},
  1283  		{
  1284  			name: "only ellipsis",
  1285  			filter: datastore.RelationshipsFilter{
  1286  				OptionalSubjectsSelectors: []datastore.SubjectsSelector{
  1287  					{
  1288  						RelationFilter: datastore.SubjectRelationFilter{
  1289  							IncludeEllipsisRelation: true,
  1290  						},
  1291  					},
  1292  				},
  1293  			},
  1294  			relationships: []string{
  1295  				"document:first#viewer@user:tom",
  1296  				"document:second#viewer@anotheruser:fred#something",
  1297  				"folder:secondfolder#viewer@anotheruser:sarah",
  1298  				"folder:someotherfolder#viewer@user:tom",
  1299  			},
  1300  			expected: []string{
  1301  				"document:first#viewer@user:tom",
  1302  				"folder:secondfolder#viewer@anotheruser:sarah",
  1303  				"folder:someotherfolder#viewer@user:tom",
  1304  			},
  1305  		},
  1306  		{
  1307  			name: "multiple subject filters",
  1308  			filter: datastore.RelationshipsFilter{
  1309  				OptionalSubjectsSelectors: []datastore.SubjectsSelector{
  1310  					{
  1311  						RelationFilter: datastore.SubjectRelationFilter{
  1312  							NonEllipsisRelation: "something",
  1313  						},
  1314  					},
  1315  					{
  1316  						OptionalSubjectIds: []string{"tom"},
  1317  					},
  1318  				},
  1319  			},
  1320  			relationships: []string{
  1321  				"document:first#viewer@user:tom",
  1322  				"document:second#viewer@anotheruser:fred#something",
  1323  				"folder:secondfolder#viewer@anotheruser:sarah",
  1324  				"folder:someotherfolder#viewer@user:tom",
  1325  			},
  1326  			expected: []string{
  1327  				"document:first#viewer@user:tom",
  1328  				"document:second#viewer@anotheruser:fred#something",
  1329  				"folder:someotherfolder#viewer@user:tom",
  1330  			},
  1331  		},
  1332  		{
  1333  			name: "multiple subject filters and resource ID prefix",
  1334  			filter: datastore.RelationshipsFilter{
  1335  				OptionalResourceIDPrefix: "s",
  1336  				OptionalSubjectsSelectors: []datastore.SubjectsSelector{
  1337  					{
  1338  						RelationFilter: datastore.SubjectRelationFilter{
  1339  							NonEllipsisRelation: "something",
  1340  						},
  1341  					},
  1342  					{
  1343  						OptionalSubjectIds: []string{"tom"},
  1344  					},
  1345  				},
  1346  			},
  1347  			relationships: []string{
  1348  				"document:first#viewer@user:tom",
  1349  				"document:second#viewer@anotheruser:fred#something",
  1350  				"folder:secondfolder#viewer@anotheruser:sarah",
  1351  				"folder:someotherfolder#viewer@user:tom",
  1352  			},
  1353  			expected: []string{
  1354  				"document:second#viewer@anotheruser:fred#something",
  1355  				"folder:someotherfolder#viewer@user:tom",
  1356  			},
  1357  		},
  1358  	}
  1359  
  1360  	for _, tc := range tcs {
  1361  		tc := tc
  1362  		t.Run(tc.name, func(t *testing.T) {
  1363  			require := require.New(t)
  1364  
  1365  			rawDS, err := tester.New(0, veryLargeGCInterval, veryLargeGCWindow, 1)
  1366  			require.NoError(err)
  1367  
  1368  			ds, _ := testfixtures.StandardDatastoreWithSchema(rawDS, require)
  1369  			ctx := context.Background()
  1370  
  1371  			for _, rel := range tc.relationships {
  1372  				tpl := tuple.MustParse(rel)
  1373  				_, err = common.WriteTuples(ctx, ds, core.RelationTupleUpdate_CREATE, tpl)
  1374  				require.NoError(err)
  1375  			}
  1376  
  1377  			headRev, err := ds.HeadRevision(ctx)
  1378  			require.NoError(err)
  1379  
  1380  			reader := ds.SnapshotReader(headRev)
  1381  			iter, err := reader.QueryRelationships(ctx, tc.filter)
  1382  			require.NoError(err)
  1383  
  1384  			var results []string
  1385  			for {
  1386  				tpl := iter.Next()
  1387  				if tpl == nil {
  1388  					err := iter.Err()
  1389  					require.NoError(err)
  1390  					break
  1391  				}
  1392  
  1393  				results = append(results, tuple.MustString(tpl))
  1394  			}
  1395  			iter.Close()
  1396  
  1397  			require.ElementsMatch(tc.expected, results)
  1398  		})
  1399  	}
  1400  }
  1401  
  1402  // TypedTouchAlreadyExistingTest tests touching a relationship twice, when valid type information is provided.
  1403  func TypedTouchAlreadyExistingTest(t *testing.T, tester DatastoreTester) {
  1404  	require := require.New(t)
  1405  
  1406  	rawDS, err := tester.New(0, veryLargeGCInterval, veryLargeGCWindow, 1)
  1407  	require.NoError(err)
  1408  
  1409  	ds, _ := testfixtures.StandardDatastoreWithData(rawDS, require)
  1410  	ctx := context.Background()
  1411  
  1412  	tpl1 := tuple.Parse("document:foo#viewer@user:tom")
  1413  
  1414  	_, err = common.WriteTuples(ctx, ds, core.RelationTupleUpdate_TOUCH, tpl1)
  1415  	require.NoError(err)
  1416  	ensureTuples(ctx, require, ds, tpl1)
  1417  
  1418  	_, err = common.WriteTuples(ctx, ds, core.RelationTupleUpdate_TOUCH, tpl1)
  1419  	require.NoError(err)
  1420  	ensureTuples(ctx, require, ds, tpl1)
  1421  }
  1422  
  1423  // TypedTouchAlreadyExistingWithCaveatTest tests touching a relationship twice, when valid type information is provided.
  1424  func TypedTouchAlreadyExistingWithCaveatTest(t *testing.T, tester DatastoreTester) {
  1425  	require := require.New(t)
  1426  
  1427  	rawDS, err := tester.New(0, veryLargeGCInterval, veryLargeGCWindow, 1)
  1428  	require.NoError(err)
  1429  
  1430  	ds, _ := testfixtures.StandardDatastoreWithData(rawDS, require)
  1431  	ctx := context.Background()
  1432  
  1433  	ctpl1 := tuple.Parse("document:foo#caveated_viewer@user:tom[test:{\"foo\":\"bar\"}]")
  1434  
  1435  	_, err = common.WriteTuples(ctx, ds, core.RelationTupleUpdate_TOUCH, ctpl1)
  1436  	require.NoError(err)
  1437  	ensureTuples(ctx, require, ds, ctpl1)
  1438  
  1439  	ctpl1Updated := tuple.Parse("document:foo#caveated_viewer@user:tom[test:{\"foo\":\"baz\"}]")
  1440  
  1441  	_, err = common.WriteTuples(ctx, ds, core.RelationTupleUpdate_TOUCH, ctpl1Updated)
  1442  	require.NoError(err)
  1443  	ensureTuples(ctx, require, ds, ctpl1Updated)
  1444  }
  1445  
  1446  // CreateTouchDeleteTouchTest tests writing a relationship, touching it, deleting it, and then touching it.
  1447  func CreateTouchDeleteTouchTest(t *testing.T, tester DatastoreTester) {
  1448  	require := require.New(t)
  1449  
  1450  	rawDS, err := tester.New(0, veryLargeGCInterval, veryLargeGCWindow, 1)
  1451  	require.NoError(err)
  1452  
  1453  	ds, _ := testfixtures.StandardDatastoreWithData(rawDS, require)
  1454  	ctx := context.Background()
  1455  
  1456  	tpl1 := makeTestTuple("foo", "tom")
  1457  	tpl2 := makeTestTuple("foo", "sarah")
  1458  
  1459  	_, err = common.WriteTuples(ctx, ds, core.RelationTupleUpdate_CREATE, tpl1, tpl2)
  1460  	require.NoError(err)
  1461  
  1462  	ensureTuples(ctx, require, ds, tpl1, tpl2)
  1463  
  1464  	_, err = common.WriteTuples(ctx, ds, core.RelationTupleUpdate_TOUCH, tpl1, tpl2)
  1465  	require.NoError(err)
  1466  
  1467  	ensureTuples(ctx, require, ds, tpl1, tpl2)
  1468  
  1469  	_, err = common.WriteTuples(ctx, ds, core.RelationTupleUpdate_DELETE, tpl1, tpl2)
  1470  	require.NoError(err)
  1471  
  1472  	ensureNotTuples(ctx, require, ds, tpl1, tpl2)
  1473  
  1474  	_, err = common.WriteTuples(ctx, ds, core.RelationTupleUpdate_TOUCH, tpl1, tpl2)
  1475  	require.NoError(err)
  1476  
  1477  	ensureTuples(ctx, require, ds, tpl1, tpl2)
  1478  }
  1479  
  1480  // TouchAlreadyExistingCaveatedTest tests touching a relationship twice.
  1481  func TouchAlreadyExistingCaveatedTest(t *testing.T, tester DatastoreTester) {
  1482  	require := require.New(t)
  1483  
  1484  	rawDS, err := tester.New(0, veryLargeGCInterval, veryLargeGCWindow, 1)
  1485  	require.NoError(err)
  1486  
  1487  	ds, _ := testfixtures.StandardDatastoreWithData(rawDS, require)
  1488  	ctx := context.Background()
  1489  
  1490  	tpl1 := tuple.MustWithCaveat(makeTestTuple("foo", "tom"), "formercaveat")
  1491  	tpl2 := makeTestTuple("foo", "sarah")
  1492  	_, err = common.WriteTuples(ctx, ds, core.RelationTupleUpdate_TOUCH, tpl1, tpl2)
  1493  	require.NoError(err)
  1494  
  1495  	ensureTuples(ctx, require, ds, tpl1, tpl2)
  1496  
  1497  	ctpl1 := tuple.MustWithCaveat(makeTestTuple("foo", "tom"), "somecaveat")
  1498  	tpl3 := makeTestTuple("foo", "fred")
  1499  
  1500  	_, err = common.WriteTuples(ctx, ds, core.RelationTupleUpdate_TOUCH, ctpl1, tpl3)
  1501  	require.NoError(err)
  1502  
  1503  	ensureTuples(ctx, require, ds, tpl2, tpl3, ctpl1)
  1504  }
  1505  
  1506  func MultipleReadsInRWTTest(t *testing.T, tester DatastoreTester) {
  1507  	require := require.New(t)
  1508  
  1509  	rawDS, err := tester.New(0, veryLargeGCInterval, veryLargeGCWindow, 1)
  1510  	require.NoError(err)
  1511  
  1512  	ds, _ := testfixtures.StandardDatastoreWithData(rawDS, require)
  1513  	ctx := context.Background()
  1514  
  1515  	_, err = ds.ReadWriteTx(ctx, func(ctx context.Context, rwt datastore.ReadWriteTransaction) error {
  1516  		it, err := rwt.QueryRelationships(ctx, datastore.RelationshipsFilter{
  1517  			OptionalResourceType: "document",
  1518  		})
  1519  		require.NoError(err)
  1520  		it.Close()
  1521  
  1522  		it, err = rwt.QueryRelationships(ctx, datastore.RelationshipsFilter{
  1523  			OptionalResourceType: "folder",
  1524  		})
  1525  		require.NoError(err)
  1526  		it.Close()
  1527  
  1528  		return nil
  1529  	})
  1530  	require.NoError(err)
  1531  }
  1532  
  1533  // ConcurrentWriteSerializationTest uses goroutines and channels to intentionally set up a
  1534  // deadlocking dependency between transactions.
  1535  func ConcurrentWriteSerializationTest(t *testing.T, tester DatastoreTester) {
  1536  	require := require.New(t)
  1537  
  1538  	rawDS, err := tester.New(0, veryLargeGCInterval, veryLargeGCWindow, 1)
  1539  	require.NoError(err)
  1540  
  1541  	ds, _ := testfixtures.StandardDatastoreWithData(rawDS, require)
  1542  	ctx := context.Background()
  1543  
  1544  	g := errgroup.Group{}
  1545  
  1546  	waitToStart := make(chan struct{})
  1547  	waitToFinish := make(chan struct{})
  1548  	waitToStartCloser := sync.Once{}
  1549  	waitToFinishCloser := sync.Once{}
  1550  
  1551  	startTime := time.Now()
  1552  
  1553  	g.Go(func() error {
  1554  		_, err := ds.ReadWriteTx(ctx, func(ctx context.Context, rwt datastore.ReadWriteTransaction) error {
  1555  			iter, err := rwt.QueryRelationships(ctx, datastore.RelationshipsFilter{
  1556  				OptionalResourceType: testResourceNamespace,
  1557  			})
  1558  			iter.Close()
  1559  			require.NoError(err)
  1560  
  1561  			// We do NOT assert the error here because serialization problems can manifest as errors
  1562  			// on the individual writes.
  1563  			rtu := tuple.Touch(makeTestTuple("new_resource", "new_user"))
  1564  			err = rwt.WriteRelationships(ctx, []*core.RelationTupleUpdate{rtu})
  1565  
  1566  			waitToStartCloser.Do(func() {
  1567  				close(waitToStart)
  1568  			})
  1569  			<-waitToFinish
  1570  
  1571  			return err
  1572  		})
  1573  		require.NoError(err)
  1574  		return nil
  1575  	})
  1576  
  1577  	<-waitToStart
  1578  
  1579  	_, err = ds.ReadWriteTx(ctx, func(ctx context.Context, rwt datastore.ReadWriteTransaction) error {
  1580  		defer waitToFinishCloser.Do(func() {
  1581  			close(waitToFinish)
  1582  		})
  1583  
  1584  		rtu := tuple.Touch(makeTestTuple("another_resource", "another_user"))
  1585  		return rwt.WriteRelationships(ctx, []*core.RelationTupleUpdate{rtu})
  1586  	})
  1587  	require.NoError(err)
  1588  	require.NoError(g.Wait())
  1589  	require.Less(time.Since(startTime), 10*time.Second)
  1590  }
  1591  
  1592  func BulkDeleteRelationshipsTest(t *testing.T, tester DatastoreTester) {
  1593  	require := require.New(t)
  1594  
  1595  	rawDS, err := tester.New(0, veryLargeGCInterval, veryLargeGCWindow, 1)
  1596  	require.NoError(err)
  1597  
  1598  	ds, _ := testfixtures.StandardDatastoreWithSchema(rawDS, require)
  1599  	ctx := context.Background()
  1600  
  1601  	// Write a bunch of relationships.
  1602  	t.Log(time.Now(), "starting write")
  1603  	_, err = ds.ReadWriteTx(ctx, func(ctx context.Context, rwt datastore.ReadWriteTransaction) error {
  1604  		_, err := rwt.BulkLoad(ctx, testfixtures.NewBulkTupleGenerator(testResourceNamespace, testReaderRelation, testUserNamespace, 1000, t))
  1605  		if err != nil {
  1606  			return err
  1607  		}
  1608  
  1609  		_, err = rwt.BulkLoad(ctx, testfixtures.NewBulkTupleGenerator(testResourceNamespace, testEditorRelation, testUserNamespace, 1000, t))
  1610  		if err != nil {
  1611  			return err
  1612  		}
  1613  
  1614  		return nil
  1615  	})
  1616  	require.NoError(err)
  1617  
  1618  	// Issue a deletion for the first set of relationships.
  1619  	t.Log(time.Now(), "starting delete")
  1620  	deleteCount := 0
  1621  	deletedRev, err := ds.ReadWriteTx(ctx, func(ctx context.Context, rwt datastore.ReadWriteTransaction) error {
  1622  		t.Log(time.Now(), "deleting")
  1623  		deleteCount++
  1624  		_, err := rwt.DeleteRelationships(ctx, &v1.RelationshipFilter{
  1625  			ResourceType:     testResourceNamespace,
  1626  			OptionalRelation: testReaderRelation,
  1627  		})
  1628  		return err
  1629  	})
  1630  	require.NoError(err)
  1631  	require.Equal(1, deleteCount)
  1632  	t.Log(time.Now(), "finished delete")
  1633  
  1634  	// Ensure the relationships were removed.
  1635  	t.Log(time.Now(), "starting check")
  1636  	reader := ds.SnapshotReader(deletedRev)
  1637  	iter, err := reader.QueryRelationships(ctx, datastore.RelationshipsFilter{
  1638  		OptionalResourceType:     testResourceNamespace,
  1639  		OptionalResourceRelation: testReaderRelation,
  1640  	})
  1641  	require.NoError(err)
  1642  	defer iter.Close()
  1643  
  1644  	require.Nil(iter.Next(), "expected no results")
  1645  }
  1646  
  1647  func onrToSubjectsFilter(onr *core.ObjectAndRelation) datastore.SubjectsFilter {
  1648  	return datastore.SubjectsFilter{
  1649  		SubjectType:        onr.Namespace,
  1650  		OptionalSubjectIds: []string{onr.ObjectId},
  1651  		RelationFilter:     datastore.SubjectRelationFilter{}.WithNonEllipsisRelation(onr.Relation),
  1652  	}
  1653  }
  1654  
  1655  func ensureTuples(ctx context.Context, require *require.Assertions, ds datastore.Datastore, tpls ...*core.RelationTuple) {
  1656  	ensureTuplesStatus(ctx, require, ds, tpls, true)
  1657  }
  1658  
  1659  func ensureNotTuples(ctx context.Context, require *require.Assertions, ds datastore.Datastore, tpls ...*core.RelationTuple) {
  1660  	ensureTuplesStatus(ctx, require, ds, tpls, false)
  1661  }
  1662  
  1663  func ensureTuplesStatus(ctx context.Context, require *require.Assertions, ds datastore.Datastore, tpls []*core.RelationTuple, mustExist bool) {
  1664  	headRev, err := ds.HeadRevision(ctx)
  1665  	require.NoError(err)
  1666  
  1667  	reader := ds.SnapshotReader(headRev)
  1668  
  1669  	for _, tpl := range tpls {
  1670  		iter, err := reader.QueryRelationships(ctx, datastore.RelationshipsFilter{
  1671  			OptionalResourceType:     tpl.ResourceAndRelation.Namespace,
  1672  			OptionalResourceIds:      []string{tpl.ResourceAndRelation.ObjectId},
  1673  			OptionalResourceRelation: tpl.ResourceAndRelation.Relation,
  1674  			OptionalSubjectsSelectors: []datastore.SubjectsSelector{
  1675  				{
  1676  					OptionalSubjectType: tpl.Subject.Namespace,
  1677  					OptionalSubjectIds:  []string{tpl.Subject.ObjectId},
  1678  				},
  1679  			},
  1680  		})
  1681  		require.NoError(err)
  1682  		defer iter.Close()
  1683  
  1684  		found := iter.Next()
  1685  
  1686  		if mustExist {
  1687  			require.NotNil(found, "expected tuple %s", tuple.MustString(tpl))
  1688  		} else {
  1689  			require.Nil(found, "expected tuple %s to not exist", tuple.MustString(tpl))
  1690  		}
  1691  
  1692  		iter.Close()
  1693  
  1694  		if mustExist {
  1695  			require.Equal(tuple.MustString(tpl), tuple.MustString(found))
  1696  		}
  1697  	}
  1698  }
  1699  
  1700  func countTuples(ctx context.Context, require *require.Assertions, ds datastore.Datastore, resourceType string) int {
  1701  	headRev, err := ds.HeadRevision(ctx)
  1702  	require.NoError(err)
  1703  
  1704  	reader := ds.SnapshotReader(headRev)
  1705  
  1706  	iter, err := reader.QueryRelationships(ctx, datastore.RelationshipsFilter{
  1707  		OptionalResourceType: resourceType,
  1708  	})
  1709  	require.NoError(err)
  1710  	defer iter.Close()
  1711  
  1712  	counter := 0
  1713  	for {
  1714  		rel := iter.Next()
  1715  		if rel == nil {
  1716  			break
  1717  		}
  1718  
  1719  		counter++
  1720  	}
  1721  
  1722  	return counter
  1723  }