teaql_runtime/
entity_save.rs1use std::collections::BTreeSet;
2use std::future::Future;
3use std::marker::PhantomData;
4use std::pin::Pin;
5use std::sync::Arc;
6
7use teaql_core::{Entity, Record, Value};
8
9use crate::{DataServiceError, GraphNode, GraphOperation, RuntimeError, UserContext};
10
11pub(crate) trait DynGraphSaver: Send + Sync {
21 fn save_graph_dyn<'a>(
22 &'a self,
23 ctx: &'a UserContext,
24 node: GraphNode,
25 ) -> Pin<Box<dyn Future<Output = Result<GraphNode, RuntimeError>> + Send + 'a>>;
26
27 fn save_ledger_dyn<'a>(
28 &'a self,
29 ctx: &'a UserContext,
30 node: GraphNode,
31 root: crate::EntityRoot,
32 ) -> Pin<Box<dyn Future<Output = Result<GraphNode, RuntimeError>> + Send + 'a>>;
33}
34
35pub(crate) struct GraphSaverFor<E> {
41 _marker: PhantomData<fn() -> E>,
42}
43
44impl<E> GraphSaverFor<E> {
45 pub(crate) fn new() -> Self {
46 Self {
47 _marker: PhantomData,
48 }
49 }
50}
51
52impl<E> DynGraphSaver for GraphSaverFor<E>
53where
54 E: teaql_data_service::QueryExecutor
55 + teaql_data_service::MutationExecutor
56 + Send
57 + Sync
58 + 'static,
59{
60 fn save_graph_dyn<'a>(
61 &'a self,
62 ctx: &'a UserContext,
63 node: GraphNode,
64 ) -> Pin<Box<dyn Future<Output = Result<GraphNode, RuntimeError>> + Send + 'a>> {
65 Box::pin(async move {
66 let entity = node.entity.clone();
67 let eds = ctx
68 .entity_data_service::<E>(entity)
69 .map_err(|e| RuntimeError::Graph(e.to_string()))?;
70 eds.save_graph_internal(node).await.map_err(|e| match e {
71 DataServiceError::Runtime(r) => r,
72 other => RuntimeError::Graph(other.to_string()),
73 })
74 })
75 }
76
77 fn save_ledger_dyn<'a>(
78 &'a self,
79 ctx: &'a UserContext,
80 mut node: GraphNode,
81 root: crate::EntityRoot,
82 ) -> Pin<Box<dyn Future<Output = Result<GraphNode, RuntimeError>> + Send + 'a>> {
83 Box::pin(async move {
84 let entity = node.entity.clone();
85 let eds = ctx
86 .entity_data_service::<E>(&entity)
87 .map_err(|e| RuntimeError::Graph(e.to_string()))?;
88 let generated_ids = eds.execute_ledger_plan_internal(root)
89 .await
90 .map_err(|e| match e {
91 DataServiceError::Runtime(r) => r,
92 other => RuntimeError::Graph(other.to_string()),
93 })?;
94
95 let descriptor = ctx.require_entity(&entity).unwrap();
96 if let Some(id_prop) = descriptor.id_property() {
97 let current_id = node.values.get(&id_prop.name).cloned().unwrap_or(Value::I64(0));
98 let root_key = crate::EntityKey::new(entity.clone(), current_id);
99 if let Some(new_id) = generated_ids.get(&root_key) {
100 node.values.insert(id_prop.name.clone(), new_id.clone());
101 }
102 }
103 Ok(node)
104 })
105 }
106}
107
108pub fn graph_node_from_entity<T: Entity>(
118 ctx: &UserContext,
119 entity: T,
120) -> Result<GraphNode, RuntimeError> {
121 let descriptor = T::entity_descriptor();
122 let dirty_fields = entity.dirty_fields();
123 let original_values = entity.original_values();
124 let is_deleted = entity.is_marked_as_delete();
125 let comment = entity.get_comment();
126 let mut node = graph_node_from_record(ctx, &descriptor.name, entity.into_record())?;
127 node.dirty_fields = dirty_fields;
128 node.original_values = original_values;
129 if is_deleted {
130 node.operation = GraphOperation::Remove;
131 node.relations.clear();
132 }
133 if let Some(c) = comment {
134 node.set_comment(c);
135 }
136 Ok(node)
137}
138
139fn graph_node_from_record(
143 ctx: &UserContext,
144 entity: &str,
145 record: Record,
146) -> Result<GraphNode, RuntimeError> {
147 let descriptor = ctx.require_entity(entity)?;
148 let mut node = GraphNode::new(entity);
149
150 for (field, value) in record {
151 if field == "_comment" {
152 if let Value::Text(comment) = value {
153 node.set_comment(comment);
154 }
155 continue;
156 }
157 if field == "_dirty_fields" {
158 if let Value::List(fields) = value {
159 let mut dirty = BTreeSet::new();
160 for f in fields {
161 if let Value::Text(t) = f {
162 dirty.insert(t);
163 }
164 }
165 node.dirty_fields = Some(dirty);
166 }
167 continue;
168 }
169 if field == "_original_values" {
170 if let Value::Object(orig) = value {
171 node.original_values = Some(orig);
172 }
173 continue;
174 }
175 let Some(relation) = descriptor.relation_by_name(&field) else {
176 node.values.insert(field, value);
177 continue;
178 };
179
180 match value {
181 Value::Null => {
182 node.relations.entry(field).or_default();
183 }
184 Value::Object(record) => {
185 let child = graph_node_from_record(ctx, &relation.target_entity, record)?;
186 node.relations.entry(field).or_default().push(child);
187 }
188 Value::List(values) => {
189 let children = node.relations.entry(field.clone()).or_default();
190 for value in values {
191 let Value::Object(record) = value else {
192 return Err(RuntimeError::Graph(format!(
193 "relation {}.{} expects object children, got {:?}",
194 entity, field, value
195 )));
196 };
197 children.push(graph_node_from_record(
198 ctx,
199 &relation.target_entity,
200 record,
201 )?);
202 }
203 }
204 other => {
205 return Err(RuntimeError::Graph(format!(
206 "relation {}.{} expects object/list/null, got {:?}",
207 entity, field, other
208 )));
209 }
210 }
211 }
212
213 Ok(node)
214}
215
216pub trait AuditedSaveExt {
229 fn save<'a>(
230 self,
231 ctx: &'a UserContext,
232 ) -> Pin<Box<dyn Future<Output = Result<GraphNode, RuntimeError>> + Send + 'a>>;
233}
234
235impl<T> AuditedSaveExt for teaql_core::Audited<T>
236where
237 T: Entity + Send + 'static,
238{
239 fn save<'a>(
240 self,
241 ctx: &'a UserContext,
242 ) -> Pin<Box<dyn Future<Output = Result<GraphNode, RuntimeError>> + Send + 'a>> {
243 Box::pin(async move {
244 let entity_name = T::entity_descriptor().name;
245 let entity = self.into_entity(); let node = graph_node_from_entity(ctx, entity)?;
247 let saver = ctx
248 .require_resource::<Arc<dyn DynGraphSaver>>()
249 .map_err(|e| {
250 RuntimeError::Graph(format!(
251 "no DynGraphSaver registered — did you call register_executor()? ({})",
252 e
253 ))
254 })?;
255 saver.save_graph_dyn(ctx, node).await
256 })
257 }
258}
259
260#[doc(hidden)]
267pub async fn save_audited_ledger_entity<T>(
268 audited: teaql_core::Audited<T>,
269 ctx: &UserContext,
270) -> Result<GraphNode, RuntimeError>
271where
272 T: crate::LedgerEntity + Send + 'static,
273{
274 let entity_name = T::entity_descriptor().name;
275 let entity = audited.into_entity();
276 let root = entity.entity_root();
277 let node = graph_node_from_entity(ctx, entity)?;
278 let saver = ctx
279 .require_resource::<Arc<dyn DynGraphSaver>>()
280 .map_err(|e| {
281 RuntimeError::Graph(format!(
282 "no DynGraphSaver registered — did you call register_executor()? ({e})"
283 ))
284 })?;
285
286 if let Some(root) = root {
287 let has_ledger_changes = !root.current_change_set().changes().is_empty()
288 || !root.deleted_keys().is_empty()
289 || !root.new_keys().is_empty();
290 if has_ledger_changes {
291 return saver.save_ledger_dyn(ctx, node, root).await;
292 }
293 }
294
295 saver.save_graph_dyn(ctx, node).await
296}