1use reifydb_value::{byte_size::ByteSize, error::Diagnostic, fragment::Fragment};
5
6pub fn memory_limit_exceeded(used: ByteSize, limit: ByteSize) -> Diagnostic {
7 Diagnostic {
8 code: "QUERY_006".to_string(),
9 rql: None,
10 message: format!("query exceeded its memory limit of {} (needs at least {})", limit, used),
11 fragment: Fragment::None,
12 label: Some("this query buffers more data in memory than the per-query limit allows".to_string()),
13 help: Some(
14 "reduce the amount of data the query materializes (narrow the FILTER, project fewer columns, or add a LIMIT), or raise the query memory limit"
15 .to_string(),
16 ),
17 column: None,
18 notes: vec![],
19 cause: None,
20 operator_chain: None,
21 }
22}
23
24pub fn column_not_found(fragment: Fragment) -> Diagnostic {
25 Diagnostic {
26 code: "QUERY_001".to_string(),
27 rql: None,
28 message: "column not found".to_string(),
29 fragment,
30 label: Some("this column does not exist in the current context".to_string()),
31 help: Some("check for typos or ensure the column is defined in the input".to_string()),
32 column: None,
33 notes: vec![],
34 cause: None,
35 operator_chain: None,
36 }
37}
38
39pub fn extend_duplicate_column(fragment: Fragment, column_name: &str) -> Diagnostic {
40 Diagnostic {
41 code: "EXTEND_002".to_string(),
42 rql: None,
43 message: format!("Cannot extend with duplicate column name '{}'", column_name),
44 fragment,
45 label: Some("column already exists in the current frame".to_string()),
46 help: Some("Use a different column name or remove the existing column first".to_string()),
47 column: None,
48 notes: vec![
49 "EXTEND operation cannot add columns that already exist in the frame".to_string(),
50 "Each column name must be unique within the result frame".to_string(),
51 "Consider using MAP if you want to replace existing columns".to_string(),
52 ],
53 cause: None,
54 operator_chain: None,
55 }
56}
57
58pub fn unsupported_source_qualification(fragment: Fragment, name: &str) -> Diagnostic {
59 Diagnostic {
60 code: "QUERY_002".to_string(),
61 rql: None,
62 message: format!("Source qualification '{}' is not supported in RQL expressions", name),
63 fragment,
64 label: Some("source qualification is only allowed for join aliases in ON clauses".to_string()),
65 help: Some("Remove the qualification or use it only with a join alias in the ON clause".to_string()),
66 column: None,
67 notes: vec![
68 "RQL uses a dataframe-centered approach where each operation produces a new dataframe"
69 .to_string(),
70 "Source qualifications are not needed as columns are unambiguous in the dataframe".to_string(),
71 "Only join aliases can be used for qualification within ON clauses to disambiguate columns"
72 .to_string(),
73 ],
74 cause: None,
75 operator_chain: None,
76 }
77}
78
79pub fn system_column_read_only(fragment: Fragment) -> Diagnostic {
80 let name = fragment.text();
81 Diagnostic {
82 code: "QUERY_004".to_string(),
83 rql: None,
84 message: format!("system column '{}' is read-only", name),
85 fragment,
86 label: Some("system columns are managed automatically and cannot be set".to_string()),
87 help: Some("remove this field from the INSERT/UPDATE values".to_string()),
88 column: None,
89 notes: vec![],
90 cause: None,
91 operator_chain: None,
92 }
93}
94
95pub fn system_column_reserved(fragment: Fragment) -> Diagnostic {
96 let name = fragment.text();
97 Diagnostic {
98 code: "QUERY_016".to_string(),
99 rql: None,
100 message: format!("column name '{}' is reserved for system columns", name),
101 fragment,
102 label: Some("names starting with '#' always refer to system columns".to_string()),
103 help: Some("rename the column so it does not start with '#'".to_string()),
104 column: None,
105 notes: vec![],
106 cause: None,
107 operator_chain: None,
108 }
109}
110
111pub fn empty_map(fragment: Fragment) -> Diagnostic {
112 Diagnostic {
113 code: "QUERY_014".to_string(),
114 rql: None,
115 message: "MAP needs at least one field".to_string(),
116 fragment,
117 label: Some("MAP with no fields would return rows with no columns".to_string()),
118 help: Some("list the fields to keep, e.g. MAP { x }".to_string()),
119 column: None,
120 notes: vec![],
121 cause: None,
122 operator_chain: None,
123 }
124}
125
126pub fn duplicate_field(fragment: Fragment, name: &str) -> Diagnostic {
127 Diagnostic {
128 code: "QUERY_009".to_string(),
129 rql: None,
130 message: format!("field '{}' is given more than once", name),
131 fragment,
132 label: Some("a row, constructor, MAP or UPDATE may give each field only once".to_string()),
133 help: Some("remove one of the values for this field".to_string()),
134 column: None,
135 notes: vec![],
136 cause: None,
137 operator_chain: None,
138 }
139}
140
141pub fn as_clause_not_query(fragment: Fragment, kind: &str) -> Diagnostic {
142 Diagnostic {
143 code: "QUERY_005".to_string(),
144 rql: None,
145 message: format!("AS clause body must be a query, found {}", kind),
146 fragment,
147 label: Some("expected a query expression here".to_string()),
148 help: Some(
149 "AS { ... } accepts only query expressions (FROM, MAP, FILTER, JOIN, ...); nested DDL statements such as CREATE/DROP/ALTER are not allowed"
150 .to_string(),
151 ),
152 column: None,
153 notes: vec![],
154 cause: None,
155 operator_chain: None,
156 }
157}
158
159pub fn not_a_query_input(fragment: Fragment, kind: &str) -> Diagnostic {
160 Diagnostic {
161 code: "QUERY_010".to_string(),
162 rql: None,
163 message: format!("{} cannot be the input of a query step", kind),
164 fragment,
165 label: Some("a query was expected here".to_string()),
166 help: Some(format!(
167 "a step such as MAP or FILTER reads from a query; run the {} as its own statement",
168 kind
169 )),
170 column: None,
171 notes: vec![],
172 cause: None,
173 operator_chain: None,
174 }
175}
176
177pub fn window_requires_deferred_view(fragment: Fragment) -> Diagnostic {
178 Diagnostic {
179 code: "QUERY_007".to_string(),
180 rql: None,
181 message: "window runs only in deferred views, not in a batch query".to_string(),
182 fragment,
183 label: Some("a batch query cannot run a window".to_string()),
184 help: Some(
185 "define the window in CREATE DEFERRED VIEW ns::name AS { ... } and read the view with FROM ns::name"
186 .to_string(),
187 ),
188 column: None,
189 notes: vec![],
190 cause: None,
191 operator_chain: None,
192 }
193}
194
195pub fn append_requires_deferred_view(fragment: Fragment) -> Diagnostic {
196 Diagnostic {
197 code: "QUERY_011".to_string(),
198 rql: None,
199 message: "append runs only in deferred views, not in a batch query".to_string(),
200 fragment,
201 label: Some("a batch query cannot run an append".to_string()),
202 help: Some(
203 "define the append in CREATE DEFERRED VIEW ns::name AS { ... } and read the view with FROM ns::name"
204 .to_string(),
205 ),
206 column: None,
207 notes: vec![],
208 cause: None,
209 operator_chain: None,
210 }
211}
212
213pub fn no_column_block(fragment: Fragment, kind: &str, name: &str) -> Diagnostic {
214 let help = match kind {
215 "series" => format!("wait for the bucket to seal, or read the {} with a regular query", kind),
216 _ => format!("wait for the column store to materialize the {}, or read it with a regular query", kind),
217 };
218 Diagnostic {
219 code: "QUERY_012".to_string(),
220 rql: None,
221 message: format!("no column block for {} '{}'", kind, name),
222 fragment,
223 label: Some(format!("this {} has not been materialized into the column store yet", kind)),
224 help: Some(help),
225 column: None,
226 notes: vec![],
227 cause: None,
228 operator_chain: None,
229 }
230}
231
232pub fn unsupported_in_column_layout(what: &str) -> Diagnostic {
233 Diagnostic {
234 code: "QUERY_013".to_string(),
235 rql: None,
236 message: format!("{} cannot be read from the column store", what),
237 fragment: Fragment::None,
238 label: Some("a column query reads only materialized table blocks".to_string()),
239 help: Some("run this query with a regular query instead".to_string()),
240 column: None,
241 notes: vec![],
242 cause: None,
243 operator_chain: None,
244 }
245}
246
247pub fn unknown_apply_operator(fragment: Fragment) -> Diagnostic {
248 let name = fragment.text().to_string();
249 Diagnostic {
250 code: "QUERY_008".to_string(),
251 rql: None,
252 message: format!("unknown operator '{}'", name),
253 fragment,
254 label: Some("no operator with this name is registered".to_string()),
255 help: Some("check the operator name for typos, or register the operator before applying it".to_string()),
256 column: None,
257 notes: vec![],
258 cause: None,
259 operator_chain: None,
260 }
261}
262
263#[cfg(test)]
264mod tests {
265 use super::*;
266
267 #[test]
268 fn memory_limit_exceeded_has_stable_code_and_fields() {
269 let d = memory_limit_exceeded(ByteSize::from_kib(5), ByteSize::from_kib(4));
270 assert_eq!(d.code, "QUERY_006");
271 assert!(matches!(d.fragment, Fragment::None));
272 assert!(d.message.contains("exceeded its memory limit of 4 KiB"), "unexpected message: {}", d.message);
273 assert!(d.message.contains("needs at least 5 KiB"), "unexpected message: {}", d.message);
274 assert!(d.label.unwrap().contains("buffers more data in memory"));
275 assert!(d.help.unwrap().contains("raise the query memory limit"));
276 }
277}