dgraph-io/dgraph · error

Schema change not allowed from [%s] => %s without deleting p

Error message

Schema change not allowed from [%s] => %s without deleting pred: %s

What it means

Dgraph does not allow converting a list-type predicate ([T]) back to a single-value predicate (T) while any data exists for that predicate, because existing postings store multiple values per node. checkSchema checks IsList(predicate) && !s.List && hasEdges(...) and rejects the change naming the old and new types.

Source

Thrown at worker/mutation.go:445

	if err != nil {
		// No schema previously defined, so no need to do checks about schema conversions.
		return nil
	}

	// schema was defined already
	switch {
	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

View on GitHub (pinned to 759e242be6)

Solutions

  1. Delete all data for the predicate first (e.g. upsert/mutation deleting tags for every node, or drop the predicate), then change the type.
  2. Keep the list type and treat single values as one-element lists in queries instead of changing the schema.
  3. Migrate to a new scalar predicate: write collapsed values to a new pred, then drop the old list pred.
  4. Verify with a count query that the predicate has no edges before attempting the alteration.

Example fix

// before
ALTER: tags: string @index(term) .   // tags currently [string] with data
// after
// first delete all tags edges, then:
ALTER: tags: string @index(term) .
Defensive patterns

Strategy: validation

Validate before calling

func validateListCollapse(pred string, wantList bool, d *dgo.Dgraph) error {
    if schema.State().IsList(pred) && !wantList {
        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 still has data; delete before list->scalar", pred)
        }
    }
    return nil
}

Try / catch

err := dgraph.Alter(ctx, op)
if err != nil && strings.Contains(err.Error(), "without deleting pred") {
    // delete all edges for the pred, then retry the alteration
}

Prevention

When it happens

Trigger: Altering 'tags: [string] @index(term)' to 'tags: string' while nodes still carry values for tags; the hasEdges check on math.MaxUint64 finds data and rejects it.

Common situations: Simplifying a schema after deciding lists are unnecessary; accidental removal of the [] brackets when editing DQL schema; ORM-generated schema drift where the model changed from array to scalar.

Related errors


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