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) use crate::limits::JSON_LITERAL_MAX_DEPTH;
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 let columns = if matches!(entity_type, InsertEntityType::Document) {
107 self.parse_document_insert_columns()?
108 } else if matches!(entity_type, InsertEntityType::Row) && self.check(&Token::Values) {
109 Vec::new()
116 } else {
117 self.expect(Token::LParen)?;
118 let columns = self.parse_ident_list()?;
119 self.expect(Token::RParen)?;
120 columns
121 };
122
123 self.expect(Token::Values)?;
125 let mut all_values = Vec::new();
126 let mut all_value_exprs = Vec::new();
127 loop {
128 self.expect(Token::LParen)?;
129 let row_exprs = self.parse_dml_expr_list()?;
130 self.expect(Token::RParen)?;
131 let row_values = row_exprs
140 .iter()
141 .map(|expr| match fold_expr_to_value(expr.clone()) {
142 Ok(value) => Ok(value),
143 Err(msg) => {
144 if crate::sql_lowering::expr_contains_parameter(expr) {
145 Ok(Value::Null)
146 } else {
147 Err(msg)
148 }
149 }
150 })
151 .collect::<Result<Vec<_>, _>>()
152 .map_err(|msg| ParseError::new(msg, self.position()))?;
153 all_value_exprs.push(row_exprs);
154 all_values.push(row_values);
155 if !self.consume(&Token::Comma)? {
156 break;
157 }
158 }
159
160 if matches!(entity_type, InsertEntityType::Document) {
165 self.reject_document_string_body(&all_value_exprs)?;
166 }
167
168 let (ttl_ms, expires_at_ms, with_metadata, auto_embed) = self.parse_with_clauses()?;
170
171 let returning = self.parse_returning_clause()?;
172
173 let suppress_events = if self.consume_ident_ci("SUPPRESS")? {
174 self.expect_ident_ci("EVENTS")?;
175 true
176 } else {
177 false
178 };
179
180 Ok(QueryExpr::Insert(InsertQuery {
181 table,
182 entity_type,
183 columns,
184 value_exprs: all_value_exprs,
185 values: all_values,
186 returning,
187 ttl_ms,
188 expires_at_ms,
189 with_metadata,
190 auto_embed,
191 suppress_events,
192 }))
193 }
194
195 fn parse_document_insert_columns(&mut self) -> Result<Vec<String>, ParseError> {
203 if !self.check(&Token::LParen) {
204 return Ok(vec!["body".to_string()]);
205 }
206 self.expect(Token::LParen)?;
207 let columns = self.parse_ident_list()?;
208 self.expect(Token::RParen)?;
209 if columns.iter().any(|column| is_legacy_ttl_column(column)) {
210 return Err(ParseError::document_insert_ttl_column(self.position()));
211 }
212 Err(ParseError::document_insert_column_list(self.position()))
213 }
214
215 fn reject_document_string_body(&self, value_exprs: &[Vec<Expr>]) -> Result<(), ParseError> {
221 for row in value_exprs {
222 for expr in row {
223 if let Expr::Literal {
224 value: Value::Text(_),
225 ..
226 } = expr
227 {
228 return Err(ParseError::document_insert_quoted_body(self.position()));
229 }
230 }
231 }
232 Ok(())
233 }
234
235 fn parse_ttl_duration(&mut self) -> Result<u64, ParseError> {
237 let ttl_value = self.parse_float()?;
239 let ttl_unit = match self.peek() {
240 Token::Ident(unit) => {
241 let unit = unit.clone();
242 self.advance()?;
243 unit
244 }
245 _ => "s".to_string(),
246 };
247
248 let multiplier_ms = match ttl_unit.to_ascii_lowercase().as_str() {
249 "ms" | "msec" | "millisecond" | "milliseconds" => 1.0,
250 "s" | "sec" | "secs" | "second" | "seconds" => 1_000.0,
251 "m" | "min" | "mins" | "minute" | "minutes" => 60_000.0,
252 "h" | "hr" | "hrs" | "hour" | "hours" => 3_600_000.0,
253 "d" | "day" | "days" => 86_400_000.0,
254 other => {
255 return Err(ParseError::new(
256 format!(
260 "unsupported TTL unit {other:?}; supported units: ms, s, m, h, d (e.g. `WITH TTL 30 m`)"
261 ),
262 self.position(),
263 ));
264 }
265 };
266
267 Ok((ttl_value * multiplier_ms) as u64)
268 }
269
270 pub fn parse_with_clauses(
273 &mut self,
274 ) -> Result<
275 (
276 Option<u64>,
277 Option<u64>,
278 Vec<(String, Value)>,
279 Option<crate::ast::AutoEmbedConfig>,
280 ),
281 ParseError,
282 > {
283 let mut ttl_ms = None;
284 let mut expires_at_ms = None;
285 let mut with_metadata = Vec::new();
286 let mut auto_embed = None;
287
288 while self.consume(&Token::With)? {
289 if self.consume_ident_ci("TTL")? {
290 ttl_ms = Some(self.parse_ttl_duration()?);
291 } else if self.consume_ident_ci("EXPIRES")? {
292 self.expect_ident_ci("AT")?;
293 let ts = self.parse_expires_at_value()?;
294 expires_at_ms = Some(ts);
295 } else if self.consume(&Token::Metadata)? || self.consume_ident_ci("METADATA")? {
296 with_metadata = self.parse_with_metadata_pairs()?;
297 } else if self.consume_ident_ci("AUTO")? {
298 self.consume_ident_ci("EMBED")?;
300 self.expect(Token::LParen)?;
301 let mut fields = Vec::new();
302 loop {
303 fields.push(self.expect_ident()?);
304 if !self.consume(&Token::Comma)? {
305 break;
306 }
307 }
308 self.expect(Token::RParen)?;
309 let provider = if self.consume(&Token::Using)? {
314 self.expect_ident()?
315 } else {
316 "openai".to_string()
317 };
318 let model = if self.consume_ident_ci("MODEL")? {
319 Some(self.parse_string()?)
320 } else {
321 None
322 };
323 auto_embed = Some(crate::ast::AutoEmbedConfig {
324 fields,
325 provider,
326 model,
327 });
328 } else {
329 return Err(ParseError::expected(
330 vec!["TTL", "EXPIRES AT", "METADATA", "AUTO EMBED"],
331 self.peek(),
332 self.position(),
333 ));
334 }
335 }
336
337 Ok((ttl_ms, expires_at_ms, with_metadata, auto_embed))
338 }
339
340 fn expect_ident_ci(&mut self, expected: &str) -> Result<(), ParseError> {
342 if self.consume_ident_ci(expected)? {
343 Ok(())
344 } else {
345 Err(ParseError::expected(
346 vec![expected],
347 self.peek(),
348 self.position(),
349 ))
350 }
351 }
352
353 fn parse_expires_at_value(&mut self) -> Result<u64, ParseError> {
355 if let Ok(value) = self.parse_integer() {
357 return Ok(value as u64);
358 }
359 if let Ok(text) = self.parse_string() {
361 let trimmed = text.trim();
363 if let Ok(ts) = trimmed.parse::<u64>() {
364 return Ok(ts);
365 }
366 return Err(ParseError::new(
368 format!("EXPIRES AT requires a unix timestamp in milliseconds, got {trimmed:?}"),
372 self.position(),
373 ));
374 }
375 Err(ParseError::expected(
376 vec!["timestamp (unix ms) or 'YYYY-MM-DD'"],
377 self.peek(),
378 self.position(),
379 ))
380 }
381
382 fn parse_with_metadata_pairs(&mut self) -> Result<Vec<(String, Value)>, ParseError> {
384 self.expect(Token::LParen)?;
385 let mut pairs = Vec::new();
386 if !self.check(&Token::RParen) {
387 loop {
388 let key = self.expect_ident_or_keyword()?.to_ascii_lowercase();
389 self.expect(Token::Eq)?;
390 let value = self.parse_literal_value()?;
391 pairs.push((key, value));
392 if !self.consume(&Token::Comma)? {
393 break;
394 }
395 }
396 }
397 self.expect(Token::RParen)?;
398 Ok(pairs)
399 }
400
401 pub fn parse_update_query(&mut self) -> Result<QueryExpr, ParseError> {
403 self.expect(Token::Update)?;
404 let table = self.expect_ident()?;
405 let target = self.parse_update_target()?;
406 self.expect(Token::Set)?;
407
408 let mut assignments = Vec::new();
409 let mut assignment_exprs = Vec::new();
410 let mut compound_assignment_ops = Vec::new();
411 loop {
412 let col = self.parse_update_assignment_target()?;
413 let compound_op = if self.consume(&Token::Eq)? {
414 None
415 } else {
416 let op = match self.peek() {
417 Token::Plus => BinOp::Add,
418 Token::Dash | Token::Minus => BinOp::Sub,
419 Token::Star => BinOp::Mul,
420 Token::Slash => BinOp::Div,
421 Token::Percent => BinOp::Mod,
422 _ => {
423 return Err(ParseError::expected(
424 vec!["=", "+=", "-=", "*=", "/=", "%="],
425 self.peek(),
426 self.position(),
427 ));
428 }
429 };
430 self.advance()?;
431 self.expect(Token::Eq)?;
432 Some(op)
433 };
434 let expr = self.parse_expr()?;
435 let folded = fold_expr_to_value(expr.clone()).ok();
436 assignment_exprs.push((col.clone(), expr));
437 compound_assignment_ops.push(compound_op);
438 if compound_op.is_none() {
439 if let Some(val) = folded {
440 assignments.push((col.clone(), val));
441 }
442 }
443 if !self.consume(&Token::Comma)? {
444 break;
445 }
446 }
447
448 let filter = if self.consume(&Token::Where)? {
449 Some(self.parse_filter()?)
450 } else {
451 None
452 };
453 let where_expr = filter.as_ref().map(filter_to_expr);
454
455 let (ttl_ms, expires_at_ms, with_metadata, _auto_embed) = self.parse_with_clauses()?;
456
457 let (claim_limit, claim_exact) = if self.consume_ident_ci("CLAIM")? {
458 if self.consume_ident_ci("EXACT")? {
459 (Some(self.parse_integer()? as u64), true)
460 } else {
461 self.expect(Token::Limit)?;
462 (Some(self.parse_integer()? as u64), false)
463 }
464 } else {
465 (None, false)
466 };
467
468 let mut order_by = if self.consume(&Token::Order)? {
469 self.expect(Token::By)?;
470 let clauses = self.parse_order_by_list()?;
471 validate_update_order_by(&clauses, self.position())?;
472 clauses
473 } else {
474 Vec::new()
475 };
476
477 let limit = if self.consume(&Token::Limit)? {
482 Some(self.parse_integer()? as u64)
483 } else {
484 None
485 };
486 let effective_limit = limit.is_some() || claim_limit.is_some();
489 if !order_by.is_empty() && !effective_limit {
490 return Err(ParseError::new(
491 "UPDATE ORDER BY requires LIMIT",
492 self.position(),
493 ));
494 }
495 if claim_limit.is_some() && order_by.is_empty() {
496 return Err(ParseError::new(
497 "UPDATE CLAIM requires ORDER BY",
498 self.position(),
499 ));
500 }
501 if !order_by.is_empty() && !update_order_by_mentions_rid(&order_by) {
502 order_by.push(OrderByClause {
503 field: FieldRef::TableColumn {
504 table: String::new(),
505 column: "rid".to_string(),
506 },
507 expr: None,
508 ascending: true,
509 nulls_first: false,
510 });
511 }
512
513 let returning = self.parse_returning_clause()?;
514
515 let suppress_events = if self.consume_ident_ci("SUPPRESS")? {
516 self.expect_ident_ci("EVENTS")?;
517 true
518 } else {
519 false
520 };
521
522 Ok(QueryExpr::Update(UpdateQuery {
523 table,
524 target,
525 assignment_exprs,
526 compound_assignment_ops,
527 assignments,
528 where_expr,
529 filter,
530 ttl_ms,
531 expires_at_ms,
532 with_metadata,
533 returning,
534 claim_limit,
535 claim_exact,
536 order_by,
537 limit,
538 suppress_events,
539 }))
540 }
541
542 fn parse_update_assignment_target(&mut self) -> Result<String, ParseError> {
543 let mut segments = vec![self.expect_column_ident()?];
549 while self.consume(&Token::Dot)? {
550 segments.push(self.expect_column_ident()?);
551 }
552 Ok(segments.join("."))
553 }
554
555 fn parse_update_target(&mut self) -> Result<UpdateTarget, ParseError> {
556 if self.check(&Token::Kv) {
563 return Err(self.removed_update_marker_error("KV"));
564 }
565 if self.check(&Token::Rows) {
566 return Err(self.removed_update_marker_error("ROWS"));
567 }
568 if matches!(self.peek(), Token::Ident(name) if name.eq_ignore_ascii_case("DOCUMENTS")) {
569 return Err(self.removed_update_marker_error("DOCUMENTS"));
570 }
571 if self.consume_ident_ci("NODES")? {
572 return Ok(UpdateTarget::Nodes);
573 }
574 if self.consume_ident_ci("EDGES")? {
575 return Ok(UpdateTarget::Edges);
576 }
577 Ok(UpdateTarget::Rows)
578 }
579
580 fn removed_update_marker_error(&self, marker: &str) -> ParseError {
584 ParseError::new(
585 format!(
586 "the `{marker}` UPDATE model marker has been removed; the catalog \
587 already knows the collection's model — write `UPDATE <collection> \
588 SET …` with no marker. (Graph updates still name `NODES` or `EDGES`, \
589 since a graph collection holds both record kinds.)"
590 ),
591 self.position(),
592 )
593 }
594
595 pub fn parse_delete_query(&mut self) -> Result<QueryExpr, ParseError> {
597 self.expect(Token::Delete)?;
598 self.expect(Token::From)?;
599 let table = self.expect_ident()?;
600
601 let filter = if self.consume(&Token::Where)? {
602 Some(self.parse_filter()?)
603 } else {
604 None
605 };
606
607 let where_expr = filter.as_ref().map(filter_to_expr);
608
609 let returning = self.parse_returning_clause()?;
610
611 let suppress_events = if self.consume_ident_ci("SUPPRESS")? {
612 self.expect_ident_ci("EVENTS")?;
613 true
614 } else {
615 false
616 };
617
618 Ok(QueryExpr::Delete(DeleteQuery {
619 table,
620 where_expr,
621 filter,
622 returning,
623 suppress_events,
624 }))
625 }
626
627 fn parse_returning_clause(&mut self) -> Result<Option<Vec<ReturningItem>>, ParseError> {
631 if !self.consume(&Token::Returning)? {
632 return Ok(None);
633 }
634 if self.consume(&Token::Star)? {
635 return Ok(Some(vec![ReturningItem::All]));
636 }
637 let mut items = Vec::new();
638 loop {
639 if returning_expr_start(self.peek()) {
640 return Err(returning_expr_not_supported(self.position()));
641 }
642 let col = self.expect_update_returning_column()?;
643 items.push(ReturningItem::Column(col));
644 if returning_expr_tail(self.peek()) {
645 return Err(returning_expr_not_supported(self.position()));
646 }
647 if !self.consume(&Token::Comma)? {
648 break;
649 }
650 }
651 if items.is_empty() {
652 return Err(ParseError::expected(
653 vec!["*", "column name"],
654 self.peek(),
655 self.position(),
656 ));
657 }
658 Ok(Some(items))
659 }
660
661 fn expect_update_returning_column(&mut self) -> Result<String, ParseError> {
662 if self.consume(&Token::Weight)? {
663 return Ok("weight".to_string());
664 }
665 self.expect_ident_or_keyword()
666 }
667
668 pub fn parse_ask_query(&mut self) -> Result<QueryExpr, ParseError> {
671 self.parse_ask_query_with_explain(false)
672 }
673
674 pub fn parse_explain_ask_query(&mut self) -> Result<QueryExpr, ParseError> {
676 self.advance()?; if !matches!(self.peek(), Token::Ident(name) if name.eq_ignore_ascii_case("ASK")) {
678 return Err(ParseError::expected(
679 vec!["ASK"],
680 self.peek(),
681 self.position(),
682 ));
683 }
684 self.parse_ask_query_with_explain(true)
685 }
686
687 fn parse_ask_query_with_explain(&mut self, explain: bool) -> Result<QueryExpr, ParseError> {
688 self.advance()?; let (question, question_param) = match self.peek() {
691 Token::String(_) => (self.parse_string()?, None),
692 Token::Dollar | Token::Question => {
693 let index = self.parse_param_slot("ASK question")?;
694 (String::new(), Some(index))
695 }
696 other => {
697 return Err(ParseError::expected(
698 vec!["string", "$N", "?"],
699 other,
700 self.position(),
701 ));
702 }
703 };
704
705 let mut provider = None;
706 let mut model = None;
707 let mut depth = None;
708 let mut limit = None;
709 let mut min_score = None;
710 let mut collection = None;
711 let mut temperature = None;
712 let mut seed = None;
713 let mut strict = true;
714 let mut stream = false;
715 let mut cache = AskCacheClause::Default;
716 let mut as_rql = false;
717 let mut execute = false;
718
719 for _ in 0..14 {
722 if self.consume(&Token::Using)? {
723 provider = Some(match &self.current.token {
724 Token::String(_) => self.parse_string()?,
725 _ => self.expect_ident()?,
726 });
727 } else if self.consume_ident_ci("MODEL")? {
728 model = Some(self.parse_string()?);
729 } else if self.consume(&Token::Depth)? {
730 depth = Some(self.parse_integer()? as usize);
731 } else if self.consume(&Token::Limit)? {
732 limit = Some(self.parse_integer()? as usize);
733 } else if self.consume(&Token::MinScore)? {
734 min_score = Some(self.parse_float()? as f32);
735 } else if self.consume(&Token::Collection)? {
736 collection = Some(self.expect_ident()?);
737 } else if self.consume_ident_ci("TEMPERATURE")? {
738 temperature = Some(self.parse_float()? as f32);
739 } else if self.consume_ident_ci("SEED")? {
740 seed = Some(self.parse_integer()? as u64);
741 } else if self.consume_ident_ci("STRICT")? {
742 let value = self.expect_ident_or_keyword()?;
743 if value.eq_ignore_ascii_case("ON") {
744 strict = true;
745 } else if value.eq_ignore_ascii_case("OFF") {
746 strict = false;
747 } else {
748 return Err(ParseError::new(
749 "Expected ON or OFF after STRICT",
750 self.position(),
751 ));
752 }
753 } else if self.consume_ident_ci("STREAM")? {
754 stream = true;
755 } else if self.consume_ident_ci("CACHE")? {
756 if !matches!(cache, AskCacheClause::Default) {
757 return Err(ParseError::new(
758 "ASK cache clause specified more than once",
759 self.position(),
760 ));
761 }
762 let ttl = self.expect_ident_or_keyword()?;
763 if !ttl.eq_ignore_ascii_case("TTL") {
764 return Err(ParseError::new("Expected TTL after CACHE", self.position()));
765 }
766 cache = AskCacheClause::CacheTtl(self.parse_string()?);
767 } else if self.consume_ident_ci("NOCACHE")? {
768 if !matches!(cache, AskCacheClause::Default) {
769 return Err(ParseError::new(
770 "ASK cache clause specified more than once",
771 self.position(),
772 ));
773 }
774 cache = AskCacheClause::NoCache;
775 } else if self.consume(&Token::As)? {
776 if as_rql {
777 return Err(ParseError::new(
778 "ASK AS RQL specified more than once",
779 self.position(),
780 ));
781 }
782 let output = self.expect_ident_or_keyword()?;
783 if !output.eq_ignore_ascii_case("RQL") {
784 return Err(ParseError::new(
785 "Expected RQL after ASK AS",
786 self.position(),
787 ));
788 }
789 as_rql = true;
790 } else if self.consume_ident_ci("EXECUTE")? {
791 if execute {
792 return Err(ParseError::new(
793 "ASK EXECUTE specified more than once",
794 self.position(),
795 ));
796 }
797 execute = true;
798 } else {
799 break;
800 }
801 }
802
803 Ok(QueryExpr::Ask(AskQuery {
804 explain,
805 question,
806 question_param,
807 provider,
808 model,
809 depth,
810 limit,
811 min_score,
812 collection,
813 temperature,
814 seed,
815 strict,
816 stream,
817 cache,
818 as_rql,
819 execute,
820 }))
821 }
822
823 fn parse_ident_list(&mut self) -> Result<Vec<String>, ParseError> {
825 let mut idents = Vec::new();
826 loop {
827 idents.push(self.expect_ident_or_keyword()?);
828 if !self.consume(&Token::Comma)? {
829 break;
830 }
831 }
832 Ok(idents)
833 }
834
835 fn parse_dml_value_list(&mut self) -> Result<Vec<Value>, ParseError> {
837 self.parse_dml_expr_list()?
838 .into_iter()
839 .map(fold_expr_to_value)
840 .collect::<Result<Vec<_>, _>>()
841 .map_err(|msg| ParseError::new(msg, self.position()))
842 }
843
844 fn parse_dml_expr_list(&mut self) -> Result<Vec<Expr>, ParseError> {
845 let mut values = Vec::new();
846 loop {
847 values.push(self.parse_expr()?);
848 if !self.consume(&Token::Comma)? {
849 break;
850 }
851 }
852 Ok(values)
853 }
854
855 pub(crate) fn parse_literal_value(&mut self) -> Result<Value, ParseError> {
857 self.enter_depth()?;
866 let result = self.parse_literal_value_inner();
867 self.exit_depth();
868 result
869 }
870
871 fn parse_literal_value_inner(&mut self) -> Result<Value, ParseError> {
872 if let Token::Ident(name) = self.peek().clone() {
879 let upper = name.to_uppercase();
880 if upper == "PASSWORD" || upper == "SECRET" {
881 self.advance()?; self.expect(Token::LParen)?;
883 let plaintext = self.parse_string()?;
884 self.expect(Token::RParen)?;
885 return Ok(match upper.as_str() {
886 "PASSWORD" => Value::Password(format!("@@plain@@{plaintext}")),
887 "SECRET" => Value::Secret(format!("@@plain@@{plaintext}").into_bytes()),
888 _ => unreachable!(),
889 });
890 }
891 if upper == "SECRET_REF" {
892 self.advance()?; self.expect(Token::LParen)?;
894 let store = self.expect_ident_or_keyword()?.to_ascii_lowercase();
895 if store != "vault" {
896 return Err(ParseError::expected(
897 vec!["vault"],
898 self.peek(),
899 self.position(),
900 ));
901 }
902 self.expect(Token::Comma)?;
903 let (collection, key) =
904 self.parse_kv_key(reddb_types::catalog::CollectionModel::Vault)?;
905 self.expect(Token::RParen)?;
906 return Ok(secret_ref_value(&store, &collection, &key));
907 }
908 }
909
910 match self.peek().clone() {
911 Token::String(s) => {
912 let s = s.clone();
913 self.advance()?;
914 Ok(Value::text(s))
915 }
916 Token::JsonLiteral(raw) => {
917 self.advance()?;
922 let json_value = reddb_types::utils::json::parse_json(&raw).map_err(|err| {
923 ParseError::new(
924 format!("invalid JSON object literal: {:?}", err.to_string()),
930 self.position(),
931 )
932 })?;
933 json_literal_depth_check(&json_value)
934 .map_err(|err| ParseError::new(err, self.position()))?;
935 let canonical = reddb_types::serde_json::Value::from(json_value);
936 let bytes = reddb_types::json::to_vec(&canonical).map_err(|err| {
937 ParseError::new(
938 format!("failed to encode JSON literal: {:?}", err.to_string()),
942 self.position(),
943 )
944 })?;
945 Ok(Value::Json(bytes))
946 }
947 Token::Integer(n) => {
948 self.advance()?;
949 Ok(Value::Integer(n))
950 }
951 Token::Float(n) => {
952 self.advance()?;
953 Ok(Value::Float(n))
954 }
955 Token::True => {
956 self.advance()?;
957 Ok(Value::Boolean(true))
958 }
959 Token::False => {
960 self.advance()?;
961 Ok(Value::Boolean(false))
962 }
963 Token::Null => {
964 self.advance()?;
965 Ok(Value::Null)
966 }
967 Token::LBracket => {
968 self.advance()?; let mut items = Vec::new();
980 if !self.check(&Token::RBracket) {
981 loop {
982 items.push(self.parse_literal_value()?);
983 if !self.consume(&Token::Comma)? {
984 break;
985 }
986 }
987 }
988 self.expect(Token::RBracket)?;
989 Ok(Value::Array(items))
990 }
991 Token::LBrace => {
992 self.advance()?; let mut map = reddb_types::json::Map::new();
995 if !self.check(&Token::RBrace) {
996 loop {
997 let key = match self.peek().clone() {
1004 Token::String(s) => {
1005 self.advance()?;
1006 s
1007 }
1008 Token::Ident(s) => {
1009 self.advance()?;
1010 s
1011 }
1012 _ => self.expect_ident_or_keyword()?.to_ascii_lowercase(),
1013 };
1014 if !self.consume(&Token::Colon)? {
1016 self.expect(Token::Eq)?;
1017 }
1018 let val = self.parse_literal_value()?;
1020 map.insert(key, literal_value_to_json(&val));
1021 if !self.consume(&Token::Comma)? {
1022 break;
1023 }
1024 }
1025 }
1026 self.expect(Token::RBrace)?;
1027 let json_val = reddb_types::json::Value::Object(map);
1028 let bytes = reddb_types::json::to_vec(&json_val).unwrap_or_default();
1029 Ok(Value::Json(bytes))
1030 }
1031 ref other => Err(ParseError::expected(
1032 vec!["string", "number", "true", "false", "null", "[", "{"],
1033 other,
1034 self.position(),
1035 )),
1036 }
1037 }
1038}
1039
1040fn is_legacy_ttl_column(column: &str) -> bool {
1045 column.eq_ignore_ascii_case("_ttl")
1046 || column.eq_ignore_ascii_case("_ttl_ms")
1047 || column.eq_ignore_ascii_case("_expires_at")
1048}
1049
1050fn literal_value_to_json(val: &Value) -> reddb_types::json::Value {
1058 match val {
1059 Value::Null => reddb_types::json::Value::Null,
1060 Value::Boolean(b) => reddb_types::json::Value::Bool(*b),
1061 Value::Integer(i) => reddb_types::json::Value::Number(*i as f64),
1062 Value::Float(f) => reddb_types::json::Value::Number(*f),
1063 Value::Text(s) => reddb_types::json::Value::String(s.to_string()),
1064 Value::Json(bytes) => {
1065 reddb_types::json::from_slice(bytes).unwrap_or(reddb_types::json::Value::Null)
1066 }
1067 Value::Array(items) => {
1068 reddb_types::json::Value::Array(items.iter().map(literal_value_to_json).collect())
1069 }
1070 _ => reddb_types::json::Value::Null,
1071 }
1072}
1073
1074fn returning_expr_start(token: &Token) -> bool {
1075 matches!(
1076 token,
1077 Token::Integer(_)
1078 | Token::Float(_)
1079 | Token::String(_)
1080 | Token::JsonLiteral(_)
1081 | Token::Null
1082 | Token::True
1083 | Token::False
1084 | Token::LParen
1085 | Token::Minus
1086 | Token::Question
1087 | Token::Dollar
1088 )
1089}
1090
1091fn returning_expr_tail(token: &Token) -> bool {
1092 matches!(
1093 token,
1094 Token::LParen
1095 | Token::Plus
1096 | Token::Minus
1097 | Token::Star
1098 | Token::Slash
1099 | Token::Percent
1100 | Token::DoublePipe
1101 | Token::Pipe
1102 | Token::Eq
1103 | Token::Ne
1104 | Token::Lt
1105 | Token::Le
1106 | Token::Gt
1107 | Token::Ge
1108 | Token::Dot
1109 | Token::Colon
1110 )
1111}
1112
1113fn validate_update_order_by(
1114 clauses: &[OrderByClause],
1115 position: crate::lexer::Position,
1116) -> Result<(), ParseError> {
1117 for clause in clauses {
1118 if clause.expr.is_some() {
1119 return Err(ParseError::new(
1120 "UPDATE ORDER BY only supports top-level fields",
1121 position,
1122 ));
1123 }
1124 match &clause.field {
1125 FieldRef::TableColumn { table, column }
1126 if table.is_empty() && !column.contains('.') => {}
1127 _ => {
1128 return Err(ParseError::new(
1129 "UPDATE ORDER BY only supports top-level fields",
1130 position,
1131 ));
1132 }
1133 }
1134 }
1135 Ok(())
1136}
1137
1138fn update_order_by_mentions_rid(clauses: &[OrderByClause]) -> bool {
1139 clauses.iter().any(|clause| {
1140 matches!(
1141 &clause.field,
1142 FieldRef::TableColumn { table, column }
1143 if table.is_empty() && column.eq_ignore_ascii_case("rid")
1144 )
1145 })
1146}
1147
1148fn returning_expr_not_supported(position: crate::lexer::Position) -> ParseError {
1149 ParseError::new(
1150 "NOT_YET_SUPPORTED: RETURNING expressions are not supported yet; use RETURNING * or named columns. Track a follow-up issue for RETURNING <expr>.",
1151 position,
1152 )
1153}
1154
1155fn secret_ref_value(store: &str, collection: &str, key: &str) -> Value {
1156 let mut map = reddb_types::json::Map::new();
1157 map.insert(
1158 "type".to_string(),
1159 reddb_types::json::Value::String("secret_ref".to_string()),
1160 );
1161 map.insert(
1162 "store".to_string(),
1163 reddb_types::json::Value::String(store.to_string()),
1164 );
1165 map.insert(
1166 "collection".to_string(),
1167 reddb_types::json::Value::String(collection.to_string()),
1168 );
1169 map.insert(
1170 "key".to_string(),
1171 reddb_types::json::Value::String(key.to_string()),
1172 );
1173 Value::Json(
1174 reddb_types::json::to_vec(&reddb_types::json::Value::Object(map)).unwrap_or_default(),
1175 )
1176}
1177
1178#[cfg(test)]
1179mod tests {
1180 use super::*;
1181 use crate::ast::{InsertEntityType, ReturningItem, UpdateTarget};
1182
1183 fn make_parser(input: &str) -> Parser<'_> {
1184 Parser::new(input).expect("lexer")
1185 }
1186
1187 fn insert(input: &str) -> InsertQuery {
1188 let mut parser = make_parser(input);
1189 let QueryExpr::Insert(query) = parser.parse_insert_query().expect("insert") else {
1190 panic!("expected insert query");
1191 };
1192 query
1193 }
1194
1195 fn update(input: &str) -> UpdateQuery {
1196 let mut parser = make_parser(input);
1197 let QueryExpr::Update(query) = parser.parse_update_query().expect("update") else {
1198 panic!("expected update query");
1199 };
1200 query
1201 }
1202
1203 fn delete(input: &str) -> DeleteQuery {
1204 let mut parser = make_parser(input);
1205 let QueryExpr::Delete(query) = parser.parse_delete_query().expect("delete") else {
1206 panic!("expected delete query");
1207 };
1208 query
1209 }
1210
1211 fn ask(input: &str) -> AskQuery {
1212 let mut parser = make_parser(input);
1213 let QueryExpr::Ask(query) = parser.parse_ask_query().expect("ask") else {
1214 panic!("expected ask query");
1215 };
1216 query
1217 }
1218
1219 #[test]
1220 fn insert_entity_types_with_options_returning_and_suppress_events() {
1221 let cases = [
1222 (
1223 "INSERT INTO items NODE (id) VALUES (1)",
1224 InsertEntityType::Node,
1225 ),
1226 (
1227 "INSERT INTO items EDGE (id) VALUES (1)",
1228 InsertEntityType::Edge,
1229 ),
1230 (
1231 "INSERT INTO items VECTOR (id) VALUES (1)",
1232 InsertEntityType::Vector,
1233 ),
1234 (
1235 "INSERT INTO items DOCUMENT VALUES ({\"id\": 1})",
1236 InsertEntityType::Document,
1237 ),
1238 ("INSERT INTO items KV (id) VALUES (1)", InsertEntityType::Kv),
1239 ];
1240 for (input, expected) in cases {
1241 assert_eq!(insert(input).entity_type, expected, "{input}");
1242 }
1243
1244 let query = insert(
1245 "INSERT INTO docs (id, body) VALUES (1, 'red'), (2, ?) \
1246 WITH TTL 2 h WITH EXPIRES AT 999 \
1247 WITH METADATA (source = 'test', score = 3) \
1248 WITH AUTO EMBED (body, title) USING openai MODEL 'text-embedding-3-small' \
1249 RETURNING * SUPPRESS EVENTS",
1250 );
1251 assert_eq!(query.table, "docs");
1252 assert_eq!(query.entity_type, InsertEntityType::Row);
1253 assert_eq!(query.columns, vec!["id", "body"]);
1254 assert_eq!(query.values.len(), 2);
1255 assert_eq!(query.value_exprs.len(), 2);
1256 assert_eq!(query.ttl_ms, Some(7_200_000));
1257 assert_eq!(query.expires_at_ms, Some(999));
1258 assert_eq!(query.with_metadata.len(), 2);
1259 assert_eq!(
1260 query.returning.as_deref(),
1261 Some([ReturningItem::All].as_slice())
1262 );
1263 let auto_embed = query.auto_embed.expect("auto embed");
1264 assert_eq!(auto_embed.fields, vec!["body", "title"]);
1265 assert_eq!(auto_embed.provider, "openai");
1266 assert_eq!(auto_embed.model.as_deref(), Some("text-embedding-3-small"));
1267 assert!(query.suppress_events);
1268 }
1269
1270 #[test]
1271 fn insert_rejects_metric_and_bad_with_clause() {
1272 let mut parser = make_parser("INSERT INTO METRIC cpu.usage (value) VALUES (1)");
1273 let err = parser
1274 .parse_insert_query()
1275 .expect_err("metric insert should fail");
1276 assert!(err.to_string().contains("INSERT INTO METRIC"));
1277
1278 let mut parser = make_parser("INSERT INTO docs (id) VALUES (1) WITH TTL 1 fortnight");
1279 let err = parser
1280 .parse_insert_query()
1281 .expect_err("bad ttl unit should fail");
1282 assert!(err.to_string().contains("unsupported TTL unit"));
1283
1284 let mut parser = make_parser("INSERT INTO docs (id) VALUES (1) WITH UNKNOWN");
1285 let err = parser
1286 .parse_insert_query()
1287 .expect_err("bad WITH should fail");
1288 assert!(err.to_string().contains("expected"));
1289 }
1290
1291 #[test]
1292 fn document_insert_canonical_bare_values_form() {
1293 let query = insert("INSERT INTO events DOCUMENT VALUES ({\"level\": \"info\"})");
1295 assert_eq!(query.table, "events");
1296 assert_eq!(query.entity_type, InsertEntityType::Document);
1297 assert_eq!(query.columns, vec!["body"]);
1298 assert_eq!(query.values.len(), 1);
1299 assert!(matches!(query.values[0][0], Value::Json(_)));
1300
1301 let query = insert(
1303 "INSERT INTO events DOCUMENT VALUES ({\"a\": 1}), ({\"b\": 2}) \
1304 WITH TTL 30 s WITH METADATA (source = 'test') RETURNING *",
1305 );
1306 assert_eq!(query.columns, vec!["body"]);
1307 assert_eq!(query.values.len(), 2);
1308 assert_eq!(query.ttl_ms, Some(30_000));
1309 assert_eq!(query.with_metadata.len(), 1);
1310 assert_eq!(
1311 query.returning.as_deref(),
1312 Some([ReturningItem::All].as_slice())
1313 );
1314 }
1315
1316 #[test]
1317 fn unmarked_bare_values_insert_parses_with_empty_columns() {
1318 let query = insert("INSERT INTO events VALUES ({\"level\": \"info\"})");
1322 assert_eq!(query.table, "events");
1323 assert_eq!(query.entity_type, InsertEntityType::Row);
1324 assert!(query.columns.is_empty());
1325 assert_eq!(query.values.len(), 1);
1326 assert!(matches!(query.values[0][0], Value::Json(_)));
1327
1328 let query = insert("INSERT INTO events VALUES ({\"a\": 1}), ({\"b\": 2})");
1330 assert!(query.columns.is_empty());
1331 assert_eq!(query.entity_type, InsertEntityType::Row);
1332 assert_eq!(query.values.len(), 2);
1333
1334 let query = insert("INSERT INTO t (id) VALUES (1)");
1336 assert_eq!(query.columns, vec!["id"]);
1337 }
1338
1339 #[test]
1340 fn document_insert_rejects_removed_forms() {
1341 let mut parser =
1343 make_parser("INSERT INTO events DOCUMENT (body) VALUES ({\"level\": \"info\"})");
1344 let err = parser
1345 .parse_insert_query()
1346 .expect_err("document column list should fail");
1347 assert!(err.to_string().contains("column list is removed"), "{err}");
1348
1349 let mut parser =
1351 make_parser("INSERT INTO events DOCUMENT (body, _ttl) VALUES ({\"a\": 1}, 30)");
1352 let err = parser
1353 .parse_insert_query()
1354 .expect_err("legacy _ttl column should fail");
1355 assert!(err.to_string().contains("WITH TTL"), "{err}");
1356
1357 let mut parser =
1359 make_parser("INSERT INTO events DOCUMENT VALUES ('{\"level\": \"info\"}')");
1360 let err = parser
1361 .parse_insert_query()
1362 .expect_err("quoted-string body should fail");
1363 let msg = err.to_string();
1364 assert!(msg.contains("inline JSON literal"), "{msg}");
1365 assert!(msg.contains("JSON_PARSE"), "{msg}");
1366 }
1367
1368 #[test]
1369 fn update_targets_compound_assignments_order_limit_returning() {
1370 let cases = [
1374 ("UPDATE docs SET count = 1", UpdateTarget::Rows),
1375 ("UPDATE docs NODES SET count = 1", UpdateTarget::Nodes),
1376 ("UPDATE docs EDGES SET count = 1", UpdateTarget::Edges),
1377 ];
1378 for (input, expected) in cases {
1379 assert_eq!(update(input).target, expected, "{input}");
1380 }
1381
1382 for marker in ["DOCUMENTS", "ROWS", "KV"] {
1384 let input = format!("UPDATE docs {marker} SET count = 1");
1385 let mut parser = make_parser(&input);
1386 let err = parser
1387 .parse_update_query()
1388 .expect_err("removed update marker should fail");
1389 let msg = err.to_string();
1390 assert!(msg.contains(marker), "{msg}");
1391 assert!(msg.contains("has been removed"), "{msg}");
1392 assert!(msg.contains("with no marker"), "{msg}");
1393 }
1394
1395 let query = update(
1396 "UPDATE docs SET count += 2, title = UPPER(title) \
1397 WHERE id = 1 WITH TTL 30 s WITH METADATA (source = 'update') \
1398 ORDER BY updated_at DESC LIMIT 5 RETURNING weight, title SUPPRESS EVENTS",
1399 );
1400 assert_eq!(query.table, "docs");
1401 assert_eq!(query.target, UpdateTarget::Rows);
1402 assert_eq!(query.assignment_exprs.len(), 2);
1403 assert_eq!(query.compound_assignment_ops, vec![Some(BinOp::Add), None]);
1404 assert_eq!(query.assignments.len(), 0);
1405 assert!(query.filter.is_some());
1406 assert!(query.where_expr.is_some());
1407 assert_eq!(query.ttl_ms, Some(30_000));
1408 assert_eq!(query.with_metadata.len(), 1);
1409 assert_eq!(query.claim_limit, None);
1410 assert!(!query.claim_exact);
1411 assert_eq!(query.limit, Some(5));
1412 assert_eq!(query.order_by.len(), 2);
1413 assert!(matches!(
1414 &query.order_by[1].field,
1415 FieldRef::TableColumn { column, .. } if column == "rid"
1416 ));
1417 assert_eq!(
1418 query.returning.as_deref(),
1419 Some(
1420 [
1421 ReturningItem::Column("weight".to_string()),
1422 ReturningItem::Column("title".to_string())
1423 ]
1424 .as_slice()
1425 )
1426 );
1427 assert!(query.suppress_events);
1428 }
1429
1430 #[test]
1431 fn update_dotted_targets_parse_unmarked_for_every_target() {
1432 let unmarked = update("UPDATE docs SET profile.address.city = 'Lisbon' WHERE name = 'ada'");
1436 assert_eq!(unmarked.target, UpdateTarget::Rows);
1437 assert_eq!(unmarked.assignment_exprs[0].0, "profile.address.city");
1438 assert_eq!(unmarked.assignments[0].0, "profile.address.city");
1439
1440 let graph = update("UPDATE social NODES SET meta.seen_at = 1");
1441 assert_eq!(graph.target, UpdateTarget::Nodes);
1442 assert_eq!(graph.assignment_exprs[0].0, "meta.seen_at");
1443 }
1444
1445 #[test]
1446 fn update_claim_limit_requires_order_by() {
1447 let query = update(
1448 "UPDATE docs SET status = 'reserved' WHERE status = 'available' \
1449 CLAIM LIMIT 2 ORDER BY priority ASC RETURNING id",
1450 );
1451 assert_eq!(query.claim_limit, Some(2));
1452 assert!(!query.claim_exact);
1453 assert_eq!(query.limit, None);
1454 assert_eq!(query.order_by.len(), 2);
1455 assert!(matches!(
1456 &query.order_by[1].field,
1457 FieldRef::TableColumn { column, .. } if column == "rid"
1458 ));
1459
1460 let query = update(
1461 "UPDATE docs SET status = 'reserved' WHERE status = 'available' \
1462 CLAIM EXACT 2 ORDER BY priority ASC RETURNING id",
1463 );
1464 assert_eq!(query.claim_limit, Some(2));
1465 assert!(query.claim_exact);
1466 assert_eq!(query.limit, None);
1467 assert_eq!(query.order_by.len(), 2);
1468 assert!(matches!(
1469 &query.order_by[1].field,
1470 FieldRef::TableColumn { column, .. } if column == "rid"
1471 ));
1472
1473 let mut parser = make_parser("UPDATE docs SET status = 'reserved' CLAIM LIMIT 1");
1474 let err = parser
1475 .parse_update_query()
1476 .expect_err("claim without order should fail");
1477 assert!(err.to_string().contains("CLAIM requires ORDER BY"));
1478 }
1479
1480 #[test]
1481 fn update_rejects_invalid_assignment_and_order_by_forms() {
1482 let mut parser = make_parser("UPDATE docs SET count ^= 1");
1483 let err = parser
1484 .parse_update_query()
1485 .expect_err("unknown compound assignment should fail");
1486 assert!(err.to_string().contains("expected"));
1487
1488 let mut parser = make_parser("UPDATE docs SET count = 1 ORDER BY updated_at");
1489 let err = parser
1490 .parse_update_query()
1491 .expect_err("ORDER BY without LIMIT should fail");
1492 assert!(err.to_string().contains("requires LIMIT"));
1493
1494 let mut parser = make_parser("UPDATE docs SET count = 1 ORDER BY updated_at + 1 LIMIT 1");
1495 let err = parser
1496 .parse_update_query()
1497 .expect_err("ORDER BY expression should fail");
1498 assert!(err.to_string().contains("top-level fields"));
1499 }
1500
1501 #[test]
1502 fn delete_returning_and_suppress_events() {
1503 let query = delete("DELETE FROM docs WHERE id = 1 RETURNING id, title SUPPRESS EVENTS");
1504 assert_eq!(query.table, "docs");
1505 assert!(query.filter.is_some());
1506 assert!(query.where_expr.is_some());
1507 assert_eq!(
1508 query.returning.as_deref(),
1509 Some(
1510 [
1511 ReturningItem::Column("id".to_string()),
1512 ReturningItem::Column("title".to_string())
1513 ]
1514 .as_slice()
1515 )
1516 );
1517 assert!(query.suppress_events);
1518
1519 let query = delete("DELETE FROM docs RETURNING *");
1520 assert_eq!(
1521 query.returning.as_deref(),
1522 Some([ReturningItem::All].as_slice())
1523 );
1524 }
1525
1526 #[test]
1527 fn returning_rejects_expression_forms() {
1528 for input in [
1529 "DELETE FROM docs RETURNING 1",
1530 "DELETE FROM docs RETURNING UPPER(title)",
1531 "DELETE FROM docs RETURNING title || body",
1532 ] {
1533 let mut parser = make_parser(input);
1534 let err = parser
1535 .parse_delete_query()
1536 .expect_err("RETURNING expression should fail");
1537 assert!(err.to_string().contains("RETURNING expressions"));
1538 }
1539 }
1540
1541 #[test]
1542 fn ask_parses_all_optional_clauses_and_cache_modes() {
1543 let query = ask(
1544 "ASK 'what changed?' USING 'openai' MODEL 'gpt' DEPTH 3 LIMIT 4 \
1545 MIN_SCORE 0.7 COLLECTION docs TEMPERATURE 0.2 SEED 42 STRICT OFF \
1546 STREAM CACHE TTL '10m'",
1547 );
1548 assert_eq!(query.question, "what changed?");
1549 assert_eq!(query.provider.as_deref(), Some("openai"));
1550 assert_eq!(query.model.as_deref(), Some("gpt"));
1551 assert_eq!(query.depth, Some(3));
1552 assert_eq!(query.limit, Some(4));
1553 assert_eq!(query.min_score, Some(0.7));
1554 assert_eq!(query.collection.as_deref(), Some("docs"));
1555 assert_eq!(query.temperature, Some(0.2));
1556 assert_eq!(query.seed, Some(42));
1557 assert!(!query.strict);
1558 assert!(query.stream);
1559 assert_eq!(query.cache, AskCacheClause::CacheTtl("10m".to_string()));
1560
1561 let query = ask("ASK ? NOCACHE");
1562 assert_eq!(query.question, "");
1563 assert_eq!(query.question_param, Some(0));
1564 assert_eq!(query.cache, AskCacheClause::NoCache);
1565 }
1566
1567 #[test]
1568 fn explain_ask_and_ask_error_paths() {
1569 let mut parser = make_parser("EXPLAIN ASK $2 STRICT ON");
1570 let QueryExpr::Ask(query) = parser.parse_explain_ask_query().expect("explain ask") else {
1571 panic!("expected ask query");
1572 };
1573 assert!(query.explain);
1574 assert_eq!(query.question_param, Some(1));
1575 assert!(query.strict);
1576
1577 let mut parser = make_parser("EXPLAIN SELECT 1");
1578 let err = parser
1579 .parse_explain_ask_query()
1580 .expect_err("missing ASK should fail");
1581 assert!(err.to_string().contains("expected"));
1582
1583 let mut parser = make_parser("ASK 'q' STRICT MAYBE");
1584 let err = parser
1585 .parse_ask_query()
1586 .expect_err("bad strict should fail");
1587 assert!(err.to_string().contains("Expected ON or OFF"));
1588
1589 let mut parser = make_parser("ASK 'q' CACHE TTL '10m' NOCACHE");
1590 let err = parser
1591 .parse_ask_query()
1592 .expect_err("duplicate cache should fail");
1593 assert!(err.to_string().contains("cache clause"));
1594
1595 let mut parser = make_parser("ASK 'q' CACHE FOREVER '10m'");
1596 let err = parser
1597 .parse_ask_query()
1598 .expect_err("bad cache ttl keyword should fail");
1599 assert!(err.to_string().contains("Expected TTL"));
1600 }
1601
1602 #[test]
1603 fn literal_value_special_constructors_arrays_and_objects() {
1604 let mut parser = make_parser("PASSWORD('pw')");
1605 assert!(matches!(
1606 parser.parse_literal_value().expect("password"),
1607 Value::Password(secret) if secret == "@@plain@@pw"
1608 ));
1609
1610 let mut parser = make_parser("SECRET('pw')");
1611 assert!(matches!(
1612 parser.parse_literal_value().expect("secret"),
1613 Value::Secret(bytes) if bytes == b"@@plain@@pw"
1614 ));
1615
1616 let mut parser = make_parser("SECRET_REF(vault, red.vault.api_key)");
1617 let value = parser.parse_literal_value().expect("secret ref");
1618 assert!(matches!(value, Value::Json(_)));
1619
1620 let mut parser = make_parser("[1, 2.5]");
1624 assert!(matches!(
1625 parser.parse_literal_value().expect("array"),
1626 Value::Array(items)
1627 if items == vec![Value::Integer(1), Value::Float(2.5)]
1628 ));
1629
1630 let mut parser = make_parser("['a', 2]");
1631 assert!(matches!(
1632 parser.parse_literal_value().expect("mixed array"),
1633 Value::Array(items)
1634 if items == vec![Value::Text("a".into()), Value::Integer(2)]
1635 ));
1636
1637 let mut parser = make_parser("[1, 2, 9007199254740993]");
1640 assert!(matches!(
1641 parser.parse_literal_value().expect("lossless big-int array"),
1642 Value::Array(items)
1643 if items
1644 == vec![
1645 Value::Integer(1),
1646 Value::Integer(2),
1647 Value::Integer(9007199254740993),
1648 ]
1649 ));
1650
1651 let mut parser = make_parser("{level = 'info', count: 2}");
1652 assert!(matches!(
1653 parser.parse_literal_value().expect("json object"),
1654 Value::Json(_)
1655 ));
1656 }
1657
1658 #[test]
1659 fn literal_value_rejects_invalid_secret_ref_and_scalar_start() {
1660 let mut parser = make_parser("SECRET_REF(config, red.vault.api_key)");
1661 let err = parser
1662 .parse_literal_value()
1663 .expect_err("non-vault secret ref should fail");
1664 assert!(err.to_string().contains("expected"));
1665
1666 let mut parser = make_parser("ORDER");
1667 let err = parser
1668 .parse_literal_value()
1669 .expect_err("non literal should fail");
1670 assert!(err.to_string().contains("expected"));
1671 }
1672
1673 #[test]
1674 fn json_depth_check_rejects_deep_literals() {
1675 let mut deep = reddb_types::utils::json::JsonValue::Array(vec![]);
1676 for _ in 0..JSON_LITERAL_MAX_DEPTH {
1677 deep = reddb_types::utils::json::JsonValue::Array(vec![deep]);
1678 }
1679 let err = json_literal_depth_check(&deep).expect_err("depth should fail");
1680 assert!(err.contains("JSON_LITERAL_MAX_DEPTH"));
1681 }
1682}