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 var: VarId,
342 name: Option<String>,
345 value: LoraValue,
346}
347
348#[derive(Debug, Clone, Default, PartialEq)]
363pub struct Row {
364 entries: smallvec::SmallVec<Option<RowEntry>, 8>,
365 len_set: u32,
366}
367
368impl Serialize for Row {
369 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
370 where
371 S: Serializer,
372 {
373 let mut ser_map = serializer.serialize_map(Some(self.len()))?;
374 for entry in self.entries.iter().flatten() {
375 match &entry.name {
376 Some(name) => ser_map.serialize_entry(name.as_str(), &entry.value)?,
377 None => {
378 let fallback = format!("_{}", entry.var);
379 ser_map.serialize_entry(fallback.as_str(), &entry.value)?;
380 }
381 }
382 }
383 ser_map.end()
384 }
385}
386
387impl Row {
388 pub fn new() -> Self {
389 Self::default()
390 }
391
392 pub fn get(&self, key: VarId) -> Option<&LoraValue> {
393 self.entries
394 .get(key.0 as usize)
395 .and_then(|slot| slot.as_ref())
396 .map(|entry| &entry.value)
397 }
398
399 pub fn get_name(&self, key: VarId) -> Option<String> {
402 self.entries
403 .get(key.0 as usize)
404 .and_then(|slot| slot.as_ref())
405 .map(|entry| match &entry.name {
406 Some(n) => n.clone(),
407 None => format!("_{}", entry.var),
408 })
409 }
410
411 pub fn insert(&mut self, key: VarId, value: LoraValue) {
412 let idx = self.ensure_slot(key);
415 match &mut self.entries[idx] {
416 Some(existing) => existing.value = value,
417 slot @ None => {
418 *slot = Some(RowEntry {
419 var: key,
420 name: None,
421 value,
422 });
423 self.len_set += 1;
424 }
425 }
426 }
427
428 pub fn insert_named(&mut self, key: VarId, name: impl Into<String>, value: LoraValue) {
429 let idx = self.ensure_slot(key);
430 let was_set = self.entries[idx].is_some();
431 self.entries[idx] = Some(RowEntry {
432 var: key,
433 name: Some(name.into()),
434 value,
435 });
436 if !was_set {
437 self.len_set += 1;
438 }
439 }
440
441 pub fn extend_from(&mut self, other: &Row) {
442 for entry in other.entries.iter().flatten() {
445 let idx = self.ensure_slot(entry.var);
446 let was_set = self.entries[idx].is_some();
447 self.entries[idx] = Some(entry.clone());
448 if !was_set {
449 self.len_set += 1;
450 }
451 }
452 }
453
454 pub fn iter(&self) -> impl Iterator<Item = (&VarId, &LoraValue)> {
455 self.entries
456 .iter()
457 .flatten()
458 .map(|entry| (&entry.var, &entry.value))
459 }
460
461 pub fn iter_named(
465 &self,
466 ) -> impl Iterator<Item = (&VarId, std::borrow::Cow<'_, str>, &LoraValue)> {
467 self.entries.iter().flatten().map(|entry| {
468 let name: std::borrow::Cow<'_, str> = match &entry.name {
469 Some(n) => std::borrow::Cow::Borrowed(n.as_str()),
470 None => std::borrow::Cow::Owned(format!("_{}", entry.var)),
471 };
472 (&entry.var, name, &entry.value)
473 })
474 }
475
476 pub fn into_iter_named(self) -> impl Iterator<Item = (VarId, String, LoraValue)> {
479 self.entries.into_iter().flatten().map(|entry| {
480 let RowEntry { var, name, value } = entry;
481 (var, name.unwrap_or_else(|| format!("_{var}")), value)
482 })
483 }
484
485 pub fn len(&self) -> usize {
486 self.len_set as usize
487 }
488
489 pub fn is_empty(&self) -> bool {
490 self.len_set == 0
491 }
492
493 pub fn contains_key(&self, key: VarId) -> bool {
494 self.entries
495 .get(key.0 as usize)
496 .is_some_and(|slot| slot.is_some())
497 }
498
499 fn ensure_slot(&mut self, key: VarId) -> usize {
502 let idx = key.0 as usize;
503 if idx >= self.entries.len() {
504 self.entries.resize_with(idx + 1, || None);
505 }
506 idx
507 }
508}
509
510#[derive(Debug, Clone, Copy, PartialEq, Eq)]
511pub enum ResultFormat {
512 Rows,
513 RowArrays,
514 Graph,
515 Combined,
516}
517
518#[derive(Debug, Clone, Copy, PartialEq, Eq)]
519pub struct ExecuteOptions {
520 pub format: ResultFormat,
521}
522
523impl Default for ExecuteOptions {
524 fn default() -> Self {
525 Self {
526 format: ResultFormat::Graph,
527 }
528 }
529}
530
531#[derive(Debug, Clone, Serialize)]
532#[serde(untagged)]
533pub enum QueryResult {
534 Rows(RowsResult),
535 RowArrays(RowArraysResult),
536 Graph(GraphResult),
537 Combined(CombinedResult),
538}
539
540#[derive(Debug, Clone, Serialize)]
541pub struct RowsResult {
542 pub rows: Vec<Row>,
543}
544
545#[derive(Debug, Clone, Serialize)]
546pub struct RowArraysResult {
547 pub columns: Vec<String>,
548 pub rows: Vec<Vec<LoraValue>>,
549}
550
551#[derive(Debug, Clone, Serialize)]
552pub struct GraphResult {
553 pub graph: HydratedGraph,
554}
555
556#[derive(Debug, Clone, Serialize)]
557pub struct CombinedResult {
558 pub columns: Vec<String>,
559 pub data: Vec<CombinedRow>,
560 pub graph: HydratedGraph,
561}
562
563#[derive(Debug, Clone, Serialize)]
564pub struct CombinedRow {
565 pub row: Vec<LoraValue>,
566}
567
568#[derive(Debug, Clone, Serialize, Default)]
569pub struct HydratedGraph {
570 pub nodes: Vec<HydratedNode>,
571 pub relationships: Vec<HydratedRelationship>,
572}
573
574#[derive(Debug, Clone, Serialize, PartialEq)]
575pub struct HydratedNode {
576 pub id: i64,
577 pub labels: Vec<String>,
578 pub properties: BTreeMap<String, LoraValue>,
579}
580
581#[derive(Debug, Clone, Serialize, PartialEq)]
582pub struct HydratedRelationship {
583 pub id: i64,
584 #[serde(rename = "startId")]
585 pub start_id: i64,
586 #[serde(rename = "endId")]
587 pub end_id: i64,
588 #[serde(rename = "type")]
589 pub rel_type: String,
590 pub properties: BTreeMap<String, LoraValue>,
591}
592
593pub fn project_rows(rows: Vec<Row>, options: ExecuteOptions) -> QueryResult {
594 match options.format {
595 ResultFormat::Rows => QueryResult::Rows(RowsResult { rows }),
596
597 ResultFormat::RowArrays => {
598 let columns = infer_columns(&rows);
599 let projected_rows = rows.iter().map(|row| row_to_array(row, &columns)).collect();
600
601 QueryResult::RowArrays(RowArraysResult {
602 columns,
603 rows: projected_rows,
604 })
605 }
606
607 ResultFormat::Graph => QueryResult::Graph(GraphResult {
608 graph: collect_hydrated_graph(&rows),
609 }),
610
611 ResultFormat::Combined => {
612 let columns = infer_columns(&rows);
613 let data = rows
614 .iter()
615 .map(|row| CombinedRow {
616 row: row_to_array(row, &columns),
617 })
618 .collect();
619
620 QueryResult::Combined(CombinedResult {
621 columns,
622 data,
623 graph: collect_hydrated_graph(&rows),
624 })
625 }
626 }
627}
628
629fn infer_columns(rows: &[Row]) -> Vec<String> {
630 rows.first()
631 .map(|row| {
632 row.iter_named()
633 .map(|(_, name, _)| name.into_owned())
634 .collect::<Vec<_>>()
635 })
636 .unwrap_or_default()
637}
638
639fn row_to_array(row: &Row, columns: &[String]) -> Vec<LoraValue> {
640 columns
643 .iter()
644 .map(|col| {
645 row.iter_named()
646 .find(|(_, name, _)| name.as_ref() == col.as_str())
647 .map(|(_, _, v)| v.clone())
648 .unwrap_or(LoraValue::Null)
649 })
650 .collect()
651}
652
653fn collect_hydrated_graph(rows: &[Row]) -> HydratedGraph {
654 let mut nodes = BTreeMap::<i64, HydratedNode>::new();
655 let mut relationships = BTreeMap::<i64, HydratedRelationship>::new();
656
657 for row in rows {
658 for (_, _, value) in row.iter_named() {
659 collect_graph_from_value(value, &mut nodes, &mut relationships);
660 }
661 }
662
663 HydratedGraph {
664 nodes: nodes.into_values().collect(),
665 relationships: relationships.into_values().collect(),
666 }
667}
668
669fn collect_graph_from_value(
670 value: &LoraValue,
671 nodes: &mut BTreeMap<i64, HydratedNode>,
672 relationships: &mut BTreeMap<i64, HydratedRelationship>,
673) {
674 match value {
675 LoraValue::List(values) => {
676 for value in values {
677 collect_graph_from_value(value, nodes, relationships);
678 }
679 }
680
681 LoraValue::Map(map) => {
682 if let Some(node) = try_as_hydrated_node(map) {
683 nodes.entry(node.id).or_insert(node);
684 return;
685 }
686
687 if let Some(rel) = try_as_hydrated_relationship(map) {
688 relationships.entry(rel.id).or_insert(rel);
689 return;
690 }
691
692 for value in map.values() {
693 collect_graph_from_value(value, nodes, relationships);
694 }
695 }
696
697 _ => {}
698 }
699}
700
701fn try_as_hydrated_node(map: &BTreeMap<String, LoraValue>) -> Option<HydratedNode> {
702 let id = match map.get("id")? {
703 LoraValue::Int(v) => *v,
704 _ => return None,
705 };
706
707 let labels = match map.get("labels")? {
708 LoraValue::List(values) => values
709 .iter()
710 .map(|v| match v {
711 LoraValue::String(s) => Some(s.clone()),
712 _ => None,
713 })
714 .collect::<Option<Vec<_>>>()?,
715 _ => return None,
716 };
717
718 let properties = match map.get("properties")? {
719 LoraValue::Map(props) => props.clone(),
720 _ => return None,
721 };
722
723 Some(HydratedNode {
724 id,
725 labels,
726 properties,
727 })
728}
729
730fn try_as_hydrated_relationship(map: &BTreeMap<String, LoraValue>) -> Option<HydratedRelationship> {
731 match map.get("kind") {
732 Some(LoraValue::String(kind)) if kind == "relationship" => {}
733 _ => return None,
734 }
735
736 let id = match map.get("id")? {
737 LoraValue::Int(v) => *v,
738 _ => return None,
739 };
740
741 let start_id = match map.get("startId").or_else(|| map.get("src"))? {
742 LoraValue::Int(v) => *v,
743 _ => return None,
744 };
745
746 let end_id = match map.get("endId").or_else(|| map.get("dst"))? {
747 LoraValue::Int(v) => *v,
748 _ => return None,
749 };
750
751 let rel_type = match map.get("type")? {
752 LoraValue::String(s) => s.clone(),
753 _ => return None,
754 };
755
756 let properties = match map.get("properties")? {
757 LoraValue::Map(props) => props.clone(),
758 _ => return None,
759 };
760
761 Some(HydratedRelationship {
762 id,
763 start_id,
764 end_id,
765 rel_type,
766 properties,
767 })
768}