icydb_core/db/query/intent/
errors.rs1use crate::{
2 db::{
3 cursor::CursorPlanError,
4 predicate::ValidateError,
5 query::plan::{
6 CursorPagingPolicyError, FluentLoadPolicyViolation, IntentKeyAccessPolicyViolation,
7 PlanError, PlannerError, PolicyPlanError,
8 },
9 response::ResponseError,
10 },
11 error::{ErrorClass, InternalError},
12};
13use thiserror::Error as ThisError;
14
15#[derive(Debug, ThisError)]
20pub enum QueryError {
21 #[error("{0}")]
22 Validate(#[from] ValidateError),
23
24 #[error("{0}")]
25 Plan(Box<PlanError>),
26
27 #[error("{0}")]
28 Intent(#[from] IntentError),
29
30 #[error("{0}")]
31 Response(#[from] ResponseError),
32
33 #[error("{0}")]
34 Execute(#[from] QueryExecuteError),
35}
36
37impl QueryError {
38 pub(crate) fn execute(err: InternalError) -> Self {
40 Self::Execute(QueryExecuteError::from(err))
41 }
42}
43
44#[derive(Debug, ThisError)]
49pub enum QueryExecuteError {
50 #[error("{0}")]
51 Corruption(InternalError),
52
53 #[error("{0}")]
54 InvariantViolation(InternalError),
55
56 #[error("{0}")]
57 Conflict(InternalError),
58
59 #[error("{0}")]
60 NotFound(InternalError),
61
62 #[error("{0}")]
63 Unsupported(InternalError),
64
65 #[error("{0}")]
66 Internal(InternalError),
67}
68
69impl QueryExecuteError {
70 #[must_use]
71 pub const fn as_internal(&self) -> &InternalError {
73 match self {
74 Self::Corruption(err)
75 | Self::InvariantViolation(err)
76 | Self::Conflict(err)
77 | Self::NotFound(err)
78 | Self::Unsupported(err)
79 | Self::Internal(err) => err,
80 }
81 }
82}
83
84impl From<InternalError> for QueryExecuteError {
85 fn from(err: InternalError) -> Self {
86 match err.class {
87 ErrorClass::Corruption => Self::Corruption(err),
88 ErrorClass::InvariantViolation => Self::InvariantViolation(err),
89 ErrorClass::Conflict => Self::Conflict(err),
90 ErrorClass::NotFound => Self::NotFound(err),
91 ErrorClass::Unsupported => Self::Unsupported(err),
92 ErrorClass::Internal => Self::Internal(err),
93 }
94 }
95}
96
97impl From<PlannerError> for QueryError {
98 fn from(err: PlannerError) -> Self {
99 match err {
100 PlannerError::Plan(err) => Self::from(*err),
101 PlannerError::Internal(err) => Self::execute(*err),
102 }
103 }
104}
105
106impl From<PlanError> for QueryError {
107 fn from(err: PlanError) -> Self {
108 Self::Plan(Box::new(err))
109 }
110}
111
112#[derive(Clone, Copy, Debug, ThisError)]
117pub enum IntentError {
118 #[error("{0}")]
119 PlanShape(#[from] PolicyPlanError),
120
121 #[error("by_ids() cannot be combined with predicates")]
122 ByIdsWithPredicate,
123
124 #[error("only() cannot be combined with predicates")]
125 OnlyWithPredicate,
126
127 #[error("multiple key access methods were used on the same query")]
128 KeyAccessConflict,
129
130 #[error(
131 "{message}",
132 message = CursorPlanError::cursor_requires_order_message()
133 )]
134 CursorRequiresOrder,
135
136 #[error(
137 "{message}",
138 message = CursorPlanError::cursor_requires_limit_message()
139 )]
140 CursorRequiresLimit,
141
142 #[error("cursor tokens can only be used with .page().execute()")]
143 CursorRequiresPagedExecution,
144
145 #[error("grouped queries require execute_grouped(...)")]
146 GroupedRequiresExecuteGrouped,
147
148 #[error("HAVING requires GROUP BY")]
149 HavingRequiresGroupBy,
150}
151
152impl From<CursorPagingPolicyError> for IntentError {
153 fn from(err: CursorPagingPolicyError) -> Self {
154 match err {
155 CursorPagingPolicyError::CursorRequiresOrder => Self::CursorRequiresOrder,
156 CursorPagingPolicyError::CursorRequiresLimit => Self::CursorRequiresLimit,
157 }
158 }
159}
160
161impl From<IntentKeyAccessPolicyViolation> for IntentError {
162 fn from(err: IntentKeyAccessPolicyViolation) -> Self {
163 match err {
164 IntentKeyAccessPolicyViolation::KeyAccessConflict => Self::KeyAccessConflict,
165 IntentKeyAccessPolicyViolation::ByIdsWithPredicate => Self::ByIdsWithPredicate,
166 IntentKeyAccessPolicyViolation::OnlyWithPredicate => Self::OnlyWithPredicate,
167 }
168 }
169}
170
171impl From<FluentLoadPolicyViolation> for IntentError {
172 fn from(err: FluentLoadPolicyViolation) -> Self {
173 match err {
174 FluentLoadPolicyViolation::CursorRequiresPagedExecution => {
175 Self::CursorRequiresPagedExecution
176 }
177 FluentLoadPolicyViolation::GroupedRequiresExecuteGrouped => {
178 Self::GroupedRequiresExecuteGrouped
179 }
180 FluentLoadPolicyViolation::CursorRequiresOrder => Self::CursorRequiresOrder,
181 FluentLoadPolicyViolation::CursorRequiresLimit => Self::CursorRequiresLimit,
182 }
183 }
184}
185
186#[cfg(test)]
191mod tests {
192 use super::*;
193 use crate::error::ErrorOrigin;
194
195 fn assert_execute_variant_for_class(err: &QueryExecuteError, class: ErrorClass) {
196 match class {
197 ErrorClass::Corruption => assert!(matches!(err, QueryExecuteError::Corruption(_))),
198 ErrorClass::InvariantViolation => {
199 assert!(matches!(err, QueryExecuteError::InvariantViolation(_)));
200 }
201 ErrorClass::Conflict => assert!(matches!(err, QueryExecuteError::Conflict(_))),
202 ErrorClass::NotFound => assert!(matches!(err, QueryExecuteError::NotFound(_))),
203 ErrorClass::Unsupported => assert!(matches!(err, QueryExecuteError::Unsupported(_))),
204 ErrorClass::Internal => assert!(matches!(err, QueryExecuteError::Internal(_))),
205 }
206 }
207
208 #[test]
209 fn query_execute_error_from_internal_preserves_class_and_origin_matrix() {
210 let cases = [
211 (ErrorClass::Corruption, ErrorOrigin::Store),
212 (ErrorClass::InvariantViolation, ErrorOrigin::Query),
213 (ErrorClass::Conflict, ErrorOrigin::Executor),
214 (ErrorClass::NotFound, ErrorOrigin::Identity),
215 (ErrorClass::Unsupported, ErrorOrigin::Cursor),
216 (ErrorClass::Internal, ErrorOrigin::Serialize),
217 ];
218
219 for (class, origin) in cases {
220 let internal = InternalError::classified(class, origin, "matrix");
221 let mapped = QueryExecuteError::from(internal);
222
223 assert_execute_variant_for_class(&mapped, class);
224 assert_eq!(mapped.as_internal().class, class);
225 assert_eq!(mapped.as_internal().origin, origin);
226 }
227 }
228
229 #[test]
230 fn planner_internal_mapping_preserves_runtime_class_and_origin() {
231 let planner_internal = PlannerError::Internal(Box::new(InternalError::classified(
232 ErrorClass::Unsupported,
233 ErrorOrigin::Cursor,
234 "cursor payload mismatch",
235 )));
236 let query_err = QueryError::from(planner_internal);
237
238 assert!(matches!(
239 query_err,
240 QueryError::Execute(QueryExecuteError::Unsupported(inner))
241 if inner.class == ErrorClass::Unsupported
242 && inner.origin == ErrorOrigin::Cursor
243 ));
244 }
245}