#![cfg(feature = "std")]
use graphitesql::{Connection, Value};
fn detail(conn: &Connection, sql: &str) -> Vec<String> {
let r = conn.query(sql).unwrap();
assert_eq!(r.columns.len(), 4); r.rows
.iter()
.map(|row| match &row[3] {
Value::Text(s) => s.clone(),
other => panic!("detail not text: {other:?}"),
})
.collect()
}
fn rows(conn: &Connection, sql: &str) -> Vec<Vec<Value>> {
conn.query(sql).unwrap().rows
}
fn setup() -> Connection {
let mut c = Connection::open_memory().unwrap();
c.execute("CREATE TABLE u(id INTEGER PRIMARY KEY, k, n)")
.unwrap();
c.execute("CREATE INDEX iuk ON u(k)").unwrap();
c.execute("CREATE TABLE t(a, uk)").unwrap();
c.execute("INSERT INTO u VALUES(1,100,'x'),(2,200,'y'),(3,200,'z')")
.unwrap();
c.execute("INSERT INTO t VALUES(10,100),(20,200),(30,999)")
.unwrap();
c
}
fn row(a: i64, n: &str) -> Vec<Value> {
vec![Value::Integer(a), Value::Text(n.to_string())]
}
fn null_row(a: i64) -> Vec<Value> {
vec![Value::Integer(a), Value::Null]
}
#[test]
fn inner_join_eqp_searches_by_index() {
let c = setup();
assert_eq!(
detail(
&c,
"EXPLAIN QUERY PLAN SELECT t.a,u.n FROM t JOIN u ON t.uk=u.k"
),
["SCAN t", "SEARCH u USING INDEX iuk (k=?)"]
);
assert_eq!(
detail(
&c,
"EXPLAIN QUERY PLAN SELECT t.a,u.n FROM t JOIN u ON u.k=t.uk"
),
["SCAN t", "SEARCH u USING INDEX iuk (k=?)"]
);
}
#[test]
fn left_join_eqp_searches_by_index() {
let c = setup();
assert_eq!(
detail(
&c,
"EXPLAIN QUERY PLAN SELECT t.a,u.n FROM t LEFT JOIN u ON t.uk=u.k"
),
["SCAN t", "SEARCH u USING INDEX iuk (k=?) LEFT-JOIN"]
);
}
#[test]
fn inner_join_fans_out_on_duplicate_key_and_drops_unmatched() {
let c = setup();
assert_eq!(
rows(
&c,
"SELECT t.a,u.n FROM t JOIN u ON t.uk=u.k ORDER BY t.a,u.n"
),
[row(10, "x"), row(20, "y"), row(20, "z")]
);
}
#[test]
fn left_join_null_extends_unmatched_keeps_fan_out() {
let c = setup();
assert_eq!(
rows(
&c,
"SELECT t.a,u.n FROM t LEFT JOIN u ON t.uk=u.k ORDER BY t.a,u.n"
),
[row(10, "x"), row(20, "y"), row(20, "z"), null_row(30)]
);
}
#[test]
fn null_key_never_matches() {
let mut c = Connection::open_memory().unwrap();
c.execute("CREATE TABLE u(id INTEGER PRIMARY KEY, k, n)")
.unwrap();
c.execute("CREATE INDEX iuk ON u(k)").unwrap();
c.execute("CREATE TABLE t(a, uk)").unwrap();
c.execute("INSERT INTO u VALUES(1,100,'x')").unwrap();
c.execute("INSERT INTO u VALUES(2,NULL,'q')").unwrap();
c.execute("INSERT INTO t VALUES(10,100),(20,NULL)").unwrap();
assert_eq!(
rows(&c, "SELECT t.a,u.n FROM t JOIN u ON t.uk=u.k ORDER BY t.a"),
[row(10, "x")]
);
assert_eq!(
rows(
&c,
"SELECT t.a,u.n FROM t LEFT JOIN u ON t.uk=u.k ORDER BY t.a"
),
[row(10, "x"), null_row(20)]
);
}
#[test]
fn non_indexed_join_column_uses_an_automatic_index() {
let mut c = Connection::open_memory().unwrap();
c.execute("CREATE TABLE u(id INTEGER PRIMARY KEY, k, n)")
.unwrap();
c.execute("CREATE TABLE t(a, uk)").unwrap();
c.execute("INSERT INTO u VALUES(1,100,'x'),(2,200,'y')")
.unwrap();
c.execute("INSERT INTO t VALUES(10,100),(20,200)").unwrap();
assert_eq!(
detail(
&c,
"EXPLAIN QUERY PLAN SELECT t.a,u.n FROM t JOIN u ON t.uk=u.k"
),
[
"SCAN t",
"BLOOM FILTER ON u (k=?)",
"SEARCH u USING AUTOMATIC COVERING INDEX (k=?)",
]
);
assert_eq!(
rows(&c, "SELECT t.a,u.n FROM t JOIN u ON t.uk=u.k ORDER BY t.a"),
[row(10, "x"), row(20, "y")]
);
}
#[test]
fn nocase_index_seek_reevaluates_on_with_binary() {
let mut c = Connection::open_memory().unwrap();
c.execute("CREATE TABLE u(id INTEGER PRIMARY KEY, k TEXT COLLATE NOCASE, n)")
.unwrap();
c.execute("CREATE INDEX iuk ON u(k)").unwrap();
c.execute("CREATE TABLE t(a, uk TEXT)").unwrap();
c.execute("INSERT INTO u VALUES(1,'Foo','x'),(2,'BAR','y')")
.unwrap();
c.execute("INSERT INTO t VALUES(10,'foo'),(20,'bar'),(30,'FOO')")
.unwrap();
assert_eq!(
detail(
&c,
"EXPLAIN QUERY PLAN SELECT t.a,u.n FROM t JOIN u ON t.uk=u.k"
),
["SCAN t", "SEARCH u USING INDEX iuk (k=?)"]
);
let r = rows(
&c,
"SELECT t.a,u.n FROM t JOIN u ON t.uk=u.k ORDER BY t.a,u.n",
);
assert_eq!(r, Vec::<Vec<Value>>::new(), "got {r:?}");
}