#![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 rows_str(c: &Connection, sql: &str) -> String {
c.query(sql)
.unwrap()
.rows
.iter()
.map(|row| row.iter().map(render).collect::<Vec<_>>().join("|"))
.collect::<Vec<_>>()
.join("\n")
}
#[test]
fn basic() {
let c = Connection::open_memory().unwrap();
assert_eq!(
rows_str(&c, "SELECT value FROM generate_series(1, 5)"),
"1\n2\n3\n4\n5"
);
assert_eq!(
rows_str(&c, "SELECT value FROM generate_series(0, 10, 3)"),
"0\n3\n6\n9"
);
assert_eq!(
rows_str(&c, "SELECT value FROM generate_series(5, 1, -2)"),
"5\n3\n1"
);
assert_eq!(
rows_str(&c, "SELECT sum(value) FROM generate_series(1, 100)"),
"5050"
);
assert_eq!(
rows_str(
&c,
"SELECT count(*) FROM generate_series(1, 20) WHERE value % 2 = 0"
),
"10"
);
}
#[test]
fn join_with_series() {
let mut c = Connection::open_memory().unwrap();
c.execute("CREATE TABLE t(id INTEGER PRIMARY KEY, n)")
.unwrap();
c.execute("INSERT INTO t(n) VALUES (10),(20)").unwrap();
let got = rows_str(
&c,
"SELECT t.id, s.value FROM t, generate_series(1, 2) AS s ORDER BY t.id, s.value",
);
assert_eq!(got, "1|1\n1|2\n2|1\n2|2");
}
#[test]
fn json_each_and_tree() {
if !sqlite3_available() {
eprintln!("sqlite3 not found; skipping");
return;
}
let c = Connection::open_memory().unwrap();
let queries = [
r#"SELECT key,value,type,atom,fullkey,path FROM json_each('{"a":1,"b":[7,8],"c":"x"}')"#,
r#"SELECT key,value,type,fullkey,path FROM json_each('[10,20,30]')"#,
r#"SELECT key,value,type,fullkey,path FROM json_tree('{"a":1,"b":[7,8]}')"#,
r#"SELECT value FROM json_each('[1,2,3,4]') WHERE value > 2"#,
r#"SELECT count(*), sum(value) FROM json_each('[5,10,15]')"#,
];
let mut failures = Vec::new();
for q in queries {
let want = {
let o = Command::new("sqlite3")
.arg(":memory:")
.arg(q)
.output()
.unwrap();
String::from_utf8_lossy(&o.stdout).trim_end().to_string()
};
let got = rows_str(&c, q);
if got != want {
failures.push(format!(
" {q}\n sqlite: {want:?}\n graphite: {got:?}"
));
}
}
assert!(
failures.is_empty(),
"{} json_each/json_tree queries diverged:\n{}",
failures.len(),
failures.join("\n")
);
}
#[test]
fn against_sqlite3() {
if !sqlite3_available() {
eprintln!("sqlite3 not found; skipping");
return;
}
let queries = [
"SELECT value FROM generate_series(1, 8)",
"SELECT value FROM generate_series(2, 20, 5)",
"SELECT value FROM generate_series(10, 0, -3)",
"SELECT sum(value), count(*), min(value), max(value) FROM generate_series(1, 50)",
"SELECT value*value FROM generate_series(1, 5)",
];
let c = Connection::open_memory().unwrap();
let mut failures = Vec::new();
for q in queries {
let want = {
let o = Command::new("sqlite3")
.arg(":memory:")
.arg(q)
.output()
.unwrap();
String::from_utf8_lossy(&o.stdout).trim_end().to_string()
};
let got = rows_str(&c, q);
if got != want {
failures.push(format!(
" {q}\n sqlite: {want:?}\n graphite: {got:?}"
));
}
}
assert!(
failures.is_empty(),
"{} generate_series queries diverged:\n{}",
failures.len(),
failures.join("\n")
);
}
fn pairs(c: &Connection, sql: &str) -> Vec<(String, String)> {
c.query(sql)
.unwrap()
.rows
.iter()
.map(|r| (render(&r[0]), render(&r[1])))
.collect()
}
#[test]
fn json_each_navigates_a_path_argument() {
let c = Connection::open_memory().unwrap();
assert_eq!(
pairs(
&c,
"SELECT key, value FROM json_each('{\"a\":[10,20]}', '$.a')"
),
[("0".into(), "10".into()), ("1".into(), "20".into())]
);
assert_eq!(
pairs(
&c,
"SELECT key, value FROM json_each('{\"a\":{\"b\":5,\"c\":6}}', '$.a')"
),
[("b".into(), "5".into()), ("c".into(), "6".into())]
);
assert_eq!(
pairs(&c, "SELECT key, value FROM json_each('{\"a\":7}', '$.a')"),
[(String::new(), "7".into())]
);
assert_eq!(
pairs(
&c,
"SELECT fullkey, path FROM json_each('{\"a\":[10,20]}', '$.a')"
),
[
("$.a[0]".into(), "$.a".into()),
("$.a[1]".into(), "$.a".into())
]
);
assert!(c
.query("SELECT * FROM json_each('{\"a\":1}', '$.zzz')")
.unwrap()
.rows
.is_empty());
}
#[test]
fn json_tree_roots_at_a_path_argument() {
let c = Connection::open_memory().unwrap();
let r = c
.query("SELECT key, value, type, fullkey, path FROM json_tree('{\"x\":[1,2]}', '$.x')")
.unwrap();
assert_eq!(render(&r.rows[0][0]), "x");
assert_eq!(render(&r.rows[0][2]), "array");
assert_eq!(render(&r.rows[0][3]), "$.x");
assert_eq!(render(&r.rows[0][4]), "$");
assert_eq!(render(&r.rows[1][3]), "$.x[0]");
assert_eq!(render(&r.rows[1][1]), "1");
assert_eq!(render(&r.rows[2][3]), "$.x[1]");
}
#[test]
fn explain_query_plan_over_a_virtual_table() {
let mut c = Connection::open_memory().unwrap();
c.execute("CREATE VIRTUAL TABLE s USING series(1, 5)")
.unwrap();
let detail = |sql: &str| -> String {
match c.query(sql).unwrap().rows.last().unwrap().last().unwrap() {
Value::Text(s) => s.clone(),
o => panic!("not text: {o:?}"),
}
};
assert_eq!(
detail("EXPLAIN QUERY PLAN SELECT * FROM s"),
"SCAN s VIRTUAL TABLE INDEX 0:"
);
assert!(detail("EXPLAIN QUERY PLAN SELECT * FROM s WHERE value > 2")
.starts_with("SCAN s VIRTUAL TABLE INDEX "));
}
#[test]
fn pragma_table_info_over_a_virtual_table() {
let mut c = Connection::open_memory().unwrap();
c.execute("CREATE VIRTUAL TABLE s USING series(1, 5)")
.unwrap();
let r = c.query("PRAGMA table_info(s)").unwrap();
assert_eq!(r.rows.len(), 1);
assert_eq!(r.rows[0][0], Value::Integer(0)); assert_eq!(r.rows[0][1], Value::Text("value".into())); }