1use super::error::ParseError;
4use super::Parser;
5use crate::ast::{
6 AskCacheClause, AskQuery, BinOp, DeleteQuery, Expr, FieldRef, Filter, InsertEntityType,
7 InsertQuery, OrderByClause, QueryExpr, ReturningItem, UpdateQuery, UpdateTarget,
8};
9use crate::lexer::Token;
10use crate::sql_lowering::{filter_to_expr, fold_expr_to_value};
11use reddb_types::types::Value;
12
13pub(crate) const JSON_LITERAL_MAX_DEPTH: u32 = 128;
17
18pub(crate) fn json_literal_depth_check(
22 value: &reddb_types::utils::json::JsonValue,
23) -> Result<(), String> {
24 use reddb_types::utils::json::JsonValue;
25 let mut stack: Vec<(&JsonValue, u32)> = vec![(value, 1)];
26 while let Some((node, depth)) = stack.pop() {
27 if depth > JSON_LITERAL_MAX_DEPTH {
28 return Err(format!(
29 "JSON object literal exceeds JSON_LITERAL_MAX_DEPTH ({})",
30 JSON_LITERAL_MAX_DEPTH
31 ));
32 }
33 match node {
34 JsonValue::Object(entries) => {
35 for (_, v) in entries {
36 stack.push((v, depth + 1));
37 }
38 }
39 JsonValue::Array(items) => {
40 for v in items {
41 stack.push((v, depth + 1));
42 }
43 }
44 _ => {}
45 }
46 }
47 Ok(())
48}
49
50impl<'a> Parser<'a> {
51 pub fn parse_insert_query(&mut self) -> Result<QueryExpr, ParseError> {
53 self.expect(Token::Insert)?;
54 self.expect(Token::Into)?;
55 if matches!(self.peek(), Token::Metric) {
63 return Err(ParseError::new(
64 "INSERT INTO METRIC is not supported in Analytics v0 — \
65 write raw samples into an ordinary TABLE/DOCUMENT \
66 collection; the metric descriptor catalog is reached \
67 via CREATE METRIC and red.analytics.metrics \
68 (PRD #782 non-goal)",
69 self.position(),
70 ));
71 }
72 let table = self.expect_ident()?;
73
74 let entity_type = match self.peek().clone() {
76 Token::Node => {
77 self.advance()?;
78 InsertEntityType::Node
79 }
80 Token::Edge => {
81 self.advance()?;
82 InsertEntityType::Edge
83 }
84 Token::Vector => {
85 self.advance()?;
86 InsertEntityType::Vector
87 }
88 Token::Document => {
89 self.advance()?;
90 InsertEntityType::Document
91 }
92 Token::Kv => {
93 self.advance()?;
94 InsertEntityType::Kv
95 }
96 _ => InsertEntityType::Row,
97 };
98
99 self.expect(Token::LParen)?;
101 let columns = self.parse_ident_list()?;
102 self.expect(Token::RParen)?;
103
104 self.expect(Token::Values)?;
106 let mut all_values = Vec::new();
107 let mut all_value_exprs = Vec::new();
108 loop {
109 self.expect(Token::LParen)?;
110 let row_exprs = self.parse_dml_expr_list()?;
111 self.expect(Token::RParen)?;
112 let row_values = row_exprs
121 .iter()
122 .map(|expr| match fold_expr_to_value(expr.clone()) {
123 Ok(value) => Ok(value),
124 Err(msg) => {
125 if crate::sql_lowering::expr_contains_parameter(expr) {
126 Ok(Value::Null)
127 } else {
128 Err(msg)
129 }
130 }
131 })
132 .collect::<Result<Vec<_>, _>>()
133 .map_err(|msg| ParseError::new(msg, self.position()))?;
134 all_value_exprs.push(row_exprs);
135 all_values.push(row_values);
136 if !self.consume(&Token::Comma)? {
137 break;
138 }
139 }
140
141 let (ttl_ms, expires_at_ms, with_metadata, auto_embed) = self.parse_with_clauses()?;
143
144 let returning = self.parse_returning_clause()?;
145
146 let suppress_events = if self.consume_ident_ci("SUPPRESS")? {
147 self.expect_ident_ci("EVENTS")?;
148 true
149 } else {
150 false
151 };
152
153 Ok(QueryExpr::Insert(InsertQuery {
154 table,
155 entity_type,
156 columns,
157 value_exprs: all_value_exprs,
158 values: all_values,
159 returning,
160 ttl_ms,
161 expires_at_ms,
162 with_metadata,
163 auto_embed,
164 suppress_events,
165 }))
166 }
167
168 fn parse_ttl_duration(&mut self) -> Result<u64, ParseError> {
170 let ttl_value = self.parse_float()?;
172 let ttl_unit = match self.peek() {
173 Token::Ident(unit) => {
174 let unit = unit.clone();
175 self.advance()?;
176 unit
177 }
178 _ => "s".to_string(),
179 };
180
181 let multiplier_ms = match ttl_unit.to_ascii_lowercase().as_str() {
182 "ms" | "msec" | "millisecond" | "milliseconds" => 1.0,
183 "s" | "sec" | "secs" | "second" | "seconds" => 1_000.0,
184 "m" | "min" | "mins" | "minute" | "minutes" => 60_000.0,
185 "h" | "hr" | "hrs" | "hour" | "hours" => 3_600_000.0,
186 "d" | "day" | "days" => 86_400_000.0,
187 other => {
188 return Err(ParseError::new(
189 format!(
193 "unsupported TTL unit {other:?}; supported units: ms, s, m, h, d (e.g. `WITH TTL 30 m`)"
194 ),
195 self.position(),
196 ));
197 }
198 };
199
200 Ok((ttl_value * multiplier_ms) as u64)
201 }
202
203 pub fn parse_with_clauses(
206 &mut self,
207 ) -> Result<
208 (
209 Option<u64>,
210 Option<u64>,
211 Vec<(String, Value)>,
212 Option<crate::ast::AutoEmbedConfig>,
213 ),
214 ParseError,
215 > {
216 let mut ttl_ms = None;
217 let mut expires_at_ms = None;
218 let mut with_metadata = Vec::new();
219 let mut auto_embed = None;
220
221 while self.consume(&Token::With)? {
222 if self.consume_ident_ci("TTL")? {
223 ttl_ms = Some(self.parse_ttl_duration()?);
224 } else if self.consume_ident_ci("EXPIRES")? {
225 self.expect_ident_ci("AT")?;
226 let ts = self.parse_expires_at_value()?;
227 expires_at_ms = Some(ts);
228 } else if self.consume(&Token::Metadata)? || self.consume_ident_ci("METADATA")? {
229 with_metadata = self.parse_with_metadata_pairs()?;
230 } else if self.consume_ident_ci("AUTO")? {
231 self.consume_ident_ci("EMBED")?;
233 self.expect(Token::LParen)?;
234 let mut fields = Vec::new();
235 loop {
236 fields.push(self.expect_ident()?);
237 if !self.consume(&Token::Comma)? {
238 break;
239 }
240 }
241 self.expect(Token::RParen)?;
242 let provider = if self.consume(&Token::Using)? {
247 self.expect_ident()?
248 } else {
249 "openai".to_string()
250 };
251 let model = if self.consume_ident_ci("MODEL")? {
252 Some(self.parse_string()?)
253 } else {
254 None
255 };
256 auto_embed = Some(crate::ast::AutoEmbedConfig {
257 fields,
258 provider,
259 model,
260 });
261 } else {
262 return Err(ParseError::expected(
263 vec!["TTL", "EXPIRES AT", "METADATA", "AUTO EMBED"],
264 self.peek(),
265 self.position(),
266 ));
267 }
268 }
269
270 Ok((ttl_ms, expires_at_ms, with_metadata, auto_embed))
271 }
272
273 fn expect_ident_ci(&mut self, expected: &str) -> Result<(), ParseError> {
275 if self.consume_ident_ci(expected)? {
276 Ok(())
277 } else {
278 Err(ParseError::expected(
279 vec![expected],
280 self.peek(),
281 self.position(),
282 ))
283 }
284 }
285
286 fn parse_expires_at_value(&mut self) -> Result<u64, ParseError> {
288 if let Ok(value) = self.parse_integer() {
290 return Ok(value as u64);
291 }
292 if let Ok(text) = self.parse_string() {
294 let trimmed = text.trim();
296 if let Ok(ts) = trimmed.parse::<u64>() {
297 return Ok(ts);
298 }
299 return Err(ParseError::new(
301 format!("EXPIRES AT requires a unix timestamp in milliseconds, got {trimmed:?}"),
305 self.position(),
306 ));
307 }
308 Err(ParseError::expected(
309 vec!["timestamp (unix ms) or 'YYYY-MM-DD'"],
310 self.peek(),
311 self.position(),
312 ))
313 }
314
315 fn parse_with_metadata_pairs(&mut self) -> Result<Vec<(String, Value)>, ParseError> {
317 self.expect(Token::LParen)?;
318 let mut pairs = Vec::new();
319 if !self.check(&Token::RParen) {
320 loop {
321 let key = self.expect_ident_or_keyword()?.to_ascii_lowercase();
322 self.expect(Token::Eq)?;
323 let value = self.parse_literal_value()?;
324 pairs.push((key, value));
325 if !self.consume(&Token::Comma)? {
326 break;
327 }
328 }
329 }
330 self.expect(Token::RParen)?;
331 Ok(pairs)
332 }
333
334 pub fn parse_update_query(&mut self) -> Result<QueryExpr, ParseError> {
336 self.expect(Token::Update)?;
337 let table = self.expect_ident()?;
338 let target = self.parse_update_target()?;
339 self.expect(Token::Set)?;
340
341 let mut assignments = Vec::new();
342 let mut assignment_exprs = Vec::new();
343 let mut compound_assignment_ops = Vec::new();
344 loop {
345 let col = self.expect_column_ident()?;
346 let compound_op = if self.consume(&Token::Eq)? {
347 None
348 } else {
349 let op = match self.peek() {
350 Token::Plus => BinOp::Add,
351 Token::Dash | Token::Minus => BinOp::Sub,
352 Token::Star => BinOp::Mul,
353 Token::Slash => BinOp::Div,
354 Token::Percent => BinOp::Mod,
355 _ => {
356 return Err(ParseError::expected(
357 vec!["=", "+=", "-=", "*=", "/=", "%="],
358 self.peek(),
359 self.position(),
360 ));
361 }
362 };
363 self.advance()?;
364 self.expect(Token::Eq)?;
365 Some(op)
366 };
367 let expr = self.parse_expr()?;
368 let folded = fold_expr_to_value(expr.clone()).ok();
369 assignment_exprs.push((col.clone(), expr));
370 compound_assignment_ops.push(compound_op);
371 if compound_op.is_none() {
372 if let Some(val) = folded {
373 assignments.push((col.clone(), val));
374 }
375 }
376 if !self.consume(&Token::Comma)? {
377 break;
378 }
379 }
380
381 let filter = if self.consume(&Token::Where)? {
382 Some(self.parse_filter()?)
383 } else {
384 None
385 };
386 let where_expr = filter.as_ref().map(filter_to_expr);
387
388 let (ttl_ms, expires_at_ms, with_metadata, _auto_embed) = self.parse_with_clauses()?;
389
390 let mut order_by = if self.consume(&Token::Order)? {
391 self.expect(Token::By)?;
392 let clauses = self.parse_order_by_list()?;
393 validate_update_order_by(&clauses, self.position())?;
394 clauses
395 } else {
396 Vec::new()
397 };
398
399 let limit = if self.consume(&Token::Limit)? {
404 Some(self.parse_integer()? as u64)
405 } else {
406 None
407 };
408 if !order_by.is_empty() && limit.is_none() {
409 return Err(ParseError::new(
410 "UPDATE ORDER BY requires LIMIT",
411 self.position(),
412 ));
413 }
414 if !order_by.is_empty() && !update_order_by_mentions_rid(&order_by) {
415 order_by.push(OrderByClause {
416 field: FieldRef::TableColumn {
417 table: String::new(),
418 column: "rid".to_string(),
419 },
420 expr: None,
421 ascending: true,
422 nulls_first: false,
423 });
424 }
425
426 let returning = self.parse_returning_clause()?;
427
428 let suppress_events = if self.consume_ident_ci("SUPPRESS")? {
429 self.expect_ident_ci("EVENTS")?;
430 true
431 } else {
432 false
433 };
434
435 Ok(QueryExpr::Update(UpdateQuery {
436 table,
437 target,
438 assignment_exprs,
439 compound_assignment_ops,
440 assignments,
441 where_expr,
442 filter,
443 ttl_ms,
444 expires_at_ms,
445 with_metadata,
446 returning,
447 order_by,
448 limit,
449 suppress_events,
450 }))
451 }
452
453 fn parse_update_target(&mut self) -> Result<UpdateTarget, ParseError> {
454 if self.consume(&Token::Kv)? {
455 return Ok(UpdateTarget::Kv);
456 }
457 if self.consume(&Token::Rows)? {
458 return Ok(UpdateTarget::Rows);
459 }
460 if self.consume_ident_ci("DOCUMENTS")? {
461 return Ok(UpdateTarget::Documents);
462 }
463 if self.consume_ident_ci("NODES")? {
464 return Ok(UpdateTarget::Nodes);
465 }
466 if self.consume_ident_ci("EDGES")? {
467 return Ok(UpdateTarget::Edges);
468 }
469 Ok(UpdateTarget::Rows)
470 }
471
472 pub fn parse_delete_query(&mut self) -> Result<QueryExpr, ParseError> {
474 self.expect(Token::Delete)?;
475 self.expect(Token::From)?;
476 let table = self.expect_ident()?;
477
478 let filter = if self.consume(&Token::Where)? {
479 Some(self.parse_filter()?)
480 } else {
481 None
482 };
483
484 let where_expr = filter.as_ref().map(filter_to_expr);
485
486 let returning = self.parse_returning_clause()?;
487
488 let suppress_events = if self.consume_ident_ci("SUPPRESS")? {
489 self.expect_ident_ci("EVENTS")?;
490 true
491 } else {
492 false
493 };
494
495 Ok(QueryExpr::Delete(DeleteQuery {
496 table,
497 where_expr,
498 filter,
499 returning,
500 suppress_events,
501 }))
502 }
503
504 fn parse_returning_clause(&mut self) -> Result<Option<Vec<ReturningItem>>, ParseError> {
508 if !self.consume(&Token::Returning)? {
509 return Ok(None);
510 }
511 if self.consume(&Token::Star)? {
512 return Ok(Some(vec![ReturningItem::All]));
513 }
514 let mut items = Vec::new();
515 loop {
516 if returning_expr_start(self.peek()) {
517 return Err(returning_expr_not_supported(self.position()));
518 }
519 let col = self.expect_update_returning_column()?;
520 items.push(ReturningItem::Column(col));
521 if returning_expr_tail(self.peek()) {
522 return Err(returning_expr_not_supported(self.position()));
523 }
524 if !self.consume(&Token::Comma)? {
525 break;
526 }
527 }
528 if items.is_empty() {
529 return Err(ParseError::expected(
530 vec!["*", "column name"],
531 self.peek(),
532 self.position(),
533 ));
534 }
535 Ok(Some(items))
536 }
537
538 fn expect_update_returning_column(&mut self) -> Result<String, ParseError> {
539 if self.consume(&Token::Weight)? {
540 return Ok("weight".to_string());
541 }
542 self.expect_ident_or_keyword()
543 }
544
545 pub fn parse_ask_query(&mut self) -> Result<QueryExpr, ParseError> {
548 self.parse_ask_query_with_explain(false)
549 }
550
551 pub fn parse_explain_ask_query(&mut self) -> Result<QueryExpr, ParseError> {
553 self.advance()?; if !matches!(self.peek(), Token::Ident(name) if name.eq_ignore_ascii_case("ASK")) {
555 return Err(ParseError::expected(
556 vec!["ASK"],
557 self.peek(),
558 self.position(),
559 ));
560 }
561 self.parse_ask_query_with_explain(true)
562 }
563
564 fn parse_ask_query_with_explain(&mut self, explain: bool) -> Result<QueryExpr, ParseError> {
565 self.advance()?; let (question, question_param) = match self.peek() {
568 Token::String(_) => (self.parse_string()?, None),
569 Token::Dollar | Token::Question => {
570 let index = self.parse_param_slot("ASK question")?;
571 (String::new(), Some(index))
572 }
573 other => {
574 return Err(ParseError::expected(
575 vec!["string", "$N", "?"],
576 other,
577 self.position(),
578 ));
579 }
580 };
581
582 let mut provider = None;
583 let mut model = None;
584 let mut depth = None;
585 let mut limit = None;
586 let mut min_score = None;
587 let mut collection = None;
588 let mut temperature = None;
589 let mut seed = None;
590 let mut strict = true;
591 let mut stream = false;
592 let mut cache = AskCacheClause::Default;
593
594 for _ in 0..12 {
597 if self.consume(&Token::Using)? {
598 provider = Some(match &self.current.token {
599 Token::String(_) => self.parse_string()?,
600 _ => self.expect_ident()?,
601 });
602 } else if self.consume_ident_ci("MODEL")? {
603 model = Some(self.parse_string()?);
604 } else if self.consume(&Token::Depth)? {
605 depth = Some(self.parse_integer()? as usize);
606 } else if self.consume(&Token::Limit)? {
607 limit = Some(self.parse_integer()? as usize);
608 } else if self.consume(&Token::MinScore)? {
609 min_score = Some(self.parse_float()? as f32);
610 } else if self.consume(&Token::Collection)? {
611 collection = Some(self.expect_ident()?);
612 } else if self.consume_ident_ci("TEMPERATURE")? {
613 temperature = Some(self.parse_float()? as f32);
614 } else if self.consume_ident_ci("SEED")? {
615 seed = Some(self.parse_integer()? as u64);
616 } else if self.consume_ident_ci("STRICT")? {
617 let value = self.expect_ident_or_keyword()?;
618 if value.eq_ignore_ascii_case("ON") {
619 strict = true;
620 } else if value.eq_ignore_ascii_case("OFF") {
621 strict = false;
622 } else {
623 return Err(ParseError::new(
624 "Expected ON or OFF after STRICT",
625 self.position(),
626 ));
627 }
628 } else if self.consume_ident_ci("STREAM")? {
629 stream = true;
630 } else if self.consume_ident_ci("CACHE")? {
631 if !matches!(cache, AskCacheClause::Default) {
632 return Err(ParseError::new(
633 "ASK cache clause specified more than once",
634 self.position(),
635 ));
636 }
637 let ttl = self.expect_ident_or_keyword()?;
638 if !ttl.eq_ignore_ascii_case("TTL") {
639 return Err(ParseError::new("Expected TTL after CACHE", self.position()));
640 }
641 cache = AskCacheClause::CacheTtl(self.parse_string()?);
642 } else if self.consume_ident_ci("NOCACHE")? {
643 if !matches!(cache, AskCacheClause::Default) {
644 return Err(ParseError::new(
645 "ASK cache clause specified more than once",
646 self.position(),
647 ));
648 }
649 cache = AskCacheClause::NoCache;
650 } else {
651 break;
652 }
653 }
654
655 Ok(QueryExpr::Ask(AskQuery {
656 explain,
657 question,
658 question_param,
659 provider,
660 model,
661 depth,
662 limit,
663 min_score,
664 collection,
665 temperature,
666 seed,
667 strict,
668 stream,
669 cache,
670 }))
671 }
672
673 fn parse_ident_list(&mut self) -> Result<Vec<String>, ParseError> {
675 let mut idents = Vec::new();
676 loop {
677 idents.push(self.expect_ident_or_keyword()?);
678 if !self.consume(&Token::Comma)? {
679 break;
680 }
681 }
682 Ok(idents)
683 }
684
685 fn parse_dml_value_list(&mut self) -> Result<Vec<Value>, ParseError> {
687 self.parse_dml_expr_list()?
688 .into_iter()
689 .map(fold_expr_to_value)
690 .collect::<Result<Vec<_>, _>>()
691 .map_err(|msg| ParseError::new(msg, self.position()))
692 }
693
694 fn parse_dml_expr_list(&mut self) -> Result<Vec<Expr>, ParseError> {
695 let mut values = Vec::new();
696 loop {
697 values.push(self.parse_expr()?);
698 if !self.consume(&Token::Comma)? {
699 break;
700 }
701 }
702 Ok(values)
703 }
704
705 pub(crate) fn parse_literal_value(&mut self) -> Result<Value, ParseError> {
707 self.enter_depth()?;
716 let result = self.parse_literal_value_inner();
717 self.exit_depth();
718 result
719 }
720
721 fn parse_literal_value_inner(&mut self) -> Result<Value, ParseError> {
722 if let Token::Ident(name) = self.peek().clone() {
729 let upper = name.to_uppercase();
730 if upper == "PASSWORD" || upper == "SECRET" {
731 self.advance()?; self.expect(Token::LParen)?;
733 let plaintext = self.parse_string()?;
734 self.expect(Token::RParen)?;
735 return Ok(match upper.as_str() {
736 "PASSWORD" => Value::Password(format!("@@plain@@{plaintext}")),
737 "SECRET" => Value::Secret(format!("@@plain@@{plaintext}").into_bytes()),
738 _ => unreachable!(),
739 });
740 }
741 if upper == "SECRET_REF" {
742 self.advance()?; self.expect(Token::LParen)?;
744 let store = self.expect_ident_or_keyword()?.to_ascii_lowercase();
745 if store != "vault" {
746 return Err(ParseError::expected(
747 vec!["vault"],
748 self.peek(),
749 self.position(),
750 ));
751 }
752 self.expect(Token::Comma)?;
753 let (collection, key) =
754 self.parse_kv_key(reddb_types::catalog::CollectionModel::Vault)?;
755 self.expect(Token::RParen)?;
756 return Ok(secret_ref_value(&store, &collection, &key));
757 }
758 }
759
760 match self.peek().clone() {
761 Token::String(s) => {
762 let s = s.clone();
763 self.advance()?;
764 Ok(Value::text(s))
765 }
766 Token::JsonLiteral(raw) => {
767 self.advance()?;
772 let json_value = reddb_types::utils::json::parse_json(&raw).map_err(|err| {
773 ParseError::new(
774 format!("invalid JSON object literal: {:?}", err.to_string()),
780 self.position(),
781 )
782 })?;
783 json_literal_depth_check(&json_value)
784 .map_err(|err| ParseError::new(err, self.position()))?;
785 let canonical = reddb_types::serde_json::Value::from(json_value);
786 let bytes = reddb_types::json::to_vec(&canonical).map_err(|err| {
787 ParseError::new(
788 format!("failed to encode JSON literal: {:?}", err.to_string()),
792 self.position(),
793 )
794 })?;
795 Ok(Value::Json(bytes))
796 }
797 Token::Integer(n) => {
798 self.advance()?;
799 Ok(Value::Integer(n))
800 }
801 Token::Float(n) => {
802 self.advance()?;
803 Ok(Value::Float(n))
804 }
805 Token::True => {
806 self.advance()?;
807 Ok(Value::Boolean(true))
808 }
809 Token::False => {
810 self.advance()?;
811 Ok(Value::Boolean(false))
812 }
813 Token::Null => {
814 self.advance()?;
815 Ok(Value::Null)
816 }
817 Token::LBracket => {
818 self.advance()?; let mut items = Vec::new();
822 if !self.check(&Token::RBracket) {
823 loop {
824 items.push(self.parse_literal_value()?);
825 if !self.consume(&Token::Comma)? {
826 break;
827 }
828 }
829 }
830 self.expect(Token::RBracket)?;
831
832 let all_numeric = items
834 .iter()
835 .all(|v| matches!(v, Value::Integer(_) | Value::Float(_)));
836 if all_numeric && !items.is_empty() {
837 let floats: Vec<f32> = items
838 .iter()
839 .map(|v| match v {
840 Value::Float(f) => *f as f32,
841 Value::Integer(i) => *i as f32,
842 _ => 0.0,
843 })
844 .collect();
845 Ok(Value::Vector(floats))
846 } else {
847 let json_arr: Vec<reddb_types::json::Value> = items
849 .iter()
850 .map(|v| match v {
851 Value::Null => reddb_types::json::Value::Null,
852 Value::Boolean(b) => reddb_types::json::Value::Bool(*b),
853 Value::Integer(i) => reddb_types::json::Value::Number(*i as f64),
854 Value::Float(f) => reddb_types::json::Value::Number(*f),
855 Value::Text(s) => reddb_types::json::Value::String(s.to_string()),
856 _ => reddb_types::json::Value::Null,
857 })
858 .collect();
859 let json_val = reddb_types::json::Value::Array(json_arr);
860 let bytes = reddb_types::json::to_vec(&json_val).unwrap_or_default();
861 Ok(Value::Json(bytes))
862 }
863 }
864 Token::LBrace => {
865 self.advance()?; let mut map = reddb_types::json::Map::new();
868 if !self.check(&Token::RBrace) {
869 loop {
870 let key = match self.peek().clone() {
877 Token::String(s) => {
878 self.advance()?;
879 s
880 }
881 Token::Ident(s) => {
882 self.advance()?;
883 s
884 }
885 _ => self.expect_ident_or_keyword()?.to_ascii_lowercase(),
886 };
887 if !self.consume(&Token::Colon)? {
889 self.expect(Token::Eq)?;
890 }
891 let val = self.parse_literal_value()?;
893 let json_val = match val {
894 Value::Null => reddb_types::json::Value::Null,
895 Value::Boolean(b) => reddb_types::json::Value::Bool(b),
896 Value::Integer(i) => reddb_types::json::Value::Number(i as f64),
897 Value::Float(f) => reddb_types::json::Value::Number(f),
898 Value::Text(s) => reddb_types::json::Value::String(s.to_string()),
899 Value::Json(ref bytes) => reddb_types::json::from_slice(bytes)
900 .unwrap_or(reddb_types::json::Value::Null),
901 _ => reddb_types::json::Value::Null,
902 };
903 map.insert(key, json_val);
904 if !self.consume(&Token::Comma)? {
905 break;
906 }
907 }
908 }
909 self.expect(Token::RBrace)?;
910 let json_val = reddb_types::json::Value::Object(map);
911 let bytes = reddb_types::json::to_vec(&json_val).unwrap_or_default();
912 Ok(Value::Json(bytes))
913 }
914 ref other => Err(ParseError::expected(
915 vec!["string", "number", "true", "false", "null", "[", "{"],
916 other,
917 self.position(),
918 )),
919 }
920 }
921}
922
923fn returning_expr_start(token: &Token) -> bool {
924 matches!(
925 token,
926 Token::Integer(_)
927 | Token::Float(_)
928 | Token::String(_)
929 | Token::JsonLiteral(_)
930 | Token::Null
931 | Token::True
932 | Token::False
933 | Token::LParen
934 | Token::Minus
935 | Token::Question
936 | Token::Dollar
937 )
938}
939
940fn returning_expr_tail(token: &Token) -> bool {
941 matches!(
942 token,
943 Token::LParen
944 | Token::Plus
945 | Token::Minus
946 | Token::Star
947 | Token::Slash
948 | Token::Percent
949 | Token::DoublePipe
950 | Token::Pipe
951 | Token::Eq
952 | Token::Ne
953 | Token::Lt
954 | Token::Le
955 | Token::Gt
956 | Token::Ge
957 | Token::Dot
958 | Token::Colon
959 )
960}
961
962fn validate_update_order_by(
963 clauses: &[OrderByClause],
964 position: crate::lexer::Position,
965) -> Result<(), ParseError> {
966 for clause in clauses {
967 if clause.expr.is_some() {
968 return Err(ParseError::new(
969 "UPDATE ORDER BY only supports top-level fields",
970 position,
971 ));
972 }
973 match &clause.field {
974 FieldRef::TableColumn { table, column }
975 if table.is_empty() && !column.contains('.') => {}
976 _ => {
977 return Err(ParseError::new(
978 "UPDATE ORDER BY only supports top-level fields",
979 position,
980 ));
981 }
982 }
983 }
984 Ok(())
985}
986
987fn update_order_by_mentions_rid(clauses: &[OrderByClause]) -> bool {
988 clauses.iter().any(|clause| {
989 matches!(
990 &clause.field,
991 FieldRef::TableColumn { table, column }
992 if table.is_empty() && column.eq_ignore_ascii_case("rid")
993 )
994 })
995}
996
997fn returning_expr_not_supported(position: crate::lexer::Position) -> ParseError {
998 ParseError::new(
999 "NOT_YET_SUPPORTED: RETURNING expressions are not supported yet; use RETURNING * or named columns. Track a follow-up issue for RETURNING <expr>.",
1000 position,
1001 )
1002}
1003
1004fn secret_ref_value(store: &str, collection: &str, key: &str) -> Value {
1005 let mut map = reddb_types::json::Map::new();
1006 map.insert(
1007 "type".to_string(),
1008 reddb_types::json::Value::String("secret_ref".to_string()),
1009 );
1010 map.insert(
1011 "store".to_string(),
1012 reddb_types::json::Value::String(store.to_string()),
1013 );
1014 map.insert(
1015 "collection".to_string(),
1016 reddb_types::json::Value::String(collection.to_string()),
1017 );
1018 map.insert(
1019 "key".to_string(),
1020 reddb_types::json::Value::String(key.to_string()),
1021 );
1022 Value::Json(
1023 reddb_types::json::to_vec(&reddb_types::json::Value::Object(map)).unwrap_or_default(),
1024 )
1025}
1026
1027#[cfg(test)]
1028mod tests {
1029 use super::*;
1030 use crate::ast::{InsertEntityType, ReturningItem, UpdateTarget};
1031
1032 fn make_parser(input: &str) -> Parser<'_> {
1033 Parser::new(input).expect("lexer")
1034 }
1035
1036 fn insert(input: &str) -> InsertQuery {
1037 let mut parser = make_parser(input);
1038 let QueryExpr::Insert(query) = parser.parse_insert_query().expect("insert") else {
1039 panic!("expected insert query");
1040 };
1041 query
1042 }
1043
1044 fn update(input: &str) -> UpdateQuery {
1045 let mut parser = make_parser(input);
1046 let QueryExpr::Update(query) = parser.parse_update_query().expect("update") else {
1047 panic!("expected update query");
1048 };
1049 query
1050 }
1051
1052 fn delete(input: &str) -> DeleteQuery {
1053 let mut parser = make_parser(input);
1054 let QueryExpr::Delete(query) = parser.parse_delete_query().expect("delete") else {
1055 panic!("expected delete query");
1056 };
1057 query
1058 }
1059
1060 fn ask(input: &str) -> AskQuery {
1061 let mut parser = make_parser(input);
1062 let QueryExpr::Ask(query) = parser.parse_ask_query().expect("ask") else {
1063 panic!("expected ask query");
1064 };
1065 query
1066 }
1067
1068 #[test]
1069 fn insert_entity_types_with_options_returning_and_suppress_events() {
1070 let cases = [
1071 (
1072 "INSERT INTO items NODE (id) VALUES (1)",
1073 InsertEntityType::Node,
1074 ),
1075 (
1076 "INSERT INTO items EDGE (id) VALUES (1)",
1077 InsertEntityType::Edge,
1078 ),
1079 (
1080 "INSERT INTO items VECTOR (id) VALUES (1)",
1081 InsertEntityType::Vector,
1082 ),
1083 (
1084 "INSERT INTO items DOCUMENT (id) VALUES (1)",
1085 InsertEntityType::Document,
1086 ),
1087 ("INSERT INTO items KV (id) VALUES (1)", InsertEntityType::Kv),
1088 ];
1089 for (input, expected) in cases {
1090 assert_eq!(insert(input).entity_type, expected, "{input}");
1091 }
1092
1093 let query = insert(
1094 "INSERT INTO docs (id, body) VALUES (1, 'red'), (2, ?) \
1095 WITH TTL 2 h WITH EXPIRES AT 999 \
1096 WITH METADATA (source = 'test', score = 3) \
1097 WITH AUTO EMBED (body, title) USING openai MODEL 'text-embedding-3-small' \
1098 RETURNING * SUPPRESS EVENTS",
1099 );
1100 assert_eq!(query.table, "docs");
1101 assert_eq!(query.entity_type, InsertEntityType::Row);
1102 assert_eq!(query.columns, vec!["id", "body"]);
1103 assert_eq!(query.values.len(), 2);
1104 assert_eq!(query.value_exprs.len(), 2);
1105 assert_eq!(query.ttl_ms, Some(7_200_000));
1106 assert_eq!(query.expires_at_ms, Some(999));
1107 assert_eq!(query.with_metadata.len(), 2);
1108 assert_eq!(
1109 query.returning.as_deref(),
1110 Some([ReturningItem::All].as_slice())
1111 );
1112 let auto_embed = query.auto_embed.expect("auto embed");
1113 assert_eq!(auto_embed.fields, vec!["body", "title"]);
1114 assert_eq!(auto_embed.provider, "openai");
1115 assert_eq!(auto_embed.model.as_deref(), Some("text-embedding-3-small"));
1116 assert!(query.suppress_events);
1117 }
1118
1119 #[test]
1120 fn insert_rejects_metric_and_bad_with_clause() {
1121 let mut parser = make_parser("INSERT INTO METRIC cpu.usage (value) VALUES (1)");
1122 let err = parser
1123 .parse_insert_query()
1124 .expect_err("metric insert should fail");
1125 assert!(err.to_string().contains("INSERT INTO METRIC"));
1126
1127 let mut parser = make_parser("INSERT INTO docs (id) VALUES (1) WITH TTL 1 fortnight");
1128 let err = parser
1129 .parse_insert_query()
1130 .expect_err("bad ttl unit should fail");
1131 assert!(err.to_string().contains("unsupported TTL unit"));
1132
1133 let mut parser = make_parser("INSERT INTO docs (id) VALUES (1) WITH UNKNOWN");
1134 let err = parser
1135 .parse_insert_query()
1136 .expect_err("bad WITH should fail");
1137 assert!(err.to_string().contains("expected"));
1138 }
1139
1140 #[test]
1141 fn update_targets_compound_assignments_order_limit_returning() {
1142 let cases = [
1143 ("UPDATE docs KV SET count = 1", UpdateTarget::Kv),
1144 ("UPDATE docs ROWS SET count = 1", UpdateTarget::Rows),
1145 (
1146 "UPDATE docs DOCUMENTS SET count = 1",
1147 UpdateTarget::Documents,
1148 ),
1149 ("UPDATE docs NODES SET count = 1", UpdateTarget::Nodes),
1150 ("UPDATE docs EDGES SET count = 1", UpdateTarget::Edges),
1151 ];
1152 for (input, expected) in cases {
1153 assert_eq!(update(input).target, expected, "{input}");
1154 }
1155
1156 let query = update(
1157 "UPDATE docs DOCUMENTS SET count += 2, title = UPPER(title) \
1158 WHERE id = 1 WITH TTL 30 s WITH METADATA (source = 'update') \
1159 ORDER BY updated_at DESC LIMIT 5 RETURNING weight, title SUPPRESS EVENTS",
1160 );
1161 assert_eq!(query.table, "docs");
1162 assert_eq!(query.target, UpdateTarget::Documents);
1163 assert_eq!(query.assignment_exprs.len(), 2);
1164 assert_eq!(query.compound_assignment_ops, vec![Some(BinOp::Add), None]);
1165 assert_eq!(query.assignments.len(), 0);
1166 assert!(query.filter.is_some());
1167 assert!(query.where_expr.is_some());
1168 assert_eq!(query.ttl_ms, Some(30_000));
1169 assert_eq!(query.with_metadata.len(), 1);
1170 assert_eq!(query.limit, Some(5));
1171 assert_eq!(query.order_by.len(), 2);
1172 assert!(matches!(
1173 &query.order_by[1].field,
1174 FieldRef::TableColumn { column, .. } if column == "rid"
1175 ));
1176 assert_eq!(
1177 query.returning.as_deref(),
1178 Some(
1179 [
1180 ReturningItem::Column("weight".to_string()),
1181 ReturningItem::Column("title".to_string())
1182 ]
1183 .as_slice()
1184 )
1185 );
1186 assert!(query.suppress_events);
1187 }
1188
1189 #[test]
1190 fn update_rejects_invalid_assignment_and_order_by_forms() {
1191 let mut parser = make_parser("UPDATE docs SET count ^= 1");
1192 let err = parser
1193 .parse_update_query()
1194 .expect_err("unknown compound assignment should fail");
1195 assert!(err.to_string().contains("expected"));
1196
1197 let mut parser = make_parser("UPDATE docs SET count = 1 ORDER BY updated_at");
1198 let err = parser
1199 .parse_update_query()
1200 .expect_err("ORDER BY without LIMIT should fail");
1201 assert!(err.to_string().contains("requires LIMIT"));
1202
1203 let mut parser = make_parser("UPDATE docs SET count = 1 ORDER BY updated_at + 1 LIMIT 1");
1204 let err = parser
1205 .parse_update_query()
1206 .expect_err("ORDER BY expression should fail");
1207 assert!(err.to_string().contains("top-level fields"));
1208 }
1209
1210 #[test]
1211 fn delete_returning_and_suppress_events() {
1212 let query = delete("DELETE FROM docs WHERE id = 1 RETURNING id, title SUPPRESS EVENTS");
1213 assert_eq!(query.table, "docs");
1214 assert!(query.filter.is_some());
1215 assert!(query.where_expr.is_some());
1216 assert_eq!(
1217 query.returning.as_deref(),
1218 Some(
1219 [
1220 ReturningItem::Column("id".to_string()),
1221 ReturningItem::Column("title".to_string())
1222 ]
1223 .as_slice()
1224 )
1225 );
1226 assert!(query.suppress_events);
1227
1228 let query = delete("DELETE FROM docs RETURNING *");
1229 assert_eq!(
1230 query.returning.as_deref(),
1231 Some([ReturningItem::All].as_slice())
1232 );
1233 }
1234
1235 #[test]
1236 fn returning_rejects_expression_forms() {
1237 for input in [
1238 "DELETE FROM docs RETURNING 1",
1239 "DELETE FROM docs RETURNING UPPER(title)",
1240 "DELETE FROM docs RETURNING title || body",
1241 ] {
1242 let mut parser = make_parser(input);
1243 let err = parser
1244 .parse_delete_query()
1245 .expect_err("RETURNING expression should fail");
1246 assert!(err.to_string().contains("RETURNING expressions"));
1247 }
1248 }
1249
1250 #[test]
1251 fn ask_parses_all_optional_clauses_and_cache_modes() {
1252 let query = ask(
1253 "ASK 'what changed?' USING 'openai' MODEL 'gpt' DEPTH 3 LIMIT 4 \
1254 MIN_SCORE 0.7 COLLECTION docs TEMPERATURE 0.2 SEED 42 STRICT OFF \
1255 STREAM CACHE TTL '10m'",
1256 );
1257 assert_eq!(query.question, "what changed?");
1258 assert_eq!(query.provider.as_deref(), Some("openai"));
1259 assert_eq!(query.model.as_deref(), Some("gpt"));
1260 assert_eq!(query.depth, Some(3));
1261 assert_eq!(query.limit, Some(4));
1262 assert_eq!(query.min_score, Some(0.7));
1263 assert_eq!(query.collection.as_deref(), Some("docs"));
1264 assert_eq!(query.temperature, Some(0.2));
1265 assert_eq!(query.seed, Some(42));
1266 assert!(!query.strict);
1267 assert!(query.stream);
1268 assert_eq!(query.cache, AskCacheClause::CacheTtl("10m".to_string()));
1269
1270 let query = ask("ASK ? NOCACHE");
1271 assert_eq!(query.question, "");
1272 assert_eq!(query.question_param, Some(0));
1273 assert_eq!(query.cache, AskCacheClause::NoCache);
1274 }
1275
1276 #[test]
1277 fn explain_ask_and_ask_error_paths() {
1278 let mut parser = make_parser("EXPLAIN ASK $2 STRICT ON");
1279 let QueryExpr::Ask(query) = parser.parse_explain_ask_query().expect("explain ask") else {
1280 panic!("expected ask query");
1281 };
1282 assert!(query.explain);
1283 assert_eq!(query.question_param, Some(1));
1284 assert!(query.strict);
1285
1286 let mut parser = make_parser("EXPLAIN SELECT 1");
1287 let err = parser
1288 .parse_explain_ask_query()
1289 .expect_err("missing ASK should fail");
1290 assert!(err.to_string().contains("expected"));
1291
1292 let mut parser = make_parser("ASK 'q' STRICT MAYBE");
1293 let err = parser
1294 .parse_ask_query()
1295 .expect_err("bad strict should fail");
1296 assert!(err.to_string().contains("Expected ON or OFF"));
1297
1298 let mut parser = make_parser("ASK 'q' CACHE TTL '10m' NOCACHE");
1299 let err = parser
1300 .parse_ask_query()
1301 .expect_err("duplicate cache should fail");
1302 assert!(err.to_string().contains("cache clause"));
1303
1304 let mut parser = make_parser("ASK 'q' CACHE FOREVER '10m'");
1305 let err = parser
1306 .parse_ask_query()
1307 .expect_err("bad cache ttl keyword should fail");
1308 assert!(err.to_string().contains("Expected TTL"));
1309 }
1310
1311 #[test]
1312 fn literal_value_special_constructors_arrays_and_objects() {
1313 let mut parser = make_parser("PASSWORD('pw')");
1314 assert!(matches!(
1315 parser.parse_literal_value().expect("password"),
1316 Value::Password(secret) if secret == "@@plain@@pw"
1317 ));
1318
1319 let mut parser = make_parser("SECRET('pw')");
1320 assert!(matches!(
1321 parser.parse_literal_value().expect("secret"),
1322 Value::Secret(bytes) if bytes == b"@@plain@@pw"
1323 ));
1324
1325 let mut parser = make_parser("SECRET_REF(vault, red.vault.api_key)");
1326 let value = parser.parse_literal_value().expect("secret ref");
1327 assert!(matches!(value, Value::Json(_)));
1328
1329 let mut parser = make_parser("[1, 2.5]");
1330 assert!(matches!(
1331 parser.parse_literal_value().expect("vector"),
1332 Value::Vector(values) if values == vec![1.0, 2.5]
1333 ));
1334
1335 let mut parser = make_parser("['a', 2]");
1336 assert!(matches!(
1337 parser.parse_literal_value().expect("json array"),
1338 Value::Json(_)
1339 ));
1340
1341 let mut parser = make_parser("{level = 'info', count: 2}");
1342 assert!(matches!(
1343 parser.parse_literal_value().expect("json object"),
1344 Value::Json(_)
1345 ));
1346 }
1347
1348 #[test]
1349 fn literal_value_rejects_invalid_secret_ref_and_scalar_start() {
1350 let mut parser = make_parser("SECRET_REF(config, red.vault.api_key)");
1351 let err = parser
1352 .parse_literal_value()
1353 .expect_err("non-vault secret ref should fail");
1354 assert!(err.to_string().contains("expected"));
1355
1356 let mut parser = make_parser("ORDER");
1357 let err = parser
1358 .parse_literal_value()
1359 .expect_err("non literal should fail");
1360 assert!(err.to_string().contains("expected"));
1361 }
1362
1363 #[test]
1364 fn json_depth_check_rejects_deep_literals() {
1365 let mut deep = reddb_types::utils::json::JsonValue::Array(vec![]);
1366 for _ in 0..JSON_LITERAL_MAX_DEPTH {
1367 deep = reddb_types::utils::json::JsonValue::Array(vec![deep]);
1368 }
1369 let err = json_literal_depth_check(&deep).expect_err("depth should fail");
1370 assert!(err.contains("JSON_LITERAL_MAX_DEPTH"));
1371 }
1372}