1use super::error::ParseError;
4use super::Parser;
5use crate::ast::{
6 CreateTreeQuery, DropTreeQuery, QueryExpr, TreeCommand, TreeNodeSpec, TreePosition,
7};
8use crate::lexer::Token;
9use reddb_types::json::Value as JsonValue;
10use reddb_types::types::Value;
11
12impl<'a> Parser<'a> {
13 pub fn parse_create_tree_body(&mut self) -> Result<QueryExpr, ParseError> {
14 let if_not_exists = self.match_if_not_exists()?;
15 let name = self.expect_ident()?;
16 self.expect(Token::In)?;
17 let collection = self.expect_ident()?;
18 self.expect_tree_ident("ROOT")?;
19 let root = self.parse_tree_node_spec(false)?;
20 let default_max_children = self.parse_tree_required_max_children()?;
21
22 Ok(QueryExpr::CreateTree(CreateTreeQuery {
23 collection,
24 name,
25 root,
26 default_max_children,
27 if_not_exists,
28 }))
29 }
30
31 pub fn parse_drop_tree_body(&mut self) -> Result<QueryExpr, ParseError> {
32 let if_exists = self.match_if_exists()?;
33 let name = self.expect_ident()?;
34 self.expect(Token::In)?;
35 let collection = self.expect_ident()?;
36 Ok(QueryExpr::DropTree(DropTreeQuery {
37 collection,
38 name,
39 if_exists,
40 }))
41 }
42
43 pub fn parse_tree_command(&mut self) -> Result<QueryExpr, ParseError> {
44 self.expect(Token::Tree)?;
45
46 if self.consume(&Token::Insert)? {
47 self.expect(Token::Into)?;
48 let (collection, tree_name) = self.parse_tree_target()?;
49 self.expect_tree_ident("PARENT")?;
50 let parent_id = self.parse_tree_entity_id()?;
51 let node = self.parse_tree_node_spec(true)?;
52 let position = self.parse_tree_position()?;
53 return Ok(QueryExpr::TreeCommand(TreeCommand::Insert {
54 collection,
55 tree_name,
56 parent_id,
57 node,
58 position,
59 }));
60 }
61
62 if self.consume_ident_ci("MOVE")? {
63 let (collection, tree_name) = self.parse_tree_target()?;
64 self.expect(Token::Node)?;
65 let node_id = self.parse_tree_entity_id()?;
66 self.expect(Token::To)?;
67 self.expect_tree_ident("PARENT")?;
68 let parent_id = self.parse_tree_entity_id()?;
69 let position = self.parse_tree_position()?;
70 return Ok(QueryExpr::TreeCommand(TreeCommand::Move {
71 collection,
72 tree_name,
73 node_id,
74 parent_id,
75 position,
76 }));
77 }
78
79 if self.consume(&Token::Delete)? {
80 let (collection, tree_name) = self.parse_tree_target()?;
81 self.expect(Token::Node)?;
82 let node_id = self.parse_tree_entity_id()?;
83 return Ok(QueryExpr::TreeCommand(TreeCommand::Delete {
84 collection,
85 tree_name,
86 node_id,
87 }));
88 }
89
90 if self.consume_ident_ci("VALIDATE")? {
91 let (collection, tree_name) = self.parse_tree_target()?;
92 return Ok(QueryExpr::TreeCommand(TreeCommand::Validate {
93 collection,
94 tree_name,
95 }));
96 }
97
98 if self.consume_ident_ci("REBALANCE")? {
99 let (collection, tree_name) = self.parse_tree_target()?;
100 let dry_run = if self.consume_ident_ci("DRY")? {
101 self.expect_tree_ident("RUN")?;
102 true
103 } else {
104 false
105 };
106 return Ok(QueryExpr::TreeCommand(TreeCommand::Rebalance {
107 collection,
108 tree_name,
109 dry_run,
110 }));
111 }
112
113 Err(ParseError::expected(
114 vec!["INSERT", "MOVE", "DELETE", "VALIDATE", "REBALANCE"],
115 self.peek(),
116 self.position(),
117 ))
118 }
119
120 fn parse_tree_target(&mut self) -> Result<(String, String), ParseError> {
121 let collection = self.expect_ident()?;
122 self.expect(Token::Dot)?;
123 let tree_name = self.expect_ident()?;
124 Ok((collection, tree_name))
125 }
126
127 fn parse_tree_node_spec(
128 &mut self,
129 allow_max_children: bool,
130 ) -> Result<TreeNodeSpec, ParseError> {
131 self.expect_tree_ident("LABEL")?;
132 let label = self.parse_tree_string_like()?;
133 let mut node_type = None;
134 let mut properties = Vec::new();
135 let mut metadata = Vec::new();
136 let mut max_children = None;
137
138 loop {
139 if self.consume_ident_ci("TYPE")? {
140 node_type = Some(self.parse_tree_string_like()?);
141 } else if self.consume(&Token::Properties)? {
142 properties = self.parse_tree_object_literal_entries()?;
143 } else if self.consume(&Token::Metadata)? {
144 metadata = self.parse_tree_object_literal_entries()?;
145 } else if allow_max_children
146 && (self.consume_ident_ci("MAX_CHILDREN")?
147 || self.consume_ident_ci("MAXCHILDREN")?)
148 {
149 max_children = Some(self.parse_tree_positive_usize()?);
150 } else {
151 break;
152 }
153 }
154
155 Ok(TreeNodeSpec {
156 label,
157 node_type,
158 properties,
159 metadata,
160 max_children,
161 })
162 }
163
164 fn parse_tree_position(&mut self) -> Result<TreePosition, ParseError> {
165 if !(self.consume_ident_ci("POSITION")?) {
166 return Ok(TreePosition::Last);
167 }
168
169 if self.consume(&Token::First)? {
170 return Ok(TreePosition::First);
171 }
172 if self.consume(&Token::Last)? {
173 return Ok(TreePosition::Last);
174 }
175
176 Ok(TreePosition::Index(self.parse_tree_positive_usize()?))
177 }
178
179 fn parse_tree_required_max_children(&mut self) -> Result<usize, ParseError> {
180 if !(self.consume_ident_ci("MAX_CHILDREN")? || self.consume_ident_ci("MAXCHILDREN")?) {
181 return Err(ParseError::expected(
182 vec!["MAX_CHILDREN"],
183 self.peek(),
184 self.position(),
185 ));
186 }
187 self.parse_tree_positive_usize()
188 }
189
190 fn parse_tree_entity_id(&mut self) -> Result<u64, ParseError> {
191 match self.peek().clone() {
192 Token::Integer(value) if value > 0 => {
193 self.advance()?;
194 Ok(value as u64)
195 }
196 Token::String(value) => {
197 self.advance()?;
198 parse_tree_entity_id_text(&value).ok_or_else(|| {
199 ParseError::new(
200 format!("invalid tree entity id {value:?}"),
205 self.position(),
206 )
207 })
208 }
209 other => Err(ParseError::expected(
210 vec!["entity id"],
211 &other,
212 self.position(),
213 )),
214 }
215 }
216
217 fn parse_tree_positive_usize(&mut self) -> Result<usize, ParseError> {
218 let value = self.parse_integer()?;
219 if value <= 0 {
220 return Err(ParseError::new(
221 "expected a positive integer".to_string(),
222 self.position(),
223 ));
224 }
225 Ok(value as usize)
226 }
227
228 fn parse_tree_string_like(&mut self) -> Result<String, ParseError> {
229 match self.peek().clone() {
230 Token::String(value) => {
231 self.advance()?;
232 Ok(value)
233 }
234 _ => self.expect_ident_or_keyword(),
235 }
236 }
237
238 fn expect_tree_ident(&mut self, expected: &str) -> Result<(), ParseError> {
239 if self.consume_ident_ci(expected)? {
240 return Ok(());
241 }
242 Err(ParseError::expected(
243 vec![expected],
244 self.peek(),
245 self.position(),
246 ))
247 }
248
249 fn parse_tree_object_literal_entries(&mut self) -> Result<Vec<(String, Value)>, ParseError> {
250 let literal = self.parse_literal_value()?;
251 let Value::Json(bytes) = literal else {
252 return Err(ParseError::new(
253 "expected object literal".to_string(),
254 self.position(),
255 ));
256 };
257 let decoded = reddb_types::json::from_slice::<JsonValue>(&bytes).map_err(|err| {
258 ParseError::new(
259 format!("failed to decode object literal: {:?}", err.to_string()),
263 self.position(),
264 )
265 })?;
266 let JsonValue::Object(object) = decoded else {
267 return Err(ParseError::new(
268 "expected object literal".to_string(),
269 self.position(),
270 ));
271 };
272 object
273 .into_iter()
274 .map(|(key, value)| Ok((key, tree_json_value_to_storage_value(&value)?)))
275 .collect()
276 }
277}
278
279fn parse_tree_entity_id_text(value: &str) -> Option<u64> {
280 value
281 .strip_prefix('e')
282 .unwrap_or(value)
283 .parse::<u64>()
284 .ok()
285 .filter(|value| *value > 0)
286}
287
288fn tree_json_value_to_storage_value(value: &JsonValue) -> Result<Value, ParseError> {
289 Ok(match value {
290 JsonValue::Null => Value::Null,
291 JsonValue::Bool(value) => Value::Boolean(*value),
292 JsonValue::Number(value) => {
293 if value.fract().abs() < f64::EPSILON {
294 Value::Integer(*value as i64)
295 } else {
296 Value::Float(*value)
297 }
298 }
299 JsonValue::String(value) => Value::text(value.clone()),
300 JsonValue::Array(_) | JsonValue::Object(_) => {
301 Value::Json(reddb_types::json::to_vec(value).map_err(|err| {
302 ParseError::new(
303 format!("failed to encode nested JSON value: {:?}", err.to_string()),
306 crate::lexer::Position::default(),
307 )
308 })?)
309 }
310 })
311}
312
313#[cfg(test)]
314mod tests {
315 use super::*;
316
317 fn parse_query(input: &str) -> Result<QueryExpr, ParseError> {
318 crate::parser::parse(input).map(|query| query.query)
319 }
320
321 fn entry_value<'a>(entries: &'a [(String, Value)], key: &str) -> &'a Value {
322 entries
323 .iter()
324 .find_map(|(entry_key, value)| (entry_key == key).then_some(value))
325 .unwrap_or_else(|| panic!("missing entry {key}"))
326 }
327
328 #[test]
329 fn create_tree_accepts_root_options_and_maxchildren_alias() {
330 let query = parse_query(
331 "CREATE TREE IF NOT EXISTS org IN forest ROOT LABEL 'Company' TYPE root \
332 METADATA {active: true, rank: 3, nested: {tier: 'gold'}} MAXCHILDREN 2",
333 )
334 .unwrap();
335
336 let QueryExpr::CreateTree(tree) = query else {
337 panic!("expected create tree");
338 };
339 assert_eq!(tree.collection, "forest");
340 assert_eq!(tree.name, "org");
341 assert!(tree.if_not_exists);
342 assert_eq!(tree.default_max_children, 2);
343 assert_eq!(tree.root.label, "Company");
344 assert_eq!(tree.root.node_type.as_deref(), Some("root"));
345 assert_eq!(tree.root.max_children, None);
346 assert_eq!(
347 entry_value(&tree.root.metadata, "active"),
348 &Value::Boolean(true)
349 );
350 assert_eq!(entry_value(&tree.root.metadata, "rank"), &Value::Integer(3));
351 assert!(matches!(
352 entry_value(&tree.root.metadata, "nested"),
353 Value::Json(_)
354 ));
355 }
356
357 #[test]
358 fn tree_insert_defaults_to_last_and_accepts_string_entity_ids() {
359 let query = parse_query(
360 "TREE INSERT INTO forest.org PARENT 'e42' LABEL child \
361 PROPERTIES {name: 'Platform'} MAX_CHILDREN 4",
362 )
363 .unwrap();
364
365 let QueryExpr::TreeCommand(TreeCommand::Insert {
366 collection,
367 tree_name,
368 parent_id,
369 node,
370 position,
371 }) = query
372 else {
373 panic!("expected tree insert");
374 };
375 assert_eq!(collection, "forest");
376 assert_eq!(tree_name, "org");
377 assert_eq!(parent_id, 42);
378 assert_eq!(node.label, "child");
379 assert_eq!(node.max_children, Some(4));
380 assert!(matches!(
381 entry_value(&node.properties, "name"),
382 Value::Text(value) if value.as_ref() == "Platform"
383 ));
384 assert_eq!(position, TreePosition::Last);
385 }
386
387 #[test]
388 fn tree_commands_cover_move_delete_validate_rebalance_and_drop() {
389 let query = parse_query("TREE MOVE forest.org NODE 'e9' TO PARENT 1 POSITION 3").unwrap();
390 let QueryExpr::TreeCommand(TreeCommand::Move {
391 collection,
392 tree_name,
393 node_id,
394 parent_id,
395 position,
396 }) = query
397 else {
398 panic!("expected tree move");
399 };
400 assert_eq!(collection, "forest");
401 assert_eq!(tree_name, "org");
402 assert_eq!(node_id, 9);
403 assert_eq!(parent_id, 1);
404 assert_eq!(position, TreePosition::Index(3));
405
406 let query = parse_query("TREE DELETE forest.org NODE 5").unwrap();
407 assert!(matches!(
408 query,
409 QueryExpr::TreeCommand(TreeCommand::Delete {
410 collection,
411 tree_name,
412 node_id,
413 }) if collection == "forest" && tree_name == "org" && node_id == 5
414 ));
415
416 let query = parse_query("TREE VALIDATE forest.org").unwrap();
417 assert!(matches!(
418 query,
419 QueryExpr::TreeCommand(TreeCommand::Validate {
420 collection,
421 tree_name,
422 }) if collection == "forest" && tree_name == "org"
423 ));
424
425 let query = parse_query("TREE REBALANCE forest.org").unwrap();
426 assert!(matches!(
427 query,
428 QueryExpr::TreeCommand(TreeCommand::Rebalance {
429 collection,
430 tree_name,
431 dry_run,
432 }) if collection == "forest" && tree_name == "org" && !dry_run
433 ));
434
435 let query = parse_query("DROP TREE IF EXISTS org IN forest").unwrap();
436 assert!(matches!(
437 query,
438 QueryExpr::DropTree(drop) if drop.name == "org"
439 && drop.collection == "forest"
440 && drop.if_exists
441 ));
442 }
443
444 #[test]
445 fn tree_parser_rejects_malformed_commands() {
446 for sql in [
447 "CREATE TREE org IN forest ROOT LABEL root",
448 "TREE INSERT INTO forest.org PARENT 0 LABEL child",
449 "TREE INSERT INTO forest.org PARENT 1 LABEL child POSITION 0",
450 "TREE INSERT INTO forest.org PARENT 1 LABEL child PROPERTIES 'not-object'",
451 "TREE UNKNOWN forest.org",
452 ] {
453 assert!(parse_query(sql).is_err(), "{sql} should not parse");
454 }
455 }
456}