dgraph-io/dgraph · error
could not drop @upsert from [%v] predicate when @unique dire
Error message
could not drop @upsert from [%v] predicate when @unique directive specified
What it means
validateSchemaForUnique enforces invariants for @unique predicates: a predicate that is @unique must keep @upsert (uniqueness enforcement is implemented via upsert transactions) and must keep a valid index tokenizer. This error fires when an update tries to remove @upsert from a predicate whose previous schema had both Upsert and (implicitly) unique constraints.
Source
Thrown at worker/mutation.go:470
return errors.Errorf("Schema change not allowed from scalar to uid or vice versa"+
" while there is data for pred: %s", x.ParseAttr(s.Predicate))
}
}
return nil
}
func validateSchemaForUnique(prevSchema pb.SchemaUpdate, currentSchema *pb.SchemaUpdate) error {
validTokenizer := func(tokenizers []string) bool {
for _, value := range tokenizers {
if value == "hash" || value == "exact" || value == "int" {
return true
}
}
return false
}
if prevSchema.Predicate != "" {
if prevSchema.Upsert && !currentSchema.Upsert {
return errors.Errorf("could not drop @upsert from [%v] predicate when @unique directive specified",
x.ParseAttr(currentSchema.Predicate))
}
if prevSchema.Unique && !validTokenizer(currentSchema.Tokenizer) {
return errors.Errorf("could not drop index %v from [%v] predicate when @unique directive specified",
prevSchema.Tokenizer, x.ParseAttr(currentSchema.Predicate))
}
}
if !currentSchema.Upsert {
currentSchema.Upsert = true
}
switch currentSchema.ValueType {
case pb.Posting_STRING:
if len(currentSchema.Tokenizer) == 0 ||
(len(currentSchema.Tokenizer) > 0 && !validTokenizer(currentSchema.Tokenizer)) {
return errors.Errorf("index for predicate [%v] is missing, add either hash or exact index with @unique",
x.ParseAttr(currentSchema.Predicate))View on GitHub (pinned to 759e242be6)
Solutions
- Keep @upsert whenever @unique is present: 'email: string @index(hash) @upsert @unique'.
- If upsert should be removed, remove @unique too in the same alteration.
- Drop and recreate the predicate without the directives if the combination is no longer wanted (mind data retention).
- Validate previous schema state (schema.State().Get) before proposing directive removals.
Example fix
// before email: string @index(hash) @unique . // after email: string @index(hash) @upsert @unique .
Defensive patterns
Strategy: validation
Validate before calling
func validateUniqueUpsert(prev, next *pb.SchemaUpdate) error {
if prev != nil && prev.Predicate != "" && prev.Unique && !next.Upsert {
return fmt.Errorf("predicate %s: @unique requires @upsert", next.Predicate)
}
return nil
} Try / catch
err := dgraph.Alter(ctx, op)
if err != nil && strings.Contains(err.Error(), "could not drop @upsert") {
// restore @upsert or remove @unique in the same update
} Prevention
- Always emit @upsert and @unique as a pair
- Remove both directives together if uniqueness is no longer needed
- Diff against previous schema state before directive removals
When it happens
Trigger: Altering a predicate that currently has '@upsert @unique' by dropping @upsert while keeping @unique, e.g. changing 'email: string @index(hash) @upsert @unique' to 'email: string @index(hash) @unique'.
Common situations: Schema cleanup removing directives one at a time without realizing @unique depends on @upsert; generated schemas where @upsert was stripped; misunderstandings that @unique alone suffices.
Understand the failure class
Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.
Related errors
- Index tokenizer is mandatory for: [%s] when specifying @upse
- could not drop index %v from [%v] predicate when @unique dir
- illegal rune found "%c", expecting {
- JSON map is followed by illegal rune "%c"
- Malformed JSON
AI-assisted analysis of dgraph-io/dgraph@759e242be6 (2026-09-01).
Data as JSON: /api/errors/f03dc6aa03c1429a.
Report an issue: GitHub.