

Time elapsed: ???
It has been a long journey. 55 years ago a mere librarian, you now stand on the verge of restoring civilisation...and before a massive pile of notes on your desk. There are books somewhere underneath them, and you've scribbled on those too. Your habit of writing down your thoughts has helped though, especially in figuring out how to read the language of the past, a distant ancestor of modern English.
Many of the inventions of the past have been rediscovered by now: the airplane, the personal computer, the phone...and many types of weaponry that still make you uncomfortable.
You have been in talks with the governments of the world to recreate the internet. They have benefitted all this time from your centralised database, but are feeling its limits. Plus, it is just a matter of time. If they create the internet themselves, they will certainly dictate the rules in their favour. You will have to recreate the internet before they do if you want knowledge to continue to be free. You start writing down a plan for the next meeting with them in a handwriting so messy that only you can read it.
Suddenly, you notice Landevin out of the corner of your eye, holding the grey book in his hand. He must have entered the room while you were sketching out your plan for the upcoming meeting.
"I've solved the formula!"
You detect a note of fear in his voice and forget all about the upcoming meeting. You've never once seen Landevin afraid. What is the matter?
These Aeons sure are persistent! Persistence seems to be a value that they all share. Let's hope things turn out for the better this time.
And since we have another chapter to work with, let's use it to think about other tables in our movie database and the relations between them.
We now have a large number of movie records that are in a format that lets us use SurrealDB's strengths when querying them, along with a strict schema for the single movie table.
We haven't used all of the relevant naive_movie data yet though, because there is some data that is best used in the creation of separate tables instead of putting everything into our movie table. Here is a sample of the fields we haven't made use of yet.
This original naive_movie data has separate fields for directors, actors, and writers, but any single person can be a director, writer, or actor, or some combination of the three. Here's how we can represent this data:
Define a person table with two fields: name (a string), and roles (an array of strings).
Instead of just CREATEing a movie, we'll use LET $movie = CREATE ONLY... to grab the output of CREATE so that it is ready to use for a later RELATE statement to join the movie to the people involved in creating it.
After the CREATE statement for each movie, pull out each name from the naive_movie's Actors, Director, and Writer fields.
There might already be a person record that matches the name. If it doesn't exist, we can just create it and add the role.
But if the person record already exists, we will have to see if the current role is already inside the roles field, because we don't want to add a duplicate value (i.e. we don't want roles to have values like ["actor", "actor", "actor", "director"]). If not, then add it.
Let's start putting the queries together to create and modify the person records, starting with the actors in each movie.
After the CREATE ONLY movie statement, we still have access to the $data parameter that contains the original naive_movie data where the actors' names are. We can split this string into individual actor names, and then create a variable called $actor that will be the result of a query for a person of that name.
At this point, $actor is going to either be a single person record, or NONE. We can now shadow the $actor variable through an IF ELSE statement so that it is guaranteed to not be none. If $actor is NONE, we will create it and add the string "actor" to its roles. And if it is not NONE, then we will add "actor" to its roles, but only if it doesn't include "actor" already.
After that, we just have to relate them. We can add a RELATE statement on the last line, where we are sure that we have a $movie record and an $actor record, neither of which are NONE. We'll call this relation starred_in.
Here is the whole block of code:
There are many other ways to achieve the same thing, so feel free to experiment if you want to try out a query that fits your own style. For example, instead of checking whether the roles field already contains "actor", you could use the function array::append() to add "actor" to the roles array and then wrap this with array::distinct() which would remove any duplicates.
The same block of code can be used for the directors ($data.Director) and writers ($data.Writer), with relation tables directed and wrote:
At the end, we can use almost the same block of code to create some country records that have a name and which are joined to movies by a has_movie table.
And before we put those relations in place, let's give each of these tables a definition to ensure that they can't be used for any other purposes. Note that the roles field for the person table, while an array<string>, in practice is almost an option<array<string>> because we can pass in an empty array for any person that doesn't have a job yet. If we wanted to ensure that each person definitely has a role, we could add an assertion, such as ASSERT $value IS NOT NONE or ASSERT !!$value.
All these definitions now make for a pretty nice visual on Surrealist's Designer view, so be sure to give it a look! The schema makes it clear that there is a relation between all of the tables except the original naive_movie data, which is floating about on its own - as it should be.
With these relations set up, we can have some fun with graph queries! Since you can never get too much practice with graph queries, let's try putting a few together. To give you the chance to try them out yourself first, so let's start out by saying up front what each query should return.
How would you select...
Every person's name and the titles of the movies that they starred in?
Every movie's title and the names of the people that starred in it?
The average rating of movies for people who worked on them?
Country names, the titles of movies filmed in them, and their total box office value?
Every person's name and the countries of the films they have been involved in?
Every person's name, the countries and names of their films, ordered by the number of films they were in?
Now let's see the answers!
We can start FROM person, grab their name, and then ->starred_in->movie to get to their movies. After that, just add .title to the end.
First we will start with FROM movie. And since we want the movies that have been starred_in by somebody, we'll turn the arrows around.
This one is a bit tricky, because people can be involved in movies in one of three ways: acting, writing, and directing. Still, a path like ->starred_in->movie simply leads to an array of movie records, so we can work from there.
To start, this query will show every movie that a person has been involved with.
There is still some duplication though, such as when James Cameron was both the writer and director for the Terminator movies. This is easy to see if we show the title field for each movie.
So we'll want to add an array::distinct() to remove those. After that, we can use the . operator to access the average_rating field, and then just math::mean() it.
And while we are at it, let's ORDER BY average_rating DESC to see who has the highest score!
And if you wanted to make the results a bit more fair, we could add a line to limit the query to people who have been involved in at least two movies.
This one is also a bit tricky, as a few movies in the database don't have a value for box_office. You can see the result in this query which shows a NONE every once in a while among the box office numbers.
And having even a single NONE value would cause the math::sum() function to return an error, as it expects an array<number> as its input.
But we can add a WHERE clause to only return movies where box_office exists. Let's try it with the !! syntax this time. We will also order by box_office and add ASC this time to see who has the lowest box office values of all amongst these movies.
The countries with the lowest box office values include one which doesn't even exist anymore!
Good work! The next two queries are somewhat harder so let's take a quick break to see how Aeon and Landevin are doing this time around.
You and Landevin did nothing but read and reread the formula this afternoon. But the conclusion is inescapable. You finally open your mouth to talk.
"So we moved too fast?"
Landevin: "Yes. We worked so hard on restoring the past that we didn't give people enough time to grow along with it."
You: "Like people who win the jackpot in a lottery, spend it all in a month and end up worse than before?"
Landevin: "Something like that, yes. We've given them knowledge and power that they had no time to prepare for. They could use it to destroy themselves, or just become addicted to the entertainment they provide and forget what it means to be human. Or something else...all we know is that humanity is doomed, and that it was about 10 years ago that it reached this point. If only I had only figured out the formula before then..."
You: "But it's not too late, because we still hold most of the restored knowledge. We need to delete it, now. And - "
Landevin: "And what?"
You: "I can't shake the feeling that we're not the first people to try this. This tunnel has always had items that don't match with the futuristic technology, like the paintings on the wall and the heavy telephone on the desk. 21st century people never used telephones like that. It must have been rediscovered."
Landevin: "You might be right. I think the ancients overestimated us when they put this book together."
You: "They sure did. Every time we find this place, we notice the shiny toys first and forget about what matters. Until it's too late."
Landevin: "Well, if we weren't the first, then maybe we won't be the last. And we have no time to clean up your notes either. Maybe the next people that find this place will benefit."
You chuckle. "If they can read them, that is. I can barely make them out myself."
Looks like no luck again this time for Aeon and Landevin. Let's get back to the last two graph queries before we move on to the next section.
Since there is a direct link from movie to person and movie to country, we will have to go from person to movie and then to country to get the countries that a person has been involved in.
First, let's look at the countries of the movies that people have starred_in.
We'll then use the same logic to add the results from wrote and directed as well.
This one is a bit tricky! The easiest way to make it work is by using a subquery that feeds into a main query. Take the following query for example which returns the names of the movies that someone has starred in, along with their count. It's close, but not quite what we need.
The problem is that num_movies will end up as 0 because movie_names has not yet been computed at the time that count(movie_names) is called.
To solve this, we can first put a query together that goes as far as all the movies that a person has been involved with. That can then be used to branch off into other areas: the names of the films, their number, and their countries.
Let's start with the subquery first.
With this movies field ready to be pulled from, we can now count them, view their titles, and use the following line to see which countries they were filmed in.
And since we now have a num_movies field that holds the number of movies per person, that allows us to add an ORDER BY num_movies DESC at the end.
This gives us Christopher Nolan in first with 14 movies, followed by Charlie Chaplin and Stanley Kubrick in second place with 12 films each, and many more thereafter.
Now it's time to start thinking about how people might actually use this database.
Let's imagine that our movie database has a number of users, many of which are allowed to delete movies. Sometimes a deletion makes sense, like after the following query that creates a new movie with the title "TEST TEST TEST".
To make the query as short as possible (because there are a lot of required fields for any movie record), we'll grab a single movie record and use its content to create a new movie with a different title.
This one should definitely be deleted, but what about movies that don't deserve to be removed forever? If a user can delete the movie called "TEST TEST TEST", it can also delete any other movies as well.
To ensure that good data doesn't just disappear forever, we can define an event that keeps an eye out for DELETE events for the movie table, and sticks the data inside a table called deleted_movie just in case.
With this event defined, we can now see the data inside deleted_movie after the movie called "TEST TEST TEST" is removed.
The same movie data will now show up, except that its id will be something like deleted_movie:0bz6th2env33sbfvv2kt.
Since the deleted_movie table is automatically generated, we don't need to think about defining a strict schema for it. And it will default to PERMISSIONS NONE so that only users with the correct level of permissions can modify them.
We added some assertions to our database in the last chapter to make sure that a movie's genres or rating could only be one of a few values, such as 'Biography', 'Family', and 'PG-13':
This works great, but we could improve it a little. If we think about it, genres and ratings are values that are relevant to a lot of other sections of the database, not necessarily just the genres and rated fields of the movie table.
For example, a user who tries to enter a movie might appreciate seeing a list of the possible genres, which would not be available if they are only used as an assertion for a field. So let's move these out into their own params that we will call $GENRES and $RATINGS.
The assertions in the DEFINE FIELD statements for genres and rated will now pull from these global parameters.
Having these global parameters is already giving us new ideas for how to use them, such as a function that returns a movie from a random genre and rating.
Much of the time the function will happen upon a combination that returns nothing, but that is because our database isn't very large yet. But a combination like 'Adventure' and 'R' will show a good number of movies.
The output will look something like this.
We are once again short on room and will have to pick up in the next chapter - assuming that this third Aeon does the same thing as the last two and that another Aeon will try in another few centuries. Let's find out if that is the case.
You informed the world's governments that your database would be unavailable for half a day while "major improvements" were made. 12 hours should be enough to delete the database, shut the security door, and get on the airplane waiting to take you to a place where nobody has ever heard of you. In any case, the world will soon be too busy with other matters to care about where you disappeared to.
A sense of panic sets in as you turn to leave your office. You grab wildly at a few of your notes, looking for something, anything. You need more time! What if you've misunderstood the formula? It feels wrong to just react to it like this. What if...
But it's too late. You need to give humanity another chance to start again.
Inside the control room, you set the security door to close for the maximum duration - one "age" - and chuckle bitterly as you remember that day half a century ago when you thought you would never need to use it. A door that locks for a few centuries, but never longer...just enough time to ensure that a new age has begun. The ancients certainly chose the name well. Perhaps the people of another age will find this place again once the door reopens and do a better job next time. But for now, you and your team have a plane to catch.
