Drop and redefine predicate in schema

I need to change a list to a scalar in my schema, but Dgraph is giving me an error (in both the JavaScript client and Ratel):

Schema change not allowed from [uid] => uid without deleting pred

Ratel has a Drop button for the predicate, but clicking it gives this ominous warning:

Are you sure?
This action will destroy data!

So I have two questions:

  1. Does dropping a predicate from the schema delete the underlying data (i.e. nodes) associated with that predicate in the database?
  2. What’s the best way for me to modify my schema but keep the data and the relationships between nodes?

Nope, it deletes just the predicate shard in the cluster. The “nodes” are safe.

Migrate your data using Upsert block to a new predicate. And then using the drop predicate operation.

But one thing worth mentioning, unless is a false alarm. Migrate [uid] => uid can’t be done right. As you are migrating from a one to many to a one to one relation. The other edges will be lost anyway in that case.

Thanks for your response. Sorry it’s taken me so long to get back to you.

Migrate [uid] => uid can’t be done right. As you are migrating from a one to many to a one to one relation. The other edges will be lost anyway in that case.

True, but in my case I only have one to migrate, so that won’t be a problem. The problem is that I inadvertently defined a one-to-many relationship…

Token.person: [uid] @reverse .

…when I should have defined a one-to-one:

Token.person: uid @reverse .

I tried to fix this today by adding a new predicate that defines a single uid instead of a list…

Token.singlePerson: uid @reverse .

…then running an upsert block to copy each Token.person into its Token.singlePerson

upsert {
  query {
    token as token(func: type(Token)) {
      person as Token.person {
        uid
      }
      uid
    }
  }
  
  mutation {
    set {
      uid(token) <Token.singlePerson> uid(person) .
    }
  }
}

…but now all Token.singlePerson edges reference the same Token.person (instead of each referencing its own).

Here’s my data before and after:

Before:

"tokens": [
  {
    "Token.person": [
      {
        "Person.handle": "BubblyCat",
        "uid": "0x102c3"
      }
    ],
  },
  {
    "Token.person": [
      {
        "Person.handle": "CuddlyBear",
        "uid": "0x102c5"
      }
    ],
  },
  {
    "Token.person": [
      {
        "Person.handle": "CuddlyCat",
        "uid": "0x102c7"
      }
    ],
  }
]
After:

"tokens": [
  {
    "Token.person": [
      {
        "Person.handle": "BubblyCat",
        "uid": "0x102c3"
      }
    ],
    "Token.singlePerson": {
      "Person.handle": "CuddlyCat",
      "uid": "0x102c7"
    },
  },
  {
    "Token.person": [
      {
        "Person.handle": "CuddlyBear",
        "uid": "0x102c5"
      }
    ],
    "Token.singlePerson": {
      "Person.handle": "CuddlyCat",
      "uid": "0x102c7"
    },
  },
  {
    "Token.person": [
      {
        "Person.handle": "CuddlyCat",
        "uid": "0x102c7"
      }
    ],
    "Token.singlePerson": {
      "Person.handle": "CuddlyCat",
      "uid": "0x102c7"
    },
  }
]

You can see that the one CuddlyCat instance was copied to all Token.singlePerson edges.

So I have two questions:

  1. Is there a way to do a bulk upsert like this in a single transaction, or do I have to run a separate upsert for each token?
  2. If I must run an upsert for each token, can I do them all in one transaction, or do I need a separate transaction for each upsert?

You can export you data, and edit your schema as one to one before bulkload it. Or use Upsert block to migrate the relation to a TMP edge and after that delete the one to many, fix that in the schema and do a second migration. That is hard, so the best way is exporting an reimporting.

Yep, that’s a know issue. See FOREACH func in DQL (loops in Bulk Upsert)

Today you have to do the migration per entity. If you do a Bulk upsert in that case, it will mess up the relations. You have to send several Upserts with a relation 1:1.

Not sure, you will have to do a loop for each entity and concat them - would be hard to do. So, as you know that there is only a single relation there, the best is export your data, fix the schema and reimport. Bulk upserts won’t work well for your case.

Thanks for your help. I was able to get it working like this:

  1. Start a transaction.
  2. Perform a query to look up the UIDs of all my Token nodes.
  3. Loop over the list of UIDs from the previous step and upsert each one so that its Token.person is copied to its Token.singlePerson.
  4. Commit the transaction.
2 Likes