1use std::collections::HashMap;
11use std::sync::Arc;
12
13use laterite_core::query::{bind_values, build as to_sql, insert_returning_id_on, text_cast};
14use laterite_core::strata::async_trait;
15use laterite_core::validation::ErrorBag;
16use laterite_core::AnyRowExt;
17use laterite_core::{t, Actor, AttrValue, Db, ModelListener, Op, Record, SaveCx, SavedCx, Text};
18use sea_query::{Alias, Expr, Query, SimpleExpr};
19
20use crate::form::FormConfig;
21use crate::sql::valid_ident;
22
23#[derive(Debug)]
25pub enum SaveError {
26 Invalid(ErrorBag),
29 Failed(String),
32}
33
34#[derive(Debug)]
36pub enum DeleteError {
37 Refused(Text),
41 Failed(String),
44}
45
46#[async_trait]
51pub trait Persister: Send + Sync + 'static {
52 async fn create(&self, cx: &mut SaveCx<'_>, rec: &Record) -> Result<i64, SaveError>;
54 async fn update(&self, cx: &mut SaveCx<'_>, id: &str, rec: &Record) -> Result<(), SaveError>;
56
57 async fn load(&self, cx: &mut SaveCx<'_>, id: &str) -> Result<Option<Record>, SaveError> {
61 let _ = (cx, id);
62 Ok(None)
63 }
64
65 async fn move_to(&self, cx: &mut SaveCx<'_>, mv: &MoveTo<'_>) -> Result<(), SaveError> {
70 if !(valid_ident(mv.entity) && valid_ident(mv.id_field) && valid_ident(mv.parent_key)) {
71 return Err(SaveError::Failed("invalid identifier".to_string()));
72 }
73 let backend = cx.backend();
74 let (sql, values) = to_sql(
75 backend,
76 Query::update()
77 .table(Alias::new(mv.entity))
78 .value(
79 Alias::new(mv.parent_key),
80 match mv.parent.and_then(|p| p.parse::<i64>().ok()) {
81 Some(parent) => SimpleExpr::from(parent),
82 None => SimpleExpr::Keyword(sea_query::Keyword::Null),
83 },
84 )
85 .and_where(
86 Expr::col(Alias::new(mv.id_field))
87 .cast_as(Alias::new(text_cast(backend)))
88 .eq(mv.id.to_string()),
89 )
90 .to_owned(),
91 );
92 bind_values(sqlx::query(&sql), values)
93 .execute(cx.conn())
94 .await
95 .map_err(|e| SaveError::Failed(e.to_string()))?;
96 Ok(())
97 }
98
99 async fn delete(&self, cx: &mut SaveCx<'_>, id: &str, rec: &Record) -> Result<(), DeleteError> {
105 let _ = (cx, id, rec);
106 Err(DeleteError::Refused(t!(
107 "Deleting this kind of record is not supported."
108 )))
109 }
110}
111
112#[derive(Debug)]
114#[non_exhaustive]
115pub struct MoveTo<'a> {
116 pub entity: &'a str,
117 pub id_field: &'a str,
118 pub id: &'a str,
120 pub parent_key: &'a str,
122 pub parent: Option<&'a str>,
124}
125
126fn bind(value: Option<&AttrValue>) -> SimpleExpr {
134 match value {
135 None | Some(AttrValue::Null) => SimpleExpr::Keyword(sea_query::Keyword::Null),
136 Some(AttrValue::Int(i)) => (*i).into(),
137 Some(AttrValue::Float(f)) => (*f).into(),
138 Some(AttrValue::Bool(b)) => i32::from(*b).into(),
139 Some(AttrValue::Text(s)) if s.is_empty() => SimpleExpr::Keyword(sea_query::Keyword::Null),
140 Some(other) => other.to_text().into(),
141 }
142}
143
144pub(crate) struct SaveRequest<'a> {
150 pub db: &'a Db,
151 pub listeners: &'a [Arc<dyn ModelListener>],
152 pub persister: &'a dyn Persister,
153 pub actor: &'a Actor,
155 pub op: Op,
156 pub id: Option<&'a str>,
158}
159
160pub(crate) async fn save(req: SaveRequest<'_>, rec: &mut Record) -> Result<(), SaveError> {
161 let SaveRequest {
162 db,
163 listeners,
164 persister,
165 actor,
166 op,
167 id,
168 } = req;
169 let mut tx = db
170 .pool
171 .begin()
172 .await
173 .map_err(|e| SaveError::Failed(e.to_string()))?;
174
175 let outcome = save_in(
176 &mut tx,
177 db.backend,
178 listeners,
179 persister,
180 actor.clone(),
181 rec,
182 op,
183 id,
184 )
185 .await;
186 match outcome {
187 Ok(()) => tx
188 .commit()
189 .await
190 .map_err(|e| SaveError::Failed(e.to_string()))?,
191 Err(e) => {
192 let _ = tx.rollback().await;
195 return Err(e);
196 }
197 }
198
199 let saved = SavedCx::new(db, actor);
201 for listener in listeners {
202 listener.after_save(&saved, rec, op).await;
203 }
204 Ok(())
205}
206
207pub(crate) struct DeleteRequest<'a> {
209 pub db: &'a Db,
210 pub listeners: &'a [Arc<dyn ModelListener>],
211 pub persister: &'a dyn Persister,
212 pub actor: &'a Actor,
213 pub entity: &'a str,
214 pub id: &'a str,
215}
216
217pub(crate) async fn delete(req: DeleteRequest<'_>) -> Result<Record, DeleteError> {
223 let DeleteRequest {
224 db,
225 listeners,
226 persister,
227 actor,
228 entity,
229 id,
230 } = req;
231
232 let mut tx = db
233 .pool
234 .begin()
235 .await
236 .map_err(|e| DeleteError::Failed(e.to_string()))?;
237
238 let outcome = delete_in(
239 &mut tx,
240 db.backend,
241 listeners,
242 persister,
243 actor.clone(),
244 entity,
245 id,
246 )
247 .await;
248 let rec = match outcome {
249 Ok(rec) => {
250 tx.commit()
251 .await
252 .map_err(|e| DeleteError::Failed(e.to_string()))?;
253 rec
254 }
255 Err(e) => {
256 let _ = tx.rollback().await;
259 return Err(e);
260 }
261 };
262
263 let saved = SavedCx::new(db, actor);
265 for listener in listeners {
266 listener.after_delete(&saved, &rec).await;
267 }
268 Ok(rec)
269}
270
271async fn delete_in(
274 tx: &mut sqlx::Transaction<'_, sqlx::Any>,
275 backend: laterite_core::migration::DbBackend,
276 listeners: &[Arc<dyn ModelListener>],
277 persister: &dyn Persister,
278 actor: Actor,
279 entity: &str,
280 id: &str,
281) -> Result<Record, DeleteError> {
282 let mut cx = SaveCx::new(backend, tx, actor);
283
284 let loaded = persister.load(&mut cx, id).await.map_err(|e| match e {
288 SaveError::Invalid(_) => DeleteError::Failed("snapshot read refused".to_string()),
289 SaveError::Failed(m) => DeleteError::Failed(m),
290 })?;
291 let mut rec = loaded.unwrap_or_else(|| Record::new(entity));
292 if rec.id().is_none() {
293 if let Ok(numeric) = id.parse::<i64>() {
294 rec.set_id(numeric);
295 }
296 }
297
298 for listener in listeners {
299 listener
300 .before_delete(&mut cx, &rec)
301 .await
302 .map_err(DeleteError::Refused)?;
303 }
304
305 persister.delete(&mut cx, id, &rec).await?;
306 Ok(rec)
307}
308
309#[allow(clippy::too_many_arguments)]
312async fn save_in(
313 tx: &mut sqlx::Transaction<'_, sqlx::Any>,
314 backend: laterite_core::migration::DbBackend,
315 listeners: &[Arc<dyn ModelListener>],
316 persister: &dyn Persister,
317 actor: Actor,
318 rec: &mut Record,
319 op: Op,
320 id: Option<&str>,
321) -> Result<(), SaveError> {
322 let mut cx = SaveCx::new(backend, tx, actor);
323
324 if op == Op::Update {
325 if let Some(id) = id {
326 if let Some(previous) = persister.load(&mut cx, id).await? {
327 rec.set_original(previous);
328 }
329 }
330 }
331
332 for listener in listeners {
333 listener
334 .before_save(&mut cx, rec, op)
335 .await
336 .map_err(SaveError::Invalid)?;
337 }
338
339 match op {
340 Op::Create => {
341 let new_id = persister.create(&mut cx, rec).await?;
342 rec.set_id(new_id);
343 }
344 Op::Update => {
345 let id = id.ok_or_else(|| SaveError::Failed("update without an id".to_string()))?;
346 persister.update(&mut cx, id, rec).await?;
347 }
348 _ => return Err(SaveError::Failed(format!("unsupported operation {op:?}"))),
350 }
351 Ok(())
352}
353
354pub struct PersisterReg {
356 pub name: String,
357 pub persister: Arc<dyn Persister>,
358}
359
360impl PersisterReg {
361 pub fn new(name: impl Into<String>, persister: Arc<dyn Persister>) -> Self {
362 Self {
363 name: name.into(),
364 persister,
365 }
366 }
367}
368
369pub type PersisterRegistry = HashMap<String, Arc<dyn Persister>>;
371
372pub(crate) struct DefaultPersister {
376 entity: String,
377 id_field: String,
378 columns: Vec<String>,
379}
380
381impl DefaultPersister {
382 fn write_columns(&self, rec: &Record) -> Vec<String> {
388 let mut cols = self.columns.clone();
389 for (key, _) in rec.iter() {
390 if key != self.id_field && !cols.iter().any(|c| c == key) {
391 cols.push(key.to_string());
392 }
393 }
394 cols.retain(|c| c != &self.id_field && crate::sql::valid_ident(c));
395 cols
396 }
397
398 pub(crate) fn from_list(config: &crate::list::ListConfig) -> Self {
402 Self {
403 entity: config.entity.clone(),
404 id_field: config.id_field.clone(),
405 columns: config.columns.iter().map(|c| c.field.clone()).collect(),
406 }
407 }
408
409 pub(crate) fn from_config(config: &FormConfig) -> Self {
410 Self {
411 entity: config.entity.clone(),
412 id_field: config.id_field.clone(),
413 columns: config.fields.iter().map(|f| f.name.clone()).collect(),
414 }
415 }
416}
417
418#[async_trait]
419impl Persister for DefaultPersister {
420 async fn create(&self, cx: &mut SaveCx<'_>, rec: &Record) -> Result<i64, SaveError> {
421 let stmt = {
424 let cols = self.write_columns(rec);
425 let vals: Vec<SimpleExpr> = cols.iter().map(|c| bind(rec.get(c))).collect();
426 Query::insert()
427 .into_table(Alias::new(&self.entity))
428 .columns(cols.iter().map(Alias::new))
429 .values_panic(vals)
430 .to_owned()
431 };
432 let backend = cx.backend();
433 insert_returning_id_on(backend, cx.conn(), stmt, Alias::new(&self.id_field))
434 .await
435 .map_err(|e| SaveError::Failed(e.to_string()))
436 }
437
438 async fn update(&self, cx: &mut SaveCx<'_>, id: &str, rec: &Record) -> Result<(), SaveError> {
439 let backend = cx.backend();
440 let columns: Vec<String> = rec
443 .iter()
444 .map(|(key, _)| key.to_string())
445 .filter(|c| c != &self.id_field && crate::sql::valid_ident(c))
446 .collect();
447 if columns.is_empty() {
448 return Ok(());
449 }
450 let (sql, values) = {
451 let mut update = Query::update();
452 update.table(Alias::new(&self.entity));
453 for column in &columns {
454 update.value(Alias::new(column), bind(rec.get(column)));
455 }
456 update.and_where(
457 Expr::col(Alias::new(&self.id_field))
458 .cast_as(Alias::new(text_cast(backend)))
459 .eq(id),
460 );
461 to_sql(backend, update)
462 };
463 bind_values(sqlx::query(&sql), values)
464 .execute(cx.conn())
465 .await
466 .map(|_| ())
467 .map_err(|e| SaveError::Failed(e.to_string()))
468 }
469
470 async fn delete(
473 &self,
474 cx: &mut SaveCx<'_>,
475 id: &str,
476 _rec: &Record,
477 ) -> Result<(), DeleteError> {
478 let backend = cx.backend();
479 let (sql, values) = {
480 let mut del = Query::delete();
481 del.from_table(Alias::new(&self.entity));
482 del.and_where(
483 Expr::col(Alias::new(&self.id_field))
484 .cast_as(Alias::new(text_cast(backend)))
485 .eq(id),
486 );
487 to_sql(backend, del)
488 };
489 bind_values(sqlx::query(&sql), values)
490 .execute(cx.conn())
491 .await
492 .map(|_| ())
493 .map_err(|e| DeleteError::Failed(e.to_string()))
494 }
495
496 async fn load(&self, cx: &mut SaveCx<'_>, id: &str) -> Result<Option<Record>, SaveError> {
500 let backend = cx.backend();
501 let (sql, values) = {
502 let mut select = Query::select();
503 select.from(Alias::new(&self.entity));
504 for column in &self.columns {
505 select.expr_as(
506 Expr::col(Alias::new(column)).cast_as(Alias::new(text_cast(backend))),
507 Alias::new(column),
508 );
509 }
510 select.and_where(
511 Expr::col(Alias::new(&self.id_field))
512 .cast_as(Alias::new(text_cast(backend)))
513 .eq(id),
514 );
515 to_sql(backend, select)
516 };
517 let row = bind_values(sqlx::query(&sql), values)
518 .fetch_optional(cx.conn())
519 .await
520 .map_err(|e| SaveError::Failed(e.to_string()))?;
521 let Some(row) = row else {
522 return Ok(None);
523 };
524 let mut previous = Record::new(&self.entity);
525 for column in &self.columns {
526 match row.get_text_opt(column) {
527 Ok(Some(v)) => {
528 previous.set(column.clone(), v);
529 }
530 Ok(None) => {
531 previous.set(column.clone(), AttrValue::Null);
532 }
533 Err(e) => return Err(SaveError::Failed(e.to_string())),
534 }
535 }
536 Ok(Some(previous))
537 }
538}
539
540#[cfg(test)]
541mod pipeline_tests {
542 use super::*;
543 use laterite_core::testing::connect_test;
544 use laterite_core::{ListenerTarget, ModelListenerReg, Text};
545 use std::sync::Mutex;
546
547 #[derive(Default)]
549 struct SpyPersister {
550 seen: Mutex<Vec<(String, HashMap<String, String>)>>,
551 }
552
553 #[async_trait]
554 impl Persister for SpyPersister {
555 async fn create(&self, _cx: &mut SaveCx<'_>, rec: &Record) -> Result<i64, SaveError> {
556 self.seen
557 .lock()
558 .unwrap()
559 .push(("create".into(), rec.to_text_map()));
560 Ok(42)
561 }
562 async fn update(
563 &self,
564 _cx: &mut SaveCx<'_>,
565 id: &str,
566 rec: &Record,
567 ) -> Result<(), SaveError> {
568 self.seen
569 .lock()
570 .unwrap()
571 .push((format!("update:{id}"), rec.to_text_map()));
572 Ok(())
573 }
574 }
575
576 #[derive(Default)]
578 struct DeleteSpy {
579 deleted: Mutex<Vec<String>>,
580 refuse: Option<&'static str>,
581 }
582
583 #[async_trait]
584 impl Persister for DeleteSpy {
585 async fn create(&self, _cx: &mut SaveCx<'_>, _rec: &Record) -> Result<i64, SaveError> {
586 Ok(1)
587 }
588 async fn update(
589 &self,
590 _cx: &mut SaveCx<'_>,
591 _id: &str,
592 _rec: &Record,
593 ) -> Result<(), SaveError> {
594 Ok(())
595 }
596 async fn load(&self, _cx: &mut SaveCx<'_>, id: &str) -> Result<Option<Record>, SaveError> {
597 let mut rec = Record::new("samples");
598 rec.set("code", format!("code-{id}"));
599 if let Ok(n) = id.parse::<i64>() {
600 rec.set_id(n);
601 }
602 Ok(Some(rec))
603 }
604 async fn delete(
605 &self,
606 _cx: &mut SaveCx<'_>,
607 id: &str,
608 _rec: &Record,
609 ) -> Result<(), DeleteError> {
610 if let Some(why) = self.refuse {
611 return Err(DeleteError::Refused(Text::new(why)));
612 }
613 self.deleted.lock().unwrap().push(id.to_string());
614 Ok(())
615 }
616 }
617
618 struct Guard;
620
621 #[async_trait]
622 impl ModelListener for Guard {
623 async fn before_delete(&self, _cx: &mut SaveCx<'_>, _rec: &Record) -> Result<(), Text> {
624 Err(Text::new("still referenced"))
625 }
626 }
627
628 #[derive(Default)]
630 struct Mourner {
631 seen: Arc<Mutex<Vec<String>>>,
632 }
633
634 #[async_trait]
635 impl ModelListener for Mourner {
636 async fn after_delete(&self, _cx: &SavedCx<'_>, rec: &Record) {
637 self.seen
638 .lock()
639 .unwrap()
640 .push(rec.text("code").unwrap_or_default().to_string());
641 }
642 }
643
644 #[tokio::test]
645 async fn a_delete_runs_the_listeners_around_the_persister() {
646 let (db, _guard) = connect_test(&[]).await;
647 let persister = DeleteSpy::default();
648 let seen = Arc::new(Mutex::new(Vec::new()));
649 let listeners: Vec<Arc<dyn ModelListener>> = vec![Arc::new(Mourner { seen: seen.clone() })];
650
651 let rec = delete(DeleteRequest {
652 db: &db,
653 listeners: &listeners,
654 persister: &persister,
655 actor: &actor(),
656 entity: "samples",
657 id: "7",
658 })
659 .await
660 .expect("deleted");
661
662 assert_eq!(persister.deleted.lock().unwrap().as_slice(), ["7"]);
663 assert_eq!(seen.lock().unwrap().as_slice(), ["code-7"]);
666 assert_eq!(rec.text("code"), Some("code-7"));
667 assert_eq!(rec.id(), Some(7));
668 }
669
670 #[tokio::test]
673 async fn a_listener_can_refuse_a_delete() {
674 let (db, _guard) = connect_test(&[]).await;
675 let persister = DeleteSpy::default();
676 let seen = Arc::new(Mutex::new(Vec::new()));
677 let listeners: Vec<Arc<dyn ModelListener>> =
678 vec![Arc::new(Guard), Arc::new(Mourner { seen: seen.clone() })];
679
680 let err = delete(DeleteRequest {
681 db: &db,
682 listeners: &listeners,
683 persister: &persister,
684 actor: &actor(),
685 entity: "samples",
686 id: "7",
687 })
688 .await
689 .expect_err("refused");
690
691 match err {
692 DeleteError::Refused(message) => assert_eq!(message.source(), "still referenced"),
693 other => panic!("expected a refusal, got {other:?}"),
694 }
695 assert!(
696 persister.deleted.lock().unwrap().is_empty(),
697 "nothing removed"
698 );
699 assert!(
700 seen.lock().unwrap().is_empty(),
701 "no after-stage on a refusal"
702 );
703 }
704
705 #[tokio::test]
708 async fn a_persister_without_delete_refuses() {
709 let (db, _guard) = connect_test(&[]).await;
710 let persister = SpyPersister::default();
711 let err = delete(DeleteRequest {
712 db: &db,
713 listeners: &[],
714 persister: &persister,
715 actor: &actor(),
716 entity: "samples",
717 id: "1",
718 })
719 .await
720 .expect_err("unsupported");
721 assert!(matches!(err, DeleteError::Refused(_)));
722 }
723
724 struct Stamp {
727 key: &'static str,
728 log: Arc<Mutex<Vec<&'static str>>>,
729 }
730
731 #[async_trait]
732 impl ModelListener for Stamp {
733 async fn before_save(
734 &self,
735 _cx: &mut SaveCx<'_>,
736 rec: &mut Record,
737 _op: Op,
738 ) -> Result<(), ErrorBag> {
739 rec.set(self.key, "set");
740 Ok(())
741 }
742 async fn after_save(&self, _cx: &SavedCx<'_>, _rec: &Record, _op: Op) {
743 self.log.lock().unwrap().push(self.key);
744 }
745 }
746
747 struct Veto;
749
750 #[async_trait]
751 impl ModelListener for Veto {
752 async fn before_save(
753 &self,
754 _cx: &mut SaveCx<'_>,
755 _rec: &mut Record,
756 _op: Op,
757 ) -> Result<(), ErrorBag> {
758 let mut bag = ErrorBag::default();
759 bag.add("name", Text::new("Not allowed"));
761 Err(bag)
762 }
763 }
764
765 fn actor() -> Actor {
766 Actor::system("test")
767 }
768
769 fn listeners(regs: &[ModelListenerReg], entity: &str) -> Vec<Arc<dyn ModelListener>> {
770 regs.iter()
771 .filter(|r| r.matches(entity))
772 .map(|r| r.listener.clone())
773 .collect()
774 }
775
776 #[tokio::test]
777 async fn a_listener_mutation_reaches_the_persister_and_the_id_comes_back() {
778 let (db, _guard) = connect_test(&[]).await;
779 let spy = SpyPersister::default();
780 let log = Arc::new(Mutex::new(Vec::new()));
781 let regs = vec![ModelListenerReg::all(Arc::new(Stamp {
782 key: "created_at",
783 log: log.clone(),
784 }))];
785 let mut rec = Record::new("samples");
786 rec.set("name", "Chair");
787
788 save(
789 SaveRequest {
790 db: &db,
791 listeners: &listeners(®s, "samples"),
792 persister: &spy,
793 actor: &actor(),
794 op: Op::Create,
795 id: None,
796 },
797 &mut rec,
798 )
799 .await
800 .unwrap();
801
802 let seen = spy.seen.lock().unwrap();
803 assert_eq!(seen[0].0, "create");
804 assert_eq!(seen[0].1.get("created_at").map(String::as_str), Some("set"));
805 assert_eq!(rec.id(), Some(42), "the create id lands on the record");
806 assert_eq!(*log.lock().unwrap(), ["created_at"]);
807 }
808
809 #[tokio::test]
810 async fn a_veto_stops_the_write_and_returns_field_messages() {
811 let (db, _guard) = connect_test(&[]).await;
812 let spy = SpyPersister::default();
813 let regs = vec![ModelListenerReg::all(Arc::new(Veto))];
814 let mut rec = Record::new("samples");
815
816 let err = save(
817 SaveRequest {
818 db: &db,
819 listeners: &listeners(®s, "samples"),
820 persister: &spy,
821 actor: &actor(),
822 op: Op::Create,
823 id: None,
824 },
825 &mut rec,
826 )
827 .await
828 .expect_err("the veto refuses the write");
829
830 match err {
831 SaveError::Invalid(bag) => assert_eq!(bag.messages("name").len(), 1),
832 SaveError::Failed(e) => panic!("expected a field error, got {e}"),
833 }
834 assert!(
835 spy.seen.lock().unwrap().is_empty(),
836 "nothing reached the persister"
837 );
838 }
839
840 #[tokio::test]
841 async fn listeners_run_in_registration_order() {
842 let (db, _guard) = connect_test(&[]).await;
843 let log = Arc::new(Mutex::new(Vec::new()));
844 let regs = vec![
845 ModelListenerReg::all(Arc::new(Stamp {
846 key: "first",
847 log: log.clone(),
848 })),
849 ModelListenerReg::all(Arc::new(Stamp {
850 key: "second",
851 log: log.clone(),
852 })),
853 ];
854 let mut rec = Record::new("samples");
855
856 save(
857 SaveRequest {
858 db: &db,
859 listeners: &listeners(®s, "samples"),
860 persister: &SpyPersister::default(),
861 actor: &actor(),
862 op: Op::Create,
863 id: None,
864 },
865 &mut rec,
866 )
867 .await
868 .unwrap();
869
870 assert_eq!(*log.lock().unwrap(), ["first", "second"]);
871 }
872
873 #[tokio::test]
874 async fn a_target_selects_which_listeners_run() {
875 let (db, _guard) = connect_test(&[]).await;
876 let log = Arc::new(Mutex::new(Vec::new()));
877 let regs = vec![
878 ModelListenerReg::all(Arc::new(Stamp {
879 key: "everywhere",
880 log: log.clone(),
881 })),
882 ModelListenerReg::for_entity(
883 "orders",
884 Arc::new(Stamp {
885 key: "orders_only",
886 log: log.clone(),
887 }),
888 ),
889 ];
890 let mut rec = Record::new("samples");
891
892 save(
893 SaveRequest {
894 db: &db,
895 listeners: &listeners(®s, "samples"),
896 persister: &SpyPersister::default(),
897 actor: &actor(),
898 op: Op::Create,
899 id: None,
900 },
901 &mut rec,
902 )
903 .await
904 .unwrap();
905
906 assert_eq!(
907 *log.lock().unwrap(),
908 ["everywhere"],
909 "another entity's listener stays out"
910 );
911 assert!(matches!(regs[1].target, ListenerTarget::Entity(ref e) if e == "orders"));
912 }
913
914 #[tokio::test]
915 async fn a_listener_injected_attribute_is_written_not_dropped() {
916 let (db, _guard) = connect_test(&[]).await;
917 let spy = SpyPersister::default();
918 let log = Arc::new(Mutex::new(Vec::new()));
919 let regs = vec![ModelListenerReg::all(Arc::new(Stamp {
920 key: "created_at",
921 log,
922 }))];
923 let mut rec = Record::new("samples");
924 rec.set("name", "Chair");
925
926 save(
927 SaveRequest {
928 db: &db,
929 listeners: &listeners(®s, "samples"),
930 persister: &spy,
931 actor: &actor(),
932 op: Op::Create,
933 id: None,
934 },
935 &mut rec,
936 )
937 .await
938 .unwrap();
939
940 let default = DefaultPersister {
941 entity: "samples".into(),
942 id_field: "id".into(),
943 columns: vec!["name".into()],
944 };
945 assert_eq!(default.write_columns(&rec), ["name", "created_at"]);
947 }
948
949 #[test]
950 fn the_id_column_is_never_written() {
951 let default = DefaultPersister {
952 entity: "samples".into(),
953 id_field: "id".into(),
954 columns: vec!["name".into()],
955 };
956 let mut rec = Record::with_id("samples", 3);
957 rec.set("name", "Chair").set("id", 3i64);
958 assert_eq!(default.write_columns(&rec), ["name"]);
959 }
960
961 #[test]
962 fn binding_maps_each_kind_to_its_storage_form() {
963 let null = SimpleExpr::Keyword(sea_query::Keyword::Null);
966 assert_eq!(bind(None), null);
967 assert_eq!(bind(Some(&AttrValue::Null)), null);
968 assert_eq!(bind(Some(&AttrValue::Text(String::new()))), null);
969 assert_eq!(bind(Some(&AttrValue::Int(7))), 7i64.into());
970 assert_eq!(bind(Some(&AttrValue::Bool(true))), 1i32.into());
972 assert_eq!(
973 bind(Some(&AttrValue::Text("Chair".into()))),
974 Some("Chair".to_string()).into()
975 );
976 }
977
978 struct SnapshotPersister;
980
981 #[async_trait]
982 impl Persister for SnapshotPersister {
983 async fn create(&self, _cx: &mut SaveCx<'_>, _rec: &Record) -> Result<i64, SaveError> {
984 Ok(1)
985 }
986 async fn update(
987 &self,
988 _cx: &mut SaveCx<'_>,
989 _id: &str,
990 _rec: &Record,
991 ) -> Result<(), SaveError> {
992 Ok(())
993 }
994 async fn load(&self, _cx: &mut SaveCx<'_>, _id: &str) -> Result<Option<Record>, SaveError> {
995 let mut previous = Record::new("samples");
996 previous.set("name", "Chair");
997 Ok(Some(previous))
998 }
999 }
1000
1001 struct WatchChanges {
1003 seen: Arc<Mutex<Vec<(bool, bool)>>>,
1004 }
1005
1006 #[async_trait]
1007 impl ModelListener for WatchChanges {
1008 async fn before_save(
1009 &self,
1010 _cx: &mut SaveCx<'_>,
1011 rec: &mut Record,
1012 _op: Op,
1013 ) -> Result<(), ErrorBag> {
1014 self.seen
1015 .lock()
1016 .unwrap()
1017 .push((rec.has_original(), rec.changed("name")));
1018 Ok(())
1019 }
1020 }
1021
1022 #[tokio::test]
1023 async fn an_update_hands_listeners_the_pre_write_row() {
1024 let (db, _guard) = connect_test(&[]).await;
1025 let seen = Arc::new(Mutex::new(Vec::new()));
1026 let regs = vec![ModelListenerReg::all(Arc::new(WatchChanges {
1027 seen: seen.clone(),
1028 }))];
1029 let mut rec = Record::with_id("samples", 1);
1030 rec.set("name", "Stool");
1031
1032 save(
1033 SaveRequest {
1034 db: &db,
1035 listeners: &listeners(®s, "samples"),
1036 persister: &SnapshotPersister,
1037 actor: &actor(),
1038 op: Op::Update,
1039 id: Some("1"),
1040 },
1041 &mut rec,
1042 )
1043 .await
1044 .unwrap();
1045
1046 assert_eq!(*seen.lock().unwrap(), [(true, true)]);
1047 }
1048
1049 #[tokio::test]
1050 async fn a_create_has_no_snapshot() {
1051 let (db, _guard) = connect_test(&[]).await;
1052 let seen = Arc::new(Mutex::new(Vec::new()));
1053 let regs = vec![ModelListenerReg::all(Arc::new(WatchChanges {
1054 seen: seen.clone(),
1055 }))];
1056 let mut rec = Record::new("samples");
1057 rec.set("name", "Chair");
1058
1059 save(
1060 SaveRequest {
1061 db: &db,
1062 listeners: &listeners(®s, "samples"),
1063 persister: &SnapshotPersister,
1064 actor: &actor(),
1065 op: Op::Create,
1066 id: None,
1067 },
1068 &mut rec,
1069 )
1070 .await
1071 .unwrap();
1072
1073 assert_eq!(*seen.lock().unwrap(), [(false, false)]);
1074 }
1075
1076 #[tokio::test]
1077 async fn an_update_passes_its_id_through() {
1078 let (db, _guard) = connect_test(&[]).await;
1079 let spy = SpyPersister::default();
1080 let mut rec = Record::with_id("samples", 7);
1081
1082 save(
1083 SaveRequest {
1084 db: &db,
1085 listeners: &[],
1086 persister: &spy,
1087 actor: &actor(),
1088 op: Op::Update,
1089 id: Some("7"),
1090 },
1091 &mut rec,
1092 )
1093 .await
1094 .unwrap();
1095
1096 assert_eq!(spy.seen.lock().unwrap()[0].0, "update:7");
1097 }
1098
1099 #[tokio::test]
1100 async fn an_update_without_an_id_fails_rather_than_writing() {
1101 let (db, _guard) = connect_test(&[]).await;
1102 let spy = SpyPersister::default();
1103 let mut rec = Record::new("samples");
1104
1105 let err = save(
1106 SaveRequest {
1107 db: &db,
1108 listeners: &[],
1109 persister: &spy,
1110 actor: &actor(),
1111 op: Op::Update,
1112 id: None,
1113 },
1114 &mut rec,
1115 )
1116 .await
1117 .expect_err("an update needs an id");
1118
1119 assert!(matches!(err, SaveError::Failed(_)));
1120 assert!(spy.seen.lock().unwrap().is_empty());
1121 }
1122}