#![recursion_limit = "512"]
use fsqlite_core::connection::Connection;
use fsqlite_types::value::SqliteValue;
async fn oracle_compare(
fconn: &Connection,
rconn: &rusqlite::Connection,
queries: &[&str],
) -> Vec<String> {
let mut mismatches = Vec::new();
for query in queries {
let frank_result = fconn.query(query).await;
let csql_result: std::result::Result<Vec<Vec<String>>, String> = (|| {
let mut stmt = rconn.prepare(query).map_err(|e| format!("prepare: {e}"))?;
let col_count = stmt.column_count();
let rows: Vec<Vec<String>> = stmt
.query_map([], |row| {
let mut vals = Vec::new();
for i in 0..col_count {
let v: rusqlite::types::Value = row.get_unwrap(i);
let s = match v {
rusqlite::types::Value::Null => "NULL".to_owned(),
rusqlite::types::Value::Integer(n) => n.to_string(),
rusqlite::types::Value::Real(f) => format!("{f}"),
rusqlite::types::Value::Text(s) => format!("'{s}'"),
rusqlite::types::Value::Blob(b) => format!(
"X'{}'",
b.iter().map(|x| format!("{x:02X}")).collect::<String>()
),
};
vals.push(s);
}
Ok(vals)
})
.map_err(|e| format!("query: {e}"))?
.collect::<std::result::Result<Vec<_>, _>>()
.map_err(|e| format!("row: {e}"))?;
Ok(rows)
})();
match (frank_result, csql_result) {
(Ok(rows), Ok(csql_rows)) => {
let frank_strs: Vec<Vec<String>> = rows
.iter()
.map(|row| {
row.values()
.iter()
.map(|v| match v {
SqliteValue::Null => "NULL".to_owned(),
SqliteValue::Integer(n) => n.to_string(),
SqliteValue::Float(f) => format!("{f}"),
SqliteValue::Text(s) => format!("'{s}'"),
SqliteValue::Blob(b) => format!(
"X'{}'",
b.iter().map(|x| format!("{x:02X}")).collect::<String>()
),
})
.collect()
})
.collect();
if frank_strs != csql_rows {
mismatches.push(format!(
"MISMATCH: {query}\n frank: {frank_strs:?}\n csql: {csql_rows:?}"
));
}
}
(Ok(rows), Err(csql_err)) => mismatches.push(format!(
"DIVERGE: {query}\n frank: OK ({} rows)\n csql: ERROR({csql_err})",
rows.len()
)),
(Err(e), Ok(csql_rows)) => mismatches.push(format!(
"PAIR_FRANK_ERROR: {query}\n frank: ERROR({e})\n csql: {csql_rows:?}"
)),
(Err(_), Err(_)) => {}
}
}
mismatches
}
fn assert_no_mismatches(mismatches: &[String], label: &str) {
if !mismatches.is_empty() {
for m in mismatches {
eprintln!("{m}\n");
}
panic!("{} {label} mismatch(es)", mismatches.len());
}
}
async fn apply_checked(
fconn: &Connection,
rconn: &rusqlite::Connection,
stmts: &[&str],
) -> Vec<String> {
let mut diverged = Vec::new();
for s in stmts {
let f = fconn.execute(s).await;
let r = rconn.execute_batch(s);
match (f, r) {
(Ok(_), Ok(())) | (Err(_), Err(_)) => {}
(Ok(_), Err(e)) => {
diverged.push(format!("STMT_DIVERGE: {s}\n frank: OK\n csql: ERROR({e})"));
}
(Err(e), Ok(())) => {
diverged.push(format!("STMT_DIVERGE: {s}\n frank: ERROR({e})\n csql: OK"));
}
}
}
diverged
}
async fn apply(fconn: &Connection, rconn: &rusqlite::Connection, stmts: &[&str]) {
for s in stmts {
fconn.execute(s).await.unwrap();
rconn.execute_batch(s).unwrap();
}
}
#[test]
fn check_sees_affinity_coerced_value_on_insert() {
asupersync::test_utils::run_test(|| async {
let fconn = Connection::open(":memory:").await.unwrap();
let rconn = rusqlite::Connection::open_in_memory().unwrap();
apply(
&fconn,
&rconn,
&[
"CREATE TABLE t (a TEXT CHECK(typeof(a) = 'text'))",
"INSERT INTO t VALUES (1)",
"INSERT INTO t VALUES (2.5)",
],
)
.await;
let m = oracle_compare(
&fconn,
&rconn,
&["SELECT typeof(a), a FROM t ORDER BY a"],
)
.await;
assert_no_mismatches(&m, "check_sees_affinity_coerced_value_on_insert");
});
}
#[test]
fn check_rejects_after_affinity_coercion_on_insert() {
asupersync::test_utils::run_test(|| async {
let fconn = Connection::open(":memory:").await.unwrap();
let rconn = rusqlite::Connection::open_in_memory().unwrap();
apply(&fconn, &rconn, &["CREATE TABLE u (b INTEGER CHECK(typeof(b) = 'text'))"]).await;
let diverged = apply_checked(
&fconn,
&rconn,
&["INSERT INTO u VALUES ('5')", "INSERT INTO u VALUES ('99')"],
)
.await;
assert_no_mismatches(&diverged, "check_rejects_after_affinity_coercion_on_insert");
let m = oracle_compare(&fconn, &rconn, &["SELECT count(*) FROM u"]).await;
assert_no_mismatches(&m, "check_rejects_after_affinity_coercion_on_insert_empty");
});
}
#[test]
fn check_sees_affinity_coerced_value_on_update() {
asupersync::test_utils::run_test(|| async {
let fconn = Connection::open(":memory:").await.unwrap();
let rconn = rusqlite::Connection::open_in_memory().unwrap();
apply(
&fconn,
&rconn,
&[
"CREATE TABLE t (id INTEGER PRIMARY KEY, a TEXT CHECK(typeof(a) = 'text'))",
"INSERT INTO t VALUES (1, 'x')",
"UPDATE t SET a = 7 WHERE id = 1",
],
)
.await;
let m = oracle_compare(
&fconn,
&rconn,
&["SELECT id, typeof(a), a FROM t ORDER BY id"],
)
.await;
assert_no_mismatches(&m, "check_sees_affinity_coerced_value_on_update");
});
}