• Start

Functions

/

Database functions

Eval

Evaluate a SurrealQL or ISO GQL query string at runtime inside the caller's transaction, gated by dedicated capabilities.

Available since: v3.2.0

The eval::* functions run a query supplied as a string inside the caller's open transaction and session context. They are intended for workloads where the query text is only known at runtime — for example, analytical queries stored in a table, where you want to blend ISO GQL and SurrealQL in one transaction, or to learn SurrealQL or rewrite existing queries from another graph database.

FunctionDescription
eval::gql()Evaluate a nested ISO GQL query and return its result
eval::surql()Evaluate a nested SurrealQL statement and return its value

For how eval::* fits with encoding, parsing, and other representation transforms, see Representations and codecs.

Evaluates an ISO GQL query string. One GQL query may lower to several internal statements. Unlike eval::surql, it is not limited to a single SurrealQL statement.

API DEFINITION

eval::gql(query: string, bindings: option<object>) -> any

eval::gql requires the gql experimental capability (same as POST /gql) and runs on the streaming query engine. Queries that include GQL mutations (INSERT, SET, REMOVE, DELETE) participate in the caller's write transaction — see GQL mutations.

Enable both capabilities on the server, for example:

surreal start --user root --pass secret --allow-experimental gql --allow-eval-query

GQL syntax is Cypher-like: parenthesised node patterns (variable:label), arrow edge patterns -[variable:type]->, a mandatory MATCH clause, and RETURN for projection. See Notable syntax differences from openCypher before pasting Neo4j queries verbatim.

The following queries create a number of person and city records, followed by INSERT RELATION to join them together. The following eval::gql queries assume this seed data when indicating output.

Seed data

CREATE person:1 SET name = 'A', age = 30, active = true, city = 'London';
CREATE person:2 SET name = 'B', age = 20, active = false, city = 'Paris';
CREATE person:3 SET name = 'C', city = 'London';
CREATE city:1 SET name = 'London';
INSERT RELATION INTO knows [
{ id: knows:k12, in: person:1, out: person:2, since: 2021 },
{ id: knows:k21, in: person:2, out: person:1, since: 2018 },
{ id: knows:k23, in: person:2, out: person:3, since: 2020 },
{ id: knows:k1c, in: person:1, out: city:1, since: 2019 },
{ id: knows:k31, in: person:3, out: person:1 }
];
GQL patternMeaning in SurrealDB
(n:person)Rows in table person bound to variable n
-[k:knows]->Directed edges in relation table knows (in / out record IDs)
n.nameField name on the bound record
$minParameter — pass via the optional bindings object

MATCH finds graph patterns; RETURN projects columns (like SELECT).

eval::gql("MATCH (n:person) RETURN n.name AS name ORDER BY name");
-- [{ name: 'A' }, { name: 'B' }, { name: 'C' }]

WHERE filters rows after the pattern matches. Missing properties (person C has no age) are treated as unknown and excluded from comparisons.

eval::gql("MATCH (n:person) WHERE n.age < 25 RETURN n.name AS name ORDER BY name");
-- { name: 'B' }

Chain node and edge patterns to walk the graph. Label both endpoints as :person to ignore the person → city edge.

eval::gql(
"MATCH (a:person)-[k:knows]->(b:person) WHERE k.since > 2020 RETURN a.name, b.name ORDER BY a.name"
);
-- { 'a.name': 'A', 'b.name': 'B' }

OPTIONAL MATCH keeps every anchor row from the preceding MATCH even when the optional pattern misses — similar to a left outer join.

eval::gql(
"MATCH (a:person) OPTIONAL MATCH (a)-[k:knows]->(b:city) RETURN a.name AS name, b.name AS city ORDER BY name"
);
-- [
-- { city: 'London', name: 'A' },
-- { city: NONE, name: 'B' },
-- { city: NONE, name: 'C' }
-- ]

Only person A has a knows edge to the city node; everyone else still appears with city: NONE.

Count outgoing person → person knows edges per person.

eval::gql(
"MATCH (a:person)-[:knows]->(b:person) RETURN a.name AS name, count(*) AS friends GROUP BY a.name ORDER BY name"
);
-- [{ friends: 1, name: 'A' }, { friends: 2, name: 'B' }, { friends: 1, name: 'C' }]

In ISO GQL on SurrealDB, variable-length hops are a postfix quantifier on the edge, not Cypher's *1..3 inside the brackets:

-- From A, who is reachable in 1–2 `knows` hops (staying on `:person` nodes)?
eval::gql(
"MATCH (a:person)-[:knows]->{1,2}(b:person) WHERE a.name = 'A' RETURN a.name AS source, b.name AS target ORDER BY target"
);
-- [
-- { source: 'A', target: 'A' },
-- { source: 'A', target: 'B' },
-- { source: 'A', target: 'C' }
-- ]

One hop reaches B; two hops loop back to A via B or reach C via B.

GQL parameters can be passed as the second argument to this function.

eval::gql(
"MATCH (n:person) WHERE n.age > $min RETURN n.name AS name ORDER BY name",
{ min: 25 }
);
-- { name: 'A' }

Bindings are isolated from the caller's scope (same as eval::surql); only keys you pass in the object are visible inside the GQL query.

For more patterns — path search (ALL SHORTEST), comma-separated joins, and side-by-side SurrealQL — see Sample GQL and SurrealQL queries.

Evaluates a single SurrealQL statement and returns its value. To run several statements, wrap them in a block { ... } — the block's final value is returned.

API DEFINITION

eval::surql(query: string, bindings: option<object>) -> any
-- Simple expression
eval::surql("RETURN 1 + 1");
-- 2

-- Caller bindings become $parameters inside the nested query
eval::surql("RETURN $a + $b", { a: 2, b: 3 });
-- 5

-- Multiple statements: use a block
eval::surql("{ LET $x = 10; RETURN $x * 2 }");
-- 20

Nested writes run in the caller's transaction:

eval::surql("CREATE person:1 SET name = 'A'");
SELECT name FROM person;
-- [{ name: 'A' }]

The evaluated query runs in an isolated scope: parameters visible at the call site are not inherited — only keys you pass in the bindings object are bound.

LET $secret = 42;
eval::surql("RETURN $secret ?? 'isolated'");
-- 'isolated'

The following are rejected inside eval::*:

  • Transaction and session controlBEGIN, CANCEL, COMMIT, USE, LIVE, KILL, OPTION, SHOW, and access statements.

  • Bare multi-statement SurrealQL in eval::surql — use { ... } instead of semicolon-separated top-level statements.

  • Protected binding names — caller bindings cannot overwrite reserved parameters such as $session.

  • Excessive nesting — recursive eval calls share the engine's computation depth limit.

Every eval::* call is checked against the current execution auth (guest, record, or system). Auth limiting in DEFINE FUNCTION bodies never raises the subject — a record-scoped caller that invokes an owner-defined function which calls eval is still evaluated as record.

All of the following must pass:

GatePurpose
allow-funcs / deny-funcsThe eval function family must be permitted
deny-arbitrary-query (and related allow rules)eval counts as an arbitrary query — denied subjects cannot use eval to bypass /sql or API lockdown
allow-eval-query / deny-eval-queryDedicated opt-in for eval::surql and eval::gql

eval cannot grant a subject more query power than arbitrary-query policy allows — including when called from inside a DEFINE FUNCTION or DEFINE API handler. You do not need --allow-arbitrary-query for eval on a default server; you do need --allow-eval-query.

For eval::gql, the gql experimental capability must also be enabled.

eval::* is enforced by the database engine that executes your query, not by the client you type into.

When the REPL connects over ws://, http://, or similar, pass capability flags on surreal start only. The same flags on surreal sql do not enable or disable eval at runtime.

# Terminal 1 — start the server with eval enabled for system users
surreal start --user root --pass secret --allow-experimental gql --allow-eval-query

# Terminal 2 — no --allow-eval-query needed on the client
surreal sql -e ws://localhost:8000 --user root --pass secret

Environment variable equivalent on the server process:

export SURREAL_CAPS_ALLOW_EVAL_QUERY=system

If you use --deny-arbitrary-query (for example to steer record users toward DEFINE API only), add matching --allow-arbitrary-query entries for any subject that should call eval.

When you open the REPL against embedded storage (memory, rocksdb://…, …) without a separate surreal start process, configure capabilities on surreal sql instead:

surreal sql --user root --pass secret --allow-experimental gql --allow-eval-query

See Capabilities and remote connections for the full remote versus embedded model.

See SURREAL_CAPS_ALLOW_EVAL_QUERY and SURREAL_CAPS_DENY_EVAL_QUERY in the environment variable reference.

Was this page helpful?