The #[surrealism] attribute controls which Rust items are exported from your extension and how SurrealDB should treat them. Its options are default, init, comment = "..." and name = "...".
Basic export
Use #[surrealism] on a function to expose it to SurrealQL:
use surrealism::surrealism;
#[surrealism]
fn can_drive(age: i64) -> bool {
age >= 18
}After packaging and DEFINE EXTENSION, this function is callable as ext::name::can_drive(), where name is the name the extension is defined with.
default
Mark one function as the extension's default export. It is called with the extension's name alone, without a function name:
#[surrealism(default)]
fn hello() -> String {
"Hello from Surrealism!".to_string()
}ext::name();The project that surreal ext init creates has a default export.
name = "..."
Export a function or a Rust module under a name other than its own. Each segment of the name uses only ASCII letters, digits and underscores, and segments are separated by :::
#[surrealism(name = "other")]
fn can_drive_bla(age: i64) -> bool {
age >= 18
}This function is called as ext::name::other().
Querying the database
Available since: v4.0.0
A function that takes a Transaction as its first parameter can run SurrealQL inside the transaction of the query that called it. A function without one cannot query the database.
use anyhow::Result;
use surrealism::{surrealism, Transaction};
use surrealdb_types::Value;
#[surrealism]
async fn top_customers(tx: Transaction, limit: i64) -> Result<Value> {
tx.sql_with_vars("SELECT * FROM customer LIMIT $limit", [("limit", limit)]).await
}The handle has four async methods:
| Method | Use |
|---|---|
tx.sql(query) | Run a SurrealQL query and convert the result into the return type. |
tx.sql_with_vars(query, vars) | The same, with parameters given as key and value pairs. |
tx.run(fnc, args) | Call a function, such as fn::user_exists, with a tuple of arguments. |
tx.run_versioned(fnc, version, args) | Call a function at a given version. |
Calls awaited together run at the same time when none of them can write. Calls that can write, and every tx.run call, run one after the other. Queries are still limited by the queries and functions capabilities that the extension's definition grants.
The handle is valid only during the call. A function that stores it and uses it after returning stops with an error.
Read-only and writeable functions
Transaction on its own gives read-only access. To modify data, declare the parameter as Transaction<ReadWrite>:
use anyhow::Result;
use surrealism::{surrealism, ReadWrite, Transaction};
use surrealdb_types::Value;
#[surrealism]
async fn create_user(tx: Transaction<ReadWrite>, name: String) -> Result<String> {
let exists: bool = tx.run("fn::user_exists", (name.clone(),)).await?;
if exists {
return Ok(format!("User {name} already exists"));
}
let _: Value = tx.sql_with_vars("CREATE user SET name = $name", [("name", name.clone())]).await?;
Ok(format!("Created user {name}"))
}The parameter type is recorded in the extension's exports manifest, so SurrealDB knows which functions can write before it calls them. A read-only function that tries to modify data fails with this error:
A read-only Surrealism function cannot modify data; the function must declare that it requires a writeable transactionThe check covers every statement that modifies data, and also the functions that have a side effect: api::invoke, eval::surql, eval::gql, the file:: functions that write, copy, rename or delete, http::post, http::put, http::patch, http::delete and sequence::nextval. A function name used in method syntax is checked in the same way.
Before SurrealDB 4.0, write access was declared with #[surrealism(writeable)], and module code queried the database through the free functions surrealism::sql(), surrealism::sql_with_vars() and surrealism::run(). These have been removed. See Upgrading a module to 4.0.
Using your own async runtime
#[surrealism] keeps the other attributes on a function, so a function can start its own runtime, for example to use a crate that needs Tokio:
#[tokio::main(flavor = "current_thread")]
#[surrealism]
async fn fetch_status(url: String) -> Result<String, String> {
// implementation omitted
Ok(url)
}#[tokio::main] has to come first, above #[surrealism], so that #[surrealism] receives the plain fn that #[tokio::main] produces. Use the current_thread flavour, because the default multi-threaded runtime needs threads that an extension does not have. A function with its own runtime cannot take a Transaction. Use the Transaction methods on their own for database access, as they run on the host's async support and need no runtime in the module.
comment = "..."
Attach human-readable metadata to an export:
#[surrealism(comment = "Parses a decimal string into an integer")]
fn parse_number(input: String) -> Result<i64, String> {
input.parse::<i64>().map_err(|e| e.to_string())
}The comment appears in extension metadata outputs such as:
surreal ext infothe export metadata of
INFO FOR DB STRUCTURE
init
Register one initialisation hook. From SurrealDB 4.0 it runs once, when the extension is built, and every call starts from the memory it leaves behind:
#[surrealism(init)]
fn warm_cache() {
// implementation omitted
}Use this for one-time setup such as preloading attached files or building lookup tables. An init function cannot be async and cannot take a Transaction. Because it runs at build time, it cannot open network connections, and a value it reads from the clock or a random source is fixed into every call. Before SurrealDB 4.0, init ran when each instance of the module started.
Namespaced modules
Apply #[surrealism] to Rust modules to group exports under namespaces:
#[surrealism]
mod math {
use surrealism::surrealism;
#[surrealism]
pub fn add(a: i64, b: i64) -> i64 {
a + b
}
#[surrealism]
pub mod util {
use surrealism::surrealism;
#[surrealism]
pub fn negate(value: i64) -> i64 {
-value
}
}
}Exports are prefixed by namespace, for example:
math::addmath::util::negate
Upgrading a module to 4.0
Available since: v4.0.0
Modules built for SurrealDB 3.3 or earlier have to be rebuilt as extensions with the new version of the surrealism crate. SurrealDB 4.0 refuses to load an older .surli file with the error Unsupported ABI version: expected 4, got 2. Rebuild the extension with the SurrealDB 4.0 SDK. Rename surrealism.toml to package.toml and set abi = 4, or scaffold a new project with surreal ext init and move the code across. The definition then has to be made again with DEFINE EXTENSION.
| Before 4.0 | From 4.0 |
|---|---|
#[surrealism(writeable)] | A Transaction<ReadWrite> parameter |
surrealism::sql(query) | tx.sql(query).await |
surrealism::sql_with_vars(query, vars) | tx.sql_with_vars(query, vars).await |
surrealism::run(fnc, version, args) | tx.run(fnc, args).await or tx.run_versioned(fnc, version, args).await |
surrealism::kv::get(), set() and the other kv functions | Records, through Transaction<ReadWrite>. Memory does not last from one call to the next, so a static keeps only what init set |
surrealism::tokio | A tokio dependency of your own, with #[tokio::main(flavor = "current_thread")] |
The module key-value store is removed. The max_kv_entries and max_kv_value_bytes capabilities in surrealism.toml and the SURREAL_SURREALISM_MAX_KV_ENTRIES and SURREAL_SURREALISM_MAX_KV_VALUE_BYTES environment variables no longer have any effect.
A function that wrote data without declaring it now fails with the read-only error shown in Read-only and writeable functions.