1use crate::cascade::{self, CascadeDeleteAction, CascadeDeleteDirective, DrainedChild, FixupLink};
60use crate::change_executor::ChangeExecutor;
61use crate::db_set::DbSet;
62use crate::dependency_graph::DependencyGraph;
63use crate::entity::{
64 EntityState, IEntitySnapshot, IEntityType, IFromRow, IGetKeyValues, INavigationSetter,
65};
66use crate::error::{EFError, EFResult};
67use crate::interceptor::{InterceptorPipeline, SaveChangesContext, SaveChangesResultContext};
68use crate::metadata::{EntityTypeMeta, NavigationKind};
69use crate::migration::MigrationEngine;
70use crate::model_builder::ModelBuilder;
71use crate::provider::{DbValue, IAsyncConnection, IDatabaseProvider};
72use crate::tracking::{ChangeTracker, EntityEntryView};
73use crate::transaction::{DbTransaction, ITransaction};
74use std::any::{Any, TypeId};
75use std::collections::HashMap;
76use std::future::Future;
77use std::pin::Pin;
78use std::sync::Arc;
79
80#[derive(Clone)]
85pub struct DbContextOptions {
86 pub(crate) connection_string: String,
87 pub(crate) provider_tag: Option<String>,
88 #[allow(clippy::type_complexity)]
89 pub(crate) provider_factory:
90 Option<Arc<dyn Fn(&str) -> EFResult<Arc<dyn IDatabaseProvider>> + Send + Sync>>,
91 pub(crate) provider_cache: Arc<std::sync::Mutex<Option<Arc<dyn IDatabaseProvider>>>>,
98 pub(crate) interceptors: Vec<Arc<dyn crate::interceptor::ISaveChangesInterceptor>>,
99 pub(crate) lazy_loading_enabled: bool,
105 pub(crate) context_key: Option<String>,
106 pub(crate) metadata_cache: Arc<crate::metadata_cache::MetadataCache>,
112 #[cfg(feature = "tracing")]
115 pub(crate) slow_query_threshold: Option<std::time::Duration>,
116}
117
118impl std::fmt::Debug for DbContextOptions {
119 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
120 f.debug_struct("DbContextOptions")
121 .field(
122 "connection_string",
123 &redact_connection_string(&self.connection_string),
124 )
125 .field("provider_tag", &self.provider_tag)
126 .finish()
127 }
128}
129
130fn redact_connection_string(cs: &str) -> String {
135 if let Some(scheme_end) = cs.find("://") {
137 let (scheme, rest) = cs.split_at(scheme_end + 3);
138 if let Some(at) = rest.find('@') {
139 let (userinfo, host_and_rest) = rest.split_at(at);
140 let redacted_user = match userinfo.find(':') {
141 Some(colon) => &userinfo[..colon],
142 None => userinfo,
143 };
144 return format!("{}{}***@{}", scheme, redacted_user, &host_and_rest[1..]);
145 }
146 return cs.to_string();
147 }
148 if cs.contains('=') {
150 return cs
151 .split(';')
152 .map(|pair| {
153 let eq = match pair.find('=') {
154 Some(e) => e,
155 None => return pair.to_string(),
156 };
157 let key = pair[..eq].trim().to_lowercase();
158 if key.contains("password") || key.contains("pwd") {
159 format!("{}=***", &pair[..eq])
160 } else {
161 pair.to_string()
162 }
163 })
164 .collect::<Vec<_>>()
165 .join(";");
166 }
167 cs.to_string()
168}
169
170impl DbContextOptions {
171 pub fn connection_string(&self) -> &str {
172 &self.connection_string
173 }
174 pub fn provider_tag(&self) -> Option<&str> {
175 self.provider_tag.as_deref()
176 }
177 pub fn lazy_loading_enabled(&self) -> bool {
178 self.lazy_loading_enabled
179 }
180 pub fn context_key(&self) -> Option<&str> {
181 self.context_key.as_deref()
182 }
183 pub fn create_provider(&self) -> EFResult<Arc<dyn IDatabaseProvider>> {
184 let mut guard = self
188 .provider_cache
189 .lock()
190 .unwrap_or_else(|p| p.into_inner());
191 if let Some(provider) = guard.as_ref() {
192 return Ok(Arc::clone(provider));
193 }
194 let factory = self.provider_factory.as_ref().ok_or_else(|| {
195 crate::error::EFError::configuration(
196 "No provider configured. Call use_sqlite / use_postgres / use_mysql first.",
197 )
198 })?;
199 let provider = factory(self.connection_string())?;
200 #[cfg(feature = "tracing")]
201 if let Some(threshold) = self.slow_query_threshold {
202 provider.set_slow_query_threshold(threshold);
203 }
204 *guard = Some(Arc::clone(&provider));
205 Ok(provider)
206 }
207}
208
209#[allow(clippy::derivable_impls)]
210impl Default for DbContextOptions {
211 fn default() -> Self {
212 Self {
213 connection_string: String::new(),
214 provider_tag: None,
215 provider_factory: None,
216 provider_cache: Arc::new(std::sync::Mutex::new(None)),
217 interceptors: Vec::new(),
218 lazy_loading_enabled: false,
219 context_key: None,
220 metadata_cache: Arc::new(crate::metadata_cache::MetadataCache::new()),
221 #[cfg(feature = "tracing")]
222 slow_query_threshold: None,
223 }
224 }
225}
226
227pub struct DbContextOptionsBuilder {
228 inner: DbContextOptions,
229}
230
231impl DbContextOptionsBuilder {
232 pub fn new() -> Self {
233 Self {
234 inner: DbContextOptions::default(),
235 }
236 }
237 pub fn connection_string(&mut self, cs: impl Into<String>) -> &mut Self {
238 self.inner.connection_string = cs.into();
239 self
240 }
241 pub fn set_provider(&mut self, tag: &str, cs: impl Into<String>) -> &mut Self {
242 self.inner.provider_tag = Some(tag.to_string());
243 self.inner.connection_string = cs.into();
244 self
245 }
246 #[allow(clippy::type_complexity)]
247 pub fn set_provider_factory(
248 &mut self,
249 tag: &str,
250 cs: impl Into<String>,
251 factory: Arc<dyn Fn(&str) -> EFResult<Arc<dyn IDatabaseProvider>> + Send + Sync>,
252 ) -> &mut Self {
253 self.inner.provider_tag = Some(tag.to_string());
254 self.inner.connection_string = cs.into();
255 self.inner.provider_factory = Some(factory);
256 self
257 }
258 pub fn add_interceptor(
272 &mut self,
273 interceptor: impl crate::interceptor::ISaveChangesInterceptor + 'static,
274 ) -> &mut Self {
275 self.inner.interceptors.push(Arc::new(interceptor));
276 self
277 }
278
279 pub fn use_lazy_loading(&mut self, enabled: bool) -> &mut Self {
297 self.inner.lazy_loading_enabled = enabled;
298 self
299 }
300
301 pub fn context_key(&mut self, key: impl Into<String>) -> &mut Self {
306 self.inner.context_key = Some(key.into());
307 self
308 }
309
310 #[cfg(feature = "tracing")]
315 pub fn slow_query_threshold(&mut self, threshold: std::time::Duration) -> &mut Self {
316 self.inner.slow_query_threshold = Some(threshold);
317 self
318 }
319
320 pub fn build(self) -> DbContextOptions {
321 self.inner
322 }
323}
324
325impl Default for DbContextOptionsBuilder {
326 fn default() -> Self {
327 Self::new()
328 }
329}
330
331#[async_trait::async_trait]
336trait ErasedSetOps: Send + Sync {
337 fn detect_changes(&self, raw_set: &mut (dyn Any + Send + Sync));
338 fn accept_all_changes(&self, raw_set: &mut (dyn Any + Send + Sync + 'static));
343 fn collect_entries(&self, raw_set: &(dyn Any + Send + Sync)) -> Vec<EntityEntryView>;
347
348 fn drain_cascade_children(
353 &self,
354 raw_set: &mut (dyn Any + Send + Sync),
355 meta: &EntityTypeMeta,
356 ) -> Vec<DrainedChild>;
357
358 fn add_cascade_child(
361 &self,
362 raw_set: &mut (dyn Any + Send + Sync),
363 child: Box<dyn Any + Send + Sync>,
364 ) -> Option<usize>;
365
366 fn get_pk_at(&self, raw_set: &(dyn Any + Send + Sync), idx: usize) -> Option<i64>;
369
370 fn set_fk_at(
372 &self,
373 raw_set: &mut (dyn Any + Send + Sync),
374 idx: usize,
375 target_type: TypeId,
376 key: i64,
377 );
378
379 async fn insert_added(
381 &self,
382 conn: &mut (dyn IAsyncConnection + Send),
383 provider: &dyn IDatabaseProvider,
384 raw_set: &mut (dyn Any + Send + Sync),
385 meta: &EntityTypeMeta,
386 ) -> EFResult<usize>;
387
388 async fn upsert_added(
390 &self,
391 conn: &mut (dyn IAsyncConnection + Send),
392 provider: &dyn IDatabaseProvider,
393 raw_set: &mut (dyn Any + Send + Sync),
394 meta: &EntityTypeMeta,
395 ) -> EFResult<usize>;
396
397 async fn update_modified(
399 &self,
400 conn: &mut (dyn IAsyncConnection + Send),
401 provider: &dyn IDatabaseProvider,
402 raw_set: &mut (dyn Any + Send + Sync),
403 meta: &EntityTypeMeta,
404 ) -> EFResult<usize>;
405
406 async fn delete_deleted(
408 &self,
409 conn: &mut (dyn IAsyncConnection + Send),
410 provider: &dyn IDatabaseProvider,
411 raw_set: &mut (dyn Any + Send + Sync),
412 meta: &EntityTypeMeta,
413 ) -> EFResult<usize>;
414
415 fn drain_cascade_deleted_children(
419 &self,
420 raw_set: &mut (dyn Any + Send + Sync),
421 meta: &EntityTypeMeta,
422 processed: &mut std::collections::HashSet<(TypeId, usize)>,
423 ) -> (
424 Vec<DrainedChild>,
425 Vec<crate::cascade::CascadeDeleteDirective>,
426 );
427
428 fn add_cascade_deleted_child(
431 &self,
432 raw_set: &mut (dyn Any + Send + Sync),
433 child: Box<dyn Any + Send + Sync>,
434 ) -> Option<usize>;
435}
436
437struct SetOps<E> {
438 _phantom: std::marker::PhantomData<E>,
439}
440impl<E> SetOps<E> {
441 fn new() -> Self {
442 Self {
443 _phantom: std::marker::PhantomData,
444 }
445 }
446}
447
448#[async_trait::async_trait]
449impl<E> ErasedSetOps for SetOps<E>
450where
451 E: IEntityType
452 + IEntitySnapshot
453 + IGetKeyValues
454 + IFromRow
455 + INavigationSetter
456 + Send
457 + Sync
458 + 'static,
459{
460 fn detect_changes(&self, raw_set: &mut (dyn Any + Send + Sync)) {
461 if let Some(db_set) = raw_set.downcast_mut::<DbSet<E>>() {
462 db_set.detect_changes();
463 }
464 }
465 fn accept_all_changes(&self, raw_set: &mut (dyn Any + Send + Sync + 'static)) {
466 if let Some(db_set) = raw_set.downcast_mut::<DbSet<E>>() {
467 db_set.accept_all_changes();
468 }
469 }
470 fn collect_entries(&self, raw_set: &(dyn Any + Send + Sync)) -> Vec<EntityEntryView> {
471 let Some(db_set) = raw_set.downcast_ref::<DbSet<E>>() else {
472 return Vec::new();
473 };
474 let type_name = E::entity_meta().type_name.to_string();
475 db_set
476 .entries
477 .iter()
478 .map(|e| EntityEntryView {
479 type_id: TypeId::of::<E>(),
480 type_name: type_name.clone(),
481 state: e.state,
482 })
483 .collect()
484 }
485
486 fn drain_cascade_children(
487 &self,
488 raw_set: &mut (dyn Any + Send + Sync),
489 meta: &EntityTypeMeta,
490 ) -> Vec<DrainedChild> {
491 let Some(db_set) = raw_set.downcast_mut::<DbSet<E>>() else {
492 return Vec::new();
493 };
494 let mut result = Vec::new();
495 for (entry_idx, entry) in db_set.entries.iter_mut().enumerate() {
496 if entry.state != EntityState::Added || entry.is_upsert {
497 continue;
498 }
499 for nav in &meta.navigations {
500 if !matches!(
501 nav.kind,
502 NavigationKind::HasMany | NavigationKind::ManyToMany
503 ) {
504 continue;
505 }
506 if let Some(items) = entry.entity.drain_has_many(nav.field_name.as_ref()) {
507 for item in items {
508 result.push(DrainedChild {
509 parent_type_id: TypeId::of::<E>(),
510 parent_entry_idx: entry_idx,
511 child: item,
512 child_type_id: nav.related_type_id,
513 fk_target_type_id: TypeId::of::<E>(),
514 through_table: nav.through_table.as_ref().map(|s| s.to_string()),
515 through_parent_fk_col: nav
516 .through_parent_fk
517 .as_ref()
518 .map(|s| s.to_string()),
519 through_child_fk_col: nav
520 .through_related_fk
521 .as_ref()
522 .map(|s| s.to_string()),
523 });
524 }
525 }
526 }
527 }
528 result
529 }
530
531 fn add_cascade_child(
532 &self,
533 raw_set: &mut (dyn Any + Send + Sync),
534 child: Box<dyn Any + Send + Sync>,
535 ) -> Option<usize> {
536 let db_set = raw_set.downcast_mut::<DbSet<E>>()?;
537 let child = child.downcast::<E>().ok()?;
538 let pk: i64 = child
539 .key_values()
540 .into_values()
541 .next()
542 .and_then(|v| v.try_into().ok())
543 .unwrap_or(0);
544 let entity = *child;
545 if pk > 0 {
546 db_set.attach(entity);
547 } else {
548 db_set.add(entity);
549 }
550 Some(db_set.entries.len() - 1)
551 }
552
553 fn get_pk_at(&self, raw_set: &(dyn Any + Send + Sync), idx: usize) -> Option<i64> {
554 let db_set = raw_set.downcast_ref::<DbSet<E>>()?;
555 let entry = db_set.entries.get(idx)?;
556 entry
557 .entity
558 .key_values()
559 .into_values()
560 .next()
561 .and_then(|v| v.try_into().ok())
562 }
563
564 fn set_fk_at(
565 &self,
566 raw_set: &mut (dyn Any + Send + Sync),
567 idx: usize,
568 target_type: TypeId,
569 key: i64,
570 ) {
571 if let Some(db_set) = raw_set.downcast_mut::<DbSet<E>>() {
572 if let Some(entry) = db_set.entries.get_mut(idx) {
573 entry.entity.set_foreign_key(target_type, key);
574 }
575 }
576 }
577
578 async fn insert_added(
579 &self,
580 conn: &mut (dyn IAsyncConnection + Send),
581 provider: &dyn IDatabaseProvider,
582 raw_set: &mut (dyn Any + Send + Sync),
583 meta: &EntityTypeMeta,
584 ) -> EFResult<usize> {
585 let db_set = raw_set
586 .downcast_mut::<DbSet<E>>()
587 .expect("SetOps type mismatch");
588 insert_added_phase(conn, provider, db_set, meta).await
589 }
590
591 async fn upsert_added(
592 &self,
593 conn: &mut (dyn IAsyncConnection + Send),
594 provider: &dyn IDatabaseProvider,
595 raw_set: &mut (dyn Any + Send + Sync),
596 meta: &EntityTypeMeta,
597 ) -> EFResult<usize> {
598 let db_set = raw_set
599 .downcast_mut::<DbSet<E>>()
600 .expect("SetOps type mismatch");
601 upsert_added_phase(conn, provider, db_set, meta).await
602 }
603
604 async fn update_modified(
605 &self,
606 conn: &mut (dyn IAsyncConnection + Send),
607 provider: &dyn IDatabaseProvider,
608 raw_set: &mut (dyn Any + Send + Sync),
609 meta: &EntityTypeMeta,
610 ) -> EFResult<usize> {
611 let db_set = raw_set
612 .downcast_mut::<DbSet<E>>()
613 .expect("SetOps type mismatch");
614 let query_filter = db_set.query_filter().cloned();
615 update_modified_phase(conn, provider, db_set, meta, query_filter.as_ref()).await
616 }
617
618 async fn delete_deleted(
619 &self,
620 conn: &mut (dyn IAsyncConnection + Send),
621 provider: &dyn IDatabaseProvider,
622 raw_set: &mut (dyn Any + Send + Sync),
623 meta: &EntityTypeMeta,
624 ) -> EFResult<usize> {
625 let db_set = raw_set
626 .downcast_mut::<DbSet<E>>()
627 .expect("SetOps type mismatch");
628 let query_filter = db_set.query_filter().cloned();
629 delete_deleted_phase(conn, provider, db_set, meta, query_filter.as_ref()).await
630 }
631
632 fn drain_cascade_deleted_children(
633 &self,
634 raw_set: &mut (dyn Any + Send + Sync),
635 meta: &EntityTypeMeta,
636 processed: &mut std::collections::HashSet<(TypeId, usize)>,
637 ) -> (Vec<DrainedChild>, Vec<CascadeDeleteDirective>) {
638 use crate::relations::DeleteBehavior;
639
640 let Some(db_set) = raw_set.downcast_mut::<DbSet<E>>() else {
641 return (Vec::new(), Vec::new());
642 };
643 let mut drained_children = Vec::new();
644 let mut directives = Vec::new();
645
646 for (entry_idx, entry) in db_set.entries.iter_mut().enumerate() {
647 if entry.state != EntityState::Deleted {
648 continue;
649 }
650 if !processed.insert((TypeId::of::<E>(), entry_idx)) {
652 continue;
653 }
654
655 let principal_pk: i64 = entry
657 .entity
658 .key_values()
659 .into_values()
660 .next()
661 .and_then(|v| v.try_into().ok())
662 .unwrap_or(0);
663
664 for nav in &meta.navigations {
665 match nav.kind {
666 NavigationKind::ManyToMany => {
667 if let (Some(table), Some(fk_col), Some(pk)) = (
669 nav.through_table.as_ref(),
670 nav.through_parent_fk.as_ref(),
671 (principal_pk > 0).then_some(principal_pk),
672 ) {
673 directives.push(CascadeDeleteDirective {
674 table: table.to_string(),
675 fk_column: fk_col.to_string(),
676 principal_pk: pk,
677 action: CascadeDeleteAction::Delete,
678 });
679 }
680 }
681 NavigationKind::HasMany => {
682 let behavior = resolve_delete_behavior(nav);
683 match behavior {
684 DeleteBehavior::Cascade => {
685 if let Some(items) =
687 entry.entity.drain_has_many(nav.field_name.as_ref())
688 {
689 for item in items {
690 drained_children.push(DrainedChild {
691 parent_type_id: TypeId::of::<E>(),
692 parent_entry_idx: entry_idx,
693 child: item,
694 child_type_id: nav.related_type_id,
695 fk_target_type_id: TypeId::of::<E>(),
696 through_table: None,
697 through_parent_fk_col: None,
698 through_child_fk_col: None,
699 });
700 }
701 }
702 if let (Some(table), Some(fk_col), Some(pk)) = (
704 nav.related_table.as_ref(),
705 nav.fk_column.as_ref(),
706 (principal_pk > 0).then_some(principal_pk),
707 ) {
708 directives.push(CascadeDeleteDirective {
709 table: table.to_string(),
710 fk_column: fk_col.to_string(),
711 principal_pk: pk,
712 action: CascadeDeleteAction::Delete,
713 });
714 }
715 }
716 DeleteBehavior::SetNull => {
717 if let (Some(table), Some(fk_col), Some(pk)) = (
719 nav.related_table.as_ref(),
720 nav.fk_column.as_ref(),
721 (principal_pk > 0).then_some(principal_pk),
722 ) {
723 directives.push(CascadeDeleteDirective {
724 table: table.to_string(),
725 fk_column: fk_col.to_string(),
726 principal_pk: pk,
727 action: CascadeDeleteAction::SetNull,
728 });
729 }
730 }
731 DeleteBehavior::Restrict | DeleteBehavior::NoAction => {
732 }
734 }
735 }
736 NavigationKind::BelongsTo | NavigationKind::HasOne => {
737 }
739 }
740 }
741 }
742 (drained_children, directives)
743 }
744
745 fn add_cascade_deleted_child(
746 &self,
747 raw_set: &mut (dyn Any + Send + Sync),
748 child: Box<dyn Any + Send + Sync>,
749 ) -> Option<usize> {
750 let db_set = raw_set.downcast_mut::<DbSet<E>>()?;
751 let child = child.downcast::<E>().ok()?;
752 let pk: i64 = child
753 .key_values()
754 .into_values()
755 .next()
756 .and_then(|v| v.try_into().ok())
757 .unwrap_or(0);
758 if pk == 0 {
759 return None;
760 }
761 let original = child.snapshot();
762 db_set.entries.push(crate::db_set::TrackedEntry {
763 entity: *child,
764 state: EntityState::Deleted,
765 original: Some(original),
766 modified_properties: Vec::new(),
767 is_upsert: false,
768 });
769 Some(db_set.entries.len() - 1)
770 }
771}
772
773pub(crate) fn resolve_delete_behavior(
779 nav: &crate::metadata::NavigationMeta,
780) -> crate::relations::DeleteBehavior {
781 use crate::relations::DeleteBehavior;
782 if let Some(b) = nav.delete_behavior {
783 return b;
784 }
785 if nav.kind == NavigationKind::ManyToMany {
786 return DeleteBehavior::Cascade;
787 }
788 if let Some(meta_fn) = nav.related_entity_meta {
789 let child_meta = meta_fn();
790 if let Some(fk_prop) = child_meta.properties.iter().find(|p| p.is_foreign_key) {
791 let is_nullable = fk_prop.type_name.contains("Option");
794 return if is_nullable {
795 DeleteBehavior::Restrict
796 } else {
797 DeleteBehavior::Cascade
798 };
799 }
800 }
801 DeleteBehavior::Cascade
802}
803
804pub struct DbContext {
809 sets: HashMap<TypeId, Box<dyn Any + Send + Sync>>,
810 savers: HashMap<TypeId, Box<dyn ErasedSetOps>>,
811 entity_metas: HashMap<TypeId, EntityTypeMeta>,
812 model_builder: ModelBuilder,
813 change_tracker: ChangeTracker,
814 provider: Arc<dyn IDatabaseProvider>,
815 interceptor_pipeline: InterceptorPipeline,
816 lazy_loading_enabled: bool,
817 ambient_transaction: Option<Box<dyn ITransaction>>,
826}
827
828impl DbContext {
829 pub fn from_options(options: &DbContextOptions) -> EFResult<Self> {
837 let provider = options.create_provider()?;
838 let built = options
839 .metadata_cache
840 .get_or_build(options.context_key.as_deref());
841 let ctx = Self {
842 sets: HashMap::new(),
843 savers: HashMap::new(),
844 entity_metas: built.entity_metas.clone(),
845 model_builder: ModelBuilder::from_built(&built),
846 change_tracker: ChangeTracker::new(),
847 provider,
848 interceptor_pipeline: InterceptorPipeline::new(options.interceptors.clone()),
849 lazy_loading_enabled: options.lazy_loading_enabled,
850 ambient_transaction: None,
851 };
852 Ok(ctx)
853 }
854
855 pub fn set<T>(&mut self) -> &mut DbSet<T>
856 where
857 T: IEntityType
858 + IEntitySnapshot
859 + IGetKeyValues
860 + IFromRow
861 + INavigationSetter
862 + Send
863 + Sync
864 + 'static,
865 {
866 let type_id = TypeId::of::<T>();
867 self.savers
868 .entry(type_id)
869 .or_insert_with(|| Box::new(SetOps::<T>::new()));
870 self.entity_metas
871 .entry(type_id)
872 .or_insert_with(T::entity_meta);
873 if !self.model_builder.has_entity(type_id) {
874 self.model_builder.register_entity_meta(T::entity_meta());
875 }
876 self.sets.entry(type_id).or_insert_with(|| {
877 let table_name = self
878 .model_builder
879 .build()
880 .into_iter()
881 .find(|m| m.type_id == type_id)
882 .map(|m| m.table_name.to_string())
883 .unwrap_or_else(|| T::entity_meta().table_name.to_string());
884 let mut db_set = DbSet::<T>::with_provider(table_name, Arc::clone(&self.provider));
885 if let Some(filter) = self.model_builder.get_query_filter(&type_id) {
886 db_set.set_query_filter(filter.clone());
887 }
888 db_set.set_filter_map(self.model_builder.filters_by_table());
889 db_set.set_lazy_loading_enabled(self.lazy_loading_enabled);
890 Box::new(db_set)
891 });
892 self.sets
893 .get_mut(&type_id)
894 .and_then(|b| b.downcast_mut::<DbSet<T>>())
895 .expect("DbSet type mismatch")
896 }
897
898 pub fn model(&mut self) -> &mut ModelBuilder {
900 &mut self.model_builder
901 }
902
903 pub fn model_builder(&self) -> &ModelBuilder {
905 &self.model_builder
906 }
907
908 pub fn entity_metas_contains<T: IEntityType>(&self) -> bool {
911 self.entity_metas.contains_key(&TypeId::of::<T>())
912 }
913
914 pub fn discover_entities(&mut self) -> EFResult<()> {
926 Ok(())
927 }
928
929 pub fn detect_changes(&mut self) {
931 let type_ids: Vec<TypeId> = self.sets.keys().copied().collect();
932 for type_id in type_ids {
933 if let Some(set) = self.sets.get_mut(&type_id) {
934 if let Some(saver) = self.savers.get(&type_id) {
935 saver.detect_changes(set.as_mut());
936 }
937 }
938 }
939 }
940
941 fn build_save_context(&self) -> SaveChangesContext {
946 let mut views: Vec<EntityEntryView> = Vec::new();
947 for (type_id, set) in &self.sets {
948 if let Some(saver) = self.savers.get(type_id) {
949 views.extend(saver.collect_entries(set.as_ref()));
950 }
951 }
952 SaveChangesContext::from_views(views)
953 }
954
955 pub async fn ensure_created(&self) -> EFResult<()> {
962 let mut metas: Vec<EntityTypeMeta> = self.model_builder.build();
963 if metas.is_empty() {
964 metas = self.entity_metas.values().cloned().collect();
965 }
966 if metas.is_empty() {
967 return Err(EFError::configuration(
968 "No entity types registered. Call ctx.discover_entities() or ctx.set::<T>() before ensure_created().",
969 ));
970 }
971 let dialect = self.provider.migration_dialect();
972 MigrationEngine::new(dialect)
973 .ensure_created(&*self.provider, &metas)
974 .await?;
975
976 for meta in &metas {
977 let rows = self.model_builder.seed_rows_for(&meta.type_id);
978 if !rows.is_empty() {
979 MigrationEngine::new(dialect)
980 .apply_seed_data(&*self.provider, meta, rows)
981 .await?;
982 }
983 }
984 Ok(())
985 }
986
987 pub async fn ensure_deleted(&self) -> EFResult<()> {
989 let mut metas: Vec<EntityTypeMeta> = self.model_builder.build();
990 if metas.is_empty() {
991 metas = self.entity_metas.values().cloned().collect();
992 }
993 if metas.is_empty() {
994 return Err(EFError::configuration(
995 "No entity types registered. Call ctx.discover_entities() or ctx.set::<T>() before ensure_deleted().",
996 ));
997 }
998 let dialect = self.provider.migration_dialect();
999 MigrationEngine::new(dialect)
1000 .ensure_deleted(&*self.provider, &metas)
1001 .await
1002 }
1003}
1004
1005impl DbContext {
1010 pub fn provider(&self) -> &dyn IDatabaseProvider {
1012 &*self.provider
1013 }
1014
1015 pub fn change_tracker(&self) -> &ChangeTracker {
1017 &self.change_tracker
1018 }
1019
1020 pub fn change_tracker_mut(&mut self) -> &mut ChangeTracker {
1022 &mut self.change_tracker
1023 }
1024
1025 pub async fn sql_query<T: IFromRow + IEntityType>(
1038 &self,
1039 sql: &str,
1040 params: &[DbValue],
1041 ) -> EFResult<Vec<T>> {
1042 let mut conn = self.provider.get_connection().await?;
1043 let rows = conn.query(sql, params).await?;
1044 crate::entity::materialize_entities(&rows)
1045 }
1046
1047 pub fn transaction_mut(&mut self) -> Option<&mut Box<dyn ITransaction>> {
1055 self.ambient_transaction.as_mut()
1056 }
1057
1058 pub async fn begin_transaction(&mut self) -> EFResult<Box<dyn ITransaction>> {
1071 let mut conn = self.provider.get_connection().await?;
1072 conn.begin_transaction().await?;
1073 Ok(Box::new(DbTransaction::new(conn)))
1074 }
1075
1076 pub async fn save_changes(&mut self) -> EFResult<SaveChangesResult> {
1081 let _save_guard = crate::observability::SaveChangesGuard::new();
1082 let type_ids: Vec<TypeId> = self.sets.keys().copied().collect();
1083 for type_id in &type_ids {
1084 let set = self.sets.get_mut(type_id).unwrap();
1085 self.savers
1086 .get(type_id)
1087 .unwrap()
1088 .detect_changes(set.as_mut());
1089 }
1090
1091 let configured_metas: HashMap<TypeId, EntityTypeMeta> = self
1094 .model_builder
1095 .build()
1096 .into_iter()
1097 .map(|m| (m.type_id, m))
1098 .collect();
1099
1100 let save_ctx = self.build_save_context();
1104 self.interceptor_pipeline.on_saving(&save_ctx).await?;
1105
1106 enum TxnSource {
1112 Ambient(Box<dyn ITransaction>),
1113 Managed(Box<dyn IAsyncConnection>),
1114 }
1115 let mut txn = match self.ambient_transaction.take() {
1116 Some(t) => TxnSource::Ambient(t),
1117 None => {
1118 let mut c = self.provider.get_connection().await?;
1119 c.begin_transaction().await?;
1120 TxnSource::Managed(c)
1121 }
1122 };
1123
1124 let type_ids: Vec<TypeId> = self.sets.keys().copied().collect();
1125
1126 let mut fixup_links: Vec<FixupLink> = Vec::new();
1132 loop {
1133 let mut all_drained: Vec<DrainedChild> = Vec::new();
1134 for type_id in &type_ids {
1135 let saver = self.savers.get(type_id).expect("saver not registered");
1136 let set = self.sets.get_mut(type_id).unwrap();
1137 let meta = configured_metas
1138 .get(type_id)
1139 .or_else(|| self.entity_metas.get(type_id))
1140 .expect("meta not found");
1141 all_drained.extend(saver.drain_cascade_children(set.as_mut(), meta));
1142 }
1143 if all_drained.is_empty() {
1144 break;
1145 }
1146 for child in all_drained {
1147 let child_saver = self.savers.get(&child.child_type_id).ok_or_else(|| {
1148 EFError::configuration(format!(
1149 "Cannot cascade-save child type {:?}: no DbSet registered. \
1150 Call ctx.set::<ChildType>() before save_changes.",
1151 child.child_type_id
1152 ))
1153 })?;
1154 let child_set = self
1155 .sets
1156 .get_mut(&child.child_type_id)
1157 .expect("set not found for registered saver");
1158 if let Some(child_idx) =
1159 child_saver.add_cascade_child(child_set.as_mut(), child.child)
1160 {
1161 if let Some(link) = fixup_links.iter_mut().find(|l| {
1162 l.parent_type_id == child.parent_type_id
1163 && l.parent_entry_idx == child.parent_entry_idx
1164 && l.child_type_id == child.child_type_id
1165 && l.through_table == child.through_table
1166 }) {
1167 link.child_entry_indices.push(child_idx);
1168 } else {
1169 fixup_links.push(FixupLink {
1170 parent_type_id: child.parent_type_id,
1171 parent_entry_idx: child.parent_entry_idx,
1172 child_type_id: child.child_type_id,
1173 child_entry_indices: vec![child_idx],
1174 fk_target_type_id: child.fk_target_type_id,
1175 through_table: child.through_table,
1176 through_parent_fk_col: child.through_parent_fk_col,
1177 through_child_fk_col: child.through_child_fk_col,
1178 });
1179 }
1180 }
1181 }
1182 }
1183
1184 let mut delete_directives: Vec<CascadeDeleteDirective> = Vec::new();
1190 let mut processed: std::collections::HashSet<(TypeId, usize)> =
1191 std::collections::HashSet::new();
1192 loop {
1193 let mut all_drained_deleted: Vec<DrainedChild> = Vec::new();
1194 for type_id in &type_ids {
1195 let saver = self.savers.get(type_id).expect("saver not registered");
1196 let set = self.sets.get_mut(type_id).unwrap();
1197 let meta = configured_metas
1198 .get(type_id)
1199 .or_else(|| self.entity_metas.get(type_id))
1200 .expect("meta not found");
1201 let (drained, directives) =
1202 saver.drain_cascade_deleted_children(set.as_mut(), meta, &mut processed);
1203 all_drained_deleted.extend(drained);
1204 delete_directives.extend(directives);
1205 }
1206 if all_drained_deleted.is_empty() {
1207 break;
1208 }
1209 for child in all_drained_deleted {
1210 let child_saver = self.savers.get(&child.child_type_id).ok_or_else(|| {
1211 EFError::configuration(format!(
1212 "Cannot cascade-delete child type {:?}: no DbSet registered. \
1213 Call ctx.set::<ChildType>() before save_changes.",
1214 child.child_type_id
1215 ))
1216 })?;
1217 let child_set = self
1218 .sets
1219 .get_mut(&child.child_type_id)
1220 .expect("set not found for registered saver");
1221 child_saver.add_cascade_deleted_child(child_set.as_mut(), child.child);
1222 }
1223 }
1224
1225 let graph = DependencyGraph::build(&configured_metas);
1227 let insert_order = graph.topological_sort();
1228 let delete_order = graph.deletion_order();
1229
1230 let mut total_added = 0usize;
1231 let mut total_updated = 0usize;
1232 let mut total_deleted = 0usize;
1233
1234 for type_id in &insert_order {
1236 if !self.sets.contains_key(type_id) || !self.savers.contains_key(type_id) {
1237 continue;
1238 }
1239 let saver = self.savers.get(type_id).expect("saver not registered");
1240 let set = self.sets.get_mut(type_id).unwrap();
1241 let meta = configured_metas
1242 .get(type_id)
1243 .or_else(|| self.entity_metas.get(type_id))
1244 .expect("meta not found");
1245 let inserted = {
1246 let conn_ref: &mut dyn IAsyncConnection = match &mut txn {
1247 TxnSource::Ambient(t) => t.connection(),
1248 TxnSource::Managed(c) => c.as_mut(),
1249 };
1250 match saver
1251 .insert_added(conn_ref, &*self.provider, set.as_mut(), meta)
1252 .await
1253 {
1254 Ok(n) => n,
1255 Err(e) => {
1256 if let TxnSource::Managed(mut conn) = txn {
1257 let _ = conn.rollback_transaction().await;
1258 } else if let TxnSource::Ambient(t) = txn {
1259 self.ambient_transaction = Some(t);
1260 }
1261 self.interceptor_pipeline
1262 .on_save_failed(&save_ctx, &e)
1263 .await;
1264 return Err(e);
1265 }
1266 }
1267 };
1268 total_added += inserted;
1269
1270 let link_indices: Vec<usize> = fixup_links
1272 .iter()
1273 .enumerate()
1274 .filter(|(_, l)| l.parent_type_id == *type_id && l.through_table.is_none())
1275 .map(|(i, _)| i)
1276 .collect();
1277
1278 let mut self_ref_updates: Vec<(String, i64, i64)> = Vec::new();
1280
1281 for link_idx in &link_indices {
1282 let link = &fixup_links[*link_idx];
1283 let parent_pk = {
1284 let parent_saver = self.savers.get(&link.parent_type_id).unwrap();
1285 let parent_set = self.sets.get(&link.parent_type_id).unwrap();
1286 parent_saver.get_pk_at(parent_set.as_ref(), link.parent_entry_idx)
1287 };
1288 let Some(pk) = parent_pk else {
1289 continue;
1290 };
1291
1292 {
1294 let child_saver = self.savers.get(&link.child_type_id).unwrap();
1295 let child_set = self.sets.get_mut(&link.child_type_id).unwrap();
1296 for &child_idx in &link.child_entry_indices {
1297 child_saver.set_fk_at(
1298 child_set.as_mut(),
1299 child_idx,
1300 link.fk_target_type_id,
1301 pk,
1302 );
1303 }
1304 }
1305
1306 if link.child_type_id == link.parent_type_id {
1308 let child_meta = configured_metas
1309 .get(&link.child_type_id)
1310 .or_else(|| self.entity_metas.get(&link.child_type_id))
1311 .unwrap();
1312 let fk_col = child_meta
1313 .properties
1314 .iter()
1315 .find(|p| p.is_foreign_key)
1316 .map(|p| p.column_name.as_ref());
1317 let pk_col = child_meta
1318 .properties
1319 .iter()
1320 .find(|p| p.is_primary_key)
1321 .map(|p| p.column_name.as_ref())
1322 .unwrap_or("id");
1323 if let Some(fk_col) = fk_col {
1324 for &child_idx in &link.child_entry_indices {
1325 let child_pk = {
1326 let child_saver = self.savers.get(&link.child_type_id).unwrap();
1327 let child_set = self.sets.get(&link.child_type_id).unwrap();
1328 child_saver.get_pk_at(child_set.as_ref(), child_idx)
1329 };
1330 if let Some(child_pk) = child_pk {
1331 let sql = format!(
1332 "UPDATE {} SET {} = ? WHERE {} = ?",
1333 child_meta.table_name, fk_col, pk_col
1334 );
1335 self_ref_updates.push((sql, pk, child_pk));
1336 }
1337 }
1338 }
1339 }
1340 }
1341
1342 for (sql, fk_val, pk_val) in self_ref_updates {
1344 let conn_ref: &mut dyn IAsyncConnection = match &mut txn {
1345 TxnSource::Ambient(t) => t.connection(),
1346 TxnSource::Managed(c) => c.as_mut(),
1347 };
1348 if let Err(e) = conn_ref
1349 .execute(&sql, &[DbValue::from(fk_val), DbValue::from(pk_val)])
1350 .await
1351 {
1352 if let TxnSource::Managed(mut conn) = txn {
1353 let _ = conn.rollback_transaction().await;
1354 } else if let TxnSource::Ambient(t) = txn {
1355 self.ambient_transaction = Some(t);
1356 }
1357 self.interceptor_pipeline
1358 .on_save_failed(&save_ctx, &e)
1359 .await;
1360 return Err(e);
1361 }
1362 }
1363 }
1364
1365 for link in &fixup_links {
1367 if link.through_table.is_none() {
1368 continue;
1369 }
1370 let table = link.through_table.as_ref().unwrap();
1371 let parent_col = link.through_parent_fk_col.as_ref().unwrap();
1372 let child_col = link.through_child_fk_col.as_ref().unwrap();
1373
1374 let parent_pk = {
1375 let parent_saver = self.savers.get(&link.parent_type_id).unwrap();
1376 let parent_set = self.sets.get(&link.parent_type_id).unwrap();
1377 parent_saver.get_pk_at(parent_set.as_ref(), link.parent_entry_idx)
1378 };
1379 let Some(parent_pk) = parent_pk else {
1380 continue;
1381 };
1382
1383 let mut child_pks: Vec<i64> = Vec::new();
1384 {
1385 let child_saver = self.savers.get(&link.child_type_id).unwrap();
1386 let child_set = self.sets.get(&link.child_type_id).unwrap();
1387 for &child_idx in &link.child_entry_indices {
1388 if let Some(child_pk) = child_saver.get_pk_at(child_set.as_ref(), child_idx) {
1389 child_pks.push(child_pk);
1390 }
1391 }
1392 }
1393
1394 if !child_pks.is_empty() {
1395 let sql = cascade::m2m_insert_sql(table, parent_col, child_col, child_pks.len());
1396 let mut params: Vec<DbValue> = Vec::with_capacity(child_pks.len() * 2);
1397 for child_pk in &child_pks {
1398 params.push(DbValue::from(parent_pk));
1399 params.push(DbValue::from(*child_pk));
1400 }
1401 let conn_ref: &mut dyn IAsyncConnection = match &mut txn {
1402 TxnSource::Ambient(t) => t.connection(),
1403 TxnSource::Managed(c) => c.as_mut(),
1404 };
1405 if let Err(e) = conn_ref.execute(&sql, ¶ms).await {
1406 if let TxnSource::Managed(mut conn) = txn {
1407 let _ = conn.rollback_transaction().await;
1408 } else if let TxnSource::Ambient(t) = txn {
1409 self.ambient_transaction = Some(t);
1410 }
1411 self.interceptor_pipeline
1412 .on_save_failed(&save_ctx, &e)
1413 .await;
1414 return Err(e);
1415 }
1416 total_added += child_pks.len();
1417 }
1418 }
1419
1420 for type_id in &insert_order {
1422 if !self.sets.contains_key(type_id) || !self.savers.contains_key(type_id) {
1423 continue;
1424 }
1425 let saver = self.savers.get(type_id).expect("saver not registered");
1426 let set = self.sets.get_mut(type_id).unwrap();
1427 let meta = configured_metas
1428 .get(type_id)
1429 .or_else(|| self.entity_metas.get(type_id))
1430 .expect("meta not found");
1431 let n = {
1432 let conn_ref: &mut dyn IAsyncConnection = match &mut txn {
1433 TxnSource::Ambient(t) => t.connection(),
1434 TxnSource::Managed(c) => c.as_mut(),
1435 };
1436 match saver
1437 .upsert_added(conn_ref, &*self.provider, set.as_mut(), meta)
1438 .await
1439 {
1440 Ok(n) => n,
1441 Err(e) => {
1442 if let TxnSource::Managed(mut conn) = txn {
1443 let _ = conn.rollback_transaction().await;
1444 } else if let TxnSource::Ambient(t) = txn {
1445 self.ambient_transaction = Some(t);
1446 }
1447 self.interceptor_pipeline
1448 .on_save_failed(&save_ctx, &e)
1449 .await;
1450 return Err(e);
1451 }
1452 }
1453 };
1454 total_added += n;
1455 }
1456
1457 for type_id in &insert_order {
1459 if !self.sets.contains_key(type_id) || !self.savers.contains_key(type_id) {
1460 continue;
1461 }
1462 let saver = self.savers.get(type_id).expect("saver not registered");
1463 let set = self.sets.get_mut(type_id).unwrap();
1464 let meta = configured_metas
1465 .get(type_id)
1466 .or_else(|| self.entity_metas.get(type_id))
1467 .expect("meta not found");
1468 let n = {
1469 let conn_ref: &mut dyn IAsyncConnection = match &mut txn {
1470 TxnSource::Ambient(t) => t.connection(),
1471 TxnSource::Managed(c) => c.as_mut(),
1472 };
1473 match saver
1474 .update_modified(conn_ref, &*self.provider, set.as_mut(), meta)
1475 .await
1476 {
1477 Ok(n) => n,
1478 Err(e) => {
1479 if let TxnSource::Managed(mut conn) = txn {
1480 let _ = conn.rollback_transaction().await;
1481 } else if let TxnSource::Ambient(t) = txn {
1482 self.ambient_transaction = Some(t);
1483 }
1484 self.interceptor_pipeline
1485 .on_save_failed(&save_ctx, &e)
1486 .await;
1487 return Err(e);
1488 }
1489 }
1490 };
1491 total_updated += n;
1492 }
1493
1494 for directive in &delete_directives {
1499 if directive.action != CascadeDeleteAction::SetNull {
1500 continue;
1501 }
1502 let sql = format!(
1503 "UPDATE {} SET {} = NULL WHERE {} = ?",
1504 directive.table, directive.fk_column, directive.fk_column
1505 );
1506 let params = vec![DbValue::from(directive.principal_pk)];
1507 let conn_ref: &mut dyn IAsyncConnection = match &mut txn {
1508 TxnSource::Ambient(t) => t.connection(),
1509 TxnSource::Managed(c) => c.as_mut(),
1510 };
1511 if let Err(e) = conn_ref.execute(&sql, ¶ms).await {
1512 if let TxnSource::Managed(mut conn) = txn {
1513 let _ = conn.rollback_transaction().await;
1514 } else if let TxnSource::Ambient(t) = txn {
1515 self.ambient_transaction = Some(t);
1516 }
1517 self.interceptor_pipeline
1518 .on_save_failed(&save_ctx, &e)
1519 .await;
1520 return Err(e);
1521 }
1522 }
1523
1524 for type_id in &delete_order {
1526 if !self.sets.contains_key(type_id) || !self.savers.contains_key(type_id) {
1527 continue;
1528 }
1529 let saver = self.savers.get(type_id).expect("saver not registered");
1530 let set = self.sets.get_mut(type_id).unwrap();
1531 let meta = configured_metas
1532 .get(type_id)
1533 .or_else(|| self.entity_metas.get(type_id))
1534 .expect("meta not found");
1535 let n = {
1536 let conn_ref: &mut dyn IAsyncConnection = match &mut txn {
1537 TxnSource::Ambient(t) => t.connection(),
1538 TxnSource::Managed(c) => c.as_mut(),
1539 };
1540 match saver
1541 .delete_deleted(conn_ref, &*self.provider, set.as_mut(), meta)
1542 .await
1543 {
1544 Ok(n) => n,
1545 Err(e) => {
1546 if let TxnSource::Managed(mut conn) = txn {
1547 let _ = conn.rollback_transaction().await;
1548 } else if let TxnSource::Ambient(t) = txn {
1549 self.ambient_transaction = Some(t);
1550 }
1551 self.interceptor_pipeline
1552 .on_save_failed(&save_ctx, &e)
1553 .await;
1554 return Err(e);
1555 }
1556 }
1557 };
1558 total_deleted += n;
1559 }
1560
1561 for directive in &delete_directives {
1566 if directive.action != CascadeDeleteAction::Delete {
1567 continue;
1568 }
1569 let sql = format!(
1570 "DELETE FROM {} WHERE {} = ?",
1571 directive.table, directive.fk_column
1572 );
1573 let params = vec![DbValue::from(directive.principal_pk)];
1574 let conn_ref: &mut dyn IAsyncConnection = match &mut txn {
1575 TxnSource::Ambient(t) => t.connection(),
1576 TxnSource::Managed(c) => c.as_mut(),
1577 };
1578 if let Err(e) = conn_ref.execute(&sql, ¶ms).await {
1579 if let TxnSource::Managed(mut conn) = txn {
1580 let _ = conn.rollback_transaction().await;
1581 } else if let TxnSource::Ambient(t) = txn {
1582 self.ambient_transaction = Some(t);
1583 }
1584 self.interceptor_pipeline
1585 .on_save_failed(&save_ctx, &e)
1586 .await;
1587 return Err(e);
1588 }
1589 }
1590
1591 match txn {
1592 TxnSource::Ambient(t) => {
1593 self.ambient_transaction = Some(t);
1594 }
1595 TxnSource::Managed(mut conn) => {
1596 if let Err(e) = conn.commit_transaction().await {
1597 self.interceptor_pipeline
1598 .on_save_failed(&save_ctx, &e)
1599 .await;
1600 return Err(e);
1601 }
1602 }
1603 }
1604 self.change_tracker.accept_all_changes();
1605 for type_id in &type_ids {
1606 let saver = self.savers.get(type_id).unwrap();
1607 let set = self.sets.get_mut(type_id).unwrap();
1608 saver.accept_all_changes(set.as_mut());
1609 }
1610
1611 let result_ctx = SaveChangesResultContext {
1613 added: total_added,
1614 updated: total_updated,
1615 deleted: total_deleted,
1616 };
1617 self.interceptor_pipeline
1618 .on_saved(&save_ctx, &result_ctx)
1619 .await?;
1620
1621 Ok(SaveChangesResult {
1622 added: total_added,
1623 updated: total_updated,
1624 deleted: total_deleted,
1625 })
1626 }
1627
1628 pub async fn use_transaction<F, R>(&mut self, f: F) -> EFResult<R>
1649 where
1650 for<'a> F: FnOnce(&'a mut Self) -> Pin<Box<dyn Future<Output = EFResult<R>> + Send + 'a>>,
1651 R: Send + 'static,
1652 {
1653 if self.ambient_transaction.is_some() {
1654 return Err(EFError::transaction(
1655 "ambient transaction already active; nested use_transaction is not supported",
1656 ));
1657 }
1658 let mut conn = self.provider.get_connection().await?;
1659 conn.begin_transaction().await?;
1660 self.ambient_transaction = Some(Box::new(DbTransaction::new(conn)));
1661 let result = f(self).await;
1662 let txn = self
1663 .ambient_transaction
1664 .take()
1665 .expect("ambient_transaction set above");
1666 match result {
1667 Ok(r) => {
1668 txn.commit().await?;
1669 Ok(r)
1670 }
1671 Err(e) => {
1672 let _ = txn.rollback().await;
1673 Err(e)
1674 }
1675 }
1676 }
1677}
1678
1679#[allow(clippy::type_complexity)]
1684pub async fn save_one_set<E>(
1685 conn: &mut dyn IAsyncConnection,
1686 provider: &dyn IDatabaseProvider,
1687 db_set: &mut DbSet<E>,
1688 meta: &EntityTypeMeta,
1689) -> EFResult<(usize, usize, usize)>
1690where
1691 E: IEntityType + IEntitySnapshot + IGetKeyValues + IFromRow,
1692{
1693 let query_filter = db_set.query_filter().cloned();
1694 let ac = insert_added_phase(conn, provider, db_set, meta).await?;
1695 let ac_upsert = upsert_added_phase(conn, provider, db_set, meta).await?;
1696 let uc = update_modified_phase(conn, provider, db_set, meta, query_filter.as_ref()).await?;
1697 let dc = delete_deleted_phase(conn, provider, db_set, meta, query_filter.as_ref()).await?;
1698 Ok((ac + ac_upsert, uc, dc))
1699}
1700
1701pub async fn insert_added_phase<E>(
1703 conn: &mut dyn IAsyncConnection,
1704 provider: &dyn IDatabaseProvider,
1705 db_set: &mut DbSet<E>,
1706 meta: &EntityTypeMeta,
1707) -> EFResult<usize>
1708where
1709 E: IEntityType + IEntitySnapshot + IGetKeyValues,
1710{
1711 let added: Vec<(&E, &EntityTypeMeta)> = db_set
1712 .tracked_by_state(EntityState::Added)
1713 .into_iter()
1714 .filter(|(_, _, _, is_upsert)| !*is_upsert)
1715 .map(|(e, _, _, _)| (e, meta))
1716 .collect();
1717 if added.is_empty() {
1718 return Ok(0);
1719 }
1720 let added_count = added.len();
1721 let mut generated_keys: Vec<i64> = vec![0; added_count];
1722 let inserted = ChangeExecutor::execute_inserts(conn, provider, &added, |idx, key| {
1723 if idx < generated_keys.len() {
1724 generated_keys[idx] = key;
1725 }
1726 })
1727 .await?;
1728 db_set.backfill_added_keys(&generated_keys);
1729 Ok(inserted)
1730}
1731
1732pub async fn upsert_added_phase<E>(
1734 conn: &mut dyn IAsyncConnection,
1735 provider: &dyn IDatabaseProvider,
1736 db_set: &mut DbSet<E>,
1737 meta: &EntityTypeMeta,
1738) -> EFResult<usize>
1739where
1740 E: IEntityType + IEntitySnapshot + IGetKeyValues,
1741{
1742 let upserts: Vec<(&E, &EntityTypeMeta)> = db_set
1743 .tracked_by_state(EntityState::Added)
1744 .into_iter()
1745 .filter(|(_, _, _, is_upsert)| *is_upsert)
1746 .map(|(e, _, _, _)| (e, meta))
1747 .collect();
1748 if upserts.is_empty() {
1749 return Ok(0);
1750 }
1751 ChangeExecutor::execute_upserts(conn, provider, &upserts).await
1752}
1753
1754#[allow(clippy::type_complexity)]
1756pub async fn update_modified_phase<E>(
1757 conn: &mut dyn IAsyncConnection,
1758 provider: &dyn IDatabaseProvider,
1759 db_set: &mut DbSet<E>,
1760 meta: &EntityTypeMeta,
1761 query_filter: Option<&crate::query::BoolExpr>,
1762) -> EFResult<usize>
1763where
1764 E: IEntityType + IEntitySnapshot + IGetKeyValues,
1765{
1766 let modified: Vec<(
1767 &E,
1768 &EntityTypeMeta,
1769 Option<&HashMap<String, DbValue>>,
1770 &[String],
1771 )> = db_set
1772 .tracked_by_state(EntityState::Modified)
1773 .into_iter()
1774 .map(|(e, orig, mods, _)| (e, meta, orig, mods))
1775 .collect();
1776 if modified.is_empty() {
1777 return Ok(0);
1778 }
1779 ChangeExecutor::execute_updates(conn, provider, &modified, query_filter).await
1780}
1781
1782#[allow(clippy::type_complexity)]
1784pub async fn delete_deleted_phase<E>(
1785 conn: &mut dyn IAsyncConnection,
1786 provider: &dyn IDatabaseProvider,
1787 db_set: &mut DbSet<E>,
1788 meta: &EntityTypeMeta,
1789 query_filter: Option<&crate::query::BoolExpr>,
1790) -> EFResult<usize>
1791where
1792 E: IEntityType + IEntitySnapshot + IGetKeyValues,
1793{
1794 let deleted: Vec<(&E, &EntityTypeMeta, Option<&HashMap<String, DbValue>>)> = db_set
1795 .tracked_by_state(EntityState::Deleted)
1796 .into_iter()
1797 .map(|(e, orig, _, _)| (e, meta, orig))
1798 .collect();
1799 if deleted.is_empty() {
1800 return Ok(0);
1801 }
1802 ChangeExecutor::execute_deletes(conn, provider, &deleted, query_filter).await
1803}
1804
1805#[derive(Debug, Clone)]
1810pub struct SaveChangesResult {
1811 pub added: usize,
1812 pub updated: usize,
1813 pub deleted: usize,
1814}
1815impl SaveChangesResult {
1816 pub fn total(&self) -> usize {
1817 self.added + self.updated + self.deleted
1818 }
1819}
1820impl std::fmt::Display for SaveChangesResult {
1821 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1822 write!(
1823 f,
1824 "{} entities modified ({} added, {} updated, {} deleted)",
1825 self.total(),
1826 self.added,
1827 self.updated,
1828 self.deleted
1829 )
1830 }
1831}