#![cfg(feature = "std")]
use graphitesql::{Connection, Value};
use std::process::Command;
fn sqlite3_available() -> bool {
Command::new("sqlite3").arg("--version").output().is_ok()
}
fn err(c: &mut Connection, sql: &str) -> String {
c.execute(sql)
.unwrap_err()
.to_string()
.trim_start_matches("error: ")
.to_string()
}
#[test]
fn rowid_alias_as_insert_column_sets_the_rowid() {
let mut c = Connection::open_memory().unwrap();
c.execute("CREATE TABLE t(a, b)").unwrap();
c.execute("INSERT INTO t(rowid, a, b) VALUES (5, 10, 20)")
.unwrap();
c.execute("INSERT INTO t(_rowid_, a) VALUES (7, 1)")
.unwrap();
c.execute("INSERT INTO t(oid, a) VALUES (9, 2)").unwrap();
let rows = c
.query("SELECT rowid, a FROM t ORDER BY rowid")
.unwrap()
.rows;
let got: Vec<(i64, Value)> = rows
.into_iter()
.map(|mut r| {
let a = r.remove(1);
match r.remove(0) {
Value::Integer(i) => (i, a),
other => panic!("rowid not integer: {other:?}"),
}
})
.collect();
assert_eq!(
got,
vec![
(5, Value::Integer(10)),
(7, Value::Integer(1)),
(9, Value::Integer(2)),
]
);
}
#[test]
fn rowid_value_is_coerced_then_integer_checked() {
let mut c = Connection::open_memory().unwrap();
c.execute("CREATE TABLE t(a)").unwrap();
c.execute("INSERT INTO t(rowid, a) VALUES ('5', 1)")
.unwrap();
c.execute("INSERT INTO t(rowid, a) VALUES (6.0, 2)")
.unwrap();
let ids: Vec<Value> = c
.query("SELECT rowid FROM t ORDER BY rowid")
.unwrap()
.rows
.into_iter()
.map(|mut r| r.remove(0))
.collect();
assert_eq!(ids, vec![Value::Integer(5), Value::Integer(6)]);
c.execute("INSERT INTO t(rowid, a) VALUES (NULL, 3)")
.unwrap();
for v in ["1.5", "'x'", "x'00'"] {
let sql = format!("INSERT INTO t(rowid, a) VALUES ({v}, 9)");
assert_eq!(err(&mut c, &sql), "datatype mismatch", "for {sql}");
}
}
#[test]
fn rowid_alias_targets_an_integer_primary_key() {
let mut c = Connection::open_memory().unwrap();
c.execute("CREATE TABLE t(a INTEGER PRIMARY KEY, b)")
.unwrap();
c.execute("INSERT INTO t(rowid, b) VALUES (3, 9)").unwrap();
c.execute("INSERT INTO t(rowid, a, b) VALUES (1, 2, 3)")
.unwrap();
let rows = c.query("SELECT a, b FROM t ORDER BY a").unwrap().rows;
let got: Vec<(Value, Value)> = rows
.into_iter()
.map(|mut r| (r.remove(0), r.remove(0)))
.collect();
assert_eq!(
got,
vec![
(Value::Integer(2), Value::Integer(3)),
(Value::Integer(3), Value::Integer(9)),
]
);
}
#[test]
fn a_real_column_named_rowid_shadows_the_pseudo_column() {
let mut c = Connection::open_memory().unwrap();
c.execute("CREATE TABLE t(rowid, a)").unwrap();
c.execute("INSERT INTO t(rowid, a) VALUES (5, 1)").unwrap();
let mut row = c.query("SELECT rowid, a FROM t").unwrap().rows.remove(0);
assert_eq!(row.remove(0), Value::Integer(5));
assert_eq!(row.remove(0), Value::Integer(1));
}
#[test]
fn rowid_is_rejected_on_a_without_rowid_table() {
let mut c = Connection::open_memory().unwrap();
c.execute("CREATE TABLE t(a PRIMARY KEY, b) WITHOUT ROWID")
.unwrap();
let e = err(&mut c, "INSERT INTO t(rowid, a, b) VALUES (1, 2, 3)");
assert_eq!(e, "table t has no column named rowid");
}
#[test]
fn matches_sqlite_cli() {
if !sqlite3_available() {
eprintln!("sqlite3 CLI not found; skipping");
return;
}
let g = env!("CARGO_BIN_EXE_graphitesql");
let run = |bin: &str, sql: &str| -> String {
let out = Command::new(bin).arg(":memory:").arg(sql).output().unwrap();
let stdout = String::from_utf8_lossy(&out.stdout);
if !stdout.trim().is_empty() {
return stdout.trim_end().to_string();
}
let mut line = String::from_utf8_lossy(&out.stderr)
.lines()
.find(|l| !l.trim_start().starts_with('^'))
.unwrap_or("")
.trim_start_matches("Error: in prepare, ")
.trim_start_matches("Error: stepping, ")
.trim_start_matches("Error: ")
.trim_start_matches("SQL error: ")
.trim_start_matches("error: ")
.trim_end()
.to_string();
if let Some(open) = line.rfind(" (") {
if line.ends_with(')')
&& line[open + 2..line.len() - 1]
.chars()
.all(|c| c.is_ascii_digit())
{
line.truncate(open);
}
}
line
};
for sql in [
"CREATE TABLE t(a,b); INSERT INTO t(rowid,a,b) VALUES(5,10,20); SELECT rowid,a,b FROM t;",
"CREATE TABLE t(a,b); INSERT INTO t(_rowid_,a) VALUES(7,1); SELECT rowid,a FROM t;",
"CREATE TABLE t(a,b); INSERT INTO t(oid,a) VALUES(9,1); SELECT rowid,a FROM t;",
"CREATE TABLE t(a); INSERT INTO t(rowid,a) VALUES('5',1); SELECT rowid FROM t;",
"CREATE TABLE t(a); INSERT INTO t(rowid,a) VALUES(5.0,1); SELECT rowid FROM t;",
"CREATE TABLE t(a); INSERT INTO t(rowid,a) VALUES(1.5,1);",
"CREATE TABLE t(a); INSERT INTO t(rowid,a) VALUES('x',1);",
"CREATE TABLE t(a); INSERT INTO t(rowid,a) VALUES(x'00',1);",
"CREATE TABLE t(a); INSERT INTO t(rowid,a) VALUES(NULL,1); SELECT typeof(rowid) FROM t;",
"CREATE TABLE t(a INTEGER PRIMARY KEY,b); INSERT INTO t(rowid,b) VALUES(3,9); SELECT a,rowid,b FROM t;",
"CREATE TABLE t(a INTEGER PRIMARY KEY,b); INSERT INTO t(rowid,a,b) VALUES(1,2,3); SELECT a,b FROM t;",
"CREATE TABLE t(rowid,a); INSERT INTO t(rowid,a) VALUES(5,1); SELECT rowid,a FROM t;",
"CREATE TABLE t(a); INSERT INTO t(rowid,a) VALUES(2,1); REPLACE INTO t(rowid,a) VALUES(2,9); SELECT rowid,a FROM t;",
"CREATE TABLE t(a); INSERT INTO t(rowid,a) SELECT 4,8; SELECT rowid,a FROM t;",
"CREATE TABLE t(a PRIMARY KEY,b) WITHOUT ROWID; INSERT INTO t(rowid,a,b) VALUES(1,2,3);",
] {
assert_eq!(run("sqlite3", sql), run(g, sql), "for {sql}");
}
}