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 statement: 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 statement: 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 statement: 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 statement: 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 statement: 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 statement: 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(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("Generator function '{name}' not found")]
182 GeneratorNotFound {
183 name: String,
184 fragment: Fragment,
185 },
186
187 #[error("Variable '{name}' is not defined")]
188 VariableNotFound {
189 name: String,
190 },
191
192 #[error("Cannot reassign immutable variable '{name}'")]
193 VariableIsImmutable {
194 name: String,
195 },
196}
197
198impl IntoDiagnostic for EngineError {
199 fn into_diagnostic(self) -> Diagnostic {
200 match self {
201 EngineError::BulkInsertColumnNotFound {
202 fragment,
203 table_name,
204 column,
205 } => Diagnostic {
206 code: "BI_001".to_string(),
207 statement: None,
208 message: format!("column `{}` not found in `{}`", column, table_name),
209 column: None,
210 fragment,
211 label: Some("unknown column".to_string()),
212 help: Some("check that the column name matches the schema".to_string()),
213 notes: vec![],
214 cause: None,
215 operator_chain: None,
216 },
217 EngineError::BulkInsertTooManyValues {
218 fragment,
219 expected,
220 actual,
221 } => Diagnostic {
222 code: "BI_003".to_string(),
223 statement: None,
224 message: format!("too many values: expected {} columns, got {}", expected, actual),
225 column: None,
226 fragment,
227 label: Some("value count mismatch".to_string()),
228 help: Some("ensure the number of values matches the column count".to_string()),
229 notes: vec![],
230 cause: None,
231 operator_chain: None,
232 },
233 EngineError::MissingRowNumberColumn => Diagnostic {
234 code: "ENG_003".to_string(),
235 statement: None,
236 message: "Frame must have a __ROW__ID__ column for UPDATE operations".to_string(),
237 column: None,
238 fragment: Fragment::None,
239 label: Some("missing required column".to_string()),
240 help: Some("Ensure the query includes the encoded ID in the result set".to_string()),
241 notes: vec!["UPDATE operations require encoded identifiers to locate existing rows"
242 .to_string()],
243 cause: None,
244 operator_chain: None,
245 },
246 EngineError::AssertionFailed {
247 fragment,
248 message,
249 expression,
250 } => {
251 let base_msg = if !message.is_empty() {
252 message.clone()
253 } else if let Some(ref expr) = expression {
254 format!("assertion failed: {}", expr)
255 } else {
256 "assertion failed".to_string()
257 };
258 let label = expression
259 .as_ref()
260 .map(|expr| format!("this expression is false: {}", expr))
261 .or_else(|| Some("assertion failed".to_string()));
262 Diagnostic {
263 code: "ASSERT".to_string(),
264 statement: None,
265 message: base_msg,
266 fragment,
267 label,
268 help: None,
269 notes: vec![],
270 column: None,
271 cause: None,
272 operator_chain: None,
273 }
274 }
275 EngineError::NoneNotAllowed {
276 fragment,
277 column_type,
278 } => Diagnostic {
279 code: "CONSTRAINT_007".to_string(),
280 statement: None,
281 message: format!(
282 "Cannot insert none into non-optional column of type {}. Declare the column as Option({}) to allow none values.",
283 column_type, column_type
284 ),
285 column: None,
286 fragment,
287 label: Some("constraint violation".to_string()),
288 help: Some(format!(
289 "The column type is {} which does not accept none. Use Option({}) if the column should be nullable.",
290 column_type, column_type
291 )),
292 notes: vec![],
293 cause: None,
294 operator_chain: None,
295 },
296 EngineError::UnknownFunction {
297 name,
298 fragment,
299 } => Diagnostic {
300 code: "FUNCTION_001".to_string(),
301 statement: None,
302 message: format!("Unknown function: {}", name),
303 column: None,
304 fragment,
305 label: Some("unknown function".to_string()),
306 help: Some("Check the function name and available functions".to_string()),
307 notes: vec![],
308 cause: None,
309 operator_chain: None,
310 },
311 EngineError::GeneratorNotFound {
312 name,
313 fragment,
314 } => Diagnostic {
315 code: "FUNCTION_009".to_string(),
316 statement: None,
317 message: format!("Generator function '{}' not found", name),
318 column: None,
319 fragment,
320 label: Some("unknown generator function".to_string()),
321 help: Some("Check the generator function name and ensure it is registered".to_string()),
322 notes: vec![],
323 cause: None,
324 operator_chain: None,
325 },
326 EngineError::VariableNotFound {
327 name,
328 } => Diagnostic {
329 code: "RUNTIME_001".to_string(),
330 statement: None,
331 message: format!("Variable '{}' is not defined", name),
332 column: None,
333 fragment: Fragment::None,
334 label: None,
335 help: Some(format!(
336 "Define the variable using 'let {} = <value>' before using it",
337 name
338 )),
339 notes: vec![],
340 cause: None,
341 operator_chain: None,
342 },
343 EngineError::VariableIsImmutable {
344 name,
345 } => Diagnostic {
346 code: "RUNTIME_003".to_string(),
347 statement: None,
348 message: format!("Cannot reassign immutable variable '{}'", name),
349 column: None,
350 fragment: Fragment::None,
351 label: None,
352 help: Some("Use 'let mut $name := value' to declare a mutable variable".to_string()),
353 notes: vec!["Only mutable variables can be reassigned".to_string()],
354 cause: None,
355 operator_chain: None,
356 },
357 }
358 }
359}
360
361impl From<EngineError> for Error {
362 fn from(err: EngineError) -> Self {
363 Error(err.into_diagnostic())
364 }
365}