{"record":{"id":"3a727c634104b7ac","repo":"cayleygraph/cayley","slug":"iterator-already-used-in-query","errorCode":null,"errorMessage":"iterator already used in query","messagePattern":"iterator already used in query","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"query/path/morphism_apply_functions.go","lineNumber":252,"sourceCode":"func savePredicatesMorphism(isIn bool, tag string) morphism {\n\treturn morphism{\n\t\tReversal: func(ctx *pathContext) (morphism, *pathContext) {\n\t\t\treturn savePredicatesMorphism(isIn, tag), ctx\n\t\t},\n\t\tApply: func(in shape.Shape, ctx *pathContext) (shape.Shape, *pathContext) {\n\t\t\treturn shape.SavePredicates(in, isIn, tag), ctx\n\t\t},\n\t}\n}\n\ntype iteratorShape struct {\n\tit   iterator.Shape\n\tsent bool\n}\n\nfunc (s *iteratorShape) BuildIterator(qs graph.QuadStore) iterator.Shape {\n\tif s.sent {\n\t\treturn iterator.NewError(fmt.Errorf(\"iterator already used in query\"))\n\t}\n\tit := s.it\n\ts.it, s.sent = nil, true\n\treturn it\n}\nfunc (s *iteratorShape) Optimize(ctx context.Context, r shape.Optimizer) (shape.Shape, bool) {\n\treturn s, false\n}\n\n// iteratorMorphism simply tacks the input iterator onto the chain.\nfunc iteratorMorphism(it iterator.Shape) morphism {\n\treturn morphism{\n\t\tReversal: func(ctx *pathContext) (morphism, *pathContext) { return iteratorMorphism(it), ctx },\n\t\tApply: func(in shape.Shape, ctx *pathContext) (shape.Shape, *pathContext) {\n\t\t\treturn join(&iteratorShape{it: it}, in), ctx\n\t\t},\n\t}\n}","sourceCodeStart":234,"sourceCodeEnd":270,"githubUrl":"https://github.com/cayleygraph/cayley/blob/81dcd7d73e45136bc0d01802a8ba4685d8a533eb/query/path/morphism_apply_functions.go#L234-L270","documentation":"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.","triggerScenarios":"Calling BuildIterator (or BuildIteratorTree) twice on the same path.Path / query object without rebuilding it, e.g. re-running a stored query object.","commonSituations":"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.","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."],"exampleFix":"// before\nit := p.BuildIterator(qs)\nrun(it)\nrun(p.BuildIterator(qs)) // second call: error iterator\n// after\nrun(p.BuildIterator(qs))\nrun(p.BuildIterator(qs)) // path rebuilds a fresh shape each call","handlingStrategy":"fallback","validationCode":"type onceIterator struct{ built atomic.Bool }\nfunc guardReuse(build func() iterator.Shape) iterator.Shape {\n  if !buildOnce.built.CompareAndSwap(false, true) {\n    return build() // rebuild instead of reusing the consumed shape\n  }\n  return build()\n}","typeGuard":"func isConsumed(s *path.IteratorShape) bool { return s == nil || s.Sent() }","tryCatchPattern":"it := p.BuildIterator(qs)\nif errIt, ok := it.(*iterator.Error); ok {\n  // e.g. 'iterator already used in query' — rebuild the query\n  it = p.BuildIterator(qs)\n}","preventionTips":["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"],"tags":["path","iterator","go","query-reuse"],"backgroundTag":"invalid-state-transition","analyzedSha":"81dcd7d73e45136bc0d01802a8ba4685d8a533eb","analyzedAt":"2026-09-06T06:14:12.358Z","contentChangedAt":"2026-09-06T06:14:12.358Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}