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

     1  package common
     2  
     3  import (
     4  	"testing"
     5  
     6  	"github.com/authzed/spicedb/pkg/datastore/options"
     7  
     8  	"github.com/authzed/spicedb/pkg/tuple"
     9  
    10  	sq "github.com/Masterminds/squirrel"
    11  	v1 "github.com/authzed/authzed-go/proto/authzed/api/v1"
    12  	"github.com/stretchr/testify/require"
    13  
    14  	"github.com/authzed/spicedb/pkg/datastore"
    15  )
    16  
    17  func TestSchemaQueryFilterer(t *testing.T) {
    18  	tests := []struct {
    19  		name                 string
    20  		run                  func(filterer SchemaQueryFilterer) SchemaQueryFilterer
    21  		expectedSQL          string
    22  		expectedArgs         []any
    23  		expectedColumnCounts map[string]int
    24  	}{
    25  		{
    26  			"relation filter",
    27  			func(filterer SchemaQueryFilterer) SchemaQueryFilterer {
    28  				return filterer.FilterToRelation("somerelation")
    29  			},
    30  			"SELECT * WHERE relation = ?",
    31  			[]any{"somerelation"},
    32  			map[string]int{
    33  				"relation": 1,
    34  			},
    35  		},
    36  		{
    37  			"resource ID filter",
    38  			func(filterer SchemaQueryFilterer) SchemaQueryFilterer {
    39  				return filterer.FilterToResourceID("someresourceid")
    40  			},
    41  			"SELECT * WHERE object_id = ?",
    42  			[]any{"someresourceid"},
    43  			map[string]int{
    44  				"object_id": 1,
    45  			},
    46  		},
    47  		{
    48  			"resource IDs filter",
    49  			func(filterer SchemaQueryFilterer) SchemaQueryFilterer {
    50  				return filterer.MustFilterWithResourceIDPrefix("someprefix")
    51  			},
    52  			"SELECT * WHERE object_id LIKE ?",
    53  			[]any{"someprefix%"},
    54  			map[string]int{}, // object_id is not statically used, so not present in the map
    55  		},
    56  		{
    57  			"resource IDs prefix filter",
    58  			func(filterer SchemaQueryFilterer) SchemaQueryFilterer {
    59  				return filterer.MustFilterToResourceIDs([]string{"someresourceid", "anotherresourceid"})
    60  			},
    61  			"SELECT * WHERE object_id IN (?, ?)",
    62  			[]any{"someresourceid", "anotherresourceid"},
    63  			map[string]int{
    64  				"object_id": 2,
    65  			},
    66  		},
    67  		{
    68  			"resource type filter",
    69  			func(filterer SchemaQueryFilterer) SchemaQueryFilterer {
    70  				return filterer.FilterToResourceType("sometype")
    71  			},
    72  			"SELECT * WHERE ns = ?",
    73  			[]any{"sometype"},
    74  			map[string]int{
    75  				"ns": 1,
    76  			},
    77  		},
    78  		{
    79  			"resource filter",
    80  			func(filterer SchemaQueryFilterer) SchemaQueryFilterer {
    81  				return filterer.FilterToResourceType("sometype").FilterToResourceID("someobj").FilterToRelation("somerel")
    82  			},
    83  			"SELECT * WHERE ns = ? AND object_id = ? AND relation = ?",
    84  			[]any{"sometype", "someobj", "somerel"},
    85  			map[string]int{
    86  				"ns":        1,
    87  				"object_id": 1,
    88  				"relation":  1,
    89  			},
    90  		},
    91  		{
    92  			"relationships filter with no IDs or relations",
    93  			func(filterer SchemaQueryFilterer) SchemaQueryFilterer {
    94  				return filterer.MustFilterWithRelationshipsFilter(datastore.RelationshipsFilter{
    95  					OptionalResourceType: "sometype",
    96  				})
    97  			},
    98  			"SELECT * WHERE ns = ?",
    99  			[]any{"sometype"},
   100  			map[string]int{
   101  				"ns": 1,
   102  			},
   103  		},
   104  		{
   105  			"relationships filter with single ID",
   106  			func(filterer SchemaQueryFilterer) SchemaQueryFilterer {
   107  				return filterer.MustFilterWithRelationshipsFilter(datastore.RelationshipsFilter{
   108  					OptionalResourceType: "sometype",
   109  					OptionalResourceIds:  []string{"someid"},
   110  				})
   111  			},
   112  			"SELECT * WHERE ns = ? AND object_id IN (?)",
   113  			[]any{"sometype", "someid"},
   114  			map[string]int{
   115  				"ns":        1,
   116  				"object_id": 1,
   117  			},
   118  		},
   119  		{
   120  			"relationships filter with no IDs",
   121  			func(filterer SchemaQueryFilterer) SchemaQueryFilterer {
   122  				return filterer.MustFilterWithRelationshipsFilter(datastore.RelationshipsFilter{
   123  					OptionalResourceType: "sometype",
   124  					OptionalResourceIds:  []string{},
   125  				})
   126  			},
   127  			"SELECT * WHERE ns = ?",
   128  			[]any{"sometype"},
   129  			map[string]int{
   130  				"ns": 1,
   131  			},
   132  		},
   133  		{
   134  			"relationships filter with multiple IDs",
   135  			func(filterer SchemaQueryFilterer) SchemaQueryFilterer {
   136  				return filterer.MustFilterWithRelationshipsFilter(datastore.RelationshipsFilter{
   137  					OptionalResourceType: "sometype",
   138  					OptionalResourceIds:  []string{"someid", "anotherid"},
   139  				})
   140  			},
   141  			"SELECT * WHERE ns = ? AND object_id IN (?, ?)",
   142  			[]any{"sometype", "someid", "anotherid"},
   143  			map[string]int{
   144  				"ns":        1,
   145  				"object_id": 2,
   146  			},
   147  		},
   148  		{
   149  			"subjects filter with no IDs or relations",
   150  			func(filterer SchemaQueryFilterer) SchemaQueryFilterer {
   151  				return filterer.MustFilterWithSubjectsSelectors(datastore.SubjectsSelector{
   152  					OptionalSubjectType: "somesubjectype",
   153  				})
   154  			},
   155  			"SELECT * WHERE ((subject_ns = ?))",
   156  			[]any{"somesubjectype"},
   157  			map[string]int{
   158  				"subject_ns": 1,
   159  			},
   160  		},
   161  		{
   162  			"multiple subjects filters with just types",
   163  			func(filterer SchemaQueryFilterer) SchemaQueryFilterer {
   164  				return filterer.MustFilterWithSubjectsSelectors(datastore.SubjectsSelector{
   165  					OptionalSubjectType: "somesubjectype",
   166  				}, datastore.SubjectsSelector{
   167  					OptionalSubjectType: "anothersubjectype",
   168  				})
   169  			},
   170  			"SELECT * WHERE ((subject_ns = ?) OR (subject_ns = ?))",
   171  			[]any{"somesubjectype", "anothersubjectype"},
   172  			map[string]int{
   173  				"subject_ns": 2,
   174  			},
   175  		},
   176  		{
   177  			"subjects filter with single ID",
   178  			func(filterer SchemaQueryFilterer) SchemaQueryFilterer {
   179  				return filterer.MustFilterWithSubjectsSelectors(datastore.SubjectsSelector{
   180  					OptionalSubjectType: "somesubjectype",
   181  					OptionalSubjectIds:  []string{"somesubjectid"},
   182  				})
   183  			},
   184  			"SELECT * WHERE ((subject_ns = ? AND subject_object_id IN (?)))",
   185  			[]any{"somesubjectype", "somesubjectid"},
   186  			map[string]int{
   187  				"subject_ns":        1,
   188  				"subject_object_id": 1,
   189  			},
   190  		},
   191  		{
   192  			"subjects filter with single ID and no type",
   193  			func(filterer SchemaQueryFilterer) SchemaQueryFilterer {
   194  				return filterer.MustFilterWithSubjectsSelectors(datastore.SubjectsSelector{
   195  					OptionalSubjectIds: []string{"somesubjectid"},
   196  				})
   197  			},
   198  			"SELECT * WHERE ((subject_object_id IN (?)))",
   199  			[]any{"somesubjectid"},
   200  			map[string]int{
   201  				"subject_object_id": 1,
   202  			},
   203  		},
   204  		{
   205  			"empty subjects filter",
   206  			func(filterer SchemaQueryFilterer) SchemaQueryFilterer {
   207  				return filterer.MustFilterWithSubjectsSelectors(datastore.SubjectsSelector{})
   208  			},
   209  			"SELECT * WHERE ((1=1))",
   210  			nil,
   211  			map[string]int{},
   212  		},
   213  		{
   214  			"subjects filter with multiple IDs",
   215  			func(filterer SchemaQueryFilterer) SchemaQueryFilterer {
   216  				return filterer.MustFilterWithSubjectsSelectors(datastore.SubjectsSelector{
   217  					OptionalSubjectType: "somesubjectype",
   218  					OptionalSubjectIds:  []string{"somesubjectid", "anothersubjectid"},
   219  				})
   220  			},
   221  			"SELECT * WHERE ((subject_ns = ? AND subject_object_id IN (?, ?)))",
   222  			[]any{"somesubjectype", "somesubjectid", "anothersubjectid"},
   223  			map[string]int{
   224  				"subject_ns":        1,
   225  				"subject_object_id": 2,
   226  			},
   227  		},
   228  		{
   229  			"subjects filter with single ellipsis relation",
   230  			func(filterer SchemaQueryFilterer) SchemaQueryFilterer {
   231  				return filterer.MustFilterWithSubjectsSelectors(datastore.SubjectsSelector{
   232  					OptionalSubjectType: "somesubjectype",
   233  					RelationFilter:      datastore.SubjectRelationFilter{}.WithEllipsisRelation(),
   234  				})
   235  			},
   236  			"SELECT * WHERE ((subject_ns = ? AND subject_relation = ?))",
   237  			[]any{"somesubjectype", "..."},
   238  			map[string]int{
   239  				"subject_ns":       1,
   240  				"subject_relation": 1,
   241  			},
   242  		},
   243  		{
   244  			"subjects filter with single defined relation",
   245  			func(filterer SchemaQueryFilterer) SchemaQueryFilterer {
   246  				return filterer.MustFilterWithSubjectsSelectors(datastore.SubjectsSelector{
   247  					OptionalSubjectType: "somesubjectype",
   248  					RelationFilter:      datastore.SubjectRelationFilter{}.WithNonEllipsisRelation("somesubrel"),
   249  				})
   250  			},
   251  			"SELECT * WHERE ((subject_ns = ? AND subject_relation = ?))",
   252  			[]any{"somesubjectype", "somesubrel"},
   253  			map[string]int{
   254  				"subject_ns":       1,
   255  				"subject_relation": 1,
   256  			},
   257  		},
   258  		{
   259  			"subjects filter with only non-ellipsis",
   260  			func(filterer SchemaQueryFilterer) SchemaQueryFilterer {
   261  				return filterer.MustFilterWithSubjectsSelectors(datastore.SubjectsSelector{
   262  					OptionalSubjectType: "somesubjectype",
   263  					RelationFilter:      datastore.SubjectRelationFilter{}.WithOnlyNonEllipsisRelations(),
   264  				})
   265  			},
   266  			"SELECT * WHERE ((subject_ns = ? AND subject_relation <> ?))",
   267  			[]any{"somesubjectype", "..."},
   268  			map[string]int{
   269  				"subject_ns":       1,
   270  				"subject_relation": 1,
   271  			},
   272  		},
   273  		{
   274  			"subjects filter with defined relation and ellipsis",
   275  			func(filterer SchemaQueryFilterer) SchemaQueryFilterer {
   276  				return filterer.MustFilterWithSubjectsSelectors(datastore.SubjectsSelector{
   277  					OptionalSubjectType: "somesubjectype",
   278  					RelationFilter:      datastore.SubjectRelationFilter{}.WithNonEllipsisRelation("somesubrel").WithEllipsisRelation(),
   279  				})
   280  			},
   281  			"SELECT * WHERE ((subject_ns = ? AND (subject_relation = ? OR subject_relation = ?)))",
   282  			[]any{"somesubjectype", "...", "somesubrel"},
   283  			map[string]int{
   284  				"subject_ns":       1,
   285  				"subject_relation": 2,
   286  			},
   287  		},
   288  		{
   289  			"subjects filter",
   290  			func(filterer SchemaQueryFilterer) SchemaQueryFilterer {
   291  				return filterer.MustFilterWithSubjectsSelectors(datastore.SubjectsSelector{
   292  					OptionalSubjectType: "somesubjectype",
   293  					OptionalSubjectIds:  []string{"somesubjectid", "anothersubjectid"},
   294  					RelationFilter:      datastore.SubjectRelationFilter{}.WithNonEllipsisRelation("somesubrel").WithEllipsisRelation(),
   295  				})
   296  			},
   297  			"SELECT * WHERE ((subject_ns = ? AND subject_object_id IN (?, ?) AND (subject_relation = ? OR subject_relation = ?)))",
   298  			[]any{"somesubjectype", "somesubjectid", "anothersubjectid", "...", "somesubrel"},
   299  			map[string]int{
   300  				"subject_ns":        1,
   301  				"subject_object_id": 2,
   302  				"subject_relation":  2,
   303  			},
   304  		},
   305  		{
   306  			"multiple subjects filter",
   307  			func(filterer SchemaQueryFilterer) SchemaQueryFilterer {
   308  				return filterer.MustFilterWithSubjectsSelectors(
   309  					datastore.SubjectsSelector{
   310  						OptionalSubjectType: "somesubjectype",
   311  						OptionalSubjectIds:  []string{"a", "b"},
   312  						RelationFilter:      datastore.SubjectRelationFilter{}.WithNonEllipsisRelation("somesubrel").WithEllipsisRelation(),
   313  					},
   314  					datastore.SubjectsSelector{
   315  						OptionalSubjectType: "anothersubjecttype",
   316  						OptionalSubjectIds:  []string{"b", "c"},
   317  						RelationFilter:      datastore.SubjectRelationFilter{}.WithNonEllipsisRelation("anotherrel").WithEllipsisRelation(),
   318  					},
   319  					datastore.SubjectsSelector{
   320  						OptionalSubjectType: "thirdsubjectype",
   321  						RelationFilter:      datastore.SubjectRelationFilter{}.WithOnlyNonEllipsisRelations(),
   322  					},
   323  				)
   324  			},
   325  			"SELECT * WHERE ((subject_ns = ? AND subject_object_id IN (?, ?) AND (subject_relation = ? OR subject_relation = ?)) OR (subject_ns = ? AND subject_object_id IN (?, ?) AND (subject_relation = ? OR subject_relation = ?)) OR (subject_ns = ? AND subject_relation <> ?))",
   326  			[]any{"somesubjectype", "a", "b", "...", "somesubrel", "anothersubjecttype", "b", "c", "...", "anotherrel", "thirdsubjectype", "..."},
   327  			map[string]int{
   328  				"subject_ns":        3,
   329  				"subject_object_id": 4,
   330  				"subject_relation":  5,
   331  			},
   332  		},
   333  		{
   334  			"v1 subject filter with namespace",
   335  			func(filterer SchemaQueryFilterer) SchemaQueryFilterer {
   336  				return filterer.FilterToSubjectFilter(&v1.SubjectFilter{
   337  					SubjectType: "subns",
   338  				})
   339  			},
   340  			"SELECT * WHERE subject_ns = ?",
   341  			[]any{"subns"},
   342  			map[string]int{
   343  				"subject_ns": 1,
   344  			},
   345  		},
   346  		{
   347  			"v1 subject filter with subject id",
   348  			func(filterer SchemaQueryFilterer) SchemaQueryFilterer {
   349  				return filterer.FilterToSubjectFilter(&v1.SubjectFilter{
   350  					SubjectType:       "subns",
   351  					OptionalSubjectId: "subid",
   352  				})
   353  			},
   354  			"SELECT * WHERE subject_ns = ? AND subject_object_id = ?",
   355  			[]any{"subns", "subid"},
   356  			map[string]int{
   357  				"subject_ns":        1,
   358  				"subject_object_id": 1,
   359  			},
   360  		},
   361  		{
   362  			"v1 subject filter with relation",
   363  			func(filterer SchemaQueryFilterer) SchemaQueryFilterer {
   364  				return filterer.FilterToSubjectFilter(&v1.SubjectFilter{
   365  					SubjectType: "subns",
   366  					OptionalRelation: &v1.SubjectFilter_RelationFilter{
   367  						Relation: "subrel",
   368  					},
   369  				})
   370  			},
   371  			"SELECT * WHERE subject_ns = ? AND subject_relation = ?",
   372  			[]any{"subns", "subrel"},
   373  			map[string]int{
   374  				"subject_ns":       1,
   375  				"subject_relation": 1,
   376  			},
   377  		},
   378  		{
   379  			"v1 subject filter with empty relation",
   380  			func(filterer SchemaQueryFilterer) SchemaQueryFilterer {
   381  				return filterer.FilterToSubjectFilter(&v1.SubjectFilter{
   382  					SubjectType: "subns",
   383  					OptionalRelation: &v1.SubjectFilter_RelationFilter{
   384  						Relation: "",
   385  					},
   386  				})
   387  			},
   388  			"SELECT * WHERE subject_ns = ? AND subject_relation = ?",
   389  			[]any{"subns", "..."},
   390  			map[string]int{
   391  				"subject_ns":       1,
   392  				"subject_relation": 1,
   393  			},
   394  		},
   395  		{
   396  			"v1 subject filter",
   397  			func(filterer SchemaQueryFilterer) SchemaQueryFilterer {
   398  				return filterer.FilterToSubjectFilter(&v1.SubjectFilter{
   399  					SubjectType:       "subns",
   400  					OptionalSubjectId: "subid",
   401  					OptionalRelation: &v1.SubjectFilter_RelationFilter{
   402  						Relation: "somerel",
   403  					},
   404  				})
   405  			},
   406  			"SELECT * WHERE subject_ns = ? AND subject_object_id = ? AND subject_relation = ?",
   407  			[]any{"subns", "subid", "somerel"},
   408  			map[string]int{
   409  				"subject_ns":        1,
   410  				"subject_object_id": 1,
   411  				"subject_relation":  1,
   412  			},
   413  		},
   414  		{
   415  			"limit",
   416  			func(filterer SchemaQueryFilterer) SchemaQueryFilterer {
   417  				return filterer.limit(100)
   418  			},
   419  			"SELECT * LIMIT 100",
   420  			nil,
   421  			map[string]int{},
   422  		},
   423  		{
   424  			"full resources filter",
   425  			func(filterer SchemaQueryFilterer) SchemaQueryFilterer {
   426  				return filterer.MustFilterWithRelationshipsFilter(
   427  					datastore.RelationshipsFilter{
   428  						OptionalResourceType:     "someresourcetype",
   429  						OptionalResourceIds:      []string{"someid", "anotherid"},
   430  						OptionalResourceRelation: "somerelation",
   431  						OptionalSubjectsSelectors: []datastore.SubjectsSelector{
   432  							{
   433  								OptionalSubjectType: "somesubjectype",
   434  								OptionalSubjectIds:  []string{"somesubjectid", "anothersubjectid"},
   435  								RelationFilter:      datastore.SubjectRelationFilter{}.WithNonEllipsisRelation("somesubrel").WithEllipsisRelation(),
   436  							},
   437  						},
   438  					},
   439  				)
   440  			},
   441  			"SELECT * WHERE ns = ? AND relation = ? AND object_id IN (?, ?) AND ((subject_ns = ? AND subject_object_id IN (?, ?) AND (subject_relation = ? OR subject_relation = ?)))",
   442  			[]any{"someresourcetype", "somerelation", "someid", "anotherid", "somesubjectype", "somesubjectid", "anothersubjectid", "...", "somesubrel"},
   443  			map[string]int{
   444  				"ns":                1,
   445  				"object_id":         2,
   446  				"relation":          1,
   447  				"subject_ns":        1,
   448  				"subject_object_id": 2,
   449  				"subject_relation":  2,
   450  			},
   451  		},
   452  		{
   453  			"order by",
   454  			func(filterer SchemaQueryFilterer) SchemaQueryFilterer {
   455  				return filterer.MustFilterWithRelationshipsFilter(
   456  					datastore.RelationshipsFilter{
   457  						OptionalResourceType: "someresourcetype",
   458  					},
   459  				).TupleOrder(options.ByResource)
   460  			},
   461  			"SELECT * WHERE ns = ? ORDER BY ns, object_id, relation, subject_ns, subject_object_id, subject_relation",
   462  			[]any{"someresourcetype"},
   463  			map[string]int{
   464  				"ns": 1,
   465  			},
   466  		},
   467  		{
   468  			"after with just namespace",
   469  			func(filterer SchemaQueryFilterer) SchemaQueryFilterer {
   470  				return filterer.MustFilterWithRelationshipsFilter(
   471  					datastore.RelationshipsFilter{
   472  						OptionalResourceType: "someresourcetype",
   473  					},
   474  				).After(tuple.MustParse("someresourcetype:foo#viewer@user:bar"), options.ByResource)
   475  			},
   476  			"SELECT * WHERE ns = ? AND (object_id,relation,subject_ns,subject_object_id,subject_relation) > (?,?,?,?,?)",
   477  			[]any{"someresourcetype", "foo", "viewer", "user", "bar", "..."},
   478  			map[string]int{
   479  				"ns": 1,
   480  			},
   481  		},
   482  		{
   483  			"after with just relation",
   484  			func(filterer SchemaQueryFilterer) SchemaQueryFilterer {
   485  				return filterer.MustFilterWithRelationshipsFilter(
   486  					datastore.RelationshipsFilter{
   487  						OptionalResourceRelation: "somerelation",
   488  					},
   489  				).After(tuple.MustParse("someresourcetype:foo#viewer@user:bar"), options.ByResource)
   490  			},
   491  			"SELECT * WHERE relation = ? AND (ns,object_id,subject_ns,subject_object_id,subject_relation) > (?,?,?,?,?)",
   492  			[]any{"somerelation", "someresourcetype", "foo", "user", "bar", "..."},
   493  			map[string]int{
   494  				"relation": 1,
   495  			},
   496  		},
   497  		{
   498  			"after with namespace and single resource id",
   499  			func(filterer SchemaQueryFilterer) SchemaQueryFilterer {
   500  				return filterer.MustFilterWithRelationshipsFilter(
   501  					datastore.RelationshipsFilter{
   502  						OptionalResourceType: "someresourcetype",
   503  						OptionalResourceIds:  []string{"one"},
   504  					},
   505  				).After(tuple.MustParse("someresourcetype:foo#viewer@user:bar"), options.ByResource)
   506  			},
   507  			"SELECT * WHERE ns = ? AND object_id IN (?) AND (relation,subject_ns,subject_object_id,subject_relation) > (?,?,?,?)",
   508  			[]any{"someresourcetype", "one", "viewer", "user", "bar", "..."},
   509  			map[string]int{
   510  				"ns":        1,
   511  				"object_id": 1,
   512  			},
   513  		},
   514  		{
   515  			"after with single resource id",
   516  			func(filterer SchemaQueryFilterer) SchemaQueryFilterer {
   517  				return filterer.MustFilterWithRelationshipsFilter(
   518  					datastore.RelationshipsFilter{
   519  						OptionalResourceIds: []string{"one"},
   520  					},
   521  				).After(tuple.MustParse("someresourcetype:foo#viewer@user:bar"), options.ByResource)
   522  			},
   523  			"SELECT * WHERE object_id IN (?) AND (ns,relation,subject_ns,subject_object_id,subject_relation) > (?,?,?,?,?)",
   524  			[]any{"one", "someresourcetype", "viewer", "user", "bar", "..."},
   525  			map[string]int{
   526  				"object_id": 1,
   527  			},
   528  		},
   529  		{
   530  			"after with namespace and resource ids",
   531  			func(filterer SchemaQueryFilterer) SchemaQueryFilterer {
   532  				return filterer.MustFilterWithRelationshipsFilter(
   533  					datastore.RelationshipsFilter{
   534  						OptionalResourceType: "someresourcetype",
   535  						OptionalResourceIds:  []string{"one", "two"},
   536  					},
   537  				).After(tuple.MustParse("someresourcetype:foo#viewer@user:bar"), options.ByResource)
   538  			},
   539  			"SELECT * WHERE ns = ? AND object_id IN (?, ?) AND (object_id,relation,subject_ns,subject_object_id,subject_relation) > (?,?,?,?,?)",
   540  			[]any{"someresourcetype", "one", "two", "foo", "viewer", "user", "bar", "..."},
   541  			map[string]int{
   542  				"ns":        1,
   543  				"object_id": 2,
   544  			},
   545  		},
   546  		{
   547  			"after with namespace and relation",
   548  			func(filterer SchemaQueryFilterer) SchemaQueryFilterer {
   549  				return filterer.MustFilterWithRelationshipsFilter(
   550  					datastore.RelationshipsFilter{
   551  						OptionalResourceType:     "someresourcetype",
   552  						OptionalResourceRelation: "somerelation",
   553  					},
   554  				).After(tuple.MustParse("someresourcetype:foo#viewer@user:bar"), options.ByResource)
   555  			},
   556  			"SELECT * WHERE ns = ? AND relation = ? AND (object_id,subject_ns,subject_object_id,subject_relation) > (?,?,?,?)",
   557  			[]any{"someresourcetype", "somerelation", "foo", "user", "bar", "..."},
   558  			map[string]int{
   559  				"ns":       1,
   560  				"relation": 1,
   561  			},
   562  		},
   563  		{
   564  			"after with subject namespace",
   565  			func(filterer SchemaQueryFilterer) SchemaQueryFilterer {
   566  				return filterer.MustFilterWithSubjectsSelectors(datastore.SubjectsSelector{
   567  					OptionalSubjectType: "somesubjectype",
   568  				}).After(tuple.MustParse("someresourcetype:foo#viewer@user:bar"), options.ByResource)
   569  			},
   570  			"SELECT * WHERE ((subject_ns = ?)) AND (ns,object_id,relation,subject_object_id,subject_relation) > (?,?,?,?,?)",
   571  			[]any{"somesubjectype", "someresourcetype", "foo", "viewer", "bar", "..."},
   572  			map[string]int{
   573  				"subject_ns": 1,
   574  			},
   575  		},
   576  		{
   577  			"after with subject namespaces",
   578  			func(filterer SchemaQueryFilterer) SchemaQueryFilterer {
   579  				// NOTE: this isn't really valid (it'll return no results), but is a good test to ensure
   580  				// the duplicate subject type results in the subject type being in the ORDER BY.
   581  				return filterer.MustFilterWithSubjectsSelectors(datastore.SubjectsSelector{
   582  					OptionalSubjectType: "somesubjectype",
   583  				}).MustFilterWithSubjectsSelectors(datastore.SubjectsSelector{
   584  					OptionalSubjectType: "anothersubjectype",
   585  				}).After(tuple.MustParse("someresourcetype:foo#viewer@user:bar"), options.ByResource)
   586  			},
   587  			"SELECT * WHERE ((subject_ns = ?)) AND ((subject_ns = ?)) AND (ns,object_id,relation,subject_ns,subject_object_id,subject_relation) > (?,?,?,?,?,?)",
   588  			[]any{"somesubjectype", "anothersubjectype", "someresourcetype", "foo", "viewer", "user", "bar", "..."},
   589  			map[string]int{
   590  				"subject_ns": 2,
   591  			},
   592  		},
   593  		{
   594  			"after with resource ID prefix",
   595  			func(filterer SchemaQueryFilterer) SchemaQueryFilterer {
   596  				return filterer.MustFilterWithResourceIDPrefix("someprefix").After(tuple.MustParse("someresourcetype:foo#viewer@user:bar"), options.ByResource)
   597  			},
   598  			"SELECT * WHERE object_id LIKE ? AND (ns,object_id,relation,subject_ns,subject_object_id,subject_relation) > (?,?,?,?,?,?)",
   599  			[]any{"someprefix%", "someresourcetype", "foo", "viewer", "user", "bar", "..."},
   600  			map[string]int{},
   601  		},
   602  		{
   603  			"order by subject",
   604  			func(filterer SchemaQueryFilterer) SchemaQueryFilterer {
   605  				return filterer.MustFilterWithRelationshipsFilter(
   606  					datastore.RelationshipsFilter{
   607  						OptionalResourceType: "someresourcetype",
   608  					},
   609  				).TupleOrder(options.BySubject)
   610  			},
   611  			"SELECT * WHERE ns = ? ORDER BY subject_ns, subject_object_id, subject_relation, ns, object_id, relation",
   612  			[]any{"someresourcetype"},
   613  			map[string]int{
   614  				"ns": 1,
   615  			},
   616  		},
   617  		{
   618  			"order by subject, after with subject namespace",
   619  			func(filterer SchemaQueryFilterer) SchemaQueryFilterer {
   620  				return filterer.MustFilterWithSubjectsSelectors(datastore.SubjectsSelector{
   621  					OptionalSubjectType: "somesubjectype",
   622  				}).After(tuple.MustParse("someresourcetype:foo#viewer@user:bar"), options.BySubject)
   623  			},
   624  			"SELECT * WHERE ((subject_ns = ?)) AND (subject_object_id,ns,object_id,relation,subject_relation) > (?,?,?,?,?)",
   625  			[]any{"somesubjectype", "bar", "someresourcetype", "foo", "viewer", "..."},
   626  			map[string]int{
   627  				"subject_ns": 1,
   628  			},
   629  		},
   630  		{
   631  			"order by subject, after with subject namespace and subject object id",
   632  			func(filterer SchemaQueryFilterer) SchemaQueryFilterer {
   633  				return filterer.MustFilterWithSubjectsSelectors(datastore.SubjectsSelector{
   634  					OptionalSubjectType: "somesubjectype",
   635  					OptionalSubjectIds:  []string{"foo"},
   636  				}).After(tuple.MustParse("someresourcetype:someresource#viewer@user:bar"), options.BySubject)
   637  			},
   638  			"SELECT * WHERE ((subject_ns = ? AND subject_object_id IN (?))) AND (ns,object_id,relation,subject_relation) > (?,?,?,?)",
   639  			[]any{"somesubjectype", "foo", "someresourcetype", "someresource", "viewer", "..."},
   640  			map[string]int{"subject_ns": 1, "subject_object_id": 1},
   641  		},
   642  		{
   643  			"order by subject, after with subject namespace and multiple subject object IDs",
   644  			func(filterer SchemaQueryFilterer) SchemaQueryFilterer {
   645  				return filterer.MustFilterWithSubjectsSelectors(datastore.SubjectsSelector{
   646  					OptionalSubjectType: "somesubjectype",
   647  					OptionalSubjectIds:  []string{"foo", "bar"},
   648  				}).After(tuple.MustParse("someresourcetype:someresource#viewer@user:next"), options.BySubject)
   649  			},
   650  			"SELECT * WHERE ((subject_ns = ? AND subject_object_id IN (?, ?))) AND (subject_object_id,ns,object_id,relation,subject_relation) > (?,?,?,?,?)",
   651  			[]any{"somesubjectype", "foo", "bar", "next", "someresourcetype", "someresource", "viewer", "..."},
   652  			map[string]int{"subject_ns": 1, "subject_object_id": 2},
   653  		},
   654  	}
   655  
   656  	for _, test := range tests {
   657  		test := test
   658  		t.Run(test.name, func(t *testing.T) {
   659  			base := sq.Select("*")
   660  			schema := NewSchemaInformation(
   661  				"ns",
   662  				"object_id",
   663  				"relation",
   664  				"subject_ns",
   665  				"subject_object_id",
   666  				"subject_relation",
   667  				"caveat",
   668  				TupleComparison,
   669  			)
   670  			filterer := NewSchemaQueryFilterer(schema, base)
   671  
   672  			ran := test.run(filterer)
   673  			require.Equal(t, test.expectedColumnCounts, ran.filteringColumnCounts)
   674  
   675  			sql, args, err := ran.queryBuilder.ToSql()
   676  			require.NoError(t, err)
   677  			require.Equal(t, test.expectedSQL, sql)
   678  			require.Equal(t, test.expectedArgs, args)
   679  		})
   680  	}
   681  }