Skip to content

Commit 1cbd7ea

Browse files
fix(unique): prevent nil pointer panic in verifyUniqueWithinMutation (#9677)
## Summary - Fix nil pointer dereference in `verifyUniqueWithinMutation` and `addQueryIfUnique` when a mutation batch contains a UID edge (`ObjectValue` is nil) on a `@unique` predicate - Add nil checks for `ObjectValue` in 3 locations before calling `dql.TypeValFrom()` - All 3 alphas crash simultaneously because the same mutation is replicated via Raft ## Test plan - [x] Added 2 regression tests that panic without the fix and pass with it - [x] `TestVerifyUniqueWithinMutationBoundsChecks/nil_ObjectValue_should_not_panic` - [x] `TestVerifyUniqueWithinMutationBoundsChecks/nil_ObjectValue_mixed_with_value_NQuad_should_not_panic` - [x] All existing tests pass Fixes #9670 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.6 (1M context) <[email protected]>
1 parent b15c87e commit 1cbd7ea

2 files changed

Lines changed: 99 additions & 0 deletions

File tree

edgraph/server.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1804,6 +1804,9 @@ func addQueryIfUnique(qctx context.Context, qc *queryContext) error {
18041804
// in the mutation, then we reject the mutation.
18051805

18061806
if !strings.HasPrefix(pred.ObjectId, "val(") {
1807+
if pred.ObjectValue == nil {
1808+
continue
1809+
}
18071810
val := strconv.Quote(fmt.Sprintf("%v", dql.TypeValFrom(pred.ObjectValue).Value))
18081811
query := fmt.Sprintf(`%v as var(func: eq(%v,"%v"))`, queryVar, predicateName, val[1:len(val)-1])
18091812
if _, err := buildQuery.WriteString(query); err != nil {
@@ -2218,6 +2221,9 @@ func verifyUniqueWithinMutation(qc *queryContext) error {
22182221
continue
22192222
}
22202223
pred1 := qc.gmuList[gmuIndex].Set[rdfIndex]
2224+
if pred1.ObjectValue == nil {
2225+
continue
2226+
}
22212227
pred1Value := dql.TypeValFrom(pred1.ObjectValue).Value
22222228
for j := range qc.uniqueVars {
22232229
if i == j {
@@ -2229,6 +2235,9 @@ func verifyUniqueWithinMutation(qc *queryContext) error {
22292235
continue
22302236
}
22312237
pred2 := qc.gmuList[gmuIndex2].Set[rdfIndex2]
2238+
if pred2.ObjectValue == nil {
2239+
continue
2240+
}
22322241
if pred2.Predicate == pred1.Predicate && dql.TypeValFrom(pred2.ObjectValue).Value == pred1Value &&
22332242
pred2.Subject != pred1.Subject {
22342243
return errors.Errorf("could not insert duplicate value [%v] for predicate [%v]",

edgraph/server_test.go

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,4 +285,94 @@ func TestVerifyUniqueWithinMutationBoundsChecks(t *testing.T) {
285285
err := verifyUniqueWithinMutation(qc)
286286
require.NoError(t, err)
287287
})
288+
289+
// Regression test for https://github.com/dgraph-io/dgraph/issues/9670
290+
// When an NQuad has a nil ObjectValue (e.g. a UID edge on a predicate
291+
// marked @unique), verifyUniqueWithinMutation would panic with a nil
292+
// pointer dereference in dql.TypeValFrom.
293+
t.Run("nil ObjectValue should not panic", func(t *testing.T) {
294+
qc := &queryContext{
295+
gmuList: []*dql.Mutation{
296+
{
297+
Set: []*api.NQuad{
298+
{
299+
Subject: "_:a",
300+
Predicate: "friend",
301+
ObjectId: "0x1",
302+
ObjectValue: nil, // UID edge, no value
303+
},
304+
},
305+
},
306+
},
307+
uniqueVars: map[uint64]uniquePredMeta{
308+
encodeIndex(0, 0): {},
309+
},
310+
}
311+
// Before fix: panics with "nil pointer dereference" in dql.TypeValFrom
312+
require.NotPanics(t, func() {
313+
_ = verifyUniqueWithinMutation(qc)
314+
})
315+
})
316+
317+
// Same issue but with two NQuads where one has a nil ObjectValue
318+
// and the other has a real value — the inner loop comparison must
319+
// also handle the nil case.
320+
t.Run("nil ObjectValue mixed with value NQuad should not panic", func(t *testing.T) {
321+
qc := &queryContext{
322+
gmuList: []*dql.Mutation{
323+
{
324+
Set: []*api.NQuad{
325+
{
326+
Subject: "_:a",
327+
Predicate: "email",
328+
ObjectValue: &api.Value{
329+
Val: &api.Value_StrVal{StrVal: "[email protected]"},
330+
},
331+
},
332+
{
333+
Subject: "_:a",
334+
Predicate: "friend",
335+
ObjectId: "0x2",
336+
ObjectValue: nil, // UID edge
337+
},
338+
},
339+
},
340+
},
341+
uniqueVars: map[uint64]uniquePredMeta{
342+
encodeIndex(0, 0): {},
343+
encodeIndex(0, 1): {},
344+
},
345+
}
346+
require.NotPanics(t, func() {
347+
err := verifyUniqueWithinMutation(qc)
348+
require.NoError(t, err)
349+
})
350+
})
351+
352+
// Verify that val(...) reference edges (ObjectId="val(x)", ObjectValue=nil)
353+
// don't panic. These go through a different code path in addQueryIfUnique
354+
// but could still appear in uniqueVars and be iterated by
355+
// verifyUniqueWithinMutation.
356+
t.Run("val() reference with nil ObjectValue should not panic", func(t *testing.T) {
357+
qc := &queryContext{
358+
gmuList: []*dql.Mutation{
359+
{
360+
Set: []*api.NQuad{
361+
{
362+
Subject: "_:a",
363+
Predicate: "email",
364+
ObjectId: "val(queryVar)",
365+
ObjectValue: nil,
366+
},
367+
},
368+
},
369+
},
370+
uniqueVars: map[uint64]uniquePredMeta{
371+
encodeIndex(0, 0): {},
372+
},
373+
}
374+
require.NotPanics(t, func() {
375+
_ = verifyUniqueWithinMutation(qc)
376+
})
377+
})
288378
}

0 commit comments

Comments
 (0)