Changing a property to a link​

This example shows how to change a property into a link. We’ll use a character in an adventure game as the type of data we will evolve.

Let’s start with this schema:

  1. scalar type CharacterClass extending enum<warrior, scholar, rogue>;
  2. type Character {
  3. required property name -> str;
  4. required property class -> CharacterClass;
  5. }

We edit the schema file and perform our first migration:

  1. edgedb migration create
  1. did you create scalar type 'default::CharacterClass'? [y,n,l,c,b,s,q,?]
  2. > y
  3. did you create object type 'default::Character'? [y,n,l,c,b,s,q,?]
  4. > y
  5. Created ./dbschema/migrations/00001.edgeql, id:
  6. m1fg76t7fbvguwhkmzrx7jwki6jxr6dvkswzeepd5v66oxg27ymkcq
  1. edgedb migrate
  1. Applied m1fg76t7fbvguwhkmzrx7jwki6jxr6dvkswzeepd5v66oxg27ymkcq
  2. (00001.edgeql)

The initial setup may look something like this:

  1. select Character {name, class};
  1. {
  2. default::Character {name: 'Alice', class: warrior},
  3. default::Character {name: 'Billie', class: scholar},
  4. default::Character {name: 'Cameron', class: rogue},
  5. }

After some development work we decide to add more details about the available classes and encapsulate that information into its own type. This way instead of a property class we want to end up with a link class to the new data structure. Since we cannot just cast a scalar into an object, we’ll need to convert between the two explicitly. This means that we will need to have both the old and the new “class” information to begin with:

  1. scalar type CharacterClass extending enum<warrior, scholar, rogue>;
  2. type NewClass {
  3. required property name -> str;
  4. multi property skills -> str;
  5. }
  6. type Character {
  7. required property name -> str;
  8. required property class -> CharacterClass;
  9. link new_class -> NewClass;
  10. }

We update the schema file and migrate to the new state:

  1. edgedb migration create
  1. did you create object type 'default::NewClass'? [y,n,l,c,b,s,q,?]
  2. > y
  3. did you create link 'new_class' of object type 'default::Character'?
  4. [y,n,l,c,b,s,q,?]
  5. > y
  6. Created ./dbschema/migrations/00002.edgeql, id:
  7. m1uttd6f7fpiwiwikhdh6qyijb6pcji747ccg2cyt5357i3wsj3l3q
  1. edgedb migrate
  1. Applied m1uttd6f7fpiwiwikhdh6qyijb6pcji747ccg2cyt5357i3wsj3l3q
  2. (00002.edgeql)

It makes sense to add a data migration as a way of consistently creating NewClass objects as well as populating new_class links based on the existing class property. So we first create an empty migration:

  1. edgedb migration create --allow-empty
  1. Created ./dbschema/migrations/00003.edgeql, id:
  2. m1iztxroh3ifoeqmvxncy77whnaei6tp5j3sewyxtrfysronjkxgga

And then edit the 00003.edgeql file to create and update objects:

  1. CREATE MIGRATION m1iztxroh3ifoeqmvxncy77whnaei6tp5j3sewyxtrfysronjkxgga
  2. ONTO m1uttd6f7fpiwiwikhdh6qyijb6pcji747ccg2cyt5357i3wsj3l3q
  3. {
  4. insert default::NewClass {
  5. name := 'Warrior',
  6. skills := {'punch', 'kick', 'run', 'jump'},
  7. };
  8. insert default::NewClass {
  9. name := 'Scholar',
  10. skills := {'read', 'write', 'analyze', 'refine'},
  11. };
  12. insert default::NewClass {
  13. name := 'Rogue',
  14. skills := {'impress', 'sing', 'steal', 'run', 'jump'},
  15. };
  16. update default::Character
  17. set {
  18. new_class := assert_single((
  19. select default::NewClass
  20. filter .name ilike <str>default::Character.class
  21. )),
  22. };
  23. };

Trying to apply the data migration will produce the following reminder:

  1. edgedb migrate
  1. edgedb error: could not read migrations in ./dbschema/migrations:
  2. could not read migration file ./dbschema/migrations/00003.edgeql:
  3. migration name should be
  4. `m1e3d3eg3j2pr7acie4n5rrhaddyhkiy5kgckd5l7h5ysrpmgwxl5a` but
  5. `m1iztxroh3ifoeqmvxncy77whnaei6tp5j3sewyxtrfysronjkxgga` is used
  6. instead.
  7. Migration names are computed from the hash of the migration
  8. contents. To proceed you must fix the statement to read as:
  9. CREATE MIGRATION m1e3d3eg3j2pr7acie4n5rrhaddyhkiy5kgckd5l7h5ysrpmgwxl5a
  10. ONTO ...
  11. if this migration is not applied to any database. Alternatively,
  12. revert the changes to the file.

The migration tool detected that we’ve altered the file and asks us to update the migration name (acting as a checksum) if this was deliberate. This is done as a precaution against accidental changes. Since we’ve done this on purpose, we can update the file and run edgedb migrate again.

We can see the changes after the data migration is complete:

  1. select Character {
  2. name,
  3. class,
  4. new_class: {
  5. name,
  6. }
  7. };
  1. {
  2. default::Character {
  3. name: 'Alice',
  4. class: warrior,
  5. new_class: default::NewClass {name: 'Warrior'},
  6. },
  7. default::Character {
  8. name: 'Billie',
  9. class: scholar,
  10. new_class: default::NewClass {name: 'Scholar'},
  11. },
  12. default::Character {
  13. name: 'Cameron',
  14. class: rogue,
  15. new_class: default::NewClass {name: 'Rogue'},
  16. },
  17. }

Everything seems to be in order. It is time to clean up the old property and CharacterClass enum:

  1. type NewClass {
  2. required property name -> str;
  3. multi property skills -> str;
  4. }
  5. type Character {
  6. required property name -> str;
  7. link new_class -> NewClass;
  8. }

The migration tools should have no trouble detecting the things we just removed:

  1. edgedb migration create
  1. did you drop property 'class' of object type 'default::Character'?
  2. [y,n,l,c,b,s,q,?]
  3. > y
  4. did you drop scalar type 'default::CharacterClass'? [y,n,l,c,b,s,q,?]
  5. > y
  6. Created ./dbschema/migrations/00004.edgeql, id:
  7. m1jdnz5bxjj6kjz2pylvudli5rvw4jyr2ilpb4hit3yutwi3bq34ha
  1. edgedb migrate
  1. Applied m1jdnz5bxjj6kjz2pylvudli5rvw4jyr2ilpb4hit3yutwi3bq34ha
  2. (00004.edgeql)

Now that the original property and scalar type are gone, we can rename the “new” components, so that they become class link and CharacterClass type, respectively:

  1. type CharacterClass {
  2. required property name -> str;
  3. multi property skills -> str;
  4. }
  5. type Character {
  6. required property name -> str;
  7. link class -> CharacterClass;
  8. }

The migration tools pick up the changes without any issues again. It may seem tempting to combine the last two steps, but deleting and renaming in a single step would cause the migration tools to report a name clash. As a general rule, it is a good idea to never mix renaming and deleting of closely interacting entities in the same migration.

  1. edgedb migration create
  1. did you rename object type 'default::NewClass' to
  2. 'default::CharacterClass'? [y,n,l,c,b,s,q,?]
  3. > y
  4. did you rename link 'new_class' of object type 'default::Character' to
  5. 'class'? [y,n,l,c,b,s,q,?]
  6. > y
  7. Created ./dbschema/migrations/00005.edgeql, id:
  8. m1ra4fhx2erkygbhi7qjxt27yup5aw5hkr5bekn5y5jeam5yn57vsa
  1. edgedb migrate
  1. Applied m1ra4fhx2erkygbhi7qjxt27yup5aw5hkr5bekn5y5jeam5yn57vsa
  2. (00005.edgeql)

Finally, we have replaced the original class property with a link:

  1. select Character {
  2. name,
  3. class: {
  4. name,
  5. skills,
  6. }
  7. };
  1. {
  2. default::Character {
  3. name: 'Alice',
  4. class: default::CharacterClass {
  5. name: 'Warrior',
  6. skills: {'punch', 'kick', 'run', 'jump'},
  7. },
  8. },
  9. default::Character {
  10. name: 'Billie',
  11. class: default::CharacterClass {
  12. name: 'Scholar',
  13. skills: {'read', 'write', 'analyze', 'refine'},
  14. },
  15. },
  16. default::Character {
  17. name: 'Cameron',
  18. class: default::CharacterClass {
  19. name: 'Rogue',
  20. skills: {'impress', 'sing', 'steal', 'run', 'jump'},
  21. },
  22. },
  23. }