1use crate::cascade::{self, CascadeDeleteAction};
7use crate::dependency_graph::DependencyGraph;
8use crate::error::{EFError, EFResult};
9use crate::interceptor::{SaveChangesContext, SaveChangesResultContext};
10use crate::metadata::EntityTypeMeta;
11use crate::provider::{DbValue, IAsyncConnection};
12use crate::transaction::ITransaction;
13use std::any::TypeId;
14use std::collections::HashMap;
15use std::sync::atomic::{AtomicU64, Ordering};
16
17use super::save_phases::SaveChangesResult;
18
19pub(super) enum TxnSource {
23 Ambient(Box<dyn ITransaction>),
25 Managed(Box<dyn IAsyncConnection>),
27}
28
29impl TxnSource {
30 fn conn(&mut self) -> &mut dyn IAsyncConnection {
31 match self {
32 TxnSource::Ambient(t) => t.connection(),
33 TxnSource::Managed(c) => c.as_mut(),
34 }
35 }
36
37 fn is_scoped(&self) -> bool {
41 matches!(self, TxnSource::Ambient(t) if t.is_scoped())
42 }
43}
44
45static SAVEPOINT_SEQ: AtomicU64 = AtomicU64::new(1);
48
49fn next_savepoint_name() -> String {
50 format!(
51 "rust_ef_save_{}",
52 SAVEPOINT_SEQ.fetch_add(1, Ordering::Relaxed)
53 )
54}
55
56impl super::DbContext {
57 fn build_save_context(&self) -> SaveChangesContext {
62 let mut views: Vec<crate::tracking::EntityEntryView> = Vec::new();
63 for (type_id, set) in &self.sets {
64 if let Some(saver) = self.savers.get(type_id) {
65 views.extend(saver.collect_entries(set.as_ref(), &self.change_tracker));
66 }
67 }
68 SaveChangesContext::from_views(views)
69 }
70
71 pub async fn save_changes(&mut self) -> EFResult<SaveChangesResult> {
76 let _save_guard = crate::observability::SaveChangesGuard::new();
77 let type_ids: Vec<TypeId> = self.sets.keys().copied().collect();
78 for type_id in &type_ids {
79 let Some(set) = self.sets.get_mut(type_id) else {
80 continue;
81 };
82 let Some(saver) = self.savers.get(type_id) else {
83 continue;
84 };
85 saver.detect_changes(set.as_mut(), &mut self.change_tracker);
86 }
87
88 let configured_metas: HashMap<TypeId, EntityTypeMeta> = self
89 .model_builder
90 .build()
91 .into_iter()
92 .map(|m| (m.type_id, m))
93 .collect();
94
95 let save_ctx = self.build_save_context();
96 self.interceptor_pipeline.on_saving(&save_ctx).await?;
97
98 if save_ctx.added_count() == 0
101 && save_ctx.modified_count() == 0
102 && save_ctx.deleted_count() == 0
103 {
104 let result_ctx = SaveChangesResultContext {
105 added: 0,
106 updated: 0,
107 deleted: 0,
108 };
109 self.interceptor_pipeline
110 .on_saved(&save_ctx, &result_ctx)
111 .await?;
112 return Ok(SaveChangesResult {
113 added: 0,
114 updated: 0,
115 deleted: 0,
116 });
117 }
118
119 let mut txn = match self.ambient_transaction.take() {
125 Some(t) => TxnSource::Ambient(t),
126 None => {
127 let mut c = self.provider.get_connection().await?;
128 c.begin_transaction().await?;
129 TxnSource::Managed(c)
130 }
131 };
132
133 let scoped = txn.is_scoped();
137 let savepoint = if scoped {
138 let name = next_savepoint_name();
139 if let Err(e) = txn.conn().create_savepoint(&name).await {
140 return self.fail_save(txn, &save_ctx, None, e).await;
141 }
142 Some(name)
143 } else {
144 None
145 };
146
147 let type_ids: Vec<TypeId> = self.sets.keys().copied().collect();
148
149 let fixup_links = match self.drain_cascade_adds(&type_ids, &configured_metas) {
151 Ok(links) => links,
152 Err(e) => {
153 return self
154 .fail_save(txn, &save_ctx, savepoint.as_deref(), e)
155 .await
156 }
157 };
158 let mut processed: std::collections::HashSet<(TypeId, usize)> =
159 std::collections::HashSet::new();
160 let delete_directives =
161 match self.drain_cascade_deletes(&type_ids, &configured_metas, &mut processed) {
162 Ok(directives) => directives,
163 Err(e) => {
164 return self
165 .fail_save(txn, &save_ctx, savepoint.as_deref(), e)
166 .await
167 }
168 };
169
170 let graph = DependencyGraph::build(&configured_metas);
171 let insert_order = graph.topological_sort();
172 let delete_order = graph.deletion_order();
173
174 let mut total_added = 0usize;
175 let mut total_updated = 0usize;
176 let mut total_deleted = 0usize;
177
178 for type_id in &insert_order {
180 if !self.sets.contains_key(type_id) || !self.savers.contains_key(type_id) {
181 continue;
182 }
183 let Some(saver) = self.savers.get(type_id) else {
184 continue;
185 };
186 let Some(set) = self.sets.get_mut(type_id) else {
187 continue;
188 };
189 let Some(meta) = configured_metas
190 .get(type_id)
191 .or_else(|| self.entity_metas.get(type_id))
192 else {
193 return self
194 .fail_save(
195 txn,
196 &save_ctx,
197 savepoint.as_deref(),
198 EFError::configuration(format!(
199 "entity metadata not found for {:?}",
200 type_id
201 )),
202 )
203 .await;
204 };
205 let inserted = match saver
206 .insert_added(
207 txn.conn(),
208 &*self.provider,
209 set.as_mut(),
210 &self.change_tracker,
211 meta,
212 )
213 .await
214 {
215 Ok(n) => n,
216 Err(e) => {
217 return self
218 .fail_save(txn, &save_ctx, savepoint.as_deref(), e)
219 .await
220 }
221 };
222 total_added += inserted;
223
224 let link_indices: Vec<usize> = fixup_links
225 .iter()
226 .enumerate()
227 .filter(|(_, l)| l.parent_type_id == *type_id && l.through_table.is_none())
228 .map(|(i, _)| i)
229 .collect();
230
231 let mut self_ref_updates: Vec<(String, i64, i64)> = Vec::new();
232
233 for link_idx in &link_indices {
234 let link = &fixup_links[*link_idx];
235 let parent_pk = {
236 let Some(parent_saver) = self.savers.get(&link.parent_type_id) else {
237 continue;
238 };
239 let Some(parent_set) = self.sets.get(&link.parent_type_id) else {
240 continue;
241 };
242 parent_saver.get_pk_at(parent_set.as_ref(), link.parent_entry_idx)
243 };
244 let Some(pk) = parent_pk else {
245 continue;
246 };
247
248 {
249 let Some(child_saver) = self.savers.get(&link.child_type_id) else {
250 continue;
251 };
252 let Some(child_set) = self.sets.get_mut(&link.child_type_id) else {
253 continue;
254 };
255 for &child_idx in &link.child_entry_indices {
256 child_saver.set_fk_at(
257 child_set.as_mut(),
258 child_idx,
259 link.fk_target_type_id,
260 pk,
261 );
262 }
263 }
264
265 if link.child_type_id == link.parent_type_id {
266 let Some(child_meta) = configured_metas
267 .get(&link.child_type_id)
268 .or_else(|| self.entity_metas.get(&link.child_type_id))
269 else {
270 continue;
271 };
272 let fk_col = child_meta
273 .properties
274 .iter()
275 .find(|p| p.is_foreign_key)
276 .map(|p| p.column_name.as_ref());
277 let pk_col = child_meta
278 .properties
279 .iter()
280 .find(|p| p.is_primary_key)
281 .map(|p| p.column_name.as_ref())
282 .unwrap_or("id");
283 if let Some(fk_col) = fk_col {
284 for &child_idx in &link.child_entry_indices {
285 let child_pk = {
286 let Some(child_saver) = self.savers.get(&link.child_type_id) else {
287 continue;
288 };
289 let Some(child_set) = self.sets.get(&link.child_type_id) else {
290 continue;
291 };
292 child_saver.get_pk_at(child_set.as_ref(), child_idx)
293 };
294 if let Some(child_pk) = child_pk {
295 let sql = format!(
296 "UPDATE {} SET {} = ? WHERE {} = ?",
297 child_meta.table_name, fk_col, pk_col
298 );
299 self_ref_updates.push((sql, pk, child_pk));
300 }
301 }
302 }
303 }
304 }
305
306 for (sql, fk_val, pk_val) in self_ref_updates {
307 if let Err(e) = txn
308 .conn()
309 .execute(&sql, &[DbValue::from(fk_val), DbValue::from(pk_val)])
310 .await
311 {
312 return self
313 .fail_save(txn, &save_ctx, savepoint.as_deref(), e)
314 .await;
315 }
316 }
317 }
318
319 for link in &fixup_links {
321 if link.through_table.is_none() {
322 continue;
323 }
324 let (Some(table), Some(parent_col), Some(child_col)) = (
325 link.through_table.as_ref(),
326 link.through_parent_fk_col.as_ref(),
327 link.through_child_fk_col.as_ref(),
328 ) else {
329 continue;
330 };
331
332 let parent_pk = {
333 let Some(parent_saver) = self.savers.get(&link.parent_type_id) else {
334 continue;
335 };
336 let Some(parent_set) = self.sets.get(&link.parent_type_id) else {
337 continue;
338 };
339 parent_saver.get_pk_at(parent_set.as_ref(), link.parent_entry_idx)
340 };
341 let Some(parent_pk) = parent_pk else {
342 continue;
343 };
344
345 let mut child_pks: Vec<i64> = Vec::new();
346 {
347 let Some(child_saver) = self.savers.get(&link.child_type_id) else {
348 continue;
349 };
350 let Some(child_set) = self.sets.get(&link.child_type_id) else {
351 continue;
352 };
353 for &child_idx in &link.child_entry_indices {
354 if let Some(child_pk) = child_saver.get_pk_at(child_set.as_ref(), child_idx) {
355 child_pks.push(child_pk);
356 }
357 }
358 }
359
360 if !child_pks.is_empty() {
361 let sql = cascade::m2m_insert_sql(table, parent_col, child_col, child_pks.len());
362 let mut params: Vec<DbValue> = Vec::with_capacity(child_pks.len() * 2);
363 for child_pk in &child_pks {
364 params.push(DbValue::from(parent_pk));
365 params.push(DbValue::from(*child_pk));
366 }
367 if let Err(e) = txn.conn().execute(&sql, ¶ms).await {
368 return self
369 .fail_save(txn, &save_ctx, savepoint.as_deref(), e)
370 .await;
371 }
372 total_added += child_pks.len();
373 }
374 }
375
376 for type_id in &insert_order {
378 if !self.sets.contains_key(type_id) || !self.savers.contains_key(type_id) {
379 continue;
380 }
381 let Some(saver) = self.savers.get(type_id) else {
382 continue;
383 };
384 let Some(set) = self.sets.get_mut(type_id) else {
385 continue;
386 };
387 let Some(meta) = configured_metas
388 .get(type_id)
389 .or_else(|| self.entity_metas.get(type_id))
390 else {
391 return self
392 .fail_save(
393 txn,
394 &save_ctx,
395 savepoint.as_deref(),
396 EFError::configuration(format!(
397 "entity metadata not found for {:?}",
398 type_id
399 )),
400 )
401 .await;
402 };
403 let n = match saver
404 .upsert_added(
405 txn.conn(),
406 &*self.provider,
407 set.as_mut(),
408 &self.change_tracker,
409 meta,
410 )
411 .await
412 {
413 Ok(n) => n,
414 Err(e) => {
415 return self
416 .fail_save(txn, &save_ctx, savepoint.as_deref(), e)
417 .await
418 }
419 };
420 total_added += n;
421 }
422
423 for type_id in &insert_order {
425 if !self.sets.contains_key(type_id) || !self.savers.contains_key(type_id) {
426 continue;
427 }
428 let Some(saver) = self.savers.get(type_id) else {
429 continue;
430 };
431 let Some(set) = self.sets.get_mut(type_id) else {
432 continue;
433 };
434 let Some(meta) = configured_metas
435 .get(type_id)
436 .or_else(|| self.entity_metas.get(type_id))
437 else {
438 return self
439 .fail_save(
440 txn,
441 &save_ctx,
442 savepoint.as_deref(),
443 EFError::configuration(format!(
444 "entity metadata not found for {:?}",
445 type_id
446 )),
447 )
448 .await;
449 };
450 let n = match saver
451 .update_modified(
452 txn.conn(),
453 &*self.provider,
454 set.as_mut(),
455 &self.change_tracker,
456 meta,
457 )
458 .await
459 {
460 Ok(n) => n,
461 Err(e) => {
462 return self
463 .fail_save(txn, &save_ctx, savepoint.as_deref(), e)
464 .await
465 }
466 };
467 total_updated += n;
468 }
469
470 for directive in &delete_directives {
472 if directive.action != CascadeDeleteAction::SetNull {
473 continue;
474 }
475 let sql = format!(
476 "UPDATE {} SET {} = NULL WHERE {} = ?",
477 directive.table, directive.fk_column, directive.fk_column
478 );
479 let params = vec![DbValue::from(directive.principal_pk)];
480 if let Err(e) = txn.conn().execute(&sql, ¶ms).await {
481 return self
482 .fail_save(txn, &save_ctx, savepoint.as_deref(), e)
483 .await;
484 }
485 }
486
487 for type_id in &delete_order {
489 if !self.sets.contains_key(type_id) || !self.savers.contains_key(type_id) {
490 continue;
491 }
492 let Some(saver) = self.savers.get(type_id) else {
493 continue;
494 };
495 let Some(set) = self.sets.get_mut(type_id) else {
496 continue;
497 };
498 let Some(meta) = configured_metas
499 .get(type_id)
500 .or_else(|| self.entity_metas.get(type_id))
501 else {
502 return self
503 .fail_save(
504 txn,
505 &save_ctx,
506 savepoint.as_deref(),
507 EFError::configuration(format!(
508 "entity metadata not found for {:?}",
509 type_id
510 )),
511 )
512 .await;
513 };
514 let n = match saver
515 .delete_deleted(
516 txn.conn(),
517 &*self.provider,
518 set.as_mut(),
519 &self.change_tracker,
520 meta,
521 )
522 .await
523 {
524 Ok(n) => n,
525 Err(e) => {
526 return self
527 .fail_save(txn, &save_ctx, savepoint.as_deref(), e)
528 .await
529 }
530 };
531 total_deleted += n;
532 }
533
534 for directive in &delete_directives {
536 if directive.action != CascadeDeleteAction::Delete {
537 continue;
538 }
539 let sql = format!(
540 "DELETE FROM {} WHERE {} = ?",
541 directive.table, directive.fk_column
542 );
543 let params = vec![DbValue::from(directive.principal_pk)];
544 if let Err(e) = txn.conn().execute(&sql, ¶ms).await {
545 return self
546 .fail_save(txn, &save_ctx, savepoint.as_deref(), e)
547 .await;
548 }
549 }
550
551 if let Some(name) = savepoint.as_deref() {
553 let _ = txn.conn().release_savepoint(name).await;
557 }
558 match txn {
559 TxnSource::Ambient(t) => {
560 self.ambient_transaction = Some(t);
561 }
562 TxnSource::Managed(mut conn) => {
563 if let Err(e) = conn.commit_transaction().await {
564 self.interceptor_pipeline
565 .on_save_failed(&save_ctx, &e)
566 .await;
567 return Err(e);
568 }
569 }
570 }
571 for type_id in &type_ids {
573 let Some(saver) = self.savers.get(type_id) else {
574 continue;
575 };
576 let Some(set) = self.sets.get_mut(type_id) else {
577 continue;
578 };
579 saver.accept_all_changes(set.as_mut(), &mut self.change_tracker);
580 }
581
582 let result_ctx = SaveChangesResultContext {
583 added: total_added,
584 updated: total_updated,
585 deleted: total_deleted,
586 };
587 self.interceptor_pipeline
588 .on_saved(&save_ctx, &result_ctx)
589 .await?;
590
591 Ok(SaveChangesResult {
592 added: total_added,
593 updated: total_updated,
594 deleted: total_deleted,
595 })
596 }
597
598 async fn fail_save(
602 &mut self,
603 txn: TxnSource,
604 save_ctx: &SaveChangesContext,
605 savepoint: Option<&str>,
606 e: EFError,
607 ) -> EFResult<SaveChangesResult> {
608 match txn {
609 TxnSource::Managed(mut conn) => {
610 let _ = conn.rollback_transaction().await;
611 }
612 TxnSource::Ambient(mut t) => {
613 if let Some(name) = savepoint {
614 let _ = t.connection().rollback_to_savepoint(name).await;
615 let _ = t.connection().release_savepoint(name).await;
616 }
617 self.ambient_transaction = Some(t);
618 }
619 }
620 self.interceptor_pipeline.on_save_failed(save_ctx, &e).await;
621 Err(e)
622 }
623}