#![cfg(feature = "std")]
use graphitesql::{Connection, Value};
use std::process::Command;
fn sqlite3(script: &str) -> String {
let o = Command::new("sqlite3")
.arg(":memory:")
.arg(script)
.output()
.unwrap();
String::from_utf8_lossy(&o.stdout).trim_end().to_string()
}
fn have_sqlite3() -> bool {
Command::new("sqlite3").arg("--version").output().is_ok()
}
fn fmt(v: &Value) -> String {
match v {
Value::Null => String::new(),
Value::Integer(i) => i.to_string(),
Value::Real(r) => {
if *r == (*r as i64) as f64 {
format!("{:.1}", r)
} else {
format!("{r}")
}
}
Value::Text(s) => s.clone(),
Value::Blob(b) => String::from_utf8_lossy(b).into_owned(),
}
}
fn graphite_rows(c: &Connection, sql: &str) -> String {
c.query(sql)
.unwrap()
.rows
.iter()
.map(|row| row.iter().map(fmt).collect::<Vec<_>>().join("|"))
.collect::<Vec<_>>()
.join("\n")
}
fn graphite_plan(c: &Connection, sql: &str) -> Vec<String> {
c.query(&format!("EXPLAIN QUERY PLAN {sql}"))
.unwrap()
.rows
.iter()
.map(|row| match row.last() {
Some(Value::Text(s)) => s.clone(),
other => panic!("plan detail not text: {other:?}"),
})
.collect()
}
fn sqlite_plan(setup: &str, sql: &str) -> Vec<String> {
let out = sqlite3(&format!("{setup}\nEXPLAIN QUERY PLAN {sql};"));
out.lines()
.filter(|l| *l != "QUERY PLAN")
.map(|l| l.trim_start_matches(['|', '-', '`', ' ']).to_string())
.collect()
}
fn graphite(setup: &str, use_vdbe: bool) -> Connection {
let mut c = Connection::open_memory().unwrap();
c.set_use_vdbe(use_vdbe);
for stmt in setup.split(';') {
if !stmt.trim().is_empty() {
c.execute(stmt).unwrap();
}
}
c
}
fn assert_rows(setup: &str, sql: &str) {
let want = sqlite3(&format!("{setup}\n{sql};"));
for &vdbe in &[true, false] {
let c = graphite(setup, vdbe);
let got = graphite_rows(&c, sql);
assert_eq!(got, want, "rows diverged (use_vdbe={vdbe}) for `{sql}`");
}
}
fn assert_plan(setup: &str, sql: &str) {
let want = sqlite_plan(setup, sql);
let c = graphite(setup, true);
let got = graphite_plan(&c, sql);
assert_eq!(got, want, "plan diverged for `{sql}`");
}
const SETUP: &str = "CREATE TABLE a(m, n);\
CREATE TABLE b(p, q);\
CREATE INDEX ib ON b(p);\
INSERT INTO a VALUES(3,30),(1,10),(2,20);\
INSERT INTO b VALUES(2,222),(1,111),(3,333);";
#[test]
fn bug_case_explicit_join() {
if !have_sqlite3() {
eprintln!("sqlite3 not found; skipping");
return;
}
assert_plan(SETUP, "SELECT * FROM b JOIN a ON a.m=b.p");
assert_rows(SETUP, "SELECT * FROM b JOIN a ON a.m=b.p");
}
#[test]
fn bug_case_comma_join() {
if !have_sqlite3() {
eprintln!("sqlite3 not found; skipping");
return;
}
assert_plan(SETUP, "SELECT * FROM b, a WHERE a.m=b.p");
assert_rows(SETUP, "SELECT * FROM b, a WHERE a.m=b.p");
}
#[test]
fn select_star_keeps_declared_column_order() {
if !have_sqlite3() {
eprintln!("sqlite3 not found; skipping");
return;
}
assert_rows(SETUP, "SELECT * FROM b JOIN a ON a.m=b.p");
assert_rows(SETUP, "SELECT b.*, a.* FROM b JOIN a ON a.m=b.p");
assert_rows(SETUP, "SELECT a.n, b.p, a.m FROM b JOIN a ON a.m=b.p");
}
#[test]
fn covering_index_when_only_indexed_column_needed() {
if !have_sqlite3() {
eprintln!("sqlite3 not found; skipping");
return;
}
assert_plan(SETUP, "SELECT b.p FROM b JOIN a ON a.m=b.p");
assert_rows(SETUP, "SELECT b.p FROM b JOIN a ON a.m=b.p");
assert_plan(SETUP, "SELECT a.m FROM b JOIN a ON a.m=b.p");
}
#[test]
fn non_unique_index_fans_out_in_index_order() {
if !have_sqlite3() {
eprintln!("sqlite3 not found; skipping");
return;
}
let setup = "CREATE TABLE a(m, n);\
CREATE TABLE b(p, q);\
CREATE INDEX ib ON b(p);\
INSERT INTO a VALUES(3,30),(1,10);\
INSERT INTO b VALUES(3,301),(1,101),(3,302),(1,102);";
assert_plan(setup, "SELECT * FROM b JOIN a ON a.m=b.p");
assert_rows(setup, "SELECT * FROM b JOIN a ON a.m=b.p");
}
#[test]
fn where_filter_on_either_side() {
if !have_sqlite3() {
eprintln!("sqlite3 not found; skipping");
return;
}
assert_plan(SETUP, "SELECT * FROM b JOIN a ON a.m=b.p WHERE b.q>150");
assert_rows(SETUP, "SELECT * FROM b JOIN a ON a.m=b.p WHERE b.q>150");
assert_rows(SETUP, "SELECT * FROM b JOIN a ON a.m=b.p WHERE a.n=30");
}
#[test]
fn explicit_order_by_still_sorts() {
if !have_sqlite3() {
eprintln!("sqlite3 not found; skipping");
return;
}
assert_rows(SETUP, "SELECT * FROM b JOIN a ON a.m=b.p ORDER BY b.q");
assert_rows(SETUP, "SELECT * FROM b JOIN a ON a.m=b.p ORDER BY a.m DESC");
}
#[test]
fn second_table_indexed_forward_seek_not_swapped() {
if !have_sqlite3() {
eprintln!("sqlite3 not found; skipping");
return;
}
let setup = "CREATE TABLE a(m, n);\
CREATE TABLE b(p, q);\
CREATE INDEX ib ON b(p);\
CREATE INDEX ia ON a(m);\
INSERT INTO a VALUES(3,30),(1,10),(2,20);\
INSERT INTO b VALUES(2,222),(1,111),(3,333);";
assert_plan(setup, "SELECT * FROM b JOIN a ON a.m=b.p");
assert_rows(setup, "SELECT * FROM b JOIN a ON a.m=b.p");
}
#[test]
fn second_table_rowid_forward_seek_not_swapped() {
if !have_sqlite3() {
eprintln!("sqlite3 not found; skipping");
return;
}
let setup = "CREATE TABLE a(m INTEGER PRIMARY KEY, n);\
CREATE TABLE b(p, q);\
CREATE INDEX ib ON b(p);\
INSERT INTO a VALUES(3,30),(1,10),(2,20);\
INSERT INTO b VALUES(2,222),(1,111),(3,333);";
assert_plan(setup, "SELECT * FROM b JOIN a ON a.m=b.p");
assert_rows(setup, "SELECT * FROM b JOIN a ON a.m=b.p");
}
#[test]
fn first_table_rowid_uses_rowid_slice_not_index_swap() {
if !have_sqlite3() {
eprintln!("sqlite3 not found; skipping");
return;
}
let setup = "CREATE TABLE b(p INTEGER PRIMARY KEY, q);\
CREATE TABLE a(m, n);\
INSERT INTO b VALUES(2,222),(1,111),(3,333);\
INSERT INTO a VALUES(3,30),(1,10),(2,20);";
assert_plan(setup, "SELECT * FROM b JOIN a ON a.m=b.p");
assert_rows(setup, "SELECT * FROM b JOIN a ON a.m=b.p");
let c = graphite(setup, true);
let plan = graphite_plan(&c, "SELECT * FROM b JOIN a ON a.m=b.p");
assert_eq!(plan[0], "SCAN a");
assert_eq!(plan[1], "SEARCH b USING INTEGER PRIMARY KEY (rowid=?)");
}
#[test]
fn left_join_not_reordered() {
if !have_sqlite3() {
eprintln!("sqlite3 not found; skipping");
return;
}
assert_plan(SETUP, "SELECT * FROM b LEFT JOIN a ON a.m=b.p");
assert_rows(SETUP, "SELECT * FROM b LEFT JOIN a ON a.m=b.p");
let c = graphite(SETUP, true);
assert_eq!(
graphite_plan(&c, "SELECT * FROM b LEFT JOIN a ON a.m=b.p")[0],
"SCAN b"
);
}
#[test]
fn neither_seekable_unchanged() {
if !have_sqlite3() {
eprintln!("sqlite3 not found; skipping");
return;
}
let setup = "CREATE TABLE a(m, n);\
CREATE TABLE b(p, q);\
INSERT INTO a VALUES(3,30),(1,10);\
INSERT INTO b VALUES(1,111),(3,333);";
assert_plan(setup, "SELECT * FROM b JOIN a ON a.m=b.p");
assert_rows(setup, "SELECT * FROM b JOIN a ON a.m=b.p");
}
#[test]
fn three_table_join_unchanged() {
if !have_sqlite3() {
eprintln!("sqlite3 not found; skipping");
return;
}
let setup = "CREATE TABLE a(m, n);\
CREATE TABLE b(p, q);\
CREATE TABLE c(x, y);\
CREATE INDEX ib ON b(p);\
INSERT INTO a VALUES(3,30),(1,10);\
INSERT INTO b VALUES(1,111),(3,333);\
INSERT INTO c VALUES(1,9),(3,8);";
let sql = "SELECT b.p,a.n,c.y FROM b JOIN a ON a.m=b.p JOIN c ON c.x=a.m ORDER BY b.p";
assert_rows(setup, sql);
}