#![cfg(feature = "testing")]
use sqlite_diff_rs::testing::assert_patchset_sql_parity;
use sqlite_diff_rs::{PatchSet, SimpleTable};
fn patchset_with(tables: &[SimpleTable]) -> PatchSet<SimpleTable, String, Vec<u8>> {
let mut ps = PatchSet::new();
for t in tables {
ps.add_table(t);
}
ps
}
#[test]
fn test_digest_simple_insert() {
let users = SimpleTable::new("users", &["id", "name"], &[0]);
let mut ps = patchset_with(&[users]);
ps.digest_sql("INSERT INTO users (id, name) VALUES (1, 'Alice');")
.unwrap();
assert_eq!(ps.len(), 1);
assert!(!ps.is_empty());
assert_ne!(ps.build(), [] as [u8; 0]);
}
#[test]
fn test_digest_simple_delete() {
let users = SimpleTable::new("users", &["id", "name"], &[0]);
let mut ps = patchset_with(&[users]);
ps.digest_sql("DELETE FROM users WHERE id = 1;").unwrap();
assert_eq!(ps.len(), 1);
}
#[test]
fn test_digest_simple_update() {
let users = SimpleTable::new("users", &["id", "name"], &[0]);
let mut ps = patchset_with(&[users]);
ps.digest_sql("UPDATE users SET name = 'Bob' WHERE id = 1;")
.unwrap();
assert_eq!(ps.len(), 1);
}
#[test]
fn test_digest_multiple_tables() {
let users = SimpleTable::new("users", &["id", "name"], &[0]);
let posts = SimpleTable::new("posts", &["id", "user_id", "content"], &[0]);
let mut ps = patchset_with(&[users, posts]);
ps.digest_sql("INSERT INTO users (id, name) VALUES (1, 'Alice');")
.unwrap();
ps.digest_sql("INSERT INTO posts (id, user_id, content) VALUES (1, 1, 'Hello World');")
.unwrap();
assert_eq!(ps.len(), 2);
}
#[test]
fn test_digest_mixed_operations() {
let users = SimpleTable::new("users", &["id", "name", "age"], &[0]);
let mut ps = patchset_with(&[users]);
ps.digest_sql(
"INSERT INTO users (id, name, age) VALUES (1, 'Alice', 30);\
INSERT INTO users (id, name, age) VALUES (2, 'Bob', 25);\
UPDATE users SET age = 31 WHERE id = 1;\
DELETE FROM users WHERE id = 2;",
)
.unwrap();
assert_eq!(ps.len(), 1);
}
#[test]
fn test_table_not_registered_error() {
let mut ps: PatchSet<SimpleTable, String, Vec<u8>> = PatchSet::new();
let result = ps.digest_sql("INSERT INTO users (id, name) VALUES (1, 'Alice');");
assert!(result.is_err());
}
#[test]
fn test_invalid_sql_error() {
let users = SimpleTable::new("users", &["id", "name"], &[0]);
let mut ps = patchset_with(&[users]);
let result = ps.digest_sql("THIS IS NOT VALID SQL");
assert!(result.is_err());
}
#[test]
fn test_create_table_rejected() {
let mut ps: PatchSet<SimpleTable, String, Vec<u8>> = PatchSet::new();
let result = ps.digest_sql("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)");
assert!(result.is_err());
}
#[test]
fn test_unknown_column_error() {
let users = SimpleTable::new("users", &["id", "name"], &[0]);
let mut ps = patchset_with(&[users]);
let result = ps.digest_sql("INSERT INTO users (id, nonexistent) VALUES (1, 'Alice');");
assert!(result.is_err());
}
#[test]
fn test_digest_integer_values() {
let numbers = SimpleTable::new("numbers", &["id", "value"], &[0]);
let mut ps = patchset_with(&[numbers]);
ps.digest_sql(
"INSERT INTO numbers (id, value) VALUES (1, 42);\
INSERT INTO numbers (id, value) VALUES (2, -100);\
INSERT INTO numbers (id, value) VALUES (3, 0);",
)
.unwrap();
assert_eq!(ps.len(), 3);
}
#[test]
fn test_digest_real_values() {
let floats = SimpleTable::new("floats", &["id", "value"], &[0]);
let mut ps = patchset_with(&[floats]);
ps.digest_sql(
"INSERT INTO floats (id, value) VALUES (1, 3.14);\
INSERT INTO floats (id, value) VALUES (2, -2.5);",
)
.unwrap();
assert_eq!(ps.len(), 2);
}
#[test]
fn test_digest_text_values() {
let texts = SimpleTable::new("texts", &["id", "value"], &[0]);
let mut ps = patchset_with(&[texts]);
ps.digest_sql(
"INSERT INTO texts (id, value) VALUES (1, 'hello');\
INSERT INTO texts (id, value) VALUES (2, 'world');\
INSERT INTO texts (id, value) VALUES (3, '');",
)
.unwrap();
assert_eq!(ps.len(), 3);
}
#[test]
fn test_digest_null_values() {
let nullable = SimpleTable::new("nullable", &["id", "value"], &[0]);
let mut ps = patchset_with(&[nullable]);
ps.digest_sql("INSERT INTO nullable (id, value) VALUES (1, NULL);")
.unwrap();
assert_eq!(ps.len(), 1);
}
#[test]
fn test_digest_blob_values() {
let blobs = SimpleTable::new("blobs", &["id", "data"], &[0]);
let mut ps = patchset_with(&[blobs]);
ps.digest_sql("INSERT INTO blobs (id, data) VALUES (1, X'DEADBEEF');")
.unwrap();
assert_eq!(ps.len(), 1);
}
#[test]
fn test_digest_composite_pk_insert() {
let composite = SimpleTable::new("composite", &["a", "b", "value"], &[0, 1]);
let mut ps = patchset_with(&[composite]);
ps.digest_sql("INSERT INTO composite (a, b, value) VALUES (1, 2, 'test');")
.unwrap();
assert_eq!(ps.len(), 1);
}
#[test]
fn test_digest_composite_pk_delete() {
let composite = SimpleTable::new("composite", &["a", "b", "value"], &[0, 1]);
let mut ps = patchset_with(&[composite]);
ps.digest_sql("DELETE FROM composite WHERE a = 1 AND b = 2;")
.unwrap();
assert_eq!(ps.len(), 1);
}
#[test]
fn test_digest_composite_pk_update() {
let composite = SimpleTable::new("composite", &["a", "b", "value"], &[0, 1]);
let mut ps = patchset_with(&[composite]);
ps.digest_sql("UPDATE composite SET value = 'updated' WHERE a = 1 AND b = 2;")
.unwrap();
assert_eq!(ps.len(), 1);
}
#[test]
fn test_insert_then_delete_cancels() {
let t = SimpleTable::new("t", &["id", "v"], &[0]);
let mut ps = patchset_with(&[t]);
ps.digest_sql(
"INSERT INTO t (id, v) VALUES (1, 'a');\
DELETE FROM t WHERE id = 1;",
)
.unwrap();
assert_eq!(ps.len(), 0);
}
#[test]
fn test_insert_then_update_becomes_insert() {
let t = SimpleTable::new("t", &["id", "v"], &[0]);
let mut ps = patchset_with(&[t]);
ps.digest_sql(
"INSERT INTO t (id, v) VALUES (1, 'a');\
UPDATE t SET v = 'b' WHERE id = 1;",
)
.unwrap();
assert_eq!(ps.len(), 1);
}
#[test]
fn test_update_then_update_consolidates() {
let t = SimpleTable::new("t", &["id", "v"], &[0]);
let mut ps = patchset_with(&[t]);
ps.digest_sql(
"UPDATE t SET v = 'a' WHERE id = 1;\
UPDATE t SET v = 'b' WHERE id = 1;",
)
.unwrap();
assert_eq!(ps.len(), 1);
}
#[test]
fn test_empty_sql() {
let users = SimpleTable::new("users", &["id", "name"], &[0]);
let mut ps = patchset_with(&[users]);
ps.digest_sql("").unwrap();
assert!(ps.is_empty());
}
#[test]
fn test_semicolons_only() {
let users = SimpleTable::new("users", &["id", "name"], &[0]);
let mut ps = patchset_with(&[users]);
ps.digest_sql(";;;").unwrap();
assert!(ps.is_empty());
}
#[test]
fn test_multiple_digest_calls() {
let users = SimpleTable::new("users", &["id", "name"], &[0]);
let mut ps = patchset_with(&[users]);
ps.digest_sql("INSERT INTO users (id, name) VALUES (1, 'Alice');")
.unwrap();
ps.digest_sql("INSERT INTO users (id, name) VALUES (2, 'Bob');")
.unwrap();
assert_eq!(ps.len(), 2);
}
#[test]
fn test_delete_rejects_non_pk_in_where() {
let users = SimpleTable::new("users", &["id", "name", "status"], &[0]);
let mut ps = patchset_with(&[users]);
let result = ps.digest_sql("DELETE FROM users WHERE id = 1 AND status = 'active'");
assert!(result.is_err());
}
#[test]
fn test_negative_numbers() {
let t = SimpleTable::new("t", &["a", "b"], &[0]);
let mut ps = patchset_with(&[t]);
ps.digest_sql("INSERT INTO t (a, b) VALUES (-42, -3.14);")
.unwrap();
assert_eq!(ps.len(), 1);
}
#[test]
fn parity_single_insert() {
let users = SimpleTable::new("users", &["id", "name"], &[0]);
assert_patchset_sql_parity(
&[users],
&[
"CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)",
"INSERT INTO users (id, name) VALUES (1, 'Alice')",
],
);
}
#[test]
fn parity_insert_and_update() {
let users = SimpleTable::new("users", &["id", "name"], &[0]);
assert_patchset_sql_parity(
&[users],
&[
"CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)",
"INSERT INTO users (id, name) VALUES (1, 'Alice')",
"UPDATE users SET name = 'Alicia' WHERE id = 1",
],
);
}
#[test]
fn parity_multi_table() {
let users = SimpleTable::new("users", &["id", "name"], &[0]);
let posts = SimpleTable::new("posts", &["id", "title"], &[0]);
assert_patchset_sql_parity(
&[users, posts],
&[
"CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)",
"CREATE TABLE posts (id INTEGER PRIMARY KEY, title TEXT)",
"INSERT INTO users (id, name) VALUES (1, 'Alice')",
"INSERT INTO posts (id, title) VALUES (1, 'Hello')",
],
);
}
#[test]
fn parity_composite_pk() {
let order_items =
SimpleTable::new("order_items", &["order_id", "item_id", "quantity"], &[0, 1]);
assert_patchset_sql_parity(
&[order_items],
&[
"CREATE TABLE order_items (order_id INTEGER, item_id INTEGER, quantity INTEGER, PRIMARY KEY (order_id, item_id))",
"INSERT INTO order_items (order_id, item_id, quantity) VALUES (1, 100, 5)",
"INSERT INTO order_items (order_id, item_id, quantity) VALUES (1, 101, 3)",
],
);
}
#[test]
fn test_explicit_values_shorter_than_column_list() {
let t = SimpleTable::new("t", &["a", "b"], &[0]);
let mut ps = patchset_with(&[t]);
let result = ps.digest_sql("INSERT INTO t (a, b) VALUES (1)");
assert!(
result.is_err(),
"expected error for fewer VALUES than column list entries"
);
}
#[test]
fn test_explicit_values_matching_column_list_accepted() {
let t = SimpleTable::new("t", &["a", "b"], &[0]);
let mut ps = patchset_with(&[t]);
ps.digest_sql("INSERT INTO t (a, b) VALUES (1, 2)").unwrap();
assert_eq!(ps.len(), 1);
}
#[test]
fn test_positional_fewer_values_already_errors() {
let t = SimpleTable::new("t", &["a", "b"], &[0]);
let mut ps = patchset_with(&[t]);
let result = ps.digest_sql("INSERT INTO t VALUES (1)");
assert!(result.is_err());
}
#[test]
fn test_atomicity_failure_leaves_builder_untouched() {
let t = SimpleTable::new("t", &["id", "v"], &[0]);
let mut ps = patchset_with(&[t]);
let result = ps.digest_sql(
"INSERT INTO t (id, v) VALUES (1, 'ok'); \
INSERT INTO unknown_table (id) VALUES (2);",
);
assert!(result.is_err(), "second statement must fail");
assert_eq!(
ps.len(),
0,
"builder must be untouched after a partial failure"
);
}
#[test]
fn test_or_in_where_delete_rejected() {
let t = SimpleTable::new("t", &["id", "v"], &[0]);
let mut ps = patchset_with(&[t]);
let result = ps.digest_sql("DELETE FROM t WHERE id = 1 OR id = 2");
assert!(result.is_err(), "OR in WHERE must be rejected");
}
#[test]
fn test_or_in_where_update_rejected() {
let t = SimpleTable::new("t", &["id", "v"], &[0]);
let mut ps = patchset_with(&[t]);
let result = ps.digest_sql("UPDATE t SET v = 'x' WHERE id = 1 OR id = 2");
assert!(result.is_err(), "OR in WHERE must be rejected");
}
#[test]
fn test_partial_pk_delete_rejected() {
let t = SimpleTable::new("t", &["a", "b", "v"], &[0, 1]);
let mut ps = patchset_with(&[t]);
let result = ps.digest_sql("DELETE FROM t WHERE a = 1");
assert!(
result.is_err(),
"partial primary key in WHERE must be rejected"
);
}
#[test]
fn test_partial_pk_update_rejected() {
let t = SimpleTable::new("t", &["a", "b", "v"], &[0, 1]);
let mut ps = patchset_with(&[t]);
let result = ps.digest_sql("UPDATE t SET v = 'x' WHERE a = 1");
assert!(
result.is_err(),
"partial primary key in WHERE must be rejected"
);
}
#[test]
fn test_complete_composite_pk_where_accepted() {
let t = SimpleTable::new("t", &["a", "b", "v"], &[0, 1]);
let mut ps = patchset_with(&[t]);
ps.digest_sql("DELETE FROM t WHERE a = 1 AND b = 2")
.unwrap();
assert_eq!(ps.len(), 1);
}