Skip to content

Commit 0cb960d

Browse files
authored
Merge pull request #2550 from authzed/barakmich/refactor_objects
refactor: Query iterators now operate in Objects and ObjectRelations instead of strings
2 parents eec7ff4 + 0a92d36 commit 0cb960d

20 files changed

+285
-203
lines changed

internal/services/integrationtesting/query_plan_consistency_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ func runQueryPlanAssertions(t *testing.T, handle *queryPlanConsistencyHandle) {
119119

120120
qctx := handle.buildContext(t)
121121

122-
seq, err := qctx.Check(it, []string{rel.Resource.ObjectID}, rel.Subject.ObjectID)
122+
seq, err := qctx.Check(it, []query.Object{query.GetObject(rel.Resource)}, rel.Subject)
123123
require.NoError(err)
124124

125125
rels, err := query.CollectAll(seq)

pkg/query/arrow.go

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ func NewArrow(left, right Iterator) *Arrow {
2323
}
2424
}
2525

26-
func (a *Arrow) CheckImpl(ctx *Context, resourceIDs []string, subjectID string) (RelationSeq, error) {
26+
func (a *Arrow) CheckImpl(ctx *Context, resources []Object, subject ObjectAndRelation) (RelationSeq, error) {
2727
// TODO -- the ordering, directionality, batching, everything can depend on other statistics.
2828
//
2929
// There are three major strategies:
@@ -36,8 +36,8 @@ func (a *Arrow) CheckImpl(ctx *Context, resourceIDs []string, subjectID string)
3636
// don't restructure the tree, but can affect the best way to evaluate the tree, sometimes dynamically.
3737

3838
return func(yield func(Relation, error) bool) {
39-
for _, rid := range resourceIDs {
40-
subit, err := ctx.IterSubjects(a.left, rid)
39+
for _, resource := range resources {
40+
subit, err := a.left.IterSubjectsImpl(ctx, resource)
4141
if err != nil {
4242
yield(Relation{}, err)
4343
return
@@ -47,7 +47,8 @@ func (a *Arrow) CheckImpl(ctx *Context, resourceIDs []string, subjectID string)
4747
yield(Relation{}, err)
4848
return
4949
}
50-
checkit, err := ctx.Check(a.right, []string{rel.Subject.ObjectID}, subjectID)
50+
checkResources := []Object{GetObject(rel.Subject)}
51+
checkit, err := a.right.CheckImpl(ctx, checkResources, subject)
5152
if err != nil {
5253
yield(Relation{}, err)
5354
return
@@ -75,11 +76,11 @@ func (a *Arrow) CheckImpl(ctx *Context, resourceIDs []string, subjectID string)
7576
}, nil
7677
}
7778

78-
func (a *Arrow) IterSubjectsImpl(ctx *Context, resourceID string) (RelationSeq, error) {
79+
func (a *Arrow) IterSubjectsImpl(ctx *Context, resource Object) (RelationSeq, error) {
7980
return nil, spiceerrors.MustBugf("unimplemented")
8081
}
8182

82-
func (a *Arrow) IterResourcesImpl(ctx *Context, subjectID string) (RelationSeq, error) {
83+
func (a *Arrow) IterResourcesImpl(ctx *Context, subject ObjectAndRelation) (RelationSeq, error) {
8384
return nil, spiceerrors.MustBugf("unimplemented")
8485
}
8586

pkg/query/arrow_test.go

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ func TestArrowIterator(t *testing.T) {
3333

3434
// Test arrow operation: find resources where left side connects to right side
3535
// This looks for documents whose parent folder has viewers
36-
relSeq, err := ctx.Check(arrow, []string{"spec1", "spec2"}, "alice")
36+
relSeq, err := ctx.Check(arrow, NewObjects("document", "spec1", "spec2"), NewObject("user", "alice").WithEllipses())
3737
require.NoError(err)
3838

3939
rels, err := CollectAll(relSeq)
@@ -61,7 +61,7 @@ func TestArrowIterator(t *testing.T) {
6161
Executor: LocalExecutor{},
6262
}
6363

64-
relSeq, err := ctx.Check(arrow, []string{}, "alice")
64+
relSeq, err := ctx.Check(arrow, []Object{}, NewObject("user", "alice").WithEllipses())
6565
require.NoError(err)
6666

6767
rels, err := CollectAll(relSeq)
@@ -78,7 +78,7 @@ func TestArrowIterator(t *testing.T) {
7878
Executor: LocalExecutor{},
7979
}
8080

81-
relSeq, err := ctx.Check(arrow, []string{"nonexistent"}, "alice")
81+
relSeq, err := ctx.Check(arrow, NewObjects("document", "nonexistent"), NewObject("user", "alice").WithEllipses())
8282
require.NoError(err)
8383

8484
rels, err := CollectAll(relSeq)
@@ -96,7 +96,7 @@ func TestArrowIterator(t *testing.T) {
9696
Executor: LocalExecutor{},
9797
}
9898

99-
relSeq, err := ctx.Check(arrow, []string{"spec1"}, "nonexistent")
99+
relSeq, err := ctx.Check(arrow, NewObjects("document", "spec1"), NewObject("user", "nonexistent").WithEllipses())
100100
require.NoError(err)
101101

102102
rels, err := CollectAll(relSeq)
@@ -115,7 +115,7 @@ func TestArrowIterator(t *testing.T) {
115115
}
116116

117117
require.Panics(func() {
118-
_, _ = ctx.IterSubjects(arrow, "spec1")
118+
_, _ = ctx.IterSubjects(arrow, NewObject("document", "spec1"))
119119
})
120120
})
121121

@@ -129,7 +129,7 @@ func TestArrowIterator(t *testing.T) {
129129
}
130130

131131
require.Panics(func() {
132-
_, _ = ctx.IterResources(arrow, "alice")
132+
_, _ = ctx.IterResources(arrow, NewObject("user", "alice").WithEllipses())
133133
})
134134
})
135135
}
@@ -164,13 +164,13 @@ func TestArrowIteratorClone(t *testing.T) {
164164
subjectID := "alice"
165165

166166
// Collect results from original iterator
167-
originalSeq, err := ctx.Check(original, resourceIDs, subjectID)
167+
originalSeq, err := ctx.Check(original, NewObjects("document", resourceIDs[0]), NewObject("user", subjectID).WithEllipses())
168168
require.NoError(err)
169169
originalResults, err := CollectAll(originalSeq)
170170
require.NoError(err)
171171

172172
// Collect results from cloned iterator
173-
clonedSeq, err := ctx.Check(cloned, resourceIDs, subjectID)
173+
clonedSeq, err := ctx.Check(cloned, NewObjects("document", resourceIDs[0]), NewObject("user", subjectID).WithEllipses())
174174
require.NoError(err)
175175
clonedResults, err := CollectAll(clonedSeq)
176176
require.NoError(err)
@@ -213,7 +213,7 @@ func TestArrowIteratorMultipleResources(t *testing.T) {
213213
}
214214

215215
// Test with multiple resource IDs
216-
relSeq, err := ctx.Check(arrow, []string{"spec1", "spec2", "nonexistent"}, "alice")
216+
relSeq, err := ctx.Check(arrow, NewObjects("document", "spec1", "spec2", "nonexistent"), NewObject("user", "alice").WithEllipses())
217217
require.NoError(err)
218218

219219
rels, err := CollectAll(relSeq)

pkg/query/build_tree_test.go

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ func TestBuildTree(t *testing.T) {
3737
Revision: revision,
3838
}
3939

40-
relSeq, err := ctx.Check(it, []string{"specialplan"}, "multiroleguy")
40+
relSeq, err := ctx.Check(it, NewObjects("document", "specialplan"), NewObject("user", "multiroleguy").WithEllipses())
4141
require.NoError(err)
4242

4343
_, err = CollectAll(relSeq)
@@ -71,7 +71,7 @@ func TestBuildTreeMultipleRelations(t *testing.T) {
7171
Revision: revision,
7272
}
7373

74-
relSeq, err := ctx.Check(it, []string{"specialplan"}, "multiroleguy")
74+
relSeq, err := ctx.Check(it, NewObjects("document", "specialplan"), NewObject("user", "multiroleguy").WithEllipses())
7575
require.NoError(err)
7676

7777
rels, err := CollectAll(relSeq)
@@ -127,7 +127,7 @@ func TestBuildTreeSubRelations(t *testing.T) {
127127
}
128128

129129
// Just test that the iterator can be executed without error
130-
relSeq, err := ctx.Check(it, []string{"companyplan"}, "legal")
130+
relSeq, err := ctx.Check(it, NewObjects("document", "companyplan"), NewObject("user", "legal").WithEllipses())
131131
require.NoError(err)
132132

133133
_, err = CollectAll(relSeq)
@@ -214,7 +214,7 @@ func TestBuildTreeIntersectionOperation(t *testing.T) {
214214
}
215215

216216
// Test execution
217-
relSeq, err := ctx.Check(it, []string{"specialplan"}, "multiroleguy")
217+
relSeq, err := ctx.Check(it, NewObjects("document", "specialplan"), NewObject("user", "multiroleguy").WithEllipses())
218218
require.NoError(err)
219219

220220
_, err = CollectAll(relSeq)
@@ -301,7 +301,7 @@ func TestBuildTreeExclusionEdgeCases(t *testing.T) {
301301
require.IsType(&Exclusion{}, it)
302302

303303
// Test execution doesn't crash
304-
relSeq, err := ctx.Check(it, []string{"test_doc"}, "alice")
304+
relSeq, err := ctx.Check(it, []Object{NewObject("document", "test_doc")}, NewObject("user", "alice").WithEllipses())
305305
require.NoError(err)
306306
_, err = CollectAll(relSeq)
307307
require.NoError(err)
@@ -522,7 +522,7 @@ func TestBuildTreeSingleRelationOptimization(t *testing.T) {
522522
}
523523

524524
// Test execution
525-
relSeq, err := ctx.Check(it, []string{"companyplan"}, "legal")
525+
relSeq, err := ctx.Check(it, NewObjects("document", "companyplan"), NewObject("user", "legal").WithEllipses())
526526
require.NoError(err)
527527

528528
_, err = CollectAll(relSeq)
@@ -607,7 +607,7 @@ func TestBuildTreeSubrelationHandling(t *testing.T) {
607607
require.Contains(explainStr, "Union") // Should contain union for base relation + arrow
608608

609609
// Test execution doesn't crash
610-
relSeq, err := ctx.Check(it, []string{"test_doc"}, "alice")
610+
relSeq, err := ctx.Check(it, []Object{NewObject("document", "test_doc")}, NewObject("user", "alice").WithEllipses())
611611
require.NoError(err)
612612
_, err = CollectAll(relSeq)
613613
require.NoError(err)
@@ -695,7 +695,7 @@ func TestBuildTreeSubrelationHandling(t *testing.T) {
695695
require.Contains(explainStr, "Union") // Should contain union for different relation types
696696

697697
// Test execution doesn't crash
698-
relSeq, err := ctx.Check(it, []string{"test_doc"}, "alice")
698+
relSeq, err := ctx.Check(it, []Object{NewObject("document", "test_doc")}, NewObject("user", "alice").WithEllipses())
699699
require.NoError(err)
700700
_, err = CollectAll(relSeq)
701701
require.NoError(err)

pkg/query/context.go

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -19,43 +19,43 @@ type Context struct {
1919
}
2020

2121
// Check tests if, for the underlying set of relationships (which may be a full expression or a basic lookup, depending on the iterator)
22-
// any of the `resourceIDs` are connected to `subjectID`.
23-
// Returns the sequence of matching relations, if they exist, at most `len(resourceIDs)`.
24-
func (ctx *Context) Check(it Iterator, resourceIDs []string, subjectID string) (RelationSeq, error) {
22+
// any of the `resources` are connected to `subject`.
23+
// Returns the sequence of matching relations, if they exist, at most `len(resources)`.
24+
func (ctx *Context) Check(it Iterator, resources []Object, subject ObjectAndRelation) (RelationSeq, error) {
2525
if ctx.Executor == nil {
2626
return nil, spiceerrors.MustBugf("no executor has been set")
2727
}
28-
return ctx.Executor.Check(ctx, it, resourceIDs, subjectID)
28+
return ctx.Executor.Check(ctx, it, resources, subject)
2929
}
3030

31-
// IterSubjects returns a sequence of all the relations in this set that match the given resourceID.
32-
func (ctx *Context) IterSubjects(it Iterator, resourceID string) (RelationSeq, error) {
31+
// IterSubjects returns a sequence of all the relations in this set that match the given resource.
32+
func (ctx *Context) IterSubjects(it Iterator, resource Object) (RelationSeq, error) {
3333
if ctx.Executor == nil {
3434
return nil, spiceerrors.MustBugf("no executor has been set")
3535
}
36-
return ctx.Executor.IterSubjects(ctx, it, resourceID)
36+
return ctx.Executor.IterSubjects(ctx, it, resource)
3737
}
3838

39-
// IterResources returns a sequence of all the relations in this set that match the given subjectID.
40-
func (ctx *Context) IterResources(it Iterator, subjectID string) (RelationSeq, error) {
39+
// IterResources returns a sequence of all the relations in this set that match the given subject.
40+
func (ctx *Context) IterResources(it Iterator, subject ObjectAndRelation) (RelationSeq, error) {
4141
if ctx.Executor == nil {
4242
return nil, spiceerrors.MustBugf("no executor has been set")
4343
}
44-
return ctx.Executor.IterResources(ctx, it, subjectID)
44+
return ctx.Executor.IterResources(ctx, it, subject)
4545
}
4646

4747
// Executor as chooses how to proceed given an iterator -- perhaps in parallel, perhaps by RPC, etc -- and chooses how to process iteration from the subtree.
4848
// The correctness logic for the results that are generated are up to each iterator, and each iterator may use statistics to choose the best, yet still correct, logical evaluation strategy.
4949
// The Executor, meanwhile, makes that evaluation happen in the most convienent form, based on its implementation.
5050
type Executor interface {
5151
// Check tests if, for the underlying set of relationships (which may be a full expression or a basic lookup, depending on the iterator)
52-
// any of the `resourceIDs` are connected to `subjectID`.
53-
// Returns the sequence of matching relations, if they exist, at most `len(resourceIDs)`.
54-
Check(ctx *Context, it Iterator, resourceIDs []string, subjectID string) (RelationSeq, error)
52+
// any of the `resources` are connected to `subject`.
53+
// Returns the sequence of matching relations, if they exist, at most `len(resources)`.
54+
Check(ctx *Context, it Iterator, resources []Object, subject ObjectAndRelation) (RelationSeq, error)
5555

56-
// IterSubjects returns a sequence of all the relations in this set that match the given resourceID.
57-
IterSubjects(ctx *Context, it Iterator, resourceID string) (RelationSeq, error)
56+
// IterSubjects returns a sequence of all the relations in this set that match the given resource.
57+
IterSubjects(ctx *Context, it Iterator, resource Object) (RelationSeq, error)
5858

59-
// IterResources returns a sequence of all the relations in this set that match the given subjectID.
60-
IterResources(ctx *Context, it Iterator, subjectID string) (RelationSeq, error)
59+
// IterResources returns a sequence of all the relations in this set that match the given subject.
60+
IterResources(ctx *Context, it Iterator, subject ObjectAndRelation) (RelationSeq, error)
6161
}

pkg/query/datastore.go

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,15 +35,20 @@ func (r *RelationIterator) buildSubjectRelationFilter() datastore.SubjectRelatio
3535
return datastore.SubjectRelationFilter{}.WithNonEllipsisRelation(r.base.Subrelation)
3636
}
3737

38-
func (r *RelationIterator) CheckImpl(ctx *Context, resourceIDs []string, subjectID string) (RelationSeq, error) {
38+
func (r *RelationIterator) CheckImpl(ctx *Context, resources []Object, subject ObjectAndRelation) (RelationSeq, error) {
39+
resourceIDs := make([]string, len(resources))
40+
for i, res := range resources {
41+
resourceIDs[i] = res.ObjectID
42+
}
43+
3944
filter := datastore.RelationshipsFilter{
4045
OptionalResourceType: r.base.DefinitionName(),
4146
OptionalResourceIds: resourceIDs,
4247
OptionalResourceRelation: r.base.RelationName(),
4348
OptionalSubjectsSelectors: []datastore.SubjectsSelector{
4449
{
4550
OptionalSubjectType: r.base.Type,
46-
OptionalSubjectIds: []string{subjectID},
51+
OptionalSubjectIds: []string{subject.ObjectID},
4752
RelationFilter: r.buildSubjectRelationFilter(),
4853
},
4954
},
@@ -63,10 +68,10 @@ func (r *RelationIterator) CheckImpl(ctx *Context, resourceIDs []string, subject
6368
return RelationSeq(relIter), nil
6469
}
6570

66-
func (r *RelationIterator) IterSubjectsImpl(ctx *Context, resourceID string) (RelationSeq, error) {
71+
func (r *RelationIterator) IterSubjectsImpl(ctx *Context, resource Object) (RelationSeq, error) {
6772
filter := datastore.RelationshipsFilter{
6873
OptionalResourceType: r.base.DefinitionName(),
69-
OptionalResourceIds: []string{resourceID},
74+
OptionalResourceIds: []string{resource.ObjectID},
7075
OptionalResourceRelation: r.base.RelationName(),
7176
OptionalSubjectsSelectors: []datastore.SubjectsSelector{
7277
{
@@ -90,7 +95,7 @@ func (r *RelationIterator) IterSubjectsImpl(ctx *Context, resourceID string) (Re
9095
return RelationSeq(relIter), nil
9196
}
9297

93-
func (r *RelationIterator) IterResourcesImpl(ctx *Context, subjectID string) (RelationSeq, error) {
98+
func (r *RelationIterator) IterResourcesImpl(ctx *Context, subject ObjectAndRelation) (RelationSeq, error) {
9499
return nil, spiceerrors.MustBugf("unimplemented")
95100
}
96101

pkg/query/exclusion.go

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,9 @@ func NewExclusion(mainSet, excluded Iterator) *Exclusion {
2020
}
2121
}
2222

23-
func (e *Exclusion) CheckImpl(ctx *Context, resourceIDs []string, subjectID string) (RelationSeq, error) {
23+
func (e *Exclusion) CheckImpl(ctx *Context, resources []Object, subject ObjectAndRelation) (RelationSeq, error) {
2424
// Get all relations from the main set
25-
mainSeq, err := ctx.Check(e.mainSet, resourceIDs, subjectID)
25+
mainSeq, err := e.mainSet.CheckImpl(ctx, resources, subject)
2626
if err != nil {
2727
return nil, err
2828
}
@@ -40,7 +40,7 @@ func (e *Exclusion) CheckImpl(ctx *Context, resourceIDs []string, subjectID stri
4040
}
4141

4242
// Get all relations from the excluded set
43-
excludedSeq, err := ctx.Check(e.excluded, resourceIDs, subjectID)
43+
excludedSeq, err := e.excluded.CheckImpl(ctx, resources, subject)
4444
if err != nil {
4545
return nil, err
4646
}
@@ -57,10 +57,8 @@ func (e *Exclusion) CheckImpl(ctx *Context, resourceIDs []string, subjectID stri
5757

5858
// Check if this relation exists in the excluded set (only compare endpoints: resource and subject object types/IDs)
5959
for _, excludedRel := range excludedRels {
60-
if mainRel.Resource.ObjectType == excludedRel.Resource.ObjectType &&
61-
mainRel.Resource.ObjectID == excludedRel.Resource.ObjectID &&
62-
mainRel.Subject.ObjectType == excludedRel.Subject.ObjectType &&
63-
mainRel.Subject.ObjectID == excludedRel.Subject.ObjectID {
60+
if GetObject(mainRel.Resource).Equals(GetObject(excludedRel.Resource)) &&
61+
GetObject(mainRel.Subject).Equals(GetObject(excludedRel.Subject)) {
6462
found = true
6563
break
6664
}
@@ -76,11 +74,11 @@ func (e *Exclusion) CheckImpl(ctx *Context, resourceIDs []string, subjectID stri
7674
}, nil
7775
}
7876

79-
func (e *Exclusion) IterSubjectsImpl(ctx *Context, resourceID string) (RelationSeq, error) {
77+
func (e *Exclusion) IterSubjectsImpl(ctx *Context, resource Object) (RelationSeq, error) {
8078
return nil, spiceerrors.MustBugf("unimplemented")
8179
}
8280

83-
func (e *Exclusion) IterResourcesImpl(ctx *Context, subjectID string) (RelationSeq, error) {
81+
func (e *Exclusion) IterResourcesImpl(ctx *Context, subject ObjectAndRelation) (RelationSeq, error) {
8482
return nil, spiceerrors.MustBugf("unimplemented")
8583
}
8684

0 commit comments

Comments
 (0)