1use reifydb_core::interface::catalog::object::ObjectId;
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 callable: {name}")]
44 UnknownCallable {
45 name: String,
46 fragment: Fragment,
47 },
48
49 #[error("function `{name}` returned a value that cannot be cast to its declared return type {declared}")]
50 ReturnTypeMismatch {
51 name: String,
52 declared: ValueType,
53 fragment: Fragment,
54 cause: Box<Diagnostic>,
55 },
56
57 #[error("Generator function '{name}' not found")]
58 GeneratorNotFound {
59 name: String,
60 fragment: Fragment,
61 },
62
63 #[error(
64 "cannot locate partitioned rows for {operation} on object {object}: query object carries no partition address"
65 )]
66 MissingPartitionAddress {
67 object: ObjectId,
68 operation: &'static str,
69 },
70
71 #[error("`{object}` declares `{column}` as its #time populator but has no such column")]
72 TimePopulatorMissing {
73 object: String,
74 column: String,
75 },
76
77 #[error("`{object}.{column}` is the declared #time populator but holds {found} on this row")]
78 TimePopulatorNotDateTime {
79 object: String,
80 column: String,
81 found: String,
82 },
83
84 #[error("cannot update the tag of series `{series}`: the tag is part of the row key")]
85 SeriesTagImmutable {
86 series: String,
87 fragment: Fragment,
88 },
89
90 #[error("cannot update the key column `{column}` of series `{series}`: the key is part of the row key")]
91 SeriesKeyImmutable {
92 series: String,
93 column: String,
94 fragment: Fragment,
95 },
96
97 #[error("series key column `{column}` cannot store {value}")]
98 SeriesKeyOutOfRange {
99 column: String,
100 value: String,
101 fragment: Fragment,
102 },
103
104 #[error("series key column `{column}` of series `{series}` changed by a pre-insert hook")]
105 SeriesKeyChangedByHook {
106 series: String,
107 column: String,
108 },
109
110 #[error("series `{series}` is partitioned by its key `{column}`, so an insert must give the key")]
111 SeriesKeyPartitionNeedsKey {
112 series: String,
113 column: String,
114 fragment: Fragment,
115 },
116}
117
118impl IntoDiagnostic for EngineError {
119 fn into_diagnostic(self) -> Diagnostic {
120 match self {
121 EngineError::BulkInsertColumnNotFound {
122 fragment,
123 table_name,
124 column,
125 } => Diagnostic {
126 code: "BI_001".to_string(),
127 rql: None,
128 message: format!("column `{}` not found in `{}`", column, table_name),
129 column: None,
130 fragment,
131 label: Some("unknown column".to_string()),
132 help: Some("check that the column name matches the shape".to_string()),
133 notes: vec![],
134 cause: None,
135 operator_chain: None,
136 },
137 EngineError::BulkInsertTooManyValues {
138 fragment,
139 expected,
140 actual,
141 } => Diagnostic {
142 code: "BI_003".to_string(),
143 rql: None,
144 message: format!("too many values: expected {} columns, got {}", expected, actual),
145 column: None,
146 fragment,
147 label: Some("value count mismatch".to_string()),
148 help: Some("ensure the number of values matches the column count".to_string()),
149 notes: vec![],
150 cause: None,
151 operator_chain: None,
152 },
153 EngineError::MissingRowNumberColumn => Diagnostic {
154 code: "ENG_003".to_string(),
155 rql: None,
156 message: "Frame must have a __ROW__ID__ column for UPDATE operations".to_string(),
157 column: None,
158 fragment: Fragment::None,
159 label: Some("missing required column".to_string()),
160 help: Some("Ensure the query includes the encoded ID in the result set".to_string()),
161 notes: vec!["UPDATE operations require encoded identifiers to locate existing rows"
162 .to_string()],
163 cause: None,
164 operator_chain: None,
165 },
166 EngineError::AssertionFailed {
167 fragment,
168 message,
169 expression,
170 } => {
171 let base_msg = if !message.is_empty() {
172 message.clone()
173 } else if let Some(ref expr) = expression {
174 format!("assertion failed: {}", expr)
175 } else {
176 "assertion failed".to_string()
177 };
178 let label = expression
179 .as_ref()
180 .map(|expr| format!("this expression is false: {}", expr))
181 .or_else(|| Some("assertion failed".to_string()));
182 Diagnostic {
183 code: "ASSERT".to_string(),
184 rql: None,
185 message: base_msg,
186 fragment,
187 label,
188 help: None,
189 notes: vec![],
190 column: None,
191 cause: None,
192 operator_chain: None,
193 }
194 }
195 EngineError::NoneNotAllowed {
196 fragment,
197 column_type,
198 } => Diagnostic {
199 code: "CONSTRAINT_007".to_string(),
200 rql: None,
201 message: format!(
202 "Cannot insert none into non-optional column of type {}. Declare the column as Option({}) to allow none values.",
203 column_type, column_type
204 ),
205 column: None,
206 fragment,
207 label: Some("constraint violation".to_string()),
208 help: Some(format!(
209 "The column type is {} which does not accept none. Use Option({}) if the column should be optional.",
210 column_type, column_type
211 )),
212 notes: vec![],
213 cause: None,
214 operator_chain: None,
215 },
216 EngineError::UnknownCallable {
217 name,
218 fragment,
219 } => Diagnostic {
220 code: "CALLABLE_001".to_string(),
221 rql: None,
222 message: format!("Unknown callable: {}", name),
223 column: None,
224 fragment,
225 label: Some("unknown callable".to_string()),
226 help: Some(
227 "Check the name and available functions, procedures, and closures".to_string()
228 ),
229 notes: vec![],
230 cause: None,
231 operator_chain: None,
232 },
233 EngineError::ReturnTypeMismatch {
234 name,
235 declared,
236 fragment,
237 cause,
238 } => Diagnostic {
239 code: "CALLABLE_002".to_string(),
240 rql: None,
241 message: format!(
242 "function `{}` returned a value that cannot be cast to its declared return type {}",
243 name, declared
244 ),
245 column: None,
246 fragment,
247 label: Some(format!("`{}` must return {}", name, declared)),
248 help: Some(format!(
249 "return a value that fits {} from `{}`, or change its declared return type",
250 declared, name
251 )),
252 notes: vec![],
253 cause: Some(cause),
254 operator_chain: None,
255 },
256 EngineError::GeneratorNotFound {
257 name,
258 fragment,
259 } => Diagnostic {
260 code: "FUNCTION_010".to_string(),
261 rql: None,
262 message: format!("Generator function '{}' not found", name),
263 column: None,
264 fragment,
265 label: Some("unknown generator function".to_string()),
266 help: Some("Check the generator function name and ensure it is registered".to_string()),
267 notes: vec![],
268 cause: None,
269 operator_chain: None,
270 },
271 EngineError::MissingPartitionAddress {
272 object,
273 operation,
274 } => Diagnostic {
275 code: "PART_001".to_string(),
276 rql: None,
277 message: format!(
278 "cannot locate partitioned rows for {} on object {}: query object carries no partition address",
279 operation, object
280 ),
281 column: None,
282 fragment: Fragment::None,
283 label: Some("missing partition address".to_string()),
284 help: Some(
285 "the query must carry the row's partition alongside its row number; rewrite the query so the partitioned source's columns flow through unmodified"
286 .to_string(),
287 ),
288 notes: vec![],
289 cause: None,
290 operator_chain: None,
291 },
292
293 EngineError::TimePopulatorMissing {
294 object,
295 column,
296 } => Diagnostic {
297 code: "TIME_001".to_string(),
298 rql: None,
299 message: format!(
300 "`{}` declares `{}` as its #time populator but has no such column",
301 object, column
302 ),
303 column: None,
304 fragment: Fragment::None,
305 label: Some("declared populator does not exist".to_string()),
306 help: Some(
307 "the populator must name one of the object's own columns; correct the `ts` key in the object's WITH clause"
308 .to_string(),
309 ),
310 notes: vec![
311 "definition-time validation rejects a populator naming an absent column, so reaching this means the object was stored with a populator it does not have"
312 .to_string(),
313 ],
314 cause: None,
315 operator_chain: None,
316 },
317
318 EngineError::TimePopulatorNotDateTime {
319 object,
320 column,
321 found,
322 } => Diagnostic {
323 code: "TIME_002".to_string(),
324 rql: None,
325 message: format!(
326 "`{}.{}` is the declared #time populator but holds {} on this row",
327 object, column, found
328 ),
329 column: None,
330 fragment: Fragment::None,
331 label: Some("populator is not a non-none DateTime".to_string()),
332 help: Some(
333 "every row of an event-time object must carry a non-none DateTime in its populator column"
334 .to_string(),
335 ),
336 notes: vec![
337 "#time is substrate-owned and cannot fall back to the write clock; silently substituting arrival time would date a replayed row to now"
338 .to_string(),
339 ],
340 cause: None,
341 operator_chain: None,
342 },
343
344 EngineError::SeriesTagImmutable {
345 series,
346 fragment,
347 } => Diagnostic {
348 code: "UPDATE_004".to_string(),
349 rql: None,
350 message: format!(
351 "cannot update the tag of series `{}`: the tag is part of the row key",
352 series
353 ),
354 column: None,
355 fragment,
356 label: Some("`tag` cannot be updated".to_string()),
357 help: Some("delete the row and insert it again with the new tag".to_string()),
358 notes: vec![],
359 cause: None,
360 operator_chain: None,
361 },
362
363 EngineError::SeriesKeyImmutable {
364 series,
365 column,
366 fragment,
367 } => Diagnostic {
368 code: "UPDATE_005".to_string(),
369 rql: None,
370 message: format!(
371 "cannot update the key column `{}` of series `{}`: the key is part of the row key",
372 column, series
373 ),
374 column: None,
375 fragment,
376 label: Some(format!("`{}` cannot be updated", column)),
377 help: Some("delete the row and insert it again with the new key".to_string()),
378 notes: vec![],
379 cause: None,
380 operator_chain: None,
381 },
382
383 EngineError::SeriesKeyOutOfRange {
384 column,
385 value,
386 fragment,
387 } => Diagnostic {
388 code: "SERIES_001".to_string(),
389 rql: None,
390 message: format!("series key column `{}` cannot store {}", column, value),
391 column: None,
392 fragment,
393 label: Some("key out of range".to_string()),
394 help: Some(format!("a series key must be between 0 and {}", u64::MAX)),
395 notes: vec![],
396 cause: None,
397 operator_chain: None,
398 },
399
400 EngineError::SeriesKeyChangedByHook {
401 series,
402 column,
403 } => Diagnostic {
404 code: "SERIES_002".to_string(),
405 rql: None,
406 message: format!(
407 "series key column `{}` of series `{}` changed by a pre-insert hook",
408 column, series
409 ),
410 column: None,
411 fragment: Fragment::None,
412 label: Some("series key changed by hook".to_string()),
413 help: Some(format!(
414 "the key `{}` of series `{}` is part of the row key, which is built before pre-insert hooks run, so a hook must not change it",
415 column, series
416 )),
417 notes: vec![],
418 cause: None,
419 operator_chain: None,
420 },
421
422 EngineError::SeriesKeyPartitionNeedsKey {
423 series,
424 column,
425 fragment,
426 } => Diagnostic {
427 code: "SERIES_003".to_string(),
428 rql: None,
429 message: format!(
430 "series `{}` is partitioned by its key `{}`, so an insert must give the key",
431 series, column
432 ),
433 column: None,
434 fragment,
435 label: Some("key not given".to_string()),
436 help: Some(format!(
437 "an integer key is generated as the next key of its partition, and the partition of `{}` is made of the key itself; give `{}` on every row",
438 series, column
439 )),
440 notes: vec![],
441 cause: None,
442 operator_chain: None,
443 },
444 }
445 }
446}
447
448impl From<EngineError> for Error {
449 fn from(err: EngineError) -> Self {
450 Error(Box::new(err.into_diagnostic()))
451 }
452}