Skip to main content

nodedb_sql/parser/preprocess/
object_literal_stmt.rs

1//! Rewrite `INSERT/UPSERT INTO coll { ... }` (and `[{ ... }, ...]`) into
2//! standard `INSERT INTO coll (cols) VALUES (row), ...`.
3
4use super::literal::value_to_sql_literal;
5use crate::parser::object_literal::{parse_object_literal, parse_object_literal_array};
6
7/// Try to rewrite `INSERT INTO coll { ... }` or `INSERT INTO coll [{ ... }, { ... }]`
8/// into standard `INSERT INTO coll (cols) VALUES (row1), (row2)`.
9///
10/// Returns `None` if the statement doesn't use object literal syntax.
11pub(super) fn try_rewrite_object_literal(sql: &str) -> Option<String> {
12    let after_into = sql["INSERT INTO ".len()..].trim_start();
13    let coll_end = after_into.find(|c: char| c.is_whitespace())?;
14    let coll_name = &after_into[..coll_end];
15    let rest = after_into[coll_end..].trim_start();
16
17    // Strip trailing semicolon before parsing.
18    let obj_str = rest.trim_end_matches(';').trim_end();
19
20    if obj_str.starts_with('[') {
21        return rewrite_array_form(coll_name, obj_str);
22    }
23
24    if !obj_str.starts_with('{') {
25        return None;
26    }
27
28    let fields = parse_object_literal(obj_str)?.ok()?;
29    if fields.is_empty() {
30        return None;
31    }
32    Some(fields_to_values_sql(coll_name, &[fields]))
33}
34
35/// Rewrite `[{ ... }, { ... }]` → multi-row VALUES.
36fn rewrite_array_form(coll_name: &str, obj_str: &str) -> Option<String> {
37    let objects = parse_object_literal_array(obj_str)?.ok()?;
38    if objects.is_empty() {
39        return None;
40    }
41    Some(fields_to_values_sql(coll_name, &objects))
42}
43
44/// Build `INSERT INTO coll (col_union) VALUES (row1), (row2), ...`
45///
46/// Collects the union of all keys across all rows. Missing keys get NULL.
47fn fields_to_values_sql(
48    coll_name: &str,
49    rows: &[std::collections::HashMap<String, nodedb_types::Value>],
50) -> String {
51    let mut all_keys: Vec<String> = rows
52        .iter()
53        .flat_map(|r| r.keys().cloned())
54        .collect::<std::collections::BTreeSet<_>>()
55        .into_iter()
56        .collect();
57    all_keys.sort();
58
59    let col_list = all_keys.join(", ");
60
61    let row_strs: Vec<String> = rows
62        .iter()
63        .map(|row| {
64            let vals: Vec<String> = all_keys
65                .iter()
66                .map(|k| match row.get(k) {
67                    Some(v) => value_to_sql_literal(v),
68                    None => "NULL".to_string(),
69                })
70                .collect();
71            format!("({})", vals.join(", "))
72        })
73        .collect();
74
75    format!(
76        "INSERT INTO {} ({}) VALUES {}",
77        coll_name,
78        col_list,
79        row_strs.join(", ")
80    )
81}