SurrealDB: The Game-Changing Database You've Been Waiting For

Cyril ScetbonCyril Scetbon
7 min read

What is SurrealDB ?

SurrealDB is a versatile multi-model database that combines the strengths of both relational and graph databases. It allows you to store and query data using traditional table structures as well as graph relations, offering a flexible approach to data modeling. It supports both schemafull and schemaless modes. In this article, we'll explore the basics of working with regular records and graph relations in SurrealDB and demonstrate how to use Record ID and traversals to access fields.

How it started

SurrealDB was initially developed as a compute layer by two brothers, Jaime and Tobie Morgan Hitchcock. It was first built in Golang, then rewritten in Rust to take advantage of generics (not available in Golang at the time), improved memory safety, a stronger type system, and better performance, on top of existing key-value engines as the storage layer (RocksDB, Speedb, and TiKV). It quickly gained attention due to its attractive features and ease of use. Over time, they added more storage layers like Apple FoundationDB, but they decided to become a real database and worked on their own storage layers. IndexDB, SurrealKV, and an in-memory engine were implemented.

Regular Records in SurrealDB

Regular records in SurrealDB are similar to rows in a traditional relational database. Each record is uniquely identified by a Record ID, which acts as a direct pointer to the data, eliminating the need for table scans. This makes querying efficient and straightforward.

Creating Regular Records

To create a record in SurrealDB, you can use the CREATE statement. If you don't specify an ID, SurrealDB will generate one for you.

-- If you want to create a record in table person with name Tobie
CREATE person SET name = 'EdgeBound';

-- But you can also do it using another syntax passing some json properties
CREATE person CONTENT {
  name: 'EdgeBound',
}

This command creates a new record in the person table with a randomly generated ID. But wait, that's not all; you can also use other syntaxes like

-- To create 8 person records with random IDs
CREATE |person:8|;

-- To create 3 person records with IDs 1 to 3
CREATE |person:1..3|;

-- To also create in one statement 6 movie records with IDs 10 to 16
CREATE |person:1..3|, |movie:10..16|;

Accessing Fields Directly

You can access fields of a record directly using its Record ID. This is particularly useful for fetching specific data without scanning the entire table.

-- Select the whole record
SELECT * FROM person:aio58g22n3upq16hsani;

-- Select specific fields
SELECT { name, email } FROM person:aio58g22n3upq16hsani;

-- My preference, starting from the record itself
person:aio58g22n3upq16hsani.{name, email}

Graph Relations in SurrealDB

Graph relations in SurrealDB allow you to model complex relationships between entities using nodes and edges. This is particularly useful for applications that require advanced querying and analysis, such as social networks or recommendation systems.

Creating Graph Relations

Graph relations are created using the RELATE statement, which follows a semantic triple structure: subject → predicate → object.

RELATE person:cyril->wishlist->product:qn55s95fafxza;
-------- Query 1 (12ms) --------
[
    {
        id: wishlist:izi8bql0peg8vnv03y48,
        in: person:cyril,
        out: product:qn55s95fafxza
    }
]

This command creates multiple entries in the wishlist table. SurrealDB doesn’t require you to pre-create person:cyrilor product:qn55s95fafxza, but without them, traversals from these records won’t return results.

SELECT ->wishlist as product FROM person:cyril;
-------- Query 1 (1ms) --------
[]

wishlist:p96u12e8401916mwe56i.out.*;
-------- Query 1 (0ns) --------
NONE

Traversing Graph Relations

SurrealDB allows you to traverse graph relations using arrow syntax (->) or dot notation (in and out). This enables you to access related data efficiently.

DELETE person, product, wishlist;

CREATE person:cyril CONTENT {password: crypto::scrypt::generate("strong password")};
CREATE product:qn55s95fafxza CONTENT {brand: "samsung", sku: 6613500};
RELATE person:cyril->wishlist:["edgebound", "Hanukkah"]->product:qn55s95fafxza;

-- Fetch friends of a person
wishlist:["edgebound", "Hanukkah"].out.*;
-------- Query 1 (0ns) --------
{
    brand: 'samsung',
    id: product:qn55s95fafxza,
    sku: 6613500
}

person:cyril->wishlist->product.*
-------- Query 1 (0ns) --------
[
    {
        brand: 'samsung',
        id: product:qn55s95fafxza,
        sku: 6613500
    }
]

-- Relation can go in the other direction
RELATE person:cyril<-is_reserved_by<-product:qn55s95fafxza;
-------- Query 1 (27ms) --------
[
    {
        id: is_reserved_by:fj1svjg8bb3rkba9r6b4,
        in: product:qn55s95fafxza,
        out: person:cyril
    }
]

Record links in SurrealDB are simple pointers from one record to another. They are efficient because they use Record IDs, which are direct pointers to data.

You can store Record IDs within other records to create links.

CREATE person:edgebound SET name = 'EdgeBound', friends = [person:helen, person:aaron];
-------- Query 1 (3ms) --------
[
    {
        friends: [
            person:helen,
            person:aaron
        ],
        id: person:edgebound,
        name: 'EdgeBound'
    }
]

Again if those records don’t exist SurrealDB won’t yell at you but when you try to access them you might get nothing or None depending on what you attempt to do.

Fetching Remote Records

You can use nested field traversal to fetch properties from linked records.

SELECT friends.name FROM person:edgebound;
-------- Query 1 (0ns) --------
[
    {
        friends: {
            name: [
                NONE,
                NONE
            ]
        }
    }
]

CREATE person:helen CONTENT {name: "Helen"};
CREATE person:aaron CONTENT {name: "Aaron"};
SELECT friends.name FROM person:edgebound;
-------- Query 1 (2ms) --------
[
    {
        id: person:helen,
        name: 'Helen'
    }
]
-------- Query 2 (0ns) --------
[
    {
        id: person:aaron,
        name: 'Aaron'
    }
]
-------- Query 3 (2ms) --------
[
    {
        friends: {
            name: [
                'Helen',
                'Aaron'
            ]
        }
    }
]

-- But wait, you can go even further
CREATE person:mateo CONTENT {name: "Mateo"};
UPSERT person:aaron CONTENT { friends: [person:mateo] };
-- We look for the names of friends of edgebound friends
person:edgebound.friends.friends.name;
-------- Query 1 (1ms) --------
[
    {
        id: person:mateo,
        name: 'Mateo'
    }
]
-------- Query 2 (5ms) --------
[
    {
        friends: [
            person:mateo
        ],
        id: person:aaron
    }
]
-------- Query 3 (3ms) --------
[
    NONE,
    [
        'Mateo'
    ]
]
-- yeah Helen has no friends...

Tell me more !

It supports vector embedding indexing, which means you can use it to do RAG.

-- Add vector embedding data to record content
CREATE article:1 SET embedding = [1, 2, 3, 4];
CREATE article:2 SET embedding = [4, 5, 6, 7];
CREATE article:3 SET embedding = [8, 9, 10, 11];

-- Define a vector embedding index for a field on the article table
DEFINE INDEX mt_obj ON vec FIELDS embedding MTREE DIMENSION 4 DIST EUCLIDEAN;

You can use FOR statements

FOR $person IN (SELECT VALUE id FROM person) {
    CREATE gift CONTENT {
        recipient: $person,
        type: "ticket",
        date: time::now(),
        event: "SurrealDB World",
    };
};

You can navigate through vertices and capture elements dynamically without predefining their exact types. Additionally, you can specify the types of edges you wish to traverse and apply filters.

-- Select all 1st, 2nd, and 3rd level people who this specific person record knows, or likes, as separate outputs
SELECT ->knows->(? AS f1)->knows->(? AS f2)->(knows, likes WHERE influencer = true AS e3)->(? AS f3) FROM person:tobie;

You can use complex IDs and use ranges on them which is perfect for time series data

LET $now = time::now(); -- yep that's a variable

CREATE temperature:['London', $now] CONTENT {
    location: 'London',
    date: time::round($now, 1h),
    temperature: 23.7
}

-- Select all records for a particular location, inclusive
SELECT * FROM temperature:['London', NONE]..=['London', time::now()];

-- Select all temperature records with IDs less than a maximum value
SELECT * FROM temperature:..['London', '2022-08-29T08:09:31'];

You can use GROUP ALL instead of specifying all your selected fields, something you can do on many new databases like the in-process dbs duckDB.

SELECT count(age > 18) FROM user GROUP ALL;

You can store arrays in variables and create relations one to one or many to many, see here we require them to be unique.

LET $from = (SELECT users FROM company:surrealdb);
LET $devs = (SELECT * FROM user WHERE tags CONTAINS 'developer');
RELATE $from->like->$devs UNIQUE SET time.connected = time::now();

And much more... They offer a wide range of functions, letting you store your ML-trained model directly in the database and use it instantly in your queries. You can access the database using a REST API or even a GraphQL API (the database must be fully defined in a schema, and this feature is still experimental and not fully implemented). They have developed an SDK for the most popular languages. The SurrealDB team also wrote a book with 21 chapters to teach you about SurrealDB.

Wanna try it out ?

Download Surrealist, an IDE that lets you start a local memory database or connect to an existing one, whether locally, what they call a sandbox environment, or in the cloud. You'll be impressed by its simplicity and the fact that everything you can do with a storage layer engine, you can also do in a sandbox environment. The only difference is that your data won't persist if it restarts.

Conclusion

SurrealDB stands out as a transformative database solution, offering a unique blend of relational and graph database capabilities. Its innovative use of Record IDs and traversals allows for efficient data access and manipulation, making it a versatile choice for various applications. Whether you're developing a straightforward CRUD application or a complex graph-based system, SurrealDB provides the necessary tools to meet your needs. Thanks to its support for both schemafull and schemaless data modeling, SurrealDB is well-equipped to handle the needs of modern, data-driven applications. It’s a game-changing database worth exploring.

0
Subscribe to my newsletter

Read articles from Cyril Scetbon directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Cyril Scetbon
Cyril Scetbon