#![cfg(feature = "std")]
use graphitesql::{Connection, Value};
use std::process::Command;
fn sqlite3_available() -> bool {
Command::new("sqlite3").arg("--version").output().is_ok()
}
fn one(c: &Connection, sql: &str) -> i64 {
match c.query(sql).unwrap().rows[0][0] {
Value::Integer(i) => i,
_ => -999,
}
}
#[test]
fn escape_semantics() {
let c = Connection::open_memory().unwrap();
assert_eq!(one(&c, r#"SELECT 'a_b' LIKE 'a\_b' ESCAPE '\'"#), 1);
assert_eq!(one(&c, r#"SELECT 'axb' LIKE 'a\_b' ESCAPE '\'"#), 0);
assert_eq!(one(&c, r#"SELECT '100%' LIKE '100\%' ESCAPE '\'"#), 1);
assert_eq!(one(&c, r#"SELECT '100x' LIKE '100\%' ESCAPE '\'"#), 0);
assert_eq!(one(&c, r#"SELECT 'a_b' NOT LIKE 'a\_b' ESCAPE '\'"#), 0);
assert_eq!(one(&c, "SELECT 'abc' LIKE 'a%'"), 1);
assert_eq!(one(&c, "SELECT likely(5)"), 5);
assert_eq!(one(&c, "SELECT unlikely(7)"), 7);
assert_eq!(one(&c, "SELECT likelihood(9, 0.5)"), 9);
assert_eq!(one(&c, "SELECT like('a%', 'abc')"), 1);
assert_eq!(one(&c, r#"SELECT like('a\%c', 'a%c', '\')"#), 1);
}
#[test]
fn escape_char_is_never_a_wildcard() {
let c = Connection::open_memory().unwrap();
assert_eq!(one(&c, "SELECT 'ab' LIKE 'a_' ESCAPE '_'"), 0);
assert_eq!(one(&c, "SELECT 'a' LIKE 'a_' ESCAPE '_'"), 1);
assert_eq!(one(&c, "SELECT 'A' LIKE 'a_' ESCAPE '_'"), 1);
assert_eq!(one(&c, "SELECT '' LIKE '_' ESCAPE '_'"), 1);
assert_eq!(one(&c, "SELECT '_' LIKE '_' ESCAPE '_'"), 0);
assert_eq!(one(&c, "SELECT 'ab' LIKE 'a%' ESCAPE '%'"), 0);
assert_eq!(one(&c, "SELECT 'a' LIKE 'a%' ESCAPE '%'"), 1);
assert_eq!(one(&c, "SELECT '%' LIKE 'a%' ESCAPE '%'"), 0);
}
#[test]
fn against_sqlite3() {
if !sqlite3_available() {
eprintln!("sqlite3 not found; skipping");
return;
}
let queries = [
r#"SELECT 'a_b' LIKE 'a\_b' ESCAPE '\'"#,
r#"SELECT 'a%b' LIKE 'a\%b' ESCAPE '\'"#,
r#"SELECT 'axb' LIKE 'a_b'"#,
r#"SELECT count(*) FROM (SELECT 'a_b' AS s UNION ALL SELECT 'axb') WHERE s LIKE 'a\_b' ESCAPE '\'"#,
r#"SELECT likely(42), unlikely(1), likelihood(3, 0.9)"#,
r#"SELECT like('%abc%', 'xxabcyy')"#,
r#"SELECT 'ab' LIKE 'a_' ESCAPE '_'"#,
r#"SELECT '_' LIKE '_' ESCAPE '_'"#,
r#"SELECT 'ab' LIKE 'a%' ESCAPE '%'"#,
];
let c = Connection::open_memory().unwrap();
let render = |v: &Value| 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(),
};
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 = c.query(q).unwrap().rows[0]
.iter()
.map(render)
.collect::<Vec<_>>()
.join("|");
if got != want {
failures.push(format!(
" {q}\n sqlite: {want:?}\n graphite: {got:?}"
));
}
}
assert!(
failures.is_empty(),
"{} LIKE/ESCAPE queries diverged:\n{}",
failures.len(),
failures.join("\n")
);
}
#[test]
fn like_is_ascii_case_insensitive_only() {
let c = Connection::open_memory().unwrap();
let f = |sql: &str| match c.query(sql).unwrap().rows[0][0] {
Value::Integer(i) => i,
ref v => panic!("expected int from {sql}, got {v:?}"),
};
assert_eq!(f("SELECT 'ABC' LIKE 'abc'"), 1);
assert_eq!(f("SELECT 'abc' LIKE 'ABC'"), 1);
assert_eq!(f("SELECT 'File.TXT' LIKE 'file.txt'"), 1);
assert_eq!(f("SELECT 'É' LIKE 'é'"), 0);
assert_eq!(f("SELECT 'café' LIKE 'CAFÉ'"), 0);
assert_eq!(f("SELECT 'Ω' LIKE 'ω'"), 0);
assert_eq!(f("SELECT 'Ā' LIKE 'ā'"), 0);
assert_eq!(f("SELECT 'café' LIKE 'café'"), 1);
assert_eq!(f("SELECT 'café' LIKE 'caf_'"), 1);
assert_eq!(f("SELECT 'résumé' LIKE 'r%é'"), 1);
}