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.parse_update_assignment_target(target)?;
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 (claim_limit, claim_exact) = if self.consume_ident_ci("CLAIM")? {
391            if self.consume_ident_ci("EXACT")? {
392                (Some(self.parse_integer()? as u64), true)
393            } else {
394                self.expect(Token::Limit)?;
395                (Some(self.parse_integer()? as u64), false)
396            }
397        } else {
398            (None, false)
399        };
400
401        let mut order_by = if self.consume(&Token::Order)? {
402            self.expect(Token::By)?;
403            let clauses = self.parse_order_by_list()?;
404            validate_update_order_by(&clauses, self.position())?;
405            clauses
406        } else {
407            Vec::new()
408        };
409
410        // Optional `LIMIT N` — used by `BATCH N ROWS` data migrations
411        // to cap a single batch. Must come after WHERE / WITH because
412        // those have their own keyword tokens that the LIMIT branch
413        // would otherwise mis-consume.
414        let limit = if self.consume(&Token::Limit)? {
415            Some(self.parse_integer()? as u64)
416        } else {
417            None
418        };
419        // CLAIM LIMIT acts as the LIMIT for the purpose of ORDER BY semantics:
420        // a claim without a conventional LIMIT still has a deterministic bound.
421        let effective_limit = limit.is_some() || claim_limit.is_some();
422        if !order_by.is_empty() && !effective_limit {
423            return Err(ParseError::new(
424                "UPDATE ORDER BY requires LIMIT",
425                self.position(),
426            ));
427        }
428        if claim_limit.is_some() && order_by.is_empty() {
429            return Err(ParseError::new(
430                "UPDATE CLAIM requires ORDER BY",
431                self.position(),
432            ));
433        }
434        if !order_by.is_empty() && !update_order_by_mentions_rid(&order_by) {
435            order_by.push(OrderByClause {
436                field: FieldRef::TableColumn {
437                    table: String::new(),
438                    column: "rid".to_string(),
439                },
440                expr: None,
441                ascending: true,
442                nulls_first: false,
443            });
444        }
445
446        let returning = self.parse_returning_clause()?;
447
448        let suppress_events = if self.consume_ident_ci("SUPPRESS")? {
449            self.expect_ident_ci("EVENTS")?;
450            true
451        } else {
452            false
453        };
454
455        Ok(QueryExpr::Update(UpdateQuery {
456            table,
457            target,
458            assignment_exprs,
459            compound_assignment_ops,
460            assignments,
461            where_expr,
462            filter,
463            ttl_ms,
464            expires_at_ms,
465            with_metadata,
466            returning,
467            claim_limit,
468            claim_exact,
469            order_by,
470            limit,
471            suppress_events,
472        }))
473    }
474
475    fn parse_update_assignment_target(
476        &mut self,
477        target: UpdateTarget,
478    ) -> Result<String, ParseError> {
479        let mut segments = vec![self.expect_column_ident()?];
480        if matches!(target, UpdateTarget::Documents) {
481            while self.consume(&Token::Dot)? {
482                segments.push(self.expect_column_ident()?);
483            }
484        }
485        Ok(segments.join("."))
486    }
487
488    fn parse_update_target(&mut self) -> Result<UpdateTarget, ParseError> {
489        if self.consume(&Token::Kv)? {
490            return Ok(UpdateTarget::Kv);
491        }
492        if self.consume(&Token::Rows)? {
493            return Ok(UpdateTarget::Rows);
494        }
495        if self.consume_ident_ci("DOCUMENTS")? {
496            return Ok(UpdateTarget::Documents);
497        }
498        if self.consume_ident_ci("NODES")? {
499            return Ok(UpdateTarget::Nodes);
500        }
501        if self.consume_ident_ci("EDGES")? {
502            return Ok(UpdateTarget::Edges);
503        }
504        Ok(UpdateTarget::Rows)
505    }
506
507    /// Parse: DELETE FROM table [WHERE filter]
508    pub fn parse_delete_query(&mut self) -> Result<QueryExpr, ParseError> {
509        self.expect(Token::Delete)?;
510        self.expect(Token::From)?;
511        let table = self.expect_ident()?;
512
513        let filter = if self.consume(&Token::Where)? {
514            Some(self.parse_filter()?)
515        } else {
516            None
517        };
518
519        let where_expr = filter.as_ref().map(filter_to_expr);
520
521        let returning = self.parse_returning_clause()?;
522
523        let suppress_events = if self.consume_ident_ci("SUPPRESS")? {
524            self.expect_ident_ci("EVENTS")?;
525            true
526        } else {
527            false
528        };
529
530        Ok(QueryExpr::Delete(DeleteQuery {
531            table,
532            where_expr,
533            filter,
534            returning,
535            suppress_events,
536        }))
537    }
538
539    /// Parse optional `RETURNING (* | col [, col ...])` clause.
540    /// Returns `None` if no RETURNING token, errors if RETURNING is present
541    /// but not followed by `*` or a non-empty column list.
542    fn parse_returning_clause(&mut self) -> Result<Option<Vec<ReturningItem>>, ParseError> {
543        if !self.consume(&Token::Returning)? {
544            return Ok(None);
545        }
546        if self.consume(&Token::Star)? {
547            return Ok(Some(vec![ReturningItem::All]));
548        }
549        let mut items = Vec::new();
550        loop {
551            if returning_expr_start(self.peek()) {
552                return Err(returning_expr_not_supported(self.position()));
553            }
554            let col = self.expect_update_returning_column()?;
555            items.push(ReturningItem::Column(col));
556            if returning_expr_tail(self.peek()) {
557                return Err(returning_expr_not_supported(self.position()));
558            }
559            if !self.consume(&Token::Comma)? {
560                break;
561            }
562        }
563        if items.is_empty() {
564            return Err(ParseError::expected(
565                vec!["*", "column name"],
566                self.peek(),
567                self.position(),
568            ));
569        }
570        Ok(Some(items))
571    }
572
573    fn expect_update_returning_column(&mut self) -> Result<String, ParseError> {
574        if self.consume(&Token::Weight)? {
575            return Ok("weight".to_string());
576        }
577        self.expect_ident_or_keyword()
578    }
579
580    /// Parse: ASK 'question' [USING provider] [MODEL 'model'] [DEPTH n]
581    /// [LIMIT n] [MIN_SCORE x] [COLLECTION col] [AS RQL]
582    pub fn parse_ask_query(&mut self) -> Result<QueryExpr, ParseError> {
583        self.parse_ask_query_with_explain(false)
584    }
585
586    /// Parse: EXPLAIN ASK 'question' ...
587    pub fn parse_explain_ask_query(&mut self) -> Result<QueryExpr, ParseError> {
588        self.advance()?; // consume EXPLAIN
589        if !matches!(self.peek(), Token::Ident(name) if name.eq_ignore_ascii_case("ASK")) {
590            return Err(ParseError::expected(
591                vec!["ASK"],
592                self.peek(),
593                self.position(),
594            ));
595        }
596        self.parse_ask_query_with_explain(true)
597    }
598
599    fn parse_ask_query_with_explain(&mut self, explain: bool) -> Result<QueryExpr, ParseError> {
600        self.advance()?; // consume ASK
601
602        let (question, question_param) = match self.peek() {
603            Token::String(_) => (self.parse_string()?, None),
604            Token::Dollar | Token::Question => {
605                let index = self.parse_param_slot("ASK question")?;
606                (String::new(), Some(index))
607            }
608            other => {
609                return Err(ParseError::expected(
610                    vec!["string", "$N", "?"],
611                    other,
612                    self.position(),
613                ));
614            }
615        };
616
617        let mut provider = None;
618        let mut model = None;
619        let mut depth = None;
620        let mut limit = None;
621        let mut min_score = None;
622        let mut collection = None;
623        let mut temperature = None;
624        let mut seed = None;
625        let mut strict = true;
626        let mut stream = false;
627        let mut cache = AskCacheClause::Default;
628        let mut as_rql = false;
629        let mut execute = false;
630
631        // Parse optional clauses in any order. Loop bound = number of
632        // clause kinds, so each can appear at most once.
633        for _ in 0..14 {
634            if self.consume(&Token::Using)? {
635                provider = Some(match &self.current.token {
636                    Token::String(_) => self.parse_string()?,
637                    _ => self.expect_ident()?,
638                });
639            } else if self.consume_ident_ci("MODEL")? {
640                model = Some(self.parse_string()?);
641            } else if self.consume(&Token::Depth)? {
642                depth = Some(self.parse_integer()? as usize);
643            } else if self.consume(&Token::Limit)? {
644                limit = Some(self.parse_integer()? as usize);
645            } else if self.consume(&Token::MinScore)? {
646                min_score = Some(self.parse_float()? as f32);
647            } else if self.consume(&Token::Collection)? {
648                collection = Some(self.expect_ident()?);
649            } else if self.consume_ident_ci("TEMPERATURE")? {
650                temperature = Some(self.parse_float()? as f32);
651            } else if self.consume_ident_ci("SEED")? {
652                seed = Some(self.parse_integer()? as u64);
653            } else if self.consume_ident_ci("STRICT")? {
654                let value = self.expect_ident_or_keyword()?;
655                if value.eq_ignore_ascii_case("ON") {
656                    strict = true;
657                } else if value.eq_ignore_ascii_case("OFF") {
658                    strict = false;
659                } else {
660                    return Err(ParseError::new(
661                        "Expected ON or OFF after STRICT",
662                        self.position(),
663                    ));
664                }
665            } else if self.consume_ident_ci("STREAM")? {
666                stream = true;
667            } else if self.consume_ident_ci("CACHE")? {
668                if !matches!(cache, AskCacheClause::Default) {
669                    return Err(ParseError::new(
670                        "ASK cache clause specified more than once",
671                        self.position(),
672                    ));
673                }
674                let ttl = self.expect_ident_or_keyword()?;
675                if !ttl.eq_ignore_ascii_case("TTL") {
676                    return Err(ParseError::new("Expected TTL after CACHE", self.position()));
677                }
678                cache = AskCacheClause::CacheTtl(self.parse_string()?);
679            } else if self.consume_ident_ci("NOCACHE")? {
680                if !matches!(cache, AskCacheClause::Default) {
681                    return Err(ParseError::new(
682                        "ASK cache clause specified more than once",
683                        self.position(),
684                    ));
685                }
686                cache = AskCacheClause::NoCache;
687            } else if self.consume(&Token::As)? {
688                if as_rql {
689                    return Err(ParseError::new(
690                        "ASK AS RQL specified more than once",
691                        self.position(),
692                    ));
693                }
694                let output = self.expect_ident_or_keyword()?;
695                if !output.eq_ignore_ascii_case("RQL") {
696                    return Err(ParseError::new(
697                        "Expected RQL after ASK AS",
698                        self.position(),
699                    ));
700                }
701                as_rql = true;
702            } else if self.consume_ident_ci("EXECUTE")? {
703                if execute {
704                    return Err(ParseError::new(
705                        "ASK EXECUTE specified more than once",
706                        self.position(),
707                    ));
708                }
709                execute = true;
710            } else {
711                break;
712            }
713        }
714
715        Ok(QueryExpr::Ask(AskQuery {
716            explain,
717            question,
718            question_param,
719            provider,
720            model,
721            depth,
722            limit,
723            min_score,
724            collection,
725            temperature,
726            seed,
727            strict,
728            stream,
729            cache,
730            as_rql,
731            execute,
732        }))
733    }
734
735    /// Parse comma-separated identifiers (accepts keywords as column names in DML context)
736    fn parse_ident_list(&mut self) -> Result<Vec<String>, ParseError> {
737        let mut idents = Vec::new();
738        loop {
739            idents.push(self.expect_ident_or_keyword()?);
740            if !self.consume(&Token::Comma)? {
741                break;
742            }
743        }
744        Ok(idents)
745    }
746
747    /// Parse comma-separated literal values for DML statements
748    fn parse_dml_value_list(&mut self) -> Result<Vec<Value>, ParseError> {
749        self.parse_dml_expr_list()?
750            .into_iter()
751            .map(fold_expr_to_value)
752            .collect::<Result<Vec<_>, _>>()
753            .map_err(|msg| ParseError::new(msg, self.position()))
754    }
755
756    fn parse_dml_expr_list(&mut self) -> Result<Vec<Expr>, ParseError> {
757        let mut values = Vec::new();
758        loop {
759            values.push(self.parse_expr()?);
760            if !self.consume(&Token::Comma)? {
761                break;
762            }
763        }
764        Ok(values)
765    }
766
767    /// Parse a single literal value (string, number, true, false, null, array)
768    pub(crate) fn parse_literal_value(&mut self) -> Result<Value, ParseError> {
769        // Depth guard: this function recurses for nested array `[…]`
770        // and object `{…}` literals (see the LBracket / LBrace arms
771        // below). Without entering the depth counter, an adversarial
772        // payload like `[[[[…(10k×)…]]]]` would overflow the Rust
773        // stack BEFORE `ParserLimits::max_depth` fires. The
774        // `JsonLiteral` token path uses `json_literal_depth_check`
775        // (iterative) — the bare `[`/`{` path needs the recursion
776        // counter explicitly.
777        self.enter_depth()?;
778        let result = self.parse_literal_value_inner();
779        self.exit_depth();
780        result
781    }
782
783    fn parse_literal_value_inner(&mut self) -> Result<Value, ParseError> {
784        // Recognize PASSWORD('plaintext') and SECRET('plaintext') as
785        // typed literal constructors. The parser stores them as
786        // sentinel-prefixed values so that the INSERT executor can
787        // apply the crypto transform (argon2id hash / AES-256-GCM
788        // encrypt) without the parser depending on auth or crypto
789        // subsystems.
790        if let Token::Ident(name) = self.peek().clone() {
791            let upper = name.to_uppercase();
792            if upper == "PASSWORD" || upper == "SECRET" {
793                self.advance()?; // consume ident
794                self.expect(Token::LParen)?;
795                let plaintext = self.parse_string()?;
796                self.expect(Token::RParen)?;
797                return Ok(match upper.as_str() {
798                    "PASSWORD" => Value::Password(format!("@@plain@@{plaintext}")),
799                    "SECRET" => Value::Secret(format!("@@plain@@{plaintext}").into_bytes()),
800                    _ => unreachable!(),
801                });
802            }
803            if upper == "SECRET_REF" {
804                self.advance()?; // consume ident
805                self.expect(Token::LParen)?;
806                let store = self.expect_ident_or_keyword()?.to_ascii_lowercase();
807                if store != "vault" {
808                    return Err(ParseError::expected(
809                        vec!["vault"],
810                        self.peek(),
811                        self.position(),
812                    ));
813                }
814                self.expect(Token::Comma)?;
815                let (collection, key) =
816                    self.parse_kv_key(reddb_types::catalog::CollectionModel::Vault)?;
817                self.expect(Token::RParen)?;
818                return Ok(secret_ref_value(&store, &collection, &key));
819            }
820        }
821
822        match self.peek().clone() {
823            Token::String(s) => {
824                let s = s.clone();
825                self.advance()?;
826                Ok(Value::text(s))
827            }
828            Token::JsonLiteral(raw) => {
829                // The lexer already validated brace balance and the
830                // 16 MiB payload ceiling. Parse the raw text into a
831                // canonical JsonValue then re-encode via `to_vec` so
832                // the on-disk bytes match the quoted form.
833                self.advance()?;
834                let json_value = reddb_types::utils::json::parse_json(&raw).map_err(|err| {
835                    ParseError::new(
836                        // F-05: render the underlying parse-error string
837                        // via `{:?}` so any user fragment serde echoed
838                        // back (unexpected character, key text, …) is
839                        // Debug-escaped before reaching the downstream
840                        // JSON / audit / log / gRPC sinks.
841                        format!("invalid JSON object literal: {:?}", err.to_string()),
842                        self.position(),
843                    )
844                })?;
845                json_literal_depth_check(&json_value)
846                    .map_err(|err| ParseError::new(err, self.position()))?;
847                let canonical = reddb_types::serde_json::Value::from(json_value);
848                let bytes = reddb_types::json::to_vec(&canonical).map_err(|err| {
849                    ParseError::new(
850                        // F-05: escape the encoder error via `{:?}` so any
851                        // user fragment it carries cannot smuggle control
852                        // bytes through downstream serialization sinks.
853                        format!("failed to encode JSON literal: {:?}", err.to_string()),
854                        self.position(),
855                    )
856                })?;
857                Ok(Value::Json(bytes))
858            }
859            Token::Integer(n) => {
860                self.advance()?;
861                Ok(Value::Integer(n))
862            }
863            Token::Float(n) => {
864                self.advance()?;
865                Ok(Value::Float(n))
866            }
867            Token::True => {
868                self.advance()?;
869                Ok(Value::Boolean(true))
870            }
871            Token::False => {
872                self.advance()?;
873                Ok(Value::Boolean(false))
874            }
875            Token::Null => {
876                self.advance()?;
877                Ok(Value::Null)
878            }
879            Token::LBracket => {
880                // Parse array literal [val1, val2, ...]
881                // For numeric arrays, produce Value::Vector; for others, produce Value::Json
882                self.advance()?; // consume '['
883                let mut items = Vec::new();
884                if !self.check(&Token::RBracket) {
885                    loop {
886                        items.push(self.parse_literal_value()?);
887                        if !self.consume(&Token::Comma)? {
888                            break;
889                        }
890                    }
891                }
892                self.expect(Token::RBracket)?;
893
894                // Check if all items are numeric (Integer or Float) -> Value::Vector
895                let all_numeric = items
896                    .iter()
897                    .all(|v| matches!(v, Value::Integer(_) | Value::Float(_)));
898                if all_numeric && !items.is_empty() {
899                    let floats: Vec<f32> = items
900                        .iter()
901                        .map(|v| match v {
902                            Value::Float(f) => *f as f32,
903                            Value::Integer(i) => *i as f32,
904                            _ => 0.0,
905                        })
906                        .collect();
907                    Ok(Value::Vector(floats))
908                } else {
909                    // Encode as JSON bytes
910                    let json_arr: Vec<reddb_types::json::Value> = items
911                        .iter()
912                        .map(|v| match v {
913                            Value::Null => reddb_types::json::Value::Null,
914                            Value::Boolean(b) => reddb_types::json::Value::Bool(*b),
915                            Value::Integer(i) => reddb_types::json::Value::Number(*i as f64),
916                            Value::Float(f) => reddb_types::json::Value::Number(*f),
917                            Value::Text(s) => reddb_types::json::Value::String(s.to_string()),
918                            _ => reddb_types::json::Value::Null,
919                        })
920                        .collect();
921                    let json_val = reddb_types::json::Value::Array(json_arr);
922                    let bytes = reddb_types::json::to_vec(&json_val).unwrap_or_default();
923                    Ok(Value::Json(bytes))
924                }
925            }
926            Token::LBrace => {
927                // Parse JSON object literal {key: value, ...}
928                self.advance()?; // consume '{'
929                let mut map = reddb_types::json::Map::new();
930                if !self.check(&Token::RBrace) {
931                    loop {
932                        // Key: string or identifier. Reserved-word
933                        // keys (`level`, `msg`, `type`, …) fall through
934                        // to `expect_ident_or_keyword`, which returns
935                        // the canonical UPPERCASE spelling; lowercase
936                        // that path so the JSON object preserves the
937                        // source casing.
938                        let key = match self.peek().clone() {
939                            Token::String(s) => {
940                                self.advance()?;
941                                s
942                            }
943                            Token::Ident(s) => {
944                                self.advance()?;
945                                s
946                            }
947                            _ => self.expect_ident_or_keyword()?.to_ascii_lowercase(),
948                        };
949                        // Separator: ':' or '='
950                        if !self.consume(&Token::Colon)? {
951                            self.expect(Token::Eq)?;
952                        }
953                        // Value: recursive
954                        let val = self.parse_literal_value()?;
955                        let json_val = match val {
956                            Value::Null => reddb_types::json::Value::Null,
957                            Value::Boolean(b) => reddb_types::json::Value::Bool(b),
958                            Value::Integer(i) => reddb_types::json::Value::Number(i as f64),
959                            Value::Float(f) => reddb_types::json::Value::Number(f),
960                            Value::Text(s) => reddb_types::json::Value::String(s.to_string()),
961                            Value::Json(ref bytes) => reddb_types::json::from_slice(bytes)
962                                .unwrap_or(reddb_types::json::Value::Null),
963                            _ => reddb_types::json::Value::Null,
964                        };
965                        map.insert(key, json_val);
966                        if !self.consume(&Token::Comma)? {
967                            break;
968                        }
969                    }
970                }
971                self.expect(Token::RBrace)?;
972                let json_val = reddb_types::json::Value::Object(map);
973                let bytes = reddb_types::json::to_vec(&json_val).unwrap_or_default();
974                Ok(Value::Json(bytes))
975            }
976            ref other => Err(ParseError::expected(
977                vec!["string", "number", "true", "false", "null", "[", "{"],
978                other,
979                self.position(),
980            )),
981        }
982    }
983}
984
985fn returning_expr_start(token: &Token) -> bool {
986    matches!(
987        token,
988        Token::Integer(_)
989            | Token::Float(_)
990            | Token::String(_)
991            | Token::JsonLiteral(_)
992            | Token::Null
993            | Token::True
994            | Token::False
995            | Token::LParen
996            | Token::Minus
997            | Token::Question
998            | Token::Dollar
999    )
1000}
1001
1002fn returning_expr_tail(token: &Token) -> bool {
1003    matches!(
1004        token,
1005        Token::LParen
1006            | Token::Plus
1007            | Token::Minus
1008            | Token::Star
1009            | Token::Slash
1010            | Token::Percent
1011            | Token::DoublePipe
1012            | Token::Pipe
1013            | Token::Eq
1014            | Token::Ne
1015            | Token::Lt
1016            | Token::Le
1017            | Token::Gt
1018            | Token::Ge
1019            | Token::Dot
1020            | Token::Colon
1021    )
1022}
1023
1024fn validate_update_order_by(
1025    clauses: &[OrderByClause],
1026    position: crate::lexer::Position,
1027) -> Result<(), ParseError> {
1028    for clause in clauses {
1029        if clause.expr.is_some() {
1030            return Err(ParseError::new(
1031                "UPDATE ORDER BY only supports top-level fields",
1032                position,
1033            ));
1034        }
1035        match &clause.field {
1036            FieldRef::TableColumn { table, column }
1037                if table.is_empty() && !column.contains('.') => {}
1038            _ => {
1039                return Err(ParseError::new(
1040                    "UPDATE ORDER BY only supports top-level fields",
1041                    position,
1042                ));
1043            }
1044        }
1045    }
1046    Ok(())
1047}
1048
1049fn update_order_by_mentions_rid(clauses: &[OrderByClause]) -> bool {
1050    clauses.iter().any(|clause| {
1051        matches!(
1052            &clause.field,
1053            FieldRef::TableColumn { table, column }
1054                if table.is_empty() && column.eq_ignore_ascii_case("rid")
1055        )
1056    })
1057}
1058
1059fn returning_expr_not_supported(position: crate::lexer::Position) -> ParseError {
1060    ParseError::new(
1061        "NOT_YET_SUPPORTED: RETURNING expressions are not supported yet; use RETURNING * or named columns. Track a follow-up issue for RETURNING <expr>.",
1062        position,
1063    )
1064}
1065
1066fn secret_ref_value(store: &str, collection: &str, key: &str) -> Value {
1067    let mut map = reddb_types::json::Map::new();
1068    map.insert(
1069        "type".to_string(),
1070        reddb_types::json::Value::String("secret_ref".to_string()),
1071    );
1072    map.insert(
1073        "store".to_string(),
1074        reddb_types::json::Value::String(store.to_string()),
1075    );
1076    map.insert(
1077        "collection".to_string(),
1078        reddb_types::json::Value::String(collection.to_string()),
1079    );
1080    map.insert(
1081        "key".to_string(),
1082        reddb_types::json::Value::String(key.to_string()),
1083    );
1084    Value::Json(
1085        reddb_types::json::to_vec(&reddb_types::json::Value::Object(map)).unwrap_or_default(),
1086    )
1087}
1088
1089#[cfg(test)]
1090mod tests {
1091    use super::*;
1092    use crate::ast::{InsertEntityType, ReturningItem, UpdateTarget};
1093
1094    fn make_parser(input: &str) -> Parser<'_> {
1095        Parser::new(input).expect("lexer")
1096    }
1097
1098    fn insert(input: &str) -> InsertQuery {
1099        let mut parser = make_parser(input);
1100        let QueryExpr::Insert(query) = parser.parse_insert_query().expect("insert") else {
1101            panic!("expected insert query");
1102        };
1103        query
1104    }
1105
1106    fn update(input: &str) -> UpdateQuery {
1107        let mut parser = make_parser(input);
1108        let QueryExpr::Update(query) = parser.parse_update_query().expect("update") else {
1109            panic!("expected update query");
1110        };
1111        query
1112    }
1113
1114    fn delete(input: &str) -> DeleteQuery {
1115        let mut parser = make_parser(input);
1116        let QueryExpr::Delete(query) = parser.parse_delete_query().expect("delete") else {
1117            panic!("expected delete query");
1118        };
1119        query
1120    }
1121
1122    fn ask(input: &str) -> AskQuery {
1123        let mut parser = make_parser(input);
1124        let QueryExpr::Ask(query) = parser.parse_ask_query().expect("ask") else {
1125            panic!("expected ask query");
1126        };
1127        query
1128    }
1129
1130    #[test]
1131    fn insert_entity_types_with_options_returning_and_suppress_events() {
1132        let cases = [
1133            (
1134                "INSERT INTO items NODE (id) VALUES (1)",
1135                InsertEntityType::Node,
1136            ),
1137            (
1138                "INSERT INTO items EDGE (id) VALUES (1)",
1139                InsertEntityType::Edge,
1140            ),
1141            (
1142                "INSERT INTO items VECTOR (id) VALUES (1)",
1143                InsertEntityType::Vector,
1144            ),
1145            (
1146                "INSERT INTO items DOCUMENT (id) VALUES (1)",
1147                InsertEntityType::Document,
1148            ),
1149            ("INSERT INTO items KV (id) VALUES (1)", InsertEntityType::Kv),
1150        ];
1151        for (input, expected) in cases {
1152            assert_eq!(insert(input).entity_type, expected, "{input}");
1153        }
1154
1155        let query = insert(
1156            "INSERT INTO docs (id, body) VALUES (1, 'red'), (2, ?) \
1157             WITH TTL 2 h WITH EXPIRES AT 999 \
1158             WITH METADATA (source = 'test', score = 3) \
1159             WITH AUTO EMBED (body, title) USING openai MODEL 'text-embedding-3-small' \
1160             RETURNING * SUPPRESS EVENTS",
1161        );
1162        assert_eq!(query.table, "docs");
1163        assert_eq!(query.entity_type, InsertEntityType::Row);
1164        assert_eq!(query.columns, vec!["id", "body"]);
1165        assert_eq!(query.values.len(), 2);
1166        assert_eq!(query.value_exprs.len(), 2);
1167        assert_eq!(query.ttl_ms, Some(7_200_000));
1168        assert_eq!(query.expires_at_ms, Some(999));
1169        assert_eq!(query.with_metadata.len(), 2);
1170        assert_eq!(
1171            query.returning.as_deref(),
1172            Some([ReturningItem::All].as_slice())
1173        );
1174        let auto_embed = query.auto_embed.expect("auto embed");
1175        assert_eq!(auto_embed.fields, vec!["body", "title"]);
1176        assert_eq!(auto_embed.provider, "openai");
1177        assert_eq!(auto_embed.model.as_deref(), Some("text-embedding-3-small"));
1178        assert!(query.suppress_events);
1179    }
1180
1181    #[test]
1182    fn insert_rejects_metric_and_bad_with_clause() {
1183        let mut parser = make_parser("INSERT INTO METRIC cpu.usage (value) VALUES (1)");
1184        let err = parser
1185            .parse_insert_query()
1186            .expect_err("metric insert should fail");
1187        assert!(err.to_string().contains("INSERT INTO METRIC"));
1188
1189        let mut parser = make_parser("INSERT INTO docs (id) VALUES (1) WITH TTL 1 fortnight");
1190        let err = parser
1191            .parse_insert_query()
1192            .expect_err("bad ttl unit should fail");
1193        assert!(err.to_string().contains("unsupported TTL unit"));
1194
1195        let mut parser = make_parser("INSERT INTO docs (id) VALUES (1) WITH UNKNOWN");
1196        let err = parser
1197            .parse_insert_query()
1198            .expect_err("bad WITH should fail");
1199        assert!(err.to_string().contains("expected"));
1200    }
1201
1202    #[test]
1203    fn update_targets_compound_assignments_order_limit_returning() {
1204        let cases = [
1205            ("UPDATE docs KV SET count = 1", UpdateTarget::Kv),
1206            ("UPDATE docs ROWS SET count = 1", UpdateTarget::Rows),
1207            (
1208                "UPDATE docs DOCUMENTS SET count = 1",
1209                UpdateTarget::Documents,
1210            ),
1211            ("UPDATE docs NODES SET count = 1", UpdateTarget::Nodes),
1212            ("UPDATE docs EDGES SET count = 1", UpdateTarget::Edges),
1213        ];
1214        for (input, expected) in cases {
1215            assert_eq!(update(input).target, expected, "{input}");
1216        }
1217
1218        let query = update(
1219            "UPDATE docs DOCUMENTS SET count += 2, title = UPPER(title) \
1220             WHERE id = 1 WITH TTL 30 s WITH METADATA (source = 'update') \
1221             ORDER BY updated_at DESC LIMIT 5 RETURNING weight, title SUPPRESS EVENTS",
1222        );
1223        assert_eq!(query.table, "docs");
1224        assert_eq!(query.target, UpdateTarget::Documents);
1225        assert_eq!(query.assignment_exprs.len(), 2);
1226        assert_eq!(query.compound_assignment_ops, vec![Some(BinOp::Add), None]);
1227        assert_eq!(query.assignments.len(), 0);
1228        assert!(query.filter.is_some());
1229        assert!(query.where_expr.is_some());
1230        assert_eq!(query.ttl_ms, Some(30_000));
1231        assert_eq!(query.with_metadata.len(), 1);
1232        assert_eq!(query.claim_limit, None);
1233        assert!(!query.claim_exact);
1234        assert_eq!(query.limit, Some(5));
1235        assert_eq!(query.order_by.len(), 2);
1236        assert!(matches!(
1237            &query.order_by[1].field,
1238            FieldRef::TableColumn { column, .. } if column == "rid"
1239        ));
1240        assert_eq!(
1241            query.returning.as_deref(),
1242            Some(
1243                [
1244                    ReturningItem::Column("weight".to_string()),
1245                    ReturningItem::Column("title".to_string())
1246                ]
1247                .as_slice()
1248            )
1249        );
1250        assert!(query.suppress_events);
1251    }
1252
1253    #[test]
1254    fn update_claim_limit_requires_order_by() {
1255        let query = update(
1256            "UPDATE docs SET status = 'reserved' WHERE status = 'available' \
1257             CLAIM LIMIT 2 ORDER BY priority ASC RETURNING id",
1258        );
1259        assert_eq!(query.claim_limit, Some(2));
1260        assert!(!query.claim_exact);
1261        assert_eq!(query.limit, None);
1262        assert_eq!(query.order_by.len(), 2);
1263        assert!(matches!(
1264            &query.order_by[1].field,
1265            FieldRef::TableColumn { column, .. } if column == "rid"
1266        ));
1267
1268        let query = update(
1269            "UPDATE docs SET status = 'reserved' WHERE status = 'available' \
1270             CLAIM EXACT 2 ORDER BY priority ASC RETURNING id",
1271        );
1272        assert_eq!(query.claim_limit, Some(2));
1273        assert!(query.claim_exact);
1274        assert_eq!(query.limit, None);
1275        assert_eq!(query.order_by.len(), 2);
1276        assert!(matches!(
1277            &query.order_by[1].field,
1278            FieldRef::TableColumn { column, .. } if column == "rid"
1279        ));
1280
1281        let mut parser = make_parser("UPDATE docs SET status = 'reserved' CLAIM LIMIT 1");
1282        let err = parser
1283            .parse_update_query()
1284            .expect_err("claim without order should fail");
1285        assert!(err.to_string().contains("CLAIM requires ORDER BY"));
1286    }
1287
1288    #[test]
1289    fn update_rejects_invalid_assignment_and_order_by_forms() {
1290        let mut parser = make_parser("UPDATE docs SET count ^= 1");
1291        let err = parser
1292            .parse_update_query()
1293            .expect_err("unknown compound assignment should fail");
1294        assert!(err.to_string().contains("expected"));
1295
1296        let mut parser = make_parser("UPDATE docs SET count = 1 ORDER BY updated_at");
1297        let err = parser
1298            .parse_update_query()
1299            .expect_err("ORDER BY without LIMIT should fail");
1300        assert!(err.to_string().contains("requires LIMIT"));
1301
1302        let mut parser = make_parser("UPDATE docs SET count = 1 ORDER BY updated_at + 1 LIMIT 1");
1303        let err = parser
1304            .parse_update_query()
1305            .expect_err("ORDER BY expression should fail");
1306        assert!(err.to_string().contains("top-level fields"));
1307    }
1308
1309    #[test]
1310    fn delete_returning_and_suppress_events() {
1311        let query = delete("DELETE FROM docs WHERE id = 1 RETURNING id, title SUPPRESS EVENTS");
1312        assert_eq!(query.table, "docs");
1313        assert!(query.filter.is_some());
1314        assert!(query.where_expr.is_some());
1315        assert_eq!(
1316            query.returning.as_deref(),
1317            Some(
1318                [
1319                    ReturningItem::Column("id".to_string()),
1320                    ReturningItem::Column("title".to_string())
1321                ]
1322                .as_slice()
1323            )
1324        );
1325        assert!(query.suppress_events);
1326
1327        let query = delete("DELETE FROM docs RETURNING *");
1328        assert_eq!(
1329            query.returning.as_deref(),
1330            Some([ReturningItem::All].as_slice())
1331        );
1332    }
1333
1334    #[test]
1335    fn returning_rejects_expression_forms() {
1336        for input in [
1337            "DELETE FROM docs RETURNING 1",
1338            "DELETE FROM docs RETURNING UPPER(title)",
1339            "DELETE FROM docs RETURNING title || body",
1340        ] {
1341            let mut parser = make_parser(input);
1342            let err = parser
1343                .parse_delete_query()
1344                .expect_err("RETURNING expression should fail");
1345            assert!(err.to_string().contains("RETURNING expressions"));
1346        }
1347    }
1348
1349    #[test]
1350    fn ask_parses_all_optional_clauses_and_cache_modes() {
1351        let query = ask(
1352            "ASK 'what changed?' USING 'openai' MODEL 'gpt' DEPTH 3 LIMIT 4 \
1353             MIN_SCORE 0.7 COLLECTION docs TEMPERATURE 0.2 SEED 42 STRICT OFF \
1354             STREAM CACHE TTL '10m'",
1355        );
1356        assert_eq!(query.question, "what changed?");
1357        assert_eq!(query.provider.as_deref(), Some("openai"));
1358        assert_eq!(query.model.as_deref(), Some("gpt"));
1359        assert_eq!(query.depth, Some(3));
1360        assert_eq!(query.limit, Some(4));
1361        assert_eq!(query.min_score, Some(0.7));
1362        assert_eq!(query.collection.as_deref(), Some("docs"));
1363        assert_eq!(query.temperature, Some(0.2));
1364        assert_eq!(query.seed, Some(42));
1365        assert!(!query.strict);
1366        assert!(query.stream);
1367        assert_eq!(query.cache, AskCacheClause::CacheTtl("10m".to_string()));
1368
1369        let query = ask("ASK ? NOCACHE");
1370        assert_eq!(query.question, "");
1371        assert_eq!(query.question_param, Some(0));
1372        assert_eq!(query.cache, AskCacheClause::NoCache);
1373    }
1374
1375    #[test]
1376    fn explain_ask_and_ask_error_paths() {
1377        let mut parser = make_parser("EXPLAIN ASK $2 STRICT ON");
1378        let QueryExpr::Ask(query) = parser.parse_explain_ask_query().expect("explain ask") else {
1379            panic!("expected ask query");
1380        };
1381        assert!(query.explain);
1382        assert_eq!(query.question_param, Some(1));
1383        assert!(query.strict);
1384
1385        let mut parser = make_parser("EXPLAIN SELECT 1");
1386        let err = parser
1387            .parse_explain_ask_query()
1388            .expect_err("missing ASK should fail");
1389        assert!(err.to_string().contains("expected"));
1390
1391        let mut parser = make_parser("ASK 'q' STRICT MAYBE");
1392        let err = parser
1393            .parse_ask_query()
1394            .expect_err("bad strict should fail");
1395        assert!(err.to_string().contains("Expected ON or OFF"));
1396
1397        let mut parser = make_parser("ASK 'q' CACHE TTL '10m' NOCACHE");
1398        let err = parser
1399            .parse_ask_query()
1400            .expect_err("duplicate cache should fail");
1401        assert!(err.to_string().contains("cache clause"));
1402
1403        let mut parser = make_parser("ASK 'q' CACHE FOREVER '10m'");
1404        let err = parser
1405            .parse_ask_query()
1406            .expect_err("bad cache ttl keyword should fail");
1407        assert!(err.to_string().contains("Expected TTL"));
1408    }
1409
1410    #[test]
1411    fn literal_value_special_constructors_arrays_and_objects() {
1412        let mut parser = make_parser("PASSWORD('pw')");
1413        assert!(matches!(
1414            parser.parse_literal_value().expect("password"),
1415            Value::Password(secret) if secret == "@@plain@@pw"
1416        ));
1417
1418        let mut parser = make_parser("SECRET('pw')");
1419        assert!(matches!(
1420            parser.parse_literal_value().expect("secret"),
1421            Value::Secret(bytes) if bytes == b"@@plain@@pw"
1422        ));
1423
1424        let mut parser = make_parser("SECRET_REF(vault, red.vault.api_key)");
1425        let value = parser.parse_literal_value().expect("secret ref");
1426        assert!(matches!(value, Value::Json(_)));
1427
1428        let mut parser = make_parser("[1, 2.5]");
1429        assert!(matches!(
1430            parser.parse_literal_value().expect("vector"),
1431            Value::Vector(values) if values == vec![1.0, 2.5]
1432        ));
1433
1434        let mut parser = make_parser("['a', 2]");
1435        assert!(matches!(
1436            parser.parse_literal_value().expect("json array"),
1437            Value::Json(_)
1438        ));
1439
1440        let mut parser = make_parser("{level = 'info', count: 2}");
1441        assert!(matches!(
1442            parser.parse_literal_value().expect("json object"),
1443            Value::Json(_)
1444        ));
1445    }
1446
1447    #[test]
1448    fn literal_value_rejects_invalid_secret_ref_and_scalar_start() {
1449        let mut parser = make_parser("SECRET_REF(config, red.vault.api_key)");
1450        let err = parser
1451            .parse_literal_value()
1452            .expect_err("non-vault secret ref should fail");
1453        assert!(err.to_string().contains("expected"));
1454
1455        let mut parser = make_parser("ORDER");
1456        let err = parser
1457            .parse_literal_value()
1458            .expect_err("non literal should fail");
1459        assert!(err.to_string().contains("expected"));
1460    }
1461
1462    #[test]
1463    fn json_depth_check_rejects_deep_literals() {
1464        let mut deep = reddb_types::utils::json::JsonValue::Array(vec![]);
1465        for _ in 0..JSON_LITERAL_MAX_DEPTH {
1466            deep = reddb_types::utils::json::JsonValue::Array(vec![deep]);
1467        }
1468        let err = json_literal_depth_check(&deep).expect_err("depth should fail");
1469        assert!(err.contains("JSON_LITERAL_MAX_DEPTH"));
1470    }
1471}