• Start

Computed Data

Closures

Anonymous functions in SurrealQL, colloquially known as closures.

Closures are small inline functions. They are generally used in two ways:

  • By binding them with LET as a parameter for reuse,

  • Passed directly into methods like .map(), .filter(), or .chain().

Parameters passed into closures are contained within ||, after which the body of the function is written. They are not a replacement for DEFINE FUNCTION when you need a named, reusable routine with permissions.

-- Define an anonymous function that doubles a number
LET $double = |$n: number| $n * 2;
RETURN $double(2); -- Returns 4

-- Define a function that concatenates two strings
LET $concat = |$a: string, $b: string| $a + $b;
RETURN $concat("Hello, ", "World!"); -- Returns "Hello, World!"

Like regular functions, you can also enforce type constraints within your functions to prevent type mismatches.

-- Define a function that greets a person, returning a string
LET $greet = |$name: string| -> string { "Hello, " + $name + "!" };
RETURN $greet("Alice"); -- Returns "Hello, Alice!"

-- Define a function with a return type
LET $to_upper = |$text: string| -> string { string::uppercase($text) };
RETURN $to_upper("hello"); -- Returns "HELLO"
RETURN $to_upper(123); -- Error: type mismatch

-- Define a function that accepts only numbers
LET $square = |$num: number| $num * $num;
RETURN $square(4); -- Returns 16
RETURN $square("4"); -- Error: type mismatch

Many of SurrealDB's functions require a closure to be passed in, making it easy to use complex logic on a value or the elements of an array.

The chain function which performs an operation on a value before passing it on:

"Two"
.replace("Two", "2")
.chain(|$num| <number>$num * 1000);

Response

2000

We can see that the input to the .chain() method is indeed a closure by creating our own that is assigned to a parameter. This closure can be passed into .chain(), returning the same output as above.

LET $my_func = |$num| <number>$num * 1000;

"Two"
.replace("Two", "2")
.chain($my_func);

The following example shows a chain of array functions used to remove useless data, followed by a check to see if all items in the array match a certain condition, and then a cast into another type. The array::filter call in the middle ensures that the string::len function that follows is being called on string values.

[NONE, NONE, "good data", "Also good", "important", NULL]
.filter(|$v| $v.is_string())
.all(|$s| $s.len() > 5)
.chain(|$v| <string>$v);

Response

'true'

Another aspect of closures is that they are able to capture parameters in their environment. This is in fact where the name "closure" comes from, as they are anonymous functions that are able to "enclose" such parameters without needing to pass them through the || parallel bars that hold its input arguments.

LET $okay_nums = [1,2,3];

[1,5,6,7,0].filter(|$n| $n IN $okay_nums);

Closures work inside a read-only context, and cannot be used to modify database resources.

-- 1. Create a test table and function
DEFINE TABLE test_table SCHEMAFULL;
DEFINE FIELD name ON test_table TYPE string;

DEFINE FUNCTION fn::test_create($name: string) -> object {
CREATE test_table CONTENT { name: $name };
{ created: true, name: $name };
};

-- 2. Call the function directly - works
fn::test_create("direct_call");

-- 3. Call the function inside .map() - fails
LET $names = ["Alice", "Bob", "Charlie"];
$names.map(|$n| fn::test_create($n));
-- Error: "Couldn't write to a read only transaction"

In many cases, a closure can be substituted by another operation such as a FOR loop or a regular SELECT statement.

DEFINE TABLE test_table SCHEMAFULL;
DEFINE FIELD name ON test_table TYPE string;

DEFINE FUNCTION fn::test_create($name: string) -> object {
CREATE test_table CONTENT { name: $name };
{ created: true, name: $name };
};

-- Function to create a record called for each name
SELECT VALUE fn::test_create($this) FROM ["Alice", "Bob", "Charlie"];

These anonymous functions provide a flexible way to define small, reusable pieces of logic that can be used throughout your queries. By leveraging them, you can write more modular and maintainable SurrealQL code.

Was this page helpful?