Dgraph: Client.Mutate never commits, so a mutation without CommitNow is silently dropped
Summary
Client.Mutate opens a transaction, mutates, and returns — it never calls Commit or Discard. Whether the write lands is decided entirely by a field the caller sets on the *api.Mutation. If CommitNow is not set, the mutation is staged into a transaction that is then abandoned, the call returns err == nil with a non-nil response, and nothing is persisted.
pkg/gofr/datasource/dgraph/dgraph.go:220:
func (d *Client) Mutate(ctx context.Context, mu any) (any, error) {
...
resp, err := d.client.NewTxn().Mutate(tracedCtx, mutation)
...
}No Commit. No Discard.
Why the behaviour depends on the caller
Verified against the pinned github.com/dgraph-io/dgo/v210 v210.0.0-20230328113526-b66f8ae53a2d:
// txn.go:153
func (txn *Txn) Mutate(ctx context.Context, mu *api.Mutation) (*api.Response, error) {
req := &api.Request{
StartTs: txn.context.StartTs,
Mutations: []*api.Mutation{mu},
CommitNow: mu.CommitNow, // <- taken from the caller's mutation
}
return txn.Do(ctx, req)
}
// txn.go:205
if err == nil {
if req.CommitNow { // <- transaction only finishes when set
txn.finished = true
}
err = txn.mergeContext(resp.GetTxn())
return resp, err
}| caller | outcome |
|---|---|
sets CommitNow: true |
committed — works |
omits CommitNow |
staged, never committed, no error returned |
Reproduction
Against a real Dgraph v21.03.0, using Client exactly as an application would (full output below is verbatim, stable over 3 consecutive runs with drop_all between):
// A: no CommitNow — what pkg/gofr/migration/dgraph.go:165 does today
respA, errA := c.Mutate(ctx, &api.Mutation{SetJson: []byte(`{"probe":"NO_COMMITNOW"}`)})
// B: with CommitNow — what docs/datasources/dgraph/page.md:117 does
respB, errB := c.Mutate(ctx, &api.Mutation{SetJson: []byte(`{"probe":"WITH_COMMITNOW"}`), CommitNow: true})A) Mutate WITHOUT CommitNow -> err=<nil>, resp non-nil=true
B) Mutate WITH CommitNow -> err=<nil>, resp non-nil=true
PERSISTED: without CommitNow = 0, with CommitNow = 1Both calls report success. Only one wrote anything.
Scope — deliberately not overstated
The documented example at docs/datasources/dgraph/page.md:117 does set CommitNow: true, so a user who copies the docs is unaffected. This is not "all Dgraph writes are lost".
What it is: a caller who omits CommitNow gets a silent no-op, and nothing in the signature
Mutate(ctx context.Context, mu any) (any, error)or in the method's doc comment (// Mutate executes a write operation (mutation) in the Dgraph database and returns the result.) indicates that a field on the argument decides whether the write happens.
That GoFr's own migration code was exactly such a caller is the evidence that this is a real trap rather than a theoretical one — pkg/gofr/migration/dgraph.go:165 on development:
_, err = c.DGraph.Mutate(context.Background(), &api.Mutation{
SetJson: jsonPayload,
})No CommitNow. That is one of three reasons Dgraph migrations never persisted; #3186 fixes the migration path by calling NewTxn() directly and committing, which leaves the public method as it is.
Secondary: leaked transaction
On the non-committing path there is also no Discard, so an open transaction is left on the server for every such call until Dgraph times it out.
Suggested fix
Either shape closes it; the second matches what #3186 just did in the migrator:
- Default
CommitNowto true inClient.Mutatewhen the caller left it unset — smallest change, matches what the docs already tell people to do, and makes the single-mutation call atomic by default. NewTxn()+Mutate+ explicitCommit, with a deferredDiscard.
Whichever is chosen, the doc comment should say what the commit semantics are, and docs/datasources/dgraph/page.md should stop relying on the reader noticing CommitNow in a code sample.
If the intent is genuinely to expose an uncommitted transaction, then the method needs to return something the caller can commit — returning any and silently dropping the write is the part that cannot stay either way.
Environment
- gofr
development@dacb55c9d github.com/dgraph-io/dgo/v210 v210.0.0-20230328113526-b66f8ae53a2d- Dgraph v21.03.0
Related
- #3186 — fixes the migration path around this; does not change
Client.Mutate
Source: gofr-dev/gofr