cayleygraph/cayley · error
iterator already used in query
Error message
iterator already used in query
What it means
path/iteratorShape wraps a shape.Shape so its BuildIterator can only be called once: after the first call it nils out the stored shape and sets sent=true. A second call returns iterator.NewError with 'iterator already used in query'. This guards against reusing a single iterator instance across queries, which would corrupt iteration state since shapes are consumed when built.
Source
Thrown at query/path/morphism_apply_functions.go:252
func savePredicatesMorphism(isIn bool, tag string) morphism {
return morphism{
Reversal: func(ctx *pathContext) (morphism, *pathContext) {
return savePredicatesMorphism(isIn, tag), ctx
},
Apply: func(in shape.Shape, ctx *pathContext) (shape.Shape, *pathContext) {
return shape.SavePredicates(in, isIn, tag), ctx
},
}
}
type iteratorShape struct {
it iterator.Shape
sent bool
}
func (s *iteratorShape) BuildIterator(qs graph.QuadStore) iterator.Shape {
if s.sent {
return iterator.NewError(fmt.Errorf("iterator already used in query"))
}
it := s.it
s.it, s.sent = nil, true
return it
}
func (s *iteratorShape) Optimize(ctx context.Context, r shape.Optimizer) (shape.Shape, bool) {
return s, false
}
// iteratorMorphism simply tacks the input iterator onto the chain.
func iteratorMorphism(it iterator.Shape) morphism {
return morphism{
Reversal: func(ctx *pathContext) (morphism, *pathContext) { return iteratorMorphism(it), ctx },
Apply: func(in shape.Shape, ctx *pathContext) (shape.Shape, *pathContext) {
return join(&iteratorShape{it: it}, in), ctx
},
}
}View on GitHub (pinned to 81dcd7d73e)
Solutions
- Rebuild the path/query (call the step's BuildPath / query BuildIteratorTree again) for each execution.
- Keep the query *specification* (steps) around and construct a fresh iterator per run, not the built iterator.
- Wrap query execution in a helper that always creates a new iterator shape.
- Check for accidental double invocation of BuildIterator on the same object in your code path.
Example fix
// before it := p.BuildIterator(qs) run(it) run(p.BuildIterator(qs)) // second call: error iterator // after run(p.BuildIterator(qs)) run(p.BuildIterator(qs)) // path rebuilds a fresh shape each call
Defensive patterns
Strategy: fallback
Validate before calling
type onceIterator struct{ built atomic.Bool }
func guardReuse(build func() iterator.Shape) iterator.Shape {
if !buildOnce.built.CompareAndSwap(false, true) {
return build() // rebuild instead of reusing the consumed shape
}
return build()
} Type guard
func isConsumed(s *path.IteratorShape) bool { return s == nil || s.Sent() } Try / catch
it := p.BuildIterator(qs)
if errIt, ok := it.(*iterator.Error); ok {
// e.g. 'iterator already used in query' — rebuild the query
it = p.BuildIterator(qs)
} Prevention
- Never cache built iterators; cache the path/query specification instead
- Call BuildIterator freshly for every execution of a query
- In servers, rebuild the query per request rather than storing a built iterator
- Watch for iterator.Error shapes surfaced from BuildIterator and treat them as rebuild signals
When it happens
Trigger: Calling BuildIterator (or BuildIteratorTree) twice on the same path.Path / query object without rebuilding it, e.g. re-running a stored query object.
Common situations: Caching a *path.Path and executing it multiple times; reusing a query across requests in a server; running the same query variable twice in batch jobs.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- ErrEmptyPath
- must execute a IteratorStep or PathStep
- Building an iterator from a morphism. Bind a QuadStore with
- cannot count iterator without a valid context
- node tokens not valid
AI-assisted analysis of cayleygraph/cayley@81dcd7d73e (2026-09-06).
Data as JSON: /api/errors/3a727c634104b7ac.
Report an issue: GitHub.