Skip to content

Commit c33320f

Browse files
authored
chore: rename all iterators to a consistent pattern (#2896)
1 parent cd8f1ce commit c33320f

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+838
-734
lines changed

pkg/query/README.md

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
# Query Package
2+
3+
The `query` package implements SpiceDB's query plan system for evaluating permissions and relationships. It provides a tree-based iterator architecture for efficient graph traversal and permission checking.
4+
5+
## Overview
6+
7+
Query plans are built from schema definitions and executed to answer three fundamental questions:
8+
9+
1. **Check**: Does a specific subject have access to a resource?
10+
2. **IterSubjects**: Which subjects have access to a given resource?
11+
3. **IterResources**: Which resources can a given subject access?
12+
13+
Each iterator in the tree implements these three methods, allowing complex permission queries to be evaluated by composing simple operations.
14+
15+
## Iterator Types
16+
17+
Iterators are organized into several categories based on their role in the query plan:
18+
19+
| Iterator | Description |
20+
|----------|-------------|
21+
| **LEAF ITERATORS** | **Data source iterators with no subiterators** |
22+
| DatastoreIterator | Queries the datastore for stored relationships. The fundamental data source for all queries. |
23+
| FixedIterator | Contains pre-computed paths. Used in testing and as frontier in recursive queries. |
24+
| SelfIterator | Produces reflexive relations (object→object). Used for `self` keyword checks. |
25+
| RecursiveSentinelIterator | Placeholder marking recursion points during tree construction. Replaced at runtime. |
26+
| | |
27+
| **UNARY ITERATORS** | **Wrap a single child iterator** |
28+
| AliasIterator | Rewrites the relation field of paths. Maps computed permissions to their defining relation. |
29+
| | |
30+
| **BINARY ITERATORS** | **Combine two child iterators** |
31+
| ArrowIterator | Graph walk operator (``). Chains two iterators for path composition. |
32+
| IntersectionArrowIterator | Conditional intersection. Implements `all()` semantics - ALL left subjects must satisfy right. |
33+
| ExclusionIterator | Set difference (`-`). Removes excluded paths from main set. |
34+
| | |
35+
| **N-ARY ITERATORS** | **Combine multiple child iterators** |
36+
| UnionIterator | Logical OR (`\|`). Concatenates results from all children with deduplication. |
37+
| IntersectionIterator | Logical AND (`&`). Intersects results from all children. |
38+
| | |
39+
| **FILTER ITERATORS** | **Apply filtering conditions to results** |
40+
| CaveatIterator | Evaluates caveat conditions on paths. Filters results based on runtime context. |
41+
| | |
42+
| **CONTROL FLOW ITERATORS** | **Manage execution flow and recursion** |
43+
| RecursiveIterator | Manages recursive schema definitions with and represents the transitive closure operation. |
44+
45+
## Key Concepts
46+
47+
### Iterator Interface
48+
49+
All iterators implement the `Iterator` interface with these core methods:
50+
51+
```go
52+
type Iterator interface {
53+
// Evaluation methods
54+
CheckImpl(ctx *Context, resources []Object, subject ObjectAndRelation) (PathSeq, error)
55+
IterSubjectsImpl(ctx *Context, resource Object, filterSubjectType ObjectType) (PathSeq, error)
56+
IterResourcesImpl(ctx *Context, subject ObjectAndRelation, filterResourceType ObjectType) (PathSeq, error)
57+
58+
// Tree navigation
59+
Clone() Iterator
60+
Subiterators() []Iterator
61+
ReplaceSubiterators(newSubs []Iterator) (Iterator, error)
62+
63+
// Metadata
64+
ID() string
65+
Explain() Explain
66+
ResourceType() ([]ObjectType, error)
67+
SubjectTypes() ([]ObjectType, error)
68+
}
69+
```
70+
71+
### Path Sequences
72+
73+
Results are returned as `PathSeq` (Go 1.23 iterators - `iter.Seq2[Path, error]`), allowing lazy evaluation and streaming of results without materializing entire result sets in memory.
74+
75+
### Context
76+
77+
The `Context` type carries execution state including:
78+
79+
- Datastore reader
80+
- Optional trace logger for debugging
81+
- Execution depth tracking
82+
- Analysis/statistics collection
83+
84+
## Usage Example
85+
86+
```go
87+
// Build a query plan from schema
88+
it, err := BuildIteratorFromSchema(schema, "document", "viewer")
89+
90+
// Create execution context
91+
ctx := NewContext(datastoreReader)
92+
93+
// Execute a check query
94+
resources := []Object{NewObject("document", "doc1")}
95+
subject := NewObject("user", "alice").WithEllipses()
96+
97+
pathSeq, err := ctx.Check(it, resources, subject)
98+
for path, err := range pathSeq {
99+
if err != nil {
100+
return err
101+
}
102+
// Process path...
103+
}
104+
```

pkg/query/alias.go

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,20 @@ import (
77
"github.com/authzed/spicedb/pkg/datastore/options"
88
)
99

10-
// Alias is an iterator that rewrites the Resource's Relation field of all paths
10+
// AliasIterator is an iterator that rewrites the Resource's Relation field of all paths
1111
// streamed from the sub-iterator to a specified alias relation.
12-
type Alias struct {
12+
type AliasIterator struct {
1313
id string
1414
relation string
1515
subIt Iterator
1616
}
1717

18-
var _ Iterator = &Alias{}
18+
var _ Iterator = &AliasIterator{}
1919

20-
// NewAlias creates a new Alias iterator that rewrites paths from the sub-iterator
20+
// NewAliasIterator creates a new Alias iterator that rewrites paths from the sub-iterator
2121
// to use the specified relation name.
22-
func NewAlias(relation string, subIt Iterator) *Alias {
23-
return &Alias{
22+
func NewAliasIterator(relation string, subIt Iterator) *AliasIterator {
23+
return &AliasIterator{
2424
id: uuid.NewString(),
2525
relation: relation,
2626
subIt: subIt,
@@ -29,7 +29,7 @@ func NewAlias(relation string, subIt Iterator) *Alias {
2929

3030
// maybePrependSelfEdge checks if a self-edge should be added and combines it with the given sequence.
3131
// 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 {
32+
func (a *AliasIterator) maybePrependSelfEdge(resource Object, subSeq PathSeq, shouldAddSelfEdge bool) PathSeq {
3333
if !shouldAddSelfEdge {
3434
// No self-edge, just rewrite paths from sub-iterator
3535
return func(yield func(Path, error) bool) {
@@ -84,7 +84,7 @@ func (a *Alias) maybePrependSelfEdge(resource Object, subSeq PathSeq, shouldAddS
8484
return DeduplicatePathSeq(combined)
8585
}
8686

87-
func (a *Alias) CheckImpl(ctx *Context, resources []Object, subject ObjectAndRelation) (PathSeq, error) {
87+
func (a *AliasIterator) CheckImpl(ctx *Context, resources []Object, subject ObjectAndRelation) (PathSeq, error) {
8888
// Get relations from sub-iterator
8989
subSeq, err := ctx.Check(a.subIt, resources, subject)
9090
if err != nil {
@@ -105,7 +105,7 @@ func (a *Alias) CheckImpl(ctx *Context, resources []Object, subject ObjectAndRel
105105
return DeduplicatePathSeq(a.maybePrependSelfEdge(Object{}, subSeq, false)), nil
106106
}
107107

108-
func (a *Alias) IterSubjectsImpl(ctx *Context, resource Object, filterSubjectType ObjectType) (PathSeq, error) {
108+
func (a *AliasIterator) IterSubjectsImpl(ctx *Context, resource Object, filterSubjectType ObjectType) (PathSeq, error) {
109109
subSeq, err := ctx.IterSubjects(a.subIt, resource, filterSubjectType)
110110
if err != nil {
111111
return nil, err
@@ -125,7 +125,7 @@ func (a *Alias) IterSubjectsImpl(ctx *Context, resource Object, filterSubjectTyp
125125
// This matches the dispatcher's identity check behavior: if resource#relation appears as
126126
// a subject anywhere in the datastore (expired or not), and the filter allows it, we
127127
// include a self-edge in the results.
128-
func (a *Alias) shouldIncludeSelfEdge(ctx *Context, resource Object, filterSubjectType ObjectType) bool {
128+
func (a *AliasIterator) shouldIncludeSelfEdge(ctx *Context, resource Object, filterSubjectType ObjectType) bool {
129129
// First check: does the filter allow this resource type as a subject?
130130
typeMatches := filterSubjectType.Type == "" || filterSubjectType.Type == resource.ObjectType
131131
relationMatches := filterSubjectType.Subrelation == "" || filterSubjectType.Subrelation == a.relation
@@ -146,7 +146,7 @@ func (a *Alias) shouldIncludeSelfEdge(ctx *Context, resource Object, filterSubje
146146

147147
// resourceExistsAsSubject queries the datastore to check if the given resource appears
148148
// as a subject in any relationship, including expired relationships.
149-
func (a *Alias) resourceExistsAsSubject(ctx *Context, resource Object) (bool, error) {
149+
func (a *AliasIterator) resourceExistsAsSubject(ctx *Context, resource Object) (bool, error) {
150150
filter := datastore.RelationshipsFilter{
151151
OptionalSubjectsSelectors: []datastore.SubjectsSelector{{
152152
OptionalSubjectType: resource.ObjectType,
@@ -174,7 +174,7 @@ func (a *Alias) resourceExistsAsSubject(ctx *Context, resource Object) (bool, er
174174
return false, nil
175175
}
176176

177-
func (a *Alias) IterResourcesImpl(ctx *Context, subject ObjectAndRelation, filterResourceType ObjectType) (PathSeq, error) {
177+
func (a *AliasIterator) IterResourcesImpl(ctx *Context, subject ObjectAndRelation, filterResourceType ObjectType) (PathSeq, error) {
178178
subSeq, err := ctx.IterResources(a.subIt, subject, filterResourceType)
179179
if err != nil {
180180
return nil, err
@@ -210,38 +210,38 @@ func (a *Alias) IterResourcesImpl(ctx *Context, subject ObjectAndRelation, filte
210210
return a.maybePrependSelfEdge(GetObject(subject), subSeq, shouldAddSelfEdge), nil
211211
}
212212

213-
func (a *Alias) Clone() Iterator {
214-
return &Alias{
213+
func (a *AliasIterator) Clone() Iterator {
214+
return &AliasIterator{
215215
id: uuid.NewString(),
216216
relation: a.relation,
217217
subIt: a.subIt.Clone(),
218218
}
219219
}
220220

221-
func (a *Alias) Explain() Explain {
221+
func (a *AliasIterator) Explain() Explain {
222222
return Explain{
223223
Name: "Alias",
224224
Info: "Alias(" + a.relation + ")",
225225
SubExplain: []Explain{a.subIt.Explain()},
226226
}
227227
}
228228

229-
func (a *Alias) Subiterators() []Iterator {
229+
func (a *AliasIterator) Subiterators() []Iterator {
230230
return []Iterator{a.subIt}
231231
}
232232

233-
func (a *Alias) ReplaceSubiterators(newSubs []Iterator) (Iterator, error) {
234-
return &Alias{id: uuid.NewString(), relation: a.relation, subIt: newSubs[0]}, nil
233+
func (a *AliasIterator) ReplaceSubiterators(newSubs []Iterator) (Iterator, error) {
234+
return &AliasIterator{id: uuid.NewString(), relation: a.relation, subIt: newSubs[0]}, nil
235235
}
236236

237-
func (a *Alias) ID() string {
237+
func (a *AliasIterator) ID() string {
238238
return a.id
239239
}
240240

241-
func (a *Alias) ResourceType() ([]ObjectType, error) {
241+
func (a *AliasIterator) ResourceType() ([]ObjectType, error) {
242242
return a.subIt.ResourceType()
243243
}
244244

245-
func (a *Alias) SubjectTypes() ([]ObjectType, error) {
245+
func (a *AliasIterator) SubjectTypes() ([]ObjectType, error) {
246246
return a.subIt.SubjectTypes()
247247
}

0 commit comments

Comments
 (0)