1use std::collections::{BTreeMap, HashMap};
2use std::fmt;
3use std::hash::{Hash, Hasher};
4
5use serde::{Deserialize, Serialize};
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
9pub struct NodeId(pub u64);
10
11impl fmt::Display for NodeId {
12 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
13 write!(f, "NodeId({})", self.0)
14 }
15}
16
17impl NodeId {
18 pub fn to_be_bytes(self) -> [u8; 8] {
20 self.0.to_be_bytes()
21 }
22
23 pub fn from_be_bytes(bytes: [u8; 8]) -> Self {
25 Self(u64::from_be_bytes(bytes))
26 }
27}
28
29#[derive(Debug, Clone, Serialize, Deserialize)]
36#[allow(missing_docs)]
37pub enum Value {
38 Null,
39 Bool(bool),
40 I64(i64),
41 F64(f64),
42 String(String),
43 List(Vec<Value>),
44 Node(Node),
46 Edge(Edge),
48 Path(PathValue),
50 Map(BTreeMap<String, Value>),
53 Date(crate::temporal::CypherDate),
55 LocalTime(crate::temporal::CypherLocalTime),
56 Time(crate::temporal::CypherTime),
57 LocalDateTime(crate::temporal::CypherLocalDateTime),
58 DateTime(crate::temporal::CypherDateTime),
59 Duration(crate::temporal::CypherDuration),
60}
61
62impl PartialEq for Value {
65 fn eq(&self, other: &Self) -> bool {
66 match (self, other) {
67 (Value::Null, Value::Null) => true,
68 (Value::Bool(a), Value::Bool(b)) => a == b,
69 (Value::I64(a), Value::I64(b)) => a == b,
70 (Value::F64(a), Value::F64(b)) => a.to_bits() == b.to_bits(),
71 (Value::String(a), Value::String(b)) => a == b,
72 (Value::List(a), Value::List(b)) => a == b,
73 (Value::Node(a), Value::Node(b)) => a == b,
74 (Value::Edge(a), Value::Edge(b)) => a == b,
75 (Value::Path(a), Value::Path(b)) => a == b,
76 (Value::Map(a), Value::Map(b)) => a == b,
77 (Value::Date(a), Value::Date(b)) => a == b,
78 (Value::LocalTime(a), Value::LocalTime(b)) => a == b,
79 (Value::Time(a), Value::Time(b)) => a == b,
80 (Value::LocalDateTime(a), Value::LocalDateTime(b)) => a == b,
81 (Value::DateTime(a), Value::DateTime(b)) => a == b,
82 (Value::Duration(a), Value::Duration(b)) => a == b,
83 _ => false,
84 }
85 }
86}
87
88impl Eq for Value {}
89
90fn hash_properties<H: Hasher>(props: &Properties, state: &mut H) {
92 let mut entries: Vec<(&String, &Value)> = props.iter().collect();
93 entries.sort_by(|a, b| a.0.cmp(b.0));
94 entries.len().hash(state);
95 for (k, v) in entries {
96 k.hash(state);
97 v.hash(state);
98 }
99}
100
101fn hash_node<H: Hasher>(n: &Node, state: &mut H) {
102 n.id.hash(state);
103 n.labels.hash(state);
104 hash_properties(&n.properties, state);
105}
106
107fn hash_edge<H: Hasher>(e: &Edge, state: &mut H) {
108 e.src.hash(state);
109 e.dst.hash(state);
110 e.label.hash(state);
111 hash_properties(&e.properties, state);
112}
113
114impl Hash for Value {
115 fn hash<H: Hasher>(&self, state: &mut H) {
116 std::mem::discriminant(self).hash(state);
117 match self {
118 Value::Null => {}
119 Value::Bool(b) => b.hash(state),
120 Value::I64(n) => n.hash(state),
121 Value::F64(f) => f.to_bits().hash(state),
122 Value::String(s) => s.hash(state),
123 Value::List(items) => items.hash(state),
124 Value::Node(n) => hash_node(n, state),
125 Value::Edge(e) => hash_edge(e, state),
126 Value::Path(p) => {
127 p.nodes.len().hash(state);
128 for n in &p.nodes {
129 hash_node(n, state);
130 }
131 p.edges.len().hash(state);
132 for e in &p.edges {
133 hash_edge(e, state);
134 }
135 }
136 Value::Map(map) => {
137 for (k, v) in map {
139 k.hash(state);
140 v.hash(state);
141 }
142 }
143 Value::Date(d) => d.hash(state),
144 Value::LocalTime(t) => t.hash(state),
145 Value::Time(t) => t.hash(state),
146 Value::LocalDateTime(dt) => dt.hash(state),
147 Value::DateTime(dt) => dt.hash(state),
148 Value::Duration(d) => d.hash(state),
149 }
150 }
151}
152
153fn fmt_properties(props: &Properties, f: &mut fmt::Formatter<'_>) -> fmt::Result {
155 let mut entries: Vec<(&String, &Value)> = props.iter().collect();
156 entries.sort_by(|a, b| a.0.cmp(b.0));
157 for (i, (k, v)) in entries.into_iter().enumerate() {
158 if i > 0 {
159 write!(f, ", ")?;
160 }
161 write!(f, "{k}: {v}")?;
162 }
163 Ok(())
164}
165
166impl fmt::Display for Value {
167 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
168 match self {
169 Value::Null => write!(f, "null"),
170 Value::Bool(b) => write!(f, "{b}"),
171 Value::I64(n) => write!(f, "{n}"),
172 Value::F64(n) => write!(f, "{n}"),
173 Value::String(s) => write!(f, "\"{s}\""),
174 Value::List(items) => {
175 write!(f, "[")?;
176 for (i, v) in items.iter().enumerate() {
177 if i > 0 {
178 write!(f, ", ")?;
179 }
180 write!(f, "{v}")?;
181 }
182 write!(f, "]")
183 }
184 Value::Node(n) => {
185 write!(f, "(")?;
186 for lbl in &n.labels {
187 write!(f, ":{lbl}")?;
188 }
189 if !n.properties.is_empty() {
190 write!(f, " {{")?;
191 fmt_properties(&n.properties, f)?;
192 write!(f, "}}")?;
193 }
194 write!(f, ")")
195 }
196 Value::Edge(e) => {
197 write!(f, "[:{} {{", e.label)?;
198 fmt_properties(&e.properties, f)?;
199 write!(f, "}}]")
200 }
201 Value::Path(p) => {
202 write!(f, "<")?;
203 if let Some(first) = p.nodes.first() {
204 write!(f, "(")?;
205 for lbl in &first.labels {
206 write!(f, ":{lbl}")?;
207 }
208 if !first.properties.is_empty() {
209 if first.labels.is_empty() {
210 write!(f, "{{")?;
211 } else {
212 write!(f, " {{")?;
213 }
214 fmt_properties(&first.properties, f)?;
215 write!(f, "}}")?;
216 }
217 write!(f, ")")?;
218 }
219 for (i, (edge, node)) in p.edges.iter().zip(p.nodes.iter().skip(1)).enumerate() {
220 let prev_node = &p.nodes[i];
222 let forward = prev_node.id == edge.src;
223 if forward {
224 write!(f, "-[:{}", edge.label)?;
225 } else {
226 write!(f, "<-[:{}", edge.label)?;
227 }
228 if !edge.properties.is_empty() {
229 write!(f, " {{")?;
230 fmt_properties(&edge.properties, f)?;
231 write!(f, "}}")?;
232 }
233 if forward {
234 write!(f, "]->(")?;
235 } else {
236 write!(f, "]-(")?;
237 }
238 for lbl in &node.labels {
239 write!(f, ":{lbl}")?;
240 }
241 if !node.properties.is_empty() {
242 if node.labels.is_empty() {
243 write!(f, "{{")?;
244 } else {
245 write!(f, " {{")?;
246 }
247 fmt_properties(&node.properties, f)?;
248 write!(f, "}}")?;
249 }
250 write!(f, ")")?;
251 }
252 write!(f, ">")
253 }
254 Value::Map(map) => {
255 write!(f, "{{")?;
256 for (i, (k, v)) in map.iter().enumerate() {
257 if i > 0 {
258 write!(f, ", ")?;
259 }
260 write!(f, "{k}: {v}")?;
261 }
262 write!(f, "}}")
263 }
264 Value::Date(d) => write!(f, "{d}"),
265 Value::LocalTime(t) => write!(f, "{t}"),
266 Value::Time(t) => write!(f, "{t}"),
267 Value::LocalDateTime(dt) => write!(f, "{dt}"),
268 Value::DateTime(dt) => write!(f, "{dt}"),
269 Value::Duration(d) => write!(f, "{d}"),
270 }
271 }
272}
273
274pub type Properties = HashMap<String, Value>;
276
277#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
279#[allow(missing_docs)]
280pub struct Node {
281 pub id: NodeId,
282 #[serde(default, alias = "label", deserialize_with = "deserialize_labels")]
285 pub labels: Vec<String>,
286 pub properties: Properties,
287}
288
289#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
291#[allow(missing_docs)]
292pub struct Edge {
293 pub src: NodeId,
294 pub dst: NodeId,
295 pub label: String,
296 pub properties: Properties,
297}
298
299#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
305#[allow(missing_docs)]
306pub struct PathValue {
307 pub nodes: Vec<Node>,
308 pub edges: Vec<Edge>,
309}
310
311impl PathValue {
312 pub fn single(node: Node) -> Self {
314 Self {
315 nodes: vec![node],
316 edges: Vec::new(),
317 }
318 }
319
320 pub fn len(&self) -> usize {
322 self.edges.len()
323 }
324
325 pub fn is_empty(&self) -> bool {
327 self.edges.is_empty()
328 }
329}
330
331#[derive(Debug, Clone, Copy, PartialEq, Eq)]
333#[allow(missing_docs)]
334pub enum Direction {
335 Outgoing,
336 Incoming,
337 Both,
338}
339
340#[derive(Serialize, Deserialize)]
342pub(crate) struct NodeRecord {
343 #[serde(default, alias = "label", deserialize_with = "deserialize_labels")]
344 pub labels: Vec<String>,
345 pub properties: Properties,
346}
347
348fn deserialize_labels<'de, D>(deserializer: D) -> std::result::Result<Vec<String>, D::Error>
351where
352 D: serde::Deserializer<'de>,
353{
354 use serde::de;
355
356 struct LabelsVisitor;
357
358 impl<'de> de::Visitor<'de> for LabelsVisitor {
359 type Value = Vec<String>;
360
361 fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
362 f.write_str("a string or list of strings")
363 }
364
365 fn visit_str<E: de::Error>(self, v: &str) -> std::result::Result<Vec<String>, E> {
366 if v.is_empty() {
367 Ok(Vec::new())
368 } else {
369 Ok(vec![v.to_string()])
370 }
371 }
372
373 fn visit_string<E: de::Error>(self, v: String) -> std::result::Result<Vec<String>, E> {
374 if v.is_empty() {
375 Ok(Vec::new())
376 } else {
377 Ok(vec![v])
378 }
379 }
380
381 fn visit_seq<A: de::SeqAccess<'de>>(
382 self,
383 mut seq: A,
384 ) -> std::result::Result<Vec<String>, A::Error> {
385 let mut labels = Vec::new();
386 while let Some(s) = seq.next_element()? {
387 labels.push(s);
388 }
389 Ok(labels)
390 }
391 }
392
393 deserializer.deserialize_any(LabelsVisitor)
394}
395
396#[derive(Debug, Clone, Copy, PartialEq, Eq)]
401pub enum QueryPhase {
402 Parse,
404 SemanticAnalysis,
407 Runtime,
409}
410
411impl fmt::Display for QueryPhase {
412 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
413 match self {
414 QueryPhase::Parse => write!(f, "parse"),
415 QueryPhase::SemanticAnalysis => write!(f, "semantic analysis"),
416 QueryPhase::Runtime => write!(f, "runtime"),
417 }
418 }
419}
420
421#[derive(Debug, Clone, Copy, PartialEq, Eq)]
426#[allow(missing_docs)]
427pub struct Span {
428 pub start: usize,
429 pub end: usize,
430 pub line: u32,
431 pub col: u32,
432}
433
434impl Span {
435 pub const fn synthetic() -> Self {
437 Self {
438 start: 0,
439 end: 0,
440 line: 0,
441 col: 0,
442 }
443 }
444
445 pub fn is_synthetic(&self) -> bool {
447 self.line == 0 && self.col == 0 && self.start == 0 && self.end == 0
448 }
449}
450
451impl fmt::Display for Span {
452 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
453 write!(f, "line {}:{}", self.line, self.col)
454 }
455}
456
457#[derive(Debug, Clone, Copy, PartialEq, Eq)]
466#[non_exhaustive]
467#[allow(missing_docs)]
468pub enum ErrorCode {
469 Other,
471 AmbiguousAggregationExpression,
472 CreatingVarLength,
473 DeleteConnectedNode,
474 DeletedEntityAccess,
475 DifferentColumnsInUnion,
476 InvalidAggregation,
477 InvalidArgumentPassingMode,
478 InvalidArgumentType,
479 InvalidArgumentValue,
480 InvalidClauseComposition,
481 InvalidDelete,
482 InvalidNumberOfArguments,
483 InvalidPropertyType,
484 InvalidUnicodeLiteral,
485 MapElementAccessByNonString,
486 MergeReadOwnWrites,
487 MissingParameter,
488 NegativeIntegerArgument,
489 NoExpressionAlias,
490 NoSingleRelationshipType,
491 NonConstantExpression,
492 NumberOutOfRange,
493 ProcedureNotFound,
494 RequiresDirectedRelationship,
495 UndefinedVariable,
496 UnknownFunction,
497 UnexpectedSyntax,
498 VariableAlreadyBound,
499 VariableTypeConflict,
500}
501
502impl ErrorCode {
503 pub fn as_str(&self) -> &'static str {
505 match self {
506 ErrorCode::Other => "Other",
507 ErrorCode::AmbiguousAggregationExpression => "AmbiguousAggregationExpression",
508 ErrorCode::CreatingVarLength => "CreatingVarLength",
509 ErrorCode::DeleteConnectedNode => "DeleteConnectedNode",
510 ErrorCode::DeletedEntityAccess => "DeletedEntityAccess",
511 ErrorCode::DifferentColumnsInUnion => "DifferentColumnsInUnion",
512 ErrorCode::InvalidAggregation => "InvalidAggregation",
513 ErrorCode::InvalidArgumentPassingMode => "InvalidArgumentPassingMode",
514 ErrorCode::InvalidArgumentType => "InvalidArgumentType",
515 ErrorCode::InvalidArgumentValue => "InvalidArgumentValue",
516 ErrorCode::InvalidClauseComposition => "InvalidClauseComposition",
517 ErrorCode::InvalidDelete => "InvalidDelete",
518 ErrorCode::InvalidNumberOfArguments => "InvalidNumberOfArguments",
519 ErrorCode::InvalidPropertyType => "InvalidPropertyType",
520 ErrorCode::InvalidUnicodeLiteral => "InvalidUnicodeLiteral",
521 ErrorCode::MapElementAccessByNonString => "MapElementAccessByNonString",
522 ErrorCode::MergeReadOwnWrites => "MergeReadOwnWrites",
523 ErrorCode::MissingParameter => "MissingParameter",
524 ErrorCode::NegativeIntegerArgument => "NegativeIntegerArgument",
525 ErrorCode::NoExpressionAlias => "NoExpressionAlias",
526 ErrorCode::NoSingleRelationshipType => "NoSingleRelationshipType",
527 ErrorCode::NonConstantExpression => "NonConstantExpression",
528 ErrorCode::NumberOutOfRange => "NumberOutOfRange",
529 ErrorCode::ProcedureNotFound => "ProcedureNotFound",
530 ErrorCode::RequiresDirectedRelationship => "RequiresDirectedRelationship",
531 ErrorCode::UndefinedVariable => "UndefinedVariable",
532 ErrorCode::UnknownFunction => "UnknownFunction",
533 ErrorCode::UnexpectedSyntax => "UnexpectedSyntax",
534 ErrorCode::VariableAlreadyBound => "VariableAlreadyBound",
535 ErrorCode::VariableTypeConflict => "VariableTypeConflict",
536 }
537 }
538}
539
540impl fmt::Display for ErrorCode {
541 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
542 f.write_str(self.as_str())
543 }
544}
545
546#[derive(Debug)]
557#[non_exhaustive]
558#[allow(missing_docs)]
559pub enum QueryError {
560 SyntaxError {
561 phase: QueryPhase,
562 code: ErrorCode,
563 message: String,
564 hint: Option<String>,
565 span: Option<Span>,
566 },
567 TypeError {
568 phase: QueryPhase,
569 code: ErrorCode,
570 message: String,
571 hint: Option<String>,
572 span: Option<Span>,
573 },
574 SemanticError {
575 phase: QueryPhase,
576 code: ErrorCode,
577 message: String,
578 hint: Option<String>,
579 span: Option<Span>,
580 },
581 EntityNotFound {
582 phase: QueryPhase,
583 code: ErrorCode,
584 message: String,
585 hint: Option<String>,
586 span: Option<Span>,
587 },
588 ArgumentError {
589 phase: QueryPhase,
590 code: ErrorCode,
591 message: String,
592 hint: Option<String>,
593 span: Option<Span>,
594 },
595 ArithmeticError {
596 phase: QueryPhase,
597 code: ErrorCode,
598 message: String,
599 hint: Option<String>,
600 span: Option<Span>,
601 },
602 ConstraintViolation {
603 phase: QueryPhase,
604 code: ErrorCode,
605 message: String,
606 hint: Option<String>,
607 span: Option<Span>,
608 },
609 ProcedureError {
610 phase: QueryPhase,
611 code: ErrorCode,
612 message: String,
613 hint: Option<String>,
614 span: Option<Span>,
615 },
616}
617
618macro_rules! query_error_fields {
620 ($self:expr) => {
621 match $self {
622 QueryError::SyntaxError {
623 phase,
624 code,
625 message,
626 hint,
627 span,
628 }
629 | QueryError::TypeError {
630 phase,
631 code,
632 message,
633 hint,
634 span,
635 }
636 | QueryError::SemanticError {
637 phase,
638 code,
639 message,
640 hint,
641 span,
642 }
643 | QueryError::EntityNotFound {
644 phase,
645 code,
646 message,
647 hint,
648 span,
649 }
650 | QueryError::ArgumentError {
651 phase,
652 code,
653 message,
654 hint,
655 span,
656 }
657 | QueryError::ArithmeticError {
658 phase,
659 code,
660 message,
661 hint,
662 span,
663 }
664 | QueryError::ConstraintViolation {
665 phase,
666 code,
667 message,
668 hint,
669 span,
670 }
671 | QueryError::ProcedureError {
672 phase,
673 code,
674 message,
675 hint,
676 span,
677 } => (phase, code, message, hint, span),
678 }
679 };
680}
681
682impl QueryError {
683 pub fn phase(&self) -> QueryPhase {
685 let (phase, _, _, _, _) = query_error_fields!(self);
686 *phase
687 }
688
689 pub fn kind(&self) -> &'static str {
691 match self {
692 QueryError::SyntaxError { .. } => "SyntaxError",
693 QueryError::TypeError { .. } => "TypeError",
694 QueryError::SemanticError { .. } => "SemanticError",
695 QueryError::EntityNotFound { .. } => "EntityNotFound",
696 QueryError::ArgumentError { .. } => "ArgumentError",
697 QueryError::ArithmeticError { .. } => "ArithmeticError",
698 QueryError::ConstraintViolation { .. } => "ConstraintViolation",
699 QueryError::ProcedureError { .. } => "ProcedureError",
700 }
701 }
702
703 pub fn code(&self) -> ErrorCode {
705 let (_, code, _, _, _) = query_error_fields!(self);
706 *code
707 }
708
709 pub fn message(&self) -> &str {
711 let (_, _, message, _, _) = query_error_fields!(self);
712 message.as_str()
713 }
714
715 pub fn hint(&self) -> Option<&str> {
717 let (_, _, _, hint, _) = query_error_fields!(self);
718 hint.as_deref()
719 }
720
721 pub fn span(&self) -> Option<Span> {
723 let (_, _, _, _, span) = query_error_fields!(self);
724 *span
725 }
726
727 pub fn with_hint(mut self, hint: impl Into<String>) -> Self {
729 let (_, _, _, hint_field, _) = query_error_fields!(&mut self);
730 *hint_field = Some(hint.into());
731 self
732 }
733
734 pub fn with_span(mut self, new_span: Span) -> Self {
737 if new_span.is_synthetic() {
738 return self;
739 }
740 let (_, _, _, _, span_field) = query_error_fields!(&mut self);
741 *span_field = Some(new_span);
742 self
743 }
744
745 pub fn with_code(mut self, new_code: ErrorCode) -> Self {
747 let (_, code_field, _, _, _) = query_error_fields!(&mut self);
748 *code_field = new_code;
749 self
750 }
751}
752
753impl fmt::Display for QueryError {
754 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
755 let (phase, code, message, hint, span) = query_error_fields!(self);
756 match span {
757 Some(s) => write!(f, "{}({}) at {}: {}", self.kind(), code, s, message)?,
758 None => write!(f, "{}({}) at {}: {}", self.kind(), code, phase, message)?,
759 }
760 if let Some(h) = hint {
761 write!(f, "\n hint: {h}")?;
762 }
763 Ok(())
764 }
765}
766
767impl std::error::Error for QueryError {}
768
769#[derive(Debug)]
774#[non_exhaustive]
775#[allow(missing_docs)]
776pub enum GraphError {
777 Storage {
778 source: rusqlite::Error,
779 hint: Option<String>,
780 },
781 Serialization {
782 context: String,
783 source: String,
784 hint: Option<String>,
785 },
786 Query(QueryError),
788 NodeNotFound {
789 id: NodeId,
790 hint: Option<String>,
791 },
792 EdgeNotFound {
793 src: NodeId,
794 label: String,
795 dst: NodeId,
796 hint: Option<String>,
797 },
798 HasEdges {
799 id: NodeId,
800 hint: Option<String>,
801 },
802 Transaction {
804 message: String,
805 hint: Option<String>,
806 },
807 IndexAlreadyExists {
808 label: String,
809 property: String,
810 hint: Option<String>,
811 },
812 IndexNotFound {
813 label: String,
814 property: String,
815 hint: Option<String>,
816 },
817 InvalidName {
818 name: String,
819 hint: Option<String>,
820 },
821 SizeLimit {
822 what: String,
823 limit: usize,
824 actual: usize,
825 hint: Option<String>,
826 },
827 SchemaMismatch {
828 found: u64,
829 supported: u64,
830 hint: Option<String>,
831 },
832}
833
834impl fmt::Display for GraphError {
835 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
836 match self {
837 GraphError::Storage { source, hint } => {
838 write!(f, "storage error: {source}")?;
839 fmt_hint(f, hint.as_deref())
840 }
841 GraphError::Serialization {
842 context,
843 source,
844 hint,
845 } => {
846 write!(f, "serialization error ({context}): {source}")?;
847 fmt_hint(f, hint.as_deref())
848 }
849 GraphError::Query(q) => q.fmt(f),
850 GraphError::NodeNotFound { id, hint } => {
851 write!(f, "node not found: {id}")?;
852 fmt_hint(f, hint.as_deref())
853 }
854 GraphError::EdgeNotFound {
855 src,
856 label,
857 dst,
858 hint,
859 } => {
860 write!(f, "edge not found: {src} -[:{label}]-> {dst}")?;
861 fmt_hint(f, hint.as_deref())
862 }
863 GraphError::HasEdges { id, hint } => {
864 write!(
865 f,
866 "cannot delete node {id} because it still has edges; use DETACH DELETE to remove edges too"
867 )?;
868 fmt_hint(f, hint.as_deref())
869 }
870 GraphError::Transaction { message, hint } => {
871 write!(f, "transaction error: {message}")?;
872 fmt_hint(f, hint.as_deref())
873 }
874 GraphError::IndexAlreadyExists {
875 label,
876 property,
877 hint,
878 } => {
879 write!(f, "index already exists: {label}.{property}")?;
880 fmt_hint(f, hint.as_deref())
881 }
882 GraphError::IndexNotFound {
883 label,
884 property,
885 hint,
886 } => {
887 write!(f, "index not found: {label}.{property}")?;
888 fmt_hint(f, hint.as_deref())
889 }
890 GraphError::InvalidName { name, hint } => {
891 write!(
892 f,
893 "invalid name '{name}': must contain only ASCII letters, digits, or underscores"
894 )?;
895 fmt_hint(f, hint.as_deref())
896 }
897 GraphError::SizeLimit {
898 what,
899 limit,
900 actual,
901 hint,
902 } => {
903 write!(
904 f,
905 "{what} ({actual} bytes) exceeds maximum of {limit} bytes"
906 )?;
907 fmt_hint(f, hint.as_deref())
908 }
909 GraphError::SchemaMismatch {
910 found,
911 supported,
912 hint,
913 } => {
914 write!(
915 f,
916 "schema version mismatch: database is v{found}, this library supports up to v{supported}"
917 )?;
918 fmt_hint(f, hint.as_deref())
919 }
920 }
921 }
922}
923
924fn fmt_hint(f: &mut fmt::Formatter<'_>, hint: Option<&str>) -> fmt::Result {
925 if let Some(h) = hint {
926 write!(f, "\n hint: {h}")?;
927 }
928 Ok(())
929}
930
931impl std::error::Error for GraphError {
932 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
933 match self {
934 GraphError::Storage { source, .. } => Some(source),
935 GraphError::Query(q) => Some(q),
936 _ => None,
937 }
938 }
939}
940
941impl From<rusqlite::Error> for GraphError {
942 fn from(source: rusqlite::Error) -> Self {
943 GraphError::Storage { source, hint: None }
944 }
945}
946
947impl From<QueryError> for GraphError {
948 fn from(error: QueryError) -> Self {
949 GraphError::Query(error)
950 }
951}
952
953impl GraphError {
954 pub fn syntax(message: impl Into<String>) -> Self {
959 GraphError::Query(QueryError::SyntaxError {
960 phase: QueryPhase::Parse,
961 code: ErrorCode::Other,
962 message: message.into(),
963 hint: None,
964 span: None,
965 })
966 }
967
968 pub fn semantic(message: impl Into<String>) -> Self {
970 GraphError::Query(QueryError::SemanticError {
971 phase: QueryPhase::SemanticAnalysis,
972 code: ErrorCode::Other,
973 message: message.into(),
974 hint: None,
975 span: None,
976 })
977 }
978
979 pub fn constraint(message: impl Into<String>) -> Self {
981 GraphError::Query(QueryError::ConstraintViolation {
982 phase: QueryPhase::Runtime,
983 code: ErrorCode::Other,
984 message: message.into(),
985 hint: None,
986 span: None,
987 })
988 }
989
990 pub fn type_error(phase: QueryPhase, message: impl Into<String>) -> Self {
992 GraphError::Query(QueryError::TypeError {
993 phase,
994 code: ErrorCode::Other,
995 message: message.into(),
996 hint: None,
997 span: None,
998 })
999 }
1000
1001 pub fn argument(phase: QueryPhase, message: impl Into<String>) -> Self {
1003 GraphError::Query(QueryError::ArgumentError {
1004 phase,
1005 code: ErrorCode::Other,
1006 message: message.into(),
1007 hint: None,
1008 span: None,
1009 })
1010 }
1011
1012 pub fn undefined_variable(name: impl fmt::Display) -> Self {
1016 GraphError::Query(QueryError::SemanticError {
1017 phase: QueryPhase::SemanticAnalysis,
1018 code: ErrorCode::UndefinedVariable,
1019 message: format!("variable `{name}` is not defined"),
1020 hint: None,
1021 span: None,
1022 })
1023 }
1024
1025 pub fn procedure_not_found(name: impl fmt::Display) -> Self {
1027 GraphError::Query(QueryError::ProcedureError {
1028 phase: QueryPhase::SemanticAnalysis,
1029 code: ErrorCode::ProcedureNotFound,
1030 message: format!("unknown procedure `{name}`"),
1031 hint: None,
1032 span: None,
1033 })
1034 }
1035
1036 pub fn invalid_argument_type(
1038 phase: QueryPhase,
1039 function: impl fmt::Display,
1040 got: impl fmt::Display,
1041 ) -> Self {
1042 GraphError::Query(QueryError::TypeError {
1043 phase,
1044 code: ErrorCode::InvalidArgumentType,
1045 message: format!("{got} is not a valid argument type for {function}"),
1046 hint: None,
1047 span: None,
1048 })
1049 }
1050
1051 pub fn invalid_argument_value(
1053 phase: QueryPhase,
1054 function: impl fmt::Display,
1055 message: impl fmt::Display,
1056 ) -> Self {
1057 GraphError::Query(QueryError::ArgumentError {
1058 phase,
1059 code: ErrorCode::InvalidArgumentValue,
1060 message: format!("{function}: {message}"),
1061 hint: None,
1062 span: None,
1063 })
1064 }
1065
1066 pub fn number_out_of_range(phase: QueryPhase, message: impl Into<String>) -> Self {
1068 GraphError::Query(QueryError::ArithmeticError {
1069 phase,
1070 code: ErrorCode::NumberOutOfRange,
1071 message: message.into(),
1072 hint: None,
1073 span: None,
1074 })
1075 }
1076
1077 pub fn query(phase: QueryPhase, code: ErrorCode, message: impl Into<String>) -> Self {
1079 let message = message.into();
1080 let mk = |kind: fn(QueryPhase, ErrorCode, String) -> QueryError| -> Self {
1081 GraphError::Query(kind(phase, code, message))
1082 };
1083 match code {
1085 ErrorCode::InvalidUnicodeLiteral
1086 | ErrorCode::InvalidClauseComposition
1087 | ErrorCode::UnexpectedSyntax => mk(QueryError::syntax_with),
1088 ErrorCode::InvalidArgumentType
1089 | ErrorCode::InvalidPropertyType
1090 | ErrorCode::MapElementAccessByNonString
1091 | ErrorCode::DeletedEntityAccess => mk(QueryError::type_with),
1092 ErrorCode::UndefinedVariable
1093 | ErrorCode::VariableAlreadyBound
1094 | ErrorCode::VariableTypeConflict
1095 | ErrorCode::AmbiguousAggregationExpression
1096 | ErrorCode::CreatingVarLength
1097 | ErrorCode::DifferentColumnsInUnion
1098 | ErrorCode::InvalidAggregation
1099 | ErrorCode::InvalidArgumentPassingMode
1100 | ErrorCode::InvalidArgumentValue
1101 | ErrorCode::InvalidDelete
1102 | ErrorCode::InvalidNumberOfArguments
1103 | ErrorCode::NegativeIntegerArgument
1104 | ErrorCode::NoExpressionAlias
1105 | ErrorCode::NoSingleRelationshipType
1106 | ErrorCode::NonConstantExpression
1107 | ErrorCode::RequiresDirectedRelationship => mk(QueryError::semantic_with),
1108 ErrorCode::MissingParameter => mk(QueryError::argument_with),
1109 ErrorCode::NumberOutOfRange => mk(QueryError::arithmetic_with),
1110 ErrorCode::DeleteConnectedNode | ErrorCode::MergeReadOwnWrites => {
1111 mk(QueryError::constraint_with)
1112 }
1113 ErrorCode::ProcedureNotFound => mk(QueryError::procedure_with),
1114 ErrorCode::UnknownFunction => mk(QueryError::syntax_with),
1115 ErrorCode::Other => mk(QueryError::semantic_with),
1116 }
1117 }
1118
1119 pub fn with_hint(mut self, hint: impl Into<String>) -> Self {
1123 match &mut self {
1124 GraphError::Query(q) => {
1125 let (_, _, _, h, _) = query_error_fields!(q);
1126 *h = Some(hint.into());
1127 }
1128 GraphError::Storage { hint: h, .. }
1129 | GraphError::Serialization { hint: h, .. }
1130 | GraphError::NodeNotFound { hint: h, .. }
1131 | GraphError::EdgeNotFound { hint: h, .. }
1132 | GraphError::HasEdges { hint: h, .. }
1133 | GraphError::Transaction { hint: h, .. }
1134 | GraphError::IndexAlreadyExists { hint: h, .. }
1135 | GraphError::IndexNotFound { hint: h, .. }
1136 | GraphError::InvalidName { hint: h, .. }
1137 | GraphError::SizeLimit { hint: h, .. }
1138 | GraphError::SchemaMismatch { hint: h, .. } => *h = Some(hint.into()),
1139 }
1140 self
1141 }
1142
1143 pub fn with_code(mut self, code: ErrorCode) -> Self {
1145 if let GraphError::Query(q) = &mut self {
1146 let (_, c, _, _, _) = query_error_fields!(q);
1147 *c = code;
1148 }
1149 self
1150 }
1151
1152 pub fn with_span(self, span: Span) -> Self {
1155 if span.is_synthetic() {
1156 return self;
1157 }
1158 match self {
1159 GraphError::Query(q) => GraphError::Query(q.with_span(span)),
1160 other => other,
1161 }
1162 }
1163
1164 pub fn serialization(context: impl Into<String>, source: impl fmt::Display) -> Self {
1166 GraphError::Serialization {
1167 context: context.into(),
1168 source: source.to_string(),
1169 hint: None,
1170 }
1171 }
1172
1173 pub fn transaction(message: impl Into<String>) -> Self {
1175 GraphError::Transaction {
1176 message: message.into(),
1177 hint: None,
1178 }
1179 }
1180}
1181
1182impl QueryError {
1184 fn syntax_with(phase: QueryPhase, code: ErrorCode, message: String) -> Self {
1185 QueryError::SyntaxError {
1186 phase,
1187 code,
1188 message,
1189 hint: None,
1190 span: None,
1191 }
1192 }
1193 fn type_with(phase: QueryPhase, code: ErrorCode, message: String) -> Self {
1194 QueryError::TypeError {
1195 phase,
1196 code,
1197 message,
1198 hint: None,
1199 span: None,
1200 }
1201 }
1202 fn semantic_with(phase: QueryPhase, code: ErrorCode, message: String) -> Self {
1203 QueryError::SemanticError {
1204 phase,
1205 code,
1206 message,
1207 hint: None,
1208 span: None,
1209 }
1210 }
1211 fn argument_with(phase: QueryPhase, code: ErrorCode, message: String) -> Self {
1212 QueryError::ArgumentError {
1213 phase,
1214 code,
1215 message,
1216 hint: None,
1217 span: None,
1218 }
1219 }
1220 fn arithmetic_with(phase: QueryPhase, code: ErrorCode, message: String) -> Self {
1221 QueryError::ArithmeticError {
1222 phase,
1223 code,
1224 message,
1225 hint: None,
1226 span: None,
1227 }
1228 }
1229 fn constraint_with(phase: QueryPhase, code: ErrorCode, message: String) -> Self {
1230 QueryError::ConstraintViolation {
1231 phase,
1232 code,
1233 message,
1234 hint: None,
1235 span: None,
1236 }
1237 }
1238 fn procedure_with(phase: QueryPhase, code: ErrorCode, message: String) -> Self {
1239 QueryError::ProcedureError {
1240 phase,
1241 code,
1242 message,
1243 hint: None,
1244 span: None,
1245 }
1246 }
1247}
1248
1249pub type Result<T> = std::result::Result<T, GraphError>;
1251
1252pub fn validate_name(name: &str) -> Result<()> {
1254 if name.is_empty() || !name.bytes().all(|b| b.is_ascii_alphanumeric() || b == b'_') {
1255 return Err(GraphError::InvalidName {
1256 name: name.to_string(),
1257 hint: None,
1258 });
1259 }
1260 Ok(())
1261}
1262
1263pub fn validate_name_length(name: &str, max_bytes: usize) -> Result<()> {
1265 if name.len() > max_bytes {
1266 return Err(GraphError::SizeLimit {
1267 what: format!("name '{}...'", &name[..max_bytes.min(32)]),
1268 limit: max_bytes,
1269 actual: name.len(),
1270 hint: None,
1271 });
1272 }
1273 Ok(())
1274}
1275
1276fn value_byte_size(val: &Value) -> usize {
1282 match val {
1283 Value::Null | Value::Bool(_) | Value::I64(_) | Value::F64(_) => 8,
1284 Value::String(s) => s.len(),
1285 Value::List(items) => items.iter().map(value_byte_size).sum(),
1286 Value::Node(n) => node_byte_size(n),
1287 Value::Edge(e) => edge_byte_size(e),
1288 Value::Path(p) => {
1289 p.nodes.iter().map(node_byte_size).sum::<usize>()
1290 + p.edges.iter().map(edge_byte_size).sum::<usize>()
1291 }
1292 Value::Map(map) => map.iter().map(|(k, v)| k.len() + value_byte_size(v)).sum(),
1293 Value::Date(_) => 12,
1294 Value::LocalTime(_) => 16,
1295 Value::Time(_) => 20,
1296 Value::LocalDateTime(_) => 20,
1297 Value::DateTime(_) => 28,
1298 Value::Duration(_) => 32,
1299 }
1300}
1301
1302fn node_byte_size(n: &Node) -> usize {
1303 8 + n.labels.iter().map(|l| l.len()).sum::<usize>()
1304 + n.properties
1305 .iter()
1306 .map(|(k, v)| k.len() + value_byte_size(v))
1307 .sum::<usize>()
1308}
1309
1310fn edge_byte_size(e: &Edge) -> usize {
1311 16 + e.label.len()
1312 + e.properties
1313 .iter()
1314 .map(|(k, v)| k.len() + value_byte_size(v))
1315 .sum::<usize>()
1316}
1317
1318pub fn validate_properties(
1320 label: &str,
1321 properties: &Properties,
1322 max_name_bytes: usize,
1323 max_value_bytes: usize,
1324) -> Result<()> {
1325 validate_name_length(label, max_name_bytes)?;
1326 for (key, val) in properties {
1327 validate_name_length(key, max_name_bytes)?;
1328 let size = value_byte_size(val);
1329 if size > max_value_bytes {
1330 return Err(GraphError::SizeLimit {
1331 what: format!("property '{key}' value"),
1332 limit: max_value_bytes,
1333 actual: size,
1334 hint: None,
1335 });
1336 }
1337 }
1338 Ok(())
1339}