#![cfg(feature = "std")]
use graphitesql::{Connection, Value};
use std::process::Command;
fn sqlite3_available() -> bool {
Command::new("sqlite3").arg("--version").output().is_ok()
}
fn err_on(c: &Connection, sql: &str) -> String {
let e = c.query(sql).unwrap_err().to_string();
e.trim_start_matches("SQL error: ")
.trim_start_matches("error: ")
.to_string()
}
#[test]
fn too_few_arguments_is_caught_over_an_empty_table() {
let mut c = Connection::open_memory().unwrap();
c.execute("CREATE TABLE t(a)").unwrap();
for sql in [
"SELECT sum() FROM t GROUP BY a",
"SELECT a FROM t GROUP BY a HAVING avg()",
"SELECT total() FROM t",
"SELECT group_concat() FROM t",
] {
let want = alloc_name(sql);
assert_eq!(err_on(&c, sql), want, "for {sql}");
}
}
#[test]
fn too_many_arguments_is_caught_over_an_empty_table() {
let mut c = Connection::open_memory().unwrap();
c.execute("CREATE TABLE t(a)").unwrap();
for sql in [
"SELECT sum(a, a) FROM t",
"SELECT avg(a, a) FROM t GROUP BY a",
"SELECT count(1, 2) FROM t",
"SELECT total(a, a) FROM t",
] {
let want = alloc_name(sql);
assert_eq!(err_on(&c, sql), want, "for {sql}");
}
}
#[test]
fn arity_is_checked_in_every_clause() {
let mut c = Connection::open_memory().unwrap();
c.execute("CREATE TABLE t(a)").unwrap();
assert_eq!(
err_on(&c, "SELECT a FROM t WHERE sum(a, a)"),
"wrong number of arguments to function sum()"
);
assert_eq!(
err_on(&c, "SELECT a FROM t ORDER BY sum(a, a)"),
"wrong number of arguments to function sum()"
);
assert_eq!(
err_on(&c, "SELECT a FROM t GROUP BY avg(a, a)"),
"wrong number of arguments to function avg()"
);
assert_eq!(
err_on(&c, "SELECT a FROM t GROUP BY a HAVING sum(a, a)"),
"wrong number of arguments to function sum()"
);
assert_eq!(
err_on(&c, "SELECT a FROM t HAVING sum(a, a)"),
"HAVING clause on a non-aggregate query"
);
}
#[test]
fn an_aggregate_used_as_a_window_is_arity_checked_too() {
let mut c = Connection::open_memory().unwrap();
c.execute("CREATE TABLE t(a, b)").unwrap();
assert_eq!(
err_on(&c, "SELECT sum(a, b) OVER () FROM t"),
"wrong number of arguments to function sum()"
);
assert_eq!(
err_on(&c, "SELECT sum() OVER () FROM t"),
"wrong number of arguments to function sum()"
);
assert_eq!(
err_on(&c, "SELECT count(a, b) OVER () FROM t"),
"wrong number of arguments to function count()"
);
assert_eq!(
err_on(&c, "SELECT group_concat(a, b, a) OVER () FROM t"),
"wrong number of arguments to function group_concat()"
);
c.execute("INSERT INTO t VALUES(1, 2), (3, 4)").unwrap();
assert_eq!(
c.query("SELECT nth_value(a, 1) OVER (ORDER BY a) FROM t")
.unwrap()
.rows
.len(),
2
);
}
#[test]
fn legitimate_arities_are_still_accepted() {
let mut c = Connection::open_memory().unwrap();
c.execute("CREATE TABLE t(a, b)").unwrap();
c.execute("INSERT INTO t VALUES(1, 10), (2, 20)").unwrap();
let one =
|sql: &str| -> Value { c.query(sql).unwrap().rows.into_iter().next().unwrap()[0].clone() };
assert_eq!(one("SELECT sum(a) FROM t"), Value::Integer(3));
assert_eq!(one("SELECT count() FROM t"), Value::Integer(2));
assert_eq!(one("SELECT count(*) FROM t"), Value::Integer(2));
assert_eq!(one("SELECT count(a) FROM t"), Value::Integer(2));
assert_eq!(
one("SELECT group_concat(a) FROM t"),
Value::Text("1,2".into())
);
assert_eq!(
one("SELECT group_concat(a, '-') FROM t"),
Value::Text("1-2".into())
);
assert_eq!(
one("SELECT max(a, b, 99) FROM t LIMIT 1"),
Value::Integer(99)
);
}
fn alloc_name(sql: &str) -> String {
let open = sql.find('(').unwrap();
let start = sql[..open]
.rfind(|c: char| !c.is_ascii_alphanumeric() && c != '_')
.map(|i| i + 1)
.unwrap_or(0);
let name = &sql[start..open];
format!("wrong number of arguments to function {name}()")
}
#[test]
fn matches_sqlite_cli() {
if !sqlite3_available() {
eprintln!("sqlite3 CLI not found; skipping");
return;
}
let g = env!("CARGO_BIN_EXE_graphitesql");
let run = |bin: &str, sql: &str| -> String {
let out = Command::new(bin).arg(":memory:").arg(sql).output().unwrap();
let stdout = String::from_utf8_lossy(&out.stdout);
let line = stdout.lines().next().unwrap_or("").trim_end().to_string();
if !line.is_empty() {
return line;
}
String::from_utf8_lossy(&out.stderr)
.lines()
.find(|l| !l.trim_start().starts_with('^'))
.unwrap_or("")
.trim_start_matches("Error: in prepare, ")
.trim_start_matches("Error: stepping, ")
.trim_start_matches("Error: ")
.trim_start_matches("SQL error: ")
.trim_start_matches("error: ")
.trim_end()
.to_string()
};
for sql in [
"CREATE TABLE t(a); SELECT sum() FROM t GROUP BY a",
"CREATE TABLE t(a); SELECT a FROM t GROUP BY a HAVING sum()",
"CREATE TABLE t(a); SELECT a FROM t GROUP BY a HAVING avg()",
"CREATE TABLE t(a); SELECT a FROM t ORDER BY sum()",
"CREATE TABLE t(a); SELECT a FROM t ORDER BY sum(a, a)",
"CREATE TABLE t(a); SELECT a FROM t ORDER BY count(1, 2)",
"CREATE TABLE t(a); SELECT avg(a, a) FROM t GROUP BY a",
"CREATE TABLE t(a); SELECT a FROM t GROUP BY sum()",
"CREATE TABLE t(a); SELECT a FROM t WHERE sum()",
"CREATE TABLE t(a); SELECT a FROM t WHERE sum(a, a)",
"CREATE TABLE t(a); SELECT sum(count(1, 2)) FROM t",
"CREATE TABLE t(a,b); SELECT sum(a, b) OVER () FROM t",
"CREATE TABLE t(a,b); SELECT sum() OVER () FROM t",
"CREATE TABLE t(a,b); SELECT group_concat(a, b, a) OVER () FROM t",
"CREATE TABLE t(a,b); INSERT INTO t VALUES(1,2),(3,4); SELECT sum(a) OVER (), group_concat(a,'-') OVER () FROM t",
"CREATE TABLE t(a); INSERT INTO t VALUES(1),(2); SELECT row_number() OVER (ORDER BY a), nth_value(a,1) OVER (ORDER BY a) FROM t",
"CREATE TABLE t(a,b); INSERT INTO t VALUES(1,10),(2,20); SELECT sum(a), count(*), count(a), count(), total(a), group_concat(a), group_concat(a,'-') FROM t",
"CREATE TABLE t(a,b); INSERT INTO t VALUES(1,10),(2,20); SELECT json_group_object(a,b) FROM t",
"CREATE TABLE t(a,b); INSERT INTO t VALUES(1,10),(2,20); SELECT max(a, b, 99) FROM t LIMIT 1",
] {
assert_eq!(run("sqlite3", sql), run(g, sql), "for {sql}");
}
}