use crate::ast::Value;
use serde_json::json;
#[crate::polydat_node(category = Json)]
fn json_with(
key: crate::derive_support::Const<&str>,
value: Value,
) -> std::sync::Arc<serde_json::Value> {
let mut m = serde_json::Map::new();
m.insert(key.0.to_string(), value_to_json(&value));
std::sync::Arc::new(serde_json::Value::Object(m))
}
#[crate::polydat_node(category = Json, variadic_min = 0)]
fn json_object(parts: &[Value]) -> std::sync::Arc<serde_json::Value> {
let mut merged = serde_json::Map::new();
for v in parts {
if let Value::Json(arc) = v
&& let serde_json::Value::Object(map) = arc.as_ref() {
for (k, val) in map {
merged.insert(k.clone(), val.clone());
}
}
}
std::sync::Arc::new(serde_json::Value::Object(merged))
}
#[crate::polydat_node(category = Json, variadic_min = 0)]
fn json_array(elems: &[Value]) -> std::sync::Arc<serde_json::Value> {
let arr: Vec<serde_json::Value> = elems.iter().map(value_to_json).collect();
std::sync::Arc::new(serde_json::Value::Array(arr))
}
#[crate::polydat_node(category = Json)]
fn to_json(input: Value) -> std::sync::Arc<serde_json::Value> {
std::sync::Arc::new(value_to_json(&input))
}
#[crate::polydat_node(category = Json)]
fn json_merge(
left: &serde_json::Value,
right: &serde_json::Value,
) -> std::sync::Arc<serde_json::Value> {
let mut result = left.clone();
if let (serde_json::Value::Object(base), serde_json::Value::Object(overlay)) =
(&mut result, right)
{
for (k, v) in overlay {
base.insert(k.clone(), v.clone());
}
}
std::sync::Arc::new(result)
}
#[crate::polydat_node(category = Conversions)]
fn json_to_str(input: &serde_json::Value) -> String {
input.to_string()
}
#[crate::polydat_node(category = Json)]
fn json_to_str_pretty(input: &serde_json::Value) -> String {
serde_json::to_string_pretty(input).unwrap_or_default()
}
#[crate::polydat_node(category = Json)]
fn str_to_json(input: &str) -> std::sync::Arc<serde_json::Value> {
let parsed = serde_json::from_str(input).unwrap_or(serde_json::Value::Null);
std::sync::Arc::new(parsed)
}
#[crate::polydat_node(category = Json)]
fn escape_json(input: String) -> String {
let json_str = serde_json::to_string(&input).unwrap_or_default();
json_str[1..json_str.len() - 1].to_string()
}
#[crate::polydat_node(category = Json)]
fn json_field(
input: &serde_json::Value,
key: crate::derive_support::Const<&str>,
) -> std::sync::Arc<serde_json::Value> {
std::sync::Arc::new(
input.get(*key).cloned().unwrap_or(serde_json::Value::Null)
)
}
fn value_to_json(v: &Value) -> serde_json::Value {
match v {
Value::Bytes(b) => {
use base64::Engine;
json!(base64::engine::general_purpose::STANDARD.encode(b))
}
other => other.to_json_value(),
}
}
#[crate::polydat_node(category = Json)]
fn json_text(input: Value) -> String {
match &input {
Value::Json(j) => {
let mut buf = String::new();
walk_json_leaves(j, &mut buf);
buf
}
other => other.to_display_string(),
}
}
fn walk_json_leaves(j: &serde_json::Value, out: &mut String) {
use serde_json::Value as J;
match j {
J::String(s) => {
if !out.is_empty() { out.push('\n'); }
out.push_str(s);
}
J::Number(n) => {
if !out.is_empty() { out.push('\n'); }
out.push_str(&n.to_string());
}
J::Bool(b) => {
if !out.is_empty() { out.push('\n'); }
out.push_str(if *b { "true" } else { "false" });
}
J::Null => {}
J::Array(arr) => {
for item in arr { walk_json_leaves(item, out); }
}
J::Object(obj) => {
for value in obj.values() { walk_json_leaves(value, out); }
}
}
}
#[crate::polydat_node(category = Json)]
fn body_column_i32(
body: Value,
column: crate::derive_support::Const<&str>,
) -> Vec<i32> {
let json = match &body {
Value::Json(j) => j,
_ => return Vec::new(),
};
extract_column_i32(json, column.0)
}
fn extract_column_i32(json: &serde_json::Value, column: &str) -> Vec<i32> {
match json {
serde_json::Value::Array(rows) => {
rows.iter()
.filter_map(|row| json_value_as_i32(row.get(column)?))
.collect()
}
serde_json::Value::Object(obj) => {
if let Some(serde_json::Value::Array(rows)) = obj.get("rows") {
return rows.iter()
.filter_map(|row| json_value_as_i32(row.get(column)?))
.collect();
}
obj.get(column)
.and_then(json_value_as_i32)
.map(|n| vec![n])
.unwrap_or_default()
}
_ => Vec::new(),
}
}
fn json_value_as_i32(v: &serde_json::Value) -> Option<i32> {
match v {
serde_json::Value::Number(n) => {
n.as_i64().map(|i| i.clamp(i32::MIN as i64, i32::MAX as i64) as i32)
.or_else(|| n.as_u64().map(|u| u.min(i32::MAX as u64) as i32))
.or_else(|| n.as_f64().and_then(|f| {
if f.is_finite() { Some(f as i32) } else { None }
}))
}
serde_json::Value::String(s) => s.trim().parse::<i32>().ok(),
_ => None,
}
}
#[crate::polydat_node(category = Json)]
fn normalize_vector(vector: &str) -> String {
let trimmed = vector.trim();
if !trimmed.starts_with('[') || !trimmed.ends_with(']') {
return vector.to_string();
}
let inner = &trimmed[1..trimmed.len()-1];
let values: Vec<f64> = inner.split(',')
.filter_map(|v| v.trim().parse::<f64>().ok())
.collect();
let norm = values.iter().map(|v| v * v).sum::<f64>().sqrt();
if norm < 1e-15 {
return vector.to_string();
}
let normalized: Vec<String> = values.iter()
.map(|v| format!("{}", v / norm))
.collect();
format!("[{}]", normalized.join(","))
}
#[crate::polydat_node(category = Json)]
fn random_vector(
seed: u64,
dim: u64,
#[poly_default(0.0f64)] min: crate::derive_support::Const<f64>,
#[poly_default(1.0f64)] max: crate::derive_support::Const<f64>,
) -> String {
let min_v = *min;
let max_v = *max;
let range = max_v - min_v;
let dim = dim as usize;
let mut h = seed;
let mut values = Vec::with_capacity(dim);
for _ in 0..dim {
h = xxhash_rust::xxh3::xxh3_64(&h.to_le_bytes());
let unit = (h as f64) / (u64::MAX as f64); values.push(format!("{}", min_v + range * unit));
}
format!("[{}]", values.join(","))
}
#[crate::polydat_node(category = Json)]
fn array_len(input: &str) -> u64 {
let trimmed = input.trim();
if trimmed == "[]" || trimmed.is_empty() {
0
} else if trimmed.starts_with('[') && trimmed.ends_with(']') {
let inner = &trimmed[1..trimmed.len() - 1];
inner.split(',').count() as u64
} else {
0
}
}
#[crate::polydat_node(category = Json)]
fn array_at(array: &str, index: u64) -> String {
let trimmed = array.trim();
if trimmed.starts_with('[') && trimmed.ends_with(']') {
let inner = &trimmed[1..trimmed.len() - 1];
let elements: Vec<&str> = inner.split(',').map(|e| e.trim()).collect();
if elements.is_empty() || (elements.len() == 1 && elements[0].is_empty()) {
String::new()
} else {
elements[(index as usize) % elements.len()].to_string()
}
} else {
String::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ast::{PolydatNode, PortType, Value};
#[test]
fn body_column_i32_extracts_array_of_rows() {
let body = Value::Json(std::sync::Arc::new(serde_json::json!([
{ "key": 4, "value": 0.5 },
{ "key": 17, "value": 0.4 },
{ "key": 42, "value": 0.3 },
])));
let node = BodyColumnI32::new(PortType::Json, "key".to_string());
let mut out = [Value::None];
node.eval(&[body], &mut out);
let Value::VecI32(slice) = &out[0] else { panic!("expected VecI32, got {:?}", out[0]) };
assert_eq!(slice.as_slice(), &[4, 17, 42]);
}
#[test]
fn body_column_i32_extracts_envelope_rows() {
let body = Value::Json(std::sync::Arc::new(serde_json::json!({
"rows": [
{ "id": 1 },
{ "id": 7 },
{ "id": 13 },
],
"metadata": "ignored",
})));
let node = BodyColumnI32::new(PortType::Json, "id".to_string());
let mut out = [Value::None];
node.eval(&[body], &mut out);
let Value::VecI32(slice) = &out[0] else { panic!("expected VecI32") };
assert_eq!(slice.as_slice(), &[1, 7, 13]);
}
#[test]
fn body_column_i32_skips_rows_missing_column() {
let body = Value::Json(std::sync::Arc::new(serde_json::json!([
{ "key": 1 },
{ "other": 2 }, { "key": "not_an_int" }, { "key": 3 },
])));
let node = BodyColumnI32::new(PortType::Json, "key".to_string());
let mut out = [Value::None];
node.eval(&[body], &mut out);
let Value::VecI32(slice) = &out[0] else { panic!("expected VecI32") };
assert_eq!(slice.as_slice(), &[1, 3]);
}
#[test]
fn body_column_i32_string_numeric_parses() {
let body = Value::Json(std::sync::Arc::new(serde_json::json!([
{ "key": "42" },
{ "key": "-7" },
])));
let node = BodyColumnI32::new(PortType::Json, "key".to_string());
let mut out = [Value::None];
node.eval(&[body], &mut out);
let Value::VecI32(slice) = &out[0] else { panic!("expected VecI32") };
assert_eq!(slice.as_slice(), &[42, -7]);
}
#[test]
fn body_column_i32_empty_body_produces_empty_vec() {
let body = Value::Json(std::sync::Arc::new(serde_json::json!([])));
let node = BodyColumnI32::new(PortType::Json, "key".to_string());
let mut out = [Value::None];
node.eval(&[body], &mut out);
let Value::VecI32(slice) = &out[0] else { panic!("expected VecI32") };
assert!(slice.as_slice().is_empty());
}
#[test]
fn body_column_i32_non_json_input_produces_empty_vec() {
let node = BodyColumnI32::new(PortType::Json, "key".to_string());
let mut out = [Value::None];
node.eval(&[Value::Str("not json".into())], &mut out);
let Value::VecI32(slice) = &out[0] else { panic!("expected VecI32") };
assert!(slice.as_slice().is_empty());
}
#[test]
fn json_text_flattens_multirow_describe_for_regex() {
let body = Value::Json(std::sync::Arc::new(serde_json::json!([
{
"keyspace_name": "system_views",
"type": "table",
"name": "sai_column_indexes",
"create_statement": "CREATE TABLE system_views.sai_column_indexes (\n keyspace_name text,\n table_name text\n);"
},
{
"keyspace_name": "system_views",
"type": "table",
"name": "indexes",
"create_statement": "CREATE VIRTUAL TABLE system_views.indexes (\n keyspace_name text\n);"
},
])));
let node = JsonText::new(PortType::Json);
let mut out = [Value::None];
node.eval(&[body], &mut out);
let text = match &out[0] {
Value::Str(s) => s.clone(),
other => panic!("expected Str, got {other:?}"),
};
assert!(text.contains("CREATE TABLE system_views.sai_column_indexes (\n"));
assert!(text.contains("CREATE VIRTUAL TABLE system_views.indexes (\n"));
let pat = regex::Regex::new(
r"(?im)^\s*(?:CREATE\s+)?(?:VIRTUAL\s+)?TABLE\s+system_views\.sai_column_indexes\s*\("
).unwrap();
assert!(pat.is_match(&text), "regex should match the flattened schema text");
}
fn jw(key: &str, value_pt: PortType, value: Value) -> Value {
let node = JsonWith::new(key.to_string(), value_pt);
let mut out = [Value::None];
node.eval(&[value], &mut out);
std::mem::replace(&mut out[0], Value::None)
}
#[test]
fn json_object_basic() {
let name = jw("name", PortType::Str, Value::Str("Alice".into()));
let age = jw("age", PortType::U64, Value::U64(30));
let active = jw("active", PortType::Bool, Value::Bool(true));
let node = JsonObject::new(3);
let mut out = [Value::None];
node.eval(&[name, age, active], &mut out);
let j = out[0].as_json();
assert_eq!(j["name"], "Alice");
assert_eq!(j["age"], 30);
assert_eq!(j["active"], true);
}
#[test]
fn json_object_nested() {
let inner_x = jw("x", PortType::U64, Value::U64(10));
let inner_y = jw("y", PortType::U64, Value::U64(20));
let inner_node = JsonObject::new(2);
let mut inner_out = [Value::None];
inner_node.eval(&[inner_x, inner_y], &mut inner_out);
let point = jw(
"point",
PortType::Json,
std::mem::replace(&mut inner_out[0], Value::None),
);
let outer = JsonObject::new(1);
let mut out = [Value::None];
outer.eval(&[point], &mut out);
let j = out[0].as_json();
assert_eq!(j["point"]["x"], 10);
assert_eq!(j["point"]["y"], 20);
}
#[test]
fn json_object_later_part_wins_on_collision() {
let first = jw("k", PortType::U64, Value::U64(1));
let second = jw("k", PortType::U64, Value::U64(2));
let node = JsonObject::new(2);
let mut out = [Value::None];
node.eval(&[first, second], &mut out);
assert_eq!(out[0].as_json()["k"], 2);
}
#[test]
fn json_object_skips_non_json_parts() {
let valid = jw("k", PortType::U64, Value::U64(1));
let node = JsonObject::new(2);
let mut out = [Value::None];
node.eval(&[valid, Value::None], &mut out);
let j = out[0].as_json();
assert_eq!(j["k"], 1);
assert_eq!(j.as_object().unwrap().len(), 1);
}
#[test]
fn json_array_basic() {
let node = JsonArray::new(3);
let mut out = [Value::None];
node.eval(
&[Value::U64(1), Value::Str("two".into()), Value::F64(3.0)],
&mut out,
);
let j = out[0].as_json();
let arr = j.as_array().unwrap();
assert_eq!(arr.len(), 3);
assert_eq!(arr[0], 1);
assert_eq!(arr[1], "two");
assert_eq!(arr[2], 3.0);
}
#[test]
fn json_to_str_compact() {
let node = JsonToStr::new();
let mut out = [Value::None];
let input = Value::Json(std::sync::Arc::new(json!({"a": 1, "b": "hello"})));
node.eval(&[input], &mut out);
let s = out[0].as_str();
assert!(s.contains("\"a\":1") || s.contains("\"a\": 1"));
assert!(s.contains("\"b\":\"hello\"") || s.contains("\"b\": \"hello\""));
}
#[test]
fn str_to_json_roundtrip() {
let to_str = JsonToStr::new();
let from_str = StrToJson::default();
let original = Value::Json(std::sync::Arc::new(json!({"key": [1, 2, 3]})));
let mut mid = [Value::None];
let mut out = [Value::None];
to_str.eval(std::slice::from_ref(&original), &mut mid);
from_str.eval(&[mid[0].clone()], &mut out);
assert_eq!(out[0].as_json(), original.as_json());
}
#[test]
fn escape_json_basic() {
let node = EscapeJson::new();
let mut out = [Value::None];
node.eval(&[Value::Str("hello \"world\"\nline2".into())], &mut out);
let s = out[0].as_str();
assert!(s.contains("\\\""));
assert!(s.contains("\\n"));
assert!(!s.starts_with('"'));
}
#[test]
fn json_merge_basic() {
let node = JsonMerge::new();
let mut out = [Value::None];
let left = Value::Json(std::sync::Arc::new(json!({"a": 1, "b": 2})));
let right = Value::Json(std::sync::Arc::new(json!({"b": 99, "c": 3})));
node.eval(&[left, right], &mut out);
let j = out[0].as_json();
assert_eq!(j["a"], 1);
assert_eq!(j["b"], 99); assert_eq!(j["c"], 3);
}
#[test]
fn json_field_basic() {
let node = JsonField::new("name".to_string());
let mut out = [Value::None];
node.eval(&[Value::Json(std::sync::Arc::new(json!({"name": "Alice", "age": 30})))], &mut out);
assert_eq!(out[0].as_json(), &json!("Alice"));
}
#[test]
fn json_field_missing() {
let node = JsonField::new("missing".to_string());
let mut out = [Value::None];
node.eval(&[Value::Json(std::sync::Arc::new(json!({"name": "Alice"})))], &mut out);
assert!(out[0].as_json().is_null());
}
#[test]
fn to_json_from_u64() {
let node = ToJson::new(PortType::U64);
let mut out = [Value::None];
node.eval(&[Value::U64(42)], &mut out);
assert_eq!(out[0].as_json(), &json!(42));
}
#[test]
fn json_pretty_print() {
let node = JsonToStrPretty::default();
let mut out = [Value::None];
node.eval(&[Value::Json(std::sync::Arc::new(json!({"a": 1})))], &mut out);
let s = out[0].as_str();
assert!(s.contains('\n'), "pretty print should have newlines");
}
}