Skip to main content

pgorm_derive/
lib.rs

1//! Derive macros for pgorm
2//!
3//! Provides `#[derive(FromRow)]` and `#[derive(Model)]` macros.
4
5use proc_macro::TokenStream;
6use syn::{DeriveInput, parse_macro_input};
7
8mod common;
9mod from_row;
10mod insert_model;
11mod model;
12mod sql_ident;
13mod update_model;
14
15/// Derive `FromRow` trait for a struct.
16///
17/// # Example
18///
19/// ```ignore
20/// use pgorm::FromRow;
21///
22/// #[derive(FromRow)]
23/// struct User {
24///     id: i64,
25///     username: String,
26///     #[orm(column = "email_address")]
27///     email: Option<String>,
28/// }
29/// ```
30///
31/// # Attributes
32///
33/// - `#[orm(column = "name")]` - Map field to a different column name
34#[proc_macro_derive(FromRow, attributes(orm))]
35pub fn derive_from_row(input: TokenStream) -> TokenStream {
36    let input = parse_macro_input!(input as DeriveInput);
37    from_row::expand(input)
38        .unwrap_or_else(|e| e.to_compile_error())
39        .into()
40}
41
42/// Derive `Model` metadata for a struct.
43///
44/// # Example
45///
46/// ```ignore
47/// use pgorm::Model;
48///
49/// #[derive(Model)]
50/// #[orm(table = "users")]
51/// struct User {
52///     #[orm(id)]
53///     user_id: i64,
54///     username: String,
55///     email: Option<String>,
56/// }
57/// ```
58///
59/// # Generated
60///
61/// - `TABLE: &'static str` - Table name
62/// - `COL_*: &'static str` - Column name constants
63/// - `SELECT_LIST: &'static str` - Comma-separated column list
64/// - `fn select_list_as(alias: &str) -> String` - Aliased column list for JOINs
65///
66/// # Attributes
67///
68/// Struct-level:
69///
70/// - `#[orm(table = "name")]` - Specify table name (required)
71/// - `#[orm(join(table = "...", on = "...", type = "inner|left|right|full|cross"))]` - Add JOINs (optional, repeatable)
72/// - `#[orm(has_many(ChildType, foreign_key = "...", as = "..."))]` - Generate select_has_many helpers (optional, repeatable)
73/// - `#[orm(belongs_to(ParentType, foreign_key = "...", as = "..."))]` - Generate select_belongs_to helpers (optional, repeatable)
74///
75/// Field-level:
76///
77/// - `#[orm(id)]` - Mark field as primary key
78/// - `#[orm(column = "name")]` - Map field to a different column name
79/// - `#[orm(table = "name")]` - Mark field as coming from a joined table (for view/join models)
80#[proc_macro_derive(Model, attributes(orm))]
81pub fn derive_model(input: TokenStream) -> TokenStream {
82    let input = parse_macro_input!(input as DeriveInput);
83    model::expand(input)
84        .unwrap_or_else(|e| e.to_compile_error())
85        .into()
86}
87
88/// Derive `ViewModel` metadata for a struct.
89///
90/// This is an alias of `Model` intended to express that the type is a read/view model
91/// (optionally including JOINs), while write models are derived separately.
92#[proc_macro_derive(ViewModel, attributes(orm))]
93pub fn derive_view_model(input: TokenStream) -> TokenStream {
94    let input = parse_macro_input!(input as DeriveInput);
95    model::expand(input)
96        .unwrap_or_else(|e| e.to_compile_error())
97        .into()
98}
99
100/// Derive `InsertModel` helpers for inserting into a table.
101///
102/// # Attributes
103///
104/// Struct-level:
105///
106/// - `#[orm(table = "name")]` - Specify table name (required)
107/// - `#[orm(returning = "TypePath")]` - Enable `insert_returning` helpers (optional)
108/// - Conflict handling (Postgres `ON CONFLICT`):
109///   - `#[orm(conflict_target = "col1,col2")]` - conflict target columns (optional)
110///   - `#[orm(conflict_constraint = "constraint_name")]` - conflict constraint (optional)
111///   - `#[orm(conflict_update = "col1,col2")]` - columns to update on conflict (optional)
112/// - Multi-table write graphs (advanced): function-style attrs like `#[orm(has_many(...))]`,
113///   `#[orm(belongs_to(...))]`, `#[orm(before_insert(...))]`. See `docs/design/multi-table-writes-final.md`.
114///
115/// Field-level:
116///
117/// - `#[orm(id)]` - Mark field as primary key (optional)
118/// - `#[orm(skip_insert)]` - Never include this field in INSERT
119/// - `#[orm(default)]` - Use SQL `DEFAULT` for this field
120/// - `#[orm(auto_now_add)]` - Use `NOW()` for this field on insert
121/// - `#[orm(column = "name")]` / `#[orm(table = "name")]` - Override column/table mapping (optional)
122#[proc_macro_derive(InsertModel, attributes(orm))]
123pub fn derive_insert_model(input: TokenStream) -> TokenStream {
124    let input = parse_macro_input!(input as DeriveInput);
125    insert_model::expand(input)
126        .unwrap_or_else(|e| e.to_compile_error())
127        .into()
128}
129
130/// Derive `UpdateModel` helpers for updating a table (patch-style).
131///
132/// # Attributes
133///
134/// Struct-level:
135///
136/// - `#[orm(table = "name")]` - Specify table name (required)
137/// - One of:
138///   - `#[orm(id_column = "id")]` - Explicit primary key column
139///   - `#[orm(model = "TypePath")]` - Derive primary key column from a `Model`
140///   - `#[orm(returning = "TypePath")]` where `TypePath::ID` exists
141/// - `#[orm(returning = "TypePath")]` - Enable `update_by_id_returning` helpers (optional)
142/// - Multi-table write graphs (advanced): see `docs/design/multi-table-writes-final.md`.
143///
144/// Field-level:
145///
146/// - `#[orm(skip_update)]` - Never include this field in UPDATE
147/// - `#[orm(default)]` - Use SQL `DEFAULT` for this field
148/// - `#[orm(auto_now)]` - Use `NOW()` for this field on update
149/// - `#[orm(column = "name")]` / `#[orm(table = "name")]` - Override column/table mapping (optional)
150#[proc_macro_derive(UpdateModel, attributes(orm))]
151pub fn derive_update_model(input: TokenStream) -> TokenStream {
152    let input = parse_macro_input!(input as DeriveInput);
153    update_model::expand(input)
154        .unwrap_or_else(|e| e.to_compile_error())
155        .into()
156}