Skip to main content

linkrs_query/optimizer/
error.rs

1//! Optimizer error type
2//!
3//! Define the error types related to query optimizers, including:
4//! The cost calculation was incorrect.
5//! Error in plan optimization
6//! The statistical information is incorrect.
7//! The rules were applied incorrectly.
8
9use thiserror::Error;
10
11use crate::planning::plan::factorization::FactorizationError;
12
13/// Optimizer error type
14#[derive(Error, Debug, Clone)]
15pub enum OptimizeError {
16    /// Node type not supported
17    #[error("Unsupported node type: {0}")]
18    UnsupportedNodeType(String),
19
20    /// Statistical information is missing.
21    #[error("Missing statistics: {0}")]
22    MissingStatistics(String),
23
24    /// Miscalculation of the costs
25    #[error("Calculation error: {0}")]
26    CalculationError(String),
27
28    /// The plan to optimize the errors has failed.
29    #[error("Plan optimization error: {0}")]
30    PlanOptimizationError(String),
31
32    /// The rule was applied incorrectly.
33    #[error("Rule application error: {0}")]
34    RuleApplicationError(String),
35
36    /// There is an error in the statistical information.
37    #[error("Statistics error: {0}")]
38    StatisticsError(String),
39
40    /// The index selection was incorrect.
41    #[error("Index selection error: {0}")]
42    IndexSelectionError(String),
43
44    /// Error in the optimization of the connection sequence.
45    #[error("Join order optimization error: {0}")]
46    JoinOrderError(String),
47
48    /// Expression conversion error.
49    #[error("Expression transform error: {0}")]
50    ExpressionTransformError(String),
51
52    /// Internal optimization error
53    #[error("Internal optimization error: {0}")]
54    InternalError(String),
55
56    /// Heuristic optimization failed
57    #[error("Heuristic optimization failed: {0}")]
58    HeuristicFailed(String),
59
60    /// Cost-based optimization failed
61    #[error("Cost-based optimization failed: {0}")]
62    CostBasedFailed(String),
63
64    /// Pipeline configuration error
65    #[error("Pipeline configuration error: {0}")]
66    ConfigurationError(String),
67
68    /// Factorization invariant violation (plan is malformed).
69    ///
70    /// Carries the structured [`FactorizationError`] rather than a flattened
71    /// string so upstream layers can downcast and react to the specific
72    /// invariant that was violated, while `Display`/`source` still render
73    /// the full message.
74    #[error("Factorization error: {0}")]
75    FactorizationError(
76        #[from]
77        #[source]
78        FactorizationError,
79    ),
80}
81
82/// Optimizer result type
83pub type OptimizeResult<T> = Result<T, OptimizeError>;
84
85/// Cost calculation related errors
86#[derive(Error, Debug, Clone)]
87pub enum CostError {
88    /// Unsupported node type
89    #[error("Unsupported node type: {0}")]
90    UnsupportedNodeType(String),
91
92    /// Missing statistics
93    #[error("Missing statistics: {0}")]
94    MissingStatistics(String),
95
96    /// Calculation error
97    #[error("Calculation error: {0}")]
98    CalculationError(String),
99}
100
101/// Type of result for the cost calculation
102pub type CostResult<T> = Result<T, CostError>;
103
104impl From<CostError> for OptimizeError {
105    fn from(err: CostError) -> Self {
106        match err {
107            CostError::UnsupportedNodeType(msg) => OptimizeError::UnsupportedNodeType(msg),
108            CostError::MissingStatistics(msg) => OptimizeError::MissingStatistics(msg),
109            CostError::CalculationError(msg) => OptimizeError::CalculationError(msg),
110        }
111    }
112}
113
114// `From<FactorizationError> for OptimizeError` is generated by the `#[from]`
115// attribute on `OptimizeError::FactorizationError`, so the structured error is
116// preserved (no `to_string()` flattening) and `?` still works at every call
117// site. `FactorizationError` is `Error + Clone + Send + Sync + 'static`, which
118// satisfies both `OptimizeError: Clone` and the boxed-source bound used by
119// `QueryError::pipeline_optimization_error`.
120
121#[cfg(test)]
122mod tests {
123    use super::*;
124
125    #[test]
126    fn test_optimize_error_display() {
127        let err = OptimizeError::UnsupportedNodeType("UnknownNode".to_string());
128        assert!(err.to_string().contains("Unsupported node type"));
129
130        let err = OptimizeError::MissingStatistics("vertex_count".to_string());
131        assert!(err.to_string().contains("Missing statistics"));
132    }
133
134    #[test]
135    fn test_cost_error_conversion() {
136        let cost_err = CostError::CalculationError("division by zero".to_string());
137        let opt_err: OptimizeError = cost_err.into();
138        assert!(matches!(opt_err, OptimizeError::CalculationError(_)));
139    }
140
141    #[test]
142    fn test_factorization_error_preserves_structure() {
143        let fact_err = FactorizationError::TooManyUnflatGroups(3);
144        let opt_err: OptimizeError = fact_err.clone().into();
145        // Structured payload is preserved, not flattened to a bare string.
146        match &opt_err {
147            OptimizeError::FactorizationError(inner) => {
148                assert_eq!(*inner, fact_err);
149                assert_eq!(
150                    inner.to_string(),
151                    "at most one unflat group allowed, found 3"
152                );
153            }
154            other => panic!("expected FactorizationError, got {other:?}"),
155        }
156        // Display still renders the full message, and `source()` is wired.
157        assert!(opt_err.to_string().contains("found 3"));
158        let src = std::error::Error::source(&opt_err).expect("source wired");
159        assert!(src.downcast_ref::<FactorizationError>().is_some());
160        // `Clone` still holds for the whole enum.
161        let _cloned = opt_err.clone();
162    }
163}