#![cfg(feature = "std")]
use graphitesql::{Connection, Value};
use std::process::Command;
fn have_sqlite3() -> bool {
Command::new("sqlite3").arg("--version").output().is_ok()
}
fn sqlite3(setup: &str, query: &str) -> String {
let sql = format!("{setup} {query};");
let o = Command::new("sqlite3")
.arg(":memory:")
.arg(sql)
.output()
.unwrap();
String::from_utf8_lossy(&o.stdout).trim_end().to_string()
}
fn graphite(setup: &str, query: &str) -> String {
let mut c = Connection::open_memory().unwrap();
for stmt in setup.split(';') {
if !stmt.trim().is_empty() {
c.execute(stmt).unwrap();
}
}
let r = c.query(query).unwrap();
r.rows
.iter()
.map(|row| row.iter().map(render).collect::<Vec<_>>().join("|"))
.collect::<Vec<_>>()
.join("\n")
}
fn render(v: &Value) -> String {
match v {
Value::Null => String::new(),
Value::Integer(i) => i.to_string(),
Value::Real(r) => graphitesql::exec::eval::format_real(*r),
Value::Text(s) => s.clone(),
Value::Blob(b) => String::from_utf8_lossy(b).into_owned(),
}
}
fn check(setup: &str, query: &str, expect: &str) {
let got = graphite(setup, query);
assert_eq!(got, expect, "graphite diverged for `{query}`");
if have_sqlite3() {
let oracle = sqlite3(setup, query);
assert_eq!(
oracle, expect,
"pinned value stale vs live sqlite3 for `{query}`"
);
}
}
#[test]
fn in_list_applies_comparison_affinity() {
let s = "CREATE TABLE t(i INTEGER, txt TEXT, n NUMERIC, b BLOB, none_col);\
INSERT INTO t VALUES(10,'10',10,10,10);";
check(s, "SELECT i IN ('10','20') FROM t", "1");
check(s, "SELECT i IN ('10.0') FROM t", "1");
check(s, "SELECT n IN ('10') FROM t", "1");
check(s, "SELECT txt IN (10,20) FROM t", "1");
check(s, "SELECT txt IN ('10') FROM t", "1");
check(s, "SELECT none_col IN ('10') FROM t", "0");
check(s, "SELECT b IN ('10') FROM t", "0");
check(s, "SELECT i NOT IN ('5','15') FROM t", "1");
check("", "SELECT 'a' IN ('A' COLLATE NOCASE)", "1");
check("", "SELECT 'a' IN ('x','A' COLLATE NOCASE)", "0");
}
#[test]
fn between_applies_comparison_affinity() {
let s = "CREATE TABLE t(i INTEGER, txt TEXT);\
INSERT INTO t VALUES(10,'10');";
check(s, "SELECT i BETWEEN '5' AND '15' FROM t", "1");
check(s, "SELECT i BETWEEN '9' AND '100' FROM t", "1");
check(s, "SELECT i BETWEEN 9.5 AND 10.5 FROM t", "1");
check(s, "SELECT txt BETWEEN 5 AND 15 FROM t", "0");
check(s, "SELECT txt BETWEEN 9 AND 100 FROM t", "0");
}
#[test]
fn rowid_alias_has_integer_affinity() {
let s = "CREATE TABLE r(a); INSERT INTO r VALUES(1),(2),(3);";
check(s, "SELECT a FROM r WHERE rowid = '2'", "2");
check(s, "SELECT a FROM r WHERE rowid < '3' ORDER BY a", "1\n2");
check(
s,
"SELECT a FROM r WHERE rowid IN ('1','3') ORDER BY a",
"1\n3",
);
check(
s,
"SELECT a FROM r WHERE rowid BETWEEN '2' AND '3' ORDER BY a",
"2\n3",
);
check(
s,
"SELECT rowid, rowid IN ('1','3') FROM r",
"1|1\n2|0\n3|1",
);
let p = "CREATE TABLE p(id INTEGER PRIMARY KEY, v);\
INSERT INTO p VALUES(1,'a'),(2,'b'),(3,'c');";
check(
p,
"SELECT v FROM p WHERE id IN ('1','3') ORDER BY id",
"a\nc",
);
check(
p,
"SELECT v FROM p WHERE id BETWEEN '2' AND '3' ORDER BY id",
"b\nc",
);
check(p, "SELECT v FROM p WHERE id = '2'", "b");
}
#[test]
fn comparison_affinity_column_vs_literal() {
let s = "CREATE TABLE t(i INTEGER, txt TEXT, n NUMERIC, r REAL, b BLOB, none_col);\
INSERT INTO t VALUES(10,'10',10,10.0,x'3130',10);";
check(s, "SELECT i < '9' FROM t", "0");
check(s, "SELECT txt < 9 FROM t", "1");
check(s, "SELECT n = '10' FROM t", "1");
check(s, "SELECT r > '5' FROM t", "1");
check(s, "SELECT none_col = '10' FROM t", "0");
check(s, "SELECT none_col = 10 FROM t", "1");
check(s, "SELECT b = '10' FROM t", "0");
check(s, "SELECT b = x'3130' FROM t", "1");
check("", "SELECT '10' < 9", "0");
check("", "SELECT 10 < '9'", "1");
check("", "SELECT '10' = 10", "0");
}
#[test]
fn storage_affinity_on_insert() {
let s = "CREATE TABLE t(i INTEGER, n NUMERIC, r REAL, b BLOB, x);";
let cols = "typeof(i)||':'||quote(i), typeof(n)||':'||quote(n), \
typeof(r)||':'||quote(r), typeof(b)||':'||quote(b), \
typeof(x)||':'||quote(x)";
check(
&format!("{s} INSERT INTO t VALUES('123','123','123','123','123');"),
&format!("SELECT {cols} FROM t"),
"integer:123|integer:123|real:123.0|text:'123'|text:'123'",
);
check(
&format!("{s} INSERT INTO t VALUES('2e2','2e2','2e2','2e2','2e2');"),
&format!("SELECT {cols} FROM t"),
"integer:200|integer:200|real:200.0|text:'2e2'|text:'2e2'",
);
check(
&format!("{s} INSERT INTO t VALUES('12abc','12abc','12abc','12abc','12abc');"),
&format!("SELECT {cols} FROM t"),
"text:'12abc'|text:'12abc'|text:'12abc'|text:'12abc'|text:'12abc'",
);
check(
&format!("{s} INSERT INTO t VALUES(12.0,12.0,12.0,12.0,12.0);"),
&format!("SELECT {cols} FROM t"),
"integer:12|integer:12|real:12.0|real:12.0|real:12.0",
);
}
#[test]
fn cast_edges() {
check("", "SELECT CAST(' 42 ' AS INTEGER)", "42");
check("", "SELECT CAST('0x10' AS INTEGER)", "0");
check("", "SELECT CAST('2e2' AS INTEGER)", "2");
check(
"",
"SELECT CAST('99999999999999999999' AS INTEGER)",
"9223372036854775807",
);
check(
"",
"SELECT CAST('-99999999999999999999' AS INTEGER)",
"-9223372036854775808",
);
check("", "SELECT CAST(3.9 AS INTEGER)", "3");
check("", "SELECT CAST(-3.9 AS INTEGER)", "-3");
check("", "SELECT CAST(1e300 AS INTEGER)", "9223372036854775807");
check("", "SELECT CAST(-1e300 AS INTEGER)", "-9223372036854775808");
check(
"",
"SELECT CAST(9223372036854775807.0 AS INTEGER)",
"9223372036854775807",
);
check("", "SELECT typeof(CAST('3.0' AS NUMERIC))", "integer");
check("", "SELECT typeof(CAST(3.0 AS NUMERIC))", "real");
check("", "SELECT CAST('abc' AS NUMERIC)", "0");
check("", "SELECT CAST(x'3132' AS INTEGER)", "12");
check("", "SELECT quote(CAST(65 AS BLOB))", "X'3635'");
check("", "SELECT typeof(CAST(NULL AS INTEGER))", "null");
check("", "SELECT quote(CAST(NULL AS BLOB))", "NULL");
}
#[test]
fn arithmetic_coercion_and_overflow() {
check("", "SELECT '10abc'+5", "15");
check("", "SELECT ' 7 '+1", "8");
check("", "SELECT 'abc'+1", "1");
check("", "SELECT -'5abc'", "-5");
check("", "SELECT typeof(9223372036854775807+1)", "real");
check("", "SELECT typeof(-9223372036854775808 * -1)", "real");
check("", "SELECT 10/0", "");
check("", "SELECT 10%0", "");
check("", "SELECT 7.5%2", "1.0");
check("", "SELECT 9223372036854775807 % -1", "0");
check(
"",
"SELECT 9223372036854775807 / -1",
"-9223372036854775807",
);
}