1#![warn(missing_docs)]
15
16use proc_macro::TokenStream as Tokens;
17use proc_macro2::{Span, TokenStream};
18use quote::{quote, quote_spanned, ToTokens};
19use syn::meta::ParseNestedMeta;
20use syn::spanned::Spanned;
21use syn::{parse_macro_input, Data, DeriveInput, Field, Fields, Ident, LitStr, Type};
22
23#[proc_macro_derive(Row, attributes(row))]
28pub fn derive_row(input: Tokens) -> Tokens {
29 let input = parse_macro_input!(input as DeriveInput);
30 row(&input)
31 .unwrap_or_else(syn::Error::into_compile_error)
32 .into()
33}
34
35#[proc_macro_derive(Columns, attributes(row))]
39pub fn derive_columns(input: Tokens) -> Tokens {
40 let input = parse_macro_input!(input as DeriveInput);
41 columns_only(&input)
42 .unwrap_or_else(syn::Error::into_compile_error)
43 .into()
44}
45
46const ROW_KEYS: &str = "`table`, `schema`, `mirror`, `sync`, `key`, `active`, `retired`, \
48 `origin`, `unmirrored`, `apply` or `crate`";
49
50const LONGEST_NAME: usize = 63;
53
54#[derive(Default)]
55struct Args {
56 table: Option<LitStr>,
57 key: Option<LitStr>,
58 schema: Option<LitStr>,
59 mirror: Option<LitStr>,
60 sync: Option<LitStr>,
61 active: Option<LitStr>,
62 retired: Option<LitStr>,
63 origin: Option<LitStr>,
64 unmirrored: Option<LitStr>,
65 apply: Option<LitStr>,
66 krate: Option<LitStr>,
67}
68
69fn parse_args(input: &DeriveInput, only_crate: bool) -> syn::Result<Args> {
72 let mut args = Args::default();
73 for attr in input.attrs.iter().filter(|a| a.path().is_ident("row")) {
74 attr.parse_nested_meta(|meta| {
75 let slot = match meta.path.get_ident().map(Ident::to_string).as_deref() {
76 Some("crate") => &mut args.krate,
77 Some(_) if only_crate => return Err(meta.error(
78 "#[derive(Columns)] is a part a row flattens in: only `crate` applies to it",
79 )),
80 Some("table") => &mut args.table,
81 Some("key") => &mut args.key,
82 Some("schema") => &mut args.schema,
83 Some("mirror") => &mut args.mirror,
84 Some("sync") => &mut args.sync,
85 Some("active") => &mut args.active,
86 Some("retired") => &mut args.retired,
87 Some("origin") => &mut args.origin,
88 Some("unmirrored") => &mut args.unmirrored,
89 Some("apply") => &mut args.apply,
90 _ => return Err(meta.error(format!("expected {ROW_KEYS}"))),
91 };
92 once(slot, &meta)
93 })?;
94 }
95 Ok(args)
96}
97
98fn once(slot: &mut Option<LitStr>, meta: &ParseNestedMeta) -> syn::Result<()> {
100 let name = meta.path.to_token_stream().to_string();
101 if slot.is_some() {
102 return Err(meta.error(format!("`{name}` is given twice")));
103 }
104 let value = meta
105 .value()
106 .map_err(|_| meta.error(format!("`{name}` takes a value: `{name} = \"…\"`")))?;
107 *slot = Some(value.parse()?);
108 Ok(())
109}
110
111fn identifier(text: &str, at: Span) -> syn::Result<String> {
114 if text.is_empty() {
115 return Err(syn::Error::new(at, "an empty name"));
116 }
117 if text.contains('\0') {
118 return Err(syn::Error::new(at, "a name cannot hold a NUL character"));
119 }
120 if text.len() > LONGEST_NAME {
121 return Err(syn::Error::new(
122 at,
123 format!("`{text}` is longer than the {LONGEST_NAME} bytes PostgreSQL keeps of a name"),
124 ));
125 }
126 Ok(text.to_owned())
127}
128
129fn option(value: Option<impl ToTokens>) -> TokenStream {
131 match value {
132 Some(value) => quote!(::core::option::Option::Some(#value)),
133 None => quote!(::core::option::Option::None),
134 }
135}
136
137fn quoted(name: &str) -> String {
139 format!("\"{}\"", name.replace('"', "\"\""))
140}
141
142fn names(lit: &LitStr) -> syn::Result<Vec<String>> {
144 let mut out: Vec<String> = Vec::new();
145 for part in lit.value().split(',') {
146 let name = identifier(part.trim(), lit.span())?;
147 if out.contains(&name) {
148 return Err(syn::Error::new_spanned(
149 lit,
150 format!("`{name}` is listed twice"),
151 ));
152 }
153 out.push(name);
154 }
155 Ok(out)
156}
157
158fn krate(args: &Args) -> syn::Result<syn::Path> {
159 match &args.krate {
160 Some(lit) => lit.parse(),
161 None => Ok(syn::parse_quote!(::magic_sync)),
162 }
163}
164
165fn option_inner(ty: &Type) -> Option<&Type> {
170 let Type::Path(path) = ty else { return None };
171 if path.qself.is_some() {
172 return None;
173 }
174 let names: Vec<String> = path
175 .path
176 .segments
177 .iter()
178 .map(|s| s.ident.to_string())
179 .collect();
180 let std_path = matches!(
181 names
182 .iter()
183 .map(String::as_str)
184 .collect::<Vec<_>>()
185 .as_slice(),
186 ["Option"] | ["std" | "core", "option", "Option"]
187 );
188 if !std_path {
189 return None;
190 }
191 let syn::PathArguments::AngleBracketed(args) = &path.path.segments.last()?.arguments else {
192 return None;
193 };
194 match args.args.first()? {
195 syn::GenericArgument::Type(inner) if args.args.len() == 1 => Some(inner),
196 _ => None,
197 }
198}
199
200#[derive(Default)]
202struct SqlxField {
203 skip: bool,
204 flatten: bool,
205 rename: Option<LitStr>,
206 try_from: Option<Type>,
207}
208
209fn sqlx_field(field: &Field) -> syn::Result<SqlxField> {
210 let mut out = SqlxField::default();
211 for attr in field.attrs.iter().filter(|a| a.path().is_ident("sqlx")) {
212 attr.parse_nested_meta(|meta| {
213 if meta.path.is_ident("skip") {
214 out.skip = true;
215 } else if meta.path.is_ident("flatten") {
216 out.flatten = true;
217 } else if meta.path.is_ident("rename") {
218 out.rename = Some(meta.value()?.parse()?);
219 } else if meta.path.is_ident("try_from") {
220 out.try_from = Some(meta.value()?.parse::<LitStr>()?.parse()?);
221 } else if meta.path.is_ident("json") {
222 return Err(meta.error("#[derive(Row)] does not support #[sqlx(json)]"));
223 } else if meta.input.peek(syn::Token![=]) {
224 let _: syn::Expr = meta.value()?.parse()?;
225 } else if meta.input.peek(syn::token::Paren) {
226 meta.parse_nested_meta(|_| Ok(()))?;
227 }
228 Ok(())
229 })?;
230 }
231 if out.skip || out.flatten {
232 let stray = out
233 .rename
234 .as_ref()
235 .map(Spanned::span)
236 .or_else(|| out.try_from.as_ref().map(Spanned::span));
237 if let Some(at) = stray {
238 return Err(syn::Error::new(
239 at,
240 "a skipped or flattened field names no column: nothing reads its `rename` or `try_from`",
241 ));
242 }
243 }
244 Ok(out)
245}
246
247#[derive(Default)]
249struct RowField {
250 generated: Option<Span>,
251 replica_owned: Option<Span>,
252 version: Option<Span>,
253 created: Option<Span>,
254 desired: Option<Span>,
255}
256
257impl RowField {
258 fn any(&self) -> Option<Span> {
259 self.generated
260 .or(self.replica_owned)
261 .or(self.version)
262 .or(self.created)
263 .or(self.desired)
264 }
265}
266
267fn row_field(field: &Field) -> syn::Result<RowField> {
268 let mut out = RowField::default();
269 for attr in field.attrs.iter().filter(|a| a.path().is_ident("row")) {
270 attr.parse_nested_meta(|meta| {
271 let flag =
272 match meta.path.get_ident().map(Ident::to_string).as_deref() {
273 Some("generated") => &mut out.generated,
274 Some("replica_owned") => &mut out.replica_owned,
275 Some("version") => &mut out.version,
276 Some("created") => &mut out.created,
277 Some("desired") => &mut out.desired,
278 _ => return Err(meta.error(
279 "expected `version`, `created`, `replica_owned`, `generated` or `desired`",
280 )),
281 };
282 let name = meta.path.to_token_stream().to_string();
283 if flag.is_some() {
284 return Err(meta.error(format!("`{name}` is given twice")));
285 }
286 if !meta.input.is_empty() && !meta.input.peek(syn::Token![,]) {
287 return Err(meta.error(format!("`{name}` takes no value")));
288 }
289 *flag = Some(meta.path.span());
290 Ok(())
291 })?;
292 }
293 let refuse = |at: Option<Span>, why: &str| match at {
294 Some(at) => Err(syn::Error::new(at, why)),
295 None => Ok(()),
296 };
297 if out.generated.is_some() {
298 refuse(
299 out.version
300 .or(out.created)
301 .or(out.replica_owned)
302 .or(out.desired),
303 "a #[row(generated)] column is computed by the database: nothing writes it",
304 )?;
305 }
306 if out.desired.is_some() {
307 refuse(
308 out.version.or(out.created),
309 "a #[row(desired)] column is one a wish sets: the version moves by itself, and a \
310 #[row(created)] column never moves",
311 )?;
312 }
313 if out.version.is_some() {
314 refuse(
315 out.created,
316 "a #[row(version)] column moves with every write; a #[row(created)] one never moves",
317 )?;
318 }
319 Ok(out)
320}
321
322struct Direct {
324 name: String,
325 at: Span,
327 ident: Ident,
328 ty: Type,
329 nullable: bool,
330 flags: RowField,
331}
332
333struct Declared {
336 tokens: TokenStream,
337 direct: Vec<Direct>,
338 flattened: Vec<Type>,
339}
340
341fn refuse_shape(input: &DeriveInput) -> syn::Result<&syn::FieldsNamed> {
342 if !input.generics.params.is_empty() || input.generics.where_clause.is_some() {
343 return Err(syn::Error::new_spanned(
344 &input.generics,
345 "a row is one table: it takes no generic parameters",
346 ));
347 }
348 let Data::Struct(data) = &input.data else {
349 return Err(syn::Error::new_spanned(
350 &input.ident,
351 "a row is a struct, one field per column",
352 ));
353 };
354 let Fields::Named(fields) = &data.fields else {
355 return Err(syn::Error::new_spanned(
356 &input.ident,
357 "a row is a struct with named fields, one per column",
358 ));
359 };
360 for attr in input.attrs.iter().filter(|a| a.path().is_ident("sqlx")) {
361 attr.parse_nested_meta(|meta| {
362 if meta.path.is_ident("rename_all") {
363 return Err(meta.error(
364 "#[derive(Row)] reads column names off the fields: rename them one by one",
365 ));
366 }
367 if meta.input.peek(syn::Token![=]) {
368 let _: syn::Expr = meta.value()?.parse()?;
369 } else if meta.input.peek(syn::token::Paren) {
370 meta.parse_nested_meta(|_| Ok(()))?;
371 }
372 Ok(())
373 })?;
374 }
375 Ok(fields)
376}
377
378fn declared(input: &DeriveInput, krate: &syn::Path, key: &[String]) -> syn::Result<Declared> {
380 let fields = refuse_shape(input)?;
381 let private = quote!(#krate::__private);
382 let mut push = Vec::new();
383 let mut bind = Vec::new();
384 let mut direct: Vec<Direct> = Vec::new();
385 let mut flattened: Vec<Type> = Vec::new();
386 for field in &fields.named {
387 let sqlx = sqlx_field(field)?;
388 let flags = row_field(field)?;
389 if let Some(at) = flags.any().filter(|_| sqlx.skip || sqlx.flatten) {
390 return Err(syn::Error::new(
391 at,
392 "a #[row(…)] field names a column; a skipped or flattened field is none",
393 ));
394 }
395 if sqlx.skip {
396 continue;
397 }
398 let ty = &field.ty;
399 let ident = field.ident.clone().expect("a named field");
400 if sqlx.flatten {
401 push.push(quote_spanned! {ty.span()=>
402 <#ty as #private::Columns>::columns(out);
403 });
404 bind.push(quote_spanned! {ty.span()=>
405 <#ty as #private::Columns>::push_binds(self.#ident, sep);
406 });
407 flattened.push(ty.clone());
408 continue;
409 }
410 let field_name = ident.to_string().trim_start_matches("r#").to_owned();
411 let (name, at) = match &sqlx.rename {
412 Some(lit) => (identifier(&lit.value(), lit.span())?, lit.span()),
413 None => (identifier(&field_name, ident.span())?, ident.span()),
414 };
415 if let Some(earlier) = direct.iter().find(|d| d.name == name) {
416 return Err(syn::Error::new(
417 at,
418 format!(
419 "the column `{name}` is already the field `{}`",
420 earlier.ident
421 ),
422 ));
423 }
424 let nullable = option_inner(ty).is_some();
425 if let Some(version) = flags.version.filter(|_| nullable) {
426 return Err(syn::Error::new(
427 version,
428 "#[row(version)] orders a row's versions: it cannot be NULL",
429 ));
430 }
431 if flags.generated.is_none() {
432 bind.push(quote!(sep.push_bind(self.#ident);));
433 }
434 let decoded = sqlx.try_from.as_ref().unwrap_or(ty);
435 let key = if key.contains(&name) {
436 quote_spanned! {ty.span()=>
437 ::core::option::Option::Some(<#ty as #krate::row::KeyValue>::KIND)
438 }
439 } else {
440 quote!(::core::option::Option::None)
441 };
442 let version = if flags.version.is_some() {
443 quote_spanned! {ty.span()=>
444 ::core::option::Option::Some(<#ty as #krate::row::Version>::KIND)
445 }
446 } else {
447 quote!(::core::option::Option::None)
448 };
449 let generated = flags.generated.is_some();
450 let replica_owned = flags.replica_owned.is_some();
451 let desired = flags.desired.is_some();
452 push.push(quote! {
453 out.push(#private::Column {
454 name: #name,
455 field: #field_name,
456 nullable: #nullable,
457 generated: #generated,
458 replica_owned: #replica_owned,
459 desired: #desired,
460 key: #key,
461 version: #version,
462 int64: #private::is_int64::<#ty>(),
463 compatible: |info| {
464 <#decoded as #private::sqlx::Type<#private::Db>>::compatible(info)
465 },
466 });
467 });
468 direct.push(Direct {
469 name,
470 at,
471 ident,
472 ty: ty.clone(),
473 nullable,
474 flags,
475 });
476 }
477
478 let role = |pick: fn(&RowField) -> Option<Span>, which: TokenStream, what: &str| {
480 let marked: Vec<&Direct> = direct.iter().filter(|d| pick(&d.flags).is_some()).collect();
481 if let [_, second, ..] = marked.as_slice() {
482 return Err(syn::Error::new(
483 pick(&second.flags).unwrap_or(second.at),
484 format!("one column at most is #[row({what})]"),
485 ));
486 }
487 let here = option(marked.first().map(|column| &column.name));
488 let parts = flattened
489 .iter()
490 .map(|part| quote!(<#part as #private::Columns>::#which));
491 let refusal =
492 format!("one column at most is #[row({what})], here or in a part flattened in");
493 Ok(quote! {
494 {
495 let found = [#here, #(#parts),*];
496 if #private::count(&found) > 1 {
497 ::core::panic!(#refusal)
498 }
499 #private::first(&found)
500 }
501 })
502 };
503 let version = role(|f| f.version, quote!(VERSION), "version")?;
504 let created = role(|f| f.created, quote!(CREATED), "created")?;
505 let owned_here = direct.iter().any(|d| d.flags.replica_owned.is_some());
506 let owned_parts = flattened
507 .iter()
508 .map(|part| quote!(|| <#part as #private::Columns>::REPLICA_OWNED));
509
510 let ident = &input.ident;
511 let tokens = quote! {
512 const _: () = {
515 let _ = <#ident as #private::Columns>::VERSION;
516 let _ = <#ident as #private::Columns>::CREATED;
517 };
518
519 impl #private::Sealed for #ident {}
520
521 impl #private::Columns for #ident {
522 const VERSION: ::core::option::Option<&'static ::core::primitive::str> = #version;
523 const CREATED: ::core::option::Option<&'static ::core::primitive::str> = #created;
524 const REPLICA_OWNED: ::core::primitive::bool = #owned_here #(#owned_parts)*;
525
526 fn columns(out: &mut ::std::vec::Vec<#private::Column>) {
527 #(#push)*
528 }
529
530 fn push_binds<'args>(
531 self,
532 sep: &mut #private::sqlx::query_builder::Separated<'_, 'args, #private::Db, &'static ::core::primitive::str>,
533 ) {
534 #(#bind)*
535 }
536 }
537 };
538 Ok(Declared {
539 tokens,
540 direct,
541 flattened,
542 })
543}
544
545fn columns_only(input: &DeriveInput) -> syn::Result<TokenStream> {
546 let args = parse_args(input, true)?;
547 let declared = declared(input, &krate(&args)?, &[])?;
548 if let Some(at) = declared.direct.iter().find_map(|d| d.flags.desired) {
549 return Err(syn::Error::new(
550 at,
551 "#[row(desired)] is a field of the setting itself, not of a part flattened in",
552 ));
553 }
554 Ok(declared.tokens)
555}
556
557#[derive(Clone, Copy, PartialEq, Eq)]
558enum Mirror {
559 Catalog,
560 Record,
561 Both,
562 Setting,
563}
564
565impl Mirror {
566 fn parse(lit: &LitStr) -> syn::Result<Self> {
567 Ok(match lit.value().as_str() {
568 "catalog" => Mirror::Catalog,
569 "record" => Mirror::Record,
570 "both" => Mirror::Both,
571 "setting" => Mirror::Setting,
572 other => {
573 return Err(syn::Error::new_spanned(
574 lit,
575 format!(
576 "mirror = \"{other}\": expected \"catalog\", \"record\", \"both\" or \"setting\""
577 ),
578 ))
579 }
580 })
581 }
582
583 fn variant(self) -> TokenStream {
584 match self {
585 Mirror::Catalog => quote!(Catalog),
586 Mirror::Record => quote!(Record),
587 Mirror::Both => quote!(Both),
588 Mirror::Setting => quote!(Setting),
589 }
590 }
591
592 fn sent_down(self) -> bool {
594 matches!(self, Mirror::Catalog | Mirror::Both)
595 }
596
597 fn versioned(self) -> bool {
599 matches!(self, Mirror::Record | Mirror::Setting)
600 }
601}
602
603fn only_sent_down(lit: Option<&LitStr>, mirror: Mirror, what: &str) -> syn::Result<()> {
605 match lit {
606 Some(lit) if !mirror.sent_down() => Err(syn::Error::new_spanned(
607 lit,
608 format!("only a table the server sends down (`catalog` or `both`) has {what}"),
609 )),
610 _ => Ok(()),
611 }
612}
613
614fn row(input: &DeriveInput) -> syn::Result<TokenStream> {
615 let args = parse_args(input, false)?;
616 let krate = krate(&args)?;
617 let private = quote!(#krate::__private);
618 let ident = &input.ident;
619 let missing = |what: &str| {
620 syn::Error::new(
621 Span::call_site(),
622 format!("#[derive(Row)] needs #[row({what} = \"…\")]"),
623 )
624 };
625 let table_lit = args.table.as_ref().ok_or_else(|| missing("table"))?;
626 let table = identifier(&table_lit.value(), table_lit.span())?;
627 let schema = match &args.schema {
628 Some(lit) => identifier(&lit.value(), lit.span())?,
629 None => "public".to_owned(),
630 };
631 let mirror = Mirror::parse(args.mirror.as_ref().ok_or_else(|| missing("mirror"))?)?;
632 let key = match &args.key {
633 Some(lit) => names(lit)?,
634 None => vec!["id".to_owned()],
635 };
636 let Declared {
637 tokens: columns,
638 direct,
639 flattened,
640 } = declared(input, &krate, &key)?;
641 let column = |name: &str| direct.iter().find(|d| d.name == name);
642
643 if mirror != Mirror::Both {
644 if let Some(at) = direct.iter().find_map(|d| d.flags.replica_owned) {
645 return Err(syn::Error::new(
646 at,
647 "#[row(replica_owned)] splits a table written on both sides: needs `mirror = \"both\"`",
648 ));
649 }
650 }
651 if !mirror.versioned() {
652 if let Some(at) = direct.iter().find_map(|d| d.flags.version) {
653 return Err(syn::Error::new(
654 at,
655 "#[row(version)] orders the reports of a replica's table: only a `record` or a \
656 `setting` has one, and a replica takes what the server sends whatever its version",
657 ));
658 }
659 }
660 if mirror != Mirror::Setting {
661 if let Some(at) = direct.iter().find_map(|d| d.flags.desired) {
662 return Err(syn::Error::new(
663 at,
664 "#[row(desired)] is a column the server may wish a replica's setting to hold: \
665 needs `mirror = \"setting\"`",
666 ));
667 }
668 if let Some(lit) = &args.apply {
669 return Err(syn::Error::new_spanned(
670 lit,
671 "only a `setting` has an `apply` rule: it says which wish a replica carries out",
672 ));
673 }
674 }
675 let (route, mut checks) = route(args.sync.as_ref(), mirror, &direct, &krate)?;
676 only_sent_down(args.active.as_ref(), mirror, "an `active` relation")?;
677 only_sent_down(args.retired.as_ref(), mirror, "a `retired` column")?;
678
679 let key_at = args.key.as_ref().map_or(ident.span(), LitStr::span);
680 let mut key_columns = Vec::new();
681 for name in &key {
682 let Some(column) = column(name) else {
683 let why = if flattened.is_empty() {
684 format!("the key column `{name}` is not a field of the row")
685 } else {
686 format!("the key column `{name}` is not a field of the row itself: a key is not flattened in")
687 };
688 return Err(syn::Error::new(key_at, why));
689 };
690 if column.nullable {
691 return Err(syn::Error::new(column.at, "a key column cannot be NULL"));
692 }
693 if let Some(at) = column.flags.any() {
694 return Err(syn::Error::new(
695 at,
696 "a key column is the row's identity: it is not generated, owned by a replica, \
697 a version, a creation time or desired",
698 ));
699 }
700 key_columns.push(column);
701 }
702
703 if let Some(lit) = &args.origin {
704 let origin = identifier(&lit.value(), lit.span())?;
705 if matches!(mirror, Mirror::Catalog) {
706 return Err(syn::Error::new_spanned(
707 lit,
708 "only a table a replica writes (`record`, `both` or `setting`) has an `origin`",
709 ));
710 }
711 let Some(column) = key_columns.iter().find(|c| c.name == origin) else {
712 return Err(syn::Error::new_spanned(
713 lit,
714 format!("origin = \"{origin}\": the replica a record belongs to is a key column"),
715 ));
716 };
717 let ty = &column.ty;
718 let refusal = format!("origin = \"{origin}\": the replica a record belongs to is a uuid");
719 checks.push(quote_spanned! {lit.span()=>
720 const _: () = ::core::assert!(
721 ::core::matches!(<#ty as #krate::row::KeyValue>::KIND, #krate::row::KeyKind::Uuid),
722 #refusal,
723 );
724 });
725 }
726 let retired = match &args.retired {
727 Some(lit) => Some(identifier(&lit.value(), lit.span())?),
728 None => None,
729 };
730 let unmirrored = match &args.unmirrored {
731 Some(lit) => {
732 let names = names(lit)?;
733 for name in &names {
734 if column(name).is_some() {
735 return Err(syn::Error::new_spanned(
736 lit,
737 format!("`{name}` is a field of the row: a column the row leaves out is not one of its fields"),
738 ));
739 }
740 if retired.as_ref() == Some(name) {
741 return Err(syn::Error::new_spanned(
742 lit,
743 format!("`{name}` is the retired column already"),
744 ));
745 }
746 }
747 names
748 }
749 None => Vec::new(),
750 };
751 let active = match &args.active {
752 Some(lit) => {
753 let text = lit.value();
754 let parts: Vec<&str> = text.split('.').collect();
755 if parts.len() > 2 {
756 return Err(syn::Error::new_spanned(
757 lit,
758 format!("active = \"{text}\": expected `view` or `schema.view`"),
759 ));
760 }
761 let quoted: Vec<String> = parts
762 .into_iter()
763 .map(|p| identifier(p, lit.span()).map(|p| quoted(&p)))
764 .collect::<syn::Result<_>>()?;
765 Some(quoted.join("."))
766 }
767 None => None,
768 };
769 if !mirror.versioned() {
770 let refusal = "#[row(version)] in a part flattened in orders the reports of a replica's \
771 table: only a `record` or a `setting` has one";
772 checks.extend(flattened.iter().map(|part| {
773 quote_spanned! {part.span()=>
774 const _: () = ::core::assert!(<#part as #private::Columns>::VERSION.is_none(), #refusal);
775 }
776 }));
777 }
778 if mirror != Mirror::Both {
779 let refusal = "#[row(replica_owned)] in a part flattened in splits a table written on \
780 both sides: the row needs `mirror = \"both\"`";
781 checks.extend(flattened.iter().map(|part| {
782 quote_spanned! {part.span()=>
783 const _: () = ::core::assert!(!<#part as #private::Columns>::REPLICA_OWNED, #refusal);
784 }
785 }));
786 }
787
788 let setting = if mirror == Mirror::Setting {
789 Some(setting(&args, ident, &direct, &krate)?)
790 } else {
791 None
792 };
793
794 let active = option(active);
795 let retired = option(retired);
796 let origin = option(args.origin.as_ref().map(LitStr::value));
797 let variant = mirror.variant();
798 let (quoted_schema, quoted_table) = (quoted(&schema), quoted(&table));
799 let version_text = match direct.iter().find(|d| d.flags.version.is_some()) {
800 Some(Direct { ident, ty, .. }) => quote_spanned! {ty.span()=>
801 ::core::option::Option::Some(<#ty as #krate::row::Version>::text(&self.#ident))
802 },
803 None => quote!(::core::option::Option::None),
804 };
805 let key_parts = key_columns.iter().map(|c| {
806 let (ident, ty) = (&c.ident, &c.ty);
807 quote_spanned! {ty.span()=>
808 <#ty as #krate::row::KeyValue>::key_part(&self.#ident)
809 }
810 });
811
812 Ok(quote! {
813 #columns
814
815 impl #private::Row for #ident {
816 const TABLE: &'static ::core::primitive::str = #table;
817 const SCHEMA: &'static ::core::primitive::str = #schema;
818 const KEY: &'static [&'static ::core::primitive::str] = &[#(#key),*];
819 const MIRROR: #krate::row::Mirror = #krate::row::Mirror::#variant;
820 const QUALIFIED: &'static ::core::primitive::str = #krate::__qualified!(#quoted_schema, #quoted_table);
821 const ROUTE: #private::Route = #route;
822 const ACTIVE: ::core::option::Option<&'static ::core::primitive::str> = #active;
823 const RETIRED: ::core::option::Option<&'static ::core::primitive::str> = #retired;
824 const UNMIRRORED: &'static [&'static ::core::primitive::str] = &[#(#unmirrored),*];
825 const ORIGIN: ::core::option::Option<&'static ::core::primitive::str> = #origin;
826
827 fn key(&self) -> #krate::row::Key {
828 ::std::vec![#(#key_parts),*]
829 }
830
831 fn version_text(&self) -> ::core::option::Option<::std::string::String> {
832 #version_text
833 }
834 }
835
836 #(#checks)*
837
838 #setting
839 })
840}
841
842fn setting(
847 args: &Args,
848 ident: &Ident,
849 direct: &[Direct],
850 krate: &syn::Path,
851) -> syn::Result<TokenStream> {
852 let refuse = |why: &str| syn::Error::new_spanned(ident, why);
853 if args.origin.is_none() {
854 return Err(refuse(
855 "a setting needs `origin = \"…\"`: the key column naming the replica it belongs to, \
856 which is where a wish of it goes",
857 ));
858 }
859 let Some(version) = direct.iter().find(|d| d.flags.version.is_some()) else {
860 return Err(refuse(
861 "a setting needs a #[row(version)] field of its own: what a wish is based on, and what \
862 moves when one is carried out",
863 ));
864 };
865 let desired: Vec<&Direct> = direct
866 .iter()
867 .filter(|d| d.flags.desired.is_some())
868 .collect();
869 if desired.is_empty() {
870 return Err(refuse(
871 "a setting needs at least one #[row(desired)] field: a column a wish may set",
872 ));
873 }
874 let apply = match &args.apply {
875 None => quote!(Always),
876 Some(lit) => match lit.value().as_str() {
877 "always" => quote!(Always),
878 "if_unchanged" => quote!(IfUnchanged),
879 other => {
880 return Err(syn::Error::new_spanned(
881 lit,
882 format!("apply = \"{other}\": expected \"always\" or \"if_unchanged\""),
883 ))
884 }
885 },
886 };
887 let fields = desired.iter().map(|d| &d.ident);
888 let (version_ident, version_ty) = (&version.ident, &version.ty);
889 Ok(quote! {
890 impl #krate::row::Setting for #ident {
891 const APPLY: #krate::row::Apply = #krate::row::Apply::#apply;
892
893 fn take_desired(&mut self, from: Self) {
894 #(self.#fields = from.#fields;)*
895 }
896
897 fn raise_version(&mut self) {
898 self.#version_ident = <#version_ty as #krate::row::Version>::raised(&self.#version_ident);
899 }
900 }
901 })
902}
903
904fn route(
907 sync: Option<&LitStr>,
908 mirror: Mirror,
909 direct: &[Direct],
910 krate: &syn::Path,
911) -> syn::Result<(TokenStream, Vec<TokenStream>)> {
912 let private = quote!(#krate::__private);
913 let Some(lit) = sync else {
914 return Ok((quote!(#private::Route::Referenced), Vec::new()));
915 };
916 if !mirror.sent_down() {
917 return Err(syn::Error::new_spanned(
918 lit,
919 "only a table the server sends down (`catalog` or `both`) takes a `sync` strategy",
920 ));
921 }
922 let text = lit.value();
923 let bad = |why: &str| syn::Error::new_spanned(lit, format!("sync = \"{text}\": {why}"));
924 let (name, inner) = match text.split_once('(') {
925 Some((name, rest)) => {
926 let inner = rest
927 .strip_suffix(')')
928 .ok_or_else(|| bad("missing the closing parenthesis"))?;
929 (name.trim(), Some(inner))
930 }
931 None => (text.trim(), None),
932 };
933 let column = |inner: Option<&str>| -> syn::Result<&Direct> {
934 let arg = inner.map(str::trim).unwrap_or_default();
935 if arg.is_empty() || arg.contains(',') || arg.contains('=') {
936 return Err(bad("expected one column name"));
937 }
938 let name = identifier(arg, lit.span())?;
939 direct
940 .iter()
941 .find(|d| d.name == name)
942 .ok_or_else(|| bad(&format!("`{name}` is not a field of the row")))
943 };
944 let of_a_key = |column: &Direct| {
946 let ty = option_inner(&column.ty).unwrap_or(&column.ty);
947 quote_spanned! {lit.span()=>
948 const _: #krate::row::KeyKind = <#ty as #krate::row::KeyValue>::KIND;
949 }
950 };
951 Ok(match name {
952 "broadcast" if inner.is_none() => (quote!(#private::Route::Broadcast), Vec::new()),
953 "assigned" => {
954 let column = column(inner)?;
955 let name = &column.name;
956 let ty = option_inner(&column.ty).unwrap_or(&column.ty);
957 let refusal = format!("sync = \"{text}\": the replica a row is sent to is a uuid");
958 let check = quote_spanned! {lit.span()=>
959 const _: () = ::core::assert!(
960 ::core::matches!(<#ty as #krate::row::KeyValue>::KIND, #krate::row::KeyKind::Uuid),
961 #refusal,
962 );
963 };
964 (quote!(#private::Route::Assigned(#name)), vec![check])
965 }
966 "follows" => {
967 let column = column(inner)?;
968 let name = &column.name;
969 (
970 quote!(#private::Route::Follows(#name)),
971 vec![of_a_key(column)],
972 )
973 }
974 "grouped" => {
975 let mut members = None;
976 let mut assigned = None;
977 for part in inner.unwrap_or_default().split(',') {
978 let expected = || bad("expected `members = …, assigned = …`");
979 let (key, value) = part.split_once('=').ok_or_else(expected)?;
980 let value = identifier(value.trim(), lit.span())?;
981 let slot = match key.trim() {
982 "members" => &mut members,
983 "assigned" => &mut assigned,
984 _ => return Err(expected()),
985 };
986 if slot.replace(value).is_some() {
987 return Err(bad(&format!("`{}` is given twice", key.trim())));
988 }
989 }
990 let (Some(members), Some(assigned)) = (members, assigned) else {
991 return Err(bad("expected `members = …, assigned = …`"));
992 };
993 (
994 quote!(#private::Route::Grouped {
995 members: #members,
996 assigned: #assigned,
997 }),
998 Vec::new(),
999 )
1000 }
1001 _ => {
1002 return Err(bad(
1003 "expected `broadcast`, `assigned(col)`, `grouped(members = …, assigned = …)` or `follows(col)`",
1004 ))
1005 }
1006 })
1007}