#![cfg(feature = "std")]
use graphitesql::{Connection, Value};
fn plan(conn: &Connection, sql: &str) -> String {
let r = conn
.query(&format!("EXPLAIN QUERY PLAN {sql}"))
.expect("explain query plan");
assert_eq!(r.rows.len(), 1, "expected one plan node for: {sql}");
match &r.rows[0][3] {
Value::Text(s) => s.clone(),
other => panic!("plan detail not text: {other:?}"),
}
}
fn rows(conn: &Connection, sql: &str) -> Vec<Vec<Value>> {
conn.query(sql).expect("query").rows
}
fn setup() -> Connection {
let mut conn = Connection::open_memory().unwrap();
conn.execute("CREATE TABLE t(a INTEGER PRIMARY KEY, b, c)")
.unwrap();
conn.execute("CREATE INDEX ic ON t(c)").unwrap();
conn.execute("INSERT INTO t VALUES(1,10,5),(2,20,5),(3,30,9),(4,NULL,5),(5,50,NULL)")
.unwrap();
conn
}
fn ints(mut got: Vec<Vec<Value>>) -> Vec<i64> {
got.sort_by_key(|r| match r[0] {
Value::Integer(i) => i,
_ => i64::MAX,
});
got.into_iter()
.map(|r| match r[0] {
Value::Integer(i) => i,
_ => panic!("not integer"),
})
.collect()
}
#[test]
fn equality_seek_covered_reports_covering_and_correct_rows() {
let conn = setup();
assert_eq!(
plan(&conn, "SELECT c FROM t WHERE c=5"),
"SEARCH t USING COVERING INDEX ic (c=?)"
);
let got = rows(&conn, "SELECT c FROM t WHERE c=5");
assert_eq!(ints(got), vec![5, 5, 5]);
}
#[test]
fn rowid_projection_is_covered() {
let conn = setup();
assert_eq!(
plan(&conn, "SELECT a, c FROM t WHERE c=5"),
"SEARCH t USING COVERING INDEX ic (c=?)"
);
assert_eq!(
plan(&conn, "SELECT a FROM t WHERE c=5"),
"SEARCH t USING COVERING INDEX ic (c=?)"
);
assert_eq!(
ints(rows(&conn, "SELECT a FROM t WHERE c=5")),
vec![1, 2, 4]
);
}
#[test]
fn non_covered_projection_uses_plain_index() {
let conn = setup();
assert_eq!(
plan(&conn, "SELECT b FROM t WHERE c=5"),
"SEARCH t USING INDEX ic (c=?)"
);
let got = rows(&conn, "SELECT b FROM t WHERE c=5 AND a=4");
assert_eq!(got, vec![vec![Value::Null]]);
}
#[test]
fn residual_where_column_not_in_index_is_not_covered() {
let conn = setup();
assert_eq!(
plan(&conn, "SELECT c FROM t WHERE c=5 AND b>0"),
"SEARCH t USING INDEX ic (c=?)"
);
assert_eq!(
plan(&conn, "SELECT c FROM t WHERE c=5 AND a>1"),
"SEARCH t USING COVERING INDEX ic (c=?)"
);
}
#[test]
fn covering_rows_equal_table_fetch_with_nulls() {
let conn = setup();
let covered = rows(&conn, "SELECT a, c FROM t WHERE c=5 ORDER BY a");
let table_path: Vec<Vec<Value>> = rows(&conn, "SELECT a, c, b FROM t WHERE c=5 ORDER BY a")
.into_iter()
.map(|mut r| {
r.truncate(2);
r
})
.collect();
assert_eq!(covered, table_path);
}
#[test]
fn range_seek_covered() {
let conn = setup();
assert_eq!(
plan(&conn, "SELECT c FROM t WHERE c>4 AND c<9"),
"SEARCH t USING COVERING INDEX ic (c>? AND c<?)"
);
assert_eq!(
ints(rows(&conn, "SELECT c FROM t WHERE c>4 AND c<9")),
vec![5, 5, 5]
);
assert_eq!(
plan(&conn, "SELECT b FROM t WHERE c>4 AND c<9"),
"SEARCH t USING INDEX ic (c>? AND c<?)"
);
}
#[test]
fn in_seek_covered() {
let conn = setup();
assert_eq!(
plan(&conn, "SELECT a, c FROM t WHERE c IN (5, 9)"),
"SEARCH t USING COVERING INDEX ic (c=?)"
);
assert_eq!(
ints(rows(&conn, "SELECT a FROM t WHERE c IN (5,9)")),
vec![1, 2, 3, 4]
);
assert_eq!(
plan(&conn, "SELECT b FROM t WHERE c IN (5,9)"),
"SEARCH t USING INDEX ic (c=?)"
);
}
#[test]
fn generated_column_table_not_covered() {
let mut conn = Connection::open_memory().unwrap();
conn.execute("CREATE TABLE g(a INTEGER PRIMARY KEY, c, d AS (c*2))")
.unwrap();
conn.execute("CREATE INDEX gic ON g(c)").unwrap();
conn.execute("INSERT INTO g(a,c) VALUES(1,5),(2,5),(3,9)")
.unwrap();
assert_eq!(
plan(&conn, "SELECT c FROM g WHERE c=5"),
"SEARCH g USING INDEX gic (c=?)"
);
assert_eq!(ints(rows(&conn, "SELECT c FROM g WHERE c=5")), vec![5, 5]);
}