dactyl_db_macros/lib.rs
1//! `dactyl::query!` proc macro.
2//!
3//! Lexically analyzes the SQL literal at compile time so:
4//
5//! 1. Hard-coded constructs are visible to the analyzer before the crate
6//! is built.
7//! 2. The literal is rewritten (currently identity) at compile time so the
8//! runtime path stays allocation-light.
9//! 3. Empty literals fail to compile with a clear message.
10//!
11//! The expanded form returns a `String` containing the (rewritten) SQL.
12//! Callers wire it into `dactyl::query` directly:
13//!
14//! ```ignore
15//! let rows = dactyl_db::query(&dactyl_db::query!("select id, title from todos"), &[])?;
16//! ```
17
18use proc_macro::TokenStream;
19use quote::quote;
20use syn::{parse_macro_input, LitStr};
21
22/// `dactyl::query!("literal")` — returns the rewritten SQL as a `String`.
23#[proc_macro]
24pub fn query(input: TokenStream) -> TokenStream {
25 let literal = parse_macro_input!(input as LitStr);
26 let text = literal.value();
27
28 if text.trim().is_empty() {
29 return syn::Error::new_spanned(literal, "`query!` requires a non-empty SQL literal")
30 .to_compile_error()
31 .into();
32 }
33
34 let rewritten = rewrite(&text);
35
36 let expanded = quote! {{
37 // Compile-time lexer: hard-fails on unparseable input. The runtime
38 // call is a no-op identity rewrite, kept so the contract is uniform
39 // with non-literal queries.
40 let __text: &str = #rewritten;
41 ::dactyl_db::__private::analyze(__text).sql
42 }};
43
44 expanded.into()
45}
46
47/// Identity rewrite — placeholder for the real rewriter. Kept as a
48/// function so swapping in the structured rewriter is a one-line change.
49fn rewrite(input: &str) -> String {
50 input.to_string()
51}