Skip to main content

nodedb_sql/parser/preprocess/
literal.rs

1// SPDX-License-Identifier: Apache-2.0
2
3//! SQL literal canonicalization for `nodedb_types::Value`.
4
5/// Convert a `nodedb_types::Value` to a SQL literal string.
6///
7/// Used by pre-processing and by Origin's pgwire handlers to build SQL
8/// from parsed field maps. Handles all Value variants.
9pub fn value_to_sql_literal(value: &nodedb_types::Value) -> String {
10    match value {
11        nodedb_types::Value::String(s) => format!("'{}'", s.replace('\'', "''")),
12        nodedb_types::Value::Integer(n) => n.to_string(),
13        nodedb_types::Value::Float(f) => {
14            if f.is_finite() {
15                format!("{f}")
16            } else {
17                // NaN / ±inf have no canonical SQL literal form. Emitting
18                // `NaN` or `inf` would be parsed as an identifier
19                // reference by the planner and either bind to an unrelated
20                // column or fail with an opaque error. Canonicalize to
21                // NULL instead; the column value becomes unknown, which
22                // matches the semantic of a non-finite numeric.
23                "NULL".to_string()
24            }
25        }
26        nodedb_types::Value::Bool(b) => if *b { "TRUE" } else { "FALSE" }.to_string(),
27        nodedb_types::Value::Null => "NULL".to_string(),
28        nodedb_types::Value::Array(items) => {
29            let inner: Vec<String> = items.iter().map(value_to_sql_literal).collect();
30            format!("ARRAY[{}]", inner.join(", "))
31        }
32        nodedb_types::Value::Bytes(b) => format!("'\\x{}'", hex::encode(b)),
33        nodedb_types::Value::Object(map) => {
34            let json = super::function_args::value_map_to_json(map);
35            format!("'{}'", json.replace('\'', "''"))
36        }
37        nodedb_types::Value::Uuid(u) => format!("'{u}'"),
38        nodedb_types::Value::Ulid(u) => format!("'{u}'"),
39        nodedb_types::Value::DateTime(dt) | nodedb_types::Value::NaiveDateTime(dt) => {
40            format!("'{dt}'")
41        }
42        nodedb_types::Value::Duration(d) => format!("'{d}'"),
43        nodedb_types::Value::Decimal(d) => d.to_string(),
44        other => format!("'{}'", format!("{other:?}").replace('\'', "''")),
45    }
46}