Skip to main content

nodedb_sql/parser/preprocess/
function_args.rs

1//! Rewrite `{ key: val }` object literals appearing inside function-call
2//! argument positions to JSON string literals: `'{"key": val}'`.
3
4use crate::parser::object_literal::parse_object_literal;
5
6/// Detect patterns like `func(arg1, arg2, { key: val })` and rewrite the
7/// `{ }` to a single-quoted JSON string. Only rewrites `{ }` that appear
8/// inside parentheses (function calls), not at statement level (INSERT).
9pub(super) fn rewrite_object_literal_args(sql: &str) -> Option<String> {
10    let mut result = String::with_capacity(sql.len());
11    let chars: Vec<char> = sql.chars().collect();
12    let mut i = 0;
13    let mut found = false;
14    let mut paren_depth: i32 = 0;
15
16    while i < chars.len() {
17        match chars[i] {
18            '(' => {
19                paren_depth += 1;
20                result.push('(');
21                i += 1;
22            }
23            ')' => {
24                paren_depth = paren_depth.saturating_sub(1);
25                result.push(')');
26                i += 1;
27            }
28            '\'' => {
29                result.push('\'');
30                i += 1;
31                while i < chars.len() {
32                    result.push(chars[i]);
33                    if chars[i] == '\'' {
34                        if i + 1 < chars.len() && chars[i + 1] == '\'' {
35                            i += 1;
36                            result.push(chars[i]);
37                        } else {
38                            break;
39                        }
40                    }
41                    i += 1;
42                }
43                i += 1;
44            }
45            '{' if paren_depth > 0 => {
46                let remaining: String = chars[i..].iter().collect();
47                if let Some(Ok(fields)) = parse_object_literal(&remaining)
48                    && let Some(end) = find_matching_brace(&chars, i)
49                {
50                    let json = value_map_to_json(&fields);
51                    result.push('\'');
52                    result.push_str(&json);
53                    result.push('\'');
54                    i = end + 1;
55                    found = true;
56                    continue;
57                }
58                result.push('{');
59                i += 1;
60            }
61            _ => {
62                result.push(chars[i]);
63                i += 1;
64            }
65        }
66    }
67
68    if found { Some(result) } else { None }
69}
70
71/// Convert a parsed field map to a JSON string without external serializer.
72fn value_map_to_json(fields: &std::collections::HashMap<String, nodedb_types::Value>) -> String {
73    let mut parts = Vec::with_capacity(fields.len());
74    let mut entries: Vec<_> = fields.iter().collect();
75    entries.sort_by_key(|(k, _)| k.as_str());
76    for (key, val) in entries {
77        parts.push(format!("\"{}\":{}", key, value_to_json(val)));
78    }
79    format!("{{{}}}", parts.join(","))
80}
81
82/// Convert a single `Value` to JSON text.
83fn value_to_json(value: &nodedb_types::Value) -> String {
84    match value {
85        nodedb_types::Value::String(s) => {
86            format!("\"{}\"", s.replace('\\', "\\\\").replace('"', "\\\""))
87        }
88        nodedb_types::Value::Integer(n) => n.to_string(),
89        nodedb_types::Value::Float(f) => {
90            if f.is_finite() {
91                format!("{f}")
92            } else {
93                // JSON has no representation for NaN / ±inf; serialize as
94                // `null` to keep the output parseable.
95                "null".to_string()
96            }
97        }
98        nodedb_types::Value::Bool(b) => if *b { "true" } else { "false" }.to_string(),
99        nodedb_types::Value::Null => "null".to_string(),
100        nodedb_types::Value::Array(items) => {
101            let inner: Vec<String> = items.iter().map(value_to_json).collect();
102            format!("[{}]", inner.join(","))
103        }
104        nodedb_types::Value::Object(map) => value_map_to_json(map),
105        _ => format!("\"{}\"", format!("{value:?}").replace('"', "\\\"")),
106    }
107}
108
109/// Find the index of the matching `}` for a `{` at position `start`.
110fn find_matching_brace(chars: &[char], start: usize) -> Option<usize> {
111    let mut depth = 0;
112    let mut in_string = false;
113    let mut i = start;
114    while i < chars.len() {
115        match chars[i] {
116            '\'' if !in_string => in_string = true,
117            '\'' if in_string => {
118                if i + 1 < chars.len() && chars[i + 1] == '\'' {
119                    i += 2;
120                    continue;
121                }
122                in_string = false;
123            }
124            '{' if !in_string => depth += 1,
125            '}' if !in_string => {
126                depth -= 1;
127                if depth == 0 {
128                    return Some(i);
129                }
130            }
131            _ => {}
132        }
133        i += 1;
134    }
135    None
136}