dgraph-io/dgraph · error

Schema change not allowed from scalar to uid or vice versa w

Error message

Schema change not allowed from scalar to uid or vice versa while there is data for pred: %s

What it means

Changing a predicate between uid (edge) and scalar types is only permitted when the predicate holds no data, since the storage layout is completely different. checkSchema's default branch rejects the change when hasEdges(predicate, math.MaxUint64) reports existing data.

Source

Thrown at worker/mutation.go:452

	case t.IsScalar() && (t.Enum() == pb.Posting_PASSWORD || s.ValueType == pb.Posting_PASSWORD):
		// can't change password -> x, x -> password
		if t.Enum() != s.ValueType {
			return errors.Errorf("Schema change not allowed from %s to %s",
				t.Enum(), typ.Enum())
		}

	case t.IsScalar() == typ.IsScalar():
		// If old type was list and new type is non-list, we don't allow it until user
		// has data.
		if schema.State().IsList(s.Predicate) && !s.List && hasEdges(s.Predicate, math.MaxUint64) {
			return errors.Errorf("Schema change not allowed from [%s] => %s without"+
				" deleting pred: %s", t.Name(), typ.Name(), x.ParseAttr(s.Predicate))
		}

	default:
		// uid => scalar or scalar => uid. Check that there shouldn't be any data.
		if hasEdges(s.Predicate, math.MaxUint64) {
			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",

View on GitHub (pinned to 759e242be6)

Solutions

  1. Delete all data for the predicate (drop all edges) before altering the type.
  2. Drop the predicate entirely and re-add with the new type, repopulating data afterwards.
  3. Introduce a new predicate with the desired type and migrate values via queries/upsert, then drop the old one.
  4. Check emptiness first with a query like 'pred(count)' or has(pred) before submitting the alteration.

Example fix

// before
ALTER: owner: string .   // owner is uid and has data
// after
// delete all owner edges (or drop pred owner), then:
ALTER: owner: string .
Defensive patterns

Strategy: validation

Validate before calling

func validateUidScalarSwap(pred string, d *dgo.Dgraph) error {
    resp, err := d.NewReadOnlyTxn().Query(context.Background(),
        fmt.Sprintf("{ q(func: has(%s), first: 1) { uid } }", pred))
    if err != nil {
        return err
    }
    if len(resp.Json) > len("{}") {
        return fmt.Errorf("pred %s has data; uid<->scalar change forbidden", pred)
    }
    return nil
}

Try / catch

err := dgraph.Alter(ctx, op)
if err != nil && strings.Contains(err.Error(), "scalar to uid or vice versa") {
    // drop pred (or delete all edges), then apply new type
}

Prevention

When it happens

Trigger: Altering 'owner: uid' to 'owner: string' (or vice versa) while any node still has an owner edge; commonly hit when refactoring whether a reference is stored as a uid or an external scalar id.

Common situations: Model refactors from node references to external IDs (or back); schema import from another database assuming free type changes; cleanup scripts run against a live database with residual data.

Related errors


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