Skip to content

Commit

Permalink
Record references (#1089)
Browse files Browse the repository at this point in the history
Co-authored-by: ekwuno <[email protected]>
  • Loading branch information
Dhghomon and Ekwuno authored Jan 16, 2025
1 parent 6699de8 commit c4664b0
Show file tree
Hide file tree
Showing 17 changed files with 937 additions and 25 deletions.
4 changes: 4 additions & 0 deletions src/content/doc-surrealdb/cli/start.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,10 @@ The start command starts a SurrealDB server in memory, on disk, or in a distribu

</Tabs>

### Capabilities arguments

Capabilities arguments such as `allow-scripting` or `deny-net` can also be passed into the `surreal start` command. These arguments, the order in which they are evaluated, and other notes on security are presented in detail in a [separate page on capabilities](/docs/surrealdb/security/capabilities).

## Positional argument

In the `surreal start` command, the path argument is used to specify the location of the database. If no argument is given, the default of `memory` for non-persistent storage in memory is assumed.
Expand Down
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;
};
```
Loading

0 comments on commit c4664b0

Please sign in to comment.