Skip to main content

reddb_server/storage/query/parser/
dml.rs

1//! DML SQL Parser: INSERT, UPDATE, DELETE
2
3use super::super::ast::{
4    AskQuery, DeleteQuery, Expr, Filter, InsertEntityType, InsertQuery, QueryExpr, ReturningItem,
5    UpdateQuery,
6};
7use super::super::lexer::Token;
8use super::error::ParseError;
9use super::Parser;
10use crate::storage::query::sql_lowering::{filter_to_expr, fold_expr_to_value};
11use crate::storage::schema::Value;
12
13/// DoS guard: maximum JSON nesting depth accepted by the parser.
14/// Mirrors typical web-server JSON limits and bails out before stack
15/// usage gets dangerous in downstream traversals.
16pub(crate) const JSON_LITERAL_MAX_DEPTH: u32 = 128;
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: &crate::utils::json::JsonValue,
23) -> Result<(), String> {
24    use crate::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        let table = self.expect_ident()?;
56
57        // Check for entity type keyword
58        let entity_type = match self.peek().clone() {
59            Token::Node => {
60                self.advance()?;
61                InsertEntityType::Node
62            }
63            Token::Edge => {
64                self.advance()?;
65                InsertEntityType::Edge
66            }
67            Token::Vector => {
68                self.advance()?;
69                InsertEntityType::Vector
70            }
71            Token::Document => {
72                self.advance()?;
73                InsertEntityType::Document
74            }
75            Token::Kv => {
76                self.advance()?;
77                InsertEntityType::Kv
78            }
79            _ => InsertEntityType::Row,
80        };
81
82        // Parse column list
83        self.expect(Token::LParen)?;
84        let columns = self.parse_ident_list()?;
85        self.expect(Token::RParen)?;
86
87        // Parse VALUES
88        self.expect(Token::Values)?;
89        let mut all_values = Vec::new();
90        let mut all_value_exprs = Vec::new();
91        loop {
92            self.expect(Token::LParen)?;
93            let row_exprs = self.parse_dml_expr_list()?;
94            self.expect(Token::RParen)?;
95            // Tolerate `$N` / `?` placeholders in VALUES rows: fold to
96            // Value::Null and rely on `user_params::bind` to substitute
97            // the caller's values before execution. Issue #355.
98            // Tolerate `$N` / `?` placeholders in VALUES rows: if fold
99            // fails on an expression that contains `Expr::Parameter`,
100            // emit a `Value::Null` placeholder. `user_params::bind`
101            // substitutes the caller-supplied value before execution.
102            // Issue #355.
103            let row_values = row_exprs
104                .iter()
105                .map(|expr| match fold_expr_to_value(expr.clone()) {
106                    Ok(value) => Ok(value),
107                    Err(msg) => {
108                        if crate::storage::query::user_params::expr_contains_parameter(&expr) {
109                            Ok(Value::Null)
110                        } else {
111                            Err(msg)
112                        }
113                    }
114                })
115                .collect::<Result<Vec<_>, _>>()
116                .map_err(|msg| ParseError::new(msg, self.position()))?;
117            all_value_exprs.push(row_exprs);
118            all_values.push(row_values);
119            if !self.consume(&Token::Comma)? {
120                break;
121            }
122        }
123
124        // Parse optional WITH clauses
125        let (ttl_ms, expires_at_ms, with_metadata, auto_embed) = self.parse_with_clauses()?;
126
127        let returning = self.parse_returning_clause()?;
128
129        let suppress_events = if self.consume_ident_ci("SUPPRESS")? {
130            self.expect_ident_ci("EVENTS")?;
131            true
132        } else {
133            false
134        };
135
136        Ok(QueryExpr::Insert(InsertQuery {
137            table,
138            entity_type,
139            columns,
140            value_exprs: all_value_exprs,
141            values: all_values,
142            returning,
143            ttl_ms,
144            expires_at_ms,
145            with_metadata,
146            auto_embed,
147            suppress_events,
148        }))
149    }
150
151    /// Parse TTL duration value using the same logic as CREATE TABLE ... WITH TTL.
152    fn parse_ttl_duration(&mut self) -> Result<u64, ParseError> {
153        // Reuse the DDL TTL parser: expects a number followed by optional unit
154        let ttl_value = self.parse_float()?;
155        let ttl_unit = match self.peek() {
156            Token::Ident(unit) => {
157                let unit = unit.clone();
158                self.advance()?;
159                unit
160            }
161            _ => "s".to_string(),
162        };
163
164        let multiplier_ms = match ttl_unit.to_ascii_lowercase().as_str() {
165            "ms" | "msec" | "millisecond" | "milliseconds" => 1.0,
166            "s" | "sec" | "secs" | "second" | "seconds" => 1_000.0,
167            "m" | "min" | "mins" | "minute" | "minutes" => 60_000.0,
168            "h" | "hr" | "hrs" | "hour" | "hours" => 3_600_000.0,
169            "d" | "day" | "days" => 86_400_000.0,
170            other => {
171                return Err(ParseError::new(
172                    // F-05: render `other` via `{:?}` so caller-controlled
173                    // bytes (CR / LF / NUL / quotes) are escaped before
174                    // landing in the JSON/audit/log/gRPC error sinks.
175                    format!("unsupported TTL unit {other:?}"),
176                    self.position(),
177                ));
178            }
179        };
180
181        Ok((ttl_value * multiplier_ms) as u64)
182    }
183
184    /// Parse WITH clauses: WITH TTL | EXPIRES AT | METADATA | AUTO EMBED
185    /// Returns (ttl_ms, expires_at_ms, metadata, auto_embed)
186    pub fn parse_with_clauses(
187        &mut self,
188    ) -> Result<
189        (
190            Option<u64>,
191            Option<u64>,
192            Vec<(String, Value)>,
193            Option<crate::storage::query::ast::AutoEmbedConfig>,
194        ),
195        ParseError,
196    > {
197        let mut ttl_ms = None;
198        let mut expires_at_ms = None;
199        let mut with_metadata = Vec::new();
200        let mut auto_embed = None;
201
202        while self.consume(&Token::With)? {
203            if self.consume_ident_ci("TTL")? {
204                ttl_ms = Some(self.parse_ttl_duration()?);
205            } else if self.consume_ident_ci("EXPIRES")? {
206                self.expect_ident_ci("AT")?;
207                let ts = self.parse_expires_at_value()?;
208                expires_at_ms = Some(ts);
209            } else if self.consume(&Token::Metadata)? || self.consume_ident_ci("METADATA")? {
210                with_metadata = self.parse_with_metadata_pairs()?;
211            } else if self.consume_ident_ci("AUTO")? {
212                // WITH AUTO EMBED (field1, field2) [USING provider] [MODEL 'model']
213                self.consume_ident_ci("EMBED")?;
214                self.expect(Token::LParen)?;
215                let mut fields = Vec::new();
216                loop {
217                    fields.push(self.expect_ident()?);
218                    if !self.consume(&Token::Comma)? {
219                        break;
220                    }
221                }
222                self.expect(Token::RParen)?;
223                // `USING` is a reserved keyword (`Token::Using`), so
224                // `consume_ident_ci` would never match. Use the typed
225                // consumer instead. See bug #108 (mirrors the #92 fix
226                // for migration `DEPENDS ON`).
227                let provider = if self.consume(&Token::Using)? {
228                    self.expect_ident()?
229                } else {
230                    "openai".to_string()
231                };
232                let model = if self.consume_ident_ci("MODEL")? {
233                    Some(self.parse_string()?)
234                } else {
235                    None
236                };
237                auto_embed = Some(crate::storage::query::ast::AutoEmbedConfig {
238                    fields,
239                    provider,
240                    model,
241                });
242            } else {
243                return Err(ParseError::expected(
244                    vec!["TTL", "EXPIRES AT", "METADATA", "AUTO EMBED"],
245                    self.peek(),
246                    self.position(),
247                ));
248            }
249        }
250
251        Ok((ttl_ms, expires_at_ms, with_metadata, auto_embed))
252    }
253
254    /// Expect a case-insensitive identifier (error if not found)
255    fn expect_ident_ci(&mut self, expected: &str) -> Result<(), ParseError> {
256        if self.consume_ident_ci(expected)? {
257            Ok(())
258        } else {
259            Err(ParseError::expected(
260                vec![expected],
261                self.peek(),
262                self.position(),
263            ))
264        }
265    }
266
267    /// Parse an absolute expiration timestamp (unix ms or string date)
268    fn parse_expires_at_value(&mut self) -> Result<u64, ParseError> {
269        // Try integer (unix timestamp in ms)
270        if let Ok(value) = self.parse_integer() {
271            return Ok(value as u64);
272        }
273        // Try string like '2026-12-31' — convert to unix ms
274        if let Ok(text) = self.parse_string() {
275            // Simple ISO date parsing: YYYY-MM-DD or YYYY-MM-DDTHH:MM:SS
276            let trimmed = text.trim();
277            if let Ok(ts) = trimmed.parse::<u64>() {
278                return Ok(ts);
279            }
280            // Basic date parsing — delegate to chrono if available, or simple heuristic
281            return Err(ParseError::new(
282                // F-05: `trimmed` is caller-controlled string-literal bytes.
283                // Render via `{:?}` so CR/LF/NUL/quotes are escaped before
284                // the message reaches the JSON / audit / log / gRPC sinks.
285                format!("EXPIRES AT requires a unix timestamp in milliseconds, got {trimmed:?}"),
286                self.position(),
287            ));
288        }
289        Err(ParseError::expected(
290            vec!["timestamp (unix ms) or 'YYYY-MM-DD'"],
291            self.peek(),
292            self.position(),
293        ))
294    }
295
296    /// Parse WITH METADATA (key1 = 'value1', key2 = 42)
297    fn parse_with_metadata_pairs(&mut self) -> Result<Vec<(String, Value)>, ParseError> {
298        self.expect(Token::LParen)?;
299        let mut pairs = Vec::new();
300        if !self.check(&Token::RParen) {
301            loop {
302                let key = self.expect_ident_or_keyword()?.to_ascii_lowercase();
303                self.expect(Token::Eq)?;
304                let value = self.parse_literal_value()?;
305                pairs.push((key, value));
306                if !self.consume(&Token::Comma)? {
307                    break;
308                }
309            }
310        }
311        self.expect(Token::RParen)?;
312        Ok(pairs)
313    }
314
315    /// Parse: UPDATE table SET col1=val1, col2=val2 [WHERE filter] [WITH TTL|EXPIRES AT|METADATA]
316    pub fn parse_update_query(&mut self) -> Result<QueryExpr, ParseError> {
317        self.expect(Token::Update)?;
318        let table = self.expect_ident()?;
319        self.expect(Token::Set)?;
320
321        let mut assignments = Vec::new();
322        let mut assignment_exprs = Vec::new();
323        loop {
324            let col = self.expect_ident()?;
325            self.expect(Token::Eq)?;
326            let expr = self.parse_expr()?;
327            let folded = fold_expr_to_value(expr.clone()).ok();
328            assignment_exprs.push((col.clone(), expr));
329            if let Some(val) = folded {
330                assignments.push((col.clone(), val));
331            }
332            if !self.consume(&Token::Comma)? {
333                break;
334            }
335        }
336
337        let filter = if self.consume(&Token::Where)? {
338            Some(self.parse_filter()?)
339        } else {
340            None
341        };
342        let where_expr = filter.as_ref().map(filter_to_expr);
343
344        let (ttl_ms, expires_at_ms, with_metadata, _auto_embed) = self.parse_with_clauses()?;
345
346        // Optional `LIMIT N` — used by `BATCH N ROWS` data migrations
347        // to cap a single batch. Must come after WHERE / WITH because
348        // those have their own keyword tokens that the LIMIT branch
349        // would otherwise mis-consume.
350        let limit = if self.consume(&Token::Limit)? {
351            Some(self.parse_integer()? as u64)
352        } else {
353            None
354        };
355
356        let returning = self.parse_returning_clause()?;
357
358        let suppress_events = if self.consume_ident_ci("SUPPRESS")? {
359            self.expect_ident_ci("EVENTS")?;
360            true
361        } else {
362            false
363        };
364
365        Ok(QueryExpr::Update(UpdateQuery {
366            table,
367            assignment_exprs,
368            assignments,
369            where_expr,
370            filter,
371            ttl_ms,
372            expires_at_ms,
373            with_metadata,
374            returning,
375            limit,
376            suppress_events,
377        }))
378    }
379
380    /// Parse: DELETE FROM table [WHERE filter]
381    pub fn parse_delete_query(&mut self) -> Result<QueryExpr, ParseError> {
382        self.expect(Token::Delete)?;
383        self.expect(Token::From)?;
384        let table = self.expect_ident()?;
385
386        let filter = if self.consume(&Token::Where)? {
387            Some(self.parse_filter()?)
388        } else {
389            None
390        };
391
392        let where_expr = filter.as_ref().map(filter_to_expr);
393
394        let returning = self.parse_returning_clause()?;
395
396        let suppress_events = if self.consume_ident_ci("SUPPRESS")? {
397            self.expect_ident_ci("EVENTS")?;
398            true
399        } else {
400            false
401        };
402
403        Ok(QueryExpr::Delete(DeleteQuery {
404            table,
405            where_expr,
406            filter,
407            returning,
408            suppress_events,
409        }))
410    }
411
412    /// Parse optional `RETURNING (* | col [, col ...])` clause.
413    /// Returns `None` if no RETURNING token, errors if RETURNING is present
414    /// but not followed by `*` or a non-empty column list.
415    fn parse_returning_clause(&mut self) -> Result<Option<Vec<ReturningItem>>, ParseError> {
416        if !self.consume(&Token::Returning)? {
417            return Ok(None);
418        }
419        if self.consume(&Token::Star)? {
420            return Ok(Some(vec![ReturningItem::All]));
421        }
422        let mut items = Vec::new();
423        loop {
424            let col = self.expect_ident_or_keyword()?;
425            items.push(ReturningItem::Column(col));
426            if !self.consume(&Token::Comma)? {
427                break;
428            }
429        }
430        if items.is_empty() {
431            return Err(ParseError::expected(
432                vec!["*", "column name"],
433                self.peek(),
434                self.position(),
435            ));
436        }
437        Ok(Some(items))
438    }
439
440    /// Parse: ASK 'question' [USING provider] [MODEL 'model'] [DEPTH n] [LIMIT n] [COLLECTION col]
441    pub fn parse_ask_query(&mut self) -> Result<QueryExpr, ParseError> {
442        self.advance()?; // consume ASK
443
444        let question = self.parse_string()?;
445
446        let mut provider = None;
447        let mut model = None;
448        let mut depth = None;
449        let mut limit = None;
450        let mut collection = None;
451
452        // Parse optional clauses in any order
453        for _ in 0..5 {
454            if self.consume(&Token::Using)? {
455                provider = Some(self.expect_ident()?);
456            } else if self.consume_ident_ci("MODEL")? {
457                model = Some(self.parse_string()?);
458            } else if self.consume(&Token::Depth)? {
459                depth = Some(self.parse_integer()? as usize);
460            } else if self.consume(&Token::Limit)? {
461                limit = Some(self.parse_integer()? as usize);
462            } else if self.consume(&Token::Collection)? {
463                collection = Some(self.expect_ident()?);
464            } else {
465                break;
466            }
467        }
468
469        Ok(QueryExpr::Ask(AskQuery {
470            question,
471            provider,
472            model,
473            depth,
474            limit,
475            collection,
476        }))
477    }
478
479    /// Parse comma-separated identifiers (accepts keywords as column names in DML context)
480    fn parse_ident_list(&mut self) -> Result<Vec<String>, ParseError> {
481        let mut idents = Vec::new();
482        loop {
483            idents.push(self.expect_ident_or_keyword()?);
484            if !self.consume(&Token::Comma)? {
485                break;
486            }
487        }
488        Ok(idents)
489    }
490
491    /// Parse comma-separated literal values for DML statements
492    fn parse_dml_value_list(&mut self) -> Result<Vec<Value>, ParseError> {
493        self.parse_dml_expr_list()?
494            .into_iter()
495            .map(fold_expr_to_value)
496            .collect::<Result<Vec<_>, _>>()
497            .map_err(|msg| ParseError::new(msg, self.position()))
498    }
499
500    fn parse_dml_expr_list(&mut self) -> Result<Vec<Expr>, ParseError> {
501        let mut values = Vec::new();
502        loop {
503            values.push(self.parse_expr()?);
504            if !self.consume(&Token::Comma)? {
505                break;
506            }
507        }
508        Ok(values)
509    }
510
511    /// Parse a single literal value (string, number, true, false, null, array)
512    pub(crate) fn parse_literal_value(&mut self) -> Result<Value, ParseError> {
513        // Depth guard: this function recurses for nested array `[…]`
514        // and object `{…}` literals (see the LBracket / LBrace arms
515        // below). Without entering the depth counter, an adversarial
516        // payload like `[[[[…(10k×)…]]]]` would overflow the Rust
517        // stack BEFORE `ParserLimits::max_depth` fires. The
518        // `JsonLiteral` token path uses `json_literal_depth_check`
519        // (iterative) — the bare `[`/`{` path needs the recursion
520        // counter explicitly.
521        self.enter_depth()?;
522        let result = self.parse_literal_value_inner();
523        self.exit_depth();
524        result
525    }
526
527    fn parse_literal_value_inner(&mut self) -> Result<Value, ParseError> {
528        // Recognize PASSWORD('plaintext') and SECRET('plaintext') as
529        // typed literal constructors. The parser stores them as
530        // sentinel-prefixed values so that the INSERT executor can
531        // apply the crypto transform (argon2id hash / AES-256-GCM
532        // encrypt) without the parser depending on auth or crypto
533        // subsystems.
534        if let Token::Ident(name) = self.peek().clone() {
535            let upper = name.to_uppercase();
536            if upper == "PASSWORD" || upper == "SECRET" {
537                self.advance()?; // consume ident
538                self.expect(Token::LParen)?;
539                let plaintext = self.parse_string()?;
540                self.expect(Token::RParen)?;
541                return Ok(match upper.as_str() {
542                    "PASSWORD" => Value::Password(format!("@@plain@@{plaintext}")),
543                    "SECRET" => Value::Secret(format!("@@plain@@{plaintext}").into_bytes()),
544                    _ => unreachable!(),
545                });
546            }
547            if upper == "SECRET_REF" {
548                self.advance()?; // consume ident
549                self.expect(Token::LParen)?;
550                let store = self.expect_ident_or_keyword()?.to_ascii_lowercase();
551                if store != "vault" {
552                    return Err(ParseError::expected(
553                        vec!["vault"],
554                        self.peek(),
555                        self.position(),
556                    ));
557                }
558                self.expect(Token::Comma)?;
559                let (collection, key) =
560                    self.parse_kv_key(crate::catalog::CollectionModel::Vault)?;
561                self.expect(Token::RParen)?;
562                return Ok(secret_ref_value(&store, &collection, &key));
563            }
564        }
565
566        match self.peek().clone() {
567            Token::String(s) => {
568                let s = s.clone();
569                self.advance()?;
570                Ok(Value::text(s))
571            }
572            Token::JsonLiteral(raw) => {
573                // The lexer already validated brace balance and the
574                // 16 MiB payload ceiling. Parse the raw text into a
575                // canonical JsonValue then re-encode via `to_vec` so
576                // the on-disk bytes match the quoted form.
577                self.advance()?;
578                let json_value = crate::utils::json::parse_json(&raw).map_err(|err| {
579                    ParseError::new(
580                        // F-05: render the underlying parse-error string
581                        // via `{:?}` so any user fragment serde echoed
582                        // back (unexpected character, key text, …) is
583                        // Debug-escaped before reaching the downstream
584                        // JSON / audit / log / gRPC sinks.
585                        format!("invalid JSON object literal: {:?}", err.to_string()),
586                        self.position(),
587                    )
588                })?;
589                json_literal_depth_check(&json_value)
590                    .map_err(|err| ParseError::new(err, self.position()))?;
591                let canonical = crate::serde_json::Value::from(json_value);
592                let bytes = crate::json::to_vec(&canonical).map_err(|err| {
593                    ParseError::new(
594                        // F-05: escape the encoder error via `{:?}` so any
595                        // user fragment it carries cannot smuggle control
596                        // bytes through downstream serialization sinks.
597                        format!("failed to encode JSON literal: {:?}", err.to_string()),
598                        self.position(),
599                    )
600                })?;
601                Ok(Value::Json(bytes))
602            }
603            Token::Integer(n) => {
604                self.advance()?;
605                Ok(Value::Integer(n))
606            }
607            Token::Float(n) => {
608                self.advance()?;
609                Ok(Value::Float(n))
610            }
611            Token::True => {
612                self.advance()?;
613                Ok(Value::Boolean(true))
614            }
615            Token::False => {
616                self.advance()?;
617                Ok(Value::Boolean(false))
618            }
619            Token::Null => {
620                self.advance()?;
621                Ok(Value::Null)
622            }
623            Token::LBracket => {
624                // Parse array literal [val1, val2, ...]
625                // For numeric arrays, produce Value::Vector; for others, produce Value::Json
626                self.advance()?; // consume '['
627                let mut items = Vec::new();
628                if !self.check(&Token::RBracket) {
629                    loop {
630                        items.push(self.parse_literal_value()?);
631                        if !self.consume(&Token::Comma)? {
632                            break;
633                        }
634                    }
635                }
636                self.expect(Token::RBracket)?;
637
638                // Check if all items are numeric (Integer or Float) -> Value::Vector
639                let all_numeric = items
640                    .iter()
641                    .all(|v| matches!(v, Value::Integer(_) | Value::Float(_)));
642                if all_numeric && !items.is_empty() {
643                    let floats: Vec<f32> = items
644                        .iter()
645                        .map(|v| match v {
646                            Value::Float(f) => *f as f32,
647                            Value::Integer(i) => *i as f32,
648                            _ => 0.0,
649                        })
650                        .collect();
651                    Ok(Value::Vector(floats))
652                } else {
653                    // Encode as JSON bytes
654                    let json_arr: Vec<crate::json::Value> = items
655                        .iter()
656                        .map(|v| match v {
657                            Value::Null => crate::json::Value::Null,
658                            Value::Boolean(b) => crate::json::Value::Bool(*b),
659                            Value::Integer(i) => crate::json::Value::Number(*i as f64),
660                            Value::Float(f) => crate::json::Value::Number(*f),
661                            Value::Text(s) => crate::json::Value::String(s.to_string()),
662                            _ => crate::json::Value::Null,
663                        })
664                        .collect();
665                    let json_val = crate::json::Value::Array(json_arr);
666                    let bytes = crate::json::to_vec(&json_val).unwrap_or_default();
667                    Ok(Value::Json(bytes))
668                }
669            }
670            Token::LBrace => {
671                // Parse JSON object literal {key: value, ...}
672                self.advance()?; // consume '{'
673                let mut map = crate::json::Map::new();
674                if !self.check(&Token::RBrace) {
675                    loop {
676                        // Key: string or identifier. Reserved-word
677                        // keys (`level`, `msg`, `type`, …) fall through
678                        // to `expect_ident_or_keyword`, which returns
679                        // the canonical UPPERCASE spelling; lowercase
680                        // that path so the JSON object preserves the
681                        // source casing.
682                        let key = match self.peek().clone() {
683                            Token::String(s) => {
684                                self.advance()?;
685                                s
686                            }
687                            Token::Ident(s) => {
688                                self.advance()?;
689                                s
690                            }
691                            _ => self.expect_ident_or_keyword()?.to_ascii_lowercase(),
692                        };
693                        // Separator: ':' or '='
694                        if !self.consume(&Token::Colon)? {
695                            self.expect(Token::Eq)?;
696                        }
697                        // Value: recursive
698                        let val = self.parse_literal_value()?;
699                        let json_val = match val {
700                            Value::Null => crate::json::Value::Null,
701                            Value::Boolean(b) => crate::json::Value::Bool(b),
702                            Value::Integer(i) => crate::json::Value::Number(i as f64),
703                            Value::Float(f) => crate::json::Value::Number(f),
704                            Value::Text(s) => crate::json::Value::String(s.to_string()),
705                            Value::Json(ref bytes) => {
706                                crate::json::from_slice(bytes).unwrap_or(crate::json::Value::Null)
707                            }
708                            _ => crate::json::Value::Null,
709                        };
710                        map.insert(key, json_val);
711                        if !self.consume(&Token::Comma)? {
712                            break;
713                        }
714                    }
715                }
716                self.expect(Token::RBrace)?;
717                let json_val = crate::json::Value::Object(map);
718                let bytes = crate::json::to_vec(&json_val).unwrap_or_default();
719                Ok(Value::Json(bytes))
720            }
721            ref other => Err(ParseError::expected(
722                vec!["string", "number", "true", "false", "null", "[", "{"],
723                other,
724                self.position(),
725            )),
726        }
727    }
728}
729
730fn secret_ref_value(store: &str, collection: &str, key: &str) -> Value {
731    let mut map = crate::json::Map::new();
732    map.insert(
733        "type".to_string(),
734        crate::json::Value::String("secret_ref".to_string()),
735    );
736    map.insert(
737        "store".to_string(),
738        crate::json::Value::String(store.to_string()),
739    );
740    map.insert(
741        "collection".to_string(),
742        crate::json::Value::String(collection.to_string()),
743    );
744    map.insert(
745        "key".to_string(),
746        crate::json::Value::String(key.to_string()),
747    );
748    Value::Json(crate::json::to_vec(&crate::json::Value::Object(map)).unwrap_or_default())
749}