Skip to main content

nodedb_lite/error/
from_sql.rs

1// SPDX-License-Identifier: Apache-2.0
2
3//! The class of each SQL planner error.
4
5use nodedb_sql::SqlError;
6
7use super::LiteError;
8
9/// Exhaustive, so a new planner error picks its own class rather than
10/// defaulting to a plan error.
11impl From<SqlError> for LiteError {
12    fn from(e: SqlError) -> Self {
13        match e {
14            // A value of the wrong kind for its column. `42804`.
15            mismatch @ (SqlError::TypeMismatch { .. }
16            | SqlError::VectorElementNotNumeric { .. }) => Self::TypeMismatch {
17                collection: String::new(),
18                detail: mismatch.to_string(),
19            },
20            // A value past the range of its type. `22003`.
21            range @ (SqlError::ConstantOverflow { .. }
22            | SqlError::NumericLiteralOutOfRange { .. }
23            | SqlError::IntegerOutOfRange { .. }
24            | SqlError::FloatOutOfRange { .. }
25            | SqlError::DecimalOutOfRange { .. }) => Self::NumericValueOutOfRange {
26                detail: range.to_string(),
27            },
28            // A constant the planner evaluated and could not compute. `22`.
29            data @ (SqlError::DivisionByZero | SqlError::DataException { .. }) => {
30                Self::DataException {
31                    detail: data.to_string(),
32                }
33            }
34            SqlError::UndefinedObject { kind, name } => Self::UndefinedObject {
35                object: format!("{kind} \"{name}\""),
36            },
37            SqlError::ObjectNotInPrerequisiteState { object, detail } => {
38                Self::ObjectNotReady { object, detail }
39            }
40            SqlError::Unsupported { detail } => Self::Unsupported { detail },
41            // A statement the object's kind does not take, such as a write to
42            // a continuous aggregate. Origin gives it the bad-request class.
43            SqlError::WrongObjectType { detail } => Self::BadRequest { detail },
44            // Refusals of a construct NodeDB does not implement. `0A000`.
45            refused @ (SqlError::SequencePerRowUnsupported { .. }
46            | SqlError::SearchFunctionOutsideSearch { .. }
47            | SqlError::UnsupportedConstraint { .. }
48            | SqlError::ConflictingEngineClause { .. }) => Self::Unsupported {
49                detail: refused.to_string(),
50            },
51            retry @ SqlError::RetryableSchemaChanged { .. } => Self::RetryableRefusal {
52                detail: retry.to_string(),
53            },
54            // Statement errors the caller must fix.
55            plan @ (SqlError::Parse { .. }
56            | SqlError::UnknownTable { .. }
57            | SqlError::UndefinedFunction { .. }
58            | SqlError::UnknownColumn { .. }
59            | SqlError::TextColumn { .. }
60            | SqlError::AmbiguousColumn { .. }
61            | SqlError::Arity { .. }
62            | SqlError::InvalidLimitValue { .. }
63            | SqlError::UnevaluableDefault { .. }
64            | SqlError::SetvalInColumnDefault { .. }
65            | SqlError::InvalidFunction { .. }
66            | SqlError::InvalidWindowFrame { .. }
67            | SqlError::MissingField { .. }
68            | SqlError::InsertColumnArityMismatch { .. }
69            | SqlError::PositionalKvInsertUnsupported { .. }
70            | SqlError::InvalidIdentifier { .. }
71            | SqlError::ReservedIdentifier { .. }
72            | SqlError::InvalidRecursiveSetOp { .. }
73            | SqlError::InvalidRecursiveSelfRef { .. }
74            | SqlError::RecursiveColumnMismatch { .. }
75            | SqlError::DuplicateRecursiveColumn { .. }
76            | SqlError::RecursionDepthExceeded { .. }
77            | SqlError::CollectionDeactivated { .. }) => Self::Query(plan.to_string()),
78        }
79    }
80}
81
82#[cfg(test)]
83mod tests {
84    use nodedb_types::error::{ErrorCode, NodeDbError};
85
86    use super::*;
87
88    fn public(e: SqlError) -> NodeDbError {
89        NodeDbError::from(LiteError::from(e))
90    }
91
92    #[test]
93    fn a_text_element_in_a_vector_literal_is_a_type_mismatch() {
94        let e = LiteError::from(SqlError::VectorElementNotNumeric {
95            column: "embedding".into(),
96            dim: 3,
97            element: "text 'a'".into(),
98        });
99        assert!(matches!(e, LiteError::TypeMismatch { .. }), "{e:?}");
100        assert_eq!(NodeDbError::from(e).code(), ErrorCode::TYPE_MISMATCH);
101        let plain = public(SqlError::TypeMismatch { detail: "x".into() });
102        assert_eq!(plain.code(), ErrorCode::TYPE_MISMATCH);
103    }
104
105    #[test]
106    fn an_out_of_range_value_is_numeric_value_out_of_range() {
107        for e in [
108            SqlError::NumericLiteralOutOfRange {
109                literal: "1e40".into(),
110            },
111            SqlError::ConstantOverflow { detail: "x".into() },
112            SqlError::IntegerOutOfRange {
113                column: "n".into(),
114                value: i64::MAX,
115                declared_type: "INTEGER",
116            },
117        ] {
118            let mapped = public(e);
119            assert_eq!(mapped.code(), ErrorCode::OVERFLOW);
120            assert!(mapped.is_overflow(), "{mapped}");
121        }
122    }
123
124    #[test]
125    fn a_failed_constant_is_a_data_exception() {
126        let mapped = public(SqlError::DataException { detail: "x".into() });
127        assert_eq!(mapped.code(), ErrorCode::DATA_EXCEPTION);
128    }
129
130    #[test]
131    fn a_statement_error_is_a_plan_error() {
132        for e in [
133            SqlError::Parse { detail: "x".into() },
134            SqlError::UndefinedFunction { name: "f".into() },
135        ] {
136            assert_eq!(public(e).code(), ErrorCode::PLAN_ERROR);
137        }
138        assert_eq!(
139            NodeDbError::from(LiteError::Query("x".into())).code(),
140            ErrorCode::PLAN_ERROR
141        );
142    }
143
144    #[test]
145    fn an_absent_object_is_an_undefined_object() {
146        let mapped = public(SqlError::UndefinedObject {
147            kind: "sequence",
148            name: "s".into(),
149        });
150        assert_eq!(mapped.code(), ErrorCode::UNDEFINED_OBJECT);
151    }
152}