use std::error::Error;
use std::io::Write;
use qql_core::ast::ShardKey;
use serde_json::Value;
use super::escape::{escape_string, format_ident, is_simple_ident};
pub fn point_to_upsert_object(point: &Value) -> Option<Value> {
let id = point.get("id")?.clone();
let mut map = serde_json::Map::new();
map.insert("id".into(), id);
if let Some(payload) = point.get("payload").and_then(|p| p.as_object()) {
for (k, v) in payload {
map.insert(k.clone(), v.clone());
}
}
if let Some(vector) = point.get("vector")
&& !vector.is_null()
{
map.insert("vector".into(), vector.clone());
}
Some(Value::Object(map))
}
pub fn format_upsert_statement(
collection: &str,
records: &[Value],
shard_key: Option<&ShardKey>,
) -> String {
let mut body = format!("UPSERT INTO {} VALUES\n", format_ident(collection));
for (idx, rec) in records.iter().enumerate() {
body.push_str(" ");
body.push_str(&format_point_literal(rec));
if idx + 1 < records.len() {
body.push(',');
}
body.push('\n');
}
if let Some(key) = shard_key {
body.push_str(&format!(" SHARD {key}"));
}
body
}
pub fn write_upsert_batch(
out: &mut impl Write,
collection: &str,
records: &[Value],
shard_key: Option<&ShardKey>,
) -> Result<(), Box<dyn Error>> {
write!(
out,
"{}",
format_upsert_statement(collection, records, shard_key)
)?;
writeln!(out, ";")?;
writeln!(out)?;
Ok(())
}
pub fn format_point_literal(point: &Value) -> String {
let Some(obj) = point.as_object() else {
return "{}".into();
};
let mut parts = Vec::new();
if let Some(id) = obj.get("id") {
parts.push(format!("id: {}", format_point_id_value(id)));
}
if let Some(vector) = obj.get("vector") {
parts.push(format!("vector: {}", format_qql_value(vector)));
}
let mut payload_keys: Vec<&String> = obj
.keys()
.filter(|k| k.as_str() != "id" && k.as_str() != "vector")
.collect();
payload_keys.sort();
for k in payload_keys {
if let Some(v) = obj.get(k) {
parts.push(format!("{}: {}", format_field_key(k), format_qql_value(v)));
}
}
format!("{{{}}}", parts.join(", "))
}
pub fn format_point_id_value(id: &Value) -> String {
match id {
Value::Number(n) => n.to_string(),
Value::String(s) => format!("'{}'", escape_string(s)),
other => format!("'{}'", escape_string(&other.to_string())),
}
}
pub fn format_field_key(key: &str) -> String {
if is_simple_ident(key) {
key.to_string()
} else {
format!("'{}'", escape_string(key))
}
}
pub fn format_qql_value(value: &Value) -> String {
match value {
Value::Null => "null".into(),
Value::Bool(b) => b.to_string(),
Value::Number(n) => n.to_string(),
Value::String(s) => format!("'{}'", escape_string(s)),
Value::Array(arr) => {
let items: Vec<String> = arr.iter().map(format_qql_value).collect();
format!("[{}]", items.join(", "))
}
Value::Object(map) => {
if map.contains_key("indices") && map.contains_key("values") && map.len() == 2 {
return format!(
"{{indices: {}, values: {}}}",
format_qql_value(&map["indices"]),
format_qql_value(&map["values"])
);
}
let mut keys: Vec<&String> = map.keys().collect();
keys.sort();
let items: Vec<String> = keys
.iter()
.map(|k| format!("{}: {}", format_field_key(k), format_qql_value(&map[*k])))
.collect();
format!("{{{}}}", items.join(", "))
}
}
}