Reference tracking begins by adding a REFERENCE clause to any DEFINE FIELD statement, as long as the field is a top-level field of type record or array of records.
DEFINEFIELDcomicsONpersonTYPEoption<array<record<comic_book>>>REFERENCE; -- Also works as `option` desugars to this syntax DEFINEFIELDcomicsONpersonTYPEarray<record<comic_book>> | NONEREFERENCE;
-- `comics` field might not be a record, does not work DEFINEFIELDcomicsONpersonTYPEarray<record<comic_book>> | stringREFERENCE; -- Not top-level field, does not work DEFINEFIELDmetadata.comicsONpersonTYPEarray<record<comic_book>>REFERENCE;
This incoming record can then be picked up with the <~ syntax that works in the same way that graph queries do.
DEFINEFIELDcomicsONpersonTYPEoption<array<record<comic_book>>>REFERENCE; CREATEperson:matSET name="Mat", comics=[comic_book:one]; CREATEperson:nynaeveSET name="Nynaeve", comics=[comic_book:one]; CREATEcomic_book:oneSETtitle="Loki, God of Stories";
CREATEperson:one, person:twoSETcomics=[comic_book:one]; CREATEpublisher:oneSETproducts=[comic_book:one, book:one]; CREATEcomic_book:oneSETtitle="Loki, God of Stories"; SELECT * FROMcomic_book;
Output
[ { id: comic_book:one, owners: [ person:one, person:two ], publishers: [ publisher:one ], title: 'Loki, God of Stories' } ]
A computed field that picks up incoming references can be further narrowed down to specify not just the table name, but also the field name of the referencing record. This can be done by enclosing the part after <~ in parentheses, adding the FIELD keyword and naming the field or fields via which incoming references will be shown.
CREATEperson:oneSETcomics=[comic_book:one]; CREATEperson:twoSETborrowed_comics=[comic_book:one]; CREATEcomic_book:oneSETtitle="Loki, God of Stories"; SELECT * FROMcomic_book;
Output
[ { all_readers: [person:one, person:two], borrowed_by: [person:two], id: comic_book:one, owned_by: [person:one], title: 'Loki, God of Stories' } ]
Specifying deletion behaviour
When working with record links, it is very likely that you will want some behaviour to happen when a referencing link is deleted. Take the following example of a person who owns a comic_book, which is later deleted. Despite the deletion, a follow-up SELECT * FROM person still shows the comic book.
A query using INFO FOR TABLE person shows that the actual statement created using REFERENCE does not finish at this point, but includes the clause ON DELETE IGNORE. This is the default behaviour for references.
{ events: {}, fields: { comics: 'DEFINE FIELD comics ON person TYPE none | array<record<comic_book>> REFERENCE ON DELETE IGNORE PERMISSIONS FULL', "comics.*": 'DEFINE FIELD comics.* ON person TYPE record<comic_book> REFERENCE ON DELETE IGNORE PERMISSIONS FULL' }, indexes: {}, lives: {}, tables: {} }
This ON DELETE clause can be modified to have some other behaviour when a reference is deleted.
ON DELETE IGNORE
As shown in the previous section, ON DELETE IGNORE is the default behaviour for references and this clause will be added automatically if not specified. It can be added manually to a statement to hint to others reading the code that this behaviour is desired.
-- Default, behaviour, so identical to: -- DEFINE FIELD friends ON person TYPE option<array<record<person>>> REFERENCE; DEFINEFIELDfriendsONpersonTYPEoption<array<record<person>>>REFERENCEONDELETEIGNORE; DEFINEFIELDfriended_byONpersonCOMPUTED<~person;
As the deletion of person:one is ignored when calculating the friended_by field, it will still show person:one even though the record itself has been deleted.
{ friended_by: [ person:one ], id: person:two }
ON DELETE UNSET
ON DELETE UNSET will unset (remove) any linked records that are deleted. This can be thought of as the opposite of ON DELETE IGNORE.
CREATEperson:one; UPDATEperson:oneSETcomments+= (CREATEONLYcommentSETtext="Estonia is bigger than I expected!").id; -- Give this one a parameter name so it can be deleted later LET$comment = CREATEONLYcommentSETtext="I don't get the joke here?"; UPDATEperson:oneSETcomments+=$comment.id; -- Now delete it DELETE$comment; -- Only one comment shows up for person:one now person:one.comments.*.*;
[ { author: [ person:one ], id: comment:gj1kb2e3tedn7kjcxxja, text: 'Estonia is bigger than I expected!' } ]
ON DELETE CASCADE
The ON DELETE CASCADE will cause a record to be deleted if any record it references is deleted. This is useful for records that should not exist if a record that links to them no longer exists.
CREATEperson:one; CREATEcommentSETauthor=person:one, text="5/10 for this blog post. The problems I have with it are..."; CREATEcommentSETauthor=person:one, text="WOW! I never knew you could cut a rope with an arrow.";
-- Show all the details of comments for 'person:one' person:one.comments.*.*; DELETEperson:one; -- Comments no longer exist SELECT * FROMcomment;
Output
-------- Query --------
[ { author: person:one, id: comment:8msvp0egg8cdlyu4vvn9, text: 'WOW! I never knew you could cut a rope with an arrow.' }, { author: person:one, id: comment:i72qfjy59vbn81hk6lrm, text: '5/10 for this blog post. The problems I have with it are...' } ]
-------- Query --------
[]
-------- Query --------
[]
ON DELETE REJECT
ON DELETE REJECT will outright make it impossible to delete a record that is referenced from somewhere else. For example, consider the case in which a house should not be demolished (deleted) until it has been disconnected from utilities such as gas, water, electricity, and so on. This can be simulated in a schema by adding a REFERENCE ON DELETE REJECT to the utility table, making it impossible for any house to be deleted if they link to it.
Note that an ON DELETE UNSET for a required field is effectively the same as an ON DELETE REJECT. In both of the following two cases, a person that has any referencing comment records will not be able to be deleted.
-- Non-optional field that attempts an UNSET when referencing 'person' is deleted DEFINEFIELDauthorONcommentTYPErecord<person>REFERENCEONDELETEUNSET; LET$person = CREATEONLYperson; CREATEcommentSETtext="Cats are so much better at climbing UP a tree than down! Lol", author=$person.id; DELETEperson;
-- Optional field which rejects the deletion of a referencing 'person' DEFINEFIELDauthorONcommentTYPEoption<record<person>>REFERENCEONDELETEREJECT; LET$person = CREATEONLYperson; CREATEcommentSETtext="Cats are so much better at climbing UP a tree than down! Lol", author=$person.id; DELETEperson;
The error message in these two cases will differ, but the behaviour is the same.
-------- Query --------
"An error occured while updating references for `person:97sfkadd56hqhimbf69m`: Couldn't coerce value for field `author` of `comment:kkigvk5knsoeg53p08n1`: Expected `record<person>` but found `NONE`"
-------- Query --------
'Cannot delete `person:3fm76xztvfab99eq780l` as it is referenced by `comment:ig0ogusbm64cier5ovv9` with an ON DELETE REJECT clause'
ON DELETE THEN
The ON DELETE THEN clause allows for custom logic when a reference is deleted. This clause includes a parameters called $this to refer to the record in question, and $reference for the reference.
In the following example, a person record's comments field will remove any comments when they are deleted, but also add the same comment to a different field called deleted_comments.
CREATEperson:oneSETcomments+= (CREATEONLYcommentSETtext="Estonia is bigger than I expected!").id; LET$comment = CREATEONLYcommentSETtext="I don't get the joke here?"; UPDATEperson:oneSETcomments+=$comment.id; DELETE$comment; SELECT * FROMperson:one;