use spg_engine::{Engine, QueryResult};
fn rows(e: &mut Engine, sql: &str) -> Vec<Vec<spg_storage::Value<'static>>> {
let r = e
.execute(sql)
.unwrap_or_else(|err| panic!("{sql}: {err:?}"));
let QueryResult::Rows { rows, .. } = r else {
panic!("expected Rows");
};
rows.into_iter()
.map(|row| row.values.into_iter().collect())
.collect()
}
fn render(v: &spg_storage::Value<'_>) -> Option<String> {
match v {
spg_storage::Value::Text(s) => Some(s.to_string()),
spg_storage::Value::Json(s) => Some(s.to_string()),
spg_storage::Value::Null => None,
other => panic!("expected Text/Json/Null, got {other:?}"),
}
}
#[test]
fn jsonb_each_keeps_json_rendering() {
let mut e = Engine::new();
let got = rows(
&mut e,
"SELECT key, value FROM jsonb_each('{\"a\": \"foo\", \"b\": \"bar\"}') ORDER BY key",
);
assert_eq!(got.len(), 2);
assert_eq!(render(&got[0][0]), Some("a".to_string()));
assert_eq!(render(&got[0][1]), Some("\"foo\"".to_string()));
assert_eq!(render(&got[1][0]), Some("b".to_string()));
assert_eq!(render(&got[1][1]), Some("\"bar\"".to_string()));
}
#[test]
fn jsonb_each_json_null_stays_jsonb_null() {
let mut e = Engine::new();
let got = rows(&mut e, "SELECT value FROM jsonb_each('{\"k\": null}')");
assert_eq!(render(&got[0][0]), Some("null".to_string()));
let got = rows(&mut e, "SELECT value FROM json_each_text('{\"k\": null}')");
assert_eq!(render(&got[0][0]), None);
}
#[test]
fn json_each_nested_and_numbers() {
let mut e = Engine::new();
let got = rows(
&mut e,
"SELECT key, value FROM json_each('{\"n\": 5, \"o\": {\"x\": 1}}') ORDER BY key",
);
assert_eq!(render(&got[0][1]), Some("5".to_string()));
assert_eq!(render(&got[1][1]), Some("{\"x\": 1}".to_string()));
}
#[test]
fn each_text_still_unwraps() {
let mut e = Engine::new();
let got = rows(
&mut e,
"SELECT value FROM jsonb_each_text('{\"a\": \"foo\"}')",
);
assert_eq!(render(&got[0][0]), Some("foo".to_string()));
}
#[test]
fn non_object_input_errors() {
let mut e = Engine::new();
let err = e.execute("SELECT * FROM jsonb_each('[1, 2]')").unwrap_err();
let msg = format!("{err:?}");
assert!(msg.contains("non-object"), "unexpected error: {msg}");
}
#[test]
fn jsonb_to_recordset_and_record() {
use spg_storage::Value;
let mut e = Engine::new();
let got = rows(
&mut e,
"SELECT a, b FROM jsonb_to_recordset('[{\"a\":1,\"b\":\"hi\"},{\"a\":2}]') AS x(a int, b text) ORDER BY a",
);
assert_eq!(got.len(), 2);
assert_eq!(got[0][0], Value::Int(1));
assert!(matches!(&got[0][1], Value::Text(s) if s == "hi"));
assert_eq!(got[1][0], Value::Int(2));
assert_eq!(got[1][1], Value::Null);
let got = rows(
&mut e,
"SELECT a, b FROM jsonb_to_record('{\"a\":7,\"b\":\"x\"}') AS x(a int, b text)",
);
assert_eq!(got.len(), 1);
assert_eq!(got[0][0], Value::Int(7));
assert!(matches!(&got[0][1], Value::Text(s) if s == "x"));
let got = rows(
&mut e,
"SELECT string_agg(a::text, ',') FROM json_to_recordset('[{\"a\":10},{\"a\":20}]') AS x(a int)",
);
assert!(matches!(&got[0][0], Value::Text(s) if s == "10,20"));
}
#[test]
fn jsonb_each_in_select_list_is_composite_srf() {
use spg_storage::Value;
let mut e = Engine::new();
let got = rows(&mut e, "SELECT jsonb_each('{\"a\": 1, \"b\": 2}'::jsonb)");
assert_eq!(got.len(), 2);
let comp = |v: &Value| match v {
Value::Composite(f) => (render(&f[0].1), render(&f[1].1)),
other => panic!("expected Composite, got {other:?}"),
};
assert_eq!(comp(&got[0][0]), (Some("a".into()), Some("1".into())));
assert_eq!(comp(&got[1][0]), (Some("b".into()), Some("2".into())));
let got = rows(
&mut e,
"SELECT jsonb_each_text('{\"a\": \"x\", \"b\": null}'::jsonb)",
);
assert_eq!(comp(&got[0][0]), (Some("a".into()), Some("x".into())));
assert_eq!(comp(&got[1][0]), (Some("b".into()), None));
e.execute("CREATE TABLE je(j jsonb)").unwrap();
e.execute("INSERT INTO je VALUES ('{\"k\": 10}'), ('{\"m\": 20}')")
.unwrap();
let got = rows(&mut e, "SELECT jsonb_each(j) FROM je");
assert_eq!(got.len(), 2);
assert_eq!(comp(&got[0][0]), (Some("k".into()), Some("10".into())));
assert_eq!(comp(&got[1][0]), (Some("m".into()), Some("20".into())));
assert_eq!(rows(&mut e, "SELECT jsonb_each(NULL::jsonb)").len(), 0);
}