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