1use reifydb_core::interface::catalog::shape::ShapeId;
5use reifydb_value::{
6 error::{Diagnostic, Error, IntoDiagnostic},
7 fragment::Fragment,
8 value::value_type::ValueType,
9};
10
11#[derive(Debug, thiserror::Error)]
12pub enum EngineError {
13 #[error("column `{column}` not found in `{table_name}`")]
14 BulkInsertColumnNotFound {
15 fragment: Fragment,
16 table_name: String,
17 column: String,
18 },
19
20 #[error("too many values: expected {expected} columns, got {actual}")]
21 BulkInsertTooManyValues {
22 fragment: Fragment,
23 expected: usize,
24 actual: usize,
25 },
26
27 #[error("Frame must have a __ROW__ID__ column for UPDATE operations")]
28 MissingRowNumberColumn,
29
30 #[error("assertion failed: {message}")]
31 AssertionFailed {
32 fragment: Fragment,
33 message: String,
34 expression: Option<String>,
35 },
36
37 #[error("Cannot insert none into non-optional column of type {column_type}")]
38 NoneNotAllowed {
39 fragment: Fragment,
40 column_type: ValueType,
41 },
42
43 #[error("Unknown function: {name}")]
44 UnknownFunction {
45 name: String,
46 fragment: Fragment,
47 },
48
49 #[error("Unknown callable: {name}")]
50 UnknownCallable {
51 name: String,
52 fragment: Fragment,
53 },
54
55 #[error("Generator function '{name}' not found")]
56 GeneratorNotFound {
57 name: String,
58 fragment: Fragment,
59 },
60
61 #[error("Variable '{name}' is not defined")]
62 VariableNotFound {
63 name: String,
64 },
65
66 #[error("Cannot reassign immutable variable '{name}'")]
67 VariableIsImmutable {
68 name: String,
69 },
70
71 #[error(
72 "cannot locate partitioned rows for {operation} on shape {shape}: query shape carries no partition address"
73 )]
74 MissingPartitionAddress {
75 shape: ShapeId,
76 operation: &'static str,
77 },
78
79 #[error("cannot change partition column via UPDATE on shape {shape}: partition columns are immutable")]
80 ImmutablePartitionColumn {
81 shape: ShapeId,
82 },
83
84 #[error(
85 "partition hash collision on shape {shape}: hash {hash:032x} maps to two distinct partition value tuples"
86 )]
87 PartitionHashCollision {
88 shape: ShapeId,
89 hash: u128,
90 },
91}
92
93impl IntoDiagnostic for EngineError {
94 fn into_diagnostic(self) -> Diagnostic {
95 match self {
96 EngineError::BulkInsertColumnNotFound {
97 fragment,
98 table_name,
99 column,
100 } => Diagnostic {
101 code: "BI_001".to_string(),
102 rql: None,
103 message: format!("column `{}` not found in `{}`", column, table_name),
104 column: None,
105 fragment,
106 label: Some("unknown column".to_string()),
107 help: Some("check that the column name matches the shape".to_string()),
108 notes: vec![],
109 cause: None,
110 operator_chain: None,
111 },
112 EngineError::BulkInsertTooManyValues {
113 fragment,
114 expected,
115 actual,
116 } => Diagnostic {
117 code: "BI_003".to_string(),
118 rql: None,
119 message: format!("too many values: expected {} columns, got {}", expected, actual),
120 column: None,
121 fragment,
122 label: Some("value count mismatch".to_string()),
123 help: Some("ensure the number of values matches the column count".to_string()),
124 notes: vec![],
125 cause: None,
126 operator_chain: None,
127 },
128 EngineError::MissingRowNumberColumn => Diagnostic {
129 code: "ENG_003".to_string(),
130 rql: None,
131 message: "Frame must have a __ROW__ID__ column for UPDATE operations".to_string(),
132 column: None,
133 fragment: Fragment::None,
134 label: Some("missing required column".to_string()),
135 help: Some("Ensure the query includes the encoded ID in the result set".to_string()),
136 notes: vec!["UPDATE operations require encoded identifiers to locate existing rows"
137 .to_string()],
138 cause: None,
139 operator_chain: None,
140 },
141 EngineError::AssertionFailed {
142 fragment,
143 message,
144 expression,
145 } => {
146 let base_msg = if !message.is_empty() {
147 message.clone()
148 } else if let Some(ref expr) = expression {
149 format!("assertion failed: {}", expr)
150 } else {
151 "assertion failed".to_string()
152 };
153 let label = expression
154 .as_ref()
155 .map(|expr| format!("this expression is false: {}", expr))
156 .or_else(|| Some("assertion failed".to_string()));
157 Diagnostic {
158 code: "ASSERT".to_string(),
159 rql: None,
160 message: base_msg,
161 fragment,
162 label,
163 help: None,
164 notes: vec![],
165 column: None,
166 cause: None,
167 operator_chain: None,
168 }
169 }
170 EngineError::NoneNotAllowed {
171 fragment,
172 column_type,
173 } => Diagnostic {
174 code: "CONSTRAINT_007".to_string(),
175 rql: None,
176 message: format!(
177 "Cannot insert none into non-optional column of type {}. Declare the column as Option({}) to allow none values.",
178 column_type, column_type
179 ),
180 column: None,
181 fragment,
182 label: Some("constraint violation".to_string()),
183 help: Some(format!(
184 "The column type is {} which does not accept none. Use Option({}) if the column should be nullable.",
185 column_type, column_type
186 )),
187 notes: vec![],
188 cause: None,
189 operator_chain: None,
190 },
191 EngineError::UnknownFunction {
192 name,
193 fragment,
194 } => Diagnostic {
195 code: "FUNCTION_001".to_string(),
196 rql: None,
197 message: format!("Unknown function: {}", name),
198 column: None,
199 fragment,
200 label: Some("unknown function".to_string()),
201 help: Some("Check the function name and available functions".to_string()),
202 notes: vec![],
203 cause: None,
204 operator_chain: None,
205 },
206 EngineError::UnknownCallable {
207 name,
208 fragment,
209 } => Diagnostic {
210 code: "CALLABLE_001".to_string(),
211 rql: None,
212 message: format!("Unknown callable: {}", name),
213 column: None,
214 fragment,
215 label: Some("unknown callable".to_string()),
216 help: Some(
217 "Check the name and available functions, procedures, and closures".to_string()
218 ),
219 notes: vec![],
220 cause: None,
221 operator_chain: None,
222 },
223 EngineError::GeneratorNotFound {
224 name,
225 fragment,
226 } => Diagnostic {
227 code: "FUNCTION_009".to_string(),
228 rql: None,
229 message: format!("Generator function '{}' not found", name),
230 column: None,
231 fragment,
232 label: Some("unknown generator function".to_string()),
233 help: Some("Check the generator function name and ensure it is registered".to_string()),
234 notes: vec![],
235 cause: None,
236 operator_chain: None,
237 },
238 EngineError::VariableNotFound {
239 name,
240 } => Diagnostic {
241 code: "RUNTIME_001".to_string(),
242 rql: None,
243 message: format!("Variable '{}' is not defined", name),
244 column: None,
245 fragment: Fragment::None,
246 label: None,
247 help: Some(format!(
248 "Define the variable using 'let {} = <value>' before using it",
249 name
250 )),
251 notes: vec![],
252 cause: None,
253 operator_chain: None,
254 },
255 EngineError::VariableIsImmutable {
256 name,
257 } => Diagnostic {
258 code: "RUNTIME_003".to_string(),
259 rql: None,
260 message: format!("Cannot reassign immutable variable '{}'", name),
261 column: None,
262 fragment: Fragment::None,
263 label: None,
264 help: Some("Use 'let mut $name := value' to declare a mutable variable".to_string()),
265 notes: vec!["Only mutable variables can be reassigned".to_string()],
266 cause: None,
267 operator_chain: None,
268 },
269
270 EngineError::MissingPartitionAddress {
271 shape,
272 operation,
273 } => Diagnostic {
274 code: "PART_001".to_string(),
275 rql: None,
276 message: format!(
277 "cannot locate partitioned rows for {} on shape {}: query shape carries no partition address",
278 operation, shape
279 ),
280 column: None,
281 fragment: Fragment::None,
282 label: Some("missing partition address".to_string()),
283 help: Some(
284 "the query must carry the row's partition alongside its row number; rewrite the query so the partitioned source's columns flow through unmodified"
285 .to_string(),
286 ),
287 notes: vec![],
288 cause: None,
289 operator_chain: None,
290 },
291
292 EngineError::ImmutablePartitionColumn {
293 shape,
294 } => Diagnostic {
295 code: "PART_002".to_string(),
296 rql: None,
297 message: format!(
298 "cannot change partition column via UPDATE on shape {}: partition columns are immutable",
299 shape
300 ),
301 column: None,
302 fragment: Fragment::None,
303 label: Some("partition column change rejected".to_string()),
304 help: Some(
305 "partition columns determine a row's physical location and cannot be updated; delete and re-insert the row instead"
306 .to_string(),
307 ),
308 notes: vec![],
309 cause: None,
310 operator_chain: None,
311 },
312
313 EngineError::PartitionHashCollision {
314 shape,
315 hash,
316 } => Diagnostic {
317 code: "PART_003".to_string(),
318 rql: None,
319 message: format!(
320 "partition hash collision on shape {}: hash {:032x} maps to two distinct partition value tuples",
321 shape, hash
322 ),
323 column: None,
324 fragment: Fragment::None,
325 label: Some("128-bit hash collision".to_string()),
326 help: Some(
327 "two distinct partition value tuples produced the same 128-bit hash; this is astronomically unlikely and points to a hashing bug or data corruption, report it as a bug"
328 .to_string(),
329 ),
330 notes: vec![],
331 cause: None,
332 operator_chain: None,
333 },
334 }
335 }
336}
337
338impl From<EngineError> for Error {
339 fn from(err: EngineError) -> Self {
340 Error(Box::new(err.into_diagnostic()))
341 }
342}