1use core_storage::Value;
2use std::collections::BTreeMap;
3
4#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16pub enum EntityKind {
17 Node,
18 Relationship,
19 Path,
20 NodeList,
22 RelationshipList,
24 PathList,
26}
27
28impl EntityKind {
29 pub fn list_of(self) -> Option<EntityKind> {
32 match self {
33 EntityKind::Node => Some(EntityKind::NodeList),
34 EntityKind::Relationship => Some(EntityKind::RelationshipList),
35 EntityKind::Path => Some(EntityKind::PathList),
36 _ => None,
37 }
38 }
39
40 pub fn element(self) -> Option<EntityKind> {
42 match self {
43 EntityKind::NodeList => Some(EntityKind::Node),
44 EntityKind::RelationshipList => Some(EntityKind::Relationship),
45 EntityKind::PathList => Some(EntityKind::Path),
46 _ => None,
47 }
48 }
49}
50
51pub fn node_element_id(key: &str) -> String {
55 format!("n:{key}")
56}
57
58pub fn relationship_element_id(rel_type: &str, start: &str, end: &str) -> String {
63 let escape = |part: &str| part.replace('\\', "\\\\").replace('|', "\\|");
64 format!("r:{}|{}|{}", escape(rel_type), escape(start), escape(end))
65}
66
67#[derive(Debug, Clone, PartialEq)]
69pub struct NodeEntity {
70 pub id: u32,
73 pub key: String,
74 pub label: String,
75 pub properties: BTreeMap<String, Value>,
78}
79
80#[derive(Debug, Clone, PartialEq)]
83pub struct RelationshipEntity {
84 pub rel_type: String,
85 pub start: String,
86 pub end: String,
87 pub start_id: u32,
89 pub end_id: u32,
90 pub properties: BTreeMap<String, Value>,
91}
92
93#[derive(Debug, Clone, PartialEq)]
96pub struct PathEntity {
97 pub nodes: Vec<NodeEntity>,
98 pub relationships: Vec<RelationshipEntity>,
99}
100
101#[derive(Debug, Clone, PartialEq)]
103pub enum Entity {
104 Node(NodeEntity),
105 Relationship(RelationshipEntity),
106 Path(PathEntity),
107 List(Vec<Option<Entity>>),
110}
111
112#[derive(Debug, Clone, PartialEq)]
119pub(crate) enum ValueKindTag {
120 Node,
121 Relationship,
122 Path,
123 UniformList(EntityKind),
126 MixedList(Vec<Option<ValueKindTag>>),
129 Map(Vec<(String, ValueKindTag)>),
131}
132
133#[non_exhaustive]
145#[derive(Debug, Clone, PartialEq)]
146pub enum ValueEntityTree {
147 Node(NodeEntity),
148 Relationship(RelationshipEntity),
149 Path(PathEntity),
150 List(Vec<Option<ValueEntityTree>>),
151 Map(Vec<(String, ValueEntityTree)>),
152}
153
154#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
163pub struct WriteStats {
164 pub nodes_created: i64,
165 pub nodes_deleted: i64,
166 pub relationships_created: i64,
167 pub relationships_deleted: i64,
168 pub properties_set: i64,
169 pub labels_added: i64,
171 pub indexes_added: i64,
172 pub indexes_removed: i64,
173 pub constraints_added: i64,
174 pub constraints_removed: i64,
175}
176
177impl WriteStats {
178 pub fn contains_updates(&self) -> bool {
180 *self != Self::default()
181 }
182}
183
184#[derive(Debug, Clone)]
188struct EntityTreeSidecar {
189 kind_trees: Vec<Option<ValueKindTag>>,
192 entity_trees: Option<Vec<Option<ValueEntityTree>>>,
195}
196
197#[derive(Debug, Clone)]
204pub struct ResultSet {
205 columns: Vec<String>,
206 rows: Vec<Vec<Option<Value>>>,
207 kinds: Vec<Option<EntityKind>>,
208 entities: Option<Vec<Option<Entity>>>,
210 trees: Option<Box<EntityTreeSidecar>>,
212 stats: Option<Box<WriteStats>>,
215}
216
217impl PartialEq for ResultSet {
218 fn eq(&self, other: &Self) -> bool {
219 self.columns == other.columns && self.rows == other.rows
220 }
221}
222
223impl ResultSet {
224 pub fn new(columns: Vec<String>) -> Self {
225 let kinds = vec![None; columns.len()];
226 Self {
227 columns,
228 rows: Vec::new(),
229 kinds,
230 entities: None,
231 trees: None,
232 stats: None,
233 }
234 }
235
236 pub fn push_row(&mut self, row: Vec<Option<Value>>) {
237 assert_eq!(
238 row.len(),
239 self.columns.len(),
240 "row arity does not match ResultSet columns"
241 );
242 self.rows.push(row);
243 if let Some(entities) = self.entities.as_mut() {
244 entities.resize(self.rows.len() * self.columns.len(), None);
245 }
246 if let Some(sidecar) = self.trees.as_mut() {
247 if let Some(entity_trees) = sidecar.entity_trees.as_mut() {
248 entity_trees.resize(self.rows.len() * self.columns.len(), None);
249 }
250 }
251 }
252
253 pub fn columns(&self) -> &[String] {
254 &self.columns
255 }
256
257 pub fn len(&self) -> usize {
258 self.rows.len()
259 }
260
261 pub fn is_empty(&self) -> bool {
262 self.rows.is_empty()
263 }
264
265 pub fn row(&self, i: usize) -> &[Option<Value>] {
267 &self.rows[i]
268 }
269
270 pub fn get(&self, i: usize, col: &str) -> Option<&Value> {
271 let idx = self.columns.iter().position(|c| c == col)?;
272 self.rows.get(i)?.get(idx)?.as_ref()
273 }
274
275 pub fn column_kind(&self, col: usize) -> Option<EntityKind> {
277 self.kinds.get(col).copied().flatten()
278 }
279
280 pub fn column_kinds(&self) -> &[Option<EntityKind>] {
282 &self.kinds
283 }
284
285 pub fn set_column_kinds(&mut self, mut kinds: Vec<Option<EntityKind>>) {
288 kinds.resize(self.columns.len(), None);
289 self.kinds = kinds;
290 if let Some(sidecar) = self.trees.as_mut() {
292 sidecar.kind_trees.resize(self.columns.len(), None);
293 }
294 }
295
296 pub fn entity(&self, row: usize, col: usize) -> Option<&Entity> {
299 if col >= self.columns.len() {
300 return None;
301 }
302 self.entities
303 .as_ref()?
304 .get(row * self.columns.len() + col)?
305 .as_ref()
306 }
307
308 pub fn has_entities(&self) -> bool {
310 self.entities.is_some()
311 }
312
313 pub fn set_entities(&mut self, entities: Vec<Option<Entity>>) {
317 assert_eq!(
318 entities.len(),
319 self.rows.len() * self.columns.len(),
320 "entity table does not match the ResultSet"
321 );
322 self.entities = Some(entities);
323 }
324
325 pub(crate) fn value_kind_tree(&self, col: usize) -> Option<&ValueKindTag> {
329 self.trees.as_ref()?.kind_trees.get(col)?.as_ref()
330 }
331
332 pub(crate) fn set_value_kind_trees(&mut self, mut kind_trees: Vec<Option<ValueKindTag>>) {
335 kind_trees.resize(self.columns.len(), None);
336 if kind_trees.iter().any(Option::is_some) {
337 match self.trees.as_mut() {
338 Some(sidecar) => sidecar.kind_trees = kind_trees,
339 None => {
340 self.trees = Some(Box::new(EntityTreeSidecar {
341 kind_trees,
342 entity_trees: None,
343 }));
344 }
345 }
346 } else if let Some(sidecar) = self.trees.as_mut() {
347 sidecar.kind_trees = kind_trees;
349 }
350 }
351
352 pub fn value_entity_tree(&self, row: usize, col: usize) -> Option<&ValueEntityTree> {
356 if col >= self.columns.len() {
357 return None;
358 }
359 self.trees
360 .as_ref()?
361 .entity_trees
362 .as_ref()?
363 .get(row * self.columns.len() + col)?
364 .as_ref()
365 }
366
367 pub(crate) fn set_value_entity_trees(&mut self, entity_trees: Vec<Option<ValueEntityTree>>) {
371 assert_eq!(
372 entity_trees.len(),
373 self.rows.len() * self.columns.len(),
374 "value entity tree table does not match the ResultSet"
375 );
376 match self.trees.as_mut() {
377 Some(sidecar) => sidecar.entity_trees = Some(entity_trees),
378 None => {
379 self.trees = Some(Box::new(EntityTreeSidecar {
380 kind_trees: vec![None; self.columns.len()],
381 entity_trees: Some(entity_trees),
382 }));
383 }
384 }
385 }
386
387 pub(crate) fn has_value_kind_trees(&self) -> bool {
390 self.trees
391 .as_ref()
392 .is_some_and(|s| s.kind_trees.iter().any(Option::is_some))
393 }
394
395 pub fn stats(&self) -> Option<&WriteStats> {
397 self.stats.as_deref()
398 }
399
400 pub fn with_stats(mut self, stats: WriteStats) -> Self {
402 self.stats = Some(Box::new(stats));
403 self
404 }
405}
406
407#[cfg(test)]
408mod tests {
409 use super::{Entity, EntityKind, NodeEntity, ResultSet, WriteStats};
410 use core_storage::Value;
411
412 #[test]
413 fn resultset_basics() {
414 let mut rs = ResultSet::new(vec!["a".into(), "b".into()]);
415 rs.push_row(vec![Some(Value::Int(1)), None]);
416 assert_eq!(rs.len(), 1);
417 assert_eq!(rs.get(0, "a"), Some(&Value::Int(1)));
418 assert_eq!(rs.get(0, "b"), None);
419 assert_eq!(rs.get(0, "zz"), None);
420 assert_eq!(rs.row(0)[1], None);
421 }
422
423 #[test]
424 #[should_panic]
425 fn resultset_arity_mismatch_panics() {
426 let mut rs = ResultSet::new(vec!["a".into()]);
427 rs.push_row(vec![]);
428 }
429
430 #[test]
431 fn resultset_empty_and_columns() {
432 let rs = ResultSet::new(vec!["a".into()]);
433 assert!(rs.is_empty());
434 assert_eq!(rs.len(), 0);
435 assert_eq!(rs.columns(), &["a".to_string()]);
436 }
437
438 #[test]
439 fn kinds_entities_and_stats_ride_beside_the_table_and_equality_ignores_them() {
440 let mut rs = ResultSet::new(vec!["n".into(), "x".into()]);
441 rs.push_row(vec![Some(Value::Str("k".into())), Some(Value::Int(1))]);
442 let plain = rs.clone();
443 rs.set_column_kinds(vec![Some(EntityKind::Node)]);
444 assert_eq!(rs.column_kinds(), &[Some(EntityKind::Node), None]);
445 let node = Entity::Node(NodeEntity {
446 id: 0,
447 key: "k".into(),
448 label: "L".into(),
449 properties: Default::default(),
450 });
451 rs.set_entities(vec![Some(node.clone()), None]);
452 assert_eq!(rs.entity(0, 0), Some(&node));
453 assert_eq!(rs.entity(0, 1), None);
454 assert_eq!(rs.entity(0, 2), None);
455 let rs = rs.with_stats(WriteStats {
456 nodes_created: 1,
457 ..Default::default()
458 });
459 assert!(rs.stats().unwrap().contains_updates());
460 assert!(!WriteStats::default().contains_updates());
461 assert_eq!(rs, plain);
462 }
463}