use spg_engine::{Engine, QueryResult};
use spg_storage::Value;
fn one_row(r: QueryResult) -> Vec<Value> {
match r {
QueryResult::Rows { rows, .. } => {
assert_eq!(rows.len(), 1);
rows.into_iter().next().unwrap().values
}
_ => panic!(),
}
}
fn engine_with_tags() -> Engine {
let mut e = Engine::new();
e.execute("CREATE TABLE t (id INT NOT NULL, tag TEXT)")
.unwrap();
e.execute(
"INSERT INTO t VALUES \
(1, 'red'), (1, 'green'), (1, 'blue'), \
(2, 'orange'), (2, 'yellow'), \
(3, NULL), (3, 'cyan')",
)
.unwrap();
e
}
#[test]
fn string_agg_two_arg_concat_with_separator() {
let mut e = engine_with_tags();
let r = e
.execute("SELECT string_agg(tag, ',') FROM t WHERE id = 1")
.unwrap();
let row = one_row(r);
assert_eq!(row[0], Value::Text("red,green,blue".into()));
}
#[test]
fn string_agg_skips_null_inputs() {
let mut e = engine_with_tags();
let r = e
.execute("SELECT string_agg(tag, ',') FROM t WHERE id = 3")
.unwrap();
let row = one_row(r);
assert_eq!(row[0], Value::Text("cyan".into()));
}
#[test]
fn string_agg_empty_group_returns_null() {
let mut e = engine_with_tags();
let r = e
.execute("SELECT string_agg(tag, ',') FROM t WHERE id = 99")
.unwrap();
let row = one_row(r);
assert_eq!(row[0], Value::Null);
}
#[test]
fn string_agg_all_null_inputs_returns_null() {
let mut e = Engine::new();
e.execute("CREATE TABLE n (v TEXT)").unwrap();
e.execute("INSERT INTO n VALUES (NULL), (NULL)").unwrap();
let r = e.execute("SELECT string_agg(v, ',') FROM n").unwrap();
let row = one_row(r);
assert_eq!(row[0], Value::Null);
}
#[test]
fn string_agg_single_value_no_separator() {
let mut e = Engine::new();
e.execute("CREATE TABLE n (v TEXT)").unwrap();
e.execute("INSERT INTO n VALUES ('only')").unwrap();
let r = e.execute("SELECT string_agg(v, '|') FROM n").unwrap();
let row = one_row(r);
assert_eq!(row[0], Value::Text("only".into()));
}
#[test]
fn string_agg_with_group_by() {
let mut e = engine_with_tags();
let r = e
.execute("SELECT id, string_agg(tag, ',') FROM t GROUP BY id ORDER BY id")
.unwrap();
let QueryResult::Rows { rows, .. } = r else {
panic!()
};
assert_eq!(rows.len(), 3);
assert_eq!(rows[0].values[0], Value::Int(1));
assert_eq!(rows[0].values[1], Value::Text("red,green,blue".into()));
assert_eq!(rows[1].values[0], Value::Int(2));
assert_eq!(rows[1].values[1], Value::Text("orange,yellow".into()));
assert_eq!(rows[2].values[0], Value::Int(3));
assert_eq!(rows[2].values[1], Value::Text("cyan".into()));
}
#[test]
fn string_agg_arity_one_arg_errors() {
let mut e = engine_with_tags();
assert!(e.execute("SELECT string_agg(tag) FROM t").is_err());
}
#[test]
fn string_agg_arity_three_args_errors() {
let mut e = engine_with_tags();
assert!(
e.execute("SELECT string_agg(tag, ',', 'x') FROM t")
.is_err()
);
}
#[test]
fn string_agg_int_input_coerced_to_text() {
let mut e = Engine::new();
e.execute("CREATE TABLE n (v INT NOT NULL)").unwrap();
e.execute("INSERT INTO n VALUES (1), (2)").unwrap();
let r = e.execute("SELECT string_agg(v::text, ',') FROM n").unwrap();
let row = one_row(r);
assert_eq!(row[0], Value::Text("1,2".into()));
}
#[test]
fn array_agg_text_returns_text_array() {
let mut e = engine_with_tags();
let r = e
.execute("SELECT array_agg(tag) FROM t WHERE id = 1")
.unwrap();
let row = one_row(r);
match &row[0] {
Value::TextArray(items) => {
let v: Vec<Option<String>> = items.clone();
assert_eq!(
v,
vec![
Some("red".into()),
Some("green".into()),
Some("blue".into()),
]
);
}
other => panic!("expected TextArray, got {other:?}"),
}
}
#[test]
fn array_agg_keeps_nulls_in_array() {
let mut e = engine_with_tags();
let r = e
.execute("SELECT array_agg(tag) FROM t WHERE id = 3")
.unwrap();
let row = one_row(r);
match &row[0] {
Value::TextArray(items) => {
assert_eq!(items.len(), 2);
assert_eq!(items[0], None);
assert_eq!(items[1], Some("cyan".into()));
}
other => panic!("expected TextArray, got {other:?}"),
}
}
#[test]
fn array_agg_empty_group_returns_null() {
let mut e = engine_with_tags();
let r = e
.execute("SELECT array_agg(tag) FROM t WHERE id = 99")
.unwrap();
let row = one_row(r);
assert_eq!(row[0], Value::Null);
}
#[test]
fn array_agg_int_returns_int_array() {
let mut e = Engine::new();
e.execute("CREATE TABLE n (v INT)").unwrap();
e.execute("INSERT INTO n VALUES (3), (1), (4), (1), (5)")
.unwrap();
let r = e.execute("SELECT array_agg(v) FROM n").unwrap();
let row = one_row(r);
match &row[0] {
Value::IntArray(items) => {
assert_eq!(items, &vec![Some(3), Some(1), Some(4), Some(1), Some(5)]);
}
other => panic!("expected IntArray, got {other:?}"),
}
}
#[test]
fn array_agg_bigint_returns_bigint_array() {
let mut e = Engine::new();
e.execute("CREATE TABLE n (v BIGINT)").unwrap();
e.execute("INSERT INTO n VALUES (10), (20)").unwrap();
let r = e.execute("SELECT array_agg(v) FROM n").unwrap();
let row = one_row(r);
match &row[0] {
Value::BigIntArray(items) => {
assert_eq!(items, &vec![Some(10), Some(20)]);
}
other => panic!("expected BigIntArray, got {other:?}"),
}
}
#[test]
fn array_agg_with_group_by() {
let mut e = engine_with_tags();
let r = e
.execute("SELECT id, array_agg(tag) FROM t GROUP BY id ORDER BY id")
.unwrap();
let QueryResult::Rows { rows, .. } = r else {
panic!()
};
assert_eq!(rows.len(), 3);
if let Value::TextArray(items) = &rows[1].values[1] {
assert_eq!(items.len(), 2);
assert_eq!(items[0], Some("orange".into()));
assert_eq!(items[1], Some("yellow".into()));
} else {
panic!()
}
}
#[test]
fn array_agg_arity_zero_errors() {
let mut e = engine_with_tags();
assert!(e.execute("SELECT array_agg() FROM t").is_err());
}
#[test]
fn array_agg_arity_two_args_errors() {
let mut e = engine_with_tags();
assert!(e.execute("SELECT array_agg(tag, id) FROM t").is_err());
}