Skip to content

Statements

RETURN

The RETURN statement returns a value or the result of a query. It also ends the context it stands in, such as a function body, so the code after it there does not run. This page covers the syntax, which constructs a RETURN ends, and how a RETURN behaves in a LET, in a clause and in a transaction.

SurrealQL Syntax
RETURN @value

RETURN is always followed by a value. As every data type in SurrealDB is a type of value, the RETURN statement can return anything from simple values to the result of queries.

-- Return a simple value
RETURN 123;
RETURN "I am a string!";
RETURN {
	prop: "value"
};

-- Return the result of a query
RETURN SELECT * FROM person;
RETURN (CREATE person).id;

Values on their own are treated as if they have an implicit RETURN in front. As such, the following queries return the same output as in the previous example.

123;
"I am a string!";
{
	prop: "value"
};
SELECT * FROM person;
(CREATE person).id;

A RETURN sets the value of a function or closure body and ends the body, so the statements after it do not run.

Function return value
DEFINE FUNCTION fn::person::create($firstname: string, $lastname: string) {
	LET $person = CREATE person CONTENT {
		firstname: $firstname,
		lastname: $lastname,
	};

	-- The RETURN statement will set the return value of the custom
	-- function, and further queries will not be executed.
	RETURN $person.id;

    -- This query will never be executed
    CREATE person SET firstname = "Stephen", lastname = "Strange";
};

fn::person::create("Thanos", "Johnson");
SELECT * FROM person;
Functions
DEFINE FUNCTION fn::round::up($num: number) -> number {
    IF $num % 2 == 0 {
        -- Ends the function
        RETURN $num;
    };

    -- Only runs when the RETURN inside the IF did not run
    RETURN $num + 1;
};

A block or an IF inside the body does not stop a RETURN. The RETURN in the inner block below ends the whole function, so the second RETURN never runs:

DEFINE FUNCTION fn::test() -> int {
    {
        RETURN 1;
    };
    RETURN 2;
};

RETURN fn::test();
Output
1

The same applies to a block that a LET binds inside a function. When $x is greater than 2, the RETURN ends the function with 'big', and the LET binds nothing:

DEFINE FUNCTION fn::g($x: int) -> string {
    LET $y = {
        IF $x > 2 { RETURN 'big' };
        'small'
    };
    RETURN 'after ' + $y;
};

RETURN [fn::g(1), fn::g(3)];
Output
['after small', 'big']

A closure body works the same way:

[1, 2].map(|$x| {
    IF $x > 1 { RETURN 'big' };
    'small'
});
Output
['small', 'big']

A RETURN sets the value of the context it stands in and ends that context. The following constructs are contexts:

ContextWhat the RETURN ends
A statement of a query or of a transactionThe statement, which gives the value as its result
A function or closure bodyThe call, which returns the value
A clause that a statement evaluates once for each record or value: WHERE, projections, ORDER BY, GROUP BY and SPLIT keys, SET, CONTENT and MERGE values, and the RETURN clause of a write, including the clauses of a table viewThe evaluation for that one record or value
Stored code: a field's VALUE, DEFAULT and ASSERT clauses, a permission, and an eventThe evaluation for that one record

Every other construct passes a RETURN outwards to the nearest context around it. These include blocks, IF and FOR, parenthesised statements, operands, array and object elements, function arguments, the value of a LET, the FROM source, LIMIT, START and TIMEOUT, the target of a write, and the records that an INSERT writes.

At the top level of a query, the nearest context is the statement itself. A RETURN inside an operand or a block therefore gives the result of the whole statement, and the expression around it does not finish:

RETURN 1 + (RETURN 2);
//- 2

RETURN [1, (RETURN 2), 3];
//- 2

RETURN { IF true { RETURN 5 }; 6 } + 1;
//- 5

A RETURN only ends its own statement. The statements after it in the query still run, and their results are not changed.

The value of a LET is not a context, so a RETURN inside it ends the LET statement. The statement gives the value of the RETURN as its result, and the LET binds nothing:

LET $x = { IF true { RETURN 1 }; 2 };
//- 1

RETURN $x;
//- NONE

When the RETURN does not run, the LET binds the last value of the block as usual:

LET $x = { IF false { RETURN 1 }; 2 };
//- NONE

RETURN $x;
//- 2

To choose the value that a LET binds, write an IF whose branches are values, with no RETURN in them:

LET $count = 150;
LET $size = IF $count > 100 { 'large' } ELSE { 'small' };

Before SurrealDB 4.0, a LET bound the value of a RETURN inside its value.

A clause that a statement evaluates once for each record is a context of its own. A RETURN there gives the value for that record only, and the statement continues with the next record. In a WHERE clause, the RETURN decides whether the record matches:

SELECT VALUE id FROM [
    { id: 1, a: 0 },
    { id: 2, a: 1 },
    { id: 3, a: 1.5 },
    { id: 4, a: 2 },
    { id: 5, a: 3 }
] WHERE {
    IF a > 2 { RETURN true };
    IF a < 1 { RETURN true };
    false
};
Output
[1, 5]

In a projection, the RETURN gives the value for the record:

SELECT VALUE {
    IF a > 2 { RETURN 'big' };
    'small'
} FROM [{ a: 0 }, { a: 3 }];
Output
['small', 'big']

In a SET clause, the RETURN gives the value of the field. The + 1 after it does not run, so x is 1:

CREATE ts:1 SET x = (RETURN 1) + 1, y = 5;
Output
[{ id: ts:1, x: 1, y: 5 }]

A clause that a statement evaluates only once, such as FROM, LIMIT or START, is not a context, and neither is the target of a write. A RETURN there ends the whole statement before it reads or writes any record. The DELETE below gives person:5 as its result and deletes nothing:

CREATE person:1, person:5 RETURN NONE;
//- []

DELETE (RETURN person:5);
//- person:5

SELECT VALUE id FROM person:5;
//- [person:5]

SELECT * FROM (RETURN 7);
//- 7

Each statement between BEGIN and COMMIT is a context of its own. A RETURN placed directly inside a transaction therefore ends only its own statement: its value is one of the results of the query, and the statements after it still run. This is the case from SurrealDB 4.0. Before 4.0, a RETURN ended the transaction early, and the statements after it did not run.

Transaction return value
BEGIN TRANSACTION;

-- We are executing quite a few queries here
LET $firstname = "John";
LET $lastname = "Doe";

LET $person = CREATE ONLY person CONTENT {
	firstname: $firstname,
	lastname: $lastname,
};

-- The person's ID is the result of this statement
RETURN $person.id;

-- One issue with this approach is that query errors are generic.
-- To get around that, use a block, which is executed as a transaction by itself.

COMMIT TRANSACTION;

Each of the statements below runs, and the two RETURN values are among the results:

Transactions
BEGIN;
-- Runs
RETURN 1;
-- Runs
CREATE a;
-- Runs
RETURN 2;
COMMIT;

A RETURN inside an IF, a block or the value of a LET that stands directly in the transaction also ends only that statement. To end part of a transaction early, put those statements in a block. The RETURN ends the block's statement, and the transaction carries on after it:

Ending a block inside a transaction
BEGIN;
CREATE c:1;
{
    CREATE c:2;
    RETURN "done";
    -- Does not run
    CREATE c:3;
};
-- Runs
CREATE c:4;
COMMIT;

To cancel the whole transaction instead, use THROW.

Was this page helpful?