Skip to content

Commit 9164672

Browse files
fix(unique): prevent nil pointer panic in verifyUniqueWithinMutation (#9670)
When a mutation batch contains a UID edge (ObjectValue is nil) on a predicate marked @unique, verifyUniqueWithinMutation and addQueryIfUnique call dql.TypeValFrom(pred.ObjectValue) without a nil check, causing a nil pointer dereference that crashes the alpha. This affects all replicas since the same mutation is replicated via Raft, causing simultaneous crashes across the entire cluster. The fix adds nil checks for ObjectValue in three locations: - addQueryIfUnique: skip building unique-check query for UID edges - verifyUniqueWithinMutation: skip pred1 and pred2 comparisons when ObjectValue is nil Fixes #9670 Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
1 parent 4120211 commit 9164672

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)