Adding a required link
This example shows how to setup a required link. We’ll use a character in an adventure game as the type of data we will evolve.
Let’s start with this schema:
type Character {
required property name -> str;
}
We edit the schema file and perform our first migration:
$
edgedb migration create
did you create object type 'default::Character'? [y,n,l,c,b,s,q,?]
> y
Created ./dbschema/migrations/00001.edgeql, id:
m1xvu7o4z5f5xfwuun2vee2cryvvzh5lfilwgkulmqpifo5m3dnd6a
$
edgedb migrate
Applied m1xvu7o4z5f5xfwuun2vee2cryvvzh5lfilwgkulmqpifo5m3dnd6a
(00001.edgeql)
This time around let’s practice performing a data migration and set up our character data. For this purpose we can create an empty migration and fill it out as we like:
$
edgedb migration create --allow-empty
Created ./dbschema/migrations/00002.edgeql, id:
m1lclvwdpwitjj4xqm45wp74y4wjyadljct5o6bsctlnh5xbto74iq
We edit the 00002.edgeql
file by simply adding the query to add characters to it. We can use for to add multiple characters like this:
CREATE MIGRATION m1lclvwdpwitjj4xqm45wp74y4wjyadljct5o6bsctlnh5xbto74iq
ONTO m1xvu7o4z5f5xfwuun2vee2cryvvzh5lfilwgkulmqpifo5m3dnd6a
{
for name in {'Alice', 'Billie', 'Cameron', 'Dana'}
union (
insert default::Character {
name := name
}
);
};
Trying to apply the data migration will produce the following reminder:
$
edgedb migrate
edgedb error: could not read migrations in ./dbschema/migrations:
could not read migration file ./dbschema/migrations/00002.edgeql:
migration name should be
`m1juin65wriqmb4vwg23fiyajjxlzj2jyjv5qp36uxenit5y63g2iq` but
`m1lclvwdpwitjj4xqm45wp74y4wjyadljct5o6bsctlnh5xbto74iq` is used instead.
Migration names are computed from the hash of the migration contents. To
proceed you must fix the statement to read as:
CREATE MIGRATION m1juin65wriqmb4vwg23fiyajjxlzj2jyjv5qp36uxenit5y63g2iq
ONTO ...
if this migration is not applied to any database. Alternatively,
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.
CREATE MIGRATION m1lclvwdpwitjj4xqm45wp74y4wjyadljct5o6bsctlnh5xbto74iq
CREATE MIGRATION m1juin65wriqmb4vwg23fiyajjxlzj2jyjv5qp36uxenit5y63g2iq
ONTO m1xvu7o4z5f5xfwuun2vee2cryvvzh5lfilwgkulmqpifo5m3dnd6a
{
# ...
};
After we apply the data migration we should be able to see the added characters:
db>
select Character {name};
{
default::Character {name: 'Alice'},
default::Character {name: 'Billie'},
default::Character {name: 'Cameron'},
default::Character {name: 'Dana'},
}
Let’s add a character class
represented by a new type to our schema and data. Unlike in this scenario, we will add the required link class
right away, without any intermediate properties. So we end up with a schema like this:
type CharacterClass {
required property name -> str;
multi property skills -> str;
}
type Character {
required property name -> str;
required link class -> CharacterClass;
}
We go ahead and try to apply this new schema:
$
edgedb migration create
did you create object type 'default::CharacterClass'? [y,n,l,c,b,s,q,?]
> y
did you create link 'class' of object type 'default::Character'?
[y,n,l,c,b,s,q,?]
> y
Please specify an expression to populate existing objects in order to make
link 'class' of object type 'default::Character' required:
fill_expr>
Uh-oh! Unlike in a situation with a required property, it’s not a good idea to just insert a new CharacterClass
object for every character. So we should abort this migration attempt and rethink our strategy. We need a separate step where the class
link is not required so that we can write some custom queries to handle the character classes:
type CharacterClass {
required property name -> str;
multi property skills -> str;
}
type Character {
required property name -> str;
link class -> CharacterClass;
}
We can now create a migration for our new schema, but we won’t apply it right away:
$
edgedb migration create
did you create object type 'default::CharacterClass'? [y,n,l,c,b,s,q,?]
> y
did you create link 'class' of object type 'default::Character'?
[y,n,l,c,b,s,q,?]
> y
Created ./dbschema/migrations/00003.edgeql, id:
m1jie3xamsm2b7ygqccwfh2degdi45oc7mwuyzjkanh2qwgiqvi2ya
We don’t need to create a blank migration to add data, we can add our modifications into the migration that adds the class
link directly. Doing this makes sense when the schema changes seem to require the data migration and the two types of changes logically go together. We will need to create some CharacterClass
objects as well as update the class
link on existing Character
objects:
CREATE MIGRATION m1jie3xamsm2b7ygqccwfh2degdi45oc7mwuyzjkanh2qwgiqvi2ya
ONTO m1juin65wriqmb4vwg23fiyajjxlzj2jyjv5qp36uxenit5y63g2iq
{
CREATE TYPE default::CharacterClass {
CREATE REQUIRED PROPERTY name -> std::str;
CREATE MULTI PROPERTY skills -> std::str;
};
ALTER TYPE default::Character {
CREATE LINK class -> default::CharacterClass;
};
insert default::CharacterClass {
name := 'Warrior',
skills := {'punch', 'kick', 'run', 'jump'},
};
insert default::CharacterClass {
name := 'Scholar',
skills := {'read', 'write', 'analyze', 'refine'},
};
insert default::CharacterClass {
name := 'Rogue',
skills := {'impress', 'sing', 'steal', 'run', 'jump'},
};
# All warriors
update default::Character
filter .name in {'Alice'}
set {
class := assert_single((
select default::CharacterClass
filter .name = 'Warrior'
)),
};
# All scholars
update default::Character
filter .name in {'Billie'}
set {
class := assert_single((
select default::CharacterClass
filter .name = 'Scholar'
)),
};
# All rogues
update default::Character
filter .name in {'Cameron', 'Dana'}
set {
class := assert_single((
select default::CharacterClass
filter .name = 'Rogue'
)),
};
};
In a real game we might have a lot more characters and so a good way to update them all is to update characters of the same class in bulk.
Just like before we’ll be reminded to fix the migration name since we’ve altered the migration file. After fixing the migration hash we can apply it. Now all our characters should have been assigned their classes:
db>
...
...
...
...
...
select Character {
name,
class: {
name
}
};
{
default::Character {
name: 'Alice',
class: default::CharacterClass {name: 'Warrior'},
},
default::Character {
name: 'Billie',
class: default::CharacterClass {name: 'Scholar'},
},
default::Character {
name: 'Cameron',
class: default::CharacterClass {name: 'Rogue'},
},
default::Character {
name: 'Dana',
class: default::CharacterClass {name: 'Rogue'},
},
}
We’re finally ready to make the class
link required. We update the schema:
type CharacterClass {
required property name -> str;
multi property skills -> str;
}
type Character {
required property name -> str;
required link class -> CharacterClass;
}
And we perform our final migration:
$
edgedb migration create
did you make link 'class' of object type 'default::Character' required?
[y,n,l,c,b,s,q,?]
> y
Please specify an expression to populate existing objects in order to
make link 'class' of object type 'default::Character' required:
fill_expr> assert_exists(.class)
Created ./dbschema/migrations/00004.edgeql, id:
m14yblybdo77c7bjtm6nugiy5cs6pl6rnuzo5b27gamy4zhuwjifia
The migration system doesn’t know that we’ve already assigned class
values to all the Character
objects, so it still asks us for an expression to be used in case any of the objects need it. We can use assert_exists(.class)
here as a way of being explicit about the fact that we expect the values to already be present. Missing values would have caused an error even without the assert_exists
wrapper, but being explicit may help us capture the intent and make debugging a little easier if anyone runs into a problem at this step.
In fact, before applying this migration, let’s actually add a new Character
to see what happens:
db>
insert Character {name := 'Eric'};
{
default::Character {
id: 9f4ac7a8-ac38-11ec-b076-afefd12d7e66,
},
}
Our attempt at migrating fails as we expected:
$
edgedb migrate
edgedb error: MissingRequiredError: missing value for required link
'class' of object type 'default::Character'
Detail: Failing object id is 'ee604992-c1b1-11ec-ad59-4f878963769f'.
After removing the bugged Character
, we can migrate without any problems:
$
edgedb migrate
Applied m14yblybdo77c7bjtm6nugiy5cs6pl6rnuzo5b27gamy4zhuwjifia
(00004.edgeql)