#![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 count(conn: &Connection, sql: &str) -> i64 {
let r = conn.query(sql).unwrap();
assert_eq!(r.rows.len(), 1);
assert_eq!(r.rows[0].len(), 1);
match &r.rows[0][0] {
Value::Integer(n) => *n,
other => panic!("count not integer: {other:?}"),
}
}
#[test]
fn one_index_uses_covering_index_in_eqp() {
let mut c = Connection::open_memory().unwrap();
c.execute("CREATE TABLE t(a INTEGER PRIMARY KEY, b, c)")
.unwrap();
c.execute("CREATE INDEX ib ON t(b)").unwrap();
c.execute("INSERT INTO t VALUES(1,10,100),(2,20,200)")
.unwrap();
assert_eq!(
detail(&c, "EXPLAIN QUERY PLAN SELECT count(*) FROM t"),
["SCAN t USING COVERING INDEX ib"]
);
}
#[test]
fn index_no_narrower_than_table_keeps_plain_scan() {
let mut c = Connection::open_memory().unwrap();
c.execute("CREATE TABLE t(a INTEGER PRIMARY KEY, b)")
.unwrap();
c.execute("CREATE INDEX ib ON t(b)").unwrap();
c.execute("INSERT INTO t VALUES(1,10),(2,20)").unwrap();
assert_eq!(
detail(&c, "EXPLAIN QUERY PLAN SELECT count(*) FROM t"),
["SCAN t"]
);
assert_eq!(count(&c, "SELECT count(*) FROM t"), 2);
}
#[test]
fn one_index_count_is_correct() {
let mut c = Connection::open_memory().unwrap();
c.execute("CREATE TABLE t(a INTEGER PRIMARY KEY, b)")
.unwrap();
c.execute("CREATE INDEX ib ON t(b)").unwrap();
c.execute("INSERT INTO t VALUES(1,10),(2,20),(3,30)")
.unwrap();
assert_eq!(count(&c, "SELECT count(*) FROM t"), 3);
}
#[test]
fn no_index_keeps_plain_scan() {
let mut c = Connection::open_memory().unwrap();
c.execute("CREATE TABLE t(a INTEGER PRIMARY KEY, b)")
.unwrap();
c.execute("INSERT INTO t VALUES(1,10),(2,20)").unwrap();
assert_eq!(
detail(&c, "EXPLAIN QUERY PLAN SELECT count(*) FROM t"),
["SCAN t"]
);
assert_eq!(count(&c, "SELECT count(*) FROM t"), 2);
}
#[test]
fn multiple_indexes_pick_cheapest_covering_index() {
let mut c = Connection::open_memory().unwrap();
c.execute("CREATE TABLE t(a INTEGER PRIMARY KEY, b, c)")
.unwrap();
c.execute("CREATE INDEX ib ON t(b)").unwrap();
c.execute("CREATE INDEX ic ON t(c)").unwrap();
c.execute("INSERT INTO t VALUES(1,10,100),(2,20,200)")
.unwrap();
assert_eq!(
detail(&c, "EXPLAIN QUERY PLAN SELECT count(*) FROM t"),
["SCAN t USING COVERING INDEX ic"]
);
assert_eq!(count(&c, "SELECT count(*) FROM t"), 2);
}
#[test]
fn count_correct_after_delete() {
let mut c = Connection::open_memory().unwrap();
c.execute("CREATE TABLE t(a INTEGER PRIMARY KEY, b, c)")
.unwrap();
c.execute("CREATE INDEX ib ON t(b)").unwrap();
c.execute("INSERT INTO t VALUES(1,10,1),(2,20,2),(3,30,3),(4,40,4)")
.unwrap();
assert_eq!(count(&c, "SELECT count(*) FROM t"), 4);
c.execute("DELETE FROM t WHERE a IN (2,3)").unwrap();
assert_eq!(
detail(&c, "EXPLAIN QUERY PLAN SELECT count(*) FROM t"),
["SCAN t USING COVERING INDEX ib"]
);
assert_eq!(count(&c, "SELECT count(*) FROM t"), 2);
}
#[test]
fn empty_table_counts_zero() {
let mut c = Connection::open_memory().unwrap();
c.execute("CREATE TABLE t(a INTEGER PRIMARY KEY, b)")
.unwrap();
c.execute("CREATE INDEX ib ON t(b)").unwrap();
assert_eq!(count(&c, "SELECT count(*) FROM t"), 0);
}
#[test]
fn partial_index_does_not_cover_count() {
let mut c = Connection::open_memory().unwrap();
c.execute("CREATE TABLE t(a INTEGER PRIMARY KEY, b)")
.unwrap();
c.execute("CREATE INDEX ib ON t(b) WHERE b > 5").unwrap();
c.execute("INSERT INTO t VALUES(1,1),(2,10)").unwrap();
assert_eq!(
detail(&c, "EXPLAIN QUERY PLAN SELECT count(*) FROM t"),
["SCAN t"]
);
assert_eq!(count(&c, "SELECT count(*) FROM t"), 2);
}
#[test]
fn without_rowid_table_uses_plain_scan() {
let mut c = Connection::open_memory().unwrap();
c.execute("CREATE TABLE t(a TEXT PRIMARY KEY, b) WITHOUT ROWID")
.unwrap();
c.execute("CREATE INDEX ib ON t(b)").unwrap();
c.execute("INSERT INTO t VALUES('x',1),('y',2)").unwrap();
assert_eq!(
detail(&c, "EXPLAIN QUERY PLAN SELECT count(*) FROM t"),
["SCAN t"]
);
assert_eq!(count(&c, "SELECT count(*) FROM t"), 2);
}
#[test]
fn count_with_where_is_not_index_covered() {
let mut c = Connection::open_memory().unwrap();
c.execute("CREATE TABLE t(a INTEGER PRIMARY KEY, b)")
.unwrap();
c.execute("CREATE INDEX ib ON t(b)").unwrap();
c.execute("INSERT INTO t VALUES(1,10),(2,20),(3,10)")
.unwrap();
let plan = detail(&c, "EXPLAIN QUERY PLAN SELECT count(*) FROM t WHERE b = 10");
assert_ne!(plan, ["SCAN t USING COVERING INDEX ib"]);
assert_eq!(count(&c, "SELECT count(*) FROM t WHERE b = 10"), 2);
}