#![cfg(feature = "std")]
use graphitesql::{Connection, Value};
use std::process::Command;
fn sqlite3_available() -> bool {
Command::new("sqlite3").arg("--version").output().is_ok()
}
fn render(v: &Value) -> String {
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(),
}
}
fn render_rows(r: &graphitesql::QueryResult) -> String {
r.rows
.iter()
.map(|row| row.iter().map(render).collect::<Vec<_>>().join("|"))
.collect::<Vec<_>>()
.join("\n")
}
#[test]
fn virtual_and_stored() {
let mut c = Connection::open_memory().unwrap();
c.execute("CREATE TABLE t(a INT, b INT, v INT AS (a+b) VIRTUAL, s INT AS (a*b) STORED)")
.unwrap();
c.execute("INSERT INTO t(a,b) VALUES (3,4),(10,20)")
.unwrap();
let r = c.query("SELECT a,b,v,s FROM t ORDER BY a").unwrap();
assert_eq!(render_rows(&r), "3|4|7|12\n10|20|30|200");
assert!(c.execute("INSERT INTO t(a,b,v) VALUES (1,2,3)").is_err());
assert!(c.execute("INSERT INTO t(a,b,s) VALUES (1,2,3)").is_err());
assert!(c.execute("UPDATE t SET v = 99 WHERE a = 3").is_err());
c.execute("UPDATE t SET b = 5 WHERE a = 3").unwrap();
let r = c.query("SELECT v, s FROM t WHERE a = 3").unwrap();
assert_eq!(r.rows[0][0], Value::Integer(8)); assert_eq!(r.rows[0][1], Value::Integer(15)); }
#[test]
fn default_is_virtual() {
let mut c = Connection::open_memory().unwrap();
c.execute("CREATE TABLE t(x INT, y AS (x * 2))").unwrap();
c.execute("INSERT INTO t(x) VALUES (21)").unwrap();
assert_eq!(
c.query("SELECT y FROM t").unwrap().rows[0][0],
Value::Integer(42)
);
}
#[test]
fn index_on_generated_column() {
if !sqlite3_available() {
eprintln!("sqlite3 not found; skipping");
return;
}
let path = std::env::temp_dir().join(format!("gsql-gen-{}.db", std::process::id()));
let path = path.to_string_lossy().into_owned();
let _ = std::fs::remove_file(&path);
{
let mut c = Connection::create(&path).unwrap();
c.execute("CREATE TABLE t(a INT, b INT, sum INT AS (a+b) STORED)")
.unwrap();
c.execute("CREATE INDEX isum ON t(sum)").unwrap();
for i in 1..=50 {
c.execute(&format!("INSERT INTO t(a,b) VALUES ({i}, {})", i * 2))
.unwrap();
}
}
let out = Command::new("sqlite3")
.arg(&path)
.arg("PRAGMA integrity_check;")
.output()
.unwrap();
assert_eq!(String::from_utf8_lossy(&out.stdout).trim(), "ok");
let c = Connection::open_readonly(&path).unwrap();
let r = c.query("SELECT a FROM t WHERE sum = 9").unwrap(); assert_eq!(r.rows.len(), 1);
assert_eq!(r.rows[0][0], Value::Integer(3));
let _ = std::fs::remove_file(&path);
}
#[test]
fn generated_against_sqlite3() {
if !sqlite3_available() {
eprintln!("sqlite3 not found; skipping");
return;
}
let stmts = [
"CREATE TABLE t(a INT, b INT, c TEXT, v AS (a+b) VIRTUAL, s AS (b*b) STORED, t2 AS (c || '!') VIRTUAL)",
"INSERT INTO t(a,b,c) VALUES (1,2,'x'),(5,5,'y'),(10,3,'z')",
"UPDATE t SET a = a + 100 WHERE c = 'y'",
"DELETE FROM t WHERE c = 'z'",
];
let query = "SELECT a,b,c,v,s,t2 FROM t ORDER BY rowid";
let path = std::env::temp_dir().join(format!("gsql-gen2-{}.db", std::process::id()));
let path = path.to_string_lossy().into_owned();
let _ = std::fs::remove_file(&path);
let script = format!("{};{query};", stmts.join(";"));
let out = Command::new("sqlite3")
.arg(&path)
.arg(&script)
.output()
.unwrap();
let want = String::from_utf8_lossy(&out.stdout).trim_end().to_string();
let _ = std::fs::remove_file(&path);
let mut g = Connection::open_memory().unwrap();
for s in stmts {
g.execute(s).unwrap();
}
let got = render_rows(&g.query(query).unwrap());
assert_eq!(got, want);
}
#[test]
fn table_must_have_a_non_generated_column() {
let mut c = Connection::open_memory().unwrap();
assert!(c
.execute("CREATE TABLE t(x INT GENERATED ALWAYS AS (1) VIRTUAL)")
.is_err());
assert!(c
.execute("CREATE TABLE t(a AS (1) STORED, b AS (2) STORED)")
.is_err());
assert!(c.execute("CREATE TABLE t(a, b AS (a + 1))").is_ok());
}
#[test]
fn generated_column_cannot_be_in_primary_key() {
let mut c = Connection::open_memory().unwrap();
assert!(c
.execute("CREATE TABLE t(a, b AS (a + 1) PRIMARY KEY)")
.is_err());
assert!(c
.execute("CREATE TABLE t(a, b AS (a + 1), PRIMARY KEY(b))")
.is_err());
assert!(c
.execute("CREATE TABLE t(a, b AS (a + 1), PRIMARY KEY(a, b))")
.is_err());
assert!(c
.execute("CREATE TABLE t(a, b AS (a + 1) STORED, PRIMARY KEY(a))")
.is_ok());
}
#[test]
fn generated_column_rejects_default_and_double_as() {
let mut c = Connection::open_memory().unwrap();
assert!(c
.execute("CREATE TABLE t(a, b AS (a + 1) DEFAULT 5)")
.is_err());
assert!(c
.execute("CREATE TABLE t(a, b AS (a + 1) AS (a + 2))")
.is_err());
assert!(c
.execute("CREATE TABLE t(a DEFAULT 5, b AS (a + 1))")
.is_ok());
}
#[test]
fn bare_insert_skips_generated_columns() {
let mut c = Connection::open_memory().unwrap();
c.execute("CREATE TABLE t1(a, b GENERATED ALWAYS AS (a+1))")
.unwrap();
c.execute("INSERT INTO t1 VALUES(5)").unwrap();
assert_eq!(
c.query("SELECT a, b FROM t1").unwrap().rows,
vec![vec![Value::Integer(5), Value::Integer(6)]]
);
c.execute("CREATE TABLE t2(a, b AS (a+1) STORED, c)")
.unwrap();
c.execute("INSERT INTO t2 VALUES(5, 9)").unwrap();
assert_eq!(
c.query("SELECT a, b, c FROM t2").unwrap().rows,
vec![vec![
Value::Integer(5),
Value::Integer(6),
Value::Integer(9)
]]
);
c.execute("INSERT INTO t1 SELECT 10").unwrap();
assert_eq!(
c.query("SELECT b FROM t1 WHERE a=10").unwrap().rows[0][0],
Value::Integer(11)
);
c.execute("CREATE TABLE t3(k TEXT PRIMARY KEY, d AS (k||'!')) WITHOUT ROWID")
.unwrap();
c.execute("INSERT INTO t3 VALUES('x')").unwrap();
assert_eq!(
c.query("SELECT d FROM t3").unwrap().rows[0][0],
Value::Text("x!".into())
);
assert!(c.execute("INSERT INTO t1 VALUES(1, 2)").is_err());
assert!(c.execute("INSERT INTO t1(a, b) VALUES(1, 2)").is_err());
}