1use crate::config::{IncludeSpec, ResolvedEntity};
4use crate::db::pool::{Connection, DbRow, Pool};
5use crate::db::Dialect;
6use crate::error::AppError;
7use crate::extensible_fields::ExtensibleRegistry;
8use crate::sql::{
9 archive, coerce_json_value_for_pg_array, delete, insert, insert_history_snapshot,
10 prune_history, select_by_column_in, select_by_id, select_list, select_list_with_includes,
11 unarchive, update, BindValue, FilterNode, IncludeSelect, QueryBuf, SortSpec,
12};
13use serde_json::Value;
14use std::collections::HashMap;
15
16pub enum TenantExecutorInner<'a> {
18 Pool(&'a Pool),
19 Conn(&'a mut Connection),
20}
21
22pub struct TenantExecutor<'a> {
23 pub executor: TenantExecutorInner<'a>,
24 pub dialect: &'a dyn crate::db::Dialect,
25}
26
27impl<'a> TenantExecutor<'a> {
28 pub fn pool(pool: &'a Pool, dialect: &'a dyn crate::db::Dialect) -> Self {
29 TenantExecutor {
30 executor: TenantExecutorInner::Pool(pool),
31 dialect,
32 }
33 }
34 pub fn conn(conn: &'a mut Connection, dialect: &'a dyn crate::db::Dialect) -> Self {
35 TenantExecutor {
36 executor: TenantExecutorInner::Conn(conn),
37 dialect,
38 }
39 }
40}
41
42pub type GraphChild = (IncludeSpec, ResolvedEntity, Vec<HashMap<String, Value>>);
45
46pub fn bulk_limit() -> usize {
51 const DEFAULT_BULK_LIMIT: usize = 100;
52 std::env::var("ARCHITECT_BULK_LIMIT")
53 .ok()
54 .and_then(|v| v.parse::<usize>().ok())
55 .filter(|&n| n > 0)
56 .unwrap_or(DEFAULT_BULK_LIMIT)
57}
58
59pub struct CrudService;
60
61impl CrudService {
62 #[allow(clippy::too_many_arguments)]
65 pub async fn list<'a>(
66 executor: &mut TenantExecutor<'a>,
67 entity: &ResolvedEntity,
68 filter: Option<&FilterNode>,
69 sort: &[SortSpec],
70 limit: Option<u32>,
71 offset: Option<u32>,
72 filter_includes: &[IncludeSelect<'_>],
73 schema_override: Option<&str>,
74 dialect: &dyn Dialect,
75 registry: Option<&ExtensibleRegistry>,
76 ) -> Result<Vec<Value>, AppError> {
77 const DEFAULT_LIMIT: u32 = 100;
78 let limit = limit.unwrap_or(DEFAULT_LIMIT).min(1000);
79 let offset = offset.unwrap_or(0);
80 let q = select_list(
81 entity,
82 filter,
83 sort,
84 Some(limit),
85 Some(offset),
86 filter_includes,
87 schema_override,
88 dialect,
89 registry,
90 )?;
91 Self::query_many_exec(executor, &q.sql, &q.params).await
92 }
93
94 #[allow(clippy::too_many_arguments)]
97 pub async fn list_with_includes<'a>(
98 executor: &mut TenantExecutor<'a>,
99 entity: &ResolvedEntity,
100 filter: Option<&FilterNode>,
101 sort: &[SortSpec],
102 limit: Option<u32>,
103 offset: Option<u32>,
104 includes: &[IncludeSelect<'_>],
105 filter_includes: &[IncludeSelect<'_>],
106 schema_override: Option<&str>,
107 dialect: &dyn Dialect,
108 registry: Option<&ExtensibleRegistry>,
109 ) -> Result<Vec<Value>, AppError> {
110 const DEFAULT_LIMIT: u32 = 100;
111 let limit = limit.unwrap_or(DEFAULT_LIMIT).min(1000);
112 let offset = offset.unwrap_or(0);
113 let q = select_list_with_includes(
114 entity,
115 filter,
116 sort,
117 Some(limit),
118 Some(offset),
119 includes,
120 filter_includes,
121 schema_override,
122 dialect,
123 registry,
124 )?;
125 Self::query_many_exec(executor, &q.sql, &q.params).await
126 }
127
128 pub async fn read<'a>(
130 executor: &mut TenantExecutor<'a>,
131 entity: &ResolvedEntity,
132 id: &Value,
133 schema_override: Option<&str>,
134 dialect: &dyn Dialect,
135 ) -> Result<Option<Value>, AppError> {
136 let q = select_by_id(entity, schema_override, dialect);
137 Self::query_one_exec(executor, &q.sql, std::slice::from_ref(id)).await
138 }
139
140 pub async fn fetch_where_column_in<'a>(
142 executor: &mut TenantExecutor<'a>,
143 entity: &ResolvedEntity,
144 column_name: &str,
145 values: &[Value],
146 schema_override: Option<&str>,
147 dialect: &dyn Dialect,
148 ) -> Result<Vec<Value>, AppError> {
149 if values.is_empty() {
150 return Ok(Vec::new());
151 }
152 let q = select_by_column_in(entity, column_name, values, schema_override, dialect);
153 Self::query_many_exec(executor, &q.sql, &q.params).await
154 }
155
156 pub async fn create<'a>(
160 executor: &mut TenantExecutor<'a>,
161 entity: &ResolvedEntity,
162 body: &HashMap<String, Value>,
163 schema_override: Option<&str>,
164 rls_tenant_id: Option<&str>,
165 caller_user_id: Option<&str>,
166 dialect: &dyn Dialect,
167 ) -> Result<Value, AppError> {
168 let include_pk = body.contains_key(&entity.pk_columns[0]);
169 let q = insert(
170 entity,
171 body,
172 include_pk,
173 schema_override,
174 rls_tenant_id,
175 caller_user_id,
176 dialect,
177 );
178 let row = Self::execute_returning_one_exec(executor, &q)
179 .await?
180 .ok_or_else(|| AppError::Db(sqlx::Error::RowNotFound))?;
181 if entity.audit_log {
182 Self::insert_audit(
183 executor,
184 entity,
185 "create",
186 &row,
187 None,
188 caller_user_id,
189 schema_override,
190 )
191 .await?;
192 }
193 Ok(row)
194 }
195
196 #[allow(clippy::too_many_arguments)]
209 pub async fn create_graph(
210 pool: &Pool,
211 parent: &ResolvedEntity,
212 parent_body: &HashMap<String, Value>,
213 children: &[GraphChild],
214 schema_override: Option<&str>,
215 rls_tenant_id: Option<&str>,
216 set_local_sql: Option<&str>,
217 caller_user_id: Option<&str>,
218 dialect: &dyn Dialect,
219 ) -> Result<(Value, HashMap<String, Vec<Value>>), AppError> {
220 let mut tx = pool.begin().await?;
221 if let Some(sql) = set_local_sql {
223 sqlx::query(sql).execute(&mut *tx).await?;
224 }
225
226 let parent_row;
227 let mut child_rows: HashMap<String, Vec<Value>> = HashMap::new();
228 {
229 let mut exec = TenantExecutor::conn(&mut tx, dialect);
232
233 parent_row = Self::create(
234 &mut exec,
235 parent,
236 parent_body,
237 schema_override,
238 rls_tenant_id,
239 caller_user_id,
240 dialect,
241 )
242 .await?;
243
244 for (spec, child_entity, bodies) in children {
245 let fk_value = parent_row
247 .get(&spec.our_key_column)
248 .cloned()
249 .ok_or_else(|| {
250 AppError::BadRequest(format!(
251 "parent row is missing key column '{}' for include '{}'",
252 spec.our_key_column, spec.name
253 ))
254 })?;
255 let mut rows = Vec::with_capacity(bodies.len());
256 for body in bodies {
257 let mut child = body.clone();
258 child.insert(spec.their_key_column.clone(), fk_value.clone());
259 let row = Self::create(
260 &mut exec,
261 child_entity,
262 &child,
263 schema_override,
264 rls_tenant_id,
265 caller_user_id,
266 dialect,
267 )
268 .await?;
269 rows.push(row);
270 }
271 child_rows.insert(spec.name.clone(), rows);
272 }
273 } tx.commit().await?; Ok((parent_row, child_rows))
277 }
278
279 pub async fn update<'a>(
283 executor: &mut TenantExecutor<'a>,
284 entity: &ResolvedEntity,
285 id: &Value,
286 body: &HashMap<String, Value>,
287 schema_override: Option<&str>,
288 caller_user_id: Option<&str>,
289 dialect: &dyn Dialect,
290 ) -> Result<Option<Value>, AppError> {
291 let versioning_enabled = entity.versioning.as_ref().is_some_and(|v| v.enabled);
292
293 let pre_row = if entity.audit_log || versioning_enabled {
294 let q = select_by_id(entity, schema_override, dialect);
295 Self::query_one_exec(executor, &q.sql, std::slice::from_ref(id)).await?
296 } else {
297 None
298 };
299
300 let result = if versioning_enabled {
301 let snap_q = insert_history_snapshot(entity, "update", schema_override, dialect);
303 let upd_q = update(entity, id, body, schema_override, caller_user_id, dialect);
304 let keep = entity.versioning.as_ref().and_then(|v| v.keep_versions);
305 let prune_q = keep.map(|_| prune_history(entity, schema_override, dialect));
306 Self::run_versioned_update(executor, id, snap_q, upd_q, prune_q, keep).await?
307 } else {
308 let q = update(entity, id, body, schema_override, caller_user_id, dialect);
309 Self::execute_returning_one_exec(executor, &q).await?
310 };
311
312 if entity.audit_log {
313 if let Some(ref post_row) = result {
314 Self::insert_audit(
315 executor,
316 entity,
317 "update",
318 post_row,
319 pre_row.as_ref(),
320 caller_user_id,
321 schema_override,
322 )
323 .await?;
324 }
325 }
326 Ok(result)
327 }
328
329 pub async fn delete<'a>(
333 executor: &mut TenantExecutor<'a>,
334 entity: &ResolvedEntity,
335 id: &Value,
336 schema_override: Option<&str>,
337 caller_user_id: Option<&str>,
338 dialect: &dyn Dialect,
339 ) -> Result<Option<Value>, AppError> {
340 let versioning_enabled = entity.versioning.as_ref().is_some_and(|v| v.enabled);
341
342 let result = if versioning_enabled {
343 let snap_q = insert_history_snapshot(entity, "delete", schema_override, dialect);
344 let del_q = delete(entity, schema_override, dialect);
345 Self::run_versioned_delete(executor, id, snap_q, del_q).await?
346 } else {
347 let q = delete(entity, schema_override, dialect);
348 Self::execute_returning_one_with_params_exec(executor, &q.sql, std::slice::from_ref(id))
349 .await?
350 };
351
352 if entity.audit_log {
353 if let Some(ref deleted_row) = result {
354 Self::insert_audit(
355 executor,
356 entity,
357 "delete",
358 deleted_row,
359 None,
360 caller_user_id,
361 schema_override,
362 )
363 .await?;
364 }
365 }
366 Ok(result)
367 }
368
369 pub async fn archive<'a>(
376 executor: &mut TenantExecutor<'a>,
377 entity: &ResolvedEntity,
378 archive_field: &str,
379 id: &Value,
380 schema_override: Option<&str>,
381 caller_user_id: Option<&str>,
382 dialect: &dyn Dialect,
383 ) -> Result<Option<Value>, AppError> {
384 let q = archive(
385 entity,
386 archive_field,
387 id,
388 caller_user_id,
389 schema_override,
390 dialect,
391 );
392 let result = Self::execute_returning_one_exec(executor, &q).await?;
393 if entity.audit_log {
394 if let Some(ref row) = result {
395 Self::insert_audit(
396 executor,
397 entity,
398 "archive",
399 row,
400 None,
401 caller_user_id,
402 schema_override,
403 )
404 .await?;
405 }
406 }
407 Ok(result)
408 }
409
410 pub async fn unarchive<'a>(
417 executor: &mut TenantExecutor<'a>,
418 entity: &ResolvedEntity,
419 archive_field: &str,
420 id: &Value,
421 schema_override: Option<&str>,
422 caller_user_id: Option<&str>,
423 dialect: &dyn Dialect,
424 ) -> Result<Option<Value>, AppError> {
425 let q = unarchive(
426 entity,
427 archive_field,
428 id,
429 caller_user_id,
430 schema_override,
431 dialect,
432 );
433 let result = Self::execute_returning_one_exec(executor, &q).await?;
434 if entity.audit_log {
435 if let Some(ref row) = result {
436 Self::insert_audit(
437 executor,
438 entity,
439 "unarchive",
440 row,
441 None,
442 caller_user_id,
443 schema_override,
444 )
445 .await?;
446 }
447 }
448 Ok(result)
449 }
450
451 pub async fn bulk_create<'a>(
457 executor: &mut TenantExecutor<'a>,
458 entity: &ResolvedEntity,
459 items: &[HashMap<String, Value>],
460 schema_override: Option<&str>,
461 rls_tenant_id: Option<&str>,
462 caller_user_id: Option<&str>,
463 dialect: &dyn Dialect,
464 ) -> Result<Vec<Value>, AppError> {
465 let bulk_limit = bulk_limit();
466 if items.len() > bulk_limit {
467 return Err(AppError::BadRequest(format!(
468 "bulk create limited to {} items",
469 bulk_limit
470 )));
471 }
472 let mut out = Vec::with_capacity(items.len());
473 match executor.executor {
474 TenantExecutorInner::Pool(pool) => {
475 let mut tx = pool.begin().await?;
476 for body in items {
477 let mut ex = TenantExecutor::conn(&mut tx, dialect);
478 let row = Self::create(
479 &mut ex,
480 entity,
481 body,
482 schema_override,
483 rls_tenant_id,
484 caller_user_id,
485 dialect,
486 )
487 .await?;
488 out.push(row);
489 }
490 tx.commit().await?;
491 }
492 TenantExecutorInner::Conn(ref mut conn) => {
493 for body in items {
494 let mut ex = TenantExecutor::conn(conn, dialect);
495 let row = Self::create(
496 &mut ex,
497 entity,
498 body,
499 schema_override,
500 rls_tenant_id,
501 caller_user_id,
502 dialect,
503 )
504 .await?;
505 out.push(row);
506 }
507 }
508 }
509 Ok(out)
510 }
511
512 pub async fn bulk_create_collecting<'a>(
518 executor: &mut TenantExecutor<'a>,
519 entity: &ResolvedEntity,
520 items: &[HashMap<String, Value>],
521 schema_override: Option<&str>,
522 rls_tenant_id: Option<&str>,
523 caller_user_id: Option<&str>,
524 dialect: &dyn Dialect,
525 ) -> Result<(Vec<Value>, Vec<(usize, AppError)>), AppError> {
526 let bulk_limit = bulk_limit();
527 if items.len() > bulk_limit {
528 return Err(AppError::BadRequest(format!(
529 "bulk create limited to {} items",
530 bulk_limit
531 )));
532 }
533 let mut out = Vec::with_capacity(items.len());
534 let mut row_errors: Vec<(usize, AppError)> = Vec::new();
535 match executor.executor {
536 TenantExecutorInner::Pool(pool) => {
537 let mut tx = pool.begin().await?;
538 for (idx, body) in items.iter().enumerate() {
539 let sp = format!("sp_{}", idx);
540 sqlx::query(&format!("SAVEPOINT {}", sp))
541 .execute(&mut *tx)
542 .await?;
543 let mut ex = TenantExecutor::conn(&mut tx, dialect);
544 match Self::create(
545 &mut ex,
546 entity,
547 body,
548 schema_override,
549 rls_tenant_id,
550 caller_user_id,
551 dialect,
552 )
553 .await
554 {
555 Ok(row) => {
556 sqlx::query(&format!("RELEASE SAVEPOINT {}", sp))
557 .execute(&mut *tx)
558 .await?;
559 out.push(row);
560 }
561 Err(e) => {
562 sqlx::query(&format!("ROLLBACK TO SAVEPOINT {}", sp))
563 .execute(&mut *tx)
564 .await?;
565 row_errors.push((idx, e));
566 }
567 }
568 }
569 if row_errors.is_empty() {
570 tx.commit().await?;
571 } else {
572 tx.rollback().await?;
573 out.clear();
574 }
575 }
576 TenantExecutorInner::Conn(ref mut conn) => {
577 for (idx, body) in items.iter().enumerate() {
578 let sp = format!("sp_{}", idx);
579 sqlx::query(&format!("SAVEPOINT {}", sp))
580 .execute(&mut **conn)
581 .await?;
582 let mut ex = TenantExecutor::conn(conn, dialect);
583 match Self::create(
584 &mut ex,
585 entity,
586 body,
587 schema_override,
588 rls_tenant_id,
589 caller_user_id,
590 dialect,
591 )
592 .await
593 {
594 Ok(row) => {
595 sqlx::query(&format!("RELEASE SAVEPOINT {}", sp))
596 .execute(&mut **conn)
597 .await?;
598 out.push(row);
599 }
600 Err(e) => {
601 sqlx::query(&format!("ROLLBACK TO SAVEPOINT {}", sp))
602 .execute(&mut **conn)
603 .await?;
604 row_errors.push((idx, e));
605 }
606 }
607 }
608 if !row_errors.is_empty() {
609 out.clear();
610 }
611 }
612 }
613 Ok((out, row_errors))
614 }
615
616 pub async fn bulk_update<'a>(
621 executor: &mut TenantExecutor<'a>,
622 entity: &ResolvedEntity,
623 items: &[HashMap<String, Value>],
624 schema_override: Option<&str>,
625 caller_user_id: Option<&str>,
626 dialect: &dyn Dialect,
627 ) -> Result<Vec<Value>, AppError> {
628 let bulk_limit = bulk_limit();
629 if items.len() > bulk_limit {
630 return Err(AppError::BadRequest(format!(
631 "bulk update limited to {} items",
632 bulk_limit
633 )));
634 }
635 let pk = &entity.pk_columns[0];
636 let mut out = Vec::with_capacity(items.len());
637 match executor.executor {
638 TenantExecutorInner::Pool(pool) => {
639 let mut tx = pool.begin().await?;
640 for body in items {
641 let id = body.get(pk).ok_or_else(|| {
642 AppError::Validation(format!("each item must have '{}'", pk))
643 })?;
644 let mut body_without_pk = body.clone();
645 body_without_pk.remove(pk);
646 let mut ex = TenantExecutor::conn(&mut tx, dialect);
647 if let Some(row) = Self::update(
648 &mut ex,
649 entity,
650 id,
651 &body_without_pk,
652 schema_override,
653 caller_user_id,
654 dialect,
655 )
656 .await?
657 {
658 out.push(row);
659 }
660 }
661 tx.commit().await?;
662 }
663 TenantExecutorInner::Conn(ref mut conn) => {
664 for body in items {
665 let id = body.get(pk).ok_or_else(|| {
666 AppError::Validation(format!("each item must have '{}'", pk))
667 })?;
668 let mut body_without_pk = body.clone();
669 body_without_pk.remove(pk);
670 let mut ex = TenantExecutor::conn(conn, dialect);
671 if let Some(row) = Self::update(
672 &mut ex,
673 entity,
674 id,
675 &body_without_pk,
676 schema_override,
677 caller_user_id,
678 dialect,
679 )
680 .await?
681 {
682 out.push(row);
683 }
684 }
685 }
686 }
687 Ok(out)
688 }
689
690 pub async fn bulk_update_collecting<'a>(
698 executor: &mut TenantExecutor<'a>,
699 entity: &ResolvedEntity,
700 items: &[HashMap<String, Value>],
701 schema_override: Option<&str>,
702 caller_user_id: Option<&str>,
703 dialect: &dyn Dialect,
704 ) -> Result<(Vec<Value>, Vec<(usize, AppError)>), AppError> {
705 let bulk_limit = bulk_limit();
706 if items.len() > bulk_limit {
707 return Err(AppError::BadRequest(format!(
708 "bulk update limited to {} items",
709 bulk_limit
710 )));
711 }
712 let pk = entity.pk_columns[0].clone();
713 let mut out = Vec::with_capacity(items.len());
714 let mut row_errors: Vec<(usize, AppError)> = Vec::new();
715 match executor.executor {
716 TenantExecutorInner::Pool(pool) => {
717 let mut tx = pool.begin().await?;
718 for (idx, body) in items.iter().enumerate() {
719 let id = match body.get(&pk) {
720 Some(id) => id.clone(),
721 None => {
722 row_errors.push((
723 idx,
724 AppError::Validation(format!("each item must have '{}'", pk)),
725 ));
726 continue;
727 }
728 };
729 let sp = format!("sp_{}", idx);
730 sqlx::query(&format!("SAVEPOINT {}", sp))
731 .execute(&mut *tx)
732 .await?;
733 let mut body_without_pk = body.clone();
734 body_without_pk.remove(&pk);
735 let mut ex = TenantExecutor::conn(&mut tx, dialect);
736 match Self::update(
737 &mut ex,
738 entity,
739 &id,
740 &body_without_pk,
741 schema_override,
742 caller_user_id,
743 dialect,
744 )
745 .await
746 {
747 Ok(Some(row)) => {
748 sqlx::query(&format!("RELEASE SAVEPOINT {}", sp))
749 .execute(&mut *tx)
750 .await?;
751 out.push(row);
752 }
753 Ok(None) => {
754 sqlx::query(&format!("RELEASE SAVEPOINT {}", sp))
755 .execute(&mut *tx)
756 .await?;
757 }
758 Err(e) => {
759 sqlx::query(&format!("ROLLBACK TO SAVEPOINT {}", sp))
760 .execute(&mut *tx)
761 .await?;
762 row_errors.push((idx, e));
763 }
764 }
765 }
766 if row_errors.is_empty() {
767 tx.commit().await?;
768 } else {
769 tx.rollback().await?;
770 out.clear();
771 }
772 }
773 TenantExecutorInner::Conn(ref mut conn) => {
774 for (idx, body) in items.iter().enumerate() {
775 let id = match body.get(&pk) {
776 Some(id) => id.clone(),
777 None => {
778 row_errors.push((
779 idx,
780 AppError::Validation(format!("each item must have '{}'", pk)),
781 ));
782 continue;
783 }
784 };
785 let sp = format!("sp_{}", idx);
786 sqlx::query(&format!("SAVEPOINT {}", sp))
787 .execute(&mut **conn)
788 .await?;
789 let mut body_without_pk = body.clone();
790 body_without_pk.remove(&pk);
791 let mut ex = TenantExecutor::conn(conn, dialect);
792 match Self::update(
793 &mut ex,
794 entity,
795 &id,
796 &body_without_pk,
797 schema_override,
798 caller_user_id,
799 dialect,
800 )
801 .await
802 {
803 Ok(Some(row)) => {
804 sqlx::query(&format!("RELEASE SAVEPOINT {}", sp))
805 .execute(&mut **conn)
806 .await?;
807 out.push(row);
808 }
809 Ok(None) => {
810 sqlx::query(&format!("RELEASE SAVEPOINT {}", sp))
811 .execute(&mut **conn)
812 .await?;
813 }
814 Err(e) => {
815 sqlx::query(&format!("ROLLBACK TO SAVEPOINT {}", sp))
816 .execute(&mut **conn)
817 .await?;
818 row_errors.push((idx, e));
819 }
820 }
821 }
822 if !row_errors.is_empty() {
823 out.clear();
824 }
825 }
826 }
827 Ok((out, row_errors))
828 }
829
830 pub async fn bulk_delete_collecting<'a>(
837 executor: &mut TenantExecutor<'a>,
838 entity: &ResolvedEntity,
839 ids: &[Value],
840 schema_override: Option<&str>,
841 caller_user_id: Option<&str>,
842 dialect: &dyn Dialect,
843 ) -> Result<(Vec<Value>, Vec<(usize, AppError)>), AppError> {
844 let bulk_limit = bulk_limit();
845 if ids.len() > bulk_limit {
846 return Err(AppError::BadRequest(format!(
847 "bulk delete limited to {} items",
848 bulk_limit
849 )));
850 }
851 let mut out = Vec::with_capacity(ids.len());
852 let mut row_errors: Vec<(usize, AppError)> = Vec::new();
853 match executor.executor {
854 TenantExecutorInner::Pool(pool) => {
855 let mut tx = pool.begin().await?;
856 for (idx, id) in ids.iter().enumerate() {
857 let sp = format!("sp_{}", idx);
858 sqlx::query(&format!("SAVEPOINT {}", sp))
859 .execute(&mut *tx)
860 .await?;
861 let mut ex = TenantExecutor::conn(&mut tx, dialect);
864 let res = Self::delete(
865 &mut ex,
866 entity,
867 id,
868 schema_override,
869 caller_user_id,
870 dialect,
871 )
872 .await;
873 match res {
874 Ok(Some(row)) => {
875 sqlx::query(&format!("RELEASE SAVEPOINT {}", sp))
876 .execute(&mut *tx)
877 .await?;
878 out.push(row);
879 }
880 Ok(None) => {
881 sqlx::query(&format!("RELEASE SAVEPOINT {}", sp))
882 .execute(&mut *tx)
883 .await?;
884 }
885 Err(e) => {
886 sqlx::query(&format!("ROLLBACK TO SAVEPOINT {}", sp))
887 .execute(&mut *tx)
888 .await?;
889 row_errors.push((idx, e));
890 }
891 }
892 }
893 if row_errors.is_empty() {
894 tx.commit().await?;
895 } else {
896 tx.rollback().await?;
897 out.clear();
898 }
899 }
900 TenantExecutorInner::Conn(ref mut conn) => {
901 for (idx, id) in ids.iter().enumerate() {
902 let sp = format!("sp_{}", idx);
903 sqlx::query(&format!("SAVEPOINT {}", sp))
904 .execute(&mut **conn)
905 .await?;
906 let mut ex = TenantExecutor::conn(conn, dialect);
907 let res = Self::delete(
908 &mut ex,
909 entity,
910 id,
911 schema_override,
912 caller_user_id,
913 dialect,
914 )
915 .await;
916 match res {
917 Ok(Some(row)) => {
918 sqlx::query(&format!("RELEASE SAVEPOINT {}", sp))
919 .execute(&mut **conn)
920 .await?;
921 out.push(row);
922 }
923 Ok(None) => {
924 sqlx::query(&format!("RELEASE SAVEPOINT {}", sp))
925 .execute(&mut **conn)
926 .await?;
927 }
928 Err(e) => {
929 sqlx::query(&format!("ROLLBACK TO SAVEPOINT {}", sp))
930 .execute(&mut **conn)
931 .await?;
932 row_errors.push((idx, e));
933 }
934 }
935 }
936 if !row_errors.is_empty() {
937 out.clear();
938 }
939 }
940 }
941 Ok((out, row_errors))
942 }
943
944 pub async fn query_history_many<'a>(
947 executor: &mut TenantExecutor<'a>,
948 sql: &str,
949 params: &[Value],
950 ) -> Result<Vec<Value>, AppError> {
951 Self::query_many_exec(executor, sql, params).await
952 }
953
954 pub async fn query_history_one<'a>(
957 executor: &mut TenantExecutor<'a>,
958 sql: &str,
959 id: &Value,
960 version: i64,
961 ) -> Result<Option<Value>, AppError> {
962 tracing::debug!(sql = %sql, "history query");
963 let mut query = sqlx::query(sql);
964 query = query.bind(Self::to_sqlx_param(id));
965 query = query.bind(version);
966 let row = match executor.executor {
967 TenantExecutorInner::Pool(pool) => query.fetch_optional(pool).await?,
968 TenantExecutorInner::Conn(ref mut conn) => query.fetch_optional(&mut **conn).await?,
969 };
970 Ok(row.map(|r| row_to_json(&r)))
971 }
972
973 async fn query_one_exec<'a>(
974 executor: &mut TenantExecutor<'a>,
975 sql: &str,
976 params: &[Value],
977 ) -> Result<Option<Value>, AppError> {
978 tracing::debug!(sql = %sql, params = ?params, "query");
979 let bind = Self::to_sqlx_param(¶ms[0]);
980 let row = match executor.executor {
981 TenantExecutorInner::Pool(pool) => {
982 sqlx::query(sql).bind(bind).fetch_optional(pool).await?
983 }
984 TenantExecutorInner::Conn(ref mut conn) => {
985 sqlx::query(sql)
986 .bind(bind)
987 .fetch_optional(&mut **conn)
988 .await?
989 }
990 };
991 Ok(row.map(|r| row_to_json(&r)))
992 }
993
994 pub async fn run_readonly_query<'a>(
1001 executor: &mut TenantExecutor<'a>,
1002 sql: &str,
1003 params: &[Value],
1004 ) -> Result<Vec<Value>, AppError> {
1005 Self::query_many_exec(executor, sql, params).await
1006 }
1007
1008 async fn query_many_exec<'a>(
1009 executor: &mut TenantExecutor<'a>,
1010 sql: &str,
1011 params: &[Value],
1012 ) -> Result<Vec<Value>, AppError> {
1013 tracing::debug!(sql = %sql, params = ?params, "query");
1014 let mut query = sqlx::query(sql);
1015 for p in params {
1016 query = query.bind(Self::to_sqlx_param(p));
1017 }
1018 let rows = match executor.executor {
1019 TenantExecutorInner::Pool(pool) => query.fetch_all(pool).await?,
1020 TenantExecutorInner::Conn(ref mut conn) => query.fetch_all(&mut **conn).await?,
1021 };
1022 Ok(rows.iter().map(row_to_json).collect())
1023 }
1024
1025 async fn execute_returning_one_exec<'a>(
1026 executor: &mut TenantExecutor<'a>,
1027 q: &QueryBuf,
1028 ) -> Result<Option<Value>, AppError> {
1029 tracing::debug!(sql = %q.sql, params = ?q.params, "query");
1030 let mut query = sqlx::query(&q.sql);
1031 for p in &q.params {
1032 query = query.bind(Self::to_sqlx_param(p));
1033 }
1034 let row = match executor.executor {
1035 TenantExecutorInner::Pool(pool) => query.fetch_optional(pool).await?,
1036 TenantExecutorInner::Conn(ref mut conn) => query.fetch_optional(&mut **conn).await?,
1037 };
1038 Ok(row.map(|r| row_to_json(&r)))
1039 }
1040
1041 async fn execute_returning_one_with_params_exec<'a>(
1042 executor: &mut TenantExecutor<'a>,
1043 sql: &str,
1044 params: &[Value],
1045 ) -> Result<Option<Value>, AppError> {
1046 tracing::debug!(sql = %sql, params = ?params, "query");
1047 let mut query = sqlx::query(sql);
1048 for p in params {
1049 query = query.bind(Self::to_sqlx_param(p));
1050 }
1051 let row = match executor.executor {
1052 TenantExecutorInner::Pool(pool) => query.fetch_optional(pool).await?,
1053 TenantExecutorInner::Conn(ref mut conn) => query.fetch_optional(&mut **conn).await?,
1054 };
1055 Ok(row.map(|r| row_to_json(&r)))
1056 }
1057
1058 fn to_sqlx_param(v: &Value) -> BindValue {
1059 BindValue::from_json(v).unwrap_or(BindValue::Null)
1060 }
1061
1062 async fn run_versioned_update<'a>(
1064 executor: &mut TenantExecutor<'a>,
1065 id: &Value,
1066 snap_q: QueryBuf,
1067 upd_q: QueryBuf,
1068 prune_q: Option<QueryBuf>,
1069 keep_versions: Option<i64>,
1070 ) -> Result<Option<Value>, AppError> {
1071 match executor.executor {
1072 TenantExecutorInner::Pool(pool) => {
1073 let mut tx = pool.begin().await?;
1074 let mut snap = sqlx::query(&snap_q.sql);
1076 snap = snap.bind(Self::to_sqlx_param(&snap_q.params[0])); snap = snap.bind(Self::to_sqlx_param(id)); snap.execute(&mut *tx).await?;
1079 let mut upd = sqlx::query(&upd_q.sql);
1081 for p in &upd_q.params {
1082 upd = upd.bind(Self::to_sqlx_param(p));
1083 }
1084 let row = upd.fetch_optional(&mut *tx).await?.map(|r| row_to_json(&r));
1085 if let (Some(pq), Some(kv)) = (prune_q, keep_versions) {
1087 let mut pr = sqlx::query(&pq.sql);
1088 pr = pr.bind(Self::to_sqlx_param(id));
1089 pr = pr.bind(kv);
1090 pr.execute(&mut *tx).await?;
1091 }
1092 tx.commit().await?;
1093 Ok(row)
1094 }
1095 TenantExecutorInner::Conn(ref mut conn) => {
1096 sqlx::query("SAVEPOINT sp_versioned_update")
1098 .execute(&mut **conn)
1099 .await?;
1100 let snap_res = async {
1101 let mut snap = sqlx::query(&snap_q.sql);
1102 snap = snap.bind(Self::to_sqlx_param(&snap_q.params[0]));
1103 snap = snap.bind(Self::to_sqlx_param(id));
1104 snap.execute(&mut **conn).await?;
1105 let mut upd = sqlx::query(&upd_q.sql);
1106 for p in &upd_q.params {
1107 upd = upd.bind(Self::to_sqlx_param(p));
1108 }
1109 let row = upd
1110 .fetch_optional(&mut **conn)
1111 .await?
1112 .map(|r| row_to_json(&r));
1113 if let (Some(pq), Some(kv)) = (prune_q, keep_versions) {
1114 let mut pr = sqlx::query(&pq.sql);
1115 pr = pr.bind(Self::to_sqlx_param(id));
1116 pr = pr.bind(kv);
1117 pr.execute(&mut **conn).await?;
1118 }
1119 Ok::<_, sqlx::Error>(row)
1120 }
1121 .await;
1122 match snap_res {
1123 Ok(row) => {
1124 sqlx::query("RELEASE SAVEPOINT sp_versioned_update")
1125 .execute(&mut **conn)
1126 .await?;
1127 Ok(row)
1128 }
1129 Err(e) => {
1130 sqlx::query("ROLLBACK TO SAVEPOINT sp_versioned_update")
1131 .execute(&mut **conn)
1132 .await?;
1133 Err(AppError::Db(e))
1134 }
1135 }
1136 }
1137 }
1138 }
1139
1140 async fn run_versioned_delete<'a>(
1142 executor: &mut TenantExecutor<'a>,
1143 id: &Value,
1144 snap_q: QueryBuf,
1145 del_q: QueryBuf,
1146 ) -> Result<Option<Value>, AppError> {
1147 match executor.executor {
1148 TenantExecutorInner::Pool(pool) => {
1149 let mut tx = pool.begin().await?;
1150 let mut snap = sqlx::query(&snap_q.sql);
1151 snap = snap.bind(Self::to_sqlx_param(&snap_q.params[0])); snap = snap.bind(Self::to_sqlx_param(id)); snap.execute(&mut *tx).await?;
1154 let mut del = sqlx::query(&del_q.sql);
1155 del = del.bind(Self::to_sqlx_param(id));
1156 let row = del.fetch_optional(&mut *tx).await?.map(|r| row_to_json(&r));
1157 tx.commit().await?;
1158 Ok(row)
1159 }
1160 TenantExecutorInner::Conn(ref mut conn) => {
1161 sqlx::query("SAVEPOINT sp_versioned_delete")
1162 .execute(&mut **conn)
1163 .await?;
1164 let snap_res = async {
1165 let mut snap = sqlx::query(&snap_q.sql);
1166 snap = snap.bind(Self::to_sqlx_param(&snap_q.params[0]));
1167 snap = snap.bind(Self::to_sqlx_param(id));
1168 snap.execute(&mut **conn).await?;
1169 let mut del = sqlx::query(&del_q.sql);
1170 del = del.bind(Self::to_sqlx_param(id));
1171 let row = del
1172 .fetch_optional(&mut **conn)
1173 .await?
1174 .map(|r| row_to_json(&r));
1175 Ok::<_, sqlx::Error>(row)
1176 }
1177 .await;
1178 match snap_res {
1179 Ok(row) => {
1180 sqlx::query("RELEASE SAVEPOINT sp_versioned_delete")
1181 .execute(&mut **conn)
1182 .await?;
1183 Ok(row)
1184 }
1185 Err(e) => {
1186 sqlx::query("ROLLBACK TO SAVEPOINT sp_versioned_delete")
1187 .execute(&mut **conn)
1188 .await?;
1189 Err(AppError::Db(e))
1190 }
1191 }
1192 }
1193 }
1194 }
1195
1196 async fn insert_audit<'a>(
1197 executor: &mut TenantExecutor<'a>,
1198 entity: &ResolvedEntity,
1199 action: &str,
1200 row: &Value,
1201 pre_row: Option<&Value>,
1202 audit_by: Option<&str>,
1203 schema_override: Option<&str>,
1204 ) -> Result<(), AppError> {
1205 let schema = schema_override.unwrap_or(&entity.schema_name);
1206 let audit_table = format!(
1207 "\"{}\".\"{}\"",
1208 schema.replace('"', "\"\""),
1209 format!("{}_audit", entity.table_name).replace('"', "\"\"")
1210 );
1211
1212 let changed = if action == "update" {
1213 pre_row.map(|pre| compute_changed_fields(pre, row, entity))
1214 } else {
1215 None
1216 };
1217
1218 let mut col_names: Vec<String> = vec![
1219 "\"audit_action\"".to_string(),
1220 "\"audit_by\"".to_string(),
1221 "\"changed_fields\"".to_string(),
1222 ];
1223 let mut placeholders: Vec<String> = Vec::new();
1224 let mut params: Vec<Value> = Vec::new();
1225
1226 params.push(Value::String(action.to_string()));
1227 placeholders.push(format!("${}", params.len()));
1228
1229 params.push(
1230 audit_by
1231 .map(|s| Value::String(s.to_string()))
1232 .unwrap_or(Value::Null),
1233 );
1234 placeholders.push(format!("${}", params.len()));
1235
1236 params.push(changed.unwrap_or(Value::Null));
1237 placeholders.push(format!("${}::jsonb", params.len()));
1238
1239 let row_obj = row.as_object();
1240 for col in &entity.columns {
1241 let raw = row_obj
1242 .and_then(|o| o.get(&col.name))
1243 .cloned()
1244 .unwrap_or(Value::Null);
1245 let val = coerce_json_value_for_pg_array(raw, col.pg_type.as_deref());
1246 let param_num = params.len() + 1;
1247 let ph = col
1248 .pg_type
1249 .as_deref()
1250 .map(|t| format!("${}::{}", param_num, t))
1251 .unwrap_or_else(|| format!("${}", param_num));
1252 col_names.push(format!("\"{}\"", col.name));
1253 placeholders.push(ph);
1254 params.push(val);
1255 }
1256
1257 let sql = format!(
1258 "INSERT INTO {} ({}) VALUES ({})",
1259 audit_table,
1260 col_names.join(", "),
1261 placeholders.join(", ")
1262 );
1263 tracing::debug!(sql = %sql, "audit insert");
1264
1265 let mut query = sqlx::query(&sql);
1266 for p in ¶ms {
1267 query = query.bind(Self::to_sqlx_param(p));
1268 }
1269 match executor.executor {
1270 TenantExecutorInner::Pool(pool) => {
1271 query.execute(pool).await?;
1272 }
1273 TenantExecutorInner::Conn(ref mut conn) => {
1274 query.execute(&mut **conn).await?;
1275 }
1276 }
1277 Ok(())
1278 }
1279}
1280
1281fn compute_changed_fields(pre: &Value, post: &Value, entity: &ResolvedEntity) -> Value {
1282 let pre_obj = match pre.as_object() {
1283 Some(o) => o,
1284 None => return Value::Null,
1285 };
1286 let post_obj = match post.as_object() {
1287 Some(o) => o,
1288 None => return Value::Null,
1289 };
1290 let mut changes = serde_json::Map::new();
1291 for col in &entity.columns {
1292 let pre_val = pre_obj.get(&col.name).unwrap_or(&Value::Null);
1293 let post_val = post_obj.get(&col.name).unwrap_or(&Value::Null);
1294 if pre_val != post_val {
1295 let mut diff = serde_json::Map::new();
1296 diff.insert("old".to_string(), pre_val.clone());
1297 diff.insert("new".to_string(), post_val.clone());
1298 changes.insert(col.name.clone(), Value::Object(diff));
1299 }
1300 }
1301 Value::Object(changes)
1302}
1303
1304fn row_to_json(row: &DbRow) -> Value {
1305 use sqlx::Column;
1306 use sqlx::Row;
1307 let mut map = serde_json::Map::new();
1308 for col in row.columns() {
1309 let name = col.name();
1310 let v = cell_to_value(row, name);
1311 map.insert(name.to_string(), v);
1312 }
1313 Value::Object(map)
1314}
1315
1316fn cell_to_value(row: &DbRow, name: &str) -> Value {
1317 use sqlx::Row;
1318 if let Ok(Some(n)) = row.try_get::<Option<i16>, _>(name) {
1319 return Value::Number(n.into());
1320 }
1321 if let Ok(Some(n)) = row.try_get::<Option<i32>, _>(name) {
1322 return Value::Number(n.into());
1323 }
1324 if let Ok(Some(n)) = row.try_get::<Option<i64>, _>(name) {
1325 return Value::Number(n.into());
1326 }
1327 if let Ok(Some(n)) = row.try_get::<Option<f32>, _>(name) {
1328 if let Some(n) = serde_json::Number::from_f64(n as f64) {
1329 return Value::Number(n);
1330 }
1331 }
1332 if let Ok(Some(n)) = row.try_get::<Option<f64>, _>(name) {
1333 if let Some(n) = serde_json::Number::from_f64(n) {
1334 return Value::Number(n);
1335 }
1336 }
1337 if let Ok(Some(b)) = row.try_get::<Option<bool>, _>(name) {
1338 return Value::Bool(b);
1339 }
1340 #[cfg(feature = "postgres")]
1341 if let Ok(Some(vec)) = row.try_get::<Option<Vec<String>>, _>(name) {
1342 return Value::Array(vec.into_iter().map(Value::String).collect());
1343 }
1344 #[cfg(feature = "postgres")]
1345 if let Ok(Some(vec)) = row.try_get::<Option<Vec<uuid::Uuid>>, _>(name) {
1346 return Value::Array(
1347 vec.into_iter()
1348 .map(|u| Value::String(u.to_string()))
1349 .collect(),
1350 );
1351 }
1352 #[cfg(feature = "postgres")]
1353 if let Ok(Some(vec)) = row.try_get::<Option<Vec<i64>>, _>(name) {
1354 return Value::Array(vec.into_iter().map(|n| Value::Number(n.into())).collect());
1355 }
1356 if let Ok(Some(u)) = row.try_get::<Option<uuid::Uuid>, _>(name) {
1357 return Value::String(u.to_string());
1358 }
1359 if let Ok(Some(d)) = row.try_get::<Option<chrono::DateTime<chrono::Utc>>, _>(name) {
1360 return Value::String(d.to_rfc3339());
1361 }
1362 if let Ok(Some(d)) = row.try_get::<Option<chrono::NaiveDateTime>, _>(name) {
1363 return Value::String(d.format("%Y-%m-%dT%H:%M:%S%.f").to_string());
1364 }
1365 if let Ok(Some(d)) = row.try_get::<Option<chrono::NaiveDate>, _>(name) {
1366 return Value::String(d.format("%Y-%m-%d").to_string());
1367 }
1368 if let Ok(Some(s)) = row.try_get::<Option<String>, _>(name) {
1369 if let Ok(n) = s.trim().parse::<f64>() {
1371 if let Some(num) = serde_json::Number::from_f64(n) {
1372 return Value::Number(num);
1373 }
1374 }
1375 return Value::String(s);
1376 }
1377 if let Ok(Some(j)) = row.try_get::<Option<serde_json::Value>, _>(name) {
1378 return j;
1379 }
1380 Value::Null
1381}