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}