1use reifydb_type::{
5 error::{Diagnostic, Error, IntoDiagnostic},
6 fragment::Fragment,
7 value::r#type::Type,
8};
9
10#[derive(Debug, thiserror::Error)]
11pub enum CastError {
12 #[error("unsupported cast from {from_type} to {to_type}")]
13 UnsupportedCast {
14 fragment: Fragment,
15 from_type: Type,
16 to_type: Type,
17 },
18
19 #[error("failed to cast to {target}")]
20 InvalidNumber {
21 fragment: Fragment,
22 target: Type,
23 cause: Diagnostic,
24 },
25
26 #[error("failed to cast to bool")]
27 InvalidBoolean {
28 fragment: Fragment,
29 cause: Diagnostic,
30 },
31
32 #[error("failed to cast to {target}")]
33 InvalidUuid {
34 fragment: Fragment,
35 target: Type,
36 cause: Diagnostic,
37 },
38
39 #[error("failed to cast to {target}")]
40 InvalidTemporal {
41 fragment: Fragment,
42 target: Type,
43 cause: Diagnostic,
44 },
45
46 #[error("failed to cast BLOB to UTF8")]
47 InvalidBlobToUtf8 {
48 fragment: Fragment,
49 cause: Diagnostic,
50 },
51}
52
53impl IntoDiagnostic for CastError {
54 fn into_diagnostic(self) -> Diagnostic {
55 match self {
56 CastError::UnsupportedCast { fragment, from_type, to_type } => {
57 let label = Some(format!("cannot cast {} of type {} to {}", fragment.text(), from_type, to_type));
58 Diagnostic {
59 code: "CAST_001".to_string(),
60 rql: None,
61 message: format!("unsupported cast from {} to {}", from_type, to_type),
62 fragment,
63 label,
64 help: Some("ensure the source and target types are compatible for casting".to_string()),
65 notes: vec!["supported casts include: numeric to numeric, string to temporal, boolean to numeric"
66 .to_string()],
67 column: None,
68 cause: None,
69 operator_chain: None,
70 }
71 }
72 CastError::InvalidNumber { fragment, target, cause } => Diagnostic {
73 code: "CAST_002".to_string(),
74 rql: None,
75 message: format!("failed to cast to {}", target),
76 fragment,
77 label: Some(format!("failed to cast to {}", target)),
78 help: None,
79 notes: vec![],
80 column: None,
81 cause: Some(Box::from(cause)),
82 operator_chain: None,
83 },
84 CastError::InvalidBoolean { fragment, cause } => Diagnostic {
85 code: "CAST_004".to_string(),
86 rql: None,
87 message: "failed to cast to bool".to_string(),
88 fragment,
89 label: Some("failed to cast to bool".to_string()),
90 help: None,
91 notes: vec![],
92 column: None,
93 cause: Some(Box::from(cause)),
94 operator_chain: None,
95 },
96 CastError::InvalidUuid { fragment, target, cause } => Diagnostic {
97 code: "CAST_005".to_string(),
98 rql: None,
99 message: format!("failed to cast to {}", target),
100 fragment,
101 label: Some(format!("failed to cast to {}", target)),
102 help: None,
103 notes: vec![],
104 column: None,
105 cause: Some(Box::from(cause)),
106 operator_chain: None,
107 },
108 CastError::InvalidTemporal { fragment, target, cause } => Diagnostic {
109 code: "CAST_003".to_string(),
110 rql: None,
111 message: format!("failed to cast to {}", target),
112 fragment,
113 label: Some(format!("failed to cast to {}", target)),
114 help: None,
115 notes: vec![],
116 column: None,
117 cause: Some(Box::from(cause)),
118 operator_chain: None,
119 },
120 CastError::InvalidBlobToUtf8 { fragment, cause } => Diagnostic {
121 code: "CAST_006".to_string(),
122 rql: None,
123 message: "failed to cast BLOB to UTF8".to_string(),
124 fragment,
125 label: Some("failed to cast BLOB to UTF8".to_string()),
126 help: Some("BLOB contains invalid UTF-8 bytes. Consider using to_utf8_lossy() function instead"
127 .to_string()),
128 notes: vec![],
129 column: None,
130 cause: Some(Box::from(cause)),
131 operator_chain: None,
132 },
133 }
134 }
135}
136
137impl From<CastError> for Error {
138 fn from(err: CastError) -> Self {
139 Error(Box::new(err.into_diagnostic()))
140 }
141}
142
143#[derive(Debug, thiserror::Error)]
144pub enum EngineError {
145 #[error("column `{column}` not found in `{table_name}`")]
146 BulkInsertColumnNotFound {
147 fragment: Fragment,
148 table_name: String,
149 column: String,
150 },
151
152 #[error("too many values: expected {expected} columns, got {actual}")]
153 BulkInsertTooManyValues {
154 fragment: Fragment,
155 expected: usize,
156 actual: usize,
157 },
158
159 #[error("Frame must have a __ROW__ID__ column for UPDATE operations")]
160 MissingRowNumberColumn,
161
162 #[error("assertion failed: {message}")]
163 AssertionFailed {
164 fragment: Fragment,
165 message: String,
166 expression: Option<String>,
167 },
168
169 #[error("Cannot insert none into non-optional column of type {column_type}")]
170 NoneNotAllowed {
171 fragment: Fragment,
172 column_type: Type,
173 },
174
175 #[error("Unknown function: {name}")]
176 UnknownFunction {
177 name: String,
178 fragment: Fragment,
179 },
180
181 #[error("Unknown callable: {name}")]
182 UnknownCallable {
183 name: String,
184 fragment: Fragment,
185 },
186
187 #[error("Generator function '{name}' not found")]
188 GeneratorNotFound {
189 name: String,
190 fragment: Fragment,
191 },
192
193 #[error("Variable '{name}' is not defined")]
194 VariableNotFound {
195 name: String,
196 },
197
198 #[error("Cannot reassign immutable variable '{name}'")]
199 VariableIsImmutable {
200 name: String,
201 },
202}
203
204impl IntoDiagnostic for EngineError {
205 fn into_diagnostic(self) -> Diagnostic {
206 match self {
207 EngineError::BulkInsertColumnNotFound {
208 fragment,
209 table_name,
210 column,
211 } => Diagnostic {
212 code: "BI_001".to_string(),
213 rql: None,
214 message: format!("column `{}` not found in `{}`", column, table_name),
215 column: None,
216 fragment,
217 label: Some("unknown column".to_string()),
218 help: Some("check that the column name matches the shape".to_string()),
219 notes: vec![],
220 cause: None,
221 operator_chain: None,
222 },
223 EngineError::BulkInsertTooManyValues {
224 fragment,
225 expected,
226 actual,
227 } => Diagnostic {
228 code: "BI_003".to_string(),
229 rql: None,
230 message: format!("too many values: expected {} columns, got {}", expected, actual),
231 column: None,
232 fragment,
233 label: Some("value count mismatch".to_string()),
234 help: Some("ensure the number of values matches the column count".to_string()),
235 notes: vec![],
236 cause: None,
237 operator_chain: None,
238 },
239 EngineError::MissingRowNumberColumn => Diagnostic {
240 code: "ENG_003".to_string(),
241 rql: None,
242 message: "Frame must have a __ROW__ID__ column for UPDATE operations".to_string(),
243 column: None,
244 fragment: Fragment::None,
245 label: Some("missing required column".to_string()),
246 help: Some("Ensure the query includes the encoded ID in the result set".to_string()),
247 notes: vec!["UPDATE operations require encoded identifiers to locate existing rows"
248 .to_string()],
249 cause: None,
250 operator_chain: None,
251 },
252 EngineError::AssertionFailed {
253 fragment,
254 message,
255 expression,
256 } => {
257 let base_msg = if !message.is_empty() {
258 message.clone()
259 } else if let Some(ref expr) = expression {
260 format!("assertion failed: {}", expr)
261 } else {
262 "assertion failed".to_string()
263 };
264 let label = expression
265 .as_ref()
266 .map(|expr| format!("this expression is false: {}", expr))
267 .or_else(|| Some("assertion failed".to_string()));
268 Diagnostic {
269 code: "ASSERT".to_string(),
270 rql: None,
271 message: base_msg,
272 fragment,
273 label,
274 help: None,
275 notes: vec![],
276 column: None,
277 cause: None,
278 operator_chain: None,
279 }
280 }
281 EngineError::NoneNotAllowed {
282 fragment,
283 column_type,
284 } => Diagnostic {
285 code: "CONSTRAINT_007".to_string(),
286 rql: None,
287 message: format!(
288 "Cannot insert none into non-optional column of type {}. Declare the column as Option({}) to allow none values.",
289 column_type, column_type
290 ),
291 column: None,
292 fragment,
293 label: Some("constraint violation".to_string()),
294 help: Some(format!(
295 "The column type is {} which does not accept none. Use Option({}) if the column should be nullable.",
296 column_type, column_type
297 )),
298 notes: vec![],
299 cause: None,
300 operator_chain: None,
301 },
302 EngineError::UnknownFunction {
303 name,
304 fragment,
305 } => Diagnostic {
306 code: "FUNCTION_001".to_string(),
307 rql: None,
308 message: format!("Unknown function: {}", name),
309 column: None,
310 fragment,
311 label: Some("unknown function".to_string()),
312 help: Some("Check the function name and available functions".to_string()),
313 notes: vec![],
314 cause: None,
315 operator_chain: None,
316 },
317 EngineError::UnknownCallable {
318 name,
319 fragment,
320 } => Diagnostic {
321 code: "CALLABLE_001".to_string(),
322 rql: None,
323 message: format!("Unknown callable: {}", name),
324 column: None,
325 fragment,
326 label: Some("unknown callable".to_string()),
327 help: Some(
328 "Check the name and available functions, procedures, and closures".to_string()
329 ),
330 notes: vec![],
331 cause: None,
332 operator_chain: None,
333 },
334 EngineError::GeneratorNotFound {
335 name,
336 fragment,
337 } => Diagnostic {
338 code: "FUNCTION_009".to_string(),
339 rql: None,
340 message: format!("Generator function '{}' not found", name),
341 column: None,
342 fragment,
343 label: Some("unknown generator function".to_string()),
344 help: Some("Check the generator function name and ensure it is registered".to_string()),
345 notes: vec![],
346 cause: None,
347 operator_chain: None,
348 },
349 EngineError::VariableNotFound {
350 name,
351 } => Diagnostic {
352 code: "RUNTIME_001".to_string(),
353 rql: None,
354 message: format!("Variable '{}' is not defined", name),
355 column: None,
356 fragment: Fragment::None,
357 label: None,
358 help: Some(format!(
359 "Define the variable using 'let {} = <value>' before using it",
360 name
361 )),
362 notes: vec![],
363 cause: None,
364 operator_chain: None,
365 },
366 EngineError::VariableIsImmutable {
367 name,
368 } => Diagnostic {
369 code: "RUNTIME_003".to_string(),
370 rql: None,
371 message: format!("Cannot reassign immutable variable '{}'", name),
372 column: None,
373 fragment: Fragment::None,
374 label: None,
375 help: Some("Use 'let mut $name := value' to declare a mutable variable".to_string()),
376 notes: vec!["Only mutable variables can be reassigned".to_string()],
377 cause: None,
378 operator_chain: None,
379 },
380 }
381 }
382}
383
384impl From<EngineError> for Error {
385 fn from(err: EngineError) -> Self {
386 Error(Box::new(err.into_diagnostic()))
387 }
388}