1use std::collections::BTreeMap;
2
3use teaql_core::{
4 DeleteCommand, Entity, EntityDescriptor, Expr, InsertCommand, PropertyDescriptor, Record,
5 SelectQuery, UpdateCommand, Value,
6};
7use teaql_sql::SqlDialect;
8
9use crate::{
10 GraphMutationKind, GraphMutationPlan, GraphNode, GraphOperation, RepositoryError, RuntimeError,
11 sorted_update_fields,
12};
13
14use super::{GraphTransactionBoundary, QueryExecutor, ResolvedRepository, helpers::*};
15
16impl<'a, D, E> ResolvedRepository<'a, D, E>
17where
18 D: SqlDialect,
19 E: QueryExecutor,
20{
21 pub fn save_graph(&self, node: GraphNode) -> Result<GraphNode, RepositoryError<E::Error>> {
22 if node.entity != self.entity {
23 return Err(RepositoryError::Runtime(RuntimeError::Graph(format!(
24 "resolved repository {} cannot save graph root {}",
25 self.entity, node.entity
26 ))));
27 }
28 let boundary = self
29 .repository
30 .executor
31 .begin_transaction()
32 .map_err(RepositoryError::Executor)?;
33 if matches!(boundary, GraphTransactionBoundary::Unsupported) {
34 return Err(RepositoryError::Runtime(RuntimeError::Graph(
35 "save_graph requires a transactional executor".to_owned(),
36 )));
37 }
38 let result = self
39 .plan_graph(node)
40 .and_then(|plan| self.execute_graph_plan(plan));
41 match result {
42 Ok(saved) => {
43 if matches!(boundary, GraphTransactionBoundary::Started) {
44 self.repository
45 .executor
46 .commit_transaction()
47 .map_err(RepositoryError::Executor)?;
48 }
49 Ok(saved)
50 }
51 Err(err) => {
52 if !matches!(boundary, GraphTransactionBoundary::Unsupported) {
53 self.repository
54 .executor
55 .rollback_transaction()
56 .map_err(RepositoryError::Executor)?;
57 }
58 Err(err)
59 }
60 }
61 }
62
63 pub fn save_entity_graph<T>(&self, entity: T) -> Result<GraphNode, RepositoryError<E::Error>>
64 where
65 T: Entity,
66 {
67 let node = self
68 .graph_node_from_entity(entity)
69 .map_err(RepositoryError::Runtime)?;
70 self.save_graph(node)
71 }
72
73 pub fn plan_graph(
74 &self,
75 node: GraphNode,
76 ) -> Result<GraphMutationPlan, RepositoryError<E::Error>> {
77 if node.entity != self.entity {
78 return Err(RepositoryError::Runtime(RuntimeError::Graph(format!(
79 "resolved repository {} cannot plan graph root {}",
80 self.entity, node.entity
81 ))));
82 }
83 let mut node = node;
84 let mut plan = GraphMutationPlan::default();
85 self.collect_graph_plan(&mut node, &mut plan)?;
86 plan.planned_root = Some(node);
87 plan.rebuild_batches();
88 Ok(plan)
89 }
90
91 pub fn execute_graph_plan(
92 &self,
93 plan: GraphMutationPlan,
94 ) -> Result<GraphNode, RepositoryError<E::Error>> {
95 let Some(root) = plan.planned_root else {
96 return Err(RepositoryError::Runtime(RuntimeError::Graph(
97 "graph mutation plan has no planned root".to_owned(),
98 )));
99 };
100 self.upsert_graph_node(root)
101 }
102
103 pub fn graph_node_from_entity<T>(&self, entity: T) -> Result<GraphNode, RuntimeError>
104 where
105 T: Entity,
106 {
107 let descriptor = T::entity_descriptor();
108 if descriptor.name != self.entity {
109 return Err(RuntimeError::Graph(format!(
110 "resolved repository {} cannot extract graph root {}",
111 self.entity, descriptor.name
112 )));
113 }
114 self.graph_node_from_record(&descriptor.name, entity.into_record())
115 }
116
117 fn collect_graph_plan(
118 &self,
119 node: &mut GraphNode,
120 plan: &mut GraphMutationPlan,
121 ) -> Result<(), RepositoryError<E::Error>> {
122 match node.operation {
123 GraphOperation::Reference => {
124 plan.push(
125 node.entity.clone(),
126 GraphMutationKind::Reference,
127 node.values.clone(),
128 Vec::new(),
129 );
130 return Ok(());
131 }
132 GraphOperation::Remove => {
133 plan.push(
134 node.entity.clone(),
135 GraphMutationKind::Delete,
136 node.values.clone(),
137 Vec::new(),
138 );
139 return Ok(());
140 }
141 GraphOperation::Upsert => {}
142 }
143
144 let descriptor = self
145 .repository
146 .metadata
147 .context
148 .require_entity(&node.entity)
149 .map_err(RepositoryError::Runtime)?;
150 let id_property = descriptor.id_property().cloned();
151 let id = id_property.as_ref().and_then(|property| {
152 node.values
153 .get(&property.name)
154 .filter(|value| !is_unassigned_id_value(value))
155 .cloned()
156 });
157 let is_update = match (id_property.as_ref(), id.as_ref()) {
158 (Some(id_property), Some(id)) => self
159 .fetch_graph_current_row(&node.entity, &id_property.name, id)?
160 .is_some(),
161 _ => false,
162 };
163 if !is_update {
164 if let Some(id_property) = id_property.as_ref() {
165 let needs_id = !node.values.contains_key(&id_property.name)
166 || node
167 .values
168 .get(&id_property.name)
169 .is_some_and(is_unassigned_id_value);
170 if needs_id {
171 let id = self
172 .repository
173 .metadata
174 .context
175 .next_id(&node.entity)
176 .map_err(RepositoryError::Runtime)?;
177 node.values.insert(id_property.name.clone(), Value::U64(id));
178 }
179 }
180 }
181 let update_fields = if is_update {
182 let mut excluded = Vec::new();
183 if let Some(id_property) = id_property.as_ref() {
184 excluded.push(id_property.name.clone());
185 }
186 if let Some(version_property) = descriptor.version_property() {
187 excluded.push(version_property.name.clone());
188 }
189 sorted_update_fields(&node.values, excluded)
190 } else {
191 Vec::new()
192 };
193 plan.push(
194 node.entity.clone(),
195 if is_update {
196 GraphMutationKind::Update
197 } else {
198 GraphMutationKind::Create
199 },
200 node.values.clone(),
201 update_fields,
202 );
203
204 for (name, children) in &mut node.relations {
205 let relation = descriptor.relation_by_name(name).ok_or_else(|| {
206 RepositoryError::Runtime(RuntimeError::MissingRelation {
207 entity: node.entity.clone(),
208 relation: name.clone(),
209 })
210 })?;
211 let child_repo = self.scoped_repository(relation.target_entity.clone());
212 for child in children {
213 ensure_relation_target(&node.entity, name, &relation.target_entity, child)?;
214 child_repo.collect_graph_plan(child, plan)?;
215 }
216 }
217 Ok(())
218 }
219
220 fn insert_graph_node(
221 &self,
222 mut node: GraphNode,
223 ) -> Result<GraphNode, RepositoryError<E::Error>> {
224 match node.operation {
225 GraphOperation::Upsert => {}
226 GraphOperation::Reference => return self.validate_reference_node(node),
227 GraphOperation::Remove => {
228 return Err(RepositoryError::Runtime(RuntimeError::Graph(format!(
229 "create graph cannot remove node {}",
230 node.entity
231 ))));
232 }
233 }
234
235 let descriptor = self
236 .repository
237 .metadata
238 .context
239 .require_entity(&node.entity)
240 .map_err(RepositoryError::Runtime)?;
241
242 let mut one_relations = Vec::new();
243 let mut many_relations = Vec::new();
244 for (name, children) in std::mem::take(&mut node.relations) {
245 let relation = descriptor.relation_by_name(&name).ok_or_else(|| {
246 RepositoryError::Runtime(RuntimeError::MissingRelation {
247 entity: node.entity.clone(),
248 relation: name.clone(),
249 })
250 })?;
251 if relation.many {
252 many_relations.push((name, relation.clone(), children));
253 } else {
254 one_relations.push((name, relation.clone(), children));
255 }
256 }
257
258 for (name, relation, children) in one_relations {
259 if children.len() > 1 {
260 return Err(RepositoryError::Runtime(RuntimeError::Graph(format!(
261 "relation {}.{} expects one child, got {}",
262 node.entity,
263 name,
264 children.len()
265 ))));
266 }
267 let mut saved_children = Vec::new();
268 for child in children {
269 ensure_relation_target(&node.entity, &name, &relation.target_entity, &child)?;
270 let child_repo = self.scoped_repository(child.entity.clone());
271 let saved_child = child_repo.insert_graph_node(child)?;
272 if relation.attach {
273 let foreign_value = saved_child
274 .values
275 .get(&relation.foreign_key)
276 .cloned()
277 .ok_or_else(|| {
278 RepositoryError::Runtime(RuntimeError::Graph(format!(
279 "saved child {} missing foreign key {} for relation {}.{}",
280 relation.target_entity, relation.foreign_key, node.entity, name
281 )))
282 })?;
283 node.values
284 .insert(relation.local_key.clone(), foreign_value);
285 }
286 saved_children.push(saved_child);
287 }
288 node.relations.insert(name, saved_children);
289 }
290
291 let command = self
292 .prepare_insert_command(&InsertCommand {
293 entity: node.entity.clone(),
294 values: node.values.clone(),
295 })
296 .map_err(RepositoryError::Runtime)?;
297 self.execute_prepared_insert(command.clone())?;
298 node.values = command.values;
299
300 for (name, relation, children) in many_relations {
301 let local_value = node
302 .values
303 .get(&relation.local_key)
304 .cloned()
305 .ok_or_else(|| {
306 RepositoryError::Runtime(RuntimeError::Graph(format!(
307 "parent {} missing local key {} for relation {}",
308 node.entity, relation.local_key, name
309 )))
310 })?;
311 let mut saved_children = Vec::new();
312 for mut child in children {
313 ensure_relation_target(&node.entity, &name, &relation.target_entity, &child)?;
314 if relation.attach {
315 child
316 .values
317 .insert(relation.foreign_key.clone(), local_value.clone());
318 }
319 let child_repo = self.scoped_repository(child.entity.clone());
320 saved_children.push(child_repo.insert_graph_node(child)?);
321 }
322 node.relations.insert(name, saved_children);
323 }
324
325 Ok(node)
326 }
327
328 fn upsert_graph_node(
329 &self,
330 mut node: GraphNode,
331 ) -> Result<GraphNode, RepositoryError<E::Error>> {
332 match node.operation {
333 GraphOperation::Upsert => {}
334 GraphOperation::Reference => return self.validate_reference_node(node),
335 GraphOperation::Remove => {
336 self.validate_remove_node(&node)?;
337 self.delete_graph_node(&node)?;
338 return Ok(node);
339 }
340 }
341
342 let descriptor = self
343 .repository
344 .metadata
345 .context
346 .require_entity(&node.entity)
347 .map_err(RepositoryError::Runtime)?;
348 let Some(id_property) = descriptor.id_property() else {
349 return Err(RepositoryError::Runtime(RuntimeError::Graph(format!(
350 "entity {} has no id property for graph upsert",
351 node.entity
352 ))));
353 };
354 let Some(id) = node
355 .values
356 .get(&id_property.name)
357 .filter(|value| !matches!(value, Value::Null))
358 .cloned()
359 else {
360 return self.insert_graph_node(node);
361 };
362
363 if self
364 .fetch_graph_current_row(&node.entity, &id_property.name, &id)?
365 .is_none()
366 {
367 return self.insert_graph_node(node);
368 }
369
370 let mut one_relations = Vec::new();
371 let mut many_relations = Vec::new();
372 for (name, children) in std::mem::take(&mut node.relations) {
373 let relation = descriptor.relation_by_name(&name).ok_or_else(|| {
374 RepositoryError::Runtime(RuntimeError::MissingRelation {
375 entity: node.entity.clone(),
376 relation: name.clone(),
377 })
378 })?;
379 if relation.many {
380 many_relations.push((name, relation.clone(), children));
381 } else {
382 one_relations.push((name, relation.clone(), children));
383 }
384 }
385
386 for (name, relation, children) in one_relations {
387 if children.len() > 1 {
388 return Err(RepositoryError::Runtime(RuntimeError::Graph(format!(
389 "relation {}.{} expects one child, got {}",
390 node.entity,
391 name,
392 children.len()
393 ))));
394 }
395 let mut saved_children = Vec::new();
396 for child in children {
397 ensure_relation_target(&node.entity, &name, &relation.target_entity, &child)?;
398 let child_repo = self.scoped_repository(child.entity.clone());
399 let saved_child = child_repo.upsert_graph_node(child)?;
400 if relation.attach {
401 let foreign_value = saved_child
402 .values
403 .get(&relation.foreign_key)
404 .cloned()
405 .ok_or_else(|| {
406 RepositoryError::Runtime(RuntimeError::Graph(format!(
407 "saved child {} missing foreign key {} for relation {}.{}",
408 relation.target_entity, relation.foreign_key, node.entity, name
409 )))
410 })?;
411 node.values
412 .insert(relation.local_key.clone(), foreign_value);
413 }
414 saved_children.push(saved_child);
415 }
416 node.relations.insert(name, saved_children);
417 }
418
419 let update = self.graph_update_command(&node, descriptor, id_property, &id);
420 if !update.values.is_empty() || update.expected_version.is_some() {
421 let prepared_update = self
422 .prepare_update_command(&update)
423 .map_err(RepositoryError::Runtime)?;
424 self.execute_prepared_update(prepared_update.clone())?;
425 for (field, value) in &prepared_update.values {
426 node.values.insert(field.clone(), value.clone());
427 }
428 if let Some(version_property) = descriptor.version_property() {
429 if let Some(expected_version) = prepared_update.expected_version {
430 node.values.insert(
431 version_property.name.clone(),
432 Value::I64(expected_version + 1),
433 );
434 }
435 }
436 }
437
438 for (name, relation, children) in many_relations {
439 let local_value = node
440 .values
441 .get(&relation.local_key)
442 .cloned()
443 .ok_or_else(|| {
444 RepositoryError::Runtime(RuntimeError::Graph(format!(
445 "parent {} missing local key {} for relation {}",
446 node.entity, relation.local_key, name
447 )))
448 })?;
449 let child_repo = self.scoped_repository(relation.target_entity.clone());
450 let existing_children = child_repo.fetch_graph_children(
451 &relation.target_entity,
452 &relation.foreign_key,
453 &local_value,
454 )?;
455 let child_descriptor = self
456 .repository
457 .metadata
458 .context
459 .require_entity(&relation.target_entity)
460 .map_err(RepositoryError::Runtime)?;
461 let child_id_property = child_descriptor.id_property().ok_or_else(|| {
462 RepositoryError::Runtime(RuntimeError::Graph(format!(
463 "entity {} has no id property for graph diff",
464 relation.target_entity
465 )))
466 })?;
467 let mut existing_by_id = BTreeMap::new();
468 for child in existing_children {
469 if let Some(id) = child.get(&child_id_property.name) {
470 existing_by_id.insert(graph_identity_key(id), child);
471 }
472 }
473
474 let mut seen = std::collections::BTreeSet::new();
475 let mut saved_children = Vec::new();
476 for mut child in children {
477 ensure_relation_target(&node.entity, &name, &relation.target_entity, &child)?;
478 if relation.attach && child.operation != GraphOperation::Reference {
479 child
480 .values
481 .insert(relation.foreign_key.clone(), local_value.clone());
482 }
483 if let Some(child_id) = child
484 .values
485 .get(&child_id_property.name)
486 .filter(|value| !is_unassigned_id_value(value))
487 {
488 let key = graph_identity_key(child_id);
489 if !seen.insert(key.clone()) {
490 return Err(RepositoryError::Runtime(RuntimeError::Graph(format!(
491 "duplicate child id {key} in relation {}.{}",
492 node.entity, name
493 ))));
494 }
495 }
496 saved_children.push(child_repo.upsert_graph_node(child)?);
497 }
498
499 if relation.delete_missing {
500 for (id_key, existing) in existing_by_id {
501 if seen.contains(&id_key) {
502 continue;
503 }
504 let Some(existing_id) = existing.get(&child_id_property.name).cloned() else {
505 continue;
506 };
507 let mut delete =
508 DeleteCommand::new(relation.target_entity.clone(), existing_id);
509 if let Some(version) = graph_record_version(&existing, child_descriptor) {
510 delete = delete.expected_version(version);
511 }
512 child_repo.delete(&delete)?;
513 }
514 }
515
516 node.relations.insert(name, saved_children);
517 }
518
519 Ok(node)
520 }
521
522 fn validate_reference_node(
523 &self,
524 node: GraphNode,
525 ) -> Result<GraphNode, RepositoryError<E::Error>> {
526 if !node.relations.is_empty() {
527 return Err(RepositoryError::Runtime(RuntimeError::Graph(format!(
528 "reference node {} cannot contain child relations",
529 node.entity
530 ))));
531 }
532 let descriptor = self
533 .repository
534 .metadata
535 .context
536 .require_entity(&node.entity)
537 .map_err(RepositoryError::Runtime)?;
538 let id_property = descriptor.id_property().ok_or_else(|| {
539 RepositoryError::Runtime(RuntimeError::Graph(format!(
540 "entity {} has no id property for graph reference",
541 node.entity
542 )))
543 })?;
544 let id = node
545 .values
546 .get(&id_property.name)
547 .filter(|value| !is_unassigned_id_value(value))
548 .cloned()
549 .ok_or_else(|| {
550 RepositoryError::Runtime(RuntimeError::Graph(format!(
551 "reference node {} missing id property {}",
552 node.entity, id_property.name
553 )))
554 })?;
555
556 for field in node.values.keys() {
557 if field == &id_property.name {
558 continue;
559 }
560 if descriptor
561 .version_property()
562 .map(|property| field == &property.name)
563 .unwrap_or(false)
564 {
565 continue;
566 }
567 return Err(RepositoryError::Runtime(RuntimeError::Graph(format!(
568 "reference node {} cannot carry mutable field {}",
569 node.entity, field
570 ))));
571 }
572
573 let current = self
574 .fetch_graph_current_row(&node.entity, &id_property.name, &id)?
575 .ok_or_else(|| {
576 RepositoryError::Runtime(RuntimeError::Graph(format!(
577 "reference node {}({}) does not exist",
578 node.entity,
579 graph_identity_key(&id)
580 )))
581 })?;
582
583 if let Some(version_property) = descriptor.version_property() {
584 if let Some(Value::I64(existing_version)) = current.get(&version_property.name) {
585 if *existing_version < 0 {
586 return Err(RepositoryError::Runtime(RuntimeError::Graph(format!(
587 "reference node {}({}) is deleted",
588 node.entity,
589 graph_identity_key(&id)
590 ))));
591 }
592 if let Some(Value::I64(expected_version)) = node.values.get(&version_property.name)
593 {
594 if expected_version != existing_version {
595 return Err(RepositoryError::Runtime(
596 RuntimeError::OptimisticLockConflict {
597 entity: node.entity,
598 id: graph_identity_key(&id),
599 },
600 ));
601 }
602 }
603 }
604 }
605
606 Ok(GraphNode {
607 entity: node.entity,
608 values: current,
609 relations: BTreeMap::new(),
610 operation: GraphOperation::Reference,
611 })
612 }
613
614 fn validate_remove_node(&self, node: &GraphNode) -> Result<(), RepositoryError<E::Error>> {
615 if !node.relations.is_empty() {
616 return Err(RepositoryError::Runtime(RuntimeError::Graph(format!(
617 "remove node {} cannot contain child relations",
618 node.entity
619 ))));
620 }
621 let descriptor = self
622 .repository
623 .metadata
624 .context
625 .require_entity(&node.entity)
626 .map_err(RepositoryError::Runtime)?;
627 let id_property = descriptor.id_property().ok_or_else(|| {
628 RepositoryError::Runtime(RuntimeError::Graph(format!(
629 "entity {} has no id property for graph remove",
630 node.entity
631 )))
632 })?;
633 let id = node
634 .values
635 .get(&id_property.name)
636 .filter(|value| !is_unassigned_id_value(value))
637 .cloned()
638 .ok_or_else(|| {
639 RepositoryError::Runtime(RuntimeError::Graph(format!(
640 "remove node {} missing id property {}",
641 node.entity, id_property.name
642 )))
643 })?;
644 let current = self
645 .fetch_graph_current_row(&node.entity, &id_property.name, &id)?
646 .ok_or_else(|| {
647 RepositoryError::Runtime(RuntimeError::Graph(format!(
648 "remove node {}({}) does not exist",
649 node.entity,
650 graph_identity_key(&id)
651 )))
652 })?;
653 if let Some(version_property) = descriptor.version_property() {
654 if let Some(Value::I64(existing_version)) = current.get(&version_property.name) {
655 if *existing_version < 0 {
656 return Err(RepositoryError::Runtime(RuntimeError::Graph(format!(
657 "remove node {}({}) is already deleted",
658 node.entity,
659 graph_identity_key(&id)
660 ))));
661 }
662 }
663 }
664 Ok(())
665 }
666
667 fn graph_node_from_record(
668 &self,
669 entity: &str,
670 record: Record,
671 ) -> Result<GraphNode, RuntimeError> {
672 let descriptor = self.repository.metadata.context.require_entity(entity)?;
673 let mut node = GraphNode::new(entity);
674
675 for (field, value) in record {
676 let Some(relation) = descriptor.relation_by_name(&field) else {
677 node.values.insert(field, value);
678 continue;
679 };
680
681 match value {
682 Value::Null => {
683 node.relations.entry(field).or_default();
684 }
685 Value::Object(record) => {
686 let child = self.graph_node_from_record(&relation.target_entity, record)?;
687 node.relations.entry(field).or_default().push(child);
688 }
689 Value::List(values) => {
690 let children = node.relations.entry(field.clone()).or_default();
691 for value in values {
692 let Value::Object(record) = value else {
693 return Err(RuntimeError::Graph(format!(
694 "relation {}.{} expects object children, got {:?}",
695 entity, field, value
696 )));
697 };
698 children
699 .push(self.graph_node_from_record(&relation.target_entity, record)?);
700 }
701 }
702 other => {
703 return Err(RuntimeError::Graph(format!(
704 "relation {}.{} expects object/list/null, got {:?}",
705 entity, field, other
706 )));
707 }
708 }
709 }
710
711 Ok(node)
712 }
713
714 fn graph_update_command(
715 &self,
716 node: &GraphNode,
717 descriptor: &EntityDescriptor,
718 id_property: &PropertyDescriptor,
719 id: &Value,
720 ) -> UpdateCommand {
721 let mut command = UpdateCommand::new(node.entity.clone(), id.clone());
722 if let Some(version_property) = descriptor.version_property() {
723 if let Some(Value::I64(version)) = node.values.get(&version_property.name) {
724 command = command.expected_version(*version);
725 }
726 }
727 for property in descriptor.properties.iter().filter(|property| {
728 !property.is_id && !property.is_version && node.values.contains_key(&property.name)
729 }) {
730 if property.name == id_property.name {
731 continue;
732 }
733 if let Some(value) = node.values.get(&property.name) {
734 command.values.insert(property.name.clone(), value.clone());
735 }
736 }
737 command
738 }
739
740 fn delete_graph_node(&self, node: &GraphNode) -> Result<u64, RepositoryError<E::Error>> {
741 let descriptor = self
742 .repository
743 .metadata
744 .context
745 .require_entity(&node.entity)
746 .map_err(RepositoryError::Runtime)?;
747 let id_property = descriptor.id_property().ok_or_else(|| {
748 RepositoryError::Runtime(RuntimeError::Graph(format!(
749 "entity {} has no id property for graph remove",
750 node.entity
751 )))
752 })?;
753 let id = node
754 .values
755 .get(&id_property.name)
756 .filter(|value| !is_unassigned_id_value(value))
757 .cloned()
758 .ok_or_else(|| {
759 RepositoryError::Runtime(RuntimeError::Graph(format!(
760 "remove node {} missing id property {}",
761 node.entity, id_property.name
762 )))
763 })?;
764 let mut delete = DeleteCommand::new(node.entity.clone(), id);
765 if let Some(version_property) = descriptor.version_property() {
766 if let Some(Value::I64(version)) = node.values.get(&version_property.name) {
767 delete = delete.expected_version(*version);
768 }
769 }
770 self.delete(&delete)
771 }
772
773 fn fetch_graph_current_row(
774 &self,
775 entity: &str,
776 id_property: &str,
777 id: &Value,
778 ) -> Result<Option<Record>, RepositoryError<E::Error>> {
779 let mut rows = self
780 .scoped_repository(entity.to_owned())
781 .fetch_all(&SelectQuery::new(entity).filter(Expr::eq(id_property, id.clone())))?;
782 Ok(rows.pop())
783 }
784
785 fn fetch_graph_children(
786 &self,
787 entity: &str,
788 foreign_key: &str,
789 parent_value: &Value,
790 ) -> Result<Vec<Record>, RepositoryError<E::Error>> {
791 self.scoped_repository(entity.to_owned()).fetch_all(
792 &SelectQuery::new(entity).filter(Expr::eq(foreign_key, parent_value.clone())),
793 )
794 }
795}
796
797fn is_unassigned_id_value(value: &Value) -> bool {
798 matches!(value, Value::Null | Value::U64(0) | Value::I64(0))
799}