Skip to main content
Preview capability APIs are not stable. An extension built against a preview capability may fail to load after a server update.
Preview capabilities are server features exposed to extensions before their APIs are finalized. The Rust SDK wraps five of them: status variables, system variables, background thread workers, SQL queries from a worker thread, and keyring access. This page covers declaring and using each one from Rust. For the concept, the preview tier, and the capabilities that exist only in C++, see Preview Capabilities.

Prerequisites

An extension that uses any preview capability installs only when vsql_allow_preview_extensions is ON:
See Enabling the Preview Tier for the details, including why SET GLOBAL is rejected for this variable. You also need a working Rust extension setup — the Creating Extensions in Rust walkthrough covers the toolchain, cargo-vsql, and your first extension! block.

What the Rust SDK Wraps

The remaining preview capabilities — auth, mysql_services, statement_event, and the column storage ABI — are C++-only today. Use the C++ SDK if you need one of those.

Registration Pattern

Declare each capability as a static, then list it by reference in the requires: section of extension! — the Rust equivalent of the C++ SDK’s .with():
The server populates the capability object at load time; before that, its accessor methods report the capability as unavailable rather than crashing. The static is required — the server holds pointers into the capability for the extension’s whole lifetime.

Status Variables

The status_var capability exposes extension-owned counters through SHOW GLOBAL STATUS. Your extension owns the storage as 'static atomics and writes to them; the server reads through the pointers each time the status variable is queried. Declare each variable as a StatusVarSpecInt backed by an AtomicI64, or Double backed by the SDK’s AtomicF64 (Rust’s standard library has no atomic f64, so the SDK provides one with new, load, and store):
After INSTALL EXTENSION, the variables appear with the extension name as a prefix:

System Variables

The sys_var capability registers MySQL system variables owned by your extension. Three types are supported: Bool, Int (with min/max bounds), and Str. Every spec carries a name, a comment shown in SHOW VARIABLES metadata, a default, and an optional on_change callback. Names and string defaults are &'static CStr values — write them as C-string literals (c"enabled"):
The funcs: section must be present before requires:, even when empty. After install, the variables are addressable with the extension name as a prefix:

Reacting to Changes

on_change is a raw C callback, invoked by the server after a variable is set. The server calls it while it holds its global system-variable lock, so keep it quick, and don’t panic — a panic here crosses the FFI boundary. The callback receives a *const vef_sys_var_change_t from the raw ABI layer:
Calling SysVarCapability::get or SysVarCapability::set, running SQL, or waiting on a thread that does either deadlocks on that lock. Keep the callback to bookkeeping in your own statics, as above, and hand work that needs SQL to a thread worker.
Wire it into a spec with on_change: Some(on_enabled_change).

Reading and Writing from Extension Code

SysVarCapability also exposes get() and set() for programmatic access through the server (so range validation and persistence are handled for you). set() takes a scope argument that selects persistence: null changes only the running value, so it reverts on restart; "PERSIST" changes the running value and writes it to the persisted config; "PERSIST_ONLY" writes to the persisted config without touching the running value, so it applies on the next restart. Both are unsafe FFI methods taking NUL-terminated C strings, and both use the inverted C convention: Some(false) means success, Some(true) means the server reported an error, and None means the capability is unavailable. On a successful get, the server writes a malloc’d string you must release with the C free(). The vsql_sys_var example shows the full pattern, including the free extern and the safety comments.

Thread Worker

The thread_worker capability runs a function you provide on a server-managed background thread. The server registers a control system variable at load time; while it is ON, your work function is called on a periodic timer, on file-descriptor readiness, or on enable/disable transitions. Your work function is plain safe Rust:
ThreadWorkerCapability::new takes the work function, a thread-name suffix, the initial sleep interval, and an optional control-variable name override. When the override is None, the control variable is named {suffix}_enabled and registered under the extension’s prefix:

Wakeups

WakeupReason tells you why the server called: Enable, Periodic, PollFd, or Disable. Your return value adjusts the next wakeup:
  • NextWakeup::unchanged() — keep the current sleep interval and poll fd.
  • NextWakeup::after(duration) — wake again after duration.
  • Set the poll_fd field to a file descriptor greater than zero to also wake when it becomes readable, or to -1 to clear a previously set one.
A zero-length Duration collapses to “no change” — the underlying C ABI reserves 0 for that, so an instant wakeup can’t be expressed. If the work function panics, the SDK catches the panic at the FFI boundary and treats the call as returning NextWakeup::unchanged() — the worker keeps running. The ThreadHandle parameter arrives by reference; pass it to SqlQueryCapability::open in villagesql::preview::sql_query to run SQL from inside the worker.

Keyring Access

The keyring capability reads and writes secrets stored in the MySQL keyring component — API keys, encryption keys, anything that shouldn’t live in a table. A keyring component (for example component_keyring_file) must be installed on the server; without one, every read and write fails with KeyringError::NoComponent.
read(data_id, auth_id, buf) fills the buffer you pass and returns Ok(Some(n)) with the secret’s length, or Ok(None) when no secret exists under data_id — a normal outcome, not an error. write(data_id, auth_id, data) returns Ok(()) on success. auth_id is the owning user; pass None for internal keys not associated with a specific user. Both return Err(KeyringError) on failure: CapabilityUnavailable (the capability was never wired up), NoComponent (no keyring component on the server), or Other. The keyring has no size probe: a secret larger than the buffer you pass to read comes back as Ok(None), indistinguishable from a missing key. Size your buffer for the largest secret you expect to store.

Next Steps

Preview Capabilities (C++)

The full capability index, the preview tier, and the C++-only capabilities: auth, mysql_services, statement_event, and column storage.

Rust API Reference

InValue, VdfReturn, extension!, func!, and custom_type! — all fields.