Skip to content

Functions

/

Database functions

Eval

Available since: v3.2.0

Note

eval::* is denied for every subject by default, including under --allow-all. You must explicitly enable it with --allow-eval-query and satisfy the arbitrary-query gate for the same subject. See Security below.

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 (Cypher Query Language) and SurrealQL in one transaction, or to learn SurrealQL or rewrite existing queries from another graph database.

Function

Description

eval::gql()

Evaluate a nested ISO GQL (Cypher Query Language) 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 (Cypher Query Language) 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 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. From 3.3.0, GQL itself needs no experimental flag; you still need --allow-eval-query. On 3.2.x, also pass --allow-experimental gql.

Enable eval on the server, for example:

surreal start --user root --pass secret --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)Records 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");
Output
[{ 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");
Output
{ 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"
);
Output
{ '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"
);
Output
[
  { 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"
);
Output
[{ friends: 1, name: 'A' }, { friends: 2, name: 'B' }, { friends: 1, name: 'C' }]

In ISO GQL (Cypher Query Language) 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"
);
Output
[
  { 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 }
);
Output
{ 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.

Note

When a GQL query returns a single record, eval::gql yields that record as an object; multiple records are returned as an array.

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;
Output
[{ 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'");
Output
'isolated'

The following are rejected inside eval::*:

  • Transaction and session control - BEGIN, 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.

From 3.3.0, eval::gql does not require the experimental gql capability (still required on 3.2.x).

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-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-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?