Skip to main content

reifydb_engine/
error.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2025 ReifyDB
3
4use 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}