keelson_macros/lib.rs
1//! keelson's derive macros: [`Bind`] for newtype column types, [`FromRow`]
2//! for row mapping.
3//!
4//! Both close the same gap. `docs/type-mappings.md` promises that the Rust
5//! type of a generated column can be replaced with your own, and that what a
6//! replacement must satisfy is a *trait bound in the generated code* — so a
7//! type that cannot bind is a compile error, not a runtime surprise. That
8//! bound is `keelson_exec::Bind`:
9//!
10//! ```text
11//! pub trait Bind: ToValue + FromValue + Send + 'static {}
12//! impl<T: ToValue + FromValue + Send + 'static> Bind for T {}
13//! ```
14//!
15//! and for every `[[types.override]]` in a keelson-gen configuration the
16//! generator emits one line asserting it:
17//!
18//! ```text
19//! const _: () = keelson_exec::assert_bind::<crate::types::UserId>();
20//! ```
21//!
22//! Satisfying that used to mean hand-writing `ToValue` and `FromValue`.
23//! `#[derive(Bind)]` writes them for a newtype.
24//!
25//! # Where this sits
26//!
27//! The derives, and nothing else. Do not depend on this crate directly:
28//! [keelson-core](https://docs.rs/keelson-core) re-exports both behind its `macros` feature,
29//! which is the path `use keelson_core::Bind;` takes and the one generated code
30//! is written against. The traits they implement live in keelson-core
31//! (`ToValue`/`FromValue`) and [keelson-exec](https://docs.rs/keelson-exec) (`FromRow`). The
32//! whole map is the [keelson](https://docs.rs/keelson) facade crate.
33//!
34//! # `#[derive(Bind)]`
35//!
36//! ```
37//! use keelson_core::{Bind, FromValue as _, ToValue as _, Value};
38//!
39//! #[derive(Debug, Clone, PartialEq, Bind)]
40//! pub struct UserId(pub i64);
41//!
42//! #[derive(Debug, Clone, PartialEq, Bind)]
43//! pub struct Email(String);
44//!
45//! // The line generated code emits for an override now passes.
46//! const _: () = keelson_exec::assert_bind::<UserId>();
47//!
48//! assert_eq!(UserId(7).to_value(), Value::I64(7));
49//! assert_eq!(UserId::from_value(Value::I64(7)).unwrap(), UserId(7));
50//! ```
51//!
52//! What it emits, and nothing else: `keelson_core::ToValue` and
53//! `keelson_core::FromValue`, each delegating to the single inner field.
54//! `Bind` itself is never implemented — it is a blanket alias, and naming the
55//! derive after the bound it satisfies is the point. Everything the inner type
56//! can do, the newtype now does: `Option<UserId>` binds as NULL-or-value
57//! through core's blanket impls, the widening `FromValue` accepts (a driver
58//! that hands back `I32` for a `BIGINT`) still applies, and the type is usable
59//! anywhere `arg(...)` takes a value.
60//!
61//! ## What it accepts
62//!
63//! One field, named or not — `struct UserId(i64);` and
64//! `struct UserId { raw: i64 }` are the same thing to this derive. Generics
65//! work (`struct Tagged<T>(T)`), with the inner type's bound added to the
66//! generated impls.
67//!
68//! ## What it refuses, and why
69//!
70//! - **Multi-field structs.** A column is one value. A struct of several is a
71//! *row*, which is what [`FromRow`] is for; if the parts genuinely are one
72//! column, the encoding is a decision (separator? escaping? what does a
73//! malformed value mean?) that belongs in your own `ToValue`/`FromValue`.
74//! - **Enums.** Same reason, one level deeper: an enum needs a chosen
75//! database representation — text or integer, the spelling of each variant,
76//! and what an unrecognised value read back means. Every one of those is a
77//! decision this derive would have to invent, and inventing it silently is
78//! exactly the "plausible guess" keelson does not make. Write the two impls
79//! (about ten lines; the unknown-variant case is
80//! `keelson_core::Error::type_mismatch`), or derive `Bind` on a newtype over
81//! the representation.
82//! - **Unions and unit structs.** Nothing to bind.
83//! - **Types with a lifetime parameter.** `FromValue` builds an *owned* value
84//! out of a `Value`, so a borrowing newtype could never read back. This one
85//! is refused rather than left to the compiler for a specific reason: the
86//! bound goes in a `where` clause, and rustc *accepts* an impl whose `where`
87//! clause can never hold — it just never applies. The derive would appear to
88//! work and then fail at the first call site, which is exactly the distant,
89//! inference-swamped failure this whole mechanism exists to replace.
90//! - **`#[keelson(...)]` options.** There are none. A newtype has one field
91//! and one meaning. The whole `keelson` namespace is refused here rather
92//! than only the unrecognised keys, so a `rename` that drifted onto a
93//! newtype is caught instead of silently doing nothing. Deriving `Bind` and
94//! `FromRow` on the same one-field struct still works — just leave its field
95//! attribute-free.
96//!
97//! Each refusal is a compile error spanned at the offending item, naming the
98//! restriction and what to do instead —
99//! `keelson-macros/tests/compile_fail/*.stderr` pins the exact text.
100//!
101//! # `#[derive(FromRow)]`
102//!
103//! ```
104//! use std::sync::Arc;
105//!
106//! use keelson_core::{FromRow, Value};
107//! use keelson_exec::{Column, FromRow as _, Row};
108//!
109//! #[derive(Debug, PartialEq, FromRow)]
110//! struct Account {
111//! id: i64,
112//! // The column is `email_address`; the field is not.
113//! #[keelson(rename = "email_address")]
114//! email: String,
115//! // A nullable column must be an Option, exactly as in a hand-written impl.
116//! nickname: Option<String>,
117//! // Read out of the same row, by the nested type's own FromRow.
118//! #[keelson(flatten)]
119//! audit: Audit,
120//! }
121//!
122//! #[derive(Debug, PartialEq, FromRow)]
123//! struct Audit {
124//! created_by: i64,
125//! }
126//!
127//! let columns: Arc<[Column]> = vec![
128//! Column::new("id"),
129//! Column::new("email_address"),
130//! Column::new("nickname"),
131//! Column::new("created_by"),
132//! ]
133//! .into();
134//! let mut row = Row::new(
135//! columns,
136//! vec![
137//! Value::I64(1),
138//! Value::Text("ada@example.com".into()),
139//! Value::Null,
140//! Value::I64(9),
141//! ],
142//! );
143//!
144//! assert_eq!(
145//! Account::from_row(&mut row).unwrap(),
146//! Account {
147//! id: 1,
148//! email: "ada@example.com".into(),
149//! nickname: None,
150//! audit: Audit { created_by: 9 },
151//! }
152//! );
153//! ```
154//!
155//! The emitted body is the shape `keelson_exec::FromRow` documents and
156//! keelson-gen already emits by hand — one `row.take("column")?` per field, by
157//! name. By name, not by position, so it survives column reordering and
158//! `SELECT *` drift; `take` rather than `get`, so `String`s and blobs move out
159//! of the row instead of cloning. Errors keep their column name, because
160//! `Row::take` puts it there.
161//!
162//! ## Field options
163//!
164//! - `#[keelson(rename = "column")]` — read that column instead of the one
165//! named after the field.
166//! - `#[keelson(flatten)]` — read the field's own type out of the same row,
167//! through its `FromRow` impl. Nested structs, in other words, and it costs
168//! one line of generated code because `FromRow::from_row` already takes the
169//! whole row.
170//!
171//! ## What it refuses, and why
172//!
173//! - **Tuple structs and unit structs.** Mapping is by name, and unnamed
174//! fields have none. Tuples up to arity 8 *already* implement `FromRow`
175//! positionally, so the alternative is to delete the struct, and the error
176//! says so with your own field types substituted in.
177//! - **Enums.** Which variant a row is depends on a discriminator column only
178//! you can name.
179//! - **Structs with a lifetime parameter.** A row is decoded into owned
180//! `Value`s and every field is taken out of it by value, so a field
181//! borrowing from the row could not outlive the mapping — refused for the
182//! same "an unsatisfiable `where` clause compiles" reason as above.
183//! - **`rename` together with `flatten`.** One names a single column, the
184//! other reads many.
185//! - **Two fields reading the same column.** `take` consumes: the value moves
186//! out and NULL is left behind, so the second field would silently decode
187//! NULL. That is a bug the derive can see, so it is a compile error rather
188//! than a mystery at runtime.
189//! - **`prefix = "..."`.** Deliberately not implemented, and the error says
190//! why rather than pretending the option does not exist. Stripping a prefix
191//! means rebuilding the row under different column names before handing it
192//! to the nested `FromRow`; the nested impl then reports failures against
193//! the *stripped* names ("no column \"id\"" when the result set says
194//! "author_id"), and the available-columns list in the error is the stripped
195//! set too. An honest prefix needs a prefix-aware view inside
196//! `keelson_exec::Row`, which is a change to the execution layer, not to a
197//! macro. Until then: `flatten` plus `rename` on the nested fields is
198//! explicit, exact, and reports real column names.
199//!
200//! # Getting at the derives
201//!
202//! They are re-exported by keelson-core behind its `macros` feature, which is
203//! how a user reaches them:
204//!
205//! ```toml
206//! keelson-core = { version = "…", features = ["macros"] }
207//! ```
208//!
209//! `use keelson_core::Bind;` then imports the *derive*; `keelson_exec::Bind`
210//! is the *trait*. Importing both is fine — they live in different namespaces
211//! — and the trait is rarely named directly, since it is a blanket alias.
212//!
213//! # What the generated code depends on
214//!
215//! Nothing is imported into your scope, and nothing you write must be: every
216//! path the expansion names is absolute.
217//!
218//! - `#[derive(Bind)]` names only `::keelson_core` (`ToValue`, `FromValue`,
219//! `Value`, `Error`).
220//! - `#[derive(FromRow)]` names only `::keelson_exec` (`FromRow`, `Row`,
221//! `ExecError`) — plus `::keelson_core` in the one case where a bound must
222//! be written out: a *generic* struct, whose emitted `where` clause says
223//! `FieldTy: ::keelson_core::FromValue`. A generic `FromRow` struct
224//! therefore needs keelson-core in its dependencies; a non-generic one does
225//! not.
226//!
227//! Both crates are dependencies you already have — `FromRow` cannot exist
228//! without keelson-exec, and keelson-exec depends on keelson-core.
229
230#![warn(missing_docs)]
231
232mod attr;
233mod bind;
234mod from_row;
235mod sql;
236
237use proc_macro::TokenStream;
238use syn::{DeriveInput, parse_macro_input};
239
240/// Implement `keelson_core::ToValue` and `keelson_core::FromValue` for a
241/// newtype by delegating to its single field — the pair
242/// `keelson_exec::Bind` requires, and so the pair a keelson-gen column
243/// override must satisfy.
244///
245/// ```
246/// use keelson_core::{Bind, FromValue as _, ToValue as _, Value};
247///
248/// #[derive(Debug, PartialEq, Bind)]
249/// struct UserId(i64);
250///
251/// const _: () = keelson_exec::assert_bind::<UserId>();
252/// assert_eq!(UserId(7).to_value(), Value::I64(7));
253/// assert_eq!(UserId::from_value(Value::I64(7)).unwrap(), UserId(7));
254/// ```
255///
256/// Single-field structs only — tuple or named, generic or not. Multi-field
257/// structs, enums, unions and unit structs are compile errors naming the
258/// restriction; see the crate documentation for the reasoning.
259#[proc_macro_derive(Bind, attributes(keelson))]
260pub fn derive_bind(input: TokenStream) -> TokenStream {
261 let input = parse_macro_input!(input as DeriveInput);
262 bind::derive(input)
263 .unwrap_or_else(syn::Error::into_compile_error)
264 .into()
265}
266
267/// Implement `keelson_exec::FromRow` by reading one column per field, by
268/// name.
269///
270/// `#[keelson(rename = "column")]` reads a differently named column;
271/// `#[keelson(flatten)]` reads a nested struct out of the same row. Named
272/// fields only. See the crate documentation for the full list of what is
273/// refused and why.
274#[proc_macro_derive(FromRow, attributes(keelson))]
275pub fn derive_from_row(input: TokenStream) -> TokenStream {
276 let input = parse_macro_input!(input as DeriveInput);
277 from_row::derive(input)
278 .unwrap_or_else(syn::Error::into_compile_error)
279 .into()
280}
281
282/// The scanner behind each dialect's `sql!`. Not called directly: the dialect
283/// crate's `sql!` forwards to it with its own `raw_query` as the first
284/// argument, which is what makes `keelson_sqlite::sql!("…")` know its dialect.
285///
286/// ```text
287/// sql_with!(keelson_sqlite::raw_query, "SELECT … WHERE id = {user_id}")
288/// // => keelson_sqlite::raw_query("SELECT … WHERE id = ?").bind(user_id)
289/// ```
290///
291/// See the `sql` module's documentation for the grammar and for what the
292/// rewriting is worth.
293#[doc(hidden)]
294#[proc_macro]
295pub fn sql_with(input: TokenStream) -> TokenStream {
296 let input = parse_macro_input!(input as sql::Input);
297 sql::expand(input)
298 .unwrap_or_else(syn::Error::into_compile_error)
299 .into()
300}