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("Generator function '{name}' not found")]
50 GeneratorNotFound {
51 name: String,
52 fragment: Fragment,
53 },
54
55 #[error(
56 "cannot locate partitioned rows for {operation} on object {object}: query object carries no partition address"
57 )]
58 MissingPartitionAddress {
59 object: ObjectId,
60 operation: &'static str,
61 },
62
63 #[error("`{object}` declares `{column}` as its #time populator but has no such column")]
64 TimePopulatorMissing {
65 object: String,
66 column: String,
67 },
68
69 #[error("`{object}.{column}` is the declared #time populator but holds {found} on this row")]
70 TimePopulatorNotDateTime {
71 object: String,
72 column: String,
73 found: String,
74 },
75}
76
77impl IntoDiagnostic for EngineError {
78 fn into_diagnostic(self) -> Diagnostic {
79 match self {
80 EngineError::BulkInsertColumnNotFound {
81 fragment,
82 table_name,
83 column,
84 } => Diagnostic {
85 code: "BI_001".to_string(),
86 rql: None,
87 message: format!("column `{}` not found in `{}`", column, table_name),
88 column: None,
89 fragment,
90 label: Some("unknown column".to_string()),
91 help: Some("check that the column name matches the shape".to_string()),
92 notes: vec![],
93 cause: None,
94 operator_chain: None,
95 },
96 EngineError::BulkInsertTooManyValues {
97 fragment,
98 expected,
99 actual,
100 } => Diagnostic {
101 code: "BI_003".to_string(),
102 rql: None,
103 message: format!("too many values: expected {} columns, got {}", expected, actual),
104 column: None,
105 fragment,
106 label: Some("value count mismatch".to_string()),
107 help: Some("ensure the number of values matches the column count".to_string()),
108 notes: vec![],
109 cause: None,
110 operator_chain: None,
111 },
112 EngineError::MissingRowNumberColumn => Diagnostic {
113 code: "ENG_003".to_string(),
114 rql: None,
115 message: "Frame must have a __ROW__ID__ column for UPDATE operations".to_string(),
116 column: None,
117 fragment: Fragment::None,
118 label: Some("missing required column".to_string()),
119 help: Some("Ensure the query includes the encoded ID in the result set".to_string()),
120 notes: vec!["UPDATE operations require encoded identifiers to locate existing rows"
121 .to_string()],
122 cause: None,
123 operator_chain: None,
124 },
125 EngineError::AssertionFailed {
126 fragment,
127 message,
128 expression,
129 } => {
130 let base_msg = if !message.is_empty() {
131 message.clone()
132 } else if let Some(ref expr) = expression {
133 format!("assertion failed: {}", expr)
134 } else {
135 "assertion failed".to_string()
136 };
137 let label = expression
138 .as_ref()
139 .map(|expr| format!("this expression is false: {}", expr))
140 .or_else(|| Some("assertion failed".to_string()));
141 Diagnostic {
142 code: "ASSERT".to_string(),
143 rql: None,
144 message: base_msg,
145 fragment,
146 label,
147 help: None,
148 notes: vec![],
149 column: None,
150 cause: None,
151 operator_chain: None,
152 }
153 }
154 EngineError::NoneNotAllowed {
155 fragment,
156 column_type,
157 } => Diagnostic {
158 code: "CONSTRAINT_007".to_string(),
159 rql: None,
160 message: format!(
161 "Cannot insert none into non-optional column of type {}. Declare the column as Option({}) to allow none values.",
162 column_type, column_type
163 ),
164 column: None,
165 fragment,
166 label: Some("constraint violation".to_string()),
167 help: Some(format!(
168 "The column type is {} which does not accept none. Use Option({}) if the column should be optional.",
169 column_type, column_type
170 )),
171 notes: vec![],
172 cause: None,
173 operator_chain: None,
174 },
175 EngineError::UnknownCallable {
176 name,
177 fragment,
178 } => Diagnostic {
179 code: "CALLABLE_001".to_string(),
180 rql: None,
181 message: format!("Unknown callable: {}", name),
182 column: None,
183 fragment,
184 label: Some("unknown callable".to_string()),
185 help: Some(
186 "Check the name and available functions, procedures, and closures".to_string()
187 ),
188 notes: vec![],
189 cause: None,
190 operator_chain: None,
191 },
192 EngineError::GeneratorNotFound {
193 name,
194 fragment,
195 } => Diagnostic {
196 code: "FUNCTION_009".to_string(),
197 rql: None,
198 message: format!("Generator function '{}' not found", name),
199 column: None,
200 fragment,
201 label: Some("unknown generator function".to_string()),
202 help: Some("Check the generator function name and ensure it is registered".to_string()),
203 notes: vec![],
204 cause: None,
205 operator_chain: None,
206 },
207 EngineError::MissingPartitionAddress {
208 object,
209 operation,
210 } => Diagnostic {
211 code: "PART_001".to_string(),
212 rql: None,
213 message: format!(
214 "cannot locate partitioned rows for {} on object {}: query object carries no partition address",
215 operation, object
216 ),
217 column: None,
218 fragment: Fragment::None,
219 label: Some("missing partition address".to_string()),
220 help: Some(
221 "the query must carry the row's partition alongside its row number; rewrite the query so the partitioned source's columns flow through unmodified"
222 .to_string(),
223 ),
224 notes: vec![],
225 cause: None,
226 operator_chain: None,
227 },
228
229 EngineError::TimePopulatorMissing {
230 object,
231 column,
232 } => Diagnostic {
233 code: "TIME_001".to_string(),
234 rql: None,
235 message: format!(
236 "`{}` declares `{}` as its #time populator but has no such column",
237 object, column
238 ),
239 column: None,
240 fragment: Fragment::None,
241 label: Some("declared populator does not exist".to_string()),
242 help: Some(
243 "the populator must name one of the object's own columns; correct the `ts` key in the object's WITH clause"
244 .to_string(),
245 ),
246 notes: vec![
247 "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"
248 .to_string(),
249 ],
250 cause: None,
251 operator_chain: None,
252 },
253
254 EngineError::TimePopulatorNotDateTime {
255 object,
256 column,
257 found,
258 } => Diagnostic {
259 code: "TIME_002".to_string(),
260 rql: None,
261 message: format!(
262 "`{}.{}` is the declared #time populator but holds {} on this row",
263 object, column, found
264 ),
265 column: None,
266 fragment: Fragment::None,
267 label: Some("populator is not a non-none DateTime".to_string()),
268 help: Some(
269 "every row of an event-time object must carry a non-none DateTime in its populator column"
270 .to_string(),
271 ),
272 notes: vec![
273 "#time is substrate-owned and cannot fall back to the write clock; silently substituting arrival time would date a replayed row to now"
274 .to_string(),
275 ],
276 cause: None,
277 operator_chain: None,
278 },
279 }
280 }
281}
282
283impl From<EngineError> for Error {
284 fn from(err: EngineError) -> Self {
285 Error(Box::new(err.into_diagnostic()))
286 }
287}