diff --git a/edgraph/server.go b/edgraph/server.go index d01052824f0..6113aba67bd 100644 --- a/edgraph/server.go +++ b/edgraph/server.go @@ -1804,6 +1804,9 @@ func addQueryIfUnique(qctx context.Context, qc *queryContext) error { // in the mutation, then we reject the mutation. if !strings.HasPrefix(pred.ObjectId, "val(") { + if pred.ObjectValue == nil { + continue + } val := strconv.Quote(fmt.Sprintf("%v", dql.TypeValFrom(pred.ObjectValue).Value)) query := fmt.Sprintf(`%v as var(func: eq(%v,"%v"))`, queryVar, predicateName, val[1:len(val)-1]) if _, err := buildQuery.WriteString(query); err != nil { @@ -2218,6 +2221,9 @@ func verifyUniqueWithinMutation(qc *queryContext) error { continue } pred1 := qc.gmuList[gmuIndex].Set[rdfIndex] + if pred1.ObjectValue == nil { + continue + } pred1Value := dql.TypeValFrom(pred1.ObjectValue).Value for j := range qc.uniqueVars { if i == j { @@ -2229,6 +2235,9 @@ func verifyUniqueWithinMutation(qc *queryContext) error { continue } pred2 := qc.gmuList[gmuIndex2].Set[rdfIndex2] + if pred2.ObjectValue == nil { + continue + } if pred2.Predicate == pred1.Predicate && dql.TypeValFrom(pred2.ObjectValue).Value == pred1Value && pred2.Subject != pred1.Subject { return errors.Errorf("could not insert duplicate value [%v] for predicate [%v]", diff --git a/edgraph/server_test.go b/edgraph/server_test.go index de8db3ff939..92a653c2e2e 100644 --- a/edgraph/server_test.go +++ b/edgraph/server_test.go @@ -285,4 +285,94 @@ func TestVerifyUniqueWithinMutationBoundsChecks(t *testing.T) { err := verifyUniqueWithinMutation(qc) require.NoError(t, err) }) + + // Regression test for https://github.com/dgraph-io/dgraph/issues/9670 + // When an NQuad has a nil ObjectValue (e.g. a UID edge on a predicate + // marked @unique), verifyUniqueWithinMutation would panic with a nil + // pointer dereference in dql.TypeValFrom. + t.Run("nil ObjectValue should not panic", func(t *testing.T) { + qc := &queryContext{ + gmuList: []*dql.Mutation{ + { + Set: []*api.NQuad{ + { + Subject: "_:a", + Predicate: "friend", + ObjectId: "0x1", + ObjectValue: nil, // UID edge, no value + }, + }, + }, + }, + uniqueVars: map[uint64]uniquePredMeta{ + encodeIndex(0, 0): {}, + }, + } + // Before fix: panics with "nil pointer dereference" in dql.TypeValFrom + require.NotPanics(t, func() { + _ = verifyUniqueWithinMutation(qc) + }) + }) + + // Same issue but with two NQuads where one has a nil ObjectValue + // and the other has a real value — the inner loop comparison must + // also handle the nil case. + t.Run("nil ObjectValue mixed with value NQuad should not panic", func(t *testing.T) { + qc := &queryContext{ + gmuList: []*dql.Mutation{ + { + Set: []*api.NQuad{ + { + Subject: "_:a", + Predicate: "email", + ObjectValue: &api.Value{ + Val: &api.Value_StrVal{StrVal: "test@example.com"}, + }, + }, + { + Subject: "_:a", + Predicate: "friend", + ObjectId: "0x2", + ObjectValue: nil, // UID edge + }, + }, + }, + }, + uniqueVars: map[uint64]uniquePredMeta{ + encodeIndex(0, 0): {}, + encodeIndex(0, 1): {}, + }, + } + require.NotPanics(t, func() { + err := verifyUniqueWithinMutation(qc) + require.NoError(t, err) + }) + }) + + // Verify that val(...) reference edges (ObjectId="val(x)", ObjectValue=nil) + // don't panic. These go through a different code path in addQueryIfUnique + // but could still appear in uniqueVars and be iterated by + // verifyUniqueWithinMutation. + t.Run("val() reference with nil ObjectValue should not panic", func(t *testing.T) { + qc := &queryContext{ + gmuList: []*dql.Mutation{ + { + Set: []*api.NQuad{ + { + Subject: "_:a", + Predicate: "email", + ObjectId: "val(queryVar)", + ObjectValue: nil, + }, + }, + }, + }, + uniqueVars: map[uint64]uniquePredMeta{ + encodeIndex(0, 0): {}, + }, + } + require.NotPanics(t, func() { + _ = verifyUniqueWithinMutation(qc) + }) + }) }