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)? {
317 self.expect_ident()?
318 } else {
319 String::new()
320 };
321 let model = if self.consume_ident_ci("MODEL")? {
322 Some(self.parse_string()?)
323 } else {
324 None
325 };
326 auto_embed = Some(crate::ast::AutoEmbedConfig {
327 fields,
328 provider,
329 model,
330 });
331 } else {
332 return Err(ParseError::expected(
333 vec!["TTL", "EXPIRES AT", "METADATA", "AUTO EMBED"],
334 self.peek(),
335 self.position(),
336 ));
337 }
338 }
339
340 Ok((ttl_ms, expires_at_ms, with_metadata, auto_embed))
341 }
342
343 fn expect_ident_ci(&mut self, expected: &str) -> Result<(), ParseError> {
345 if self.consume_ident_ci(expected)? {
346 Ok(())
347 } else {
348 Err(ParseError::expected(
349 vec![expected],
350 self.peek(),
351 self.position(),
352 ))
353 }
354 }
355
356 fn parse_expires_at_value(&mut self) -> Result<u64, ParseError> {
358 if let Ok(value) = self.parse_integer() {
360 return Ok(value as u64);
361 }
362 if let Ok(text) = self.parse_string() {
364 let trimmed = text.trim();
366 if let Ok(ts) = trimmed.parse::<u64>() {
367 return Ok(ts);
368 }
369 return Err(ParseError::new(
371 format!("EXPIRES AT requires a unix timestamp in milliseconds, got {trimmed:?}"),
375 self.position(),
376 ));
377 }
378 Err(ParseError::expected(
379 vec!["timestamp (unix ms) or 'YYYY-MM-DD'"],
380 self.peek(),
381 self.position(),
382 ))
383 }
384
385 fn parse_with_metadata_pairs(&mut self) -> Result<Vec<(String, Value)>, ParseError> {
387 self.expect(Token::LParen)?;
388 let mut pairs = Vec::new();
389 if !self.check(&Token::RParen) {
390 loop {
391 let key = self.expect_ident_or_keyword()?.to_ascii_lowercase();
392 self.expect(Token::Eq)?;
393 let value = self.parse_literal_value()?;
394 pairs.push((key, value));
395 if !self.consume(&Token::Comma)? {
396 break;
397 }
398 }
399 }
400 self.expect(Token::RParen)?;
401 Ok(pairs)
402 }
403
404 pub fn parse_update_query(&mut self) -> Result<QueryExpr, ParseError> {
406 self.expect(Token::Update)?;
407 let table = self.expect_ident()?;
408 let target = self.parse_update_target()?;
409 self.expect(Token::Set)?;
410
411 let mut assignments = Vec::new();
412 let mut assignment_exprs = Vec::new();
413 let mut compound_assignment_ops = Vec::new();
414 loop {
415 let col = self.parse_update_assignment_target()?;
416 let compound_op = if self.consume(&Token::Eq)? {
417 None
418 } else {
419 let op = match self.peek() {
420 Token::Plus => BinOp::Add,
421 Token::Dash | Token::Minus => BinOp::Sub,
422 Token::Star => BinOp::Mul,
423 Token::Slash => BinOp::Div,
424 Token::Percent => BinOp::Mod,
425 _ => {
426 return Err(ParseError::expected(
427 vec!["=", "+=", "-=", "*=", "/=", "%="],
428 self.peek(),
429 self.position(),
430 ));
431 }
432 };
433 self.advance()?;
434 self.expect(Token::Eq)?;
435 Some(op)
436 };
437 let expr = self.parse_expr()?;
438 let folded = fold_expr_to_value(expr.clone()).ok();
439 assignment_exprs.push((col.clone(), expr));
440 compound_assignment_ops.push(compound_op);
441 if compound_op.is_none() {
442 if let Some(val) = folded {
443 assignments.push((col.clone(), val));
444 }
445 }
446 if !self.consume(&Token::Comma)? {
447 break;
448 }
449 }
450
451 let filter = if self.consume(&Token::Where)? {
452 Some(self.parse_filter()?)
453 } else {
454 None
455 };
456 let where_expr = filter.as_ref().map(filter_to_expr);
457
458 let (ttl_ms, expires_at_ms, with_metadata, _auto_embed) = self.parse_with_clauses()?;
459
460 let (claim_limit, claim_exact) = if self.consume_ident_ci("CLAIM")? {
461 if self.consume_ident_ci("EXACT")? {
462 (Some(self.parse_integer()? as u64), true)
463 } else {
464 self.expect(Token::Limit)?;
465 (Some(self.parse_integer()? as u64), false)
466 }
467 } else {
468 (None, false)
469 };
470
471 let mut order_by = if self.consume(&Token::Order)? {
472 self.expect(Token::By)?;
473 let clauses = self.parse_order_by_list()?;
474 validate_update_order_by(&clauses, self.position())?;
475 clauses
476 } else {
477 Vec::new()
478 };
479
480 let limit = if self.consume(&Token::Limit)? {
485 Some(self.parse_integer()? as u64)
486 } else {
487 None
488 };
489 let effective_limit = limit.is_some() || claim_limit.is_some();
492 if !order_by.is_empty() && !effective_limit {
493 return Err(ParseError::new(
494 "UPDATE ORDER BY requires LIMIT",
495 self.position(),
496 ));
497 }
498 if claim_limit.is_some() && order_by.is_empty() {
499 return Err(ParseError::new(
500 "UPDATE CLAIM requires ORDER BY",
501 self.position(),
502 ));
503 }
504 if !order_by.is_empty() && !update_order_by_mentions_rid(&order_by) {
505 order_by.push(OrderByClause {
506 field: FieldRef::TableColumn {
507 table: String::new(),
508 column: "rid".to_string(),
509 },
510 expr: None,
511 ascending: true,
512 nulls_first: false,
513 });
514 }
515
516 let returning = self.parse_returning_clause()?;
517
518 let suppress_events = if self.consume_ident_ci("SUPPRESS")? {
519 self.expect_ident_ci("EVENTS")?;
520 true
521 } else {
522 false
523 };
524
525 Ok(QueryExpr::Update(UpdateQuery {
526 table,
527 target,
528 assignment_exprs,
529 compound_assignment_ops,
530 assignments,
531 where_expr,
532 filter,
533 ttl_ms,
534 expires_at_ms,
535 with_metadata,
536 returning,
537 claim_limit,
538 claim_exact,
539 order_by,
540 limit,
541 suppress_events,
542 }))
543 }
544
545 fn parse_update_assignment_target(&mut self) -> Result<String, ParseError> {
546 let mut segments = vec![self.expect_column_ident()?];
552 while self.consume(&Token::Dot)? {
553 segments.push(self.expect_column_ident()?);
554 }
555 Ok(segments.join("."))
556 }
557
558 fn parse_update_target(&mut self) -> Result<UpdateTarget, ParseError> {
559 if self.check(&Token::Kv) {
566 return Err(self.removed_update_marker_error("KV"));
567 }
568 if self.check(&Token::Rows) {
569 return Err(self.removed_update_marker_error("ROWS"));
570 }
571 if matches!(self.peek(), Token::Ident(name) if name.eq_ignore_ascii_case("DOCUMENTS")) {
572 return Err(self.removed_update_marker_error("DOCUMENTS"));
573 }
574 if self.consume_ident_ci("NODES")? {
575 return Ok(UpdateTarget::Nodes);
576 }
577 if self.consume_ident_ci("EDGES")? {
578 return Ok(UpdateTarget::Edges);
579 }
580 Ok(UpdateTarget::Rows)
581 }
582
583 fn removed_update_marker_error(&self, marker: &str) -> ParseError {
587 ParseError::new(
588 format!(
589 "the `{marker}` UPDATE model marker has been removed; the catalog \
590 already knows the collection's model — write `UPDATE <collection> \
591 SET …` with no marker. (Graph updates still name `NODES` or `EDGES`, \
592 since a graph collection holds both record kinds.)"
593 ),
594 self.position(),
595 )
596 }
597
598 pub fn parse_delete_query(&mut self) -> Result<QueryExpr, ParseError> {
600 self.expect(Token::Delete)?;
601 self.expect(Token::From)?;
602 let table = self.expect_ident()?;
603
604 let filter = if self.consume(&Token::Where)? {
605 Some(self.parse_filter()?)
606 } else {
607 None
608 };
609
610 let where_expr = filter.as_ref().map(filter_to_expr);
611
612 let returning = self.parse_returning_clause()?;
613
614 let suppress_events = if self.consume_ident_ci("SUPPRESS")? {
615 self.expect_ident_ci("EVENTS")?;
616 true
617 } else {
618 false
619 };
620
621 Ok(QueryExpr::Delete(DeleteQuery {
622 table,
623 where_expr,
624 filter,
625 returning,
626 suppress_events,
627 }))
628 }
629
630 fn parse_returning_clause(&mut self) -> Result<Option<Vec<ReturningItem>>, ParseError> {
634 if !self.consume(&Token::Returning)? {
635 return Ok(None);
636 }
637 if self.consume(&Token::Star)? {
638 return Ok(Some(vec![ReturningItem::All]));
639 }
640 let mut items = Vec::new();
641 loop {
642 if returning_expr_start(self.peek()) {
643 return Err(returning_expr_not_supported(self.position()));
644 }
645 let col = self.expect_update_returning_column()?;
646 items.push(ReturningItem::Column(col));
647 if returning_expr_tail(self.peek()) {
648 return Err(returning_expr_not_supported(self.position()));
649 }
650 if !self.consume(&Token::Comma)? {
651 break;
652 }
653 }
654 if items.is_empty() {
655 return Err(ParseError::expected(
656 vec!["*", "column name"],
657 self.peek(),
658 self.position(),
659 ));
660 }
661 Ok(Some(items))
662 }
663
664 fn expect_update_returning_column(&mut self) -> Result<String, ParseError> {
665 if self.consume(&Token::Weight)? {
666 return Ok("weight".to_string());
667 }
668 self.expect_ident_or_keyword()
669 }
670
671 pub fn parse_ask_query(&mut self) -> Result<QueryExpr, ParseError> {
674 self.parse_ask_query_with_explain(false)
675 }
676
677 pub fn parse_explain_ask_query(&mut self) -> Result<QueryExpr, ParseError> {
679 self.advance()?; if !matches!(self.peek(), Token::Ident(name) if name.eq_ignore_ascii_case("ASK")) {
681 return Err(ParseError::expected(
682 vec!["ASK"],
683 self.peek(),
684 self.position(),
685 ));
686 }
687 self.parse_ask_query_with_explain(true)
688 }
689
690 fn parse_ask_query_with_explain(&mut self, explain: bool) -> Result<QueryExpr, ParseError> {
691 self.advance()?; let (question, question_param) = match self.peek() {
694 Token::String(_) => (self.parse_string()?, None),
695 Token::Dollar | Token::Question => {
696 let index = self.parse_param_slot("ASK question")?;
697 (String::new(), Some(index))
698 }
699 other => {
700 return Err(ParseError::expected(
701 vec!["string", "$N", "?"],
702 other,
703 self.position(),
704 ));
705 }
706 };
707
708 let mut provider = None;
709 let mut model = None;
710 let mut depth = None;
711 let mut limit = None;
712 let mut min_score = None;
713 let mut collection = None;
714 let mut temperature = None;
715 let mut seed = None;
716 let mut strict = true;
717 let mut stream = false;
718 let mut cache = AskCacheClause::Default;
719 let mut plan_only = false;
720 let mut steps = None;
721
722 for _ in 0..15 {
725 if self.consume(&Token::Using)? {
726 provider = Some(match &self.current.token {
727 Token::String(_) => self.parse_string()?,
728 _ => self.expect_ident()?,
729 });
730 } else if self.consume_ident_ci("MODEL")? {
731 model = Some(self.parse_string()?);
732 } else if self.consume(&Token::Depth)? {
733 depth = Some(self.parse_integer()? as usize);
734 } else if self.consume(&Token::Limit)? {
735 limit = Some(self.parse_integer()? as usize);
736 } else if self.consume(&Token::MinScore)? {
737 min_score = Some(self.parse_float()? as f32);
738 } else if self.consume(&Token::Collection)? {
739 collection = Some(self.expect_ident()?);
740 } else if self.consume_ident_ci("TEMPERATURE")? {
741 temperature = Some(self.parse_float()? as f32);
742 } else if self.consume_ident_ci("SEED")? {
743 seed = Some(self.parse_integer()? as u64);
744 } else if self.consume_ident_ci("STRICT")? {
745 let value = self.expect_ident_or_keyword()?;
746 if value.eq_ignore_ascii_case("ON") {
747 strict = true;
748 } else if value.eq_ignore_ascii_case("OFF") {
749 strict = false;
750 } else {
751 return Err(ParseError::new(
752 "Expected ON or OFF after STRICT",
753 self.position(),
754 ));
755 }
756 } else if self.consume_ident_ci("STREAM")? {
757 stream = true;
758 } else if self.consume_ident_ci("CACHE")? {
759 if !matches!(cache, AskCacheClause::Default) {
760 return Err(ParseError::new(
761 "ASK cache clause specified more than once",
762 self.position(),
763 ));
764 }
765 let ttl = self.expect_ident_or_keyword()?;
766 if !ttl.eq_ignore_ascii_case("TTL") {
767 return Err(ParseError::new("Expected TTL after CACHE", self.position()));
768 }
769 cache = AskCacheClause::CacheTtl(self.parse_string()?);
770 } else if self.consume_ident_ci("NOCACHE")? {
771 if !matches!(cache, AskCacheClause::Default) {
772 return Err(ParseError::new(
773 "ASK cache clause specified more than once",
774 self.position(),
775 ));
776 }
777 cache = AskCacheClause::NoCache;
778 } else if self.consume(&Token::As)? {
779 return Err(ParseError::new(
783 "ASK ... AS RQL was removed: use `ASK '<question>' PLAN` to return the \
784 typed plan and candidate query without executing",
785 self.position(),
786 ));
787 } else if self.consume_ident_ci("EXECUTE")? {
788 return Err(ParseError::new(
792 "ASK ... EXECUTE was removed: read-only candidates auto-execute by default; \
793 use `ASK '<question>' PLAN` to inspect the query without executing",
794 self.position(),
795 ));
796 } else if self.consume_ident_ci("PLAN")? {
797 if plan_only {
798 return Err(ParseError::new(
799 "ASK PLAN specified more than once",
800 self.position(),
801 ));
802 }
803 plan_only = true;
804 } else if self.consume_ident_ci("STEPS")? {
805 if steps.is_some() {
806 return Err(ParseError::new(
807 "ASK STEPS specified more than once",
808 self.position(),
809 ));
810 }
811 let n = self.parse_integer()?;
812 if n < 1 {
813 return Err(ParseError::new(
814 "ASK STEPS must be a positive integer",
815 self.position(),
816 ));
817 }
818 steps = Some(n as usize);
819 } else {
820 break;
821 }
822 }
823
824 Ok(QueryExpr::Ask(AskQuery {
825 explain,
826 question,
827 question_param,
828 provider,
829 model,
830 depth,
831 limit,
832 min_score,
833 collection,
834 temperature,
835 seed,
836 strict,
837 stream,
838 cache,
839 plan_only,
840 steps,
841 }))
842 }
843
844 fn parse_ident_list(&mut self) -> Result<Vec<String>, ParseError> {
846 let mut idents = Vec::new();
847 loop {
848 idents.push(self.expect_ident_or_keyword()?);
849 if !self.consume(&Token::Comma)? {
850 break;
851 }
852 }
853 Ok(idents)
854 }
855
856 fn parse_dml_value_list(&mut self) -> Result<Vec<Value>, ParseError> {
858 self.parse_dml_expr_list()?
859 .into_iter()
860 .map(fold_expr_to_value)
861 .collect::<Result<Vec<_>, _>>()
862 .map_err(|msg| ParseError::new(msg, self.position()))
863 }
864
865 fn parse_dml_expr_list(&mut self) -> Result<Vec<Expr>, ParseError> {
866 let mut values = Vec::new();
867 loop {
868 values.push(self.parse_expr()?);
869 if !self.consume(&Token::Comma)? {
870 break;
871 }
872 }
873 Ok(values)
874 }
875
876 pub(crate) fn parse_literal_value(&mut self) -> Result<Value, ParseError> {
878 self.enter_depth()?;
887 let result = self.parse_literal_value_inner();
888 self.exit_depth();
889 result
890 }
891
892 fn parse_literal_value_inner(&mut self) -> Result<Value, ParseError> {
893 if let Token::Ident(name) = self.peek().clone() {
900 let upper = name.to_uppercase();
901 if upper == "PASSWORD" || upper == "SECRET" {
902 self.advance()?; self.expect(Token::LParen)?;
904 let plaintext = self.parse_string()?;
905 self.expect(Token::RParen)?;
906 return Ok(match upper.as_str() {
907 "PASSWORD" => Value::Password(format!("@@plain@@{plaintext}")),
908 "SECRET" => Value::Secret(format!("@@plain@@{plaintext}").into_bytes()),
909 _ => unreachable!(),
910 });
911 }
912 if upper == "SECRET_REF" {
913 self.advance()?; self.expect(Token::LParen)?;
915 let store = self.expect_ident_or_keyword()?.to_ascii_lowercase();
916 if store != "vault" {
917 return Err(ParseError::expected(
918 vec!["vault"],
919 self.peek(),
920 self.position(),
921 ));
922 }
923 self.expect(Token::Comma)?;
924 let (collection, key) =
925 self.parse_kv_key(reddb_types::catalog::CollectionModel::Vault)?;
926 self.expect(Token::RParen)?;
927 return Ok(secret_ref_value(&store, &collection, &key));
928 }
929 }
930
931 match self.peek().clone() {
932 Token::String(s) => {
933 let s = s.clone();
934 self.advance()?;
935 Ok(Value::text(s))
936 }
937 Token::JsonLiteral(raw) => {
938 self.advance()?;
943 let json_value = reddb_types::utils::json::parse_json(&raw).map_err(|err| {
944 ParseError::new(
945 format!("invalid JSON object literal: {:?}", err.to_string()),
951 self.position(),
952 )
953 })?;
954 json_literal_depth_check(&json_value)
955 .map_err(|err| ParseError::new(err, self.position()))?;
956 let canonical = reddb_types::serde_json::Value::from(json_value);
957 let bytes = reddb_types::json::to_vec(&canonical).map_err(|err| {
958 ParseError::new(
959 format!("failed to encode JSON literal: {:?}", err.to_string()),
963 self.position(),
964 )
965 })?;
966 Ok(Value::Json(bytes))
967 }
968 Token::Integer(n) => {
969 self.advance()?;
970 Ok(Value::Integer(n))
971 }
972 Token::Float(n) => {
973 self.advance()?;
974 Ok(Value::Float(n))
975 }
976 Token::True => {
977 self.advance()?;
978 Ok(Value::Boolean(true))
979 }
980 Token::False => {
981 self.advance()?;
982 Ok(Value::Boolean(false))
983 }
984 Token::Null => {
985 self.advance()?;
986 Ok(Value::Null)
987 }
988 Token::LBracket => {
989 self.advance()?; let mut items = Vec::new();
1001 if !self.check(&Token::RBracket) {
1002 loop {
1003 items.push(self.parse_literal_value()?);
1004 if !self.consume(&Token::Comma)? {
1005 break;
1006 }
1007 }
1008 }
1009 self.expect(Token::RBracket)?;
1010 Ok(Value::Array(items))
1011 }
1012 Token::LBrace => {
1013 self.advance()?; let mut map = reddb_types::json::Map::new();
1016 if !self.check(&Token::RBrace) {
1017 loop {
1018 let key = match self.peek().clone() {
1025 Token::String(s) => {
1026 self.advance()?;
1027 s
1028 }
1029 Token::Ident(s) => {
1030 self.advance()?;
1031 s
1032 }
1033 _ => self.expect_ident_or_keyword()?.to_ascii_lowercase(),
1034 };
1035 if !self.consume(&Token::Colon)? {
1037 self.expect(Token::Eq)?;
1038 }
1039 let val = self.parse_literal_value()?;
1041 map.insert(key, literal_value_to_json(&val));
1042 if !self.consume(&Token::Comma)? {
1043 break;
1044 }
1045 }
1046 }
1047 self.expect(Token::RBrace)?;
1048 let json_val = reddb_types::json::Value::Object(map);
1049 let bytes = reddb_types::json::to_vec(&json_val).unwrap_or_default();
1050 Ok(Value::Json(bytes))
1051 }
1052 ref other => Err(ParseError::expected(
1053 vec!["string", "number", "true", "false", "null", "[", "{"],
1054 other,
1055 self.position(),
1056 )),
1057 }
1058 }
1059}
1060
1061fn is_legacy_ttl_column(column: &str) -> bool {
1066 column.eq_ignore_ascii_case("_ttl")
1067 || column.eq_ignore_ascii_case("_ttl_ms")
1068 || column.eq_ignore_ascii_case("_expires_at")
1069}
1070
1071fn literal_value_to_json(val: &Value) -> reddb_types::json::Value {
1079 match val {
1080 Value::Null => reddb_types::json::Value::Null,
1081 Value::Boolean(b) => reddb_types::json::Value::Bool(*b),
1082 Value::Integer(i) => reddb_types::json::Value::Number(*i as f64),
1083 Value::Float(f) => reddb_types::json::Value::Number(*f),
1084 Value::Text(s) => reddb_types::json::Value::String(s.to_string()),
1085 Value::Json(bytes) => {
1086 reddb_types::json::from_slice(bytes).unwrap_or(reddb_types::json::Value::Null)
1087 }
1088 Value::Array(items) => {
1089 reddb_types::json::Value::Array(items.iter().map(literal_value_to_json).collect())
1090 }
1091 _ => reddb_types::json::Value::Null,
1092 }
1093}
1094
1095fn returning_expr_start(token: &Token) -> bool {
1096 matches!(
1097 token,
1098 Token::Integer(_)
1099 | Token::Float(_)
1100 | Token::String(_)
1101 | Token::JsonLiteral(_)
1102 | Token::Null
1103 | Token::True
1104 | Token::False
1105 | Token::LParen
1106 | Token::Minus
1107 | Token::Question
1108 | Token::Dollar
1109 )
1110}
1111
1112fn returning_expr_tail(token: &Token) -> bool {
1113 matches!(
1114 token,
1115 Token::LParen
1116 | Token::Plus
1117 | Token::Minus
1118 | Token::Star
1119 | Token::Slash
1120 | Token::Percent
1121 | Token::DoublePipe
1122 | Token::Pipe
1123 | Token::Eq
1124 | Token::Ne
1125 | Token::Lt
1126 | Token::Le
1127 | Token::Gt
1128 | Token::Ge
1129 | Token::Dot
1130 | Token::Colon
1131 )
1132}
1133
1134fn validate_update_order_by(
1135 clauses: &[OrderByClause],
1136 position: crate::lexer::Position,
1137) -> Result<(), ParseError> {
1138 for clause in clauses {
1139 if clause.expr.is_some() {
1140 return Err(ParseError::new(
1141 "UPDATE ORDER BY only supports top-level fields",
1142 position,
1143 ));
1144 }
1145 match &clause.field {
1146 FieldRef::TableColumn { table, column }
1147 if table.is_empty() && !column.contains('.') => {}
1148 _ => {
1149 return Err(ParseError::new(
1150 "UPDATE ORDER BY only supports top-level fields",
1151 position,
1152 ));
1153 }
1154 }
1155 }
1156 Ok(())
1157}
1158
1159fn update_order_by_mentions_rid(clauses: &[OrderByClause]) -> bool {
1160 clauses.iter().any(|clause| {
1161 matches!(
1162 &clause.field,
1163 FieldRef::TableColumn { table, column }
1164 if table.is_empty() && column.eq_ignore_ascii_case("rid")
1165 )
1166 })
1167}
1168
1169fn returning_expr_not_supported(position: crate::lexer::Position) -> ParseError {
1170 ParseError::new(
1171 "NOT_YET_SUPPORTED: RETURNING expressions are not supported yet; use RETURNING * or named columns. Track a follow-up issue for RETURNING <expr>.",
1172 position,
1173 )
1174}
1175
1176fn secret_ref_value(store: &str, collection: &str, key: &str) -> Value {
1177 let mut map = reddb_types::json::Map::new();
1178 map.insert(
1179 "type".to_string(),
1180 reddb_types::json::Value::String("secret_ref".to_string()),
1181 );
1182 map.insert(
1183 "store".to_string(),
1184 reddb_types::json::Value::String(store.to_string()),
1185 );
1186 map.insert(
1187 "collection".to_string(),
1188 reddb_types::json::Value::String(collection.to_string()),
1189 );
1190 map.insert(
1191 "key".to_string(),
1192 reddb_types::json::Value::String(key.to_string()),
1193 );
1194 Value::Json(
1195 reddb_types::json::to_vec(&reddb_types::json::Value::Object(map)).unwrap_or_default(),
1196 )
1197}
1198
1199#[cfg(test)]
1200mod tests {
1201 use super::*;
1202 use crate::ast::{InsertEntityType, ReturningItem, UpdateTarget};
1203
1204 fn make_parser(input: &str) -> Parser<'_> {
1205 Parser::new(input).expect("lexer")
1206 }
1207
1208 fn insert(input: &str) -> InsertQuery {
1209 let mut parser = make_parser(input);
1210 let QueryExpr::Insert(query) = parser.parse_insert_query().expect("insert") else {
1211 panic!("expected insert query");
1212 };
1213 query
1214 }
1215
1216 fn update(input: &str) -> UpdateQuery {
1217 let mut parser = make_parser(input);
1218 let QueryExpr::Update(query) = parser.parse_update_query().expect("update") else {
1219 panic!("expected update query");
1220 };
1221 query
1222 }
1223
1224 fn delete(input: &str) -> DeleteQuery {
1225 let mut parser = make_parser(input);
1226 let QueryExpr::Delete(query) = parser.parse_delete_query().expect("delete") else {
1227 panic!("expected delete query");
1228 };
1229 query
1230 }
1231
1232 fn ask(input: &str) -> AskQuery {
1233 let mut parser = make_parser(input);
1234 let QueryExpr::Ask(query) = parser.parse_ask_query().expect("ask") else {
1235 panic!("expected ask query");
1236 };
1237 query
1238 }
1239
1240 #[test]
1241 fn insert_entity_types_with_options_returning_and_suppress_events() {
1242 let cases = [
1243 (
1244 "INSERT INTO items NODE (id) VALUES (1)",
1245 InsertEntityType::Node,
1246 ),
1247 (
1248 "INSERT INTO items EDGE (id) VALUES (1)",
1249 InsertEntityType::Edge,
1250 ),
1251 (
1252 "INSERT INTO items VECTOR (id) VALUES (1)",
1253 InsertEntityType::Vector,
1254 ),
1255 (
1256 "INSERT INTO items DOCUMENT VALUES ({\"id\": 1})",
1257 InsertEntityType::Document,
1258 ),
1259 ("INSERT INTO items KV (id) VALUES (1)", InsertEntityType::Kv),
1260 ];
1261 for (input, expected) in cases {
1262 assert_eq!(insert(input).entity_type, expected, "{input}");
1263 }
1264
1265 let query = insert(
1266 "INSERT INTO docs (id, body) VALUES (1, 'red'), (2, ?) \
1267 WITH TTL 2 h WITH EXPIRES AT 999 \
1268 WITH METADATA (source = 'test', score = 3) \
1269 WITH AUTO EMBED (body, title) USING openai MODEL 'text-embedding-3-small' \
1270 RETURNING * SUPPRESS EVENTS",
1271 );
1272 assert_eq!(query.table, "docs");
1273 assert_eq!(query.entity_type, InsertEntityType::Row);
1274 assert_eq!(query.columns, vec!["id", "body"]);
1275 assert_eq!(query.values.len(), 2);
1276 assert_eq!(query.value_exprs.len(), 2);
1277 assert_eq!(query.ttl_ms, Some(7_200_000));
1278 assert_eq!(query.expires_at_ms, Some(999));
1279 assert_eq!(query.with_metadata.len(), 2);
1280 assert_eq!(
1281 query.returning.as_deref(),
1282 Some([ReturningItem::All].as_slice())
1283 );
1284 let auto_embed = query.auto_embed.expect("auto embed");
1285 assert_eq!(auto_embed.fields, vec!["body", "title"]);
1286 assert_eq!(auto_embed.provider, "openai");
1287 assert_eq!(auto_embed.model.as_deref(), Some("text-embedding-3-small"));
1288 assert!(query.suppress_events);
1289 }
1290
1291 #[test]
1292 fn insert_rejects_metric_and_bad_with_clause() {
1293 let mut parser = make_parser("INSERT INTO METRIC cpu.usage (value) VALUES (1)");
1294 let err = parser
1295 .parse_insert_query()
1296 .expect_err("metric insert should fail");
1297 assert!(err.to_string().contains("INSERT INTO METRIC"));
1298
1299 let mut parser = make_parser("INSERT INTO docs (id) VALUES (1) WITH TTL 1 fortnight");
1300 let err = parser
1301 .parse_insert_query()
1302 .expect_err("bad ttl unit should fail");
1303 assert!(err.to_string().contains("unsupported TTL unit"));
1304
1305 let mut parser = make_parser("INSERT INTO docs (id) VALUES (1) WITH UNKNOWN");
1306 let err = parser
1307 .parse_insert_query()
1308 .expect_err("bad WITH should fail");
1309 assert!(err.to_string().contains("expected"));
1310 }
1311
1312 #[test]
1313 fn document_insert_canonical_bare_values_form() {
1314 let query = insert("INSERT INTO events DOCUMENT VALUES ({\"level\": \"info\"})");
1316 assert_eq!(query.table, "events");
1317 assert_eq!(query.entity_type, InsertEntityType::Document);
1318 assert_eq!(query.columns, vec!["body"]);
1319 assert_eq!(query.values.len(), 1);
1320 assert!(matches!(query.values[0][0], Value::Json(_)));
1321
1322 let query = insert(
1324 "INSERT INTO events DOCUMENT VALUES ({\"a\": 1}), ({\"b\": 2}) \
1325 WITH TTL 30 s WITH METADATA (source = 'test') RETURNING *",
1326 );
1327 assert_eq!(query.columns, vec!["body"]);
1328 assert_eq!(query.values.len(), 2);
1329 assert_eq!(query.ttl_ms, Some(30_000));
1330 assert_eq!(query.with_metadata.len(), 1);
1331 assert_eq!(
1332 query.returning.as_deref(),
1333 Some([ReturningItem::All].as_slice())
1334 );
1335 }
1336
1337 #[test]
1338 fn unmarked_bare_values_insert_parses_with_empty_columns() {
1339 let query = insert("INSERT INTO events VALUES ({\"level\": \"info\"})");
1343 assert_eq!(query.table, "events");
1344 assert_eq!(query.entity_type, InsertEntityType::Row);
1345 assert!(query.columns.is_empty());
1346 assert_eq!(query.values.len(), 1);
1347 assert!(matches!(query.values[0][0], Value::Json(_)));
1348
1349 let query = insert("INSERT INTO events VALUES ({\"a\": 1}), ({\"b\": 2})");
1351 assert!(query.columns.is_empty());
1352 assert_eq!(query.entity_type, InsertEntityType::Row);
1353 assert_eq!(query.values.len(), 2);
1354
1355 let query = insert("INSERT INTO t (id) VALUES (1)");
1357 assert_eq!(query.columns, vec!["id"]);
1358 }
1359
1360 #[test]
1361 fn document_insert_rejects_removed_forms() {
1362 let mut parser =
1364 make_parser("INSERT INTO events DOCUMENT (body) VALUES ({\"level\": \"info\"})");
1365 let err = parser
1366 .parse_insert_query()
1367 .expect_err("document column list should fail");
1368 assert!(err.to_string().contains("column list is removed"), "{err}");
1369
1370 let mut parser =
1372 make_parser("INSERT INTO events DOCUMENT (body, _ttl) VALUES ({\"a\": 1}, 30)");
1373 let err = parser
1374 .parse_insert_query()
1375 .expect_err("legacy _ttl column should fail");
1376 assert!(err.to_string().contains("WITH TTL"), "{err}");
1377
1378 let mut parser =
1380 make_parser("INSERT INTO events DOCUMENT VALUES ('{\"level\": \"info\"}')");
1381 let err = parser
1382 .parse_insert_query()
1383 .expect_err("quoted-string body should fail");
1384 let msg = err.to_string();
1385 assert!(msg.contains("inline JSON literal"), "{msg}");
1386 assert!(msg.contains("JSON_PARSE"), "{msg}");
1387 }
1388
1389 #[test]
1390 fn update_targets_compound_assignments_order_limit_returning() {
1391 let cases = [
1395 ("UPDATE docs SET count = 1", UpdateTarget::Rows),
1396 ("UPDATE docs NODES SET count = 1", UpdateTarget::Nodes),
1397 ("UPDATE docs EDGES SET count = 1", UpdateTarget::Edges),
1398 ];
1399 for (input, expected) in cases {
1400 assert_eq!(update(input).target, expected, "{input}");
1401 }
1402
1403 for marker in ["DOCUMENTS", "ROWS", "KV"] {
1405 let input = format!("UPDATE docs {marker} SET count = 1");
1406 let mut parser = make_parser(&input);
1407 let err = parser
1408 .parse_update_query()
1409 .expect_err("removed update marker should fail");
1410 let msg = err.to_string();
1411 assert!(msg.contains(marker), "{msg}");
1412 assert!(msg.contains("has been removed"), "{msg}");
1413 assert!(msg.contains("with no marker"), "{msg}");
1414 }
1415
1416 let query = update(
1417 "UPDATE docs SET count += 2, title = UPPER(title) \
1418 WHERE id = 1 WITH TTL 30 s WITH METADATA (source = 'update') \
1419 ORDER BY updated_at DESC LIMIT 5 RETURNING weight, title SUPPRESS EVENTS",
1420 );
1421 assert_eq!(query.table, "docs");
1422 assert_eq!(query.target, UpdateTarget::Rows);
1423 assert_eq!(query.assignment_exprs.len(), 2);
1424 assert_eq!(query.compound_assignment_ops, vec![Some(BinOp::Add), None]);
1425 assert_eq!(query.assignments.len(), 0);
1426 assert!(query.filter.is_some());
1427 assert!(query.where_expr.is_some());
1428 assert_eq!(query.ttl_ms, Some(30_000));
1429 assert_eq!(query.with_metadata.len(), 1);
1430 assert_eq!(query.claim_limit, None);
1431 assert!(!query.claim_exact);
1432 assert_eq!(query.limit, Some(5));
1433 assert_eq!(query.order_by.len(), 2);
1434 assert!(matches!(
1435 &query.order_by[1].field,
1436 FieldRef::TableColumn { column, .. } if column == "rid"
1437 ));
1438 assert_eq!(
1439 query.returning.as_deref(),
1440 Some(
1441 [
1442 ReturningItem::Column("weight".to_string()),
1443 ReturningItem::Column("title".to_string())
1444 ]
1445 .as_slice()
1446 )
1447 );
1448 assert!(query.suppress_events);
1449 }
1450
1451 #[test]
1452 fn update_dotted_targets_parse_unmarked_for_every_target() {
1453 let unmarked = update("UPDATE docs SET profile.address.city = 'Lisbon' WHERE name = 'ada'");
1457 assert_eq!(unmarked.target, UpdateTarget::Rows);
1458 assert_eq!(unmarked.assignment_exprs[0].0, "profile.address.city");
1459 assert_eq!(unmarked.assignments[0].0, "profile.address.city");
1460
1461 let graph = update("UPDATE social NODES SET meta.seen_at = 1");
1462 assert_eq!(graph.target, UpdateTarget::Nodes);
1463 assert_eq!(graph.assignment_exprs[0].0, "meta.seen_at");
1464 }
1465
1466 #[test]
1467 fn update_claim_limit_requires_order_by() {
1468 let query = update(
1469 "UPDATE docs SET status = 'reserved' WHERE status = 'available' \
1470 CLAIM LIMIT 2 ORDER BY priority ASC RETURNING id",
1471 );
1472 assert_eq!(query.claim_limit, Some(2));
1473 assert!(!query.claim_exact);
1474 assert_eq!(query.limit, None);
1475 assert_eq!(query.order_by.len(), 2);
1476 assert!(matches!(
1477 &query.order_by[1].field,
1478 FieldRef::TableColumn { column, .. } if column == "rid"
1479 ));
1480
1481 let query = update(
1482 "UPDATE docs SET status = 'reserved' WHERE status = 'available' \
1483 CLAIM EXACT 2 ORDER BY priority ASC RETURNING id",
1484 );
1485 assert_eq!(query.claim_limit, Some(2));
1486 assert!(query.claim_exact);
1487 assert_eq!(query.limit, None);
1488 assert_eq!(query.order_by.len(), 2);
1489 assert!(matches!(
1490 &query.order_by[1].field,
1491 FieldRef::TableColumn { column, .. } if column == "rid"
1492 ));
1493
1494 let mut parser = make_parser("UPDATE docs SET status = 'reserved' CLAIM LIMIT 1");
1495 let err = parser
1496 .parse_update_query()
1497 .expect_err("claim without order should fail");
1498 assert!(err.to_string().contains("CLAIM requires ORDER BY"));
1499 }
1500
1501 #[test]
1502 fn update_rejects_invalid_assignment_and_order_by_forms() {
1503 let mut parser = make_parser("UPDATE docs SET count ^= 1");
1504 let err = parser
1505 .parse_update_query()
1506 .expect_err("unknown compound assignment should fail");
1507 assert!(err.to_string().contains("expected"));
1508
1509 let mut parser = make_parser("UPDATE docs SET count = 1 ORDER BY updated_at");
1510 let err = parser
1511 .parse_update_query()
1512 .expect_err("ORDER BY without LIMIT should fail");
1513 assert!(err.to_string().contains("requires LIMIT"));
1514
1515 let mut parser = make_parser("UPDATE docs SET count = 1 ORDER BY updated_at + 1 LIMIT 1");
1516 let err = parser
1517 .parse_update_query()
1518 .expect_err("ORDER BY expression should fail");
1519 assert!(err.to_string().contains("top-level fields"));
1520 }
1521
1522 #[test]
1523 fn delete_returning_and_suppress_events() {
1524 let query = delete("DELETE FROM docs WHERE id = 1 RETURNING id, title SUPPRESS EVENTS");
1525 assert_eq!(query.table, "docs");
1526 assert!(query.filter.is_some());
1527 assert!(query.where_expr.is_some());
1528 assert_eq!(
1529 query.returning.as_deref(),
1530 Some(
1531 [
1532 ReturningItem::Column("id".to_string()),
1533 ReturningItem::Column("title".to_string())
1534 ]
1535 .as_slice()
1536 )
1537 );
1538 assert!(query.suppress_events);
1539
1540 let query = delete("DELETE FROM docs RETURNING *");
1541 assert_eq!(
1542 query.returning.as_deref(),
1543 Some([ReturningItem::All].as_slice())
1544 );
1545 }
1546
1547 #[test]
1548 fn returning_rejects_expression_forms() {
1549 for input in [
1550 "DELETE FROM docs RETURNING 1",
1551 "DELETE FROM docs RETURNING UPPER(title)",
1552 "DELETE FROM docs RETURNING title || body",
1553 ] {
1554 let mut parser = make_parser(input);
1555 let err = parser
1556 .parse_delete_query()
1557 .expect_err("RETURNING expression should fail");
1558 assert!(err.to_string().contains("RETURNING expressions"));
1559 }
1560 }
1561
1562 #[test]
1563 fn ask_parses_all_optional_clauses_and_cache_modes() {
1564 let query = ask(
1565 "ASK 'what changed?' USING 'openai' MODEL 'gpt' DEPTH 3 LIMIT 4 \
1566 MIN_SCORE 0.7 COLLECTION docs TEMPERATURE 0.2 SEED 42 STRICT OFF \
1567 STREAM CACHE TTL '10m'",
1568 );
1569 assert_eq!(query.question, "what changed?");
1570 assert_eq!(query.provider.as_deref(), Some("openai"));
1571 assert_eq!(query.model.as_deref(), Some("gpt"));
1572 assert_eq!(query.depth, Some(3));
1573 assert_eq!(query.limit, Some(4));
1574 assert_eq!(query.min_score, Some(0.7));
1575 assert_eq!(query.collection.as_deref(), Some("docs"));
1576 assert_eq!(query.temperature, Some(0.2));
1577 assert_eq!(query.seed, Some(42));
1578 assert!(!query.strict);
1579 assert!(query.stream);
1580 assert_eq!(query.cache, AskCacheClause::CacheTtl("10m".to_string()));
1581
1582 let query = ask("ASK ? NOCACHE");
1583 assert_eq!(query.question, "");
1584 assert_eq!(query.question_param, Some(0));
1585 assert_eq!(query.cache, AskCacheClause::NoCache);
1586 }
1587
1588 #[test]
1589 fn ask_parses_steps_budget_clause() {
1590 assert_eq!(ask("ASK 'q'").steps, None);
1592
1593 let query = ask("ASK 'q' STEPS 2 STRICT OFF");
1596 assert_eq!(query.steps, Some(2));
1597
1598 let query = ask("ASK 'q' DEPTH 3 STEPS 5 LIMIT 4");
1600 assert_eq!(query.steps, Some(5));
1601 assert_eq!(query.depth, Some(3));
1602 assert_eq!(query.limit, Some(4));
1603 }
1604
1605 #[test]
1606 fn ask_steps_clause_error_paths() {
1607 let mut parser = make_parser("ASK 'q' STEPS 0");
1608 let err = parser.parse_ask_query().expect_err("STEPS 0 should fail");
1609 assert!(
1610 err.to_string()
1611 .contains("ASK STEPS must be a positive integer"),
1612 "got: {err}"
1613 );
1614
1615 let mut parser = make_parser("ASK 'q' STEPS 2 STEPS 3");
1616 let err = parser
1617 .parse_ask_query()
1618 .expect_err("duplicate STEPS should fail");
1619 assert!(
1620 err.to_string()
1621 .contains("ASK STEPS specified more than once"),
1622 "got: {err}"
1623 );
1624
1625 let mut parser = make_parser("ASK 'q' STEPS abc");
1626 let err = parser
1627 .parse_ask_query()
1628 .expect_err("non-integer STEPS should fail");
1629 assert!(
1630 err.to_string().to_ascii_lowercase().contains("integer"),
1631 "got: {err}"
1632 );
1633 }
1634
1635 #[test]
1636 fn explain_ask_and_ask_error_paths() {
1637 let mut parser = make_parser("EXPLAIN ASK $2 STRICT ON");
1638 let QueryExpr::Ask(query) = parser.parse_explain_ask_query().expect("explain ask") else {
1639 panic!("expected ask query");
1640 };
1641 assert!(query.explain);
1642 assert_eq!(query.question_param, Some(1));
1643 assert!(query.strict);
1644
1645 let mut parser = make_parser("EXPLAIN SELECT 1");
1646 let err = parser
1647 .parse_explain_ask_query()
1648 .expect_err("missing ASK should fail");
1649 assert!(err.to_string().contains("expected"));
1650
1651 let mut parser = make_parser("ASK 'q' STRICT MAYBE");
1652 let err = parser
1653 .parse_ask_query()
1654 .expect_err("bad strict should fail");
1655 assert!(err.to_string().contains("Expected ON or OFF"));
1656
1657 let mut parser = make_parser("ASK 'q' CACHE TTL '10m' NOCACHE");
1658 let err = parser
1659 .parse_ask_query()
1660 .expect_err("duplicate cache should fail");
1661 assert!(err.to_string().contains("cache clause"));
1662
1663 let mut parser = make_parser("ASK 'q' CACHE FOREVER '10m'");
1664 let err = parser
1665 .parse_ask_query()
1666 .expect_err("bad cache ttl keyword should fail");
1667 assert!(err.to_string().contains("Expected TTL"));
1668 }
1669
1670 #[test]
1671 fn literal_value_special_constructors_arrays_and_objects() {
1672 let mut parser = make_parser("PASSWORD('pw')");
1673 assert!(matches!(
1674 parser.parse_literal_value().expect("password"),
1675 Value::Password(secret) if secret == "@@plain@@pw"
1676 ));
1677
1678 let mut parser = make_parser("SECRET('pw')");
1679 assert!(matches!(
1680 parser.parse_literal_value().expect("secret"),
1681 Value::Secret(bytes) if bytes == b"@@plain@@pw"
1682 ));
1683
1684 let mut parser = make_parser("SECRET_REF(vault, red.vault.api_key)");
1685 let value = parser.parse_literal_value().expect("secret ref");
1686 assert!(matches!(value, Value::Json(_)));
1687
1688 let mut parser = make_parser("[1, 2.5]");
1692 assert!(matches!(
1693 parser.parse_literal_value().expect("array"),
1694 Value::Array(items)
1695 if items == vec![Value::Integer(1), Value::Float(2.5)]
1696 ));
1697
1698 let mut parser = make_parser("['a', 2]");
1699 assert!(matches!(
1700 parser.parse_literal_value().expect("mixed array"),
1701 Value::Array(items)
1702 if items == vec![Value::Text("a".into()), Value::Integer(2)]
1703 ));
1704
1705 let mut parser = make_parser("[1, 2, 9007199254740993]");
1708 assert!(matches!(
1709 parser.parse_literal_value().expect("lossless big-int array"),
1710 Value::Array(items)
1711 if items
1712 == vec![
1713 Value::Integer(1),
1714 Value::Integer(2),
1715 Value::Integer(9007199254740993),
1716 ]
1717 ));
1718
1719 let mut parser = make_parser("{level = 'info', count: 2}");
1720 assert!(matches!(
1721 parser.parse_literal_value().expect("json object"),
1722 Value::Json(_)
1723 ));
1724 }
1725
1726 #[test]
1727 fn literal_value_rejects_invalid_secret_ref_and_scalar_start() {
1728 let mut parser = make_parser("SECRET_REF(config, red.vault.api_key)");
1729 let err = parser
1730 .parse_literal_value()
1731 .expect_err("non-vault secret ref should fail");
1732 assert!(err.to_string().contains("expected"));
1733
1734 let mut parser = make_parser("ORDER");
1735 let err = parser
1736 .parse_literal_value()
1737 .expect_err("non literal should fail");
1738 assert!(err.to_string().contains("expected"));
1739 }
1740
1741 #[test]
1742 fn json_depth_check_rejects_deep_literals() {
1743 let mut deep = reddb_types::utils::json::JsonValue::Array(vec![]);
1744 for _ in 0..JSON_LITERAL_MAX_DEPTH {
1745 deep = reddb_types::utils::json::JsonValue::Array(vec![deep]);
1746 }
1747 let err = json_literal_depth_check(&deep).expect_err("depth should fail");
1748 assert!(err.contains("JSON_LITERAL_MAX_DEPTH"));
1749 }
1750}