Procedural macros for hyperdb-api:
#[derive(FromRow)]— maps query result rows to Rust structs at runtime#[derive(Table)]— generatesCREATE TABLESQL from a struct and (optionally) registers it for compile-time validationquery_as!(T, "sql")— typed query builder, validated at build time when thecompile-timefeature is enabledquery_scalar!(T, "sql")— single-column query builder, validated at build time
Add hyperdb-api-derive directly to your [dependencies]:
hyperdb-api-derive = { version = "0.3", features = ["compile-time"] }Without
features = ["compile-time"]the macros are pure pass-throughs — zero validation overhead, zero new dependencies. Add the feature to opt in.
Maps named query result columns to struct fields at runtime.
use hyperdb_api::FromRow;
use hyperdb_api_derive::FromRow;
#[derive(Debug, FromRow)]
struct User {
id: i32,
name: String,
#[hyperdb(rename = "email_address")]
email: Option<String>,
}
let users: Vec<User> = conn.fetch_all_as("SELECT id, name, email_address FROM users")?;#[hyperdb(rename = "col")]— use a different SQL column name than the field name.#[hyperdb(index = N)]— use positional access at columnN(zero-based) instead of name lookup. Mutually exclusive withrename.#[hyperdb(primary_key)]— documents intent; silently ignored byFromRow(relevant toderive(Table)).Option<T>fields tolerate SQL NULL (→None); non-Optionfields error on NULL.
When you need transformation — parsing a string column into an enum, defaulting NULLs, splitting a column across multiple fields — write the impl directly:
impl FromRow for User {
fn from_row(row: hyperdb_api::RowAccessor<'_>) -> hyperdb_api::Result<Self> {
Ok(User {
id: row.get("id")?,
name: row.get("full_name")?,
email: row.get_opt("email_address")?,
})
}
}Required (T) |
Optional (Option<T>) |
|
|---|---|---|
| By name | row.get(name)? |
row.get_opt(name)? |
| By index | row.position(idx)? |
row.position_opt(idx)? |
Generates a hyperdb_api::Table impl with NAME and CREATE_SQL constants. Useful for runtime migrations, test fixtures, and as the source of truth for compile-time validation.
use hyperdb_api::Table;
use hyperdb_api_derive::{FromRow, Table};
#[derive(Debug, FromRow, Table)]
#[hyperdb(table = "users", register)]
struct User {
#[hyperdb(primary_key)]
id: i64,
name: String,
email: Option<String>,
}
// Use the derived CREATE_SQL to create the table at runtime:
conn.execute_command(User::CREATE_SQL)?;
println!("{}", User::NAME); // "users"
println!("{}", User::CREATE_SQL); // "CREATE TABLE IF NOT EXISTS users (id BIGINT NOT NULL, ...)"#[hyperdb(table = "name")]— override the SQL table name (default:lower_snake_caseof the struct ident, e.g.UserOrder→user_order).#[hyperdb(register)]— register this struct with the compile-time validator. Required forquery_as!validation to work. Has no effect without thecompile-timefeature.
#[hyperdb(primary_key)]— documents intent; the column isNOT NULLfor non-Optionfields regardless.#[hyperdb(rename = "col")]— use a different SQL column name.
| Rust type | SQL type |
|---|---|
i16 |
SMALLINT |
i32 |
INTEGER |
i64 |
BIGINT |
f32 |
REAL |
f64 |
DOUBLE PRECISION |
bool |
BOOLEAN |
String |
TEXT |
Vec<u8> |
BYTES |
chrono::NaiveDate |
DATE |
chrono::NaiveDateTime |
TIMESTAMP |
chrono::NaiveTime |
TIME |
chrono::DateTime<Utc> |
TIMESTAMPTZ |
Numeric |
NUMERIC |
Option<T> |
nullable version of T (no NOT NULL) |
Any other type produces a compile error with a suggestion to write a manual impl Table.
Returns a [hyperdb_api::QueryAs<T>] builder. Validates the SQL at build time when compile-time feature is enabled.
use hyperdb_api_derive::{query_as, FromRow, Table};
let users: Vec<User> = query_as!(User, "SELECT id, name, email FROM users ORDER BY id")
.fetch_all(&conn)?;
let alice: Option<User> = query_as!(User, "SELECT id, name, email FROM users WHERE id = 1")
.fetch_optional(&conn)?;Builder methods: .fetch_all(&conn), .fetch_one(&conn), .fetch_optional(&conn).
With features = ["compile-time"] and HYPERD_PATH set, query_as! validates at build time that:
- The target struct is registered via
#[derive(Table)] #[hyperdb(register)] - All referenced tables exist (seeded lazily from registered structs)
- All struct fields appear in the projected columns
Bad SQL produces a compile_error! pointing at the SQL string literal:
error: column "emai1" does not exist on any table in the query;
check for a typo or a renamed/dropped column
error: `User` requires column "email" but the query does not project it;
add it to the SELECT list or remove the field from `User`
derive(Table) registers the struct at macro expansion time. Within a single file, struct derives always expand before function-body macros — ordering within a file is never a problem.
Across files: the module containing derive(Table) structs must be declared (mod structs;) before the module containing query_as! calls in your lib.rs / main.rs. Reorder the mod declarations if you get a false StructNotRegistered error.
Like query_as! but for single-column queries. T must implement hyperdb_api::RowValue.
use hyperdb_api_derive::query_scalar;
let count: i64 = query_scalar!(i64, "SELECT COUNT(*) FROM users").fetch_one(&conn)?;
let names: Vec<String> = query_scalar!(String, "SELECT name FROM users").fetch_all(&conn)?;With compile-time feature, validates that the SQL projects exactly one column.
To see compile-time errors as squigglies in the editor:
1. Add HYPERD_PATH to your shell (~/.zshrc or ~/.bashrc):
export HYPERD_PATH=/path/to/your/project/.hyperd/currentRestart your terminal and VS Code after changing this.
2. Add a .vscode/settings.json in your workspace root:
{
"rust-analyzer.cargo.features": ["hyperdb-api-derive/compile-time"],
"rust-analyzer.server.extraEnv": {
"HYPERD_PATH": "${workspaceFolder}/.hyperd/current"
}
}Important:
rust-analyzer.cargo.featuresmust be a flat array of"package/feature"strings. RA silently ignores the JSON-object form and builds with no features, so validation never fires.
3. Reload the VS Code window (Cmd+Shift+P → Developer: Reload Window).
After RA finishes indexing you'll see squigglies on bad SQL strings and errors in the Problems panel. The first expansion starts an embedded Hyper instance (~156 ms); subsequent expansions reuse it.
To temporarily disable (if hyperd is unavailable on a machine):
{
"rust-analyzer.procMacro.ignored": {
"hyperdb-api-derive": ["query_as", "query_scalar"]
}
}- Type checking not yet implemented — only column names are validated. Runtime
Error::Column { kind: TypeMismatch }still catches type drift. - No parameter type checking — bind parameters are opaque at compile time.
- Validates struct vs. SQL, not SQL vs. production DB — struct/prod schema drift is still a runtime error.
INSERT/UPDATE/DELETEwithoutRETURNINGare not supported byquery_as!; useConnection::execute_commanddirectly.