cayleygraph/cayley · error

cannot count iterator without a valid context

Error message

cannot count iterator without a valid context

What it means

QuadIteratorSize counts how many quads reference a given node value. The GAED datastore stores the node-entry lookup in qs.context (the datastore client/transaction context), so if the store was constructed without a valid context there is no datastore handle to issue the Get against, and the method fails with this error instead of panicking on a nil context.

Source

Thrown at graph/gaedatastore/quadstore.go:539

	}
	return graph.Stats{
		Nodes: refs.Size{
			Value: m.NodeCount,
			Exact: true,
		},
		Quads: refs.Size{
			Value: m.QuadCount,
			Exact: true,
		},
	}, nil
}

func (qs *QuadStore) QuadIteratorSize(ctx context.Context, d quad.Direction, val graph.Ref) (refs.Size, error) {
	t, ok := val.(*Token)
	if !ok || t.Kind != nodeKind {
		return refs.Size{Value: 0, Exact: true}, nil
	} else if qs.context == nil {
		return refs.Size{}, errors.New("cannot count iterator without a valid context")
	}
	key := qs.createKeyFromToken(t)
	n := new(NodeEntry)
	err := datastore.Get(qs.context, key, n)
	if err != nil && err != datastore.ErrNoSuchEntity {
		return refs.Size{}, err
	}
	return refs.Size{Value: n.Size, Exact: true}, nil
}

func (qs *QuadStore) Close() error {
	qs.context = nil
	return nil
}

func (qs *QuadStore) QuadDirection(val graph.Ref, dir quad.Direction) (graph.Ref, error) {
	t, ok := val.(*Token)
	if !ok {

View on GitHub (pinned to 81dcd7d73e)

Solutions

  1. Initialize the QuadStore through its normal open/new path so qs.context is set to a valid datastore client/transaction before calling QuadIteratorSize.
  2. If constructing the store manually, assign the App Engine datastore context to qs.context prior to any query.
  3. Guard the call: check that the store was opened successfully (no nil context) before counting iterator sizes.

Example fix

// before
qs := &gaedatastore.QuadStore{}
size, err := qs.QuadIteratorSize(ctx, quad.Any, nodeToken)
// after
qs, err := gaedatastore.New(ctx, opts) // ensures internal context is set
if err != nil { return err }
size, err := qs.QuadIteratorSize(ctx, quad.Any, nodeToken)
Defensive patterns

Strategy: try-catch

Validate before calling

if val, ok := ref.(*gaedatastore.Token); !ok || val.Kind != gaedatastore.NodeKind || qs.Context == nil {
    // skip size query
}

Type guard

func isCountableNodeToken(qs *gaedatastore.QuadStore, ref graph.Ref) bool {
    t, ok := ref.(*gaedatastore.Token)
    return ok && t.Kind == gaedatastore.NodeKind && qs.HasContext()
}

Try / catch

size, err := qs.QuadIteratorSize(ctx, d, val)
if err != nil {
    if strings.Contains(err.Error(), "valid context") {
        return refs.Size{}, fmt.Errorf("store not opened with datastore context: %w", err)
    }
    return refs.Size{}, err
}

Prevention

When it happens

Trigger: Calling QuadIteratorSize(ctx, d, val) where val is a *Token with Kind == nodeKind (i.e. a node value) while the QuadStore's qs.context field is nil. The error is only reachable for node tokens; non-node tokens return a zero size early.

Common situations: Embedding or manually constructing gaedatastore.QuadStore (e.g. in tests or custom deployments) without running the normal open path that assigns the datastore context; running on App Engine with a context that was never injected into the store.

Understand the failure class

Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.

Related errors


AI-assisted analysis of cayleygraph/cayley@81dcd7d73e (2026-09-06). Data as JSON: /api/errors/a7a13faa9b260673. Report an issue: GitHub.