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(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(
543 &mut self,
544 target: UpdateTarget,
545 ) -> Result<String, ParseError> {
546 let mut segments = vec![self.expect_column_ident()?];
547 if matches!(target, UpdateTarget::Documents) {
548 while self.consume(&Token::Dot)? {
549 segments.push(self.expect_column_ident()?);
550 }
551 }
552 Ok(segments.join("."))
553 }
554
555 fn parse_update_target(&mut self) -> Result<UpdateTarget, ParseError> {
556 if self.consume(&Token::Kv)? {
557 return Ok(UpdateTarget::Kv);
558 }
559 if self.consume(&Token::Rows)? {
560 return Ok(UpdateTarget::Rows);
561 }
562 if self.consume_ident_ci("DOCUMENTS")? {
563 return Ok(UpdateTarget::Documents);
564 }
565 if self.consume_ident_ci("NODES")? {
566 return Ok(UpdateTarget::Nodes);
567 }
568 if self.consume_ident_ci("EDGES")? {
569 return Ok(UpdateTarget::Edges);
570 }
571 Ok(UpdateTarget::Rows)
572 }
573
574 pub fn parse_delete_query(&mut self) -> Result<QueryExpr, ParseError> {
576 self.expect(Token::Delete)?;
577 self.expect(Token::From)?;
578 let table = self.expect_ident()?;
579
580 let filter = if self.consume(&Token::Where)? {
581 Some(self.parse_filter()?)
582 } else {
583 None
584 };
585
586 let where_expr = filter.as_ref().map(filter_to_expr);
587
588 let returning = self.parse_returning_clause()?;
589
590 let suppress_events = if self.consume_ident_ci("SUPPRESS")? {
591 self.expect_ident_ci("EVENTS")?;
592 true
593 } else {
594 false
595 };
596
597 Ok(QueryExpr::Delete(DeleteQuery {
598 table,
599 where_expr,
600 filter,
601 returning,
602 suppress_events,
603 }))
604 }
605
606 fn parse_returning_clause(&mut self) -> Result<Option<Vec<ReturningItem>>, ParseError> {
610 if !self.consume(&Token::Returning)? {
611 return Ok(None);
612 }
613 if self.consume(&Token::Star)? {
614 return Ok(Some(vec![ReturningItem::All]));
615 }
616 let mut items = Vec::new();
617 loop {
618 if returning_expr_start(self.peek()) {
619 return Err(returning_expr_not_supported(self.position()));
620 }
621 let col = self.expect_update_returning_column()?;
622 items.push(ReturningItem::Column(col));
623 if returning_expr_tail(self.peek()) {
624 return Err(returning_expr_not_supported(self.position()));
625 }
626 if !self.consume(&Token::Comma)? {
627 break;
628 }
629 }
630 if items.is_empty() {
631 return Err(ParseError::expected(
632 vec!["*", "column name"],
633 self.peek(),
634 self.position(),
635 ));
636 }
637 Ok(Some(items))
638 }
639
640 fn expect_update_returning_column(&mut self) -> Result<String, ParseError> {
641 if self.consume(&Token::Weight)? {
642 return Ok("weight".to_string());
643 }
644 self.expect_ident_or_keyword()
645 }
646
647 pub fn parse_ask_query(&mut self) -> Result<QueryExpr, ParseError> {
650 self.parse_ask_query_with_explain(false)
651 }
652
653 pub fn parse_explain_ask_query(&mut self) -> Result<QueryExpr, ParseError> {
655 self.advance()?; if !matches!(self.peek(), Token::Ident(name) if name.eq_ignore_ascii_case("ASK")) {
657 return Err(ParseError::expected(
658 vec!["ASK"],
659 self.peek(),
660 self.position(),
661 ));
662 }
663 self.parse_ask_query_with_explain(true)
664 }
665
666 fn parse_ask_query_with_explain(&mut self, explain: bool) -> Result<QueryExpr, ParseError> {
667 self.advance()?; let (question, question_param) = match self.peek() {
670 Token::String(_) => (self.parse_string()?, None),
671 Token::Dollar | Token::Question => {
672 let index = self.parse_param_slot("ASK question")?;
673 (String::new(), Some(index))
674 }
675 other => {
676 return Err(ParseError::expected(
677 vec!["string", "$N", "?"],
678 other,
679 self.position(),
680 ));
681 }
682 };
683
684 let mut provider = None;
685 let mut model = None;
686 let mut depth = None;
687 let mut limit = None;
688 let mut min_score = None;
689 let mut collection = None;
690 let mut temperature = None;
691 let mut seed = None;
692 let mut strict = true;
693 let mut stream = false;
694 let mut cache = AskCacheClause::Default;
695 let mut as_rql = false;
696 let mut execute = false;
697
698 for _ in 0..14 {
701 if self.consume(&Token::Using)? {
702 provider = Some(match &self.current.token {
703 Token::String(_) => self.parse_string()?,
704 _ => self.expect_ident()?,
705 });
706 } else if self.consume_ident_ci("MODEL")? {
707 model = Some(self.parse_string()?);
708 } else if self.consume(&Token::Depth)? {
709 depth = Some(self.parse_integer()? as usize);
710 } else if self.consume(&Token::Limit)? {
711 limit = Some(self.parse_integer()? as usize);
712 } else if self.consume(&Token::MinScore)? {
713 min_score = Some(self.parse_float()? as f32);
714 } else if self.consume(&Token::Collection)? {
715 collection = Some(self.expect_ident()?);
716 } else if self.consume_ident_ci("TEMPERATURE")? {
717 temperature = Some(self.parse_float()? as f32);
718 } else if self.consume_ident_ci("SEED")? {
719 seed = Some(self.parse_integer()? as u64);
720 } else if self.consume_ident_ci("STRICT")? {
721 let value = self.expect_ident_or_keyword()?;
722 if value.eq_ignore_ascii_case("ON") {
723 strict = true;
724 } else if value.eq_ignore_ascii_case("OFF") {
725 strict = false;
726 } else {
727 return Err(ParseError::new(
728 "Expected ON or OFF after STRICT",
729 self.position(),
730 ));
731 }
732 } else if self.consume_ident_ci("STREAM")? {
733 stream = true;
734 } else if self.consume_ident_ci("CACHE")? {
735 if !matches!(cache, AskCacheClause::Default) {
736 return Err(ParseError::new(
737 "ASK cache clause specified more than once",
738 self.position(),
739 ));
740 }
741 let ttl = self.expect_ident_or_keyword()?;
742 if !ttl.eq_ignore_ascii_case("TTL") {
743 return Err(ParseError::new("Expected TTL after CACHE", self.position()));
744 }
745 cache = AskCacheClause::CacheTtl(self.parse_string()?);
746 } else if self.consume_ident_ci("NOCACHE")? {
747 if !matches!(cache, AskCacheClause::Default) {
748 return Err(ParseError::new(
749 "ASK cache clause specified more than once",
750 self.position(),
751 ));
752 }
753 cache = AskCacheClause::NoCache;
754 } else if self.consume(&Token::As)? {
755 if as_rql {
756 return Err(ParseError::new(
757 "ASK AS RQL specified more than once",
758 self.position(),
759 ));
760 }
761 let output = self.expect_ident_or_keyword()?;
762 if !output.eq_ignore_ascii_case("RQL") {
763 return Err(ParseError::new(
764 "Expected RQL after ASK AS",
765 self.position(),
766 ));
767 }
768 as_rql = true;
769 } else if self.consume_ident_ci("EXECUTE")? {
770 if execute {
771 return Err(ParseError::new(
772 "ASK EXECUTE specified more than once",
773 self.position(),
774 ));
775 }
776 execute = true;
777 } else {
778 break;
779 }
780 }
781
782 Ok(QueryExpr::Ask(AskQuery {
783 explain,
784 question,
785 question_param,
786 provider,
787 model,
788 depth,
789 limit,
790 min_score,
791 collection,
792 temperature,
793 seed,
794 strict,
795 stream,
796 cache,
797 as_rql,
798 execute,
799 }))
800 }
801
802 fn parse_ident_list(&mut self) -> Result<Vec<String>, ParseError> {
804 let mut idents = Vec::new();
805 loop {
806 idents.push(self.expect_ident_or_keyword()?);
807 if !self.consume(&Token::Comma)? {
808 break;
809 }
810 }
811 Ok(idents)
812 }
813
814 fn parse_dml_value_list(&mut self) -> Result<Vec<Value>, ParseError> {
816 self.parse_dml_expr_list()?
817 .into_iter()
818 .map(fold_expr_to_value)
819 .collect::<Result<Vec<_>, _>>()
820 .map_err(|msg| ParseError::new(msg, self.position()))
821 }
822
823 fn parse_dml_expr_list(&mut self) -> Result<Vec<Expr>, ParseError> {
824 let mut values = Vec::new();
825 loop {
826 values.push(self.parse_expr()?);
827 if !self.consume(&Token::Comma)? {
828 break;
829 }
830 }
831 Ok(values)
832 }
833
834 pub(crate) fn parse_literal_value(&mut self) -> Result<Value, ParseError> {
836 self.enter_depth()?;
845 let result = self.parse_literal_value_inner();
846 self.exit_depth();
847 result
848 }
849
850 fn parse_literal_value_inner(&mut self) -> Result<Value, ParseError> {
851 if let Token::Ident(name) = self.peek().clone() {
858 let upper = name.to_uppercase();
859 if upper == "PASSWORD" || upper == "SECRET" {
860 self.advance()?; self.expect(Token::LParen)?;
862 let plaintext = self.parse_string()?;
863 self.expect(Token::RParen)?;
864 return Ok(match upper.as_str() {
865 "PASSWORD" => Value::Password(format!("@@plain@@{plaintext}")),
866 "SECRET" => Value::Secret(format!("@@plain@@{plaintext}").into_bytes()),
867 _ => unreachable!(),
868 });
869 }
870 if upper == "SECRET_REF" {
871 self.advance()?; self.expect(Token::LParen)?;
873 let store = self.expect_ident_or_keyword()?.to_ascii_lowercase();
874 if store != "vault" {
875 return Err(ParseError::expected(
876 vec!["vault"],
877 self.peek(),
878 self.position(),
879 ));
880 }
881 self.expect(Token::Comma)?;
882 let (collection, key) =
883 self.parse_kv_key(reddb_types::catalog::CollectionModel::Vault)?;
884 self.expect(Token::RParen)?;
885 return Ok(secret_ref_value(&store, &collection, &key));
886 }
887 }
888
889 match self.peek().clone() {
890 Token::String(s) => {
891 let s = s.clone();
892 self.advance()?;
893 Ok(Value::text(s))
894 }
895 Token::JsonLiteral(raw) => {
896 self.advance()?;
901 let json_value = reddb_types::utils::json::parse_json(&raw).map_err(|err| {
902 ParseError::new(
903 format!("invalid JSON object literal: {:?}", err.to_string()),
909 self.position(),
910 )
911 })?;
912 json_literal_depth_check(&json_value)
913 .map_err(|err| ParseError::new(err, self.position()))?;
914 let canonical = reddb_types::serde_json::Value::from(json_value);
915 let bytes = reddb_types::json::to_vec(&canonical).map_err(|err| {
916 ParseError::new(
917 format!("failed to encode JSON literal: {:?}", err.to_string()),
921 self.position(),
922 )
923 })?;
924 Ok(Value::Json(bytes))
925 }
926 Token::Integer(n) => {
927 self.advance()?;
928 Ok(Value::Integer(n))
929 }
930 Token::Float(n) => {
931 self.advance()?;
932 Ok(Value::Float(n))
933 }
934 Token::True => {
935 self.advance()?;
936 Ok(Value::Boolean(true))
937 }
938 Token::False => {
939 self.advance()?;
940 Ok(Value::Boolean(false))
941 }
942 Token::Null => {
943 self.advance()?;
944 Ok(Value::Null)
945 }
946 Token::LBracket => {
947 self.advance()?; let mut items = Vec::new();
959 if !self.check(&Token::RBracket) {
960 loop {
961 items.push(self.parse_literal_value()?);
962 if !self.consume(&Token::Comma)? {
963 break;
964 }
965 }
966 }
967 self.expect(Token::RBracket)?;
968 Ok(Value::Array(items))
969 }
970 Token::LBrace => {
971 self.advance()?; let mut map = reddb_types::json::Map::new();
974 if !self.check(&Token::RBrace) {
975 loop {
976 let key = match self.peek().clone() {
983 Token::String(s) => {
984 self.advance()?;
985 s
986 }
987 Token::Ident(s) => {
988 self.advance()?;
989 s
990 }
991 _ => self.expect_ident_or_keyword()?.to_ascii_lowercase(),
992 };
993 if !self.consume(&Token::Colon)? {
995 self.expect(Token::Eq)?;
996 }
997 let val = self.parse_literal_value()?;
999 map.insert(key, literal_value_to_json(&val));
1000 if !self.consume(&Token::Comma)? {
1001 break;
1002 }
1003 }
1004 }
1005 self.expect(Token::RBrace)?;
1006 let json_val = reddb_types::json::Value::Object(map);
1007 let bytes = reddb_types::json::to_vec(&json_val).unwrap_or_default();
1008 Ok(Value::Json(bytes))
1009 }
1010 ref other => Err(ParseError::expected(
1011 vec!["string", "number", "true", "false", "null", "[", "{"],
1012 other,
1013 self.position(),
1014 )),
1015 }
1016 }
1017}
1018
1019fn is_legacy_ttl_column(column: &str) -> bool {
1024 column.eq_ignore_ascii_case("_ttl")
1025 || column.eq_ignore_ascii_case("_ttl_ms")
1026 || column.eq_ignore_ascii_case("_expires_at")
1027}
1028
1029fn literal_value_to_json(val: &Value) -> reddb_types::json::Value {
1037 match val {
1038 Value::Null => reddb_types::json::Value::Null,
1039 Value::Boolean(b) => reddb_types::json::Value::Bool(*b),
1040 Value::Integer(i) => reddb_types::json::Value::Number(*i as f64),
1041 Value::Float(f) => reddb_types::json::Value::Number(*f),
1042 Value::Text(s) => reddb_types::json::Value::String(s.to_string()),
1043 Value::Json(bytes) => {
1044 reddb_types::json::from_slice(bytes).unwrap_or(reddb_types::json::Value::Null)
1045 }
1046 Value::Array(items) => {
1047 reddb_types::json::Value::Array(items.iter().map(literal_value_to_json).collect())
1048 }
1049 _ => reddb_types::json::Value::Null,
1050 }
1051}
1052
1053fn returning_expr_start(token: &Token) -> bool {
1054 matches!(
1055 token,
1056 Token::Integer(_)
1057 | Token::Float(_)
1058 | Token::String(_)
1059 | Token::JsonLiteral(_)
1060 | Token::Null
1061 | Token::True
1062 | Token::False
1063 | Token::LParen
1064 | Token::Minus
1065 | Token::Question
1066 | Token::Dollar
1067 )
1068}
1069
1070fn returning_expr_tail(token: &Token) -> bool {
1071 matches!(
1072 token,
1073 Token::LParen
1074 | Token::Plus
1075 | Token::Minus
1076 | Token::Star
1077 | Token::Slash
1078 | Token::Percent
1079 | Token::DoublePipe
1080 | Token::Pipe
1081 | Token::Eq
1082 | Token::Ne
1083 | Token::Lt
1084 | Token::Le
1085 | Token::Gt
1086 | Token::Ge
1087 | Token::Dot
1088 | Token::Colon
1089 )
1090}
1091
1092fn validate_update_order_by(
1093 clauses: &[OrderByClause],
1094 position: crate::lexer::Position,
1095) -> Result<(), ParseError> {
1096 for clause in clauses {
1097 if clause.expr.is_some() {
1098 return Err(ParseError::new(
1099 "UPDATE ORDER BY only supports top-level fields",
1100 position,
1101 ));
1102 }
1103 match &clause.field {
1104 FieldRef::TableColumn { table, column }
1105 if table.is_empty() && !column.contains('.') => {}
1106 _ => {
1107 return Err(ParseError::new(
1108 "UPDATE ORDER BY only supports top-level fields",
1109 position,
1110 ));
1111 }
1112 }
1113 }
1114 Ok(())
1115}
1116
1117fn update_order_by_mentions_rid(clauses: &[OrderByClause]) -> bool {
1118 clauses.iter().any(|clause| {
1119 matches!(
1120 &clause.field,
1121 FieldRef::TableColumn { table, column }
1122 if table.is_empty() && column.eq_ignore_ascii_case("rid")
1123 )
1124 })
1125}
1126
1127fn returning_expr_not_supported(position: crate::lexer::Position) -> ParseError {
1128 ParseError::new(
1129 "NOT_YET_SUPPORTED: RETURNING expressions are not supported yet; use RETURNING * or named columns. Track a follow-up issue for RETURNING <expr>.",
1130 position,
1131 )
1132}
1133
1134fn secret_ref_value(store: &str, collection: &str, key: &str) -> Value {
1135 let mut map = reddb_types::json::Map::new();
1136 map.insert(
1137 "type".to_string(),
1138 reddb_types::json::Value::String("secret_ref".to_string()),
1139 );
1140 map.insert(
1141 "store".to_string(),
1142 reddb_types::json::Value::String(store.to_string()),
1143 );
1144 map.insert(
1145 "collection".to_string(),
1146 reddb_types::json::Value::String(collection.to_string()),
1147 );
1148 map.insert(
1149 "key".to_string(),
1150 reddb_types::json::Value::String(key.to_string()),
1151 );
1152 Value::Json(
1153 reddb_types::json::to_vec(&reddb_types::json::Value::Object(map)).unwrap_or_default(),
1154 )
1155}
1156
1157#[cfg(test)]
1158mod tests {
1159 use super::*;
1160 use crate::ast::{InsertEntityType, ReturningItem, UpdateTarget};
1161
1162 fn make_parser(input: &str) -> Parser<'_> {
1163 Parser::new(input).expect("lexer")
1164 }
1165
1166 fn insert(input: &str) -> InsertQuery {
1167 let mut parser = make_parser(input);
1168 let QueryExpr::Insert(query) = parser.parse_insert_query().expect("insert") else {
1169 panic!("expected insert query");
1170 };
1171 query
1172 }
1173
1174 fn update(input: &str) -> UpdateQuery {
1175 let mut parser = make_parser(input);
1176 let QueryExpr::Update(query) = parser.parse_update_query().expect("update") else {
1177 panic!("expected update query");
1178 };
1179 query
1180 }
1181
1182 fn delete(input: &str) -> DeleteQuery {
1183 let mut parser = make_parser(input);
1184 let QueryExpr::Delete(query) = parser.parse_delete_query().expect("delete") else {
1185 panic!("expected delete query");
1186 };
1187 query
1188 }
1189
1190 fn ask(input: &str) -> AskQuery {
1191 let mut parser = make_parser(input);
1192 let QueryExpr::Ask(query) = parser.parse_ask_query().expect("ask") else {
1193 panic!("expected ask query");
1194 };
1195 query
1196 }
1197
1198 #[test]
1199 fn insert_entity_types_with_options_returning_and_suppress_events() {
1200 let cases = [
1201 (
1202 "INSERT INTO items NODE (id) VALUES (1)",
1203 InsertEntityType::Node,
1204 ),
1205 (
1206 "INSERT INTO items EDGE (id) VALUES (1)",
1207 InsertEntityType::Edge,
1208 ),
1209 (
1210 "INSERT INTO items VECTOR (id) VALUES (1)",
1211 InsertEntityType::Vector,
1212 ),
1213 (
1214 "INSERT INTO items DOCUMENT VALUES ({\"id\": 1})",
1215 InsertEntityType::Document,
1216 ),
1217 ("INSERT INTO items KV (id) VALUES (1)", InsertEntityType::Kv),
1218 ];
1219 for (input, expected) in cases {
1220 assert_eq!(insert(input).entity_type, expected, "{input}");
1221 }
1222
1223 let query = insert(
1224 "INSERT INTO docs (id, body) VALUES (1, 'red'), (2, ?) \
1225 WITH TTL 2 h WITH EXPIRES AT 999 \
1226 WITH METADATA (source = 'test', score = 3) \
1227 WITH AUTO EMBED (body, title) USING openai MODEL 'text-embedding-3-small' \
1228 RETURNING * SUPPRESS EVENTS",
1229 );
1230 assert_eq!(query.table, "docs");
1231 assert_eq!(query.entity_type, InsertEntityType::Row);
1232 assert_eq!(query.columns, vec!["id", "body"]);
1233 assert_eq!(query.values.len(), 2);
1234 assert_eq!(query.value_exprs.len(), 2);
1235 assert_eq!(query.ttl_ms, Some(7_200_000));
1236 assert_eq!(query.expires_at_ms, Some(999));
1237 assert_eq!(query.with_metadata.len(), 2);
1238 assert_eq!(
1239 query.returning.as_deref(),
1240 Some([ReturningItem::All].as_slice())
1241 );
1242 let auto_embed = query.auto_embed.expect("auto embed");
1243 assert_eq!(auto_embed.fields, vec!["body", "title"]);
1244 assert_eq!(auto_embed.provider, "openai");
1245 assert_eq!(auto_embed.model.as_deref(), Some("text-embedding-3-small"));
1246 assert!(query.suppress_events);
1247 }
1248
1249 #[test]
1250 fn insert_rejects_metric_and_bad_with_clause() {
1251 let mut parser = make_parser("INSERT INTO METRIC cpu.usage (value) VALUES (1)");
1252 let err = parser
1253 .parse_insert_query()
1254 .expect_err("metric insert should fail");
1255 assert!(err.to_string().contains("INSERT INTO METRIC"));
1256
1257 let mut parser = make_parser("INSERT INTO docs (id) VALUES (1) WITH TTL 1 fortnight");
1258 let err = parser
1259 .parse_insert_query()
1260 .expect_err("bad ttl unit should fail");
1261 assert!(err.to_string().contains("unsupported TTL unit"));
1262
1263 let mut parser = make_parser("INSERT INTO docs (id) VALUES (1) WITH UNKNOWN");
1264 let err = parser
1265 .parse_insert_query()
1266 .expect_err("bad WITH should fail");
1267 assert!(err.to_string().contains("expected"));
1268 }
1269
1270 #[test]
1271 fn document_insert_canonical_bare_values_form() {
1272 let query = insert("INSERT INTO events DOCUMENT VALUES ({\"level\": \"info\"})");
1274 assert_eq!(query.table, "events");
1275 assert_eq!(query.entity_type, InsertEntityType::Document);
1276 assert_eq!(query.columns, vec!["body"]);
1277 assert_eq!(query.values.len(), 1);
1278 assert!(matches!(query.values[0][0], Value::Json(_)));
1279
1280 let query = insert(
1282 "INSERT INTO events DOCUMENT VALUES ({\"a\": 1}), ({\"b\": 2}) \
1283 WITH TTL 30 s WITH METADATA (source = 'test') RETURNING *",
1284 );
1285 assert_eq!(query.columns, vec!["body"]);
1286 assert_eq!(query.values.len(), 2);
1287 assert_eq!(query.ttl_ms, Some(30_000));
1288 assert_eq!(query.with_metadata.len(), 1);
1289 assert_eq!(
1290 query.returning.as_deref(),
1291 Some([ReturningItem::All].as_slice())
1292 );
1293 }
1294
1295 #[test]
1296 fn unmarked_bare_values_insert_parses_with_empty_columns() {
1297 let query = insert("INSERT INTO events VALUES ({\"level\": \"info\"})");
1301 assert_eq!(query.table, "events");
1302 assert_eq!(query.entity_type, InsertEntityType::Row);
1303 assert!(query.columns.is_empty());
1304 assert_eq!(query.values.len(), 1);
1305 assert!(matches!(query.values[0][0], Value::Json(_)));
1306
1307 let query = insert("INSERT INTO events VALUES ({\"a\": 1}), ({\"b\": 2})");
1309 assert!(query.columns.is_empty());
1310 assert_eq!(query.entity_type, InsertEntityType::Row);
1311 assert_eq!(query.values.len(), 2);
1312
1313 let query = insert("INSERT INTO t (id) VALUES (1)");
1315 assert_eq!(query.columns, vec!["id"]);
1316 }
1317
1318 #[test]
1319 fn document_insert_rejects_removed_forms() {
1320 let mut parser =
1322 make_parser("INSERT INTO events DOCUMENT (body) VALUES ({\"level\": \"info\"})");
1323 let err = parser
1324 .parse_insert_query()
1325 .expect_err("document column list should fail");
1326 assert!(err.to_string().contains("column list is removed"), "{err}");
1327
1328 let mut parser =
1330 make_parser("INSERT INTO events DOCUMENT (body, _ttl) VALUES ({\"a\": 1}, 30)");
1331 let err = parser
1332 .parse_insert_query()
1333 .expect_err("legacy _ttl column should fail");
1334 assert!(err.to_string().contains("WITH TTL"), "{err}");
1335
1336 let mut parser =
1338 make_parser("INSERT INTO events DOCUMENT VALUES ('{\"level\": \"info\"}')");
1339 let err = parser
1340 .parse_insert_query()
1341 .expect_err("quoted-string body should fail");
1342 let msg = err.to_string();
1343 assert!(msg.contains("inline JSON literal"), "{msg}");
1344 assert!(msg.contains("JSON_PARSE"), "{msg}");
1345 }
1346
1347 #[test]
1348 fn update_targets_compound_assignments_order_limit_returning() {
1349 let cases = [
1350 ("UPDATE docs KV SET count = 1", UpdateTarget::Kv),
1351 ("UPDATE docs ROWS SET count = 1", UpdateTarget::Rows),
1352 (
1353 "UPDATE docs DOCUMENTS SET count = 1",
1354 UpdateTarget::Documents,
1355 ),
1356 ("UPDATE docs NODES SET count = 1", UpdateTarget::Nodes),
1357 ("UPDATE docs EDGES SET count = 1", UpdateTarget::Edges),
1358 ];
1359 for (input, expected) in cases {
1360 assert_eq!(update(input).target, expected, "{input}");
1361 }
1362
1363 let query = update(
1364 "UPDATE docs DOCUMENTS SET count += 2, title = UPPER(title) \
1365 WHERE id = 1 WITH TTL 30 s WITH METADATA (source = 'update') \
1366 ORDER BY updated_at DESC LIMIT 5 RETURNING weight, title SUPPRESS EVENTS",
1367 );
1368 assert_eq!(query.table, "docs");
1369 assert_eq!(query.target, UpdateTarget::Documents);
1370 assert_eq!(query.assignment_exprs.len(), 2);
1371 assert_eq!(query.compound_assignment_ops, vec![Some(BinOp::Add), None]);
1372 assert_eq!(query.assignments.len(), 0);
1373 assert!(query.filter.is_some());
1374 assert!(query.where_expr.is_some());
1375 assert_eq!(query.ttl_ms, Some(30_000));
1376 assert_eq!(query.with_metadata.len(), 1);
1377 assert_eq!(query.claim_limit, None);
1378 assert!(!query.claim_exact);
1379 assert_eq!(query.limit, Some(5));
1380 assert_eq!(query.order_by.len(), 2);
1381 assert!(matches!(
1382 &query.order_by[1].field,
1383 FieldRef::TableColumn { column, .. } if column == "rid"
1384 ));
1385 assert_eq!(
1386 query.returning.as_deref(),
1387 Some(
1388 [
1389 ReturningItem::Column("weight".to_string()),
1390 ReturningItem::Column("title".to_string())
1391 ]
1392 .as_slice()
1393 )
1394 );
1395 assert!(query.suppress_events);
1396 }
1397
1398 #[test]
1399 fn update_claim_limit_requires_order_by() {
1400 let query = update(
1401 "UPDATE docs SET status = 'reserved' WHERE status = 'available' \
1402 CLAIM LIMIT 2 ORDER BY priority ASC RETURNING id",
1403 );
1404 assert_eq!(query.claim_limit, Some(2));
1405 assert!(!query.claim_exact);
1406 assert_eq!(query.limit, None);
1407 assert_eq!(query.order_by.len(), 2);
1408 assert!(matches!(
1409 &query.order_by[1].field,
1410 FieldRef::TableColumn { column, .. } if column == "rid"
1411 ));
1412
1413 let query = update(
1414 "UPDATE docs SET status = 'reserved' WHERE status = 'available' \
1415 CLAIM EXACT 2 ORDER BY priority ASC RETURNING id",
1416 );
1417 assert_eq!(query.claim_limit, Some(2));
1418 assert!(query.claim_exact);
1419 assert_eq!(query.limit, None);
1420 assert_eq!(query.order_by.len(), 2);
1421 assert!(matches!(
1422 &query.order_by[1].field,
1423 FieldRef::TableColumn { column, .. } if column == "rid"
1424 ));
1425
1426 let mut parser = make_parser("UPDATE docs SET status = 'reserved' CLAIM LIMIT 1");
1427 let err = parser
1428 .parse_update_query()
1429 .expect_err("claim without order should fail");
1430 assert!(err.to_string().contains("CLAIM requires ORDER BY"));
1431 }
1432
1433 #[test]
1434 fn update_rejects_invalid_assignment_and_order_by_forms() {
1435 let mut parser = make_parser("UPDATE docs SET count ^= 1");
1436 let err = parser
1437 .parse_update_query()
1438 .expect_err("unknown compound assignment should fail");
1439 assert!(err.to_string().contains("expected"));
1440
1441 let mut parser = make_parser("UPDATE docs SET count = 1 ORDER BY updated_at");
1442 let err = parser
1443 .parse_update_query()
1444 .expect_err("ORDER BY without LIMIT should fail");
1445 assert!(err.to_string().contains("requires LIMIT"));
1446
1447 let mut parser = make_parser("UPDATE docs SET count = 1 ORDER BY updated_at + 1 LIMIT 1");
1448 let err = parser
1449 .parse_update_query()
1450 .expect_err("ORDER BY expression should fail");
1451 assert!(err.to_string().contains("top-level fields"));
1452 }
1453
1454 #[test]
1455 fn delete_returning_and_suppress_events() {
1456 let query = delete("DELETE FROM docs WHERE id = 1 RETURNING id, title SUPPRESS EVENTS");
1457 assert_eq!(query.table, "docs");
1458 assert!(query.filter.is_some());
1459 assert!(query.where_expr.is_some());
1460 assert_eq!(
1461 query.returning.as_deref(),
1462 Some(
1463 [
1464 ReturningItem::Column("id".to_string()),
1465 ReturningItem::Column("title".to_string())
1466 ]
1467 .as_slice()
1468 )
1469 );
1470 assert!(query.suppress_events);
1471
1472 let query = delete("DELETE FROM docs RETURNING *");
1473 assert_eq!(
1474 query.returning.as_deref(),
1475 Some([ReturningItem::All].as_slice())
1476 );
1477 }
1478
1479 #[test]
1480 fn returning_rejects_expression_forms() {
1481 for input in [
1482 "DELETE FROM docs RETURNING 1",
1483 "DELETE FROM docs RETURNING UPPER(title)",
1484 "DELETE FROM docs RETURNING title || body",
1485 ] {
1486 let mut parser = make_parser(input);
1487 let err = parser
1488 .parse_delete_query()
1489 .expect_err("RETURNING expression should fail");
1490 assert!(err.to_string().contains("RETURNING expressions"));
1491 }
1492 }
1493
1494 #[test]
1495 fn ask_parses_all_optional_clauses_and_cache_modes() {
1496 let query = ask(
1497 "ASK 'what changed?' USING 'openai' MODEL 'gpt' DEPTH 3 LIMIT 4 \
1498 MIN_SCORE 0.7 COLLECTION docs TEMPERATURE 0.2 SEED 42 STRICT OFF \
1499 STREAM CACHE TTL '10m'",
1500 );
1501 assert_eq!(query.question, "what changed?");
1502 assert_eq!(query.provider.as_deref(), Some("openai"));
1503 assert_eq!(query.model.as_deref(), Some("gpt"));
1504 assert_eq!(query.depth, Some(3));
1505 assert_eq!(query.limit, Some(4));
1506 assert_eq!(query.min_score, Some(0.7));
1507 assert_eq!(query.collection.as_deref(), Some("docs"));
1508 assert_eq!(query.temperature, Some(0.2));
1509 assert_eq!(query.seed, Some(42));
1510 assert!(!query.strict);
1511 assert!(query.stream);
1512 assert_eq!(query.cache, AskCacheClause::CacheTtl("10m".to_string()));
1513
1514 let query = ask("ASK ? NOCACHE");
1515 assert_eq!(query.question, "");
1516 assert_eq!(query.question_param, Some(0));
1517 assert_eq!(query.cache, AskCacheClause::NoCache);
1518 }
1519
1520 #[test]
1521 fn explain_ask_and_ask_error_paths() {
1522 let mut parser = make_parser("EXPLAIN ASK $2 STRICT ON");
1523 let QueryExpr::Ask(query) = parser.parse_explain_ask_query().expect("explain ask") else {
1524 panic!("expected ask query");
1525 };
1526 assert!(query.explain);
1527 assert_eq!(query.question_param, Some(1));
1528 assert!(query.strict);
1529
1530 let mut parser = make_parser("EXPLAIN SELECT 1");
1531 let err = parser
1532 .parse_explain_ask_query()
1533 .expect_err("missing ASK should fail");
1534 assert!(err.to_string().contains("expected"));
1535
1536 let mut parser = make_parser("ASK 'q' STRICT MAYBE");
1537 let err = parser
1538 .parse_ask_query()
1539 .expect_err("bad strict should fail");
1540 assert!(err.to_string().contains("Expected ON or OFF"));
1541
1542 let mut parser = make_parser("ASK 'q' CACHE TTL '10m' NOCACHE");
1543 let err = parser
1544 .parse_ask_query()
1545 .expect_err("duplicate cache should fail");
1546 assert!(err.to_string().contains("cache clause"));
1547
1548 let mut parser = make_parser("ASK 'q' CACHE FOREVER '10m'");
1549 let err = parser
1550 .parse_ask_query()
1551 .expect_err("bad cache ttl keyword should fail");
1552 assert!(err.to_string().contains("Expected TTL"));
1553 }
1554
1555 #[test]
1556 fn literal_value_special_constructors_arrays_and_objects() {
1557 let mut parser = make_parser("PASSWORD('pw')");
1558 assert!(matches!(
1559 parser.parse_literal_value().expect("password"),
1560 Value::Password(secret) if secret == "@@plain@@pw"
1561 ));
1562
1563 let mut parser = make_parser("SECRET('pw')");
1564 assert!(matches!(
1565 parser.parse_literal_value().expect("secret"),
1566 Value::Secret(bytes) if bytes == b"@@plain@@pw"
1567 ));
1568
1569 let mut parser = make_parser("SECRET_REF(vault, red.vault.api_key)");
1570 let value = parser.parse_literal_value().expect("secret ref");
1571 assert!(matches!(value, Value::Json(_)));
1572
1573 let mut parser = make_parser("[1, 2.5]");
1577 assert!(matches!(
1578 parser.parse_literal_value().expect("array"),
1579 Value::Array(items)
1580 if items == vec![Value::Integer(1), Value::Float(2.5)]
1581 ));
1582
1583 let mut parser = make_parser("['a', 2]");
1584 assert!(matches!(
1585 parser.parse_literal_value().expect("mixed array"),
1586 Value::Array(items)
1587 if items == vec![Value::Text("a".into()), Value::Integer(2)]
1588 ));
1589
1590 let mut parser = make_parser("[1, 2, 9007199254740993]");
1593 assert!(matches!(
1594 parser.parse_literal_value().expect("lossless big-int array"),
1595 Value::Array(items)
1596 if items
1597 == vec![
1598 Value::Integer(1),
1599 Value::Integer(2),
1600 Value::Integer(9007199254740993),
1601 ]
1602 ));
1603
1604 let mut parser = make_parser("{level = 'info', count: 2}");
1605 assert!(matches!(
1606 parser.parse_literal_value().expect("json object"),
1607 Value::Json(_)
1608 ));
1609 }
1610
1611 #[test]
1612 fn literal_value_rejects_invalid_secret_ref_and_scalar_start() {
1613 let mut parser = make_parser("SECRET_REF(config, red.vault.api_key)");
1614 let err = parser
1615 .parse_literal_value()
1616 .expect_err("non-vault secret ref should fail");
1617 assert!(err.to_string().contains("expected"));
1618
1619 let mut parser = make_parser("ORDER");
1620 let err = parser
1621 .parse_literal_value()
1622 .expect_err("non literal should fail");
1623 assert!(err.to_string().contains("expected"));
1624 }
1625
1626 #[test]
1627 fn json_depth_check_rejects_deep_literals() {
1628 let mut deep = reddb_types::utils::json::JsonValue::Array(vec![]);
1629 for _ in 0..JSON_LITERAL_MAX_DEPTH {
1630 deep = reddb_types::utils::json::JsonValue::Array(vec![deep]);
1631 }
1632 let err = json_literal_depth_check(&deep).expect_err("depth should fail");
1633 assert!(err.contains("JSON_LITERAL_MAX_DEPTH"));
1634 }
1635}