premix_macros/
lib.rs

1use proc_macro::TokenStream;
2use quote::quote;
3use syn::{Data, DeriveInput, Field, Fields, parse_macro_input};
4
5mod relations;
6
7#[proc_macro_derive(Model, attributes(has_many, belongs_to, premix))]
8pub fn derive_model(input: TokenStream) -> TokenStream {
9    let input = parse_macro_input!(input as DeriveInput);
10
11    let impl_block = match generate_generic_impl(&input) {
12        Ok(tokens) => tokens,
13        Err(err) => return TokenStream::from(err.to_compile_error()),
14    };
15
16    let rel_block = match relations::impl_relations(&input) {
17        Ok(tokens) => tokens,
18        Err(err) => return TokenStream::from(err.to_compile_error()),
19    };
20
21    TokenStream::from(quote! {
22        #impl_block
23        #rel_block
24    })
25}
26
27fn generate_generic_impl(input: &DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
28    let struct_name = &input.ident;
29    let table_name = struct_name.to_string().to_lowercase() + "s";
30
31    let all_fields = if let Data::Struct(data) = &input.data {
32        if let Fields::Named(fields) = &data.fields {
33            &fields.named
34        } else {
35            return Err(syn::Error::new_spanned(
36                &data.fields,
37                "Premix Model only supports structs with named fields",
38            ));
39        }
40    } else {
41        return Err(syn::Error::new_spanned(
42            input,
43            "Premix Model only supports structs",
44        ));
45    };
46
47    let mut db_fields = Vec::new();
48    let mut ignored_field_idents = Vec::new();
49
50    for field in all_fields {
51        if is_ignored(field) {
52            ignored_field_idents.push(field.ident.as_ref().unwrap());
53        } else {
54            db_fields.push(field);
55        }
56    }
57
58    let field_idents: Vec<_> = db_fields
59        .iter()
60        .map(|f| f.ident.as_ref().unwrap())
61        .collect();
62    let field_types: Vec<_> = db_fields.iter().map(|f| &f.ty).collect();
63    let field_indices: Vec<_> = (0..db_fields.len()).collect();
64    let field_names: Vec<_> = field_idents.iter().map(|id| id.to_string()).collect();
65    let field_idents_len = field_idents.len();
66
67    let eager_load_body = relations::generate_eager_load_body(input)?;
68    let has_version = field_names.contains(&"version".to_string());
69    let has_soft_delete = field_names.contains(&"deleted_at".to_string());
70
71    let update_impl = if has_version {
72        quote! {
73            async fn update<'a, E>(&mut self, executor: E) -> Result<premix_core::UpdateResult, premix_core::sqlx::Error>
74            where
75                E: premix_core::IntoExecutor<'a, DB = DB>
76            {
77                let mut executor = executor.into_executor();
78                let table_name = Self::table_name();
79                let set_clause = vec![ #( format!("{} = {}", #field_names, <DB as premix_core::SqlDialect>::placeholder(1 + #field_indices)) ),* ].join(", ");
80                let id_p = <DB as premix_core::SqlDialect>::placeholder(1 + #field_idents_len);
81                let ver_p = <DB as premix_core::SqlDialect>::placeholder(2 + #field_idents_len);
82                let sql = format!(
83                    "UPDATE {} SET {}, version = version + 1 WHERE id = {} AND version = {}",
84                    table_name, set_clause, id_p, ver_p
85                );
86
87                let mut query = premix_core::sqlx::query::<DB>(&sql)
88                    #( .bind(&self.#field_idents) )*
89                    .bind(&self.id)
90                    .bind(&self.version);
91
92                let result = executor.execute(query).await?;
93
94                if <DB as premix_core::SqlDialect>::rows_affected(&result) == 0 {
95                    let exists_p = <DB as premix_core::SqlDialect>::placeholder(1);
96                    let exists_sql = format!("SELECT id FROM {} WHERE id = {}", table_name, exists_p);
97                    let exists_query = premix_core::sqlx::query_as::<DB, (i32,)>(&exists_sql).bind(&self.id);
98                    let exists = executor.fetch_optional(exists_query).await?;
99
100                    if exists.is_none() {
101                        Ok(premix_core::UpdateResult::NotFound)
102                    } else {
103                        Ok(premix_core::UpdateResult::VersionConflict)
104                    }
105                } else {
106                    self.version += 1;
107                    Ok(premix_core::UpdateResult::Success)
108                }
109            }
110        }
111    } else {
112        quote! {
113            async fn update<'a, E>(&mut self, executor: E) -> Result<premix_core::UpdateResult, premix_core::sqlx::Error>
114            where
115                E: premix_core::IntoExecutor<'a, DB = DB>
116            {
117                let mut executor = executor.into_executor();
118                let table_name = Self::table_name();
119                let set_clause = vec![ #( format!("{} = {}", #field_names, <DB as premix_core::SqlDialect>::placeholder(1 + #field_indices)) ),* ].join(", ");
120                let id_p = <DB as premix_core::SqlDialect>::placeholder(1 + #field_idents_len);
121                let sql = format!("UPDATE {} SET {} WHERE id = {}", table_name, set_clause, id_p);
122
123                let mut query = premix_core::sqlx::query::<DB>(&sql)
124                    #( .bind(&self.#field_idents) )*
125                    .bind(&self.id);
126
127                let result = executor.execute(query).await?;
128
129                if <DB as premix_core::SqlDialect>::rows_affected(&result) == 0 {
130                    Ok(premix_core::UpdateResult::NotFound)
131                } else {
132                    Ok(premix_core::UpdateResult::Success)
133                }
134            }
135        }
136    };
137
138    let delete_impl = if has_soft_delete {
139        quote! {
140            async fn delete<'a, E>(&mut self, executor: E) -> Result<(), premix_core::sqlx::Error>
141            where
142                E: premix_core::IntoExecutor<'a, DB = DB>
143            {
144                let mut executor = executor.into_executor();
145                let table_name = Self::table_name();
146                let id_p = <DB as premix_core::SqlDialect>::placeholder(1);
147                let sql = format!("UPDATE {} SET deleted_at = {} WHERE id = {}", table_name, <DB as premix_core::SqlDialect>::current_timestamp_fn(), id_p);
148
149                let query = premix_core::sqlx::query::<DB>(&sql).bind(&self.id);
150                executor.execute(query).await?;
151
152                self.deleted_at = Some("DELETED".to_string());
153                Ok(())
154            }
155            fn has_soft_delete() -> bool { true }
156        }
157    } else {
158        quote! {
159            async fn delete<'a, E>(&mut self, executor: E) -> Result<(), premix_core::sqlx::Error>
160            where
161                E: premix_core::IntoExecutor<'a, DB = DB>
162            {
163                let mut executor = executor.into_executor();
164                let table_name = Self::table_name();
165                let id_p = <DB as premix_core::SqlDialect>::placeholder(1);
166                let sql = format!("DELETE FROM {} WHERE id = {}", table_name, id_p);
167
168                let query = premix_core::sqlx::query::<DB>(&sql).bind(&self.id);
169                executor.execute(query).await?;
170
171                Ok(())
172            }
173            fn has_soft_delete() -> bool { false }
174        }
175    };
176
177    let mut related_model_bounds = Vec::new();
178    for field in all_fields {
179        for attr in &field.attrs {
180            if (attr.path().is_ident("has_many") || attr.path().is_ident("belongs_to"))
181                && let Ok(related_ident) = attr.parse_args::<syn::Ident>()
182            {
183                related_model_bounds.push(quote! { #related_ident: premix_core::Model<DB> });
184            }
185        }
186    }
187
188    // Generic Implementation
189    Ok(quote! {
190        impl<'r, R> premix_core::sqlx::FromRow<'r, R> for #struct_name
191        where
192            R: premix_core::sqlx::Row,
193            R::Database: premix_core::sqlx::Database,
194            #(
195                #field_types: premix_core::sqlx::Type<R::Database> + premix_core::sqlx::Decode<'r, R::Database>,
196            )*
197            for<'c> &'c str: premix_core::sqlx::ColumnIndex<R>,
198        {
199            fn from_row(row: &'r R) -> Result<Self, premix_core::sqlx::Error> {
200                use premix_core::sqlx::Row;
201                Ok(Self {
202                    #(
203                        #field_idents: row.try_get(#field_names)?,
204                    )*
205                    #(
206                        #ignored_field_idents: None,
207                    )*
208                })
209            }
210        }
211
212        #[premix_core::async_trait::async_trait]
213        impl<DB> premix_core::Model<DB> for #struct_name
214        where
215            DB: premix_core::SqlDialect,
216            for<'c> &'c str: premix_core::sqlx::ColumnIndex<DB::Row>,
217            usize: premix_core::sqlx::ColumnIndex<DB::Row>,
218            for<'q> <DB as premix_core::sqlx::Database>::Arguments<'q>: premix_core::sqlx::IntoArguments<'q, DB>,
219            for<'c> &'c mut <DB as premix_core::sqlx::Database>::Connection: premix_core::sqlx::Executor<'c, Database = DB>,
220            i32: premix_core::sqlx::Type<DB> + for<'q> premix_core::sqlx::Encode<'q, DB> + for<'r> premix_core::sqlx::Decode<'r, DB>,
221            i64: premix_core::sqlx::Type<DB> + for<'q> premix_core::sqlx::Encode<'q, DB> + for<'r> premix_core::sqlx::Decode<'r, DB>,
222            String: premix_core::sqlx::Type<DB> + for<'q> premix_core::sqlx::Encode<'q, DB> + for<'r> premix_core::sqlx::Decode<'r, DB>,
223            bool: premix_core::sqlx::Type<DB> + for<'q> premix_core::sqlx::Encode<'q, DB> + for<'r> premix_core::sqlx::Decode<'r, DB>,
224            Option<String>: premix_core::sqlx::Type<DB> + for<'q> premix_core::sqlx::Encode<'q, DB> + for<'r> premix_core::sqlx::Decode<'r, DB>,
225            #( #related_model_bounds, )*
226        {
227            fn table_name() -> &'static str {
228                #table_name
229            }
230
231            fn create_table_sql() -> String {
232                let mut cols = vec!["id ".to_string() + <DB as premix_core::SqlDialect>::auto_increment_pk()];
233                #(
234                    if #field_names != "id" {
235                        let field_name: &str = #field_names;
236                        let sql_type = if field_name.ends_with("_id") {
237                            <DB as premix_core::SqlDialect>::int_type()
238                        } else {
239                            match field_name {
240                                "name" | "title" | "status" | "email" | "role" => <DB as premix_core::SqlDialect>::text_type(),
241                                "age" | "version" | "price" | "balance" => <DB as premix_core::SqlDialect>::int_type(),
242                                "is_active" => <DB as premix_core::SqlDialect>::bool_type(),
243                                "deleted_at" => <DB as premix_core::SqlDialect>::text_type(),
244                                _ => <DB as premix_core::SqlDialect>::text_type(),
245                            }
246                        };
247                        cols.push(format!("{} {}", #field_names, sql_type));
248                    }
249                )*
250                format!("CREATE TABLE IF NOT EXISTS {} ({})", #table_name, cols.join(", "))
251            }
252
253            fn list_columns() -> Vec<String> {
254                vec![ #( #field_names.to_string() ),* ]
255            }
256
257            async fn save<'a, E>(&mut self, executor: E) -> Result<(), premix_core::sqlx::Error>
258            where
259                E: premix_core::IntoExecutor<'a, DB = DB>
260            {
261                let mut executor = executor.into_executor();
262                use premix_core::ModelHooks;
263                self.before_save().await?;
264
265                // Filter out 'id' and 'version' for INSERT
266                let columns: Vec<&str> = vec![ #( #field_names ),* ]
267                    .into_iter()
268                    .filter(|&c| {
269                        if c == "id" { return self.id != 0; }
270                        true
271                    })
272                    .collect();
273
274                let placeholders = (1..=columns.len())
275                    .map(|i| <DB as premix_core::SqlDialect>::placeholder(i))
276                    .collect::<Vec<_>>()
277                    .join(", ");
278
279                let sql = format!("INSERT INTO {} ({}) VALUES ({})", #table_name, columns.join(", "), placeholders);
280
281                let mut query = premix_core::sqlx::query::<DB>(&sql);
282
283                // Bind only non-id/version fields
284                #(
285                    if #field_names != "id" {
286                        query = query.bind(&self.#field_idents);
287                    } else {
288                        if self.id != 0 {
289                            query = query.bind(&self.id);
290                        }
291                    }
292                )*
293
294                let result = executor.execute(query).await?;
295
296                // Sync the ID from Database
297                let last_id = <DB as premix_core::SqlDialect>::last_insert_id(&result);
298                if last_id > 0 {
299                     self.id = last_id as i32;
300                }
301
302                self.after_save().await?;
303                Ok(())
304            }
305
306            #update_impl
307            #delete_impl
308
309            async fn find_by_id<'a, E>(executor: E, id: i32) -> Result<Option<Self>, premix_core::sqlx::Error>
310            where
311                E: premix_core::IntoExecutor<'a, DB = DB>
312            {
313                let mut executor = executor.into_executor();
314                let p = <DB as premix_core::SqlDialect>::placeholder(1);
315                let mut where_clause = format!("WHERE id = {}", p);
316                if Self::has_soft_delete() {
317                    where_clause.push_str(" AND deleted_at IS NULL");
318                }
319                let sql = format!("SELECT * FROM {} {} LIMIT 1", #table_name, where_clause);
320                let query = premix_core::sqlx::query_as::<DB, Self>(&sql).bind(id);
321
322                executor.fetch_optional(query).await
323            }
324
325            async fn eager_load<'a, E>(models: &mut [Self], relation: &str, executor: E) -> Result<(), premix_core::sqlx::Error>
326            where
327                E: premix_core::IntoExecutor<'a, DB = DB>
328            {
329                let mut executor = executor.into_executor();
330                #eager_load_body
331            }
332        }
333    })
334}
335
336fn is_ignored(field: &Field) -> bool {
337    for attr in &field.attrs {
338        if attr.path().is_ident("premix")
339            && let Ok(meta) = attr.parse_args::<syn::Ident>()
340            && meta == "ignore"
341        {
342            return true;
343        }
344    }
345    false
346}