1use proc_macro::TokenStream;
2use quote::quote;
3use syn::{parse_macro_input, Data, DeriveInput, Fields, Lit, Meta, NestedMeta};
4
5#[proc_macro_derive(Entity, attributes(table, column, key, relation))]
6pub fn entity(input: TokenStream) -> TokenStream {
7 let input = parse_macro_input!(input as DeriveInput);
8 let struct_name = input.ident;
9
10 let mut table_name = struct_name.to_string();
12 let mut table_schema: Option<String> = None;
13 for attr in &input.attrs {
14 if attr.path.is_ident("table") {
15 if let Ok(Meta::List(list)) = attr.parse_meta() {
16 for nested in list.nested.iter() {
17 match nested {
18 NestedMeta::Meta(Meta::NameValue(nv)) if nv.path.is_ident("name") => {
19 if let Lit::Str(s) = &nv.lit {
20 table_name = s.value();
21 }
22 }
23 NestedMeta::Meta(Meta::NameValue(nv)) if nv.path.is_ident("schema") => {
24 if let Lit::Str(s) = &nv.lit {
25 table_schema = Some(s.value());
26 }
27 }
28 _ => {}
29 }
30 }
31 }
32 }
33 }
34
35 let mut columns = Vec::new();
36 let mut keys = Vec::new();
37 let mut relations = Vec::new();
38 let mut from_ms_fields = Vec::new();
39 let mut from_pg_fields = Vec::new();
40 let mut from_ms_fields_with_prefix = Vec::new();
41 let mut from_pg_fields_with_prefix = Vec::new();
42 let mut assoc_consts = Vec::new();
43 let mut insert_stmts = Vec::new();
44 let mut update_set_stmts = Vec::new();
45 let mut update_where_stmts = Vec::new();
46 let mut delete_where_stmts = Vec::new();
47 let mut validate_stmts = Vec::new();
48 let mut first_key_col = String::new();
49 let mut has_identity = false;
50 let mut key_trait_impls = Vec::new();
51
52 if let Data::Struct(ds) = input.data {
53 if let Fields::Named(fields_named) = ds.fields {
54 for field in fields_named.named {
55 let ident = field.ident.unwrap();
56 let ty = field.ty.clone();
57
58 let mut is_option = false;
59 let mut is_string = false;
60 let mut inner_ty = ty.clone();
61 if let syn::Type::Path(tp) = &ty {
62 if tp.path.segments.len() == 1 && tp.path.segments[0].ident == "Option" {
63 is_option = true;
64 if let syn::PathArguments::AngleBracketed(args) = &tp.path.segments[0].arguments {
65 if let Some(syn::GenericArgument::Type(t)) = args.args.first() {
66 inner_ty = t.clone();
67 if let syn::Type::Path(itp) = t {
68 if itp.path.is_ident("String") {
69 is_string = true;
70 }
71 }
72 }
73 }
74 } else if tp.path.is_ident("String") {
75 is_string = true;
76 }
77 }
78
79 let mut is_relation = false;
81 let mut rel_foreign_key = String::new();
82 let mut rel_table = String::new();
83 let mut rel_table_number: Option<u32> = None;
84 let mut rel_ignore_in_update = false;
85 let mut rel_ignore_in_insert = false;
86
87 for attr in field.attrs.iter() {
88 if attr.path.is_ident("relation") {
89 is_relation = true;
90 if let Ok(Meta::List(list)) = attr.parse_meta() {
91 for nested in list.nested.iter() {
92 match nested {
93 NestedMeta::Meta(Meta::NameValue(nv)) if nv.path.is_ident("foreign_key") => {
94 if let Lit::Str(s) = &nv.lit {
95 rel_foreign_key = s.value();
96 }
97 }
98 NestedMeta::Meta(Meta::NameValue(nv)) if nv.path.is_ident("table") => {
99 if let Lit::Str(s) = &nv.lit {
100 rel_table = s.value();
101 }
102 }
103 NestedMeta::Meta(Meta::NameValue(nv)) if nv.path.is_ident("table_number") => {
104 if let Lit::Int(i) = &nv.lit {
105 rel_table_number = i.base10_parse().ok();
106 }
107 }
108 NestedMeta::Meta(Meta::Path(p)) if p.is_ident("ignore_in_update") => {
109 rel_ignore_in_update = true;
110 }
111 NestedMeta::Meta(Meta::Path(p)) if p.is_ident("ignore_in_insert") => {
112 rel_ignore_in_insert = true;
113 }
114 _ => {}
115 }
116 }
117 }
118 }
119 }
120
121 if is_relation {
122 let table_num_tokens = match rel_table_number {
123 Some(v) => quote! { Some(#v) },
124 None => quote! { None },
125 };
126 relations.push(quote! {
127 ::rquery_orm::mapping::RelationMeta {
128 name: stringify!(#ident),
129 foreign_key: #rel_foreign_key,
130 table: #rel_table,
131 table_number: #table_num_tokens,
132 ignore_in_update: #rel_ignore_in_update,
133 ignore_in_insert: #rel_ignore_in_insert,
134 }
135 });
136 continue;
137 }
138
139 let mut col_name = ident.to_string();
141 let mut col_name_lit: Option<syn::LitStr> = None;
142 let mut is_key = false;
143 let mut is_identity = false;
144 let mut required = false;
145 let mut allow_null = false;
146 let mut max_length: Option<usize> = None;
147 let mut min_length: Option<usize> = None;
148 let mut allow_empty = true;
149 let mut regex: Option<String> = None;
150 let mut err_max_length: Option<String> = None;
151 let mut err_min_length: Option<String> = None;
152 let mut err_required: Option<String> = None;
153 let mut err_allow_null: Option<String> = None;
154 let mut err_allow_empty: Option<String> = None;
155 let mut err_regex: Option<String> = None;
156 let mut ignore = false;
157 let mut ignore_in_update = false;
158 let mut ignore_in_insert = false;
159 let mut ignore_in_delete = false;
160 let mut key_ignore_in_update = false;
161 let mut key_ignore_in_insert = false;
162
163 for attr in field.attrs.iter() {
164 if attr.path.is_ident("column") {
165 if let Ok(Meta::List(list)) = attr.parse_meta() {
166 for nested in list.nested.iter() {
167 match nested {
168 NestedMeta::Meta(Meta::NameValue(nv)) => {
169 if nv.path.is_ident("name") {
170 if let Lit::Str(s) = &nv.lit { col_name = s.value(); }
171 } else if nv.path.is_ident("max_length") {
172 if let Lit::Int(i) = &nv.lit { max_length = i.base10_parse().ok(); }
173 } else if nv.path.is_ident("min_length") {
174 if let Lit::Int(i) = &nv.lit { min_length = i.base10_parse().ok(); }
175 } else if nv.path.is_ident("regex") {
176 if let Lit::Str(s) = &nv.lit { regex = Some(s.value()); }
177 } else if nv.path.is_ident("error_max_length") {
178 if let Lit::Str(s) = &nv.lit { err_max_length = Some(s.value()); }
179 } else if nv.path.is_ident("error_min_length") {
180 if let Lit::Str(s) = &nv.lit { err_min_length = Some(s.value()); }
181 } else if nv.path.is_ident("error_required") {
182 if let Lit::Str(s) = &nv.lit { err_required = Some(s.value()); }
183 } else if nv.path.is_ident("error_allow_null") {
184 if let Lit::Str(s) = &nv.lit { err_allow_null = Some(s.value()); }
185 } else if nv.path.is_ident("error_allow_empty") {
186 if let Lit::Str(s) = &nv.lit { err_allow_empty = Some(s.value()); }
187 } else if nv.path.is_ident("error_regex") {
188 if let Lit::Str(s) = &nv.lit { err_regex = Some(s.value()); }
189 } else if nv.path.is_ident("allow_empty") {
190 if let Lit::Bool(b) = &nv.lit { allow_empty = b.value; }
191 } else if nv.path.is_ident("required") {
192 if let Lit::Bool(b) = &nv.lit { required = b.value; }
193 } else if nv.path.is_ident("allow_null") {
194 if let Lit::Bool(b) = &nv.lit { allow_null = b.value; }
195 } else if nv.path.is_ident("ignore_in_update") {
196 if let Lit::Bool(b) = &nv.lit { ignore_in_update = b.value; }
197 } else if nv.path.is_ident("ignore_in_insert") {
198 if let Lit::Bool(b) = &nv.lit { ignore_in_insert = b.value; }
199 } else if nv.path.is_ident("ignore_in_delete") {
200 if let Lit::Bool(b) = &nv.lit { ignore_in_delete = b.value; }
201 } else if nv.path.is_ident("ignore") {
202 if let Lit::Bool(b) = &nv.lit { ignore = b.value; }
203 }
204 }
205 NestedMeta::Meta(Meta::Path(p)) => {
206 if p.is_ident("required") { required = true; }
207 else if p.is_ident("allow_null") { allow_null = true; }
208 else if p.is_ident("allow_empty") { allow_empty = true; }
209 else if p.is_ident("ignore") { ignore = true; }
210 else if p.is_ident("ignore_in_update") { ignore_in_update = true; }
211 else if p.is_ident("ignore_in_insert") { ignore_in_insert = true; }
212 else if p.is_ident("ignore_in_delete") { ignore_in_delete = true; }
213 }
214 _ => {}
215 }
216 }
217 }
218 } else if attr.path.is_ident("key") {
219 is_key = true;
220 if let Ok(Meta::List(list)) = attr.parse_meta() {
221 for nested in list.nested.iter() {
222 match nested {
223 NestedMeta::Meta(Meta::NameValue(nv)) => {
224 if nv.path.is_ident("is_identity") {
225 if let Lit::Bool(b) = &nv.lit { is_identity = b.value; }
226 } else if nv.path.is_ident("name") {
227 if let Lit::Str(s) = &nv.lit { col_name = s.value(); }
228 } else if nv.path.is_ident("ignore_in_update") {
229 if let Lit::Bool(b) = &nv.lit { key_ignore_in_update = b.value; }
230 } else if nv.path.is_ident("ignore_in_insert") {
231 if let Lit::Bool(b) = &nv.lit { key_ignore_in_insert = b.value; }
232 }
233 }
234 NestedMeta::Meta(Meta::Path(p)) => {
235 if p.is_ident("ignore_in_update") { key_ignore_in_update = true; }
236 else if p.is_ident("ignore_in_insert") { key_ignore_in_insert = true; }
237 }
238 _ => {}
239 }
240 }
241 }
242 }
243 }
244
245 let col_name_lit_inner = syn::LitStr::new(&col_name, proc_macro2::Span::call_site());
247
248 let max_length_token = match max_length { Some(v) => quote! { Some(#v) }, None => quote! { None } };
249 let min_length_token = match min_length { Some(v) => quote! { Some(#v) }, None => quote! { None } };
250 let regex_token = match regex.as_ref() { Some(s) => quote! { Some(#s) }, None => quote! { None } };
251 let err_max_length_token = match err_max_length.as_ref() { Some(s) => quote! { Some(#s) }, None => quote! { None } };
252 let err_min_length_token = match err_min_length.as_ref() { Some(s) => quote! { Some(#s) }, None => quote! { None } };
253 let err_required_token = match err_required.as_ref() { Some(s) => quote! { Some(#s) }, None => quote! { None } };
254 let err_allow_null_token = match err_allow_null.as_ref() { Some(s) => quote! { Some(#s) }, None => quote! { None } };
255 let err_allow_empty_token = match err_allow_empty.as_ref() { Some(s) => quote! { Some(#s) }, None => quote! { None } };
256 let err_regex_token = match err_regex.as_ref() { Some(s) => quote! { Some(#s) }, None => quote! { None } };
257
258 columns.push(quote! {
259 ::rquery_orm::mapping::ColumnMeta {
260 name: #col_name_lit_inner,
261 required: #required,
262 allow_null: #allow_null,
263 max_length: #max_length_token,
264 min_length: #min_length_token,
265 allow_empty: #allow_empty,
266 regex: #regex_token,
267 error_max_length: #err_max_length_token,
268 error_min_length: #err_min_length_token,
269 error_required: #err_required_token,
270 error_allow_null: #err_allow_null_token,
271 error_allow_empty: #err_allow_empty_token,
272 error_regex: #err_regex_token,
273 ignore: #ignore,
274 ignore_in_update: #ignore_in_update,
275 ignore_in_insert: #ignore_in_insert,
276 ignore_in_delete: #ignore_in_delete,
277 }
278 });
279
280 if is_key {
281 keys.push(quote! {
282 ::rquery_orm::mapping::KeyMeta {
283 column: #col_name,
284 is_identity: #is_identity,
285 ignore_in_update: #key_ignore_in_update,
286 ignore_in_insert: #key_ignore_in_insert,
287 }
288 });
289 if first_key_col.is_empty() {
290 first_key_col = col_name.clone();
291 if let syn::Type::Path(tp) = &ty {
292 if tp.qself.is_none() {
293 if tp.path.is_ident("i32") {
294 key_trait_impls.push(quote! { impl ::rquery_orm::mapping::KeyAsInt for #struct_name { fn key(&self) -> i32 { self.#ident } } });
295 } else if tp.path.is_ident("String") {
296 key_trait_impls.push(quote! { impl ::rquery_orm::mapping::KeyAsString for #struct_name { fn key(&self) -> String { self.#ident.clone() } } });
297 } else {
298 let last = tp.path.segments.last().unwrap().ident.to_string();
299 if last == "Uuid" {
300 key_trait_impls.push(quote! { impl ::rquery_orm::mapping::KeyAsGuid for #struct_name { fn key(&self) -> uuid::Uuid { self.#ident } } });
301 }
302 }
303 }
304 }
305 }
306 if is_identity { has_identity = true; }
307 }
308
309 assoc_consts.push(quote! { pub const #ident: &'static str = #col_name_lit_inner; });
311
312 if is_option {
313 if is_string {
314 from_ms_fields.push(quote! { #ident: row.try_get::<&str, _>(#col_name_lit_inner)?.map(|v| v.to_string()) });
315 from_ms_fields_with_prefix.push(quote! { #ident: { let k = format!("{}_{}", prefix, #col_name_lit_inner); row.try_get::<&str, _>(k.as_str())?.map(|v| v.to_string()) } });
316 } else {
317 from_ms_fields.push(quote! { #ident: row.try_get::<#inner_ty, _>(#col_name_lit_inner)? });
318 from_ms_fields_with_prefix.push(quote! { #ident: { let k = format!("{}_{}", prefix, #col_name_lit_inner); row.try_get::<#inner_ty, _>(k.as_str())? } });
319 }
320 from_pg_fields.push(quote! { #ident: row.try_get(#col_name_lit_inner)? });
321 from_pg_fields_with_prefix.push(quote! { #ident: { let k = format!("{}_{}", prefix, #col_name_lit_inner); row.try_get(k.as_str())? } });
322 } else {
323 if is_string {
324 from_ms_fields.push(quote! { #ident: row.try_get::<&str, _>(#col_name_lit_inner)?.unwrap().to_string() });
325 from_ms_fields_with_prefix.push(quote! { #ident: { let k = format!("{}_{}", prefix, #col_name_lit_inner); row.try_get::<&str, _>(k.as_str())?.unwrap().to_string() } });
326 } else {
327 from_ms_fields.push(quote! { #ident: row.try_get::<#inner_ty, _>(#col_name_lit_inner)?.unwrap() });
328 from_ms_fields_with_prefix.push(quote! { #ident: { let k = format!("{}_{}", prefix, #col_name_lit_inner); row.try_get::<#inner_ty, _>(k.as_str())?.unwrap() } });
329 }
330 from_pg_fields.push(quote! { #ident: row.try_get(#col_name_lit_inner)? });
331 from_pg_fields_with_prefix.push(quote! { #ident: { let k = format!("{}_{}", prefix, #col_name_lit_inner); row.try_get(k.as_str())? } });
332 }
333
334 if !is_identity && !ignore && !ignore_in_insert && !key_ignore_in_insert {
335 insert_stmts.push(quote! {
336 cols.push(#col_name);
337 vals.push(match style {
338 ::rquery_orm::query::PlaceholderStyle::AtP => format!("@P{}", idx),
339 ::rquery_orm::query::PlaceholderStyle::Dollar => format!("${}", idx),
340 });
341 params.push(self.#ident.clone().to_param());
342 idx += 1;
343 });
344 }
345
346 if !is_key {
347 if !ignore && !ignore_in_update {
348 update_set_stmts.push(quote! {
349 sets.push(format!("{} = {}", #col_name, match style {
350 ::rquery_orm::query::PlaceholderStyle::AtP => format!("@P{}", idx),
351 ::rquery_orm::query::PlaceholderStyle::Dollar => format!("${}", idx),
352 }));
353 params.push(self.#ident.clone().to_param());
354 idx += 1;
355 });
356 }
357 } else if !key_ignore_in_update {
358 update_where_stmts.push(quote! {
359 wheres.push(format!("{} = {}", #col_name, match style {
360 ::rquery_orm::query::PlaceholderStyle::AtP => format!("@P{}", idx),
361 ::rquery_orm::query::PlaceholderStyle::Dollar => format!("${}", idx),
362 }));
363 params.push(self.#ident.clone().to_param());
364 idx += 1;
365 });
366 delete_where_stmts.push(quote! {
367 wheres.push(format!("{} = {}", #col_name, match style {
368 ::rquery_orm::query::PlaceholderStyle::AtP => format!("@P{}", idx),
369 ::rquery_orm::query::PlaceholderStyle::Dollar => format!("${}", idx),
370 }));
371 params.push(self.#ident.clone().to_param());
372 idx += 1;
373 });
374 }
375
376 let field_literal = col_name.clone();
378 let required_push = if let Some(msg) = err_required.clone() {
379 quote! { errors.push(#msg.to_string()); }
380 } else {
381 let n = field_literal.clone();
382 quote! { errors.push(format!("{} is required", #n)); }
383 };
384 let allow_null_push = if let Some(msg) = err_allow_null.clone() {
385 quote! { errors.push(#msg.to_string()); }
386 } else {
387 let n = field_literal.clone();
388 quote! { errors.push(format!("{} cannot be null", #n)); }
389 };
390 let allow_empty_push = if let Some(msg) = err_allow_empty.clone() {
391 quote! { errors.push(#msg.to_string()); }
392 } else {
393 let n = field_literal.clone();
394 quote! { errors.push(format!("{} cannot be empty", #n)); }
395 };
396 let max_check = if let Some(max) = max_length {
397 let err_push = if let Some(msg) = err_max_length.clone() {
398 quote! { errors.push(#msg.to_string()); }
399 } else {
400 let n = field_literal.clone();
401 quote! { errors.push(format!("{} exceeds max length {}", #n, #max)); }
402 };
403 quote! { if value.len() > #max { #err_push } }
404 } else { quote! {} };
405 let min_check = if let Some(min) = min_length {
406 let err_push = if let Some(msg) = err_min_length.clone() {
407 quote! { errors.push(#msg.to_string()); }
408 } else {
409 let n = field_literal.clone();
410 quote! { errors.push(format!("{} below min length {}", #n, #min)); }
411 };
412 quote! { if value.len() < #min { #err_push } }
413 } else { quote! {} };
414 let regex_check = if let Some(re) = regex.clone() {
415 let err_push = if let Some(msg) = err_regex.clone() {
416 quote! { errors.push(#msg.to_string()); }
417 } else {
418 let n = field_literal.clone();
419 quote! { errors.push(format!("{} has invalid format", #n)); }
420 };
421 quote! {{
422 static RE: ::std::sync::OnceLock<::regex::Regex> = ::std::sync::OnceLock::new();
423 let re = RE.get_or_init(|| ::regex::Regex::new(#re).unwrap());
424 if !re.is_match(value) { #err_push }
425 }}
426 } else { quote! {} };
427 if is_option {
428 let some_branch = if is_string {
429 quote! {
430 if value.is_empty() {
431 if #required {
432 #required_push
433 } else if !#allow_empty {
434 #allow_empty_push
435 }
436 }
437 #max_check
438 #min_check
439 #regex_check
440 }
441 } else {
442 quote! {}
443 };
444 validate_stmts.push(quote! {
445 match &self.#ident {
446 None => {
447 if #required {
448 #required_push
449 } else if !#allow_null {
450 #allow_null_push
451 }
452 }
453 Some(value) => { #some_branch }
454 }
455 });
456 } else if is_string {
457 validate_stmts.push(quote! {
458 let value = &self.#ident;
459 if value.is_empty() {
460 if #required {
461 #required_push
462 } else if !#allow_empty {
463 #allow_empty_push
464 }
465 }
466 #max_check
467 #min_check
468 #regex_check
469 });
470 }
471 }
472 }
473 }
474
475 let table_name_lit = syn::LitStr::new(&table_name, proc_macro2::Span::call_site());
476 let schema_tokens = match table_schema {
477 Some(s) => {
478 let s_lit = syn::LitStr::new(&s, proc_macro2::Span::call_site());
479 quote! { Some(#s_lit) }
480 }
481 None => quote! { None },
482 };
483 let first_key_col_literal = first_key_col.clone();
484
485 let expanded = quote! {
486 const COLUMNS: &[::rquery_orm::mapping::ColumnMeta] = &[#(#columns),*];
487 const KEYS: &[::rquery_orm::mapping::KeyMeta] = &[#(#keys),*];
488 const RELATIONS: &[::rquery_orm::mapping::RelationMeta] = &[#(#relations),*];
489 static TABLE_META: ::rquery_orm::mapping::TableMeta = ::rquery_orm::mapping::TableMeta {
490 name: #table_name_lit,
491 schema: #schema_tokens,
492 columns: COLUMNS,
493 keys: KEYS,
494 relations: RELATIONS,
495 };
496
497 impl ::rquery_orm::mapping::Entity for #struct_name {
498 fn table() -> &'static ::rquery_orm::mapping::TableMeta {
499 &TABLE_META
500 }
501 }
502
503 impl ::rquery_orm::mapping::FromRowNamed for #struct_name {
504 fn from_row_ms(row: &tiberius::Row) -> anyhow::Result<Self> {
505 Ok(Self { #(#from_ms_fields),* })
506 }
507 fn from_row_pg(row: &tokio_postgres::Row) -> anyhow::Result<Self> {
508 Ok(Self { #(#from_pg_fields),* })
509 }
510 }
511
512 impl ::rquery_orm::mapping::FromRowWithPrefix for #struct_name {
513 fn from_row_ms_with(row: &tiberius::Row, prefix: &str) -> anyhow::Result<Self> {
514 Ok(Self { #(#from_ms_fields_with_prefix),* })
515 }
516 fn from_row_pg_with(row: &tokio_postgres::Row, prefix: &str) -> anyhow::Result<Self> {
517 Ok(Self { #(#from_pg_fields_with_prefix),* })
518 }
519 }
520
521 impl ::rquery_orm::mapping::Validatable for #struct_name {
522 fn validate(&self) -> Result<(), Vec<String>> {
523 let mut errors = Vec::new();
524 #(#validate_stmts)*
525 if errors.is_empty() { Ok(()) } else { Err(errors) }
526 }
527 }
528
529 impl ::rquery_orm::mapping::Persistable for #struct_name {
530 fn build_insert(&self, style: ::rquery_orm::query::PlaceholderStyle) -> (String, Vec<::rquery_orm::query::SqlParam>, bool) {
531 use ::rquery_orm::query::ToParam;
532 let mut cols = Vec::new();
533 let mut vals = Vec::new();
534 let mut params = Vec::new();
535 let mut idx = 1;
536 #(#insert_stmts)*
537 let sql = format!("INSERT INTO {} ({}) VALUES ({})", #table_name, cols.join(", "), vals.join(", "));
538 (sql, params, #has_identity)
539 }
540
541 fn build_update(&self, style: ::rquery_orm::query::PlaceholderStyle) -> (String, Vec<::rquery_orm::query::SqlParam>) {
542 use ::rquery_orm::query::ToParam;
543 let mut sets = Vec::new();
544 let mut wheres = Vec::new();
545 let mut params = Vec::new();
546 let mut idx = 1;
547 #(#update_set_stmts)*
548 #(#update_where_stmts)*
549 let sql = format!("UPDATE {} SET {} WHERE {}", #table_name, sets.join(", "), wheres.join(" AND "));
550 (sql, params)
551 }
552
553 fn build_delete(&self, style: ::rquery_orm::query::PlaceholderStyle) -> (String, Vec<::rquery_orm::query::SqlParam>) {
554 use ::rquery_orm::query::ToParam;
555 let mut wheres = Vec::new();
556 let mut params = Vec::new();
557 let mut idx = 1;
558 #(#delete_where_stmts)*
559 let sql = format!("DELETE FROM {} WHERE {}", #table_name, wheres.join(" AND "));
560 (sql, params)
561 }
562
563 fn build_delete_by_key(key: ::rquery_orm::query::SqlParam, style: ::rquery_orm::query::PlaceholderStyle) -> (String, Vec<::rquery_orm::query::SqlParam>) {
564 let placeholder = match style {
565 ::rquery_orm::query::PlaceholderStyle::AtP => "@P1".to_string(),
566 ::rquery_orm::query::PlaceholderStyle::Dollar => "$1".to_string(),
567 };
568 let sql = format!("DELETE FROM {} WHERE {} = {}", #table_name, #first_key_col_literal, placeholder);
569 (sql, vec![key])
570 }
571 }
572
573 impl #struct_name {
574 pub const TABLE: &'static str = #table_name_lit;
575 #(#assoc_consts)*
576 }
577
578 #(#key_trait_impls)*
579 };
580
581 TokenStream::from(expanded)
582}