sqlorm_macros/
lib.rs

1#![cfg(any(feature = "postgres", feature = "sqlite"))]
2use proc_macro::TokenStream;
3use quote::format_ident;
4use syn::parse_macro_input;
5use syn::{Field, Fields, ItemStruct};
6
7mod naming;
8mod traits;
9
10use crate::entity::EntityStruct;
11mod entity;
12mod qb;
13mod sql;
14
15mod attrs;
16mod gen_columns;
17mod relations;
18
19#[proc_macro_derive(Entity, attributes(sql))]
20pub fn entity(input: TokenStream) -> TokenStream {
21    let es = parse_macro_input!(input as EntityStruct);
22    entity::handle(es).into()
23}
24
25/// Transforms a struct into a database entity with ORM capabilities.
26///
27/// This is the primary way to define database entities in SQLOrm. The macro automatically
28/// generates all necessary database operations like `save()`, `find_by_id()`, and query methods.
29///
30/// # Basic Usage
31///
32/// ```rust,ignore
33/// use sqlorm::table;
34///
35/// #[table]
36/// struct User {
37///     #[sql(pk)]
38///     id: i64,
39///     email: String,
40///     name: String,
41/// }
42/// ```
43///
44/// # Custom Table Name
45///
46/// ```rust,ignore
47/// #[table(name = "app_users")]
48/// struct User {
49///     #[sql(pk)]
50///     id: i64,
51///     email: String,
52/// }
53/// ```
54///
55/// After applying this macro, you can use standard ORM operations:
56/// - `user.save(&pool).await`
57/// - `User::query().filter(...).fetch_all(&pool).await`
58///
59/// With feature `extra-traits` enable
60/// - `User::find_by_id(&pool, 1).await`
61/// - `User::find_by_email(&pool, "user@example.com".to_string()).await`
62///
63/// # Supported Field Attributes
64///
65/// ## `#[sql(...)]` Attributes
66///
67/// - **`pk`** - Mark field as primary key (required, exactly one per struct)
68/// - **`unique`** - Mark field as unique (generates `find_by_*` methods)
69/// - **`skip`** - Exclude field from SQL operations
70/// - **`timestamp(field_name, factory)`** - Automatic timestamp management:
71///   - `created_at` - Set on insert
72///   - `updated_at` - Set on insert and update  
73///   - `deleted_at` - For soft deletes
74/// - **`relation(...)`** - Define relationships:
75///   - `belongs_to -> SomeOtherStruct, relation = "some_other_struct", on = field`
76///   - `has_many -> SomeOtherStruct, relation = "some_other_structs", on = field`
77///   - `has_one -> SomeOtherStruct, relation = "some_other_struct", on = field`
78///
79///
80/// # Complete Example
81///
82/// ```rust,ignore
83/// use chrono::{DateTime, Utc};
84/// use sqlorm::table;
85///
86/// #[derive(Debug, Clone, Default)]
87/// #[table(name = "users")]
88/// pub struct User {
89///     #[sql(pk)]
90///     #[sql(relation(has_many -> Post, relation = "posts", on = user_id))]
91///     pub id: i64,
92///     
93///     #[sql(unique)]
94///     pub email: String,
95///     
96///     #[sql(unique)]
97///     pub username: String,
98///     
99///     pub first_name: String,
100///     pub last_name: String,
101///     
102///     #[sql(skip)]
103///     pub posts: Option<Vec<Post>>,
104///     
105///     #[sql(timestamp(created_at, chrono::Utc::now()))]
106///     pub created_at: DateTime<Utc>,
107///     
108///     #[sql(timestamp(updated_at, chrono::Utc::now()))]
109///     pub updated_at: DateTime<Utc>,
110/// }
111///
112/// #[derive(Debug, Clone, Default)]
113/// #[table(name = "posts")]
114/// pub struct Post {
115///     #[sql(pk)]
116///     pub id: i64,
117///     
118///     pub title: String,
119///     pub content: String,
120///     
121///     #[sql(relation(belongs_to -> User, relation = "author", on = id))]
122///     pub user_id: i64,
123///     
124///     #[sql(skip)]
125///     pub author: Option<User>,
126/// }
127/// ```
128///
129#[proc_macro_attribute]
130pub fn table(args: TokenStream, input: TokenStream) -> TokenStream {
131    let mut model = parse_macro_input!(input as ItemStruct);
132
133    let mut existing_derives = Vec::new();
134    model.attrs.retain(|attr| {
135        if attr.path().is_ident("derive") {
136            existing_derives.push(attr.clone());
137            false
138        } else {
139            true
140        }
141    });
142
143    let table_name = if args.is_empty() {
144        model.ident.to_string().to_lowercase()
145    } else {
146        let meta_list: syn::punctuated::Punctuated<syn::MetaNameValue, syn::Token![,]> =
147            syn::parse_macro_input!(args with syn::punctuated::Punctuated::parse_terminated);
148
149        let mut table_name = model.ident.to_string().to_lowercase();
150        for meta in meta_list {
151            if meta.path.is_ident("name") {
152                if let syn::Expr::Lit(syn::ExprLit {
153                    lit: syn::Lit::Str(lit_str),
154                    ..
155                }) = meta.value
156                {
157                    table_name = lit_str.value();
158                    break;
159                }
160            }
161        }
162        table_name
163    };
164
165    inject_relation_fields(&mut model).expect("Failed to inject relation fields");
166
167    // reapply the derive attributes after field injection
168    quote::quote! {
169        #(#existing_derives)*
170        #[derive(::sqlorm::sqlx::FromRow,::sqlorm::Entity)]
171        #[sql(name = #table_name)]
172        #model
173    }
174    .into()
175}
176
177/// Scans struct fields for relation attributes and automatically injects
178/// corresponding relation fields (e.g., posts: Option<Vec<Post>>) with proper attributes.
179/// Throws compile errors if the relation field names are already used.
180/// Uses existing attribute parsing logic to extract relation information.
181fn inject_relation_fields(model: &mut ItemStruct) -> syn::Result<()> {
182    use crate::attrs::parse_entity_field;
183    use crate::relations::RelationType;
184
185    let mut relations_to_inject = Vec::new();
186
187    if let Fields::Named(ref fields) = model.fields {
188        for field in fields.named.iter() {
189            let entity_field = parse_entity_field(field)?;
190            if let Some(field_relations) = entity_field.relations {
191                relations_to_inject.extend(field_relations);
192            }
193        }
194    }
195
196    if let Fields::Named(ref mut fields) = model.fields {
197        for relation in &relations_to_inject {
198            for existing_field in fields.named.iter() {
199                if let Some(field_name) = &existing_field.ident {
200                    if field_name.to_string() == relation.relation_name {
201                        return Err(syn::Error::new_spanned(
202                            field_name,
203                            format!(
204                                "Field '{}' is reserved for auto-generated relation field. Remove this field as it will be injected automatically based on relation attributes.",
205                                relation.relation_name
206                            ),
207                        ));
208                    }
209                }
210            }
211        }
212
213        for relation in relations_to_inject {
214            let field_ident = format_ident!("{}", relation.relation_name);
215            let field_type: syn::Type = match relation.kind {
216                RelationType::HasMany => {
217                    let other_type = &relation.other;
218                    syn::parse_quote! { Option<Vec<#other_type>> }
219                }
220                RelationType::BelongsTo | RelationType::HasOne => {
221                    let other_type = &relation.other;
222                    syn::parse_quote! { Option<#other_type> }
223                }
224            };
225
226            let new_field: Field = syn::parse_quote! {
227                #[sql(skip)]
228                #[sqlx(skip)]
229                pub #field_ident: #field_type
230            };
231            fields.named.push(new_field);
232        }
233    }
234    Ok(())
235}