#![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")
}
fn diff_against_sqlite3(setup: &[&str], queries: &[&str]) {
if !sqlite3_available() {
eprintln!("sqlite3 not found; skipping");
return;
}
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-geopoly-{}-{n}.db", std::process::id()));
let path = path.to_string_lossy().into_owned();
let _ = std::fs::remove_file(&path);
let mut g = Connection::open_memory().unwrap();
for s in setup {
let out = Command::new("sqlite3").arg(&path).arg(s).output().unwrap();
assert!(
out.status.success(),
"{}",
String::from_utf8_lossy(&out.stderr)
);
g.execute(s).unwrap();
}
let mut failures = Vec::new();
for &q in queries {
let want = {
let o = Command::new("sqlite3").arg(&path).arg(q).output().unwrap();
String::from_utf8_lossy(&o.stdout).trim_end().to_string()
};
let got = rows_str(&g, q);
if got != want {
failures.push(format!(
" {q}\n sqlite: {want:?}\n graphite: {got:?}"
));
}
}
let _ = std::fs::remove_file(&path);
assert!(
failures.is_empty(),
"{} geopoly queries diverged:\n{}",
failures.len(),
failures.join("\n")
);
}
#[test]
fn json_blob_area_roundtrip() {
diff_against_sqlite3(
&[],
&[
"SELECT geopoly_json('[[0,0],[4,0],[4,3],[0,3],[0,0]]')",
"SELECT geopoly_json('[[0.5,0.25],[1,0],[1,1],[0.5,0.25]]')",
"SELECT geopoly_json('[[-1.5,-2.5],[3,0],[0,4],[-1.5,-2.5]]')",
"SELECT hex(geopoly_blob('[[0,0],[1,0],[1,1],[0,0]]'))",
"SELECT hex(geopoly_blob('[[0,0],[4,0],[4,3],[0,3],[0,0]]'))",
"SELECT geopoly_json(geopoly_blob('[[0,0],[4,0],[4,3],[0,3],[0,0]]'))",
"SELECT geopoly_area(geopoly_blob('[[0,0],[4,0],[4,3],[0,0]]'))",
"SELECT geopoly_area('[[0,0],[4,0],[4,3],[0,0]]')",
"SELECT geopoly_area('[[0,0],[4,3],[4,0],[0,0]]')",
"SELECT geopoly_area('[[0.0,0.0],[4.5,0.0],[4.5,3.5],[0.0,3.5],[0.0,0.0]]')",
"SELECT typeof(geopoly_area('[[0,0],[4,0],[4,3],[0,0]]'))",
],
);
}
#[test]
fn invalid_input_is_null() {
diff_against_sqlite3(
&[],
&[
"SELECT geopoly_json('not json')",
"SELECT geopoly_area(NULL)",
"SELECT geopoly_area('[[0,0],[1,1]]')", "SELECT geopoly_json('[[0,0],[2,0],[2,2],[0,2]]')", "SELECT geopoly_json('[[0,0],[1,0],[1,1]]')", "SELECT geopoly_json('[[00,0],[1,0],[1,1],[0,0]]')", "SELECT geopoly_json('[[0,0],[1,0],[1,1],[0,0]] extra')", "SELECT geopoly_json(x'00')", "SELECT geopoly_area(12345)", "SELECT geopoly_json(' [ [0,0] , [1,0] , [1,1] , [0,0] ] ')",
"SELECT geopoly_json('[[0,0],[1,0],[1,1],[0,0],]')",
"SELECT geopoly_json('[[0,0,9],[1,0,9],[1,1,9],[0,0,9]]')",
"SELECT geopoly_area('[[.5,0],[1,0],[1,1],[.5,0]]')",
"SELECT geopoly_json('[[0,0],[1,0],[1,1],[0,1.5e2]]')",
],
);
}
#[test]
fn bbox_ccw() {
diff_against_sqlite3(
&[],
&[
"SELECT geopoly_json(geopoly_bbox('[[0,0],[4,0],[4,3],[0,3],[0,0]]'))",
"SELECT hex(geopoly_bbox('[[1,1],[5,2],[3,7],[-1,4],[1,1]]'))",
"SELECT geopoly_json(geopoly_ccw('[[0,0],[4,0],[4,3],[0,0]]'))",
"SELECT geopoly_json(geopoly_ccw('[[0,0],[4,3],[4,0],[0,0]]'))",
"SELECT hex(geopoly_ccw('[[0,0],[4,3],[4,0],[0,3],[0,0]]'))",
],
);
}
#[test]
fn regular() {
diff_against_sqlite3(
&[],
&[
"SELECT geopoly_json(geopoly_regular(0,0,10,3))",
"SELECT geopoly_json(geopoly_regular(0,0,10,4))",
"SELECT geopoly_json(geopoly_regular(0,0,10,6))",
"SELECT geopoly_json(geopoly_regular(5,5,2,4))",
"SELECT geopoly_json(geopoly_regular(-3.5,2.25,7,8))",
"SELECT hex(geopoly_regular(1.5,-2.5,3.7,13))",
"SELECT hex(geopoly_regular(0,0,1e6,60))",
"SELECT geopoly_regular(0,0,10,2)", "SELECT geopoly_regular(0,0,0,4)", "SELECT geopoly_regular(0,0,-1,4)", "SELECT geopoly_regular(0,0,10,1001) IS NULL", "SELECT length(geopoly_blob(geopoly_regular(0,0,10,1001)))",
"SELECT length(geopoly_blob(geopoly_regular(0,0,10,1000)))",
],
);
}
#[test]
fn contains_point() {
diff_against_sqlite3(
&[],
&[
"SELECT geopoly_contains_point('[[0,0],[4,0],[4,4],[0,4],[0,0]]', 2, 2)",
"SELECT geopoly_contains_point('[[0,0],[4,0],[4,4],[0,4],[0,0]]', 0, 0)",
"SELECT geopoly_contains_point('[[0,0],[4,0],[4,4],[0,4],[0,0]]', 2, 0)",
"SELECT geopoly_contains_point('[[0,0],[4,0],[4,4],[0,4],[0,0]]', 4, 2)",
"SELECT geopoly_contains_point('[[0,0],[4,0],[4,4],[0,4],[0,0]]', 0, 2)",
"SELECT geopoly_contains_point('[[0,0],[4,0],[4,4],[0,4],[0,0]]', 5, 5)",
"SELECT geopoly_contains_point('[[0,0],[4,0],[4,4],[0,4],[0,0]]', -1, 2)",
"SELECT geopoly_contains_point('[[0,0],[4,0],[4,4],[0,4],[0,0]]', '2', '2')",
"SELECT geopoly_contains_point('bad', 1, 1)",
],
);
}
#[test]
fn overlap_within() {
let a = "[[0,0],[3,0],[3,3],[0,3],[0,0]]";
let inner = "[[1,1],[2,1],[2,2],[1,2],[1,1]]";
let touch = "[[3,0],[6,0],[6,3],[3,3],[3,0]]";
let cross = "[[1,1],[4,1],[4,4],[1,4],[1,1]]";
let apart = "[[10,10],[11,10],[11,11],[10,11],[10,10]]";
let queries = [
format!("SELECT geopoly_overlap('{a}','{apart}')"), format!("SELECT geopoly_overlap('{a}','{touch}')"), format!("SELECT geopoly_overlap('{a}','{cross}')"), format!("SELECT geopoly_overlap('{inner}','{a}')"), format!("SELECT geopoly_overlap('{a}','{inner}')"), format!("SELECT geopoly_overlap('{a}','{a}')"), format!("SELECT geopoly_within('{a}','{a}')"),
format!("SELECT geopoly_within('{a}','{inner}')"),
format!("SELECT geopoly_within('{inner}','{a}')"),
format!("SELECT geopoly_within('{a}','{apart}')"),
format!("SELECT geopoly_overlap(NULL,'{a}')"),
format!("SELECT geopoly_within('nope','{a}')"),
];
let refs: Vec<&str> = queries.iter().map(String::as_str).collect();
diff_against_sqlite3(&[], &refs);
}
#[test]
fn svg_xform() {
diff_against_sqlite3(
&[],
&[
"SELECT geopoly_svg('[[0,0],[1,0],[1,1],[0,0]]','fill=\"red\"')",
"SELECT geopoly_svg('[[0,0],[1,0],[1,1],[0,0]]','fill=\"red\"','stroke=\"blue\"')",
"SELECT geopoly_svg('[[0.5,0.25],[1,0],[1,1],[0.5,0.25]]')",
"SELECT geopoly_svg('[[0,0],[1,0],[1,1],[0,0]]', NULL, 'a=1')",
"SELECT geopoly_json(geopoly_xform('[[0,0],[1,0],[1,1],[0,0]]',1,0,0,1,5,10))",
"SELECT geopoly_json(geopoly_xform('[[0,0],[1,0],[1,1],[0,0]]',2,0,0,2,0,0))",
"SELECT geopoly_json(geopoly_xform('[[0,0],[1,0],[1,1],[0,0]]',0.1,0,0,0.1,0,0))",
"SELECT hex(geopoly_xform('[[0,0],[4,0],[4,3],[0,3],[0,0]]',0,-1,1,0,0,0))",
"SELECT geopoly_xform(NULL,1,0,0,1,0,0)",
],
);
}
#[test]
fn group_bbox_aggregate() {
diff_against_sqlite3(
&["CREATE TABLE t(g)", "INSERT INTO t VALUES('[[0,0],[1,0],[1,1],[0,1],[0,0]]'),('[[5,5],[6,5],[6,6],[5,6],[5,5]]')"],
&[
"SELECT geopoly_json(geopoly_group_bbox(g)) FROM t",
"SELECT geopoly_json(geopoly_group_bbox(geopoly_blob(g))) FROM t",
"SELECT geopoly_json(geopoly_group_bbox(g)) FROM t GROUP BY (g LIKE '[[5%')",
],
);
diff_against_sqlite3(
&[
"CREATE TABLE u(g)",
"INSERT INTO u VALUES(NULL),('garbage')",
],
&[
"SELECT hex(geopoly_group_bbox(g)) FROM u",
"SELECT geopoly_group_bbox(g) IS NULL FROM u WHERE 0", ],
);
}
#[test]
fn connection_smoke() {
let c = Connection::open_memory().unwrap();
assert_eq!(
rows_str(&c, "SELECT geopoly_area('[[0,0],[4,0],[4,3],[0,0]]')"),
"6.0"
);
assert_eq!(
rows_str(&c, "SELECT geopoly_area('[[0,0],[4,3],[4,0],[0,0]]')"),
"-6.0"
);
assert_eq!(
rows_str(&c, "SELECT hex(geopoly_blob('[[0,0],[1,0],[1,1],[0,0]]'))"),
"0100000300000000000000000000803F000000000000803F0000803F",
);
assert_eq!(
rows_str(
&c,
"SELECT geopoly_contains_point('[[0,0],[4,0],[4,4],[0,4],[0,0]]',2,2)"
),
"2"
);
assert_eq!(
rows_str(
&c,
"SELECT geopoly_contains_point('[[0,0],[4,0],[4,4],[0,4],[0,0]]',2,0)"
),
"1"
);
assert_eq!(
rows_str(
&c,
"SELECT geopoly_contains_point('[[0,0],[4,0],[4,4],[0,4],[0,0]]',9,9)"
),
"0"
);
let a = "[[0,0],[3,0],[3,3],[0,3],[0,0]]";
assert_eq!(
rows_str(&c, &format!("SELECT geopoly_overlap('{a}','{a}')")),
"4"
);
assert_eq!(
rows_str(&c, &format!("SELECT geopoly_within('{a}','{a}')")),
"2"
);
assert_eq!(rows_str(&c, "SELECT geopoly_area('bad')"), "");
}