hyperdb-api-derive
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]:
= { = "0.3", = ["compile-time"] }
Without
features = ["compile-time"]the macros are pure pass-throughs — zero validation overhead, zero new dependencies. Add the feature to opt in.
#[derive(FromRow)]
Maps named query result columns to struct fields at runtime.
use FromRow;
use FromRow;
let users: = conn.fetch_all_as?;
Attributes
#[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.
Hand-writing FromRow
When you need transformation — parsing a string column into an enum, defaulting NULLs, splitting a column across multiple fields — write the impl directly:
RowAccessor cheat sheet
Required (T) |
Optional (Option<T>) |
|
|---|---|---|
| By name | row.get(name)? |
row.get_opt(name)? |
| By index | row.position(idx)? |
row.position_opt(idx)? |
#[derive(Table)]
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 Table;
use ;
// Use the derived CREATE_SQL to create the table at runtime:
conn.execute_command?;
println!; // "users"
println!; // "CREATE TABLE IF NOT EXISTS users (id BIGINT NOT NULL, ...)"
Struct-level attributes
#[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.
Field-level attributes
#[hyperdb(primary_key)]— documents intent; the column isNOT NULLfor non-Optionfields regardless.#[hyperdb(rename = "col")]— use a different SQL column name.
Supported field types
| 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.
query_as!(T, "sql" [, args...])
Returns a [hyperdb_api::QueryAs<T>] builder. Validates the SQL at build time when compile-time feature is enabled.
use ;
let users: = query_as!
.fetch_all?;
let alice: = query_as!
.fetch_optional?;
Builder methods: .fetch_all(&conn), .fetch_one(&conn), .fetch_optional(&conn).
Compile-time validation
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`
Module ordering constraint
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.
query_scalar!(T, "sql" [, args...])
Like query_as! but for single-column queries. T must implement hyperdb_api::RowValue.
use query_scalar;
let count: i64 = query_scalar!.fetch_one?;
let names: = query_scalar!.fetch_all?;
With compile-time feature, validates that the SQL projects exactly one column.
VS Code: squigglies on bad SQL
To see compile-time errors as squigglies in the editor:
1. Add HYPERD_PATH to your shell (~/.zshrc or ~/.bashrc):
Restart your terminal and VS Code after changing this.
2. Add a .vscode/settings.json in your workspace root:
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):
Known limitations
- 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.