1use lora_analyzer::symbols::VarId;
2use lora_store::{
3 LoraBinary, LoraDate, LoraDateTime, LoraDuration, LoraLocalDateTime, LoraLocalTime, LoraPoint,
4 LoraTime, LoraVector, NodeId, PropertyValue, RelationshipId, VectorValues,
5};
6
7#[derive(Debug, Clone, PartialEq)]
10pub struct LoraPath {
11 pub nodes: Vec<NodeId>,
12 pub rels: Vec<RelationshipId>,
13}
14use serde::ser::{SerializeMap, SerializeSeq};
15use serde::{Serialize, Serializer};
16use std::collections::BTreeMap;
17
18#[derive(Debug, Clone, PartialEq)]
19pub enum LoraValue {
20 Null,
21 Bool(bool),
22 Int(i64),
23 Float(f64),
24 String(String),
25 Binary(LoraBinary),
26 List(Vec<LoraValue>),
27 Map(BTreeMap<String, LoraValue>),
28 Node(NodeId),
29 Relationship(RelationshipId),
30 Path(LoraPath),
31 Date(LoraDate),
32 Time(LoraTime),
33 LocalTime(LoraLocalTime),
34 DateTime(LoraDateTime),
35 LocalDateTime(LoraLocalDateTime),
36 Duration(LoraDuration),
37 Point(LoraPoint),
38 Vector(LoraVector),
39}
40
41impl LoraValue {
42 pub fn is_truthy(&self) -> bool {
43 match self {
44 LoraValue::Null => false,
45 LoraValue::Bool(v) => *v,
46 _ => true,
47 }
48 }
49
50 pub fn as_i64(&self) -> Option<i64> {
51 match self {
52 LoraValue::Int(v) => Some(*v),
53 _ => None,
54 }
55 }
56
57 pub fn as_f64(&self) -> Option<f64> {
58 match self {
59 LoraValue::Int(v) => Some(*v as f64),
60 LoraValue::Float(v) => Some(*v),
61 _ => None,
62 }
63 }
64}
65
66impl Serialize for LoraValue {
67 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
68 where
69 S: Serializer,
70 {
71 match self {
72 LoraValue::Null => serializer.serialize_unit(),
73 LoraValue::Bool(v) => serializer.serialize_bool(*v),
74 LoraValue::Int(v) => serializer.serialize_i64(*v),
75 LoraValue::Float(v) => serializer.serialize_f64(*v),
76 LoraValue::String(v) => serializer.serialize_str(v),
77 LoraValue::Binary(v) => serialize_binary(serializer, v),
78
79 LoraValue::List(values) => {
80 let mut seq = serializer.serialize_seq(Some(values.len()))?;
81 for value in values {
82 seq.serialize_element(value)?;
83 }
84 seq.end()
85 }
86
87 LoraValue::Map(map) => {
88 let mut ser_map = serializer.serialize_map(Some(map.len()))?;
89 for (k, v) in map {
90 ser_map.serialize_entry(k, v)?;
91 }
92 ser_map.end()
93 }
94
95 LoraValue::Node(id) => {
97 let mut ser_map = serializer.serialize_map(Some(2))?;
98 ser_map.serialize_entry("kind", "node")?;
99 ser_map.serialize_entry("id", id)?;
100 ser_map.end()
101 }
102
103 LoraValue::Relationship(id) => {
104 let mut ser_map = serializer.serialize_map(Some(2))?;
105 ser_map.serialize_entry("kind", "relationship")?;
106 ser_map.serialize_entry("id", id)?;
107 ser_map.end()
108 }
109
110 LoraValue::Path(path) => {
111 let mut ser_map = serializer.serialize_map(Some(3))?;
112 ser_map.serialize_entry("kind", "path")?;
113 ser_map.serialize_entry("nodes", &path.nodes)?;
114 ser_map.serialize_entry("rels", &path.rels)?;
115 ser_map.end()
116 }
117
118 LoraValue::Date(d) => serializer.serialize_str(&d.to_string()),
119 LoraValue::Time(t) => serializer.serialize_str(&t.to_string()),
120 LoraValue::LocalTime(t) => serializer.serialize_str(&t.to_string()),
121 LoraValue::DateTime(dt) => serializer.serialize_str(&dt.to_string()),
122 LoraValue::LocalDateTime(dt) => serializer.serialize_str(&dt.to_string()),
123 LoraValue::Duration(dur) => serializer.serialize_str(&dur.to_string()),
124 LoraValue::Point(p) => {
125 let len = if p.z.is_some() { 4 } else { 3 };
126 let mut m = serializer.serialize_map(Some(len))?;
127 m.serialize_entry("srid", &p.srid)?;
128 m.serialize_entry("x", &p.x)?;
129 m.serialize_entry("y", &p.y)?;
130 if let Some(z) = p.z {
131 m.serialize_entry("z", &z)?;
132 }
133 m.end()
134 }
135 LoraValue::Vector(v) => serialize_vector(serializer, v),
136 }
137 }
138}
139
140fn serialize_binary<S: Serializer>(serializer: S, v: &LoraBinary) -> Result<S::Ok, S::Error> {
141 let mut m = serializer.serialize_map(Some(3))?;
142 m.serialize_entry("kind", "binary")?;
143 m.serialize_entry("length", &v.len())?;
144 m.serialize_entry("segments", v.segments())?;
145 m.end()
146}
147
148fn serialize_vector<S: Serializer>(serializer: S, v: &LoraVector) -> Result<S::Ok, S::Error> {
149 let mut m = serializer.serialize_map(Some(4))?;
150 m.serialize_entry("kind", "vector")?;
151 m.serialize_entry("dimension", &v.dimension)?;
152 m.serialize_entry("coordinateType", v.coordinate_type().as_str())?;
153 match &v.values {
157 VectorValues::Float64(values) => m.serialize_entry("values", values)?,
158 VectorValues::Float32(values) => {
159 let widened: Vec<f64> = values.iter().map(|x| *x as f64).collect();
160 m.serialize_entry("values", &widened)?;
161 }
162 VectorValues::Integer64(values) => m.serialize_entry("values", values)?,
163 VectorValues::Integer32(values) => {
164 let widened: Vec<i64> = values.iter().map(|x| *x as i64).collect();
165 m.serialize_entry("values", &widened)?;
166 }
167 VectorValues::Integer16(values) => {
168 let widened: Vec<i64> = values.iter().map(|x| *x as i64).collect();
169 m.serialize_entry("values", &widened)?;
170 }
171 VectorValues::Integer8(values) => {
172 let widened: Vec<i64> = values.iter().map(|x| *x as i64).collect();
173 m.serialize_entry("values", &widened)?;
174 }
175 }
176 m.end()
177}
178
179impl From<PropertyValue> for LoraValue {
180 fn from(value: PropertyValue) -> Self {
181 match value {
182 PropertyValue::Null => LoraValue::Null,
183 PropertyValue::Bool(v) => LoraValue::Bool(v),
184 PropertyValue::Int(v) => LoraValue::Int(v),
185 PropertyValue::Float(v) => LoraValue::Float(v),
186 PropertyValue::String(v) => LoraValue::String(v),
187 PropertyValue::Binary(v) => LoraValue::Binary(v),
188 PropertyValue::List(values) => {
189 LoraValue::List(values.into_iter().map(LoraValue::from).collect())
190 }
191 PropertyValue::Map(map) => LoraValue::Map(
192 map.into_iter()
193 .map(|(k, v)| (k, LoraValue::from(v)))
194 .collect(),
195 ),
196 PropertyValue::Date(d) => LoraValue::Date(d),
197 PropertyValue::Time(t) => LoraValue::Time(t),
198 PropertyValue::LocalTime(t) => LoraValue::LocalTime(t),
199 PropertyValue::DateTime(dt) => LoraValue::DateTime(dt),
200 PropertyValue::LocalDateTime(dt) => LoraValue::LocalDateTime(dt),
201 PropertyValue::Duration(dur) => LoraValue::Duration(dur),
202 PropertyValue::Point(p) => LoraValue::Point(p),
203 PropertyValue::Vector(v) => LoraValue::Vector(v),
204 }
205 }
206}
207
208impl From<&PropertyValue> for LoraValue {
212 fn from(value: &PropertyValue) -> Self {
213 match value {
214 PropertyValue::Null => LoraValue::Null,
215 PropertyValue::Bool(v) => LoraValue::Bool(*v),
216 PropertyValue::Int(v) => LoraValue::Int(*v),
217 PropertyValue::Float(v) => LoraValue::Float(*v),
218 PropertyValue::String(v) => LoraValue::String(v.clone()),
219 PropertyValue::Binary(v) => LoraValue::Binary(v.clone()),
220 PropertyValue::List(values) => {
221 LoraValue::List(values.iter().map(LoraValue::from).collect())
222 }
223 PropertyValue::Map(map) => LoraValue::Map(
224 map.iter()
225 .map(|(k, v)| (k.clone(), LoraValue::from(v)))
226 .collect(),
227 ),
228 PropertyValue::Date(d) => LoraValue::Date(d.clone()),
229 PropertyValue::Time(t) => LoraValue::Time(t.clone()),
230 PropertyValue::LocalTime(t) => LoraValue::LocalTime(t.clone()),
231 PropertyValue::DateTime(dt) => LoraValue::DateTime(dt.clone()),
232 PropertyValue::LocalDateTime(dt) => LoraValue::LocalDateTime(dt.clone()),
233 PropertyValue::Duration(dur) => LoraValue::Duration(dur.clone()),
234 PropertyValue::Point(p) => LoraValue::Point(p.clone()),
235 PropertyValue::Vector(v) => LoraValue::Vector(v.clone()),
236 }
237 }
238}
239
240impl From<LoraValue> for PropertyValue {
241 fn from(value: LoraValue) -> Self {
242 match value {
243 LoraValue::Null => PropertyValue::Null,
244 LoraValue::Bool(v) => PropertyValue::Bool(v),
245 LoraValue::Int(v) => PropertyValue::Int(v),
246 LoraValue::Float(v) => PropertyValue::Float(v),
247 LoraValue::String(v) => PropertyValue::String(v),
248 LoraValue::Binary(v) => PropertyValue::Binary(v),
249 LoraValue::List(values) => {
250 PropertyValue::List(values.into_iter().map(PropertyValue::from).collect())
251 }
252 LoraValue::Map(map) => PropertyValue::Map(
253 map.into_iter()
254 .map(|(k, v)| (k, PropertyValue::from(v)))
255 .collect(),
256 ),
257 LoraValue::Node(id) => PropertyValue::String(format!("node:{id}")),
258 LoraValue::Relationship(id) => PropertyValue::String(format!("rel:{id}")),
259 LoraValue::Path(_) => PropertyValue::Null,
260 LoraValue::Date(d) => PropertyValue::Date(d),
261 LoraValue::Time(t) => PropertyValue::Time(t),
262 LoraValue::LocalTime(t) => PropertyValue::LocalTime(t),
263 LoraValue::DateTime(dt) => PropertyValue::DateTime(dt),
264 LoraValue::LocalDateTime(dt) => PropertyValue::LocalDateTime(dt),
265 LoraValue::Duration(dur) => PropertyValue::Duration(dur),
266 LoraValue::Point(p) => PropertyValue::Point(p),
267 LoraValue::Vector(v) => PropertyValue::Vector(v),
268 }
269 }
270}
271
272#[derive(Debug, Clone, PartialEq)]
275pub enum PropertyConversionError {
276 NestedVectorInList,
279 #[allow(dead_code)]
283 UnsupportedKind(&'static str),
284}
285
286impl std::fmt::Display for PropertyConversionError {
287 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
288 match self {
289 PropertyConversionError::NestedVectorInList => {
290 write!(f, "lists stored as properties cannot contain VECTOR values")
291 }
292 PropertyConversionError::UnsupportedKind(kind) => {
293 write!(f, "cannot store {kind} as a property")
294 }
295 }
296 }
297}
298
299impl std::error::Error for PropertyConversionError {}
300
301pub fn lora_value_to_property(value: LoraValue) -> Result<PropertyValue, PropertyConversionError> {
309 fn visit(value: &LoraValue, inside_list: bool) -> Result<(), PropertyConversionError> {
314 match value {
315 LoraValue::Vector(_) if inside_list => Err(PropertyConversionError::NestedVectorInList),
316 LoraValue::List(items) => {
317 for item in items {
318 visit(item, true)?;
319 }
320 Ok(())
321 }
322 LoraValue::Map(m) => {
323 for v in m.values() {
324 visit(v, inside_list)?;
325 }
326 Ok(())
327 }
328 _ => Ok(()),
329 }
330 }
331
332 visit(&value, false)?;
333 Ok(PropertyValue::from(value))
334}
335
336#[derive(Debug, Clone, PartialEq)]
337struct RowEntry {
338 name: Option<String>,
341 value: LoraValue,
342}
343
344#[derive(Debug, Clone, Default, PartialEq)]
345pub struct Row {
346 values: BTreeMap<VarId, RowEntry>,
347}
348
349impl Serialize for Row {
350 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
351 where
352 S: Serializer,
353 {
354 let mut ser_map = serializer.serialize_map(Some(self.values.len()))?;
355 for (key, entry) in &self.values {
356 match &entry.name {
357 Some(name) => ser_map.serialize_entry(name.as_str(), &entry.value)?,
358 None => {
359 let fallback = format!("_{key}");
360 ser_map.serialize_entry(fallback.as_str(), &entry.value)?;
361 }
362 }
363 }
364 ser_map.end()
365 }
366}
367
368impl Row {
369 pub fn new() -> Self {
370 Self {
371 values: BTreeMap::new(),
372 }
373 }
374
375 pub fn get(&self, key: VarId) -> Option<&LoraValue> {
376 self.values.get(&key).map(|entry| &entry.value)
377 }
378
379 pub fn get_name(&self, key: VarId) -> Option<String> {
382 self.values.get(&key).map(|entry| match &entry.name {
383 Some(n) => n.clone(),
384 None => format!("_{key}"),
385 })
386 }
387
388 pub fn insert(&mut self, key: VarId, value: LoraValue) {
389 use std::collections::btree_map::Entry;
392 match self.values.entry(key) {
393 Entry::Occupied(mut e) => e.get_mut().value = value,
394 Entry::Vacant(e) => {
395 e.insert(RowEntry { name: None, value });
396 }
397 }
398 }
399
400 pub fn insert_named(&mut self, key: VarId, name: impl Into<String>, value: LoraValue) {
401 self.values.insert(
402 key,
403 RowEntry {
404 name: Some(name.into()),
405 value,
406 },
407 );
408 }
409
410 pub fn extend_from(&mut self, other: &Row) {
411 for (k, v) in &other.values {
412 self.values.insert(*k, v.clone());
413 }
414 }
415
416 pub fn iter(&self) -> impl Iterator<Item = (&VarId, &LoraValue)> {
417 self.values.iter().map(|(k, entry)| (k, &entry.value))
418 }
419
420 pub fn iter_named(
424 &self,
425 ) -> impl Iterator<Item = (&VarId, std::borrow::Cow<'_, str>, &LoraValue)> {
426 self.values.iter().map(|(k, entry)| {
427 let name: std::borrow::Cow<'_, str> = match &entry.name {
428 Some(n) => std::borrow::Cow::Borrowed(n.as_str()),
429 None => std::borrow::Cow::Owned(format!("_{k}")),
430 };
431 (k, name, &entry.value)
432 })
433 }
434
435 pub fn into_iter_named(self) -> impl Iterator<Item = (VarId, String, LoraValue)> {
438 self.values.into_iter().map(|(k, entry)| {
439 (
440 k,
441 entry.name.unwrap_or_else(|| format!("_{k}")),
442 entry.value,
443 )
444 })
445 }
446
447 pub fn len(&self) -> usize {
448 self.values.len()
449 }
450
451 pub fn is_empty(&self) -> bool {
452 self.values.is_empty()
453 }
454
455 pub fn contains_key(&self, key: VarId) -> bool {
456 self.values.contains_key(&key)
457 }
458}
459
460#[derive(Debug, Clone, Copy, PartialEq, Eq)]
461pub enum ResultFormat {
462 Rows,
463 RowArrays,
464 Graph,
465 Combined,
466}
467
468#[derive(Debug, Clone, Copy, PartialEq, Eq)]
469pub struct ExecuteOptions {
470 pub format: ResultFormat,
471}
472
473impl Default for ExecuteOptions {
474 fn default() -> Self {
475 Self {
476 format: ResultFormat::Graph,
477 }
478 }
479}
480
481#[derive(Debug, Clone, Serialize)]
482#[serde(untagged)]
483pub enum QueryResult {
484 Rows(RowsResult),
485 RowArrays(RowArraysResult),
486 Graph(GraphResult),
487 Combined(CombinedResult),
488}
489
490#[derive(Debug, Clone, Serialize)]
491pub struct RowsResult {
492 pub rows: Vec<Row>,
493}
494
495#[derive(Debug, Clone, Serialize)]
496pub struct RowArraysResult {
497 pub columns: Vec<String>,
498 pub rows: Vec<Vec<LoraValue>>,
499}
500
501#[derive(Debug, Clone, Serialize)]
502pub struct GraphResult {
503 pub graph: HydratedGraph,
504}
505
506#[derive(Debug, Clone, Serialize)]
507pub struct CombinedResult {
508 pub columns: Vec<String>,
509 pub data: Vec<CombinedRow>,
510 pub graph: HydratedGraph,
511}
512
513#[derive(Debug, Clone, Serialize)]
514pub struct CombinedRow {
515 pub row: Vec<LoraValue>,
516}
517
518#[derive(Debug, Clone, Serialize, Default)]
519pub struct HydratedGraph {
520 pub nodes: Vec<HydratedNode>,
521 pub relationships: Vec<HydratedRelationship>,
522}
523
524#[derive(Debug, Clone, Serialize, PartialEq)]
525pub struct HydratedNode {
526 pub id: i64,
527 pub labels: Vec<String>,
528 pub properties: BTreeMap<String, LoraValue>,
529}
530
531#[derive(Debug, Clone, Serialize, PartialEq)]
532pub struct HydratedRelationship {
533 pub id: i64,
534 #[serde(rename = "startId")]
535 pub start_id: i64,
536 #[serde(rename = "endId")]
537 pub end_id: i64,
538 #[serde(rename = "type")]
539 pub rel_type: String,
540 pub properties: BTreeMap<String, LoraValue>,
541}
542
543pub fn project_rows(rows: Vec<Row>, options: ExecuteOptions) -> QueryResult {
544 match options.format {
545 ResultFormat::Rows => QueryResult::Rows(RowsResult { rows }),
546
547 ResultFormat::RowArrays => {
548 let columns = infer_columns(&rows);
549 let projected_rows = rows.iter().map(|row| row_to_array(row, &columns)).collect();
550
551 QueryResult::RowArrays(RowArraysResult {
552 columns,
553 rows: projected_rows,
554 })
555 }
556
557 ResultFormat::Graph => QueryResult::Graph(GraphResult {
558 graph: collect_hydrated_graph(&rows),
559 }),
560
561 ResultFormat::Combined => {
562 let columns = infer_columns(&rows);
563 let data = rows
564 .iter()
565 .map(|row| CombinedRow {
566 row: row_to_array(row, &columns),
567 })
568 .collect();
569
570 QueryResult::Combined(CombinedResult {
571 columns,
572 data,
573 graph: collect_hydrated_graph(&rows),
574 })
575 }
576 }
577}
578
579fn infer_columns(rows: &[Row]) -> Vec<String> {
580 rows.first()
581 .map(|row| {
582 row.iter_named()
583 .map(|(_, name, _)| name.into_owned())
584 .collect::<Vec<_>>()
585 })
586 .unwrap_or_default()
587}
588
589fn row_to_array(row: &Row, columns: &[String]) -> Vec<LoraValue> {
590 columns
593 .iter()
594 .map(|col| {
595 row.iter_named()
596 .find(|(_, name, _)| name.as_ref() == col.as_str())
597 .map(|(_, _, v)| v.clone())
598 .unwrap_or(LoraValue::Null)
599 })
600 .collect()
601}
602
603fn collect_hydrated_graph(rows: &[Row]) -> HydratedGraph {
604 let mut nodes = BTreeMap::<i64, HydratedNode>::new();
605 let mut relationships = BTreeMap::<i64, HydratedRelationship>::new();
606
607 for row in rows {
608 for (_, _, value) in row.iter_named() {
609 collect_graph_from_value(value, &mut nodes, &mut relationships);
610 }
611 }
612
613 HydratedGraph {
614 nodes: nodes.into_values().collect(),
615 relationships: relationships.into_values().collect(),
616 }
617}
618
619fn collect_graph_from_value(
620 value: &LoraValue,
621 nodes: &mut BTreeMap<i64, HydratedNode>,
622 relationships: &mut BTreeMap<i64, HydratedRelationship>,
623) {
624 match value {
625 LoraValue::List(values) => {
626 for value in values {
627 collect_graph_from_value(value, nodes, relationships);
628 }
629 }
630
631 LoraValue::Map(map) => {
632 if let Some(node) = try_as_hydrated_node(map) {
633 nodes.entry(node.id).or_insert(node);
634 return;
635 }
636
637 if let Some(rel) = try_as_hydrated_relationship(map) {
638 relationships.entry(rel.id).or_insert(rel);
639 return;
640 }
641
642 for value in map.values() {
643 collect_graph_from_value(value, nodes, relationships);
644 }
645 }
646
647 _ => {}
648 }
649}
650
651fn try_as_hydrated_node(map: &BTreeMap<String, LoraValue>) -> Option<HydratedNode> {
652 let id = match map.get("id")? {
653 LoraValue::Int(v) => *v,
654 _ => return None,
655 };
656
657 let labels = match map.get("labels")? {
658 LoraValue::List(values) => values
659 .iter()
660 .map(|v| match v {
661 LoraValue::String(s) => Some(s.clone()),
662 _ => None,
663 })
664 .collect::<Option<Vec<_>>>()?,
665 _ => return None,
666 };
667
668 let properties = match map.get("properties")? {
669 LoraValue::Map(props) => props.clone(),
670 _ => return None,
671 };
672
673 Some(HydratedNode {
674 id,
675 labels,
676 properties,
677 })
678}
679
680fn try_as_hydrated_relationship(map: &BTreeMap<String, LoraValue>) -> Option<HydratedRelationship> {
681 match map.get("kind") {
682 Some(LoraValue::String(kind)) if kind == "relationship" => {}
683 _ => return None,
684 }
685
686 let id = match map.get("id")? {
687 LoraValue::Int(v) => *v,
688 _ => return None,
689 };
690
691 let start_id = match map.get("startId").or_else(|| map.get("src"))? {
692 LoraValue::Int(v) => *v,
693 _ => return None,
694 };
695
696 let end_id = match map.get("endId").or_else(|| map.get("dst"))? {
697 LoraValue::Int(v) => *v,
698 _ => return None,
699 };
700
701 let rel_type = match map.get("type")? {
702 LoraValue::String(s) => s.clone(),
703 _ => return None,
704 };
705
706 let properties = match map.get("properties")? {
707 LoraValue::Map(props) => props.clone(),
708 _ => return None,
709 };
710
711 Some(HydratedRelationship {
712 id,
713 start_id,
714 end_id,
715 rel_type,
716 properties,
717 })
718}