

Time elapsed: 35y
Your plan to recreate the Minitel is going well, especially in your part of the world where people are used to working with technology. The great powers of Europe wanted the same access, and you were willing to grant it. You put an offer together that provides a number of Minitels for government usage, just as they asked.
However, there were conditions - tough ones. One was that they must allow Landevin to put together a team that can set up Minitels wherever they like. And everyone, even the poorest citizen, must be able to access it for free. Any government that interferes with the team will lose all access to the network.
The last condition was the hardest: allowing Landevin's team to visit anywhere unannounced, including courts and jails. How they hated that one! And they all refused at first, except San Marino. The other countries were shocked, calling it "unworthy of an independent country". Then a few months went by and San Marino began to gain a huge technological advantage over them. They all jumped aboard after that.
You know that they will eventually figure out the technology for themselves, but for the moment there is no danger of this happening. The knowledge is all inside your database, and all they can do is display the results on the screen which they write down on paper for their own use later. You smile as you remember doing the exact same thing so many years ago when the printer first ran out of ink...
A full 35 years have passed since Aeon began the project to restore civilization, and it seems to be getting closer to succeeding. There isn't much time left! In this small amount of time remaining to us, let's focus on advanced queries and any other concepts that we haven't encountered yet over the next two chapters to ensure that we are as fluent in SurrealQL as possible.
DEFINE APIAeon seems to be very interested in keeping access to knowledge as free as possible.
Now, one of the problems that can happen when you make a service free for all to use is that it gets used a lot - so much that the service itself slows down. It would be nice to set some limits on the usage of the database so that you can make sure that free users don't take up all of the bandwidth. This is called rate limiting.
In SurrealDB, this can be achieved by using a statement called DEFINE API. This statement lets you create your own endpoints for users to access. Here is an example of a simple endpoint that returns all of the article records in the database.
The things to note here are:
"/articles" is the name of the endpoint. This will show up at the end of the route /api/namespace/database. For example, if you started an instance with surreal start and used the namespace aeons_namespace and database aeons_database, this API endpoint would be accessible at http://localhost:8000/api/aeons_namespace/aeons_database/articles.
FOR get means a GET request, the simplest kind. With a GET request, the user just requests that the endpoint give it a response and doesn't provide any additional data.
The response contains headers, a status, and a body.
We can test this endpoint out in a number of ways. One is by using a function called api::invoke.
Another way is by using something like curl or Postman or some other API testing tool. Here is how you would
You'll see an output like this that shows that the endpoint is working as expected.
Pretty good so far! But a SELECT * FROM article query could take a long time if there are a lot of article records. How can this be improved?
The first thing we can do is change the get to a post, so that the user can post some info inside an object. This information is available inside a set parameter called $request. With that, we can add a WHERE $request.body.keyword IN text, in which keyword is a field that holds a keyword to search for such as "Greek", or "Kago", or "harvesting". That will cut down on the number of articles returned.
With that, a user can now use the endpoint along with the object {"keyword": "Greek"} that will lead to a SELECT * FROM article WHERE "Greek" IN text query.
The output will look like this. Pretty good so far!
Our API endpoint now returns a generally smaller number of articles, but there's nothing in there to specifically limit the amount of resource time that a user can have. Let's now change the endpoint so that instead of returning the results of a single keyword, it takes an array of keywords and does a SELECT for each one of them.
Since we are making this endpoint for free users, we don't want them to pass in hundreds of keywords per request. Ideally, they should only add a few. But instead of trying to put some logic together based on user type, system load and so on, we can just give each request a maximum timeout.
This can be done by adding a clause called MIDDLEWARE. After the MIDDLEWARE clause you can insert api functions to specify certain behaviour like a maximum body size, timeout, what headers should be added, and so on. We will use just one of them: the 'api::timeout()' function.
The example below will now include 10,000 other articles with random strings to fill up the database a bit and have the operation take longer.
With a 100m timeout, a request like this will work just fine.
But this next unserious query with far too many keywords will not work. Thanks to the middleware, the server simply gives up after 100 milliseconds and tells the user to go check the documentation.
Wonderful! Now how do we force users to use this endpoint instead of signing in and doing their own queries? This is done by using a flag when starting the server called --deny-arbitrary-query. This flag is followed by guest, record, or system, depending on what level you want to set it at. The most common usage is probably --deny-arbitrary-query record, which lets you force record users to use the API endpoints while allowing system users to do whatever they need to do. Here is a simple example of the surreal start command that uses this flag.
Now let's turn our attention back to the query language and see what other tips and tricks we can cover during this chapter.
We learned about bidirectional querying with the <-> operator in Chapter 8, which let us create marriages and friendships that could only be defined once thanks to a unique index on a key which was created by the sorted Record IDs found at the in and out fields.
Here is a similar example to the one we saw in that chapter.
Thanks to this key, we can only RELATE with friends_with once, and the second RELATE statement will fail.
After that, a bit of query magic with the array::complement() function lets us find every friend that a cat has, except for itself.
Now what happens if a dog would like to be friends too? We can easily set up a the relation:
But the <->friends_with<->cat part of the SELECT query above is only looking for cats at the end of the friends_with graph table, instead of both cats and dogs. We can fix this by changing <->cat to <->(cat, dog), and by selecting FROM cat, dog instead of just FROM cat. This will now look for all possible cat or dog friends of each cat and dog in the database.
If you would like to match on any record type, you can use the ? wild card operator instead. Using this, we could see each of a cat's friends, be they cats, dogs, humans, or anything else.
Here is another simpler example of the ? operator in action. Landevin, who is in Europe, has recently seen an interesting comet and has also visited a city known as Lutetia. At the same time, a villager from Lutetia noticed him and wondered just what this suspicious outsider was up to.
We can represent these pieces of data and their relations with some pretty simple queries.
Landevin at this point has three relations in total: he visited somewhere, he saw something, and he was seen. If we want to see them all, we could use the ? operator to ask SurrealDB to return whatever it finds at the end of each graph table related to this person:landevin record.
And for something a little more readable, we can also add .* to display all the fields of these related records. This next query will show all the fields of the records Landevin did something to, followed by all the fields of the records that did something to him.
A WHERE clause can also be added to a relation query. If we only wanted to see records that had a name, we could add a WHERE clause to filter out the records that don't have this information (in this case, just the mysterious comet). That gives us the query below which shows everything that has a relation to Landevin, as long as it has a name.
To finish up this chapter, we will quickly go over some random bits and pieces that are good to know and quick to pick up at this point now that we have become more advanced users of SurrealDB.
One convenient aspect to defining a function is that it can call itself! Let's see what happens if we create a function that simply calls itself over and over again.
Instead of freezing up the database, SurrealDB recognizes that this would lead to too much "computation depth" and simply gives up. As the error message notes, infinite recursion can happen not only inside a function, but also due to subqueries and computed values.
We can create a different function that only calls itself if a random bool is true, which will keep it from being called infinitely. Instead, you should see anywhere from 1 to some other small number of person records created.
For something more useful, here is a demonstration of the Fibonacci sequence as a SurrealQL function.
These keywords were mentioned once: way, way back at the very beginning of the intro of this book. Time to give them some attention!
TEMPFILES is a recent addition to SurrealDB's syntax, and is used for when you need to make queries that are so large that all the memory required ends up giving your computer a hard time. While memory is by default the most performant option, that is of no help when your computer has completely run out of spare memory and needs to start swapping back and forth with whatever spare resources it has.
In this case, you can add the TEMPFILES keyword to the end of a SELECT statement to instruct the computer to process the statement inside temporary files, rather than memory. While much slower than memory in most cases, this may result in a significant speedup for massive queries that would otherwise be a burden on your computer.
Similarly, you can also add TIMEOUT followed by a duration if you want SurrealDB to quit early if a query is taking too long. Note that the actual execution time will be somewhat longer than the timeout, which becomes more noticeable for queries that process massive amounts of data. The output from a query that creates 100000 records and another much larger one that creates half a million records shows the difference:
You can gain some insight into what method SurrealDB is using in your queries by adding the EXPLAIN keyword. Take the following two queries on some simple person records, one of which uses an index, and one that doesn't.
Note that 'Iterate Table' (also known as a table scan) shows up once an index has not been found, which means a scan over every record in the database. As the fallback option, this tends to be less performant than other ways to query your data.
We can demonstrate this by first creating a bunch of person objects with an id going from 1 to 100000. We will then compare two queries, one of which uses the WHERE clause (which will perform a table scan), while the other uses the .. range syntax which will be much faster.
To do a quick comparison, we will create a variable to hold the time before the operation, and then will subtract it from the time after the operation to get the duration. And to ensure that the SELECT statement doesn't clutter up the screen, we'll assign it to a variable called $nothing that we will never read.
Adding the EXPLAIN keyword to the end of these two queries gives the details.
The freeform nature of objects makes them both flexible and also somewhat unpredictable and tough to work with at times. SurrealDB has a number of functions that work on objects that can help make them easier to work with.
Three of them turn an object into an array (or array of arrays), made out of:
its keys, using object::keys()
its values, using object::values()
both keys and values, using object::entries()
Let's give two of these functions a try with some of the possible records for the Minitels in Europe.
SurrealDB has two more object functions: object::len() which returns the number of key-value pairs, and object::from_entries() which lets you build an object from an array of keys and values.
The next chapter will be sort of a continuation of this one, but dipping into even deeper and darker parts of SurrealDB - and even some of its source code!
Take the following data below as an example, which has fields called name, NAME, and Name.
A lot will depend on what other fields are in the data and just how much of a mess it is. But one of SurrealDB's object functions should be able to help in any case. Here is an example of the .keys() function being used to check the spelling of the name field which then allows different records to be created. After this the original planet data could be deleted and replaced with this one.
To get you started, here is how a query using this function would work.
One way to randomize the removal of the items is to use .remove(0) to remove an item from the front if the rand::bool() function returns true, and .remove(-1) to remove an item from the end if the rand::bool() function returns false.
blog_article that is related to a comment and a review...How would you show all the data for the comment and review in a single query?
To get all the comments, we can use the following query.
However, we also want to include review records, which are linked by a table called about. To do that, we can put all these possible names in parentheses.