Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Record references #1089

Open
wants to merge 16 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/content/doc-surrealdb/integration/rpc.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
sidebar_position: 4
sidebar_label: RPC Protocol
title: RPC Protocol | Integration
description: The RPC protocol allows for easy bi-directional communication with SurrealDB.
description: The RPC protocol allows for easy bidirectional communication with SurrealDB.
---

import Since from '@components/shared/Since.astro'
Expand Down
2 changes: 1 addition & 1 deletion src/content/doc-surrealdb/introduction/mongo.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ For more in-depth explanations of SurrealDB concepts, see the [concepts page](/d
reference and embedding
</td>
<td colspan="2" scope="row" data-label="SurrealDB">
record links, embedding and graph relations
record links, embedding and graph relations
</td>
</tr>
</tbody>
Expand Down
2 changes: 1 addition & 1 deletion src/content/doc-surrealdb/introduction/sql.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ For more in-depth explanations of SurrealDB concepts, see the [concepts page](/d
join
</td>
<td colspan="2" scope="row" data-label="SurrealDB">
record links, embedding and graph relations
record links, embedding and graph relations
</td>
</tr>
</tbody>
Expand Down
302 changes: 295 additions & 7 deletions src/content/doc-surrealdb/reference-guide/graph_relations.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ The first item to take into account when using graph relations is whether they a

SurrealDB has two main ways to create relations between one record and another: record links, and graph relations.

A record link is a simple pointer from one record to another, a link that exists any time a record holds the record ID of another record. Record links are the most efficient method because record IDs are direct pointers to the data of a record, and do not require a table scan.
A record link is a simple pointer from one record to another, a link that exists any time a record holds the record ID of another record. Record links are extremely efficient because record IDs are direct pointers to the data of a record, and do not require a table scan.

Take the following example that creates one `user` who has written two `comment`s.

Expand All @@ -32,7 +32,7 @@ UPDATE $new_user SET comments += (CREATE ONLY comment SET
.id;
```

Querying a record link is easy as the link is unidirectional with nothing in between. In this case, the linked comments are simply a field on a `user` record and accessing them is as simple as any other field on a `user` record.
Querying a record link is easy as the link by default is unidirectional with nothing in between. In this case, the linked comments are simply a field on a `user` record and accessing them is as simple as any other field on a `user` record.

```surql
SELECT
Expand All @@ -59,7 +59,7 @@ FROM user;
]
```

The unidirectionality of a record link is also a limitation, because the only way to query in the other direction is by using a subquery. With some knowledge of SurrealQL this is certainly doable, but a case like this is an indication that a graph link may be the better solution.
Until SurrealDB version 2.2.0, record links were strictly unidirectional. The only way to query in the other direction was by using a subquery, which made graph edges the only easy option for bidirectional links.

```surql
SELECT
Expand All @@ -68,6 +68,13 @@ SELECT
-- for the id of the current comment
(SELECT id, name FROM user WHERE $parent.id IN comments) AS author
FROM comment;

-- Equivalent graph query is much easier
-- to read and write
SELECT
*,
<-wrote<-author
FROM comment;
```

```surql
Expand Down Expand Up @@ -97,7 +104,63 @@ FROM comment;
]
```

The other limitation is that there is no metadata about the context in which the comment was created. Take the following metadata for instance which contains information about a user's current location, operating system, and mood. Where does this data belong?
Since version 2.2.0, a record link can be bidirectional by [defining a field](/docs/surrealql/statements/define/field) with the `REFERENCE` clause, allowing referenced records to define a field or use the `.refs()` method to track incoming references.

```surql
DEFINE FIELD comments ON user TYPE option<array<record<comment>>> REFERENCE;
DEFINE FIELD author ON comment TYPE references;

LET $new_user = CREATE ONLY user SET name = "User McUserson";
-- Create a new comment, use the output to update the user
UPDATE $new_user SET comments += (CREATE ONLY comment:one SET
text = "I learned something new!",
created_at = time::now())
.id;
UPDATE $new_user SET comments += (CREATE ONLY comment:two SET
text = "I don't get it, can you explain?",
created_at = time::now())
.id;

-- 'author' field is populated with the 'user' who wrote the comment
SELECT * FROM ONLY comment:one;
-- Or use .refs() to grab the references
comment:one.refs();
```

```surql title="Output"
-------- Query --------

{
author: [
user:ie8yc8woe0rwo5cgln57
],
created_at: d'2024-12-31T04:51:47.504Z',
id: comment:one,
text: 'I learned something new!'
}

-------- Query --------

[
user:ie8yc8woe0rwo5cgln57
]
```

If your use case involves bidirectional links, consider the following items to make a decision.

Record links are preferred if:

* Performance is of the utmost importance.
* You don't need to put complex queries together.
* You want to specify in the schema what behaviour should take place when a linked record is deleted (cascade delete, refuse to delete, ignore, etc.).

Graph links are preferred if:

* You want to quickly create links without touching the database schema, or among multiple record types. For example, a single `RELATE person:one->wrote->[blog:one, book:one, comment:one]` is enough to create links between a `person` and three other record types, whereas using record links may be a more involved process involving several `DEFINE FIELD` statements.
* You want to put together complex queries that take advantage of SurrealQL's expressive arrow syntax, like `->wrote->comment<-wrote<-person->wrote->comment FROM person`.
* You want to visualize your schema using Surrealist's designer view.

Finally, graph links are not just preferred but almost certainly necessary if you need to keep metadata about the context in which a link is created. Take the following metadata for the examples above involving a user and its comments which contains information about a user's current location, operating system, and mood. Where does this data belong?

```surql
{
Expand All @@ -107,7 +170,208 @@ The other limitation is that there is no metadata about the context in which the
}
```

This metadata isn't information about the user as a whole, nor the comment itself. It's information about the moment in time in which the `user` and `comment` were linked, and thus is best stored in a separate table. If this sort of metadata is necessary, then a graph table is the ideal solution.
This metadata isn't information about the user as a whole, nor the comment itself. It's information about the moment in time in which the `user` and `comment` were linked, and thus is best stored in a separate table.

Or you might have some information about the link itself, which would also belong nowhere else but inside a graph table.

```surql
{
friends_since: d'2024-12-31T06:43:21.981Z',
friendship_strength: 0.4
}
```

Graph links also excel when it comes to weighting relations. This can be done through a field on the graph table...

```surql
-- Create 4 'npc' records
CREATE |npc:1..4|;

FOR $npc IN SELECT * FROM npc {
-- Give each npc 20 random interactions
FOR $_ IN 0..20 {
-- Looks for a random NPC, use array::complement to filter out self
LET $counterpart = rand::enum(array::complement((SELECT * FROM npc), [$npc]));
-- See if they have a relation yet
LET $existing = SELECT * FROM knows WHERE in = $npc.id AND out = $counterpart.id;
-- If relation exists, increase 'greeted' by one
IF !!$existing {
UPDATE $existing SET greeted += 1;
-- Otherwise create the relation and set 'greeted' to 1
} ELSE {
RELATE $npc->knows->$counterpart SET greeted = 1;
}
};
};

SELECT
id,
->knows.{ like_strength: greeted, with: out } AS relations
FROM npc;
```

```surql title="Which NPC each NPC likes the most"
[
{
id: npc:1,
relations: [
{
like_strength: 8,
with: npc:3
},
{
like_strength: 8,
with: npc:4
},
{
like_strength: 4,
with: npc:2
}
]
},
{
id: npc:2,
relations: [
{
like_strength: 10,
with: npc:1
},
{
like_strength: 4,
with: npc:3
},
{
like_strength: 6,
with: npc:4
}
]
},
{
id: npc:3,
relations: [
{
like_strength: 6,
with: npc:2
},
{
like_strength: 3,
with: npc:4
},
{
like_strength: 11,
with: npc:1
}
]
},
{
id: npc:4,
relations: [
{
like_strength: 7,
with: npc:1
},
{
like_strength: 6,
with: npc:3
},
{
like_strength: 7,
with: npc:2
}
]
}
]
```

...or through counting the number of edges.

```surql
-- Create 4 'npc' records
CREATE |npc:1..4|;

FOR $npc IN SELECT * FROM npc {
-- Give each npc 20 random interactions
FOR $_ IN 0..20 {
-- Looks for a random NPC, use array::complement to filter out self
LET $counterpart = rand::enum(array::complement((SELECT * FROM npc), [$npc]));
RELATE $npc->greeted->$counterpart;
};
};

SELECT
count() AS like_strength,
in AS npc,
out AS counterpart
FROM greeted
GROUP BY npc, counterpart;
```

```surql title="Which NPC each NPC likes the most"
[
{
counterpart: npc:2,
like_strength: 6,
npc: npc:1
},
{
counterpart: npc:3,
like_strength: 9,
npc: npc:1
},
{
counterpart: npc:4,
like_strength: 5,
npc: npc:1
},
{
counterpart: npc:1,
like_strength: 9,
npc: npc:2
},
{
counterpart: npc:3,
like_strength: 6,
npc: npc:2
},
{
counterpart: npc:4,
like_strength: 5,
npc: npc:2
},
{
counterpart: npc:1,
like_strength: 10,
npc: npc:3
},
{
counterpart: npc:2,
like_strength: 7,
npc: npc:3
},
{
counterpart: npc:4,
like_strength: 3,
npc: npc:3
},
{
counterpart: npc:1,
like_strength: 6,
npc: npc:4
},
{
counterpart: npc:2,
like_strength: 4,
npc: npc:4
},
{
counterpart: npc:3,
like_strength: 10,
npc: npc:4
}
]
```

If this sort of metadata or weighting is necessary, then a graph table is the ideal solution.

## Creating a graph relation

Expand Down Expand Up @@ -247,7 +511,8 @@ RELATE person:two->friends_with->person:one;

-------- Query --------

"Database index `only_one_friendship` already contains '[person:one, person:two]', with record `friends_with:dblidwpc44qqz5bvioiu`"
"Database index `only_one_friendship` already contains '[person:one, person:two]',
with record `friends_with:dblidwpc44qqz5bvioiu`"
```

### Querying a graph relation between equals
Expand Down Expand Up @@ -556,4 +821,27 @@ While developed for graph relations in particular, this path can be used in any
For more details on SurrealDB's recursive syntax, see the following pages:

* [Idioms: recursive paths](/docs/surrealql/datamodel/idioms#recursive-paths)
* [Chapter 8 of Aeon's Surreal Renaissance](/learn/book/chapter-08#longer-relational-queries)
* [Chapter 8 of Aeon's Surreal Renaissance](/learn/book/chapter-08#longer-relational-queries)

### When links are deleted

As mentioned above, record links since version 2.2.0 have the ability to specify what behaviour should take place when a referencing link is deleted. Graph links have a simpler behaviour in which they will be deleted if at least of the linked records is deleted.

```surql
-- likes record created without problems
RELATE person:one->likes->person:two;
CREATE person:one, person:two;
DELETE person:one;
-- 'likes' record is now gone
SELECT * FROM likes;
```

A record link allows for more complex behaviour such as the following in which a linked record is removed from the `comments` field if it is deleted, but also adds the record ID to a field called `deleted_comments` for record keeping. For more information on these `ON DELETE` clauses, see the [page on record references](/docs/surrealql/datamodel/references/).

```surql
DEFINE FIELD comments ON person TYPE option<array<record<comment>>> REFERENCE ON DELETE THEN {
UPDATE $this SET
deleted_comments += $reference,
comments -= $reference;
};
```
4 changes: 2 additions & 2 deletions src/content/doc-surrealql/datamodel/records.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -88,9 +88,9 @@ SELECT friends.friends.friends.name FROM person:tobie;

## Next steps

You've now seen how to create records using randomly generated ids, or specific record ids. This is just the beginning! The power and flexibility which is possible with the remote record fetching functionality within queries opens up a whole new set of possibilities for storing and querying traditional data, and connected datasets. Take a look at the next chapter to understand how futures can be used to dynamically compute values only when retrieved.
You've now seen how to create records using randomly generated ids, or specific record ids. This is just the beginning! The power and flexibility which is possible with the remote record fetching functionality within queries opens up a whole new set of possibilities for storing and querying traditional data, and connected datasets. The next page follows up with a feature available in SurrealDB since version 2.2.0: the ability to use record links in a bidirectional manner thanks to reference tracking.

Also checkout this explainer video on using record links in SurrealDB:
Also check out this explainer video on using record links in SurrealDB:

<div class="video-container">
<iframe width="560" height="315" src="https://www.youtube.com/embed/TyX45cyZ-W0?si=S9M59afDEiqxeC5d" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
Expand Down
Loading
Loading