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