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 entity: &'a str,
25 node: GraphNode,
26 ) -> Pin<Box<dyn Future<Output = Result<GraphNode, RuntimeError>> + Send + 'a>>;
27
28 fn save_ledger_dyn<'a>(
29 &'a self,
30 ctx: &'a UserContext,
31 entity: &'a str,
32 root: crate::EntityRoot,
33 ) -> Pin<Box<dyn Future<Output = Result<GraphNode, RuntimeError>> + Send + 'a>>;
34}
35
36pub(crate) struct GraphSaverFor<E> {
42 _marker: PhantomData<fn() -> E>,
43}
44
45impl<E> GraphSaverFor<E> {
46 pub(crate) fn new() -> Self {
47 Self {
48 _marker: PhantomData,
49 }
50 }
51}
52
53impl<E> DynGraphSaver for GraphSaverFor<E>
54where
55 E: teaql_data_service::QueryExecutor
56 + teaql_data_service::MutationExecutor
57 + Send
58 + Sync
59 + 'static,
60{
61 fn save_graph_dyn<'a>(
62 &'a self,
63 ctx: &'a UserContext,
64 entity: &'a str,
65 node: GraphNode,
66 ) -> Pin<Box<dyn Future<Output = Result<GraphNode, RuntimeError>> + Send + 'a>> {
67 Box::pin(async move {
68 let eds = ctx
69 .entity_data_service::<E>(entity)
70 .map_err(|e| RuntimeError::Graph(e.to_string()))?;
71 eds.save_graph_internal(node).await.map_err(|e| match e {
72 DataServiceError::Runtime(r) => r,
73 other => RuntimeError::Graph(other.to_string()),
74 })
75 })
76 }
77
78 fn save_ledger_dyn<'a>(
79 &'a self,
80 ctx: &'a UserContext,
81 entity: &'a str,
82 root: crate::EntityRoot,
83 ) -> Pin<Box<dyn Future<Output = Result<GraphNode, RuntimeError>> + Send + 'a>> {
84 Box::pin(async move {
85 let eds = ctx
86 .entity_data_service::<E>(entity)
87 .map_err(|e| RuntimeError::Graph(e.to_string()))?;
88 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 Ok(GraphNode::new(entity))
95 })
96 }
97}
98
99pub fn graph_node_from_entity<T: Entity>(
109 ctx: &UserContext,
110 entity: T,
111) -> Result<GraphNode, RuntimeError> {
112 let descriptor = T::entity_descriptor();
113 let dirty_fields = entity.dirty_fields();
114 let original_values = entity.original_values();
115 let is_deleted = entity.is_marked_as_delete();
116 let comment = entity.get_comment();
117 let mut node = graph_node_from_record(ctx, &descriptor.name, entity.into_record())?;
118 node.dirty_fields = dirty_fields;
119 node.original_values = original_values;
120 if is_deleted {
121 node.operation = GraphOperation::Remove;
122 node.relations.clear();
123 }
124 if let Some(c) = comment {
125 node.set_comment(c);
126 }
127 Ok(node)
128}
129
130fn graph_node_from_record(
134 ctx: &UserContext,
135 entity: &str,
136 record: Record,
137) -> Result<GraphNode, RuntimeError> {
138 let descriptor = ctx.require_entity(entity)?;
139 let mut node = GraphNode::new(entity);
140
141 for (field, value) in record {
142 if field == "_comment" {
143 if let Value::Text(comment) = value {
144 node.set_comment(comment);
145 }
146 continue;
147 }
148 if field == "_dirty_fields" {
149 if let Value::List(fields) = value {
150 let mut dirty = BTreeSet::new();
151 for f in fields {
152 if let Value::Text(t) = f {
153 dirty.insert(t);
154 }
155 }
156 node.dirty_fields = Some(dirty);
157 }
158 continue;
159 }
160 if field == "_original_values" {
161 if let Value::Object(orig) = value {
162 node.original_values = Some(orig);
163 }
164 continue;
165 }
166 let Some(relation) = descriptor.relation_by_name(&field) else {
167 node.values.insert(field, value);
168 continue;
169 };
170
171 match value {
172 Value::Null => {
173 node.relations.entry(field).or_default();
174 }
175 Value::Object(record) => {
176 let child = graph_node_from_record(ctx, &relation.target_entity, record)?;
177 node.relations.entry(field).or_default().push(child);
178 }
179 Value::List(values) => {
180 let children = node.relations.entry(field.clone()).or_default();
181 for value in values {
182 let Value::Object(record) = value else {
183 return Err(RuntimeError::Graph(format!(
184 "relation {}.{} expects object children, got {:?}",
185 entity, field, value
186 )));
187 };
188 children.push(graph_node_from_record(
189 ctx,
190 &relation.target_entity,
191 record,
192 )?);
193 }
194 }
195 other => {
196 return Err(RuntimeError::Graph(format!(
197 "relation {}.{} expects object/list/null, got {:?}",
198 entity, field, other
199 )));
200 }
201 }
202 }
203
204 Ok(node)
205}
206
207pub trait AuditedSaveExt {
220 fn save<'a>(
221 self,
222 ctx: &'a UserContext,
223 ) -> Pin<Box<dyn Future<Output = Result<GraphNode, RuntimeError>> + Send + 'a>>;
224}
225
226impl<T> AuditedSaveExt for teaql_core::Audited<T>
227where
228 T: Entity + Send + 'static,
229{
230 fn save<'a>(
231 self,
232 ctx: &'a UserContext,
233 ) -> Pin<Box<dyn Future<Output = Result<GraphNode, RuntimeError>> + Send + 'a>> {
234 Box::pin(async move {
235 let entity_name = T::entity_descriptor().name;
236 let entity = self.into_entity(); let node = graph_node_from_entity(ctx, entity)?;
238 let saver = ctx
239 .require_resource::<Arc<dyn DynGraphSaver>>()
240 .map_err(|e| {
241 RuntimeError::Graph(format!(
242 "no DynGraphSaver registered — did you call register_executor()? ({})",
243 e
244 ))
245 })?;
246 saver.save_graph_dyn(ctx, &entity_name, node).await
247 })
248 }
249}
250
251#[doc(hidden)]
258pub async fn save_audited_ledger_entity<T>(
259 audited: teaql_core::Audited<T>,
260 ctx: &UserContext,
261) -> Result<GraphNode, RuntimeError>
262where
263 T: crate::LedgerEntity + Send + 'static,
264{
265 let entity_name = T::entity_descriptor().name;
266 let entity = audited.into_entity();
267 let root = entity.entity_root();
268 let node = graph_node_from_entity(ctx, entity)?;
269 let saver = ctx
270 .require_resource::<Arc<dyn DynGraphSaver>>()
271 .map_err(|e| {
272 RuntimeError::Graph(format!(
273 "no DynGraphSaver registered — did you call register_executor()? ({e})"
274 ))
275 })?;
276
277 if let Some(root) = root {
278 let has_ledger_changes = !root.current_change_set().changes().is_empty()
279 || !root.deleted_keys().is_empty()
280 || !root.new_keys().is_empty();
281 if has_ledger_changes {
282 return saver.save_ledger_dyn(ctx, &entity_name, root).await;
283 }
284 }
285
286 saver.save_graph_dyn(ctx, &entity_name, node).await
287}