@@ -2,6 +2,9 @@ package query
22
33import (
44 "github.com/google/uuid"
5+
6+ "github.com/authzed/spicedb/pkg/datastore"
7+ "github.com/authzed/spicedb/pkg/datastore/options"
58)
69
710// Alias is an iterator that rewrites the Resource's Relation field of all paths
@@ -24,60 +27,46 @@ func NewAlias(relation string, subIt Iterator) *Alias {
2427 }
2528}
2629
27- func (a * Alias ) CheckImpl (ctx * Context , resources []Object , subject ObjectAndRelation ) (PathSeq , error ) {
28- // First, check for self-edge: if the object with internal relation matches the subject
29- for _ , resource := range resources {
30- resourceWithAlias := resource .WithRelation (a .relation )
31- if resourceWithAlias .ObjectID == subject .ObjectID &&
32- resourceWithAlias .ObjectType == subject .ObjectType &&
33- resourceWithAlias .Relation == subject .Relation {
34- // Return the self-edge path first
35- selfPath := Path {
36- Resource : GetObject (resourceWithAlias ),
37- Relation : resourceWithAlias .Relation ,
38- Subject : subject ,
39- Metadata : make (map [string ]any ),
40- }
41-
42- // Also get relations from sub-iterator
43- subSeq , err := ctx .Check (a .subIt , resources , subject )
44- if err != nil {
45- return nil , err
46- }
47-
48- // Create combined sequence with self-edge and rewritten paths
49- combined := func (yield func (Path , error ) bool ) {
50- // Yield the self-edge first
51- if ! yield (selfPath , nil ) {
30+ // maybePrependSelfEdge checks if a self-edge should be added and combines it with the given sequence.
31+ // A self-edge is added when the resource with the alias relation matches the subject.
32+ func (a * Alias ) maybePrependSelfEdge (resource Object , subSeq PathSeq , shouldAddSelfEdge bool ) PathSeq {
33+ if ! shouldAddSelfEdge {
34+ // No self-edge, just rewrite paths from sub-iterator
35+ return func (yield func (Path , error ) bool ) {
36+ for path , err := range subSeq {
37+ if err != nil {
38+ yield (Path {}, err )
5239 return
5340 }
5441
55- // Then yield rewritten paths from sub-iterator
56- for path , err := range subSeq {
57- if err != nil {
58- yield (Path {}, err )
59- return
60- }
61-
62- path .Relation = a .relation
63- if ! yield (path , nil ) {
64- return
65- }
42+ path .Relation = a .relation
43+ if ! yield (path , nil ) {
44+ return
6645 }
6746 }
68-
69- // Wrap with deduplication to handle duplicate paths after rewriting
70- return DeduplicatePathSeq (combined ), nil
7147 }
7248 }
7349
74- // No self-edge detected, just rewrite paths from sub-iterator
75- subSeq , err := ctx .Check (a .subIt , resources , subject )
76- if err != nil {
77- return nil , err
50+ // Create a self-edge path
51+ selfPath := Path {
52+ Resource : resource ,
53+ Relation : a .relation ,
54+ Subject : ObjectAndRelation {
55+ ObjectType : resource .ObjectType ,
56+ ObjectID : resource .ObjectID ,
57+ Relation : a .relation ,
58+ },
59+ Metadata : make (map [string ]any ),
7860 }
7961
80- rewritten := func (yield func (Path , error ) bool ) {
62+ // Combine self-edge with paths from sub-iterator
63+ combined := func (yield func (Path , error ) bool ) {
64+ // Yield the self-edge first
65+ if ! yield (selfPath , nil ) {
66+ return
67+ }
68+
69+ // Then yield rewritten paths from sub-iterator
8170 for path , err := range subSeq {
8271 if err != nil {
8372 yield (Path {}, err )
@@ -91,84 +80,111 @@ func (a *Alias) CheckImpl(ctx *Context, resources []Object, subject ObjectAndRel
9180 }
9281 }
9382
94- // Wrap with deduplication to handle duplicate paths after rewriting
95- return DeduplicatePathSeq (rewritten ), nil
83+ // Wrap with deduplication to handle duplicate paths
84+ return DeduplicatePathSeq (combined )
9685}
9786
98- func (a * Alias ) IterSubjectsImpl (ctx * Context , resource Object , filterSubjectType ObjectType ) (PathSeq , error ) {
99- subSeq , err := ctx .IterSubjects (a .subIt , resource , filterSubjectType )
87+ func (a * Alias ) CheckImpl (ctx * Context , resources []Object , subject ObjectAndRelation ) (PathSeq , error ) {
88+ // Get relations from sub-iterator
89+ subSeq , err := ctx .Check (a .subIt , resources , subject )
10090 if err != nil {
10191 return nil , err
10292 }
10393
104- return func (yield func (Path , error ) bool ) {
105- for path , err := range subSeq {
106- if err != nil {
107- yield (Path {}, err )
108- return
109- }
110-
111- path .Relation = a .relation
112- if ! yield (path , nil ) {
113- return
114- }
94+ // Check for self-edge: if any resource with the alias relation matches the subject
95+ for _ , resource := range resources {
96+ resourceWithAlias := resource .WithRelation (a .relation )
97+ if resourceWithAlias .ObjectID == subject .ObjectID &&
98+ resourceWithAlias .ObjectType == subject .ObjectType &&
99+ resourceWithAlias .Relation == subject .Relation {
100+ return a .maybePrependSelfEdge (GetObject (resourceWithAlias ), subSeq , true ), nil
115101 }
116- }, nil
102+ }
103+
104+ // No self-edge detected, just rewrite paths from sub-iterator
105+ return DeduplicatePathSeq (a .maybePrependSelfEdge (Object {}, subSeq , false )), nil
117106}
118107
119- func (a * Alias ) IterResourcesImpl (ctx * Context , subject ObjectAndRelation , filterResourceType ObjectType ) (PathSeq , error ) {
120- subSeq , err := ctx .IterResources (a .subIt , subject , filterResourceType )
108+ func (a * Alias ) IterSubjectsImpl (ctx * Context , resource Object , filterSubjectType ObjectType ) (PathSeq , error ) {
109+ subSeq , err := ctx .IterSubjects (a .subIt , resource , filterSubjectType )
121110 if err != nil {
122111 return nil , err
123112 }
124113
125- // If the relation on the alias iterator is the same
126- // as the subject relation on the subject, we have
127- // a self edge and want to yield it accordingly.
128- if a .relation == subject .Relation {
129- selfPath := Path {
130- Resource : GetObject (subject ),
131- Relation : a .relation ,
132- Subject : subject ,
133- }
114+ // Check if we should add a self-edge based on identity semantics.
115+ // The dispatcher Check includes an identity check (see filterForFoundMemberResource
116+ // in internal/graph/check.go): if the resource (with relation) matches the subject
117+ // exactly, it returns MEMBER. This only applies if the resource actually appears
118+ // as a subject in the data and the filter allows it.
119+ shouldAddSelfEdge := a .shouldIncludeSelfEdge (ctx , resource , filterSubjectType )
134120
135- combined := func (yield func (Path , error ) bool ) {
136- // Yield the self path first
137- if ! yield (selfPath , nil ) {
138- return
139- }
121+ return a .maybePrependSelfEdge (resource , subSeq , shouldAddSelfEdge ), nil
122+ }
140123
141- for subPath , err := range subSeq {
142- if err != nil {
143- yield (Path {}, err )
144- return
145- }
124+ // shouldIncludeSelfEdge checks if a self-edge should be included for the given resource.
125+ // This matches the dispatcher's identity check behavior: if resource#relation appears as
126+ // a subject anywhere in the datastore (expired or not), and the filter allows it, we
127+ // include a self-edge in the results.
128+ func (a * Alias ) shouldIncludeSelfEdge (ctx * Context , resource Object , filterSubjectType ObjectType ) bool {
129+ // First check: does the filter allow this resource type as a subject?
130+ typeMatches := filterSubjectType .Type == "" || filterSubjectType .Type == resource .ObjectType
131+ relationMatches := filterSubjectType .Subrelation == "" || filterSubjectType .Subrelation == a .relation
132+ if ! typeMatches || ! relationMatches || ctx .Reader == nil {
133+ return false
134+ }
146135
147- // Then yield remaining paths with the rewrite
148- subPath .Relation = a .relation
149- if ! yield (subPath , nil ) {
150- return
151- }
152- }
153- }
136+ // Second check: does the resource actually appear as a subject in the data?
137+ // We check for ANY relationships (expired or not) because the dispatcher's
138+ // identity check applies regardless of expiration.
139+ exists , err := a .resourceExistsAsSubject (ctx , resource )
140+ if err != nil {
141+ // On error, conservatively return false rather than failing the entire operation
142+ return false
143+ }
144+ return exists
145+ }
154146
155- return DeduplicatePathSeq (combined ), nil
147+ // resourceExistsAsSubject queries the datastore to check if the given resource appears
148+ // as a subject in any relationship, including expired relationships.
149+ func (a * Alias ) resourceExistsAsSubject (ctx * Context , resource Object ) (bool , error ) {
150+ filter := datastore.RelationshipsFilter {
151+ OptionalSubjectsSelectors : []datastore.SubjectsSelector {{
152+ OptionalSubjectType : resource .ObjectType ,
153+ OptionalSubjectIds : []string {resource .ObjectID },
154+ RelationFilter : datastore.SubjectRelationFilter {}.WithNonEllipsisRelation (a .relation ),
155+ }},
156+ OptionalExpirationOption : datastore .ExpirationFilterOptionNone ,
156157 }
157158
158- // else we don't have a subpath and do the normal logic
159- return func (yield func (Path , error ) bool ) {
160- for path , err := range subSeq {
161- if err != nil {
162- yield (Path {}, err )
163- return
164- }
159+ iter , err := ctx .Reader .QueryRelationships (ctx , filter ,
160+ options .WithLimit (options .LimitOne ),
161+ options .WithSkipExpiration (true )) // Include expired relationships
162+ if err != nil {
163+ return false , err
164+ }
165165
166- path . Relation = a . relation
167- if ! yield ( path , nil ) {
168- return
169- }
166+ // Check if any relationship exists
167+ for _ , err := range iter {
168+ if err != nil {
169+ return false , err
170170 }
171- }, nil
171+ return true , nil
172+ }
173+
174+ return false , nil
175+ }
176+
177+ func (a * Alias ) IterResourcesImpl (ctx * Context , subject ObjectAndRelation , filterResourceType ObjectType ) (PathSeq , error ) {
178+ subSeq , err := ctx .IterResources (a .subIt , subject , filterResourceType )
179+ if err != nil {
180+ return nil , err
181+ }
182+
183+ // If the relation on the alias iterator is the same as the subject relation,
184+ // we have a self-edge and should yield it
185+ shouldAddSelfEdge := a .relation == subject .Relation
186+
187+ return a .maybePrependSelfEdge (GetObject (subject ), subSeq , shouldAddSelfEdge ), nil
172188}
173189
174190func (a * Alias ) Clone () Iterator {
0 commit comments