#![cfg(feature = "std")]
use graphitesql::sql::parser::parse;
use graphitesql::{Connection, Value};
use std::process::Command;
fn sqlite_available() -> bool {
Command::new("sqlite3").arg("--version").output().is_ok()
}
fn render(result: &graphitesql::QueryResult) -> String {
result
.rows
.iter()
.map(|row| {
row.iter()
.map(|v| match v {
Value::Null => String::new(),
Value::Integer(i) => i.to_string(),
Value::Text(s) => s.clone(),
Value::Real(r) => graphitesql::exec::eval::format_real(*r),
Value::Blob(b) => b.iter().map(|x| format!("{x:02x}")).collect(),
})
.collect::<Vec<_>>()
.join("|")
})
.collect::<Vec<_>>()
.join("\n")
}
const SETUP: &str = "CREATE TABLE t(id INTEGER PRIMARY KEY, a INT);\
CREATE TABLE u(id INTEGER PRIMARY KEY, b INT);\
INSERT INTO t(a) VALUES (10),(20),(30);\
INSERT INTO u(b) VALUES (100),(200);";
fn both() -> (Connection, String) {
use std::sync::atomic::{AtomicU64, Ordering};
static SEQ: AtomicU64 = AtomicU64::new(0);
let n = SEQ.fetch_add(1, Ordering::Relaxed);
let path = std::env::temp_dir().join(format!("gsql-psurf-{}-{n}.db", std::process::id()));
let path = path.to_string_lossy().into_owned();
let _ = std::fs::remove_file(&path);
let o = Command::new("sqlite3")
.arg(&path)
.arg(SETUP)
.output()
.unwrap();
assert!(o.status.success(), "{}", String::from_utf8_lossy(&o.stderr));
let mut g = Connection::open_memory().unwrap();
for s in SETUP.split(';') {
if !s.trim().is_empty() {
g.execute(s).unwrap();
}
}
(g, path)
}
fn sqlite_query(path: &str, sql: &str) -> String {
let o = Command::new("sqlite3")
.arg(path)
.arg(format!("{sql};"))
.output()
.unwrap();
String::from_utf8_lossy(&o.stdout).trim_end().to_string()
}
#[test]
fn redundant_from_parens_match_sqlite() {
if !sqlite_available() {
eprintln!("sqlite3 not found; skipping");
return;
}
let (g, path) = both();
let queries = [
"SELECT a FROM (t) ORDER BY a",
"SELECT a FROM ((t)) ORDER BY a",
"SELECT z.a FROM (t) AS z ORDER BY z.a",
"SELECT a FROM ((t) z) ORDER BY a",
"SELECT x FROM ((SELECT 7 AS x))",
"SELECT y.x FROM ((SELECT 7 AS x) y)",
"SELECT * FROM (((SELECT 42)))",
"SELECT a FROM (t NOT INDEXED) ORDER BY a",
];
for q in queries {
let want = sqlite_query(&path, q);
let got = render(&g.query(q).unwrap());
assert_eq!(got, want, "diverged: {q}");
}
let _ = std::fs::remove_file(&path);
}
#[test]
fn schema_qualified_pragma_match_sqlite() {
if !sqlite_available() {
eprintln!("sqlite3 not found; skipping");
return;
}
let (g, path) = both();
for q in [
"PRAGMA main.user_version",
"PRAGMA main.table_info(t)",
"PRAGMA main.index_list(t)",
] {
let want = sqlite_query(&path, q);
let got = render(&g.query(q).unwrap());
assert_eq!(got, want, "pragma diverged: {q}");
}
let _ = std::fs::remove_file(&path);
}
#[test]
fn with_prefixed_insert_select_match_sqlite() {
if !sqlite_available() {
eprintln!("sqlite3 not found; skipping");
return;
}
let (mut g, path) = both();
let stmt =
"WITH src(v) AS (SELECT 100 UNION ALL SELECT 200) INSERT INTO t(a) SELECT v FROM src";
{
let o = Command::new("sqlite3")
.arg(&path)
.arg(format!("{stmt};"))
.output()
.unwrap();
assert!(o.status.success(), "{}", String::from_utf8_lossy(&o.stderr));
}
g.execute(stmt).unwrap();
let q = "SELECT a FROM t ORDER BY a";
assert_eq!(render(&g.query(q).unwrap()), sqlite_query(&path, q));
let mut g2 = Connection::open_memory().unwrap();
g2.execute("CREATE TABLE n(x INT)").unwrap();
g2.execute(
"WITH RECURSIVE c(x) AS (SELECT 1 UNION ALL SELECT x+1 FROM c WHERE x<5) \
INSERT INTO n(x) SELECT x FROM c",
)
.unwrap();
let got = render(&g2.query("SELECT x FROM n ORDER BY x").unwrap());
assert_eq!(got, "1\n2\n3\n4\n5");
let _ = std::fs::remove_file(&path);
}
#[test]
fn deferred_forms_still_rejected() {
assert!(parse("SELECT * FROM (t JOIN u)").is_err());
assert!(parse("SELECT * FROM (t, u)").is_err());
assert!(parse("SELECT * FROM (t JOIN u) AS j").is_err());
}
#[test]
fn with_prefixed_insert_values_parses() {
assert!(parse("WITH x AS (SELECT 1) INSERT INTO t(a) VALUES (1)").is_ok());
assert!(parse("WITH x AS (SELECT 1) INSERT INTO t DEFAULT VALUES").is_ok());
assert!(parse("WITH x(n) AS (VALUES(5)) INSERT INTO t VALUES ((SELECT n FROM x))").is_ok());
}
#[test]
fn offset_and_end_are_usable_column_names() {
if !sqlite_available() {
eprintln!("sqlite3 not found; skipping");
return;
}
let mut g = Connection::open_memory().unwrap();
g.execute("CREATE TABLE k(\"offset\" INT, \"end\" INT)")
.unwrap();
g.execute("INSERT INTO k VALUES (3, 7), (1, 9)").unwrap();
let path = std::env::temp_dir().join(format!("gsql-kw-{}.db", std::process::id()));
let path = path.to_string_lossy().into_owned();
let _ = std::fs::remove_file(&path);
let o = Command::new("sqlite3")
.arg(&path)
.arg("CREATE TABLE k(\"offset\" INT, \"end\" INT); INSERT INTO k VALUES (3,7),(1,9);")
.output()
.unwrap();
assert!(o.status.success());
for q in [
"SELECT offset FROM k ORDER BY offset",
"SELECT end FROM k ORDER BY end",
"SELECT offset + end FROM k ORDER BY offset",
"SELECT offset FROM k ORDER BY offset LIMIT 1 OFFSET 1",
] {
let want = sqlite_query(&path, q);
let got = render(&g.query(q).unwrap());
assert_eq!(got, want, "diverged: {q}");
}
let _ = std::fs::remove_file(&path);
for q in [
"SELECT order FROM k",
"SELECT limit FROM k",
"SELECT table FROM k",
"SELECT where FROM k",
"SELECT from FROM k",
] {
assert!(parse(q).is_err(), "should still reject: {q}");
}
}
#[test]
fn paren_handling_no_regression() {
assert!(parse("SELECT * FROM (SELECT 1) x").is_ok());
assert!(parse("SELECT * FROM t WHERE (id, a) = (1, 10)").is_ok());
assert!(parse("SELECT * FROM t").is_ok());
}