Expand description
Compile-time-checked, typed SurrealQL for Rust.
surrealguard-rs runs the SurrealGuard
analyzer against your schema during compilation. A wrong table, unknown
field, bad function arity, or kind mismatch becomes a cargo check error,
the result type is generated from the inferred response, and the query’s
parameters are type-checked against the kinds their uses imply — no build
script, no language server, no runtime schema fetch.
use surrealguard_rs::query;
let adults = query!("SELECT name, age FROM user WHERE age >= $min", min = 18)
.fetch_all(&db)
.await?;
for row in adults {
println!("{} is {}", row.name, row.age); // String, i64 — inferred
}§The macros
This crate re-exports the proc-macros from
surrealguard-macros and
provides the runtime types their output refers to, so depending on
surrealguard-rs alone is enough.
query!checks a query and returns a typedQuery<T>, whereTis the inferred result rendered as a nameless struct. You get nested field access without ever writing a type — the same ergonomics as sqlx’s anonymousquery!record.query_file!isquery!with the SurrealQL read from a file at compile time, resolved relative to the crate root (assqlx::query_file!does).surql!checks a query and expands to its validated text as a&'static str— the lighter form when you only want validation.
§Executing
With the default runtime feature, a Query<T> runs against the
official surrealdb SDK. The verbs mirror sqlx:
| method | returns | available when |
|---|---|---|
fetch | T | always — T is the analyzed shape |
fetch_all | Vec<T::Row> | the query yields a row set |
fetch_one | T::Row | the query yields a row set |
fetch_optional | Option<T::Row> | the query yields a row set |
execute | () | always |
fetch is the honest one: it hands back exactly the type
the analyzer inferred, whatever its shape. The row-set verbs are gated on
the Rows trait, which is implemented for the shapes a row set can take —
Vec<R> (a plain SELECT) and Option<R> (SELECT … FROM ONLY …). A
query that returns a scalar therefore cannot call fetch_all; that is a
compile error, not a runtime surprise:
query!("RETURN 1 + 1").fetch_all(&db).await?;
// error: this query does not return a set of rows
// note: use `.fetch(&db)` to get the value this query actually returns§Parameters
Parameters are supplied by name and checked against the kind the analyzer inferred for each use site. There is no unchecked bind: the macro rejects a missing parameter, an unknown one, and a value whose Rust type cannot become the inferred kind.
query!("SELECT name FROM user WHERE age > $min", min = 18) // ok
query!("SELECT name FROM user WHERE age > $min", min = "18") // compile error
query!("SELECT name FROM user WHERE age > $min") // compile error: missing `min`
query!("SELECT name FROM user", limit = 10) // compile error: no `$limit`§Multiple statements
When more than one statement in a query responds, T is a tuple of the
responding statements’ types, in source order. Non-responding statements
(a bare LET, a DEFINE) are skipped — they still occupy a slot in the
SDK’s response, and this crate accounts for that so the tuple lines up with
what you wrote.
let (users, posts) = query!("SELECT name FROM user; SELECT title FROM post;")
.fetch(&db)
.await?;Because a tuple is not a row set, fetch_all on a multi-statement query is
a compile error — you must use fetch and destructure.
§Schema awareness
The macros resolve your schema at compile time from, in order:
- the
SURREALGUARD_SCHEMAenvironment variable — a.surqlfile or a directory, relative toCARGO_MANIFEST_DIRunless absolute; - otherwise a convention path under the crate root, tried in turn:
schema/, thenmigrations/, thenschema.surql.
A directory contributes every .surql/.surrealql file sorted by name,
so zero-padded migrations (0001_*.surql, 0002_*.surql, …) apply in
order. Each schema file is tracked with include_bytes!, so editing it
forces a rebuild of the crate that calls the macro. With no schema
configured, queries are still checked for everything that does not depend on
one (syntax, function arity, operators, …).
§Kind → Rust mapping
Generated types implement surrealdb_types::SurrealValue, which is what
the SDK’s take actually requires — not serde::Deserialize. The mapping
is chosen so every generated type decodes the Value the server really
sends: the SDK performs no coercion whatsoever, so an approximate mapping is
a runtime failure rather than a lossy read.
| SurrealQL kind | Rust type |
|---|---|
bool | bool |
int | i64 |
float | f64 |
decimal | Decimal |
number | Number |
string | String |
datetime | Datetime |
duration | Duration |
uuid | Uuid |
bytes | Bytes |
regex | Regex |
record<t> | RecordId |
geometry | Geometry |
option<T> | Option<T> |
array<T> / set | Vec<T> |
closed object | a nested, nameless struct |
any / open object | Value |
§Feature flags
runtime(default) — pulls in thesurrealdbSDK and enables thefetch*/executemethods. Turn it off (default-features = false) to get checking and typing with no SDK dependency;Query::decodestill decodes a response obtained some other way.
The SDK is depended on with default-features = false, so it contributes no
engine or protocol of its own — the executing crate picks those through its
own surrealdb dependency, and Cargo unifies the two.
Re-exports§
pub use surrealdb_types;
Macros§
- query
- Checks a
SurrealQLstring literal at compile time and expands to asurrealguard_rs::Query<T>whoseTis the inferred, nameless result type. - query_
file - Like [
query], but reads theSurrealQLfrom a file at compile time. - surql
- Checks a
SurrealQLstring literal at compile time; expands to its text.
Structs§
- Error
- What went wrong running a
Query. - Query
- A compile-time-checked query paired with its inferred result type
T.
Enums§
Traits§
- Rows
- A result shape that is a set of rows.
Type Aliases§
- Result
- The result of running a compile-time-checked query.