Skip to main content

linkrs_core/error/
query.rs

1//! Query layer error type
2//!
3//! This includes errors that occur during the processes of query parsing, validation, and execution.
4//!
5//! ## Design
6//!
7//! `QueryError` is a struct with boxed source error to keep size small (~24 bytes).
8//! This follows the same pattern as `DBError` for consistency.
9
10use std::error::Error;
11
12use super::BoxedError;
13use crate::error::codes::{ErrorCode, PublicError, ToPublicError};
14use crate::error::manager::ManagerError;
15use crate::error::storage::StorageError;
16use crate::error::DBError;
17
18/// Query processing phase enumeration
19///
20/// Used to identify which phase of query processing an error occurred in
21#[derive(Debug, Clone, Copy, PartialEq, Eq)]
22pub enum QueryPhase {
23    Parse,
24    Validate,
25    Plan,
26    Optimize,
27    Execute,
28}
29
30impl std::fmt::Display for QueryPhase {
31    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
32        match self {
33            QueryPhase::Parse => write!(f, "parse"),
34            QueryPhase::Validate => write!(f, "validate"),
35            QueryPhase::Plan => write!(f, "plan"),
36            QueryPhase::Optimize => write!(f, "optimize"),
37            QueryPhase::Execute => write!(f, "execute"),
38        }
39    }
40}
41
42/// Error type during the planned node access
43///
44/// Errors that occur during the query plan traversal and validation processes
45#[derive(Debug, Clone)]
46pub enum PlanNodeVisitError {
47    VisitError {
48        node_id: Option<String>,
49        message: String,
50    },
51    TraversalError {
52        path: String,
53        message: String,
54    },
55    ValidationError {
56        node_type: String,
57        message: String,
58    },
59}
60
61impl std::fmt::Display for PlanNodeVisitError {
62    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
63        match self {
64            PlanNodeVisitError::VisitError { node_id, message } => {
65                if let Some(id) = node_id {
66                    write!(f, "Visit error at node {}: {}", id, message)
67                } else {
68                    write!(f, "Visit error: {}", message)
69                }
70            }
71            PlanNodeVisitError::TraversalError { path, message } => {
72                write!(f, "Traversal error in {}: {}", path, message)
73            }
74            PlanNodeVisitError::ValidationError { node_type, message } => {
75                write!(f, "Validation failed for {}: {}", node_type, message)
76            }
77        }
78    }
79}
80
81impl std::error::Error for PlanNodeVisitError {}
82
83impl PlanNodeVisitError {
84    pub fn visit_error(message: impl Into<String>) -> Self {
85        PlanNodeVisitError::VisitError {
86            node_id: None,
87            message: message.into(),
88        }
89    }
90
91    pub fn visit_error_with_node(node_id: impl Into<String>, message: impl Into<String>) -> Self {
92        PlanNodeVisitError::VisitError {
93            node_id: Some(node_id.into()),
94            message: message.into(),
95        }
96    }
97
98    pub fn traversal_error(path: impl Into<String>, message: impl Into<String>) -> Self {
99        PlanNodeVisitError::TraversalError {
100            path: path.into(),
101            message: message.into(),
102        }
103    }
104
105    pub fn validation_error(node_type: impl Into<String>, message: impl Into<String>) -> Self {
106        PlanNodeVisitError::ValidationError {
107            node_type: node_type.into(),
108            message: message.into(),
109        }
110    }
111}
112
113/// Query operation result type aliases
114pub type QueryResult<T> = Result<T, QueryError>;
115
116/// Structured parse error information
117///
118/// Preserves detailed error context from the parser for better error reporting.
119#[derive(Debug, Clone, PartialEq, Eq)]
120pub struct StructuredParseError {
121    /// Error category
122    pub kind: ParseErrorKind,
123    /// Human-readable error message
124    pub message: String,
125    /// Line and column position in the source
126    pub position: crate::types::Position,
127    /// Byte offset in the source (if available)
128    pub offset: Option<usize>,
129    /// The unexpected token that caused the error
130    pub unexpected_token: Option<String>,
131    /// List of expected tokens at the error location
132    pub expected_tokens: Vec<String>,
133    /// Helpful hints for fixing the error
134    pub hints: Vec<String>,
135    /// Context information (converted to string for Clone support)
136    pub context: Option<String>,
137    /// Whether the error was produced during error recovery (parsing
138    /// continued after synchronizing to the next clause boundary)
139    pub recovered: bool,
140}
141
142/// Parse error kind enumeration
143#[derive(Debug, Clone, Copy, PartialEq, Eq)]
144pub enum ParseErrorKind {
145    LexicalError,
146    SyntaxError,
147    UnexpectedToken,
148    UnterminatedString,
149    UnterminatedComment,
150    InvalidNumber,
151    InvalidEscapeSequence,
152    UnicodeEscapeError,
153    UnexpectedEndOfInput,
154    InvalidCharacter,
155    UnknownKeyword,
156    RecursionLimitExceeded,
157    UnsupportedFeature,
158    SemanticError,
159}
160
161impl std::fmt::Display for ParseErrorKind {
162    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
163        match self {
164            ParseErrorKind::LexicalError => write!(f, "Lexical error"),
165            ParseErrorKind::SyntaxError => write!(f, "Syntax error"),
166            ParseErrorKind::UnexpectedToken => write!(f, "Unexpected token"),
167            ParseErrorKind::UnterminatedString => write!(f, "Unterminated string"),
168            ParseErrorKind::UnterminatedComment => write!(f, "Unterminated comment"),
169            ParseErrorKind::InvalidNumber => write!(f, "Invalid number"),
170            ParseErrorKind::InvalidEscapeSequence => write!(f, "Invalid escape sequence"),
171            ParseErrorKind::UnicodeEscapeError => write!(f, "Unicode escape error"),
172            ParseErrorKind::UnexpectedEndOfInput => write!(f, "Unexpected end of input"),
173            ParseErrorKind::InvalidCharacter => write!(f, "Invalid character"),
174            ParseErrorKind::UnknownKeyword => write!(f, "Unknown keyword"),
175            ParseErrorKind::RecursionLimitExceeded => write!(f, "Recursion limit exceeded"),
176            ParseErrorKind::UnsupportedFeature => write!(f, "Unsupported feature"),
177            ParseErrorKind::SemanticError => write!(f, "Semantic error"),
178        }
179    }
180}
181
182impl std::fmt::Display for StructuredParseError {
183    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
184        write!(
185            f,
186            "{} at line {}, column {}: {}",
187            self.kind, self.position.line, self.position.column, self.message
188        )?;
189
190        if let Some(ref token) = self.unexpected_token {
191            writeln!(f, "\n  Unexpected token: {}", token)?;
192        }
193
194        if !self.expected_tokens.is_empty() {
195            writeln!(
196                f,
197                "\n  Expected one of: {}",
198                self.expected_tokens.join(", ")
199            )?;
200        }
201
202        if let Some(ref context) = self.context {
203            writeln!(f, "\n  Context: {}", context)?;
204        }
205
206        if !self.hints.is_empty() {
207            writeln!(f, "\n  Hint(s):")?;
208            for hint in &self.hints {
209                writeln!(f, "    - {}", hint)?;
210            }
211        }
212
213        if self.recovered {
214            writeln!(f, "\n  (recovered, parsing continued)")?;
215        }
216
217        Ok(())
218    }
219}
220
221impl std::error::Error for StructuredParseError {}
222
223/// Query error kind enumeration
224#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
225pub enum QueryErrorKind {
226    Storage,
227    Parse,
228    Planning,
229    Optimization,
230    InvalidQuery,
231    Execution,
232    Expression,
233    PlanNodeVisit,
234    Session,
235    Permission,
236    Transaction,
237    Type,
238    Timeout,
239    FeatureDisabled,
240}
241
242impl QueryErrorKind {
243    pub fn as_str(&self) -> &'static str {
244        match self {
245            QueryErrorKind::Storage => "storage",
246            QueryErrorKind::Parse => "parse",
247            QueryErrorKind::Planning => "planning",
248            QueryErrorKind::Optimization => "optimization",
249            QueryErrorKind::InvalidQuery => "invalid_query",
250            QueryErrorKind::Execution => "execution",
251            QueryErrorKind::Expression => "expression",
252            QueryErrorKind::PlanNodeVisit => "plan_node_visit",
253            QueryErrorKind::Session => "session",
254            QueryErrorKind::Permission => "permission",
255            QueryErrorKind::Transaction => "transaction",
256            QueryErrorKind::Type => "type",
257            QueryErrorKind::Timeout => "timeout",
258            QueryErrorKind::FeatureDisabled => "feature_disabled",
259        }
260    }
261}
262
263/// Query layer error type
264///
265/// Design principles:
266/// 1. Small size: Uses boxed errors to keep struct size minimal (~24 bytes)
267/// 2. Full context: Preserves error chain
268/// 3. Clone support: Can be cloned for logging/propagation
269#[derive(Debug)]
270pub struct QueryError {
271    kind: QueryErrorKind,
272    message: String,
273    source: Option<BoxedError>,
274}
275
276impl QueryError {
277    pub fn new(kind: QueryErrorKind, message: impl Into<String>) -> Self {
278        Self {
279            kind,
280            message: message.into(),
281            source: None,
282        }
283    }
284
285    pub fn with_source(mut self, source: BoxedError) -> Self {
286        self.source = Some(source);
287        self
288    }
289
290    pub fn kind(&self) -> QueryErrorKind {
291        self.kind
292    }
293
294    pub fn message(&self) -> &str {
295        &self.message
296    }
297
298    pub fn source(&self) -> &Option<BoxedError> {
299        &self.source
300    }
301
302    fn from_boxed<E: Error + Send + Sync + 'static>(kind: QueryErrorKind, error: E) -> Self {
303        Self {
304            kind,
305            message: error.to_string(),
306            source: Some(Box::new(error)),
307        }
308    }
309}
310
311impl std::fmt::Display for QueryError {
312    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
313        write!(f, "[{}] {}", self.kind.as_str(), self.message)?;
314        if let Some(ref source) = self.source {
315            write!(f, "\n  Caused by: {}", source)?;
316        }
317        Ok(())
318    }
319}
320
321impl Clone for QueryError {
322    fn clone(&self) -> Self {
323        Self {
324            kind: self.kind,
325            message: self.message.clone(),
326            source: None,
327        }
328    }
329}
330
331impl std::error::Error for QueryError {
332    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
333        self.source
334            .as_ref()
335            .map(|e| e.as_ref() as &(dyn std::error::Error + 'static))
336    }
337}
338
339impl QueryError {
340    pub fn storage(message: impl Into<String>) -> Self {
341        Self::new(QueryErrorKind::Storage, message)
342    }
343
344    pub fn parse(message: impl Into<String>) -> Self {
345        Self::new(QueryErrorKind::Parse, message)
346    }
347
348    pub fn planning(message: impl Into<String>) -> Self {
349        Self::new(QueryErrorKind::Planning, message)
350    }
351
352    pub fn optimization(message: impl Into<String>) -> Self {
353        Self::new(QueryErrorKind::Optimization, message)
354    }
355
356    pub fn invalid_query(message: impl Into<String>) -> Self {
357        Self::new(QueryErrorKind::InvalidQuery, message)
358    }
359
360    pub fn execution(message: impl Into<String>) -> Self {
361        Self::new(QueryErrorKind::Execution, message)
362    }
363
364    pub fn expression(message: impl Into<String>) -> Self {
365        Self::new(QueryErrorKind::Expression, message)
366    }
367
368    pub fn transaction(message: impl Into<String>) -> Self {
369        Self::new(QueryErrorKind::Transaction, message)
370    }
371
372    pub fn type_error(message: impl Into<String>) -> Self {
373        Self::new(QueryErrorKind::Type, message)
374    }
375
376    pub fn timeout(message: impl Into<String>) -> Self {
377        Self::new(QueryErrorKind::Timeout, message)
378    }
379
380    /// Error for operations requiring a compile-time feature that is not
381    /// enabled in this build.
382    pub fn feature_disabled(feature: &str, operation: &str) -> Self {
383        Self::new(
384            QueryErrorKind::FeatureDisabled,
385            format!(
386                "operation {operation} requires the \"{feature}\" feature, which is not enabled in this build"
387            ),
388        )
389    }
390
391    pub fn structured_parse_error(err: StructuredParseError) -> Self {
392        Self::from_boxed(QueryErrorKind::Parse, err)
393    }
394
395    pub fn parse_error(message: impl Into<String>) -> Self {
396        Self::structured_parse_error(StructuredParseError {
397            kind: ParseErrorKind::SyntaxError,
398            message: message.into(),
399            position: crate::types::Position::new(0, 0),
400            offset: None,
401            unexpected_token: None,
402            expected_tokens: Vec::new(),
403            hints: Vec::new(),
404            context: None,
405            recovered: false,
406        })
407    }
408
409    pub fn parse_error_with_offset(message: impl Into<String>, offset: usize) -> Self {
410        Self::structured_parse_error(StructuredParseError {
411            kind: ParseErrorKind::SyntaxError,
412            message: message.into(),
413            position: crate::types::Position::new(0, 0),
414            offset: Some(offset),
415            unexpected_token: None,
416            expected_tokens: Vec::new(),
417            hints: Vec::new(),
418            context: None,
419            recovered: false,
420        })
421    }
422
423    pub fn parse_error_with_location(
424        message: impl Into<String>,
425        offset: usize,
426        location: impl Into<String>,
427    ) -> Self {
428        Self::structured_parse_error(StructuredParseError {
429            kind: ParseErrorKind::SyntaxError,
430            message: message.into(),
431            position: crate::types::Position::new(0, 0),
432            offset: Some(offset),
433            unexpected_token: None,
434            expected_tokens: Vec::new(),
435            hints: vec![location.into()],
436            context: None,
437            recovered: false,
438        })
439    }
440
441    pub fn offset(&self) -> Option<usize> {
442        if self.kind == QueryErrorKind::Parse {
443            if let Some(ref source) = self.source {
444                if let Some(pe) = source.downcast_ref::<StructuredParseError>() {
445                    return pe.offset;
446                }
447            }
448        }
449        None
450    }
451
452    pub fn location(&self) -> Option<&str> {
453        if self.kind == QueryErrorKind::Parse {
454            if let Some(ref source) = self.source {
455                if let Some(pe) = source.downcast_ref::<StructuredParseError>() {
456                    if !pe.hints.is_empty() {
457                        return Some(&pe.hints[0]);
458                    }
459                }
460            }
461        }
462        None
463    }
464
465    pub fn parse_error_position(&self) -> Option<crate::types::Position> {
466        if self.kind == QueryErrorKind::Parse {
467            if let Some(ref source) = self.source {
468                if let Some(pe) = source.downcast_ref::<StructuredParseError>() {
469                    return Some(pe.position);
470                }
471            }
472        }
473        None
474    }
475
476    pub fn parse_error_kind(&self) -> Option<ParseErrorKind> {
477        if self.kind == QueryErrorKind::Parse {
478            if let Some(ref source) = self.source {
479                if let Some(pe) = source.downcast_ref::<StructuredParseError>() {
480                    return Some(pe.kind);
481                }
482            }
483        }
484        None
485    }
486
487    pub fn pipeline_parse_error<E: std::error::Error + Send + Sync + 'static>(e: E) -> Self {
488        Self::from_boxed(QueryErrorKind::Parse, e)
489    }
490
491    pub fn pipeline_validation_error<E: std::error::Error + Send + Sync + 'static>(e: E) -> Self {
492        Self::from_boxed(QueryErrorKind::InvalidQuery, e)
493    }
494
495    pub fn pipeline_planning_error<E: std::error::Error + Send + Sync + 'static>(e: E) -> Self {
496        Self::from_boxed(QueryErrorKind::Planning, e)
497    }
498
499    pub fn pipeline_optimization_error<E: std::error::Error + Send + Sync + 'static>(e: E) -> Self {
500        Self::from_boxed(QueryErrorKind::Optimization, e)
501    }
502
503    pub fn pipeline_execution_error<E: std::error::Error + Send + Sync + 'static>(e: E) -> Self {
504        Self::from_boxed(QueryErrorKind::Execution, e)
505    }
506
507    pub fn pipeline_error(phase: QueryPhase, message: String) -> Self {
508        match phase {
509            QueryPhase::Parse => Self::parse(message),
510            QueryPhase::Validate => Self::invalid_query(message),
511            QueryPhase::Plan => Self::planning(message),
512            QueryPhase::Optimize => Self::optimization(message),
513            QueryPhase::Execute => Self::execution(message),
514        }
515    }
516}
517
518impl From<StorageError> for QueryError {
519    fn from(e: StorageError) -> Self {
520        Self::from_boxed(QueryErrorKind::Storage, e)
521    }
522}
523
524impl From<DBError> for QueryError {
525    fn from(e: DBError) -> Self {
526        use crate::error::ErrorKind;
527        match e.kind() {
528            ErrorKind::Query => {
529                if let Some(ref source) = e.source() {
530                    if let Some(qe) = source.downcast_ref::<QueryError>() {
531                        return qe.clone();
532                    }
533                }
534                Self::execution(e.message().to_string())
535            }
536            ErrorKind::Storage => Self::storage(e.message()),
537            ErrorKind::Expression => Self::expression(e.message()),
538            ErrorKind::Plan => Self::execution(e.message()),
539            ErrorKind::Manager => Self::execution(e.message()),
540            ErrorKind::Validation => Self::invalid_query(e.message()),
541            ErrorKind::Io => Self::execution(e.message()),
542            ErrorKind::TypeDeduction => Self::type_error(e.message()),
543            ErrorKind::Serialization => Self::execution(e.message()),
544            ErrorKind::Index => Self::execution(e.message()),
545            ErrorKind::Transaction => Self::transaction(e.message()),
546            ErrorKind::Internal => Self::execution(e.message()),
547            ErrorKind::Session => Self::execution(e.message()),
548            ErrorKind::Auth => Self::execution(e.message()),
549            ErrorKind::Permission => Self::execution(e.message()),
550            ErrorKind::MemoryLimitExceeded => Self::execution(e.message()),
551            ErrorKind::Fulltext => Self::execution(e.message()),
552            ErrorKind::Coordinator => Self::execution(e.message()),
553            ErrorKind::Vector => Self::execution(e.message()),
554            ErrorKind::VectorCoordinator => Self::execution(e.message()),
555            ErrorKind::Search => Self::execution(e.message()),
556            ErrorKind::GraphService => Self::execution(e.message()),
557        }
558    }
559}
560
561impl From<std::io::Error> for QueryError {
562    fn from(e: std::io::Error) -> Self {
563        Self::execution(e.to_string())
564    }
565}
566
567impl From<PlanNodeVisitError> for QueryError {
568    fn from(e: PlanNodeVisitError) -> Self {
569        Self::from_boxed(QueryErrorKind::PlanNodeVisit, e)
570    }
571}
572
573impl From<ManagerError> for QueryError {
574    fn from(e: ManagerError) -> Self {
575        Self::execution(e.to_string())
576    }
577}
578
579impl ToPublicError for QueryError {
580    fn to_public_error(&self) -> PublicError {
581        PublicError::new(self.to_error_code(), self.to_public_message())
582    }
583
584    fn to_error_code(&self) -> ErrorCode {
585        match self.kind {
586            QueryErrorKind::Parse => ErrorCode::ParseError,
587            QueryErrorKind::InvalidQuery => ErrorCode::ValidationError,
588            QueryErrorKind::Planning => ErrorCode::ExecutionError,
589            QueryErrorKind::Optimization => ErrorCode::ExecutionError,
590            QueryErrorKind::Execution => ErrorCode::ExecutionError,
591            QueryErrorKind::Expression => ErrorCode::ExecutionError,
592            QueryErrorKind::Storage => ErrorCode::InternalError,
593            QueryErrorKind::PlanNodeVisit => ErrorCode::ExecutionError,
594            QueryErrorKind::Session => ErrorCode::Unauthorized,
595            QueryErrorKind::Permission => ErrorCode::PermissionDenied,
596            QueryErrorKind::Transaction => ErrorCode::ExecutionError,
597            QueryErrorKind::Type => ErrorCode::TypeError,
598            QueryErrorKind::Timeout => ErrorCode::Timeout,
599            QueryErrorKind::FeatureDisabled => ErrorCode::ValidationError,
600        }
601    }
602
603    fn to_public_message(&self) -> String {
604        match self.kind {
605            QueryErrorKind::Session => self.message.clone(),
606            QueryErrorKind::Permission => self.message.clone(),
607            QueryErrorKind::Storage => "Storage operation failed".to_string(),
608            _ => self.message.clone(),
609        }
610    }
611}
612
613#[cfg(test)]
614mod tests {
615    use super::*;
616
617    #[test]
618    fn test_queryerror_size() {
619        assert!(
620            std::mem::size_of::<QueryError>() <= 64,
621            "QueryError should be small, got {} bytes",
622            std::mem::size_of::<QueryError>()
623        );
624    }
625
626    #[test]
627    fn test_queryerror_creation() {
628        let err = QueryError::parse("test parse error");
629        assert_eq!(err.kind(), QueryErrorKind::Parse);
630        assert!(err.message().contains("test parse error"));
631    }
632
633    #[test]
634    fn test_queryerror_with_source() {
635        let storage_err = StorageError::node_not_found(
636            crate::types::VertexId::try_from_int64(42).expect("test vertex id"),
637        );
638        let query_err = QueryError::from(storage_err);
639        assert_eq!(query_err.kind(), QueryErrorKind::Storage);
640    }
641
642    #[test]
643    fn test_queryerror_clone() {
644        let err = QueryError::execution("test error");
645        let cloned = err.clone();
646        assert_eq!(err.kind(), cloned.kind());
647        assert_eq!(err.message(), cloned.message());
648    }
649
650    #[test]
651    fn test_pipeline_error() {
652        let err = QueryError::pipeline_error(QueryPhase::Parse, "parse failed".to_string());
653        assert_eq!(err.kind(), QueryErrorKind::Parse);
654
655        let err = QueryError::pipeline_error(QueryPhase::Execute, "exec failed".to_string());
656        assert_eq!(err.kind(), QueryErrorKind::Execution);
657    }
658}