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#[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 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 Ok(quote! {
917 #[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 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 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 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 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 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}