Skip to main content

premix_macros/
lib.rs

1use proc_macro::TokenStream;
2use quote::quote;
3use syn::{
4    Attribute, Data, DeriveInput, Field, Fields, Ident, LitStr, Token, parse_macro_input,
5    punctuated::Punctuated,
6};
7
8mod relations;
9mod static_query;
10
11/// Compile-time query macro for true Zero-Overhead SQL generation.
12///
13/// This macro generates SQL at compile time, achieving 0% overhead compared to raw sqlx.
14///
15/// # Example
16///
17/// ```ignore
18/// use premix_orm::prelude::*;
19///
20/// // FIND (SELECT + LIMIT 1)
21/// let user = premix_query!(User, FIND, filter_eq("id", 1))
22///     .fetch_one(&pool).await?;
23///
24/// // INSERT
25/// let new_user = premix_query!(
26///     User, INSERT,
27///     set("name", "Bob"),
28///     set("age", 30)
29/// ).fetch_one(&pool).await?;
30///
31/// // UPDATE
32/// premix_query!(
33///     User, UPDATE,
34///     set("age", 31),
35///     filter_eq("name", "Bob")
36/// ).execute(&pool).await?;
37///
38/// // DELETE
39/// premix_query!(
40///     User, DELETE,
41///     filter_eq("id", 1)
42/// ).execute(&pool).await?;
43///
44/// // UPDATE + RETURNING *
45/// let updated = premix_query!(
46///     User, UPDATE,
47///     set("age", 32),
48///     filter_eq("name", "Bob"),
49///     returning_all()
50/// ).fetch_one(&pool).await?;
51/// ```
52///
53/// # Supported Operations
54///
55/// - `SELECT` - Generate SELECT query
56/// - `FIND` - Generate SELECT + LIMIT 1 query
57/// - `INSERT` - Generate INSERT query (with `set` assignments)
58/// - `UPDATE` - Generate UPDATE query (with `set` assignments and filters)
59/// - `DELETE` - Generate DELETE query (with filters)
60///
61/// # Supported Clauses
62///
63/// - `filter_eq/ne/gt/lt/gte/lte("col", val)` - WHERE clause conditions
64/// - `set("col", val)` - SET/VALUES clause for INSERT/UPDATE
65/// - `limit(N)` - LIMIT N
66/// - `offset(N)` - OFFSET N
67/// - `returning_all()` - Append `RETURNING *` for UPDATE/DELETE
68#[proc_macro]
69pub fn premix_query(input: TokenStream) -> TokenStream {
70    let input = parse_macro_input!(input as static_query::StaticQueryInput);
71    TokenStream::from(static_query::generate_static_query(input))
72}
73
74#[proc_macro_derive(Model, attributes(has_many, belongs_to, premix))]
75pub fn derive_model(input: TokenStream) -> TokenStream {
76    let input = parse_macro_input!(input as DeriveInput);
77    match derive_model_impl(&input) {
78        Ok(tokens) => TokenStream::from(tokens),
79        Err(err) => TokenStream::from(err.to_compile_error()),
80    }
81}
82
83fn derive_model_impl(input: &DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
84    let impl_block = generate_generic_impl(input)?;
85    let rel_block = relations::impl_relations(input)?;
86    Ok(quote! {
87        #impl_block
88        #rel_block
89    })
90}
91
92fn generate_generic_impl(input: &DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
93    let struct_name = &input.ident;
94    let table_name = struct_name.to_string().to_lowercase() + "s";
95    let custom_hooks = has_premix_flag(&input.attrs, "custom_hooks");
96    let custom_validation = has_premix_flag(&input.attrs, "custom_validation");
97
98    let all_fields = if let Data::Struct(data) = &input.data {
99        if let Fields::Named(fields) = &data.fields {
100            &fields.named
101        } else {
102            return Err(syn::Error::new_spanned(
103                &data.fields,
104                "Premix Model only supports structs with named fields",
105            ));
106        }
107    } else {
108        return Err(syn::Error::new_spanned(
109            input,
110            "Premix Model only supports structs",
111        ));
112    };
113
114    let mut db_fields = Vec::new();
115    let mut ignored_field_idents = Vec::new();
116
117    for field in all_fields {
118        if is_ignored(field) {
119            ignored_field_idents.push(field.ident.as_ref().unwrap());
120        } else {
121            db_fields.push(field);
122        }
123    }
124
125    let field_idents: Vec<_> = db_fields
126        .iter()
127        .map(|f| f.ident.as_ref().unwrap())
128        .collect();
129    let field_types: Vec<_> = db_fields.iter().map(|f| &f.ty).collect();
130    let _field_indices: Vec<_> = (0..db_fields.len()).collect();
131    let field_names: Vec<_> = field_idents.iter().map(|id| id.to_string()).collect();
132    let field_names_no_id: Vec<_> = field_names
133        .iter()
134        .filter(|name| *name != "id")
135        .cloned()
136        .collect();
137    let field_names_no_id_len = field_names_no_id.len();
138    // all_columns_joined and no_id_columns_joined removed as part of Zero-Overhead optimization (replaced by concat!)
139
140    // Prepare head/tail for concat! (to avoid trailing commas and handle separators)
141    let all_cols_head = field_names.first().cloned().unwrap_or_default();
142    let all_cols_tail: Vec<_> = field_names.iter().skip(1).cloned().collect();
143
144    let no_id_cols_head = field_names_no_id.first().cloned().unwrap_or_default();
145    let no_id_cols_tail: Vec<_> = field_names_no_id.iter().skip(1).cloned().collect();
146
147    let field_idents_len = field_idents.len();
148    let field_nullables: Vec<_> = db_fields.iter().map(|f| is_option_type(&f.ty)).collect();
149    let field_primary_keys: Vec<_> = field_names.iter().map(|n| n == "id").collect();
150    let field_sql_types: Vec<_> = db_fields
151        .iter()
152        .map(|field| {
153            let name = field.ident.as_ref().unwrap().to_string();
154            sql_type_for_field(&name, &field.ty).to_string()
155        })
156        .collect();
157    let field_sql_type_exprs: Vec<_> = db_fields
158        .iter()
159        .map(|field| {
160            let name = field.ident.as_ref().unwrap().to_string();
161            sql_type_expr_for_field(&name, &field.ty)
162        })
163        .collect();
164    let sensitive_field_literals: Vec<LitStr> = db_fields
165        .iter()
166        .filter(|f| is_sensitive(f))
167        .map(|f| {
168            LitStr::new(
169                &f.ident.as_ref().unwrap().to_string(),
170                f.ident.as_ref().unwrap().span(),
171            )
172        })
173        .collect();
174
175    let relation_meta = relations::collect_relation_metadata(input)?;
176    let relation_names: Vec<LitStr> = relation_meta
177        .iter()
178        .map(|meta| LitStr::new(&meta.relation_name, proc_macro2::Span::call_site()))
179        .collect();
180    let eager_relation_names: Vec<LitStr> = relation_meta
181        .iter()
182        .filter(|meta| meta.eager)
183        .map(|meta| LitStr::new(&meta.relation_name, proc_macro2::Span::call_site()))
184        .collect();
185    let eager_load_body = relations::generate_eager_load_body(input)?;
186    let (index_specs, foreign_key_specs) = collect_schema_specs(all_fields, &table_name)?;
187    let index_tokens: Vec<_> = index_specs
188        .iter()
189        .map(|spec| {
190            let name = &spec.name;
191            let columns = &spec.columns;
192            let unique = spec.unique;
193            quote! {
194                premix_orm::schema::SchemaIndex {
195                    name: #name.to_string(),
196                    columns: vec![#(#columns.to_string()),*],
197                    unique: #unique,
198                }
199            }
200        })
201        .collect();
202    let foreign_key_tokens: Vec<_> = foreign_key_specs
203        .iter()
204        .map(|spec| {
205            let column = &spec.column;
206            let ref_table = &spec.ref_table;
207            let ref_column = &spec.ref_column;
208            quote! {
209                premix_orm::schema::SchemaForeignKey {
210                    column: #column.to_string(),
211                    ref_table: #ref_table.to_string(),
212                    ref_column: #ref_column.to_string(),
213                }
214            }
215        })
216        .collect();
217    let has_version = field_names.contains(&"version".to_string());
218    let has_soft_delete = field_names.contains(&"deleted_at".to_string());
219
220    let save_update_block = if has_version {
221        quote! {
222            if self.id != 0 {
223                let table_name = Self::table_name();
224                static SQL: ::std::sync::OnceLock<String> = ::std::sync::OnceLock::new();
225                let sql = SQL.get_or_init(|| {
226                    let mut set_clause = String::with_capacity(#field_idents_len * 8);
227                    let mut i = 1usize;
228                    #(
229                        if i > 1 {
230                            set_clause.push_str(", ");
231                        }
232                        set_clause.push_str(#field_names);
233                        set_clause.push_str(" = ");
234                        set_clause.push_str(&<DB as premix_orm::SqlDialect>::placeholder(i));
235                        i += 1;
236                    )*
237                    let id_p = <DB as premix_orm::SqlDialect>::placeholder(1 + #field_idents_len);
238                    let ver_p = <DB as premix_orm::SqlDialect>::placeholder(2 + #field_idents_len);
239                    let mut sql = String::with_capacity(set_clause.len() + table_name.len() + 64);
240                    use ::std::fmt::Write;
241                    let _ = write!(
242                        sql,
243                        "UPDATE {} SET {}, version = version + 1 WHERE id = {} AND version = {}",
244                        table_name,
245                        set_clause,
246                        id_p,
247                        ver_p
248                    );
249                    sql
250                });
251
252                premix_orm::tracing::debug!(
253                    operation = "update",
254                    table = table_name,
255                    sql = %sql,
256                    "premix query"
257                );
258
259                let mut query = premix_orm::sqlx::query::<DB>(sql).persistent(true)
260                    #( .bind(&self.#field_idents) )*
261                    .bind(&self.id)
262                    .bind(&self.version);
263
264                let result = executor.execute(query).await?;
265                if <DB as premix_orm::SqlDialect>::rows_affected(&result) == 0 {
266                    static EXISTS_SQL: ::std::sync::OnceLock<String> = ::std::sync::OnceLock::new();
267                    let exists_sql = EXISTS_SQL.get_or_init(|| {
268                        let exists_p = <DB as premix_orm::SqlDialect>::placeholder(1);
269                        let mut exists_sql = String::with_capacity(table_name.len() + 32);
270                        use ::std::fmt::Write;
271                        let _ = write!(exists_sql, "SELECT id FROM {} WHERE id = {}", table_name, exists_p);
272                        exists_sql
273                    });
274                    let exists_query =
275                        premix_orm::sqlx::query_as::<DB, (i32,)>(exists_sql)
276                            .persistent(true)
277                            .bind(&self.id);
278                    let exists = executor.fetch_optional(exists_query).await?;
279                    if exists.is_some() {
280                        return Err(premix_orm::sqlx::Error::Protocol(
281                            "premix save failed: version conflict".into(),
282                        ));
283                    }
284                } else {
285                    self.version += 1;
286                    self.after_save().await?;
287                    return Ok(());
288                }
289            }
290        }
291    } else {
292        quote! {
293            if self.id != 0 {
294                let table_name = Self::table_name();
295                static SQL: ::std::sync::OnceLock<String> = ::std::sync::OnceLock::new();
296                let sql = SQL.get_or_init(|| {
297                    let mut set_clause = String::with_capacity(#field_idents_len * 8);
298                    let mut i = 1usize;
299                    #(
300                        if i > 1 {
301                            set_clause.push_str(", ");
302                        }
303                        set_clause.push_str(#field_names);
304                        set_clause.push_str(" = ");
305                        set_clause.push_str(&<DB as premix_orm::SqlDialect>::placeholder(i));
306                        i += 1;
307                    )*
308                    let id_p = <DB as premix_orm::SqlDialect>::placeholder(1 + #field_idents_len);
309                    let mut sql = String::with_capacity(set_clause.len() + table_name.len() + 32);
310                    use ::std::fmt::Write;
311                    let _ = write!(sql, "UPDATE {} SET {} WHERE id = {}", table_name, set_clause, id_p);
312                    sql
313                });
314
315                premix_orm::tracing::debug!(
316                    operation = "update",
317                    table = table_name,
318                    sql = %sql,
319                    "premix query"
320                );
321
322                let mut query = premix_orm::sqlx::query::<DB>(sql).persistent(true)
323                    #( .bind(&self.#field_idents) )*
324                    .bind(&self.id);
325
326                let result = executor.execute(query).await?;
327                if <DB as premix_orm::SqlDialect>::rows_affected(&result) > 0 {
328                    self.after_save().await?;
329                    return Ok(());
330                }
331            }
332        }
333    };
334
335    let save_fast_update_block = if has_version {
336        quote! {
337            if self.id != 0 {
338                let table_name = Self::table_name();
339                static SQL: ::std::sync::OnceLock<String> = ::std::sync::OnceLock::new();
340                let sql = SQL.get_or_init(|| {
341                    let mut set_clause = String::with_capacity(#field_idents_len * 8);
342                    let mut i = 1usize;
343                    #(
344                        if i > 1 {
345                            set_clause.push_str(", ");
346                        }
347                        set_clause.push_str(#field_names);
348                        set_clause.push_str(" = ");
349                        set_clause.push_str(&<DB as premix_orm::SqlDialect>::placeholder(i));
350                        i += 1;
351                    )*
352                    let id_p = <DB as premix_orm::SqlDialect>::placeholder(1 + #field_idents_len);
353                    let ver_p = <DB as premix_orm::SqlDialect>::placeholder(2 + #field_idents_len);
354                    let mut sql = String::with_capacity(set_clause.len() + table_name.len() + 64);
355                    use ::std::fmt::Write;
356                    let _ = write!(
357                        sql,
358                        "UPDATE {} SET {}, version = version + 1 WHERE id = {} AND version = {}",
359                        table_name,
360                        set_clause,
361                        id_p,
362                        ver_p
363                    );
364                    sql
365                });
366
367                let mut query = premix_orm::sqlx::query::<DB>(sql).persistent(true)
368                    #( .bind(&self.#field_idents) )*
369                    .bind(&self.id)
370                    .bind(&self.version);
371
372                let result = executor.execute(query).await?;
373                if <DB as premix_orm::SqlDialect>::rows_affected(&result) == 0 {
374                    static EXISTS_SQL: ::std::sync::OnceLock<String> = ::std::sync::OnceLock::new();
375                    let exists_sql = EXISTS_SQL.get_or_init(|| {
376                        let exists_p = <DB as premix_orm::SqlDialect>::placeholder(1);
377                        let mut exists_sql = String::with_capacity(table_name.len() + 32);
378                        use ::std::fmt::Write;
379                        let _ = write!(exists_sql, "SELECT id FROM {} WHERE id = {}", table_name, exists_p);
380                        exists_sql
381                    });
382                    let exists_query =
383                        premix_orm::sqlx::query_as::<DB, (i32,)>(exists_sql)
384                            .persistent(true)
385                            .bind(&self.id);
386                    let exists = executor.fetch_optional(exists_query).await?;
387                    if exists.is_some() {
388                        return Err(premix_orm::sqlx::Error::Protocol(
389                            "premix save failed: version conflict".into(),
390                        ));
391                    }
392                } else {
393                    self.version += 1;
394                    return Ok(());
395                }
396            }
397        }
398    } else {
399        quote! {
400            if self.id != 0 {
401                let table_name = Self::table_name();
402                static SQL: ::std::sync::OnceLock<String> = ::std::sync::OnceLock::new();
403                let sql = SQL.get_or_init(|| {
404                    let mut set_clause = String::with_capacity(#field_idents_len * 8);
405                    let mut i = 1usize;
406                    #(
407                        if i > 1 {
408                            set_clause.push_str(", ");
409                        }
410                        set_clause.push_str(#field_names);
411                        set_clause.push_str(" = ");
412                        set_clause.push_str(&<DB as premix_orm::SqlDialect>::placeholder(i));
413                        i += 1;
414                    )*
415                    let id_p = <DB as premix_orm::SqlDialect>::placeholder(1 + #field_idents_len);
416                    let mut sql = String::with_capacity(set_clause.len() + table_name.len() + 32);
417                    use ::std::fmt::Write;
418                    let _ = write!(sql, "UPDATE {} SET {} WHERE id = {}", table_name, set_clause, id_p);
419                    sql
420                });
421
422                let mut query = premix_orm::sqlx::query::<DB>(sql).persistent(true)
423                    #( .bind(&self.#field_idents) )*
424                    .bind(&self.id);
425
426                let result = executor.execute(query).await?;
427                if <DB as premix_orm::SqlDialect>::rows_affected(&result) > 0 {
428                    return Ok(());
429                }
430            }
431        }
432    };
433
434    let update_impl = if has_version {
435        quote! {
436            fn update<'a, E>(
437                &'a mut self,
438                executor: E,
439            ) -> impl ::std::future::Future<
440                Output = Result<premix_orm::UpdateResult, premix_orm::sqlx::Error>,
441            > + Send
442            where
443                E: premix_orm::IntoExecutor<'a, DB = DB>
444            {
445                async move {
446                let mut executor = executor.into_executor();
447                let table_name = Self::table_name();
448                static SQL: ::std::sync::OnceLock<String> = ::std::sync::OnceLock::new();
449                let sql = SQL.get_or_init(|| {
450                    let mut set_clause = String::with_capacity(#field_idents_len * 8);
451                    let mut i = 1usize;
452                    #(
453                        if i > 1 {
454                            set_clause.push_str(", ");
455                        }
456                        set_clause.push_str(#field_names);
457                        set_clause.push_str(" = ");
458                        set_clause.push_str(&<DB as premix_orm::SqlDialect>::placeholder(i));
459                        i += 1;
460                    )*
461                    let id_p = <DB as premix_orm::SqlDialect>::placeholder(1 + #field_idents_len);
462                    let ver_p = <DB as premix_orm::SqlDialect>::placeholder(2 + #field_idents_len);
463                    let mut sql = String::with_capacity(set_clause.len() + table_name.len() + 64);
464                    use ::std::fmt::Write;
465                    let _ = write!(
466                        sql,
467                        "UPDATE {} SET {}, version = version + 1 WHERE id = {} AND version = {}",
468                        table_name,
469                        set_clause,
470                        id_p,
471                        ver_p
472                    );
473                    sql
474                });
475
476                premix_orm::tracing::debug!(
477                    operation = "update",
478                    table = table_name,
479                    sql = %sql,
480                    "premix query"
481                );
482
483                let mut query = premix_orm::sqlx::query::<DB>(sql).persistent(true)
484                    #( .bind(&self.#field_idents) )*
485                    .bind(&self.id)
486                    .bind(&self.version);
487
488                let result = executor.execute(query).await?;
489
490                if <DB as premix_orm::SqlDialect>::rows_affected(&result) == 0 {
491                    static EXISTS_SQL: ::std::sync::OnceLock<String> = ::std::sync::OnceLock::new();
492                    let exists_sql = EXISTS_SQL.get_or_init(|| {
493                        let exists_p = <DB as premix_orm::SqlDialect>::placeholder(1);
494                        let mut exists_sql = String::with_capacity(table_name.len() + 32);
495                        use ::std::fmt::Write;
496                        let _ = write!(exists_sql, "SELECT id FROM {} WHERE id = {}", table_name, exists_p);
497                        exists_sql
498                    });
499                    let exists_query =
500                        premix_orm::sqlx::query_as::<DB, (i32,)>(exists_sql)
501                            .persistent(true)
502                            .bind(&self.id);
503                    let exists = executor.fetch_optional(exists_query).await?;
504
505                    if exists.is_none() {
506                        Ok(premix_orm::UpdateResult::NotFound)
507                    } else {
508                        Ok(premix_orm::UpdateResult::VersionConflict)
509                    }
510                } else {
511                    self.version += 1;
512                    Ok(premix_orm::UpdateResult::Success)
513                }
514                }
515            }
516
517            fn update_fast<'a, E>(
518                &'a mut self,
519                executor: E,
520            ) -> impl ::std::future::Future<
521                Output = Result<premix_orm::UpdateResult, premix_orm::sqlx::Error>,
522            > + Send
523            where
524                E: premix_orm::IntoExecutor<'a, DB = DB>
525            {
526                async move {
527                let mut executor = executor.into_executor();
528                let table_name = Self::table_name();
529                static SQL: ::std::sync::OnceLock<String> = ::std::sync::OnceLock::new();
530                let sql = SQL.get_or_init(|| {
531                    let mut set_clause = String::with_capacity(#field_idents_len * 8);
532                    let mut i = 1usize;
533                    #(
534                        if i > 1 {
535                            set_clause.push_str(", ");
536                        }
537                        set_clause.push_str(#field_names);
538                        set_clause.push_str(" = ");
539                        set_clause.push_str(&<DB as premix_orm::SqlDialect>::placeholder(i));
540                        i += 1;
541                    )*
542                    let id_p = <DB as premix_orm::SqlDialect>::placeholder(1 + #field_idents_len);
543                    let ver_p = <DB as premix_orm::SqlDialect>::placeholder(2 + #field_idents_len);
544                    let mut sql = String::with_capacity(set_clause.len() + table_name.len() + 64);
545                    use ::std::fmt::Write;
546                    let _ = write!(
547                        sql,
548                        "UPDATE {} SET {}, version = version + 1 WHERE id = {} AND version = {}",
549                        table_name,
550                        set_clause,
551                        id_p,
552                        ver_p
553                    );
554                    sql
555                });
556
557                let mut query = premix_orm::sqlx::query::<DB>(sql).persistent(true)
558                    #( .bind(&self.#field_idents) )*
559                    .bind(&self.id)
560                    .bind(&self.version);
561
562                let result = executor.execute(query).await?;
563
564                if <DB as premix_orm::SqlDialect>::rows_affected(&result) == 0 {
565                    static EXISTS_SQL: ::std::sync::OnceLock<String> = ::std::sync::OnceLock::new();
566                    let exists_sql = EXISTS_SQL.get_or_init(|| {
567                        let exists_p = <DB as premix_orm::SqlDialect>::placeholder(1);
568                        let mut exists_sql = String::with_capacity(table_name.len() + 32);
569                        use ::std::fmt::Write;
570                        let _ = write!(exists_sql, "SELECT id FROM {} WHERE id = {}", table_name, exists_p);
571                        exists_sql
572                    });
573                    let exists_query =
574                        premix_orm::sqlx::query_as::<DB, (i32,)>(exists_sql)
575                            .persistent(true)
576                            .bind(&self.id);
577                    let exists = executor.fetch_optional(exists_query).await?;
578
579                    if exists.is_none() {
580                        Ok(premix_orm::UpdateResult::NotFound)
581                    } else {
582                        Ok(premix_orm::UpdateResult::VersionConflict)
583                    }
584                } else {
585                    self.version += 1;
586                    Ok(premix_orm::UpdateResult::Success)
587                }
588                }
589            }
590            fn update_ultra<'a, E>(
591                &'a mut self,
592                executor: E,
593            ) -> impl ::std::future::Future<
594                Output = Result<premix_orm::UpdateResult, premix_orm::sqlx::Error>,
595            > + Send
596            where
597                E: premix_orm::IntoExecutor<'a, DB = DB>
598            {
599                async move { self.update_fast(executor).await }
600            }
601        }
602    } else {
603        quote! {
604            fn update<'a, E>(
605                &'a mut self,
606                executor: E,
607            ) -> impl ::std::future::Future<
608                Output = Result<premix_orm::UpdateResult, premix_orm::sqlx::Error>,
609            > + Send
610            where
611                E: premix_orm::IntoExecutor<'a, DB = DB>
612            {
613                async move {
614                let mut executor = executor.into_executor();
615                let table_name = Self::table_name();
616                static SQL: ::std::sync::OnceLock<String> = ::std::sync::OnceLock::new();
617                let sql = SQL.get_or_init(|| {
618                    let mut set_clause = String::with_capacity(#field_idents_len * 8);
619                    let mut i = 1usize;
620                    #(
621                        if i > 1 {
622                            set_clause.push_str(", ");
623                        }
624                        set_clause.push_str(#field_names);
625                        set_clause.push_str(" = ");
626                        set_clause.push_str(&<DB as premix_orm::SqlDialect>::placeholder(i));
627                        i += 1;
628                    )*
629                    let id_p = <DB as premix_orm::SqlDialect>::placeholder(1 + #field_idents_len);
630                    let mut sql = String::with_capacity(set_clause.len() + table_name.len() + 32);
631                    use ::std::fmt::Write;
632                    let _ = write!(sql, "UPDATE {} SET {} WHERE id = {}", table_name, set_clause, id_p);
633                    sql
634                });
635
636                premix_orm::tracing::debug!(
637                    operation = "update",
638                    table = table_name,
639                    sql = %sql,
640                    "premix query"
641                );
642
643                let mut query = premix_orm::sqlx::query::<DB>(sql).persistent(true)
644                    #( .bind(&self.#field_idents) )*
645                    .bind(&self.id);
646
647                let result = executor.execute(query).await?;
648
649                if <DB as premix_orm::SqlDialect>::rows_affected(&result) == 0 {
650                    Ok(premix_orm::UpdateResult::NotFound)
651                } else {
652                    Ok(premix_orm::UpdateResult::Success)
653                }
654                }
655            }
656
657            fn update_fast<'a, E>(
658                &'a mut self,
659                executor: E,
660            ) -> impl ::std::future::Future<
661                Output = Result<premix_orm::UpdateResult, premix_orm::sqlx::Error>,
662            > + Send
663            where
664                E: premix_orm::IntoExecutor<'a, DB = DB>
665            {
666                async move {
667                let mut executor = executor.into_executor();
668                let table_name = Self::table_name();
669                static SQL: ::std::sync::OnceLock<String> = ::std::sync::OnceLock::new();
670                let sql = SQL.get_or_init(|| {
671                    let mut set_clause = String::with_capacity(#field_idents_len * 8);
672                    let mut i = 1usize;
673                    #(
674                        if i > 1 {
675                            set_clause.push_str(", ");
676                        }
677                        set_clause.push_str(#field_names);
678                        set_clause.push_str(" = ");
679                        set_clause.push_str(&<DB as premix_orm::SqlDialect>::placeholder(i));
680                        i += 1;
681                    )*
682                    let id_p = <DB as premix_orm::SqlDialect>::placeholder(1 + #field_idents_len);
683                    let mut sql = String::with_capacity(set_clause.len() + table_name.len() + 32);
684                    use ::std::fmt::Write;
685                    let _ = write!(sql, "UPDATE {} SET {} WHERE id = {}", table_name, set_clause, id_p);
686                    sql
687                });
688
689                let mut query = premix_orm::sqlx::query::<DB>(sql).persistent(true)
690                    #( .bind(&self.#field_idents) )*
691                    .bind(&self.id);
692
693                let result = executor.execute(query).await?;
694
695                if <DB as premix_orm::SqlDialect>::rows_affected(&result) == 0 {
696                    Ok(premix_orm::UpdateResult::NotFound)
697                } else {
698                    Ok(premix_orm::UpdateResult::Success)
699                }
700                }
701            }
702            fn update_ultra<'a, E>(
703                &'a mut self,
704                executor: E,
705            ) -> impl ::std::future::Future<
706                Output = Result<premix_orm::UpdateResult, premix_orm::sqlx::Error>,
707            > + Send
708            where
709                E: premix_orm::IntoExecutor<'a, DB = DB>
710            {
711                async move { self.update_fast(executor).await }
712            }
713        }
714    };
715
716    let delete_impl = if has_soft_delete {
717        quote! {
718            fn delete<'a, E>(
719                &'a mut self,
720                executor: E,
721            ) -> impl ::std::future::Future<Output = Result<(), premix_orm::sqlx::Error>>
722            + Send
723            where
724                E: premix_orm::IntoExecutor<'a, DB = DB>
725            {
726                async move {
727                let mut executor = executor.into_executor();
728                let table_name = Self::table_name();
729                static SQL: ::std::sync::OnceLock<String> = ::std::sync::OnceLock::new();
730                let sql = SQL.get_or_init(|| {
731                    let id_p = <DB as premix_orm::SqlDialect>::placeholder(1);
732                    let mut sql = String::with_capacity(table_name.len() + 64);
733                    use ::std::fmt::Write;
734                    let _ = write!(
735                        sql,
736                        "UPDATE {} SET deleted_at = {} WHERE id = {}",
737                        table_name,
738                        <DB as premix_orm::SqlDialect>::current_timestamp_fn(),
739                        id_p
740                    );
741                    sql
742                });
743
744                premix_orm::tracing::debug!(
745                    operation = "delete",
746                    table = table_name,
747                    sql = %sql,
748                    "premix query"
749                );
750
751                let query = premix_orm::sqlx::query::<DB>(sql).persistent(true).bind(&self.id);
752                executor.execute(query).await?;
753
754                self.deleted_at = Some("DELETED".to_string());
755                Ok(())
756                }
757            }
758            fn delete_fast<'a, E>(
759                &'a mut self,
760                executor: E,
761            ) -> impl ::std::future::Future<Output = Result<(), premix_orm::sqlx::Error>>
762            + Send
763            where
764                E: premix_orm::IntoExecutor<'a, DB = DB>
765            {
766                async move {
767                let mut executor = executor.into_executor();
768                let table_name = Self::table_name();
769                static SQL: ::std::sync::OnceLock<String> = ::std::sync::OnceLock::new();
770                let sql = SQL.get_or_init(|| {
771                    let id_p = <DB as premix_orm::SqlDialect>::placeholder(1);
772                    let mut sql = String::with_capacity(table_name.len() + 64);
773                    use ::std::fmt::Write;
774                    let _ = write!(
775                        sql,
776                        "UPDATE {} SET deleted_at = {} WHERE id = {}",
777                        table_name,
778                        <DB as premix_orm::SqlDialect>::current_timestamp_fn(),
779                        id_p
780                    );
781                    sql
782                });
783
784                let query = premix_orm::sqlx::query::<DB>(sql).persistent(true).bind(&self.id);
785                executor.execute(query).await?;
786
787                self.deleted_at = Some("DELETED".to_string());
788                Ok(())
789                }
790            }
791            fn delete_ultra<'a, E>(
792                &'a mut self,
793                executor: E,
794            ) -> impl ::std::future::Future<Output = Result<(), premix_orm::sqlx::Error>>
795            + Send
796            where
797                E: premix_orm::IntoExecutor<'a, DB = DB>
798            {
799                async move { self.delete_fast(executor).await }
800            }
801            fn has_soft_delete() -> bool { true }
802        }
803    } else {
804        quote! {
805            fn delete<'a, E>(
806                &'a mut self,
807                executor: E,
808            ) -> impl ::std::future::Future<Output = Result<(), premix_orm::sqlx::Error>>
809            + Send
810            where
811                E: premix_orm::IntoExecutor<'a, DB = DB>
812            {
813                async move {
814                let mut executor = executor.into_executor();
815                let table_name = Self::table_name();
816                static SQL: ::std::sync::OnceLock<String> = ::std::sync::OnceLock::new();
817                let sql = SQL.get_or_init(|| {
818                    let id_p = <DB as premix_orm::SqlDialect>::placeholder(1);
819                    let mut sql = String::with_capacity(table_name.len() + 24);
820                    use ::std::fmt::Write;
821                    let _ = write!(sql, "DELETE FROM {} WHERE id = {}", table_name, id_p);
822                    sql
823                });
824
825                premix_orm::tracing::debug!(
826                    operation = "delete",
827                    table = table_name,
828                    sql = %sql,
829                    "premix query"
830                );
831
832                let query = premix_orm::sqlx::query::<DB>(sql).persistent(true).bind(&self.id);
833                executor.execute(query).await?;
834
835                Ok(())
836                }
837            }
838            fn delete_fast<'a, E>(
839                &'a mut self,
840                executor: E,
841            ) -> impl ::std::future::Future<Output = Result<(), premix_orm::sqlx::Error>>
842            + Send
843            where
844                E: premix_orm::IntoExecutor<'a, DB = DB>
845            {
846                async move {
847                let mut executor = executor.into_executor();
848                let table_name = Self::table_name();
849                static SQL: ::std::sync::OnceLock<String> = ::std::sync::OnceLock::new();
850                let sql = SQL.get_or_init(|| {
851                    let id_p = <DB as premix_orm::SqlDialect>::placeholder(1);
852                    let mut sql = String::with_capacity(table_name.len() + 24);
853                    use ::std::fmt::Write;
854                    let _ = write!(sql, "DELETE FROM {} WHERE id = {}", table_name, id_p);
855                    sql
856                });
857
858                let query = premix_orm::sqlx::query::<DB>(sql).persistent(true).bind(&self.id);
859                executor.execute(query).await?;
860
861                Ok(())
862                }
863            }
864            fn delete_ultra<'a, E>(
865                &'a mut self,
866                executor: E,
867            ) -> impl ::std::future::Future<Output = Result<(), premix_orm::sqlx::Error>>
868            + Send
869            where
870                E: premix_orm::IntoExecutor<'a, DB = DB>
871            {
872                async move { self.delete_fast(executor).await }
873            }
874            fn has_soft_delete() -> bool { false }
875        }
876    };
877
878    let mut related_model_bounds = Vec::new();
879    for field in all_fields {
880        for attr in &field.attrs {
881            if attr.path().is_ident("has_many")
882                && let Ok(related_ident) = attr.parse_args::<syn::Ident>()
883            {
884                related_model_bounds.push(quote! { #related_ident: premix_orm::Model<DB> });
885            } else if attr.path().is_ident("belongs_to")
886                && let Ok(related_ident) = attr.parse_args::<syn::Ident>()
887            {
888                related_model_bounds.push(quote! { #related_ident: premix_orm::Model<DB> + Clone });
889            }
890        }
891    }
892
893    let hooks_impl = if custom_hooks {
894        quote! {}
895    } else {
896        quote! {
897            impl premix_orm::ModelHooks for #struct_name {}
898        }
899    };
900
901    let validation_impl = if custom_validation {
902        quote! {}
903    } else {
904        quote! {
905            impl premix_orm::ModelValidation for #struct_name {}
906        }
907    };
908
909    let col_consts: Vec<_> = field_names
910        .iter()
911        .zip(field_idents.iter())
912        .map(|(name, ident)| {
913            let const_name = syn::Ident::new(&ident.to_string().to_uppercase(), ident.span());
914            quote! {
915                pub const #const_name: &str = #name;
916            }
917        })
918        .collect();
919
920    let columns_mod_ident = syn::Ident::new(
921        &format!("columns_{}", struct_name.to_string().to_lowercase()),
922        struct_name.span(),
923    );
924
925    // Generic Implementation
926    Ok(quote! {
927        // Generate column constants
928        #[allow(non_snake_case)]
929        pub mod #columns_mod_ident {
930             #( #col_consts )*
931        }
932
933        impl<'r, R> premix_orm::sqlx::FromRow<'r, R> for #struct_name
934        where
935            R: premix_orm::sqlx::Row,
936            R::Database: premix_orm::sqlx::Database,
937            #(
938                #field_types: premix_orm::sqlx::Type<R::Database> + premix_orm::sqlx::Decode<'r, R::Database>,
939            )*
940            for<'c> &'c str: premix_orm::sqlx::ColumnIndex<R>,
941        {
942            fn from_row(row: &'r R) -> Result<Self, premix_orm::sqlx::Error> {
943                use premix_orm::sqlx::Row;
944                Ok(Self {
945                    #(
946                        #field_idents: row.try_get(#field_names)?,
947                    )*
948                    #(
949                        #ignored_field_idents: None,
950                    )*
951                })
952            }
953        }
954
955
956        impl<DB> premix_orm::Model<DB> for #struct_name
957        where
958            DB: premix_orm::SqlDialect,
959            for<'c> &'c str: premix_orm::sqlx::ColumnIndex<DB::Row>,
960            usize: premix_orm::sqlx::ColumnIndex<DB::Row>,
961            for<'q> <DB as premix_orm::sqlx::Database>::Arguments<'q>: premix_orm::sqlx::IntoArguments<'q, DB>,
962            for<'c> &'c mut <DB as premix_orm::sqlx::Database>::Connection: premix_orm::sqlx::Executor<'c, Database = DB>,
963            i32: premix_orm::sqlx::Type<DB> + for<'q> premix_orm::sqlx::Encode<'q, DB> + for<'r> premix_orm::sqlx::Decode<'r, DB>,
964            i64: premix_orm::sqlx::Type<DB> + for<'q> premix_orm::sqlx::Encode<'q, DB> + for<'r> premix_orm::sqlx::Decode<'r, DB>,
965            String: premix_orm::sqlx::Type<DB> + for<'q> premix_orm::sqlx::Encode<'q, DB> + for<'r> premix_orm::sqlx::Decode<'r, DB>,
966            bool: premix_orm::sqlx::Type<DB> + for<'q> premix_orm::sqlx::Encode<'q, DB> + for<'r> premix_orm::sqlx::Decode<'r, DB>,
967            Option<String>: premix_orm::sqlx::Type<DB> + for<'q> premix_orm::sqlx::Encode<'q, DB> + for<'r> premix_orm::sqlx::Decode<'r, DB>,
968            #( #related_model_bounds, )*
969        {
970            fn table_name() -> &'static str {
971                #table_name
972            }
973
974            fn create_table_sql() -> String {
975                let mut cols = vec!["id ".to_string() + <DB as premix_orm::SqlDialect>::auto_increment_pk()];
976                #(
977                    if #field_names != "id" {
978                        let sql_type = #field_sql_type_exprs;
979                        cols.push(format!("{} {}", #field_names, sql_type));
980                    }
981                )*
982                format!("CREATE TABLE IF NOT EXISTS {} ({})", #table_name, cols.join(", "))
983            }
984
985            fn list_columns() -> ::std::vec::Vec<::std::string::String> {
986                vec![ #( #field_names.to_string() ),* ]
987            }
988
989            fn sensitive_fields() -> &'static [&'static str] {
990                &[ #( #sensitive_field_literals ),* ]
991            }
992
993            fn relation_names() -> &'static [&'static str] {
994                &[ #( #relation_names ),* ]
995            }
996
997            fn default_includes() -> &'static [&'static str] {
998                &[ #( #eager_relation_names ),* ]
999            }
1000
1001            fn from_row_fast(row: &<DB as premix_orm::sqlx::Database>::Row) -> Result<Self, premix_orm::sqlx::Error>
1002            where
1003                usize: premix_orm::sqlx::ColumnIndex<<DB as premix_orm::sqlx::Database>::Row>,
1004                for<'c> &'c str: premix_orm::sqlx::ColumnIndex<<DB as premix_orm::sqlx::Database>::Row>,
1005            {
1006                use premix_orm::sqlx::Row;
1007                let mut idx: usize = 0;
1008                #(
1009                    let #field_idents = row.try_get(idx)?;
1010                    idx += 1;
1011                )*
1012                Ok(Self {
1013                    #( #field_idents, )*
1014                    #( #ignored_field_idents: None, )*
1015                })
1016            }
1017
1018            fn save<'a, E>(
1019                &'a mut self,
1020                executor: E,
1021            ) -> impl ::std::future::Future<Output = ::std::result::Result<(), premix_orm::sqlx::Error>>
1022            + Send
1023            where
1024                E: premix_orm::IntoExecutor<'a, DB = DB>
1025            {
1026                async move {
1027                let mut executor = executor.into_executor();
1028                use premix_orm::ModelHooks;
1029                self.before_save().await?;
1030
1031                #save_update_block
1032
1033                // CONSTANT column lists to avoid runtime joining/allocation
1034                // We use head/tail pattern to insert ", " separator without trailing comma
1035                const ALL_COLUMNS_LIST: &str = concat!(#all_cols_head, #( ", ", #all_cols_tail ),*);
1036                const NO_ID_COLUMNS_LIST: &str = concat!(#no_id_cols_head, #( ", ", #no_id_cols_tail ),*);
1037
1038                let supports_returning = <DB as premix_orm::SqlDialect>::supports_returning();
1039                if supports_returning {
1040                    let sql = if self.id == 0 {
1041                        static INSERT_NO_ID_RETURNING_SQL: std::sync::OnceLock<String> =
1042                            std::sync::OnceLock::new();
1043                        INSERT_NO_ID_RETURNING_SQL.get_or_init(|| {
1044                            let placeholders =
1045                                premix_orm::cached_placeholders::<DB>(#field_names_no_id_len);
1046                            format!(
1047                                "INSERT INTO {} ({}) VALUES ({}) RETURNING id",
1048                                #table_name,
1049                                NO_ID_COLUMNS_LIST,
1050                                placeholders
1051                            )
1052                        })
1053                    } else {
1054                        static INSERT_WITH_ID_RETURNING_SQL: std::sync::OnceLock<String> =
1055                            std::sync::OnceLock::new();
1056                        INSERT_WITH_ID_RETURNING_SQL.get_or_init(|| {
1057                            let placeholders =
1058                                premix_orm::cached_placeholders::<DB>(#field_idents_len);
1059                            format!(
1060                                "INSERT INTO {} ({}) VALUES ({}) RETURNING id",
1061                                #table_name,
1062                                ALL_COLUMNS_LIST,
1063                                placeholders
1064                            )
1065                        })
1066                    };
1067
1068                    premix_orm::tracing::debug!(
1069                        operation = "insert",
1070                        table = #table_name,
1071                        sql = %sql,
1072                        "premix query"
1073                    );
1074
1075                    let mut query = premix_orm::sqlx::query_as::<DB, (i32,)>(sql.as_str())
1076                        .persistent(true);
1077                    #(
1078                        if #field_names != "id" {
1079                            query = query.bind(&self.#field_idents);
1080                        } else if self.id != 0 {
1081                            query = query.bind(&self.id);
1082                        }
1083                    )*
1084
1085                    if let Some((id,)) = executor.fetch_optional(query).await? {
1086                        self.id = id;
1087                    }
1088                } else {
1089                    let sql = if self.id == 0 {
1090                        static INSERT_NO_ID_SQL: std::sync::OnceLock<String> =
1091                            std::sync::OnceLock::new();
1092                        INSERT_NO_ID_SQL.get_or_init(|| {
1093                            let placeholders =
1094                                premix_orm::cached_placeholders::<DB>(#field_names_no_id_len);
1095                            format!(
1096                                "INSERT INTO {} ({}) VALUES ({})",
1097                                #table_name,
1098                                NO_ID_COLUMNS_LIST,
1099                                placeholders
1100                            )
1101                        })
1102                    } else {
1103                        static INSERT_WITH_ID_SQL: std::sync::OnceLock<String> =
1104                            std::sync::OnceLock::new();
1105                        INSERT_WITH_ID_SQL.get_or_init(|| {
1106                            let placeholders =
1107                                premix_orm::cached_placeholders::<DB>(#field_idents_len);
1108                            format!(
1109                                "INSERT INTO {} ({}) VALUES ({})",
1110                                #table_name,
1111                                ALL_COLUMNS_LIST,
1112                                placeholders
1113                            )
1114                        })
1115                    };
1116
1117                    premix_orm::tracing::debug!(
1118                        operation = "insert",
1119                        table = #table_name,
1120                        sql = %sql,
1121                        "premix query"
1122                    );
1123
1124                    let mut query = premix_orm::sqlx::query::<DB>(sql.as_str()).persistent(true);
1125                    #(
1126                        if #field_names != "id" {
1127                            query = query.bind(&self.#field_idents);
1128                        } else if self.id != 0 {
1129                            query = query.bind(&self.id);
1130                        }
1131                    )*
1132
1133                    let result = executor.execute(query).await?;
1134                    let last_id = <DB as premix_orm::SqlDialect>::last_insert_id(&result);
1135                    if last_id > 0 {
1136                        self.id = last_id as i32;
1137                    }
1138                }
1139
1140                self.after_save().await?;
1141                Ok(())
1142                }
1143            }
1144
1145            fn save_fast<'a, E>(
1146                &'a mut self,
1147                executor: E,
1148            ) -> impl ::std::future::Future<Output = ::std::result::Result<(), premix_orm::sqlx::Error>>
1149            + Send
1150            where
1151                E: premix_orm::IntoExecutor<'a, DB = DB>
1152            {
1153                async move {
1154                let mut executor = executor.into_executor();
1155
1156                #save_fast_update_block
1157
1158                // CONSTANT column lists to avoid runtime joining/allocation
1159                // We use head/tail pattern to insert ", " separator without trailing comma
1160                const ALL_COLUMNS_LIST: &str = concat!(#all_cols_head, #( ", ", #all_cols_tail ),*);
1161                const NO_ID_COLUMNS_LIST: &str = concat!(#no_id_cols_head, #( ", ", #no_id_cols_tail ),*);
1162
1163                let supports_returning = <DB as premix_orm::SqlDialect>::supports_returning();
1164                if supports_returning {
1165                    let sql = if self.id == 0 {
1166                        static INSERT_NO_ID_RETURNING_SQL: std::sync::OnceLock<String> =
1167                            std::sync::OnceLock::new();
1168                        INSERT_NO_ID_RETURNING_SQL.get_or_init(|| {
1169                            let placeholders =
1170                                premix_orm::cached_placeholders::<DB>(#field_names_no_id_len);
1171                            format!(
1172                                "INSERT INTO {} ({}) VALUES ({}) RETURNING id",
1173                                #table_name,
1174                                NO_ID_COLUMNS_LIST,
1175                                placeholders
1176                            )
1177                        })
1178                    } else {
1179                        static INSERT_WITH_ID_RETURNING_SQL: std::sync::OnceLock<String> =
1180                            std::sync::OnceLock::new();
1181                        INSERT_WITH_ID_RETURNING_SQL.get_or_init(|| {
1182                            let placeholders =
1183                                premix_orm::cached_placeholders::<DB>(#field_idents_len);
1184                            format!(
1185                                "INSERT INTO {} ({}) VALUES ({}) RETURNING id",
1186                                #table_name,
1187                                ALL_COLUMNS_LIST,
1188                                placeholders
1189                            )
1190                        })
1191                    };
1192
1193                    let mut query = premix_orm::sqlx::query_as::<DB, (i32,)>(sql.as_str())
1194                        .persistent(true);
1195                    #(
1196                        if #field_names != "id" {
1197                            query = query.bind(&self.#field_idents);
1198                        } else if self.id != 0 {
1199                            query = query.bind(&self.id);
1200                        }
1201                    )*
1202
1203                    if let Some((id,)) = executor.fetch_optional(query).await? {
1204                        self.id = id;
1205                    }
1206                } else {
1207                    let sql = if self.id == 0 {
1208                        static INSERT_NO_ID_SQL: std::sync::OnceLock<String> =
1209                            std::sync::OnceLock::new();
1210                        INSERT_NO_ID_SQL.get_or_init(|| {
1211                            let placeholders =
1212                                premix_orm::cached_placeholders::<DB>(#field_names_no_id_len);
1213                            format!(
1214                                "INSERT INTO {} ({}) VALUES ({})",
1215                                #table_name,
1216                                NO_ID_COLUMNS_LIST,
1217                                placeholders
1218                            )
1219                        })
1220                    } else {
1221                        static INSERT_WITH_ID_SQL: std::sync::OnceLock<String> =
1222                            std::sync::OnceLock::new();
1223                        INSERT_WITH_ID_SQL.get_or_init(|| {
1224                            let placeholders =
1225                                premix_orm::cached_placeholders::<DB>(#field_idents_len);
1226                            format!(
1227                                "INSERT INTO {} ({}) VALUES ({})",
1228                                #table_name,
1229                                ALL_COLUMNS_LIST,
1230                                placeholders
1231                            )
1232                        })
1233                    };
1234
1235                    let mut query = premix_orm::sqlx::query::<DB>(sql.as_str()).persistent(true);
1236                    #(
1237                        if #field_names != "id" {
1238                            query = query.bind(&self.#field_idents);
1239                        } else if self.id != 0 {
1240                            query = query.bind(&self.id);
1241                        }
1242                    )*
1243
1244                    let result = executor.execute(query).await?;
1245                    let last_id = <DB as premix_orm::SqlDialect>::last_insert_id(&result);
1246                    if last_id > 0 {
1247                        self.id = last_id as i32;
1248                    }
1249                }
1250
1251                Ok(())
1252                }
1253            }
1254            fn save_ultra<'a, E>(
1255                &'a mut self,
1256                executor: E,
1257            ) -> impl ::std::future::Future<Output = ::std::result::Result<(), premix_orm::sqlx::Error>>
1258            + Send
1259            where
1260                E: premix_orm::IntoExecutor<'a, DB = DB>
1261            {
1262                async move {
1263                let mut executor = executor.into_executor();
1264
1265                // CONSTANT column lists to avoid runtime joining/allocation
1266                // We use head/tail pattern to insert ", " separator without trailing comma
1267                const ALL_COLUMNS_LIST: &str = concat!(#all_cols_head, #( ", ", #all_cols_tail ),*);
1268                const NO_ID_COLUMNS_LIST: &str = concat!(#no_id_cols_head, #( ", ", #no_id_cols_tail ),*);
1269
1270                let column_list: &str = if self.id == 0 { NO_ID_COLUMNS_LIST } else { ALL_COLUMNS_LIST };
1271
1272                // We still need to calculate placeholders at runtime because they depend on the count and DB dialect
1273                let count = if self.id == 0 { #field_names_no_id_len } else { #field_idents_len };
1274                let placeholders = premix_orm::cached_placeholders::<DB>(count);
1275
1276                let sql = format!(
1277                    "INSERT INTO {} ({}) VALUES ({})",
1278                    #table_name,
1279                    column_list,
1280                    placeholders
1281                );
1282
1283                let mut query = premix_orm::sqlx::query::<DB>(&sql).persistent(true);
1284                #(
1285                    if #field_names != "id" {
1286                        query = query.bind(&self.#field_idents);
1287                    } else if self.id != 0 {
1288                        query = query.bind(&self.id);
1289                    }
1290                )*
1291
1292                let result = executor.execute(query).await?;
1293                let last_id = <DB as premix_orm::SqlDialect>::last_insert_id(&result);
1294                if last_id > 0 {
1295                    self.id = last_id as i32;
1296                }
1297
1298                Ok(())
1299                }
1300            }
1301
1302            #update_impl
1303            #delete_impl
1304
1305            fn find_by_id<'a, E>(
1306                executor: E,
1307                id: i32,
1308            ) -> impl ::std::future::Future<Output = ::std::result::Result<::std::option::Option<Self>, premix_orm::sqlx::Error>>
1309            + Send
1310            where
1311                E: premix_orm::IntoExecutor<'a, DB = DB>
1312            {
1313                async move {
1314                let mut executor = executor.into_executor();
1315                let p = <DB as premix_orm::SqlDialect>::placeholder(1);
1316
1317                // Optimization: Pre-calculate the base SQL string
1318                let sql = if Self::has_soft_delete() {
1319                    format!("SELECT * FROM {} WHERE id = {} AND deleted_at IS NULL LIMIT 1", #table_name, p)
1320                } else {
1321                    format!("SELECT * FROM {} WHERE id = {} LIMIT 1", #table_name, p)
1322                };
1323
1324                premix_orm::tracing::debug!(
1325                    operation = "select",
1326                    table = #table_name,
1327                    sql = %sql,
1328                    "premix query"
1329                );
1330                let query = premix_orm::sqlx::query_as::<DB, Self>(&sql)
1331                    .persistent(true)
1332                    .bind(id);
1333                executor.fetch_optional(query).await
1334                }
1335            }
1336
1337            fn eager_load<'a>(
1338                models: &mut [Self],
1339                relation: &str,
1340                executor: premix_orm::Executor<'a, DB>,
1341            ) -> impl ::std::future::Future<Output = Result<(), premix_orm::sqlx::Error>> + Send
1342            {
1343                async move {
1344                    let mut executor = executor;
1345                    #eager_load_body
1346                }
1347            }
1348        }
1349
1350        #hooks_impl
1351        #validation_impl
1352
1353        impl premix_orm::ModelSchema for #struct_name {
1354            fn schema() -> premix_orm::schema::SchemaTable {
1355                let columns = vec![
1356                    #(
1357                        premix_orm::schema::SchemaColumn {
1358                            name: #field_names.to_string(),
1359                            sql_type: #field_sql_types.to_string(),
1360                            nullable: #field_nullables,
1361                            primary_key: #field_primary_keys,
1362                        }
1363                    ),*
1364                ];
1365                let indexes = vec![
1366                    #(#index_tokens),*
1367                ];
1368                let foreign_keys = vec![
1369                    #(#foreign_key_tokens),*
1370                ];
1371                premix_orm::schema::SchemaTable {
1372                    name: #table_name.to_string(),
1373                    columns,
1374                    indexes,
1375                    foreign_keys,
1376                    create_sql: None,
1377                }
1378            }
1379        }
1380    })
1381}
1382
1383fn has_premix_field_flag(field: &Field, flag: &str) -> bool {
1384    for attr in &field.attrs {
1385        if attr.path().is_ident("premix")
1386            && let Ok(meta) = attr.parse_args::<syn::Ident>()
1387            && meta == flag
1388        {
1389            return true;
1390        }
1391    }
1392    false
1393}
1394
1395fn is_ignored(field: &Field) -> bool {
1396    has_premix_field_flag(field, "ignore")
1397}
1398
1399fn is_sensitive(field: &Field) -> bool {
1400    has_premix_field_flag(field, "sensitive")
1401}
1402
1403struct IndexSpec {
1404    name: String,
1405    columns: Vec<String>,
1406    unique: bool,
1407}
1408
1409struct ForeignKeySpec {
1410    column: String,
1411    ref_table: String,
1412    ref_column: String,
1413}
1414
1415fn collect_schema_specs(
1416    fields: &syn::punctuated::Punctuated<Field, Token![,]>,
1417    table_name: &str,
1418) -> syn::Result<(Vec<IndexSpec>, Vec<ForeignKeySpec>)> {
1419    let mut indexes = Vec::new();
1420    let mut foreign_keys = Vec::new();
1421
1422    for field in fields {
1423        if is_ignored(field) {
1424            continue;
1425        }
1426        let field_name = field
1427            .ident
1428            .as_ref()
1429            .ok_or_else(|| syn::Error::new_spanned(field, "Field must have an ident"))?
1430            .to_string();
1431
1432        for attr in &field.attrs {
1433            if !attr.path().is_ident("premix") {
1434                continue;
1435            }
1436
1437            attr.parse_nested_meta(|meta| {
1438                if meta.path.is_ident("index") || meta.path.is_ident("unique") {
1439                    let unique = meta.path.is_ident("unique");
1440                    let mut name = None;
1441                    if meta.input.peek(syn::token::Paren) {
1442                        meta.parse_nested_meta(|nested| {
1443                            if nested.path.is_ident("name") {
1444                                let lit: LitStr = nested.value()?.parse()?;
1445                                name = Some(lit.value());
1446                                Ok(())
1447                            } else {
1448                                Err(nested.error("unsupported index option"))
1449                            }
1450                        })?;
1451                    }
1452                    let index_name =
1453                        name.unwrap_or_else(|| format!("idx_{}_{}", table_name, field_name));
1454                    indexes.push(IndexSpec {
1455                        name: index_name,
1456                        columns: vec![field_name.clone()],
1457                        unique,
1458                    });
1459                } else if meta.path.is_ident("foreign_key") {
1460                    let mut ref_table = None;
1461                    let mut ref_column = None;
1462                    meta.parse_nested_meta(|nested| {
1463                        if nested.path.is_ident("table") {
1464                            let lit: LitStr = nested.value()?.parse()?;
1465                            ref_table = Some(lit.value());
1466                            Ok(())
1467                        } else if nested.path.is_ident("column") {
1468                            let lit: LitStr = nested.value()?.parse()?;
1469                            ref_column = Some(lit.value());
1470                            Ok(())
1471                        } else {
1472                            Err(nested.error("unsupported foreign_key option"))
1473                        }
1474                    })?;
1475
1476                    let ref_table = ref_table.ok_or_else(|| {
1477                        syn::Error::new_spanned(attr, "foreign_key requires table = \"...\"")
1478                    })?;
1479                    let ref_column = ref_column.unwrap_or_else(|| "id".to_string());
1480                    foreign_keys.push(ForeignKeySpec {
1481                        column: field_name.clone(),
1482                        ref_table,
1483                        ref_column,
1484                    });
1485                }
1486                Ok(())
1487            })?;
1488        }
1489    }
1490
1491    Ok((indexes, foreign_keys))
1492}
1493
1494fn is_option_type(ty: &syn::Type) -> bool {
1495    if let syn::Type::Path(path) = ty {
1496        if let Some(seg) = path.path.segments.last() {
1497            return seg.ident == "Option";
1498        }
1499    }
1500    false
1501}
1502
1503fn has_premix_flag(attrs: &[Attribute], flag: &str) -> bool {
1504    for attr in attrs {
1505        if attr.path().is_ident("premix") {
1506            let args = attr.parse_args_with(Punctuated::<Ident, Token![,]>::parse_terminated);
1507            if let Ok(args) = args {
1508                if args.iter().any(|ident| ident == flag) {
1509                    return true;
1510                }
1511            }
1512        }
1513    }
1514    false
1515}
1516
1517fn type_name_for_field(ty: &syn::Type) -> Option<String> {
1518    if let syn::Type::Path(path) = ty {
1519        let segment = path.path.segments.last()?;
1520        let ident = segment.ident.to_string();
1521        if ident == "Option" {
1522            if let syn::PathArguments::AngleBracketed(args) = &segment.arguments {
1523                for arg in args.args.iter() {
1524                    if let syn::GenericArgument::Type(inner) = arg {
1525                        return type_name_for_field(inner);
1526                    }
1527                }
1528            }
1529            None
1530        } else if ident == "Vec" {
1531            if let syn::PathArguments::AngleBracketed(args) = &segment.arguments {
1532                if let Some(syn::GenericArgument::Type(inner)) = args.args.first() {
1533                    if let Some(inner_ident) = type_name_for_field(inner) {
1534                        return Some(format!("Vec<{}>", inner_ident));
1535                    }
1536                }
1537            }
1538            Some("Vec".to_string())
1539        } else {
1540            Some(ident)
1541        }
1542    } else {
1543        None
1544    }
1545}
1546
1547fn sql_type_for_field(name: &str, ty: &syn::Type) -> &'static str {
1548    let type_name = type_name_for_field(ty);
1549    match type_name.as_deref() {
1550        Some("i8" | "i16" | "i32" | "isize" | "u8" | "u16" | "u32" | "usize") => "INTEGER",
1551        Some("i64" | "u64") => "BIGINT",
1552        Some("f32" | "f64") => "REAL",
1553        Some("bool") => "BOOLEAN",
1554        Some("String" | "str") => "TEXT",
1555        Some("Uuid" | "DateTime" | "NaiveDateTime" | "NaiveDate") => "TEXT",
1556        Some("Vec<u8>") => "BLOB",
1557        _ => {
1558            if name == "id" || name.ends_with("_id") {
1559                "INTEGER"
1560            } else {
1561                "TEXT"
1562            }
1563        }
1564    }
1565}
1566
1567fn sql_type_expr_for_field(name: &str, ty: &syn::Type) -> proc_macro2::TokenStream {
1568    let type_name = type_name_for_field(ty);
1569    match type_name.as_deref() {
1570        Some("i8" | "i16" | "i32" | "isize" | "u8" | "u16" | "u32" | "usize") => {
1571            quote! { <DB as premix_orm::SqlDialect>::int_type() }
1572        }
1573        Some("i64" | "u64") => quote! { <DB as premix_orm::SqlDialect>::bigint_type() },
1574        Some("f32" | "f64") => quote! { <DB as premix_orm::SqlDialect>::float_type() },
1575        Some("bool") => quote! { <DB as premix_orm::SqlDialect>::bool_type() },
1576        Some("String" | "str") => quote! { <DB as premix_orm::SqlDialect>::text_type() },
1577        Some("Uuid" | "DateTime" | "NaiveDateTime" | "NaiveDate") => {
1578            quote! { <DB as premix_orm::SqlDialect>::text_type() }
1579        }
1580        Some("Vec<u8>") => quote! { <DB as premix_orm::SqlDialect>::blob_type() },
1581        _ => {
1582            if name == "id" || name.ends_with("_id") {
1583                quote! { <DB as premix_orm::SqlDialect>::int_type() }
1584            } else {
1585                quote! { <DB as premix_orm::SqlDialect>::text_type() }
1586            }
1587        }
1588    }
1589}
1590
1591#[cfg(test)]
1592mod tests {
1593    use syn::parse_quote;
1594
1595    use super::*;
1596
1597    #[test]
1598    fn generate_generic_impl_includes_table_and_columns() {
1599        let input: DeriveInput = parse_quote! {
1600            struct User {
1601                id: i32,
1602                name: String,
1603                version: i32,
1604                deleted_at: Option<String>,
1605            }
1606        };
1607        let tokens = generate_generic_impl(&input).unwrap().to_string();
1608        assert!(tokens.contains("CREATE TABLE IF NOT EXISTS"));
1609        assert!(tokens.contains("users"));
1610        assert!(tokens.contains("deleted_at"));
1611        assert!(tokens.contains("version"));
1612    }
1613
1614    #[test]
1615    fn generate_generic_impl_rejects_tuple_struct() {
1616        let input: DeriveInput = parse_quote! {
1617            struct User(i32, String);
1618        };
1619        let err = generate_generic_impl(&input).unwrap_err();
1620        assert!(err.to_string().contains("named fields"));
1621    }
1622
1623    #[test]
1624    fn generate_generic_impl_rejects_non_struct() {
1625        let input: DeriveInput = parse_quote! {
1626            enum User {
1627                A,
1628                B,
1629            }
1630        };
1631        let err = generate_generic_impl(&input).unwrap_err();
1632        assert!(err.to_string().contains("only supports structs"));
1633    }
1634
1635    #[test]
1636    fn generate_generic_impl_version_update_branch() {
1637        let input: DeriveInput = parse_quote! {
1638            struct User {
1639                id: i32,
1640                version: i32,
1641                name: String,
1642            }
1643        };
1644        let tokens = generate_generic_impl(&input).unwrap().to_string();
1645        assert!(tokens.contains("version = version + 1"));
1646    }
1647
1648    #[test]
1649    fn generate_generic_impl_no_version_branch() {
1650        let input: DeriveInput = parse_quote! {
1651            struct User {
1652                id: i32,
1653                name: String,
1654            }
1655        };
1656        let tokens = generate_generic_impl(&input).unwrap().to_string();
1657        assert!(!tokens.contains("version = version + 1"));
1658    }
1659
1660    #[test]
1661    fn generate_generic_impl_includes_default_hooks_and_validation() {
1662        let input: DeriveInput = parse_quote! {
1663            struct User {
1664                id: i32,
1665                name: String,
1666            }
1667        };
1668        let tokens = generate_generic_impl(&input).unwrap().to_string();
1669        assert!(tokens.contains("ModelHooks"));
1670        assert!(tokens.contains("ModelValidation"));
1671    }
1672
1673    #[test]
1674    fn generate_generic_impl_includes_schema_impl() {
1675        let input: DeriveInput = parse_quote! {
1676            struct User {
1677                id: i32,
1678                name: String,
1679            }
1680        };
1681        let tokens = generate_generic_impl(&input).unwrap().to_string();
1682        assert!(tokens.contains("ModelSchema"));
1683        assert!(tokens.contains("SchemaColumn"));
1684    }
1685
1686    #[test]
1687    fn generate_generic_impl_includes_index_and_foreign_key_metadata() {
1688        let input: DeriveInput = parse_quote! {
1689            struct User {
1690                id: i32,
1691                #[premix(index)]
1692                name: String,
1693                #[premix(unique(name = "users_email_uidx"))]
1694                email: String,
1695                #[premix(foreign_key(table = "accounts", column = "id"))]
1696                account_id: i32,
1697            }
1698        };
1699        let tokens = generate_generic_impl(&input).unwrap().to_string();
1700        assert!(tokens.contains("SchemaIndex"));
1701        assert!(tokens.contains("idx_users_name"));
1702        assert!(tokens.contains("users_email_uidx"));
1703        assert!(tokens.contains("SchemaForeignKey"));
1704        assert!(tokens.contains("accounts"));
1705        assert!(tokens.contains("account_id"));
1706    }
1707
1708    #[test]
1709    fn generate_generic_impl_includes_sensitive_fields() {
1710        let input: DeriveInput = parse_quote! {
1711            struct User {
1712                id: i32,
1713                #[premix(sensitive)]
1714                email: String,
1715            }
1716        };
1717        let tokens = generate_generic_impl(&input).unwrap().to_string();
1718        assert!(tokens.contains("sensitive_fields"));
1719        assert!(tokens.contains("\"email\""));
1720    }
1721
1722    #[test]
1723    fn generate_generic_impl_skips_custom_hooks_and_validation() {
1724        let input: DeriveInput = parse_quote! {
1725            #[premix(custom_hooks, custom_validation)]
1726            struct User {
1727                id: i32,
1728                name: String,
1729            }
1730        };
1731        let tokens = generate_generic_impl(&input).unwrap().to_string();
1732        assert!(!tokens.contains("impl premix_orm :: ModelHooks"));
1733        assert!(!tokens.contains("impl premix_orm :: ModelValidation"));
1734    }
1735
1736    #[test]
1737    fn is_ignored_detects_attribute() {
1738        let field: Field = parse_quote! {
1739            #[premix(ignore)]
1740            ignored: Option<String>
1741        };
1742        assert!(is_ignored(&field));
1743    }
1744
1745    #[test]
1746    fn is_ignored_false_for_other_attrs() {
1747        let field: Field = parse_quote! {
1748            #[serde(skip)]
1749            name: String
1750        };
1751        assert!(!is_ignored(&field));
1752    }
1753
1754    #[test]
1755    fn is_ignored_false_for_premix_other_arg() {
1756        let field: Field = parse_quote! {
1757            #[premix(skip)]
1758            name: String
1759        };
1760        assert!(!is_ignored(&field));
1761    }
1762
1763    #[test]
1764    fn is_sensitive_detects_attribute() {
1765        let field: Field = parse_quote! {
1766            #[premix(sensitive)]
1767            secret: String
1768        };
1769        assert!(is_sensitive(&field));
1770    }
1771
1772    #[test]
1773    fn is_sensitive_false_for_other_attrs() {
1774        let field: Field = parse_quote! {
1775            #[serde(skip)]
1776            secret: String
1777        };
1778        assert!(!is_sensitive(&field));
1779    }
1780
1781    #[test]
1782    fn is_ignored_false_when_premix_has_no_args() {
1783        let field: Field = parse_quote! {
1784            #[premix]
1785            name: String
1786        };
1787        assert!(!is_ignored(&field));
1788    }
1789
1790    #[test]
1791    fn derive_model_impl_emits_tokens() {
1792        let input: DeriveInput = parse_quote! {
1793            struct User {
1794                id: i32,
1795                name: String,
1796            }
1797        };
1798        let tokens = derive_model_impl(&input).unwrap().to_string();
1799        assert!(tokens.contains("impl"));
1800    }
1801
1802    #[test]
1803    fn derive_model_impl_propagates_error() {
1804        let input: DeriveInput = parse_quote! {
1805            enum User {
1806                A,
1807            }
1808        };
1809        let err = derive_model_impl(&input).unwrap_err();
1810        assert!(err.to_string().contains("only supports structs"));
1811    }
1812
1813    #[test]
1814    fn generate_generic_impl_includes_soft_delete_delete_impl() {
1815        let input: DeriveInput = parse_quote! {
1816            struct AuditLog {
1817                id: i32,
1818                deleted_at: Option<String>,
1819            }
1820        };
1821        let tokens = generate_generic_impl(&input).unwrap().to_string();
1822        assert!(tokens.contains("deleted_at ="));
1823        assert!(tokens.contains("has_soft_delete"));
1824    }
1825
1826    #[test]
1827    fn generate_generic_impl_ignores_marked_fields() {
1828        let input: DeriveInput = parse_quote! {
1829            struct User {
1830                id: i32,
1831                name: String,
1832                #[premix(ignore)]
1833                temp: Option<String>,
1834            }
1835        };
1836        let tokens = generate_generic_impl(&input).unwrap().to_string();
1837        assert!(tokens.contains("temp : None"));
1838        assert!(!tokens.contains("\"temp\""));
1839    }
1840
1841    #[test]
1842    fn generate_generic_impl_adds_relation_bounds() {
1843        let input: DeriveInput = parse_quote! {
1844            struct User {
1845                id: i32,
1846                #[has_many(Post)]
1847                posts: Vec<Post>,
1848            }
1849        };
1850        let tokens = generate_generic_impl(&input).unwrap().to_string();
1851        assert!(tokens.contains("Post : premix_orm :: Model < DB >"));
1852    }
1853
1854    #[test]
1855    fn generate_generic_impl_records_field_names() {
1856        let input: DeriveInput = parse_quote! {
1857            struct Account {
1858                id: i32,
1859                user_id: i32,
1860                is_active: bool,
1861            }
1862        };
1863        let tokens = generate_generic_impl(&input).unwrap().to_string();
1864        assert!(tokens.contains("\"user_id\""));
1865        assert!(tokens.contains("\"is_active\""));
1866    }
1867
1868    #[test]
1869    fn generate_generic_impl_creates_column_constants() {
1870        let input: DeriveInput = parse_quote! {
1871            struct User {
1872                id: i32,
1873                name: String,
1874            }
1875        };
1876        let tokens = generate_generic_impl(&input).unwrap().to_string();
1877        assert!(tokens.contains("pub mod columns_user"));
1878        assert!(tokens.contains("pub const ID : & str = \"id\""));
1879        assert!(tokens.contains("pub const NAME : & str = \"name\""));
1880    }
1881}