use spg_engine::{Engine, QueryResult};
use spg_storage::Value;
fn one_row(r: QueryResult) -> Vec<Value<'static>> {
match r {
QueryResult::Rows { rows, .. } => {
assert_eq!(rows.len(), 1, "expected exactly 1 row");
rows.into_iter().next().unwrap().values
}
_ => panic!("expected rows"),
}
}
fn text(e: &mut Engine, sql: &str) -> String {
let r = e.execute(sql).unwrap_or_else(|err| {
panic!("execute({sql:?}) failed: {err:?}");
});
let row = one_row(r);
match &row[0] {
Value::Text(s) => s.to_string(),
Value::Null => panic!("got NULL, expected Text"),
other => panic!("expected Text, got {other:?}"),
}
}
#[test]
fn trim_strips_leading_and_trailing_space() {
let mut e = Engine::new();
assert_eq!(text(&mut e, "SELECT trim(' hello ')"), "hello");
}
#[test]
fn trim_only_leading_space() {
let mut e = Engine::new();
assert_eq!(text(&mut e, "SELECT trim(' hello')"), "hello");
}
#[test]
fn trim_only_trailing_space() {
let mut e = Engine::new();
assert_eq!(text(&mut e, "SELECT trim('hello ')"), "hello");
}
#[test]
fn trim_inner_space_preserved() {
let mut e = Engine::new();
assert_eq!(
text(&mut e, "SELECT trim(' hello world ')"),
"hello world"
);
}
#[test]
fn trim_all_space_yields_empty() {
let mut e = Engine::new();
assert_eq!(text(&mut e, "SELECT trim(' ')"), "");
}
#[test]
fn trim_empty_input_yields_empty() {
let mut e = Engine::new();
assert_eq!(text(&mut e, "SELECT trim('')"), "");
}
#[test]
fn trim_no_strippable_chars_passthrough() {
let mut e = Engine::new();
assert_eq!(text(&mut e, "SELECT trim('hello')"), "hello");
}
#[test]
fn trim_does_not_strip_tab_or_newline_by_default() {
let mut e = Engine::new();
assert_eq!(text(&mut e, "SELECT trim('\thello\n')"), "\thello\n");
}
#[test]
fn trim_with_chars_strips_given_set() {
let mut e = Engine::new();
assert_eq!(text(&mut e, "SELECT trim('xxhelloxx', 'x')"), "hello");
}
#[test]
fn trim_with_multi_char_set() {
let mut e = Engine::new();
assert_eq!(text(&mut e, "SELECT trim('xyhelloyx', 'xy')"), "hello");
}
#[test]
fn trim_with_chars_does_not_strip_inner_occurrences() {
let mut e = Engine::new();
assert_eq!(
text(&mut e, "SELECT trim('xhelloxxworld', 'x')"),
"helloxxworld"
);
}
#[test]
fn trim_chars_unicode_multibyte_set() {
let mut e = Engine::new();
assert_eq!(text(&mut e, "SELECT trim('日hello日', '日')"), "hello");
}
#[test]
fn ltrim_strips_left_only_default_space() {
let mut e = Engine::new();
assert_eq!(text(&mut e, "SELECT ltrim(' hello ')"), "hello ");
}
#[test]
fn rtrim_strips_right_only_default_space() {
let mut e = Engine::new();
assert_eq!(text(&mut e, "SELECT rtrim(' hello ')"), " hello");
}
#[test]
fn ltrim_with_chars() {
let mut e = Engine::new();
assert_eq!(text(&mut e, "SELECT ltrim('xxhello', 'x')"), "hello");
}
#[test]
fn rtrim_with_chars() {
let mut e = Engine::new();
assert_eq!(text(&mut e, "SELECT rtrim('helloxx', 'x')"), "hello");
}
#[test]
fn ltrim_doesnt_touch_right_side() {
let mut e = Engine::new();
assert_eq!(text(&mut e, "SELECT ltrim('xxhelloxx', 'x')"), "helloxx");
}
#[test]
fn rtrim_doesnt_touch_left_side() {
let mut e = Engine::new();
assert_eq!(text(&mut e, "SELECT rtrim('xxhelloxx', 'x')"), "xxhello");
}
#[test]
fn ltrim_empty_input() {
let mut e = Engine::new();
assert_eq!(text(&mut e, "SELECT ltrim('')"), "");
}
#[test]
fn rtrim_empty_input() {
let mut e = Engine::new();
assert_eq!(text(&mut e, "SELECT rtrim('')"), "");
}
#[test]
fn btrim_is_same_as_trim() {
let mut e = Engine::new();
assert_eq!(text(&mut e, "SELECT btrim(' hello ')"), "hello");
assert_eq!(text(&mut e, "SELECT btrim('xxhelloxx', 'x')"), "hello");
}
#[test]
fn trim_null_input_returns_null() {
let mut e = Engine::new();
let row = one_row(e.execute("SELECT trim(NULL)").unwrap());
assert_eq!(row[0], Value::Null);
}
#[test]
fn trim_null_chars_returns_null() {
let mut e = Engine::new();
let row = one_row(e.execute("SELECT trim('xx', NULL)").unwrap());
assert_eq!(row[0], Value::Null);
}
#[test]
fn ltrim_null_returns_null() {
let mut e = Engine::new();
let row = one_row(e.execute("SELECT ltrim(NULL)").unwrap());
assert_eq!(row[0], Value::Null);
}
#[test]
fn rtrim_null_returns_null() {
let mut e = Engine::new();
let row = one_row(e.execute("SELECT rtrim(NULL)").unwrap());
assert_eq!(row[0], Value::Null);
}
#[test]
fn trim_column_type_is_text() {
let mut e = Engine::new();
let r = e.execute("SELECT trim(' x ')").unwrap();
let QueryResult::Rows { columns, .. } = r else {
panic!()
};
assert_eq!(columns[0].ty, spg_storage::DataType::Text);
}
#[test]
fn trim_over_column() {
let mut e = Engine::new();
e.execute("CREATE TABLE u (n TEXT NOT NULL)").unwrap();
e.execute("INSERT INTO u VALUES (' john '), (' ')")
.unwrap();
let r = e.execute("SELECT trim(n) FROM u ORDER BY n").unwrap();
let QueryResult::Rows { rows, .. } = r else {
panic!()
};
assert_eq!(rows[0].values[0], Value::text(String::new()));
assert_eq!(rows[1].values[0], Value::text("john"));
}
#[test]
fn trim_no_args_errors() {
let mut e = Engine::new();
assert!(e.execute("SELECT trim()").is_err());
}
#[test]
fn trim_too_many_args_errors() {
let mut e = Engine::new();
assert!(e.execute("SELECT trim('a', 'b', 'c')").is_err());
}
#[test]
fn ltrim_no_args_errors() {
let mut e = Engine::new();
assert!(e.execute("SELECT ltrim()").is_err());
}
#[test]
fn trim_numeric_input_coerced_to_text() {
let mut e = Engine::new();
let m = e
.execute("SELECT trim(42)")
.expect_err("PG rejects trim(integer)")
.to_string();
assert!(
m.contains("function pg_catalog.btrim(integer) does not exist"),
"{m}"
);
}
#[test]
fn trim_inside_where() {
let mut e = Engine::new();
e.execute("CREATE TABLE u (id INT NOT NULL, n TEXT NOT NULL)")
.unwrap();
e.execute("INSERT INTO u VALUES (1, ' alice '), (2, ' bob ')")
.unwrap();
let r = e
.execute("SELECT id FROM u WHERE trim(n) = 'alice'")
.unwrap();
let QueryResult::Rows { rows, .. } = r else {
panic!()
};
assert_eq!(rows.len(), 1);
assert_eq!(rows[0].values[0], Value::Int(1));
}
#[test]
fn trim_inside_insert_values() {
let mut e = Engine::new();
e.execute("CREATE TABLE u (n TEXT NOT NULL)").unwrap();
e.execute("INSERT INTO u VALUES (trim(' hello '))")
.unwrap();
let row = one_row(e.execute("SELECT n FROM u").unwrap());
assert_eq!(row[0], Value::text("hello"));
}
#[test]
fn nested_trim_calls() {
let mut e = Engine::new();
assert_eq!(
text(&mut e, "SELECT trim(trim(' hello '), 'h')"),
"ello"
);
}