1use std::collections::{BTreeMap, BTreeSet};
2
3use teaql_core::{Record, Value};
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq)]
6pub enum GraphOperation {
7 Upsert,
8 Create,
9 Reference,
10 Remove,
11}
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
14pub enum GraphMutationKind {
15 Create,
16 Update,
17 Delete,
18 Reference,
19}
20
21#[derive(Debug, Clone, PartialEq)]
22pub struct GraphMutationPlanItem {
23 pub entity: String,
24 pub kind: GraphMutationKind,
25 pub values: Record,
26 pub update_fields: Vec<String>,
27}
28
29#[derive(Debug, Clone, PartialEq)]
30pub struct GraphMutationBatch {
31 pub entity: String,
32 pub kind: GraphMutationKind,
33 pub update_fields: Vec<String>,
34 pub items: Vec<GraphMutationPlanItem>,
35}
36
37#[derive(Debug, Clone, PartialEq, Default)]
38pub struct GraphMutationPlan {
39 pub planned_root: Option<GraphNode>,
40 pub items: Vec<GraphMutationPlanItem>,
41 pub batches: Vec<GraphMutationBatch>,
42}
43
44impl GraphMutationPlan {
45 pub fn push(
46 &mut self,
47 entity: impl Into<String>,
48 kind: GraphMutationKind,
49 values: Record,
50 update_fields: Vec<String>,
51 ) {
52 self.items.push(GraphMutationPlanItem {
53 entity: entity.into(),
54 kind,
55 values,
56 update_fields,
57 });
58 }
59
60 pub fn rebuild_batches(&mut self) {
61 let mut grouped: BTreeMap<
62 (String, GraphMutationKind, Vec<String>),
63 Vec<GraphMutationPlanItem>,
64 > = BTreeMap::new();
65 for item in &self.items {
66 let update_fields = match item.kind {
67 GraphMutationKind::Update => item.update_fields.clone(),
68 _ => Vec::new(),
69 };
70 grouped
71 .entry((item.entity.clone(), item.kind, update_fields))
72 .or_default()
73 .push(item.clone());
74 }
75 self.batches = grouped
76 .into_iter()
77 .map(
78 |((entity, kind, update_fields), items)| GraphMutationBatch {
79 entity,
80 kind,
81 update_fields,
82 items,
83 },
84 )
85 .collect();
86 }
87
88 pub fn grouped_counts(&self) -> BTreeMap<(String, GraphMutationKind), usize> {
89 let mut counts = BTreeMap::new();
90 for batch in &self.batches {
91 *counts
92 .entry((batch.entity.clone(), batch.kind))
93 .or_insert(0) += batch.items.len();
94 }
95 counts
96 }
97
98 pub fn batch_count(&self) -> usize {
99 self.batches.len()
100 }
101
102 pub fn len(&self) -> usize {
103 self.items.len()
104 }
105
106 pub fn is_empty(&self) -> bool {
107 self.items.is_empty()
108 }
109}
110
111pub fn sorted_update_fields(
112 values: &Record,
113 excluded: impl IntoIterator<Item = String>,
114) -> Vec<String> {
115 let excluded = excluded.into_iter().collect::<BTreeSet<_>>();
116 values
117 .keys()
118 .filter(|field| !excluded.contains(*field))
119 .cloned()
120 .collect()
121}
122
123#[derive(Debug, Clone, PartialEq)]
124pub struct GraphNode {
125 pub entity: String,
126 pub values: Record,
127 pub relations: BTreeMap<String, Vec<GraphNode>>,
128 pub operation: GraphOperation,
129 pub comment: Option<String>,
132}
133
134impl GraphNode {
135 pub fn new(entity: impl Into<String>) -> Self {
136 Self {
137 entity: entity.into(),
138 values: Record::new(),
139 relations: BTreeMap::new(),
140 operation: GraphOperation::Upsert,
141 comment: None,
142 }
143 }
144
145 pub fn operation(mut self, operation: GraphOperation) -> Self {
146 self.operation = operation;
147 self
148 }
149
150 pub fn reference(mut self) -> Self {
151 self.operation = GraphOperation::Reference;
152 self
153 }
154
155 pub fn remove(mut self) -> Self {
156 self.operation = GraphOperation::Remove;
157 self
158 }
159
160 pub fn value(mut self, field: impl Into<String>, value: impl Into<Value>) -> Self {
161 self.values.insert(field.into(), value.into());
162 self
163 }
164
165 pub fn relation(mut self, name: impl Into<String>, node: GraphNode) -> Self {
166 self.relations.entry(name.into()).or_default().push(node);
167 self
168 }
169
170 pub fn relations(
171 mut self,
172 name: impl Into<String>,
173 nodes: impl IntoIterator<Item = GraphNode>,
174 ) -> Self {
175 self.relations.entry(name.into()).or_default().extend(nodes);
176 self
177 }
178
179 pub fn id(&self) -> Option<&Value> {
180 self.values.get("id")
181 }
182
183 pub fn comment(mut self, comment: impl Into<String>) -> Self {
186 self.comment = Some(comment.into());
187 self
188 }
189
190 pub fn set_comment(&mut self, comment: impl Into<String>) {
192 self.comment = Some(comment.into());
193 }
194}
195
196#[derive(Debug)]
206pub struct ScopedCommentNode<'a> {
207 pub parent: Option<&'a ScopedCommentNode<'a>>,
209 pub track: teaql_core::TraceNode,
210}
211
212impl<'a> ScopedCommentNode<'a> {
213 pub fn to_trace_chain(&self) -> Vec<teaql_core::TraceNode> {
214 let mut chain = Vec::new();
215 let mut current: Option<&ScopedCommentNode<'_>> = Some(self);
216
217 while let Some(node) = current {
218 if !node.track.comment.is_empty() {
219 chain.push(node.track.clone());
220 }
221 current = node.parent;
222 }
223
224 chain.reverse();
225 chain
226 }
227}