Skip to main content

reddb_rql/parser/
dml.rs

1//! DML SQL Parser: INSERT, UPDATE, DELETE
2
3use 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
13/// Maximum nesting depth for JSON object literals — shared constant
14/// that now lives in the crate-level [`crate::limits`] module so every
15/// depth cap is co-located with [`crate::limits::ParserLimits`].
16pub(crate) use crate::limits::JSON_LITERAL_MAX_DEPTH;
17
18/// Walk a parsed `JsonValue` tree and bail out if nesting exceeds
19/// `JSON_LITERAL_MAX_DEPTH`. Iterative to avoid the very stack
20/// overflow we're trying to prevent.
21pub(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    /// Parse: INSERT INTO table [NODE|EDGE|VECTOR|DOCUMENT|KV] (col1, col2) VALUES (val1, val2), (val3, val4) [RETURNING]
52    pub fn parse_insert_query(&mut self) -> Result<QueryExpr, ParseError> {
53        self.expect(Token::Insert)?;
54        self.expect(Token::Into)?;
55        // Issue #789 — Analytics v0 explicitly excludes `INSERT INTO
56        // METRIC <path>` as a raw write path (PRD #782 non-goal). Raw
57        // samples land in ordinary RedDB collections; the metric
58        // descriptor catalog is reached through `CREATE METRIC` and
59        // `red.analytics.metrics`. Reject the form here before the
60        // identifier slot so the error names the actual reason, not a
61        // generic "expected identifier".
62        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        // Check for entity type keyword
75        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        // Parse column list
100        self.expect(Token::LParen)?;
101        let columns = self.parse_ident_list()?;
102        self.expect(Token::RParen)?;
103
104        // Parse VALUES
105        self.expect(Token::Values)?;
106        let mut all_values = Vec::new();
107        let mut all_value_exprs = Vec::new();
108        loop {
109            self.expect(Token::LParen)?;
110            let row_exprs = self.parse_dml_expr_list()?;
111            self.expect(Token::RParen)?;
112            // Tolerate `$N` / `?` placeholders in VALUES rows: fold to
113            // Value::Null and rely on `user_params::bind` to substitute
114            // the caller's values before execution. Issue #355.
115            // Tolerate `$N` / `?` placeholders in VALUES rows: if fold
116            // fails on an expression that contains `Expr::Parameter`,
117            // emit a `Value::Null` placeholder. `user_params::bind`
118            // substitutes the caller-supplied value before execution.
119            // Issue #355.
120            let row_values = row_exprs
121                .iter()
122                .map(|expr| match fold_expr_to_value(expr.clone()) {
123                    Ok(value) => Ok(value),
124                    Err(msg) => {
125                        if crate::sql_lowering::expr_contains_parameter(expr) {
126                            Ok(Value::Null)
127                        } else {
128                            Err(msg)
129                        }
130                    }
131                })
132                .collect::<Result<Vec<_>, _>>()
133                .map_err(|msg| ParseError::new(msg, self.position()))?;
134            all_value_exprs.push(row_exprs);
135            all_values.push(row_values);
136            if !self.consume(&Token::Comma)? {
137                break;
138            }
139        }
140
141        // Parse optional WITH clauses
142        let (ttl_ms, expires_at_ms, with_metadata, auto_embed) = self.parse_with_clauses()?;
143
144        let returning = self.parse_returning_clause()?;
145
146        let suppress_events = if self.consume_ident_ci("SUPPRESS")? {
147            self.expect_ident_ci("EVENTS")?;
148            true
149        } else {
150            false
151        };
152
153        Ok(QueryExpr::Insert(InsertQuery {
154            table,
155            entity_type,
156            columns,
157            value_exprs: all_value_exprs,
158            values: all_values,
159            returning,
160            ttl_ms,
161            expires_at_ms,
162            with_metadata,
163            auto_embed,
164            suppress_events,
165        }))
166    }
167
168    /// Parse TTL duration value using the same logic as CREATE TABLE ... WITH TTL.
169    fn parse_ttl_duration(&mut self) -> Result<u64, ParseError> {
170        // Reuse the DDL TTL parser: expects a number followed by optional unit
171        let ttl_value = self.parse_float()?;
172        let ttl_unit = match self.peek() {
173            Token::Ident(unit) => {
174                let unit = unit.clone();
175                self.advance()?;
176                unit
177            }
178            _ => "s".to_string(),
179        };
180
181        let multiplier_ms = match ttl_unit.to_ascii_lowercase().as_str() {
182            "ms" | "msec" | "millisecond" | "milliseconds" => 1.0,
183            "s" | "sec" | "secs" | "second" | "seconds" => 1_000.0,
184            "m" | "min" | "mins" | "minute" | "minutes" => 60_000.0,
185            "h" | "hr" | "hrs" | "hour" | "hours" => 3_600_000.0,
186            "d" | "day" | "days" => 86_400_000.0,
187            other => {
188                return Err(ParseError::new(
189                    // F-05: render `other` via `{:?}` so caller-controlled
190                    // bytes (CR / LF / NUL / quotes) are escaped before
191                    // landing in the JSON/audit/log/gRPC error sinks.
192                    format!(
193                        "unsupported TTL unit {other:?}; supported units: ms, s, m, h, d (e.g. `WITH TTL 30 m`)"
194                    ),
195                    self.position(),
196                ));
197            }
198        };
199
200        Ok((ttl_value * multiplier_ms) as u64)
201    }
202
203    /// Parse WITH clauses: WITH TTL | EXPIRES AT | METADATA | AUTO EMBED
204    /// Returns (ttl_ms, expires_at_ms, metadata, auto_embed)
205    pub fn parse_with_clauses(
206        &mut self,
207    ) -> Result<
208        (
209            Option<u64>,
210            Option<u64>,
211            Vec<(String, Value)>,
212            Option<crate::ast::AutoEmbedConfig>,
213        ),
214        ParseError,
215    > {
216        let mut ttl_ms = None;
217        let mut expires_at_ms = None;
218        let mut with_metadata = Vec::new();
219        let mut auto_embed = None;
220
221        while self.consume(&Token::With)? {
222            if self.consume_ident_ci("TTL")? {
223                ttl_ms = Some(self.parse_ttl_duration()?);
224            } else if self.consume_ident_ci("EXPIRES")? {
225                self.expect_ident_ci("AT")?;
226                let ts = self.parse_expires_at_value()?;
227                expires_at_ms = Some(ts);
228            } else if self.consume(&Token::Metadata)? || self.consume_ident_ci("METADATA")? {
229                with_metadata = self.parse_with_metadata_pairs()?;
230            } else if self.consume_ident_ci("AUTO")? {
231                // WITH AUTO EMBED (field1, field2) [USING provider] [MODEL 'model']
232                self.consume_ident_ci("EMBED")?;
233                self.expect(Token::LParen)?;
234                let mut fields = Vec::new();
235                loop {
236                    fields.push(self.expect_ident()?);
237                    if !self.consume(&Token::Comma)? {
238                        break;
239                    }
240                }
241                self.expect(Token::RParen)?;
242                // `USING` is a reserved keyword (`Token::Using`), so
243                // `consume_ident_ci` would never match. Use the typed
244                // consumer instead. See bug #108 (mirrors the #92 fix
245                // for migration `DEPENDS ON`).
246                let provider = if self.consume(&Token::Using)? {
247                    self.expect_ident()?
248                } else {
249                    "openai".to_string()
250                };
251                let model = if self.consume_ident_ci("MODEL")? {
252                    Some(self.parse_string()?)
253                } else {
254                    None
255                };
256                auto_embed = Some(crate::ast::AutoEmbedConfig {
257                    fields,
258                    provider,
259                    model,
260                });
261            } else {
262                return Err(ParseError::expected(
263                    vec!["TTL", "EXPIRES AT", "METADATA", "AUTO EMBED"],
264                    self.peek(),
265                    self.position(),
266                ));
267            }
268        }
269
270        Ok((ttl_ms, expires_at_ms, with_metadata, auto_embed))
271    }
272
273    /// Expect a case-insensitive identifier (error if not found)
274    fn expect_ident_ci(&mut self, expected: &str) -> Result<(), ParseError> {
275        if self.consume_ident_ci(expected)? {
276            Ok(())
277        } else {
278            Err(ParseError::expected(
279                vec![expected],
280                self.peek(),
281                self.position(),
282            ))
283        }
284    }
285
286    /// Parse an absolute expiration timestamp (unix ms or string date)
287    fn parse_expires_at_value(&mut self) -> Result<u64, ParseError> {
288        // Try integer (unix timestamp in ms)
289        if let Ok(value) = self.parse_integer() {
290            return Ok(value as u64);
291        }
292        // Try string like '2026-12-31' — convert to unix ms
293        if let Ok(text) = self.parse_string() {
294            // Simple ISO date parsing: YYYY-MM-DD or YYYY-MM-DDTHH:MM:SS
295            let trimmed = text.trim();
296            if let Ok(ts) = trimmed.parse::<u64>() {
297                return Ok(ts);
298            }
299            // Basic date parsing — delegate to chrono if available, or simple heuristic
300            return Err(ParseError::new(
301                // F-05: `trimmed` is caller-controlled string-literal bytes.
302                // Render via `{:?}` so CR/LF/NUL/quotes are escaped before
303                // the message reaches the JSON / audit / log / gRPC sinks.
304                format!("EXPIRES AT requires a unix timestamp in milliseconds, got {trimmed:?}"),
305                self.position(),
306            ));
307        }
308        Err(ParseError::expected(
309            vec!["timestamp (unix ms) or 'YYYY-MM-DD'"],
310            self.peek(),
311            self.position(),
312        ))
313    }
314
315    /// Parse WITH METADATA (key1 = 'value1', key2 = 42)
316    fn parse_with_metadata_pairs(&mut self) -> Result<Vec<(String, Value)>, ParseError> {
317        self.expect(Token::LParen)?;
318        let mut pairs = Vec::new();
319        if !self.check(&Token::RParen) {
320            loop {
321                let key = self.expect_ident_or_keyword()?.to_ascii_lowercase();
322                self.expect(Token::Eq)?;
323                let value = self.parse_literal_value()?;
324                pairs.push((key, value));
325                if !self.consume(&Token::Comma)? {
326                    break;
327                }
328            }
329        }
330        self.expect(Token::RParen)?;
331        Ok(pairs)
332    }
333
334    /// Parse: UPDATE table SET col1=val1, col2=val2 [WHERE filter] [WITH TTL|EXPIRES AT|METADATA]
335    pub fn parse_update_query(&mut self) -> Result<QueryExpr, ParseError> {
336        self.expect(Token::Update)?;
337        let table = self.expect_ident()?;
338        let target = self.parse_update_target()?;
339        self.expect(Token::Set)?;
340
341        let mut assignments = Vec::new();
342        let mut assignment_exprs = Vec::new();
343        let mut compound_assignment_ops = Vec::new();
344        loop {
345            let col = self.expect_column_ident()?;
346            let compound_op = if self.consume(&Token::Eq)? {
347                None
348            } else {
349                let op = match self.peek() {
350                    Token::Plus => BinOp::Add,
351                    Token::Dash | Token::Minus => BinOp::Sub,
352                    Token::Star => BinOp::Mul,
353                    Token::Slash => BinOp::Div,
354                    Token::Percent => BinOp::Mod,
355                    _ => {
356                        return Err(ParseError::expected(
357                            vec!["=", "+=", "-=", "*=", "/=", "%="],
358                            self.peek(),
359                            self.position(),
360                        ));
361                    }
362                };
363                self.advance()?;
364                self.expect(Token::Eq)?;
365                Some(op)
366            };
367            let expr = self.parse_expr()?;
368            let folded = fold_expr_to_value(expr.clone()).ok();
369            assignment_exprs.push((col.clone(), expr));
370            compound_assignment_ops.push(compound_op);
371            if compound_op.is_none() {
372                if let Some(val) = folded {
373                    assignments.push((col.clone(), val));
374                }
375            }
376            if !self.consume(&Token::Comma)? {
377                break;
378            }
379        }
380
381        let filter = if self.consume(&Token::Where)? {
382            Some(self.parse_filter()?)
383        } else {
384            None
385        };
386        let where_expr = filter.as_ref().map(filter_to_expr);
387
388        let (ttl_ms, expires_at_ms, with_metadata, _auto_embed) = self.parse_with_clauses()?;
389
390        let mut order_by = if self.consume(&Token::Order)? {
391            self.expect(Token::By)?;
392            let clauses = self.parse_order_by_list()?;
393            validate_update_order_by(&clauses, self.position())?;
394            clauses
395        } else {
396            Vec::new()
397        };
398
399        // Optional `LIMIT N` — used by `BATCH N ROWS` data migrations
400        // to cap a single batch. Must come after WHERE / WITH because
401        // those have their own keyword tokens that the LIMIT branch
402        // would otherwise mis-consume.
403        let limit = if self.consume(&Token::Limit)? {
404            Some(self.parse_integer()? as u64)
405        } else {
406            None
407        };
408        if !order_by.is_empty() && limit.is_none() {
409            return Err(ParseError::new(
410                "UPDATE ORDER BY requires LIMIT",
411                self.position(),
412            ));
413        }
414        if !order_by.is_empty() && !update_order_by_mentions_rid(&order_by) {
415            order_by.push(OrderByClause {
416                field: FieldRef::TableColumn {
417                    table: String::new(),
418                    column: "rid".to_string(),
419                },
420                expr: None,
421                ascending: true,
422                nulls_first: false,
423            });
424        }
425
426        let returning = self.parse_returning_clause()?;
427
428        let suppress_events = if self.consume_ident_ci("SUPPRESS")? {
429            self.expect_ident_ci("EVENTS")?;
430            true
431        } else {
432            false
433        };
434
435        Ok(QueryExpr::Update(UpdateQuery {
436            table,
437            target,
438            assignment_exprs,
439            compound_assignment_ops,
440            assignments,
441            where_expr,
442            filter,
443            ttl_ms,
444            expires_at_ms,
445            with_metadata,
446            returning,
447            order_by,
448            limit,
449            suppress_events,
450        }))
451    }
452
453    fn parse_update_target(&mut self) -> Result<UpdateTarget, ParseError> {
454        if self.consume(&Token::Kv)? {
455            return Ok(UpdateTarget::Kv);
456        }
457        if self.consume(&Token::Rows)? {
458            return Ok(UpdateTarget::Rows);
459        }
460        if self.consume_ident_ci("DOCUMENTS")? {
461            return Ok(UpdateTarget::Documents);
462        }
463        if self.consume_ident_ci("NODES")? {
464            return Ok(UpdateTarget::Nodes);
465        }
466        if self.consume_ident_ci("EDGES")? {
467            return Ok(UpdateTarget::Edges);
468        }
469        Ok(UpdateTarget::Rows)
470    }
471
472    /// Parse: DELETE FROM table [WHERE filter]
473    pub fn parse_delete_query(&mut self) -> Result<QueryExpr, ParseError> {
474        self.expect(Token::Delete)?;
475        self.expect(Token::From)?;
476        let table = self.expect_ident()?;
477
478        let filter = if self.consume(&Token::Where)? {
479            Some(self.parse_filter()?)
480        } else {
481            None
482        };
483
484        let where_expr = filter.as_ref().map(filter_to_expr);
485
486        let returning = self.parse_returning_clause()?;
487
488        let suppress_events = if self.consume_ident_ci("SUPPRESS")? {
489            self.expect_ident_ci("EVENTS")?;
490            true
491        } else {
492            false
493        };
494
495        Ok(QueryExpr::Delete(DeleteQuery {
496            table,
497            where_expr,
498            filter,
499            returning,
500            suppress_events,
501        }))
502    }
503
504    /// Parse optional `RETURNING (* | col [, col ...])` clause.
505    /// Returns `None` if no RETURNING token, errors if RETURNING is present
506    /// but not followed by `*` or a non-empty column list.
507    fn parse_returning_clause(&mut self) -> Result<Option<Vec<ReturningItem>>, ParseError> {
508        if !self.consume(&Token::Returning)? {
509            return Ok(None);
510        }
511        if self.consume(&Token::Star)? {
512            return Ok(Some(vec![ReturningItem::All]));
513        }
514        let mut items = Vec::new();
515        loop {
516            if returning_expr_start(self.peek()) {
517                return Err(returning_expr_not_supported(self.position()));
518            }
519            let col = self.expect_update_returning_column()?;
520            items.push(ReturningItem::Column(col));
521            if returning_expr_tail(self.peek()) {
522                return Err(returning_expr_not_supported(self.position()));
523            }
524            if !self.consume(&Token::Comma)? {
525                break;
526            }
527        }
528        if items.is_empty() {
529            return Err(ParseError::expected(
530                vec!["*", "column name"],
531                self.peek(),
532                self.position(),
533            ));
534        }
535        Ok(Some(items))
536    }
537
538    fn expect_update_returning_column(&mut self) -> Result<String, ParseError> {
539        if self.consume(&Token::Weight)? {
540            return Ok("weight".to_string());
541        }
542        self.expect_ident_or_keyword()
543    }
544
545    /// Parse: ASK 'question' [USING provider] [MODEL 'model'] [DEPTH n]
546    /// [LIMIT n] [MIN_SCORE x] [COLLECTION col] [AS RQL]
547    pub fn parse_ask_query(&mut self) -> Result<QueryExpr, ParseError> {
548        self.parse_ask_query_with_explain(false)
549    }
550
551    /// Parse: EXPLAIN ASK 'question' ...
552    pub fn parse_explain_ask_query(&mut self) -> Result<QueryExpr, ParseError> {
553        self.advance()?; // consume EXPLAIN
554        if !matches!(self.peek(), Token::Ident(name) if name.eq_ignore_ascii_case("ASK")) {
555            return Err(ParseError::expected(
556                vec!["ASK"],
557                self.peek(),
558                self.position(),
559            ));
560        }
561        self.parse_ask_query_with_explain(true)
562    }
563
564    fn parse_ask_query_with_explain(&mut self, explain: bool) -> Result<QueryExpr, ParseError> {
565        self.advance()?; // consume ASK
566
567        let (question, question_param) = match self.peek() {
568            Token::String(_) => (self.parse_string()?, None),
569            Token::Dollar | Token::Question => {
570                let index = self.parse_param_slot("ASK question")?;
571                (String::new(), Some(index))
572            }
573            other => {
574                return Err(ParseError::expected(
575                    vec!["string", "$N", "?"],
576                    other,
577                    self.position(),
578                ));
579            }
580        };
581
582        let mut provider = None;
583        let mut model = None;
584        let mut depth = None;
585        let mut limit = None;
586        let mut min_score = None;
587        let mut collection = None;
588        let mut temperature = None;
589        let mut seed = None;
590        let mut strict = true;
591        let mut stream = false;
592        let mut cache = AskCacheClause::Default;
593        let mut as_rql = false;
594        let mut execute = false;
595
596        // Parse optional clauses in any order. Loop bound = number of
597        // clause kinds, so each can appear at most once.
598        for _ in 0..14 {
599            if self.consume(&Token::Using)? {
600                provider = Some(match &self.current.token {
601                    Token::String(_) => self.parse_string()?,
602                    _ => self.expect_ident()?,
603                });
604            } else if self.consume_ident_ci("MODEL")? {
605                model = Some(self.parse_string()?);
606            } else if self.consume(&Token::Depth)? {
607                depth = Some(self.parse_integer()? as usize);
608            } else if self.consume(&Token::Limit)? {
609                limit = Some(self.parse_integer()? as usize);
610            } else if self.consume(&Token::MinScore)? {
611                min_score = Some(self.parse_float()? as f32);
612            } else if self.consume(&Token::Collection)? {
613                collection = Some(self.expect_ident()?);
614            } else if self.consume_ident_ci("TEMPERATURE")? {
615                temperature = Some(self.parse_float()? as f32);
616            } else if self.consume_ident_ci("SEED")? {
617                seed = Some(self.parse_integer()? as u64);
618            } else if self.consume_ident_ci("STRICT")? {
619                let value = self.expect_ident_or_keyword()?;
620                if value.eq_ignore_ascii_case("ON") {
621                    strict = true;
622                } else if value.eq_ignore_ascii_case("OFF") {
623                    strict = false;
624                } else {
625                    return Err(ParseError::new(
626                        "Expected ON or OFF after STRICT",
627                        self.position(),
628                    ));
629                }
630            } else if self.consume_ident_ci("STREAM")? {
631                stream = true;
632            } else if self.consume_ident_ci("CACHE")? {
633                if !matches!(cache, AskCacheClause::Default) {
634                    return Err(ParseError::new(
635                        "ASK cache clause specified more than once",
636                        self.position(),
637                    ));
638                }
639                let ttl = self.expect_ident_or_keyword()?;
640                if !ttl.eq_ignore_ascii_case("TTL") {
641                    return Err(ParseError::new("Expected TTL after CACHE", self.position()));
642                }
643                cache = AskCacheClause::CacheTtl(self.parse_string()?);
644            } else if self.consume_ident_ci("NOCACHE")? {
645                if !matches!(cache, AskCacheClause::Default) {
646                    return Err(ParseError::new(
647                        "ASK cache clause specified more than once",
648                        self.position(),
649                    ));
650                }
651                cache = AskCacheClause::NoCache;
652            } else if self.consume(&Token::As)? {
653                if as_rql {
654                    return Err(ParseError::new(
655                        "ASK AS RQL specified more than once",
656                        self.position(),
657                    ));
658                }
659                let output = self.expect_ident_or_keyword()?;
660                if !output.eq_ignore_ascii_case("RQL") {
661                    return Err(ParseError::new(
662                        "Expected RQL after ASK AS",
663                        self.position(),
664                    ));
665                }
666                as_rql = true;
667            } else if self.consume_ident_ci("EXECUTE")? {
668                if execute {
669                    return Err(ParseError::new(
670                        "ASK EXECUTE specified more than once",
671                        self.position(),
672                    ));
673                }
674                execute = true;
675            } else {
676                break;
677            }
678        }
679
680        Ok(QueryExpr::Ask(AskQuery {
681            explain,
682            question,
683            question_param,
684            provider,
685            model,
686            depth,
687            limit,
688            min_score,
689            collection,
690            temperature,
691            seed,
692            strict,
693            stream,
694            cache,
695            as_rql,
696            execute,
697        }))
698    }
699
700    /// Parse comma-separated identifiers (accepts keywords as column names in DML context)
701    fn parse_ident_list(&mut self) -> Result<Vec<String>, ParseError> {
702        let mut idents = Vec::new();
703        loop {
704            idents.push(self.expect_ident_or_keyword()?);
705            if !self.consume(&Token::Comma)? {
706                break;
707            }
708        }
709        Ok(idents)
710    }
711
712    /// Parse comma-separated literal values for DML statements
713    fn parse_dml_value_list(&mut self) -> Result<Vec<Value>, ParseError> {
714        self.parse_dml_expr_list()?
715            .into_iter()
716            .map(fold_expr_to_value)
717            .collect::<Result<Vec<_>, _>>()
718            .map_err(|msg| ParseError::new(msg, self.position()))
719    }
720
721    fn parse_dml_expr_list(&mut self) -> Result<Vec<Expr>, ParseError> {
722        let mut values = Vec::new();
723        loop {
724            values.push(self.parse_expr()?);
725            if !self.consume(&Token::Comma)? {
726                break;
727            }
728        }
729        Ok(values)
730    }
731
732    /// Parse a single literal value (string, number, true, false, null, array)
733    pub(crate) fn parse_literal_value(&mut self) -> Result<Value, ParseError> {
734        // Depth guard: this function recurses for nested array `[…]`
735        // and object `{…}` literals (see the LBracket / LBrace arms
736        // below). Without entering the depth counter, an adversarial
737        // payload like `[[[[…(10k×)…]]]]` would overflow the Rust
738        // stack BEFORE `ParserLimits::max_depth` fires. The
739        // `JsonLiteral` token path uses `json_literal_depth_check`
740        // (iterative) — the bare `[`/`{` path needs the recursion
741        // counter explicitly.
742        self.enter_depth()?;
743        let result = self.parse_literal_value_inner();
744        self.exit_depth();
745        result
746    }
747
748    fn parse_literal_value_inner(&mut self) -> Result<Value, ParseError> {
749        // Recognize PASSWORD('plaintext') and SECRET('plaintext') as
750        // typed literal constructors. The parser stores them as
751        // sentinel-prefixed values so that the INSERT executor can
752        // apply the crypto transform (argon2id hash / AES-256-GCM
753        // encrypt) without the parser depending on auth or crypto
754        // subsystems.
755        if let Token::Ident(name) = self.peek().clone() {
756            let upper = name.to_uppercase();
757            if upper == "PASSWORD" || upper == "SECRET" {
758                self.advance()?; // consume ident
759                self.expect(Token::LParen)?;
760                let plaintext = self.parse_string()?;
761                self.expect(Token::RParen)?;
762                return Ok(match upper.as_str() {
763                    "PASSWORD" => Value::Password(format!("@@plain@@{plaintext}")),
764                    "SECRET" => Value::Secret(format!("@@plain@@{plaintext}").into_bytes()),
765                    _ => unreachable!(),
766                });
767            }
768            if upper == "SECRET_REF" {
769                self.advance()?; // consume ident
770                self.expect(Token::LParen)?;
771                let store = self.expect_ident_or_keyword()?.to_ascii_lowercase();
772                if store != "vault" {
773                    return Err(ParseError::expected(
774                        vec!["vault"],
775                        self.peek(),
776                        self.position(),
777                    ));
778                }
779                self.expect(Token::Comma)?;
780                let (collection, key) =
781                    self.parse_kv_key(reddb_types::catalog::CollectionModel::Vault)?;
782                self.expect(Token::RParen)?;
783                return Ok(secret_ref_value(&store, &collection, &key));
784            }
785        }
786
787        match self.peek().clone() {
788            Token::String(s) => {
789                let s = s.clone();
790                self.advance()?;
791                Ok(Value::text(s))
792            }
793            Token::JsonLiteral(raw) => {
794                // The lexer already validated brace balance and the
795                // 16 MiB payload ceiling. Parse the raw text into a
796                // canonical JsonValue then re-encode via `to_vec` so
797                // the on-disk bytes match the quoted form.
798                self.advance()?;
799                let json_value = reddb_types::utils::json::parse_json(&raw).map_err(|err| {
800                    ParseError::new(
801                        // F-05: render the underlying parse-error string
802                        // via `{:?}` so any user fragment serde echoed
803                        // back (unexpected character, key text, …) is
804                        // Debug-escaped before reaching the downstream
805                        // JSON / audit / log / gRPC sinks.
806                        format!("invalid JSON object literal: {:?}", err.to_string()),
807                        self.position(),
808                    )
809                })?;
810                json_literal_depth_check(&json_value)
811                    .map_err(|err| ParseError::new(err, self.position()))?;
812                let canonical = reddb_types::serde_json::Value::from(json_value);
813                let bytes = reddb_types::json::to_vec(&canonical).map_err(|err| {
814                    ParseError::new(
815                        // F-05: escape the encoder error via `{:?}` so any
816                        // user fragment it carries cannot smuggle control
817                        // bytes through downstream serialization sinks.
818                        format!("failed to encode JSON literal: {:?}", err.to_string()),
819                        self.position(),
820                    )
821                })?;
822                Ok(Value::Json(bytes))
823            }
824            Token::Integer(n) => {
825                self.advance()?;
826                Ok(Value::Integer(n))
827            }
828            Token::Float(n) => {
829                self.advance()?;
830                Ok(Value::Float(n))
831            }
832            Token::True => {
833                self.advance()?;
834                Ok(Value::Boolean(true))
835            }
836            Token::False => {
837                self.advance()?;
838                Ok(Value::Boolean(false))
839            }
840            Token::Null => {
841                self.advance()?;
842                Ok(Value::Null)
843            }
844            Token::LBracket => {
845                // Parse array literal [val1, val2, ...]
846                // For numeric arrays, produce Value::Vector; for others, produce Value::Json
847                self.advance()?; // consume '['
848                let mut items = Vec::new();
849                if !self.check(&Token::RBracket) {
850                    loop {
851                        items.push(self.parse_literal_value()?);
852                        if !self.consume(&Token::Comma)? {
853                            break;
854                        }
855                    }
856                }
857                self.expect(Token::RBracket)?;
858
859                // Check if all items are numeric (Integer or Float) -> Value::Vector
860                let all_numeric = items
861                    .iter()
862                    .all(|v| matches!(v, Value::Integer(_) | Value::Float(_)));
863                if all_numeric && !items.is_empty() {
864                    let floats: Vec<f32> = items
865                        .iter()
866                        .map(|v| match v {
867                            Value::Float(f) => *f as f32,
868                            Value::Integer(i) => *i as f32,
869                            _ => 0.0,
870                        })
871                        .collect();
872                    Ok(Value::Vector(floats))
873                } else {
874                    // Encode as JSON bytes
875                    let json_arr: Vec<reddb_types::json::Value> = items
876                        .iter()
877                        .map(|v| match v {
878                            Value::Null => reddb_types::json::Value::Null,
879                            Value::Boolean(b) => reddb_types::json::Value::Bool(*b),
880                            Value::Integer(i) => reddb_types::json::Value::Number(*i as f64),
881                            Value::Float(f) => reddb_types::json::Value::Number(*f),
882                            Value::Text(s) => reddb_types::json::Value::String(s.to_string()),
883                            _ => reddb_types::json::Value::Null,
884                        })
885                        .collect();
886                    let json_val = reddb_types::json::Value::Array(json_arr);
887                    let bytes = reddb_types::json::to_vec(&json_val).unwrap_or_default();
888                    Ok(Value::Json(bytes))
889                }
890            }
891            Token::LBrace => {
892                // Parse JSON object literal {key: value, ...}
893                self.advance()?; // consume '{'
894                let mut map = reddb_types::json::Map::new();
895                if !self.check(&Token::RBrace) {
896                    loop {
897                        // Key: string or identifier. Reserved-word
898                        // keys (`level`, `msg`, `type`, …) fall through
899                        // to `expect_ident_or_keyword`, which returns
900                        // the canonical UPPERCASE spelling; lowercase
901                        // that path so the JSON object preserves the
902                        // source casing.
903                        let key = match self.peek().clone() {
904                            Token::String(s) => {
905                                self.advance()?;
906                                s
907                            }
908                            Token::Ident(s) => {
909                                self.advance()?;
910                                s
911                            }
912                            _ => self.expect_ident_or_keyword()?.to_ascii_lowercase(),
913                        };
914                        // Separator: ':' or '='
915                        if !self.consume(&Token::Colon)? {
916                            self.expect(Token::Eq)?;
917                        }
918                        // Value: recursive
919                        let val = self.parse_literal_value()?;
920                        let json_val = match val {
921                            Value::Null => reddb_types::json::Value::Null,
922                            Value::Boolean(b) => reddb_types::json::Value::Bool(b),
923                            Value::Integer(i) => reddb_types::json::Value::Number(i as f64),
924                            Value::Float(f) => reddb_types::json::Value::Number(f),
925                            Value::Text(s) => reddb_types::json::Value::String(s.to_string()),
926                            Value::Json(ref bytes) => reddb_types::json::from_slice(bytes)
927                                .unwrap_or(reddb_types::json::Value::Null),
928                            _ => reddb_types::json::Value::Null,
929                        };
930                        map.insert(key, json_val);
931                        if !self.consume(&Token::Comma)? {
932                            break;
933                        }
934                    }
935                }
936                self.expect(Token::RBrace)?;
937                let json_val = reddb_types::json::Value::Object(map);
938                let bytes = reddb_types::json::to_vec(&json_val).unwrap_or_default();
939                Ok(Value::Json(bytes))
940            }
941            ref other => Err(ParseError::expected(
942                vec!["string", "number", "true", "false", "null", "[", "{"],
943                other,
944                self.position(),
945            )),
946        }
947    }
948}
949
950fn returning_expr_start(token: &Token) -> bool {
951    matches!(
952        token,
953        Token::Integer(_)
954            | Token::Float(_)
955            | Token::String(_)
956            | Token::JsonLiteral(_)
957            | Token::Null
958            | Token::True
959            | Token::False
960            | Token::LParen
961            | Token::Minus
962            | Token::Question
963            | Token::Dollar
964    )
965}
966
967fn returning_expr_tail(token: &Token) -> bool {
968    matches!(
969        token,
970        Token::LParen
971            | Token::Plus
972            | Token::Minus
973            | Token::Star
974            | Token::Slash
975            | Token::Percent
976            | Token::DoublePipe
977            | Token::Pipe
978            | Token::Eq
979            | Token::Ne
980            | Token::Lt
981            | Token::Le
982            | Token::Gt
983            | Token::Ge
984            | Token::Dot
985            | Token::Colon
986    )
987}
988
989fn validate_update_order_by(
990    clauses: &[OrderByClause],
991    position: crate::lexer::Position,
992) -> Result<(), ParseError> {
993    for clause in clauses {
994        if clause.expr.is_some() {
995            return Err(ParseError::new(
996                "UPDATE ORDER BY only supports top-level fields",
997                position,
998            ));
999        }
1000        match &clause.field {
1001            FieldRef::TableColumn { table, column }
1002                if table.is_empty() && !column.contains('.') => {}
1003            _ => {
1004                return Err(ParseError::new(
1005                    "UPDATE ORDER BY only supports top-level fields",
1006                    position,
1007                ));
1008            }
1009        }
1010    }
1011    Ok(())
1012}
1013
1014fn update_order_by_mentions_rid(clauses: &[OrderByClause]) -> bool {
1015    clauses.iter().any(|clause| {
1016        matches!(
1017            &clause.field,
1018            FieldRef::TableColumn { table, column }
1019                if table.is_empty() && column.eq_ignore_ascii_case("rid")
1020        )
1021    })
1022}
1023
1024fn returning_expr_not_supported(position: crate::lexer::Position) -> ParseError {
1025    ParseError::new(
1026        "NOT_YET_SUPPORTED: RETURNING expressions are not supported yet; use RETURNING * or named columns. Track a follow-up issue for RETURNING <expr>.",
1027        position,
1028    )
1029}
1030
1031fn secret_ref_value(store: &str, collection: &str, key: &str) -> Value {
1032    let mut map = reddb_types::json::Map::new();
1033    map.insert(
1034        "type".to_string(),
1035        reddb_types::json::Value::String("secret_ref".to_string()),
1036    );
1037    map.insert(
1038        "store".to_string(),
1039        reddb_types::json::Value::String(store.to_string()),
1040    );
1041    map.insert(
1042        "collection".to_string(),
1043        reddb_types::json::Value::String(collection.to_string()),
1044    );
1045    map.insert(
1046        "key".to_string(),
1047        reddb_types::json::Value::String(key.to_string()),
1048    );
1049    Value::Json(
1050        reddb_types::json::to_vec(&reddb_types::json::Value::Object(map)).unwrap_or_default(),
1051    )
1052}
1053
1054#[cfg(test)]
1055mod tests {
1056    use super::*;
1057    use crate::ast::{InsertEntityType, ReturningItem, UpdateTarget};
1058
1059    fn make_parser(input: &str) -> Parser<'_> {
1060        Parser::new(input).expect("lexer")
1061    }
1062
1063    fn insert(input: &str) -> InsertQuery {
1064        let mut parser = make_parser(input);
1065        let QueryExpr::Insert(query) = parser.parse_insert_query().expect("insert") else {
1066            panic!("expected insert query");
1067        };
1068        query
1069    }
1070
1071    fn update(input: &str) -> UpdateQuery {
1072        let mut parser = make_parser(input);
1073        let QueryExpr::Update(query) = parser.parse_update_query().expect("update") else {
1074            panic!("expected update query");
1075        };
1076        query
1077    }
1078
1079    fn delete(input: &str) -> DeleteQuery {
1080        let mut parser = make_parser(input);
1081        let QueryExpr::Delete(query) = parser.parse_delete_query().expect("delete") else {
1082            panic!("expected delete query");
1083        };
1084        query
1085    }
1086
1087    fn ask(input: &str) -> AskQuery {
1088        let mut parser = make_parser(input);
1089        let QueryExpr::Ask(query) = parser.parse_ask_query().expect("ask") else {
1090            panic!("expected ask query");
1091        };
1092        query
1093    }
1094
1095    #[test]
1096    fn insert_entity_types_with_options_returning_and_suppress_events() {
1097        let cases = [
1098            (
1099                "INSERT INTO items NODE (id) VALUES (1)",
1100                InsertEntityType::Node,
1101            ),
1102            (
1103                "INSERT INTO items EDGE (id) VALUES (1)",
1104                InsertEntityType::Edge,
1105            ),
1106            (
1107                "INSERT INTO items VECTOR (id) VALUES (1)",
1108                InsertEntityType::Vector,
1109            ),
1110            (
1111                "INSERT INTO items DOCUMENT (id) VALUES (1)",
1112                InsertEntityType::Document,
1113            ),
1114            ("INSERT INTO items KV (id) VALUES (1)", InsertEntityType::Kv),
1115        ];
1116        for (input, expected) in cases {
1117            assert_eq!(insert(input).entity_type, expected, "{input}");
1118        }
1119
1120        let query = insert(
1121            "INSERT INTO docs (id, body) VALUES (1, 'red'), (2, ?) \
1122             WITH TTL 2 h WITH EXPIRES AT 999 \
1123             WITH METADATA (source = 'test', score = 3) \
1124             WITH AUTO EMBED (body, title) USING openai MODEL 'text-embedding-3-small' \
1125             RETURNING * SUPPRESS EVENTS",
1126        );
1127        assert_eq!(query.table, "docs");
1128        assert_eq!(query.entity_type, InsertEntityType::Row);
1129        assert_eq!(query.columns, vec!["id", "body"]);
1130        assert_eq!(query.values.len(), 2);
1131        assert_eq!(query.value_exprs.len(), 2);
1132        assert_eq!(query.ttl_ms, Some(7_200_000));
1133        assert_eq!(query.expires_at_ms, Some(999));
1134        assert_eq!(query.with_metadata.len(), 2);
1135        assert_eq!(
1136            query.returning.as_deref(),
1137            Some([ReturningItem::All].as_slice())
1138        );
1139        let auto_embed = query.auto_embed.expect("auto embed");
1140        assert_eq!(auto_embed.fields, vec!["body", "title"]);
1141        assert_eq!(auto_embed.provider, "openai");
1142        assert_eq!(auto_embed.model.as_deref(), Some("text-embedding-3-small"));
1143        assert!(query.suppress_events);
1144    }
1145
1146    #[test]
1147    fn insert_rejects_metric_and_bad_with_clause() {
1148        let mut parser = make_parser("INSERT INTO METRIC cpu.usage (value) VALUES (1)");
1149        let err = parser
1150            .parse_insert_query()
1151            .expect_err("metric insert should fail");
1152        assert!(err.to_string().contains("INSERT INTO METRIC"));
1153
1154        let mut parser = make_parser("INSERT INTO docs (id) VALUES (1) WITH TTL 1 fortnight");
1155        let err = parser
1156            .parse_insert_query()
1157            .expect_err("bad ttl unit should fail");
1158        assert!(err.to_string().contains("unsupported TTL unit"));
1159
1160        let mut parser = make_parser("INSERT INTO docs (id) VALUES (1) WITH UNKNOWN");
1161        let err = parser
1162            .parse_insert_query()
1163            .expect_err("bad WITH should fail");
1164        assert!(err.to_string().contains("expected"));
1165    }
1166
1167    #[test]
1168    fn update_targets_compound_assignments_order_limit_returning() {
1169        let cases = [
1170            ("UPDATE docs KV SET count = 1", UpdateTarget::Kv),
1171            ("UPDATE docs ROWS SET count = 1", UpdateTarget::Rows),
1172            (
1173                "UPDATE docs DOCUMENTS SET count = 1",
1174                UpdateTarget::Documents,
1175            ),
1176            ("UPDATE docs NODES SET count = 1", UpdateTarget::Nodes),
1177            ("UPDATE docs EDGES SET count = 1", UpdateTarget::Edges),
1178        ];
1179        for (input, expected) in cases {
1180            assert_eq!(update(input).target, expected, "{input}");
1181        }
1182
1183        let query = update(
1184            "UPDATE docs DOCUMENTS SET count += 2, title = UPPER(title) \
1185             WHERE id = 1 WITH TTL 30 s WITH METADATA (source = 'update') \
1186             ORDER BY updated_at DESC LIMIT 5 RETURNING weight, title SUPPRESS EVENTS",
1187        );
1188        assert_eq!(query.table, "docs");
1189        assert_eq!(query.target, UpdateTarget::Documents);
1190        assert_eq!(query.assignment_exprs.len(), 2);
1191        assert_eq!(query.compound_assignment_ops, vec![Some(BinOp::Add), None]);
1192        assert_eq!(query.assignments.len(), 0);
1193        assert!(query.filter.is_some());
1194        assert!(query.where_expr.is_some());
1195        assert_eq!(query.ttl_ms, Some(30_000));
1196        assert_eq!(query.with_metadata.len(), 1);
1197        assert_eq!(query.limit, Some(5));
1198        assert_eq!(query.order_by.len(), 2);
1199        assert!(matches!(
1200            &query.order_by[1].field,
1201            FieldRef::TableColumn { column, .. } if column == "rid"
1202        ));
1203        assert_eq!(
1204            query.returning.as_deref(),
1205            Some(
1206                [
1207                    ReturningItem::Column("weight".to_string()),
1208                    ReturningItem::Column("title".to_string())
1209                ]
1210                .as_slice()
1211            )
1212        );
1213        assert!(query.suppress_events);
1214    }
1215
1216    #[test]
1217    fn update_rejects_invalid_assignment_and_order_by_forms() {
1218        let mut parser = make_parser("UPDATE docs SET count ^= 1");
1219        let err = parser
1220            .parse_update_query()
1221            .expect_err("unknown compound assignment should fail");
1222        assert!(err.to_string().contains("expected"));
1223
1224        let mut parser = make_parser("UPDATE docs SET count = 1 ORDER BY updated_at");
1225        let err = parser
1226            .parse_update_query()
1227            .expect_err("ORDER BY without LIMIT should fail");
1228        assert!(err.to_string().contains("requires LIMIT"));
1229
1230        let mut parser = make_parser("UPDATE docs SET count = 1 ORDER BY updated_at + 1 LIMIT 1");
1231        let err = parser
1232            .parse_update_query()
1233            .expect_err("ORDER BY expression should fail");
1234        assert!(err.to_string().contains("top-level fields"));
1235    }
1236
1237    #[test]
1238    fn delete_returning_and_suppress_events() {
1239        let query = delete("DELETE FROM docs WHERE id = 1 RETURNING id, title SUPPRESS EVENTS");
1240        assert_eq!(query.table, "docs");
1241        assert!(query.filter.is_some());
1242        assert!(query.where_expr.is_some());
1243        assert_eq!(
1244            query.returning.as_deref(),
1245            Some(
1246                [
1247                    ReturningItem::Column("id".to_string()),
1248                    ReturningItem::Column("title".to_string())
1249                ]
1250                .as_slice()
1251            )
1252        );
1253        assert!(query.suppress_events);
1254
1255        let query = delete("DELETE FROM docs RETURNING *");
1256        assert_eq!(
1257            query.returning.as_deref(),
1258            Some([ReturningItem::All].as_slice())
1259        );
1260    }
1261
1262    #[test]
1263    fn returning_rejects_expression_forms() {
1264        for input in [
1265            "DELETE FROM docs RETURNING 1",
1266            "DELETE FROM docs RETURNING UPPER(title)",
1267            "DELETE FROM docs RETURNING title || body",
1268        ] {
1269            let mut parser = make_parser(input);
1270            let err = parser
1271                .parse_delete_query()
1272                .expect_err("RETURNING expression should fail");
1273            assert!(err.to_string().contains("RETURNING expressions"));
1274        }
1275    }
1276
1277    #[test]
1278    fn ask_parses_all_optional_clauses_and_cache_modes() {
1279        let query = ask(
1280            "ASK 'what changed?' USING 'openai' MODEL 'gpt' DEPTH 3 LIMIT 4 \
1281             MIN_SCORE 0.7 COLLECTION docs TEMPERATURE 0.2 SEED 42 STRICT OFF \
1282             STREAM CACHE TTL '10m'",
1283        );
1284        assert_eq!(query.question, "what changed?");
1285        assert_eq!(query.provider.as_deref(), Some("openai"));
1286        assert_eq!(query.model.as_deref(), Some("gpt"));
1287        assert_eq!(query.depth, Some(3));
1288        assert_eq!(query.limit, Some(4));
1289        assert_eq!(query.min_score, Some(0.7));
1290        assert_eq!(query.collection.as_deref(), Some("docs"));
1291        assert_eq!(query.temperature, Some(0.2));
1292        assert_eq!(query.seed, Some(42));
1293        assert!(!query.strict);
1294        assert!(query.stream);
1295        assert_eq!(query.cache, AskCacheClause::CacheTtl("10m".to_string()));
1296
1297        let query = ask("ASK ? NOCACHE");
1298        assert_eq!(query.question, "");
1299        assert_eq!(query.question_param, Some(0));
1300        assert_eq!(query.cache, AskCacheClause::NoCache);
1301    }
1302
1303    #[test]
1304    fn explain_ask_and_ask_error_paths() {
1305        let mut parser = make_parser("EXPLAIN ASK $2 STRICT ON");
1306        let QueryExpr::Ask(query) = parser.parse_explain_ask_query().expect("explain ask") else {
1307            panic!("expected ask query");
1308        };
1309        assert!(query.explain);
1310        assert_eq!(query.question_param, Some(1));
1311        assert!(query.strict);
1312
1313        let mut parser = make_parser("EXPLAIN SELECT 1");
1314        let err = parser
1315            .parse_explain_ask_query()
1316            .expect_err("missing ASK should fail");
1317        assert!(err.to_string().contains("expected"));
1318
1319        let mut parser = make_parser("ASK 'q' STRICT MAYBE");
1320        let err = parser
1321            .parse_ask_query()
1322            .expect_err("bad strict should fail");
1323        assert!(err.to_string().contains("Expected ON or OFF"));
1324
1325        let mut parser = make_parser("ASK 'q' CACHE TTL '10m' NOCACHE");
1326        let err = parser
1327            .parse_ask_query()
1328            .expect_err("duplicate cache should fail");
1329        assert!(err.to_string().contains("cache clause"));
1330
1331        let mut parser = make_parser("ASK 'q' CACHE FOREVER '10m'");
1332        let err = parser
1333            .parse_ask_query()
1334            .expect_err("bad cache ttl keyword should fail");
1335        assert!(err.to_string().contains("Expected TTL"));
1336    }
1337
1338    #[test]
1339    fn literal_value_special_constructors_arrays_and_objects() {
1340        let mut parser = make_parser("PASSWORD('pw')");
1341        assert!(matches!(
1342            parser.parse_literal_value().expect("password"),
1343            Value::Password(secret) if secret == "@@plain@@pw"
1344        ));
1345
1346        let mut parser = make_parser("SECRET('pw')");
1347        assert!(matches!(
1348            parser.parse_literal_value().expect("secret"),
1349            Value::Secret(bytes) if bytes == b"@@plain@@pw"
1350        ));
1351
1352        let mut parser = make_parser("SECRET_REF(vault, red.vault.api_key)");
1353        let value = parser.parse_literal_value().expect("secret ref");
1354        assert!(matches!(value, Value::Json(_)));
1355
1356        let mut parser = make_parser("[1, 2.5]");
1357        assert!(matches!(
1358            parser.parse_literal_value().expect("vector"),
1359            Value::Vector(values) if values == vec![1.0, 2.5]
1360        ));
1361
1362        let mut parser = make_parser("['a', 2]");
1363        assert!(matches!(
1364            parser.parse_literal_value().expect("json array"),
1365            Value::Json(_)
1366        ));
1367
1368        let mut parser = make_parser("{level = 'info', count: 2}");
1369        assert!(matches!(
1370            parser.parse_literal_value().expect("json object"),
1371            Value::Json(_)
1372        ));
1373    }
1374
1375    #[test]
1376    fn literal_value_rejects_invalid_secret_ref_and_scalar_start() {
1377        let mut parser = make_parser("SECRET_REF(config, red.vault.api_key)");
1378        let err = parser
1379            .parse_literal_value()
1380            .expect_err("non-vault secret ref should fail");
1381        assert!(err.to_string().contains("expected"));
1382
1383        let mut parser = make_parser("ORDER");
1384        let err = parser
1385            .parse_literal_value()
1386            .expect_err("non literal should fail");
1387        assert!(err.to_string().contains("expected"));
1388    }
1389
1390    #[test]
1391    fn json_depth_check_rejects_deep_literals() {
1392        let mut deep = reddb_types::utils::json::JsonValue::Array(vec![]);
1393        for _ in 0..JSON_LITERAL_MAX_DEPTH {
1394            deep = reddb_types::utils::json::JsonValue::Array(vec![deep]);
1395        }
1396        let err = json_literal_depth_check(&deep).expect_err("depth should fail");
1397        assert!(err.contains("JSON_LITERAL_MAX_DEPTH"));
1398    }
1399}