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