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

  1. Keep @upsert whenever @unique is present: 'email: string @index(hash) @upsert @unique'.
  2. If upsert should be removed, remove @unique too in the same alteration.
  3. Drop and recreate the predicate without the directives if the combination is no longer wanted (mind data retention).
  4. 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

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


AI-assisted analysis of dgraph-io/dgraph@759e242be6 (2026-09-01). Data as JSON: /api/errors/f03dc6aa03c1429a. Report an issue: GitHub.