use vibesql_ast::Statement;
use vibesql_parser::Parser;
use vibesql_types::SqlValue;
use crate::*;
fn ddl(db: &mut vibesql_storage::Database, sql: &str) {
let stmt = Parser::parse_sql(sql).unwrap_or_else(|e| panic!("parse {:?}: {:?}", sql, e));
match stmt {
Statement::CreateTable(s) => {
CreateTableExecutor::execute(&s, db).expect("CREATE TABLE");
}
Statement::CreateIndex(s) => {
CreateIndexExecutor::execute(&s, db).expect("CREATE INDEX");
}
Statement::Insert(s) => {
InsertExecutor::execute(db, &s).expect("INSERT");
}
Statement::CreateTrigger(s) => {
crate::advanced_objects::execute_create_trigger(&s, db).expect("CREATE TRIGGER");
}
other => panic!("unsupported ddl: {:?}", other),
}
}
fn update(db: &mut vibesql_storage::Database, sql: &str) -> Result<usize, crate::ExecutorError> {
match Parser::parse_sql(sql).expect("parse update") {
Statement::Update(s) => UpdateExecutor::execute(&s, db),
other => panic!("expected UPDATE, got {:?}", other),
}
}
fn delete(db: &mut vibesql_storage::Database, sql: &str) {
match Parser::parse_sql(sql).expect("parse delete") {
Statement::Delete(s) => {
DeleteExecutor::execute(&s, db).expect("DELETE");
}
other => panic!("expected DELETE, got {:?}", other),
}
}
fn select(db: &mut vibesql_storage::Database, sql: &str) -> Vec<vibesql_storage::Row> {
match Parser::parse_sql(sql).expect("parse select") {
Statement::Select(s) => SelectExecutor::new(db).execute(&s).expect("SELECT"),
other => panic!("expected SELECT, got {:?}", other),
}
}
fn int(v: &SqlValue) -> i64 {
match v {
SqlValue::Integer(i) => *i,
SqlValue::Bigint(i) => *i,
other => panic!("expected integer, got {:?}", other),
}
}
fn text(v: &SqlValue) -> String {
match v {
SqlValue::Varchar(s) => s.to_string(),
other => panic!("expected text, got {:?}", other),
}
}
#[test]
fn set_rowid_relocates_row() {
let mut db = vibesql_storage::Database::new();
ddl(&mut db, "CREATE TABLE t(x)");
ddl(&mut db, "INSERT INTO t VALUES(1)");
ddl(&mut db, "INSERT INTO t VALUES(2)");
ddl(&mut db, "INSERT INTO t VALUES(3)");
assert_eq!(update(&mut db, "UPDATE t SET rowid=99 WHERE rowid=1").unwrap(), 1);
let rows = select(&mut db, "SELECT rowid, x FROM t ORDER BY rowid");
let got: Vec<(i64, i64)> =
rows.iter().map(|r| (int(&r.values[0]), int(&r.values[1]))).collect();
assert_eq!(got, vec![(2, 2), (3, 3), (99, 1)]);
}
#[test]
fn set_underscore_rowid_alias() {
let mut db = vibesql_storage::Database::new();
ddl(&mut db, "CREATE TABLE t(x)");
ddl(&mut db, "INSERT INTO t VALUES(1)");
assert_eq!(update(&mut db, "UPDATE t SET _rowid_=50 WHERE rowid=1").unwrap(), 1);
let rows = select(&mut db, "SELECT rowid, x FROM t");
assert_eq!(int(&rows[0].values[0]), 50);
assert_eq!(int(&rows[0].values[1]), 1);
}
#[test]
fn set_rowid_conflict_errors() {
let mut db = vibesql_storage::Database::new();
ddl(&mut db, "CREATE TABLE t(x)");
ddl(&mut db, "INSERT INTO t VALUES(1)");
ddl(&mut db, "INSERT INTO t VALUES(2)");
ddl(&mut db, "INSERT INTO t VALUES(3)");
let err = update(&mut db, "UPDATE t SET rowid=2 WHERE rowid=1").unwrap_err();
assert!(
err.to_string().contains("UNIQUE constraint failed: t.rowid"),
"expected rowid UNIQUE error, got: {}",
err
);
let rows = select(&mut db, "SELECT rowid, x FROM t ORDER BY rowid");
let got: Vec<(i64, i64)> =
rows.iter().map(|r| (int(&r.values[0]), int(&r.values[1]))).collect();
assert_eq!(got, vec![(1, 1), (2, 2), (3, 3)]);
}
#[test]
fn set_rowid_swap_errors_intermediate_collision() {
let mut db = vibesql_storage::Database::new();
ddl(&mut db, "CREATE TABLE t(a)");
ddl(&mut db, "INSERT INTO t VALUES(10)"); ddl(&mut db, "INSERT INTO t VALUES(20)");
let err = update(
&mut db,
"UPDATE t SET rowid = CASE rowid WHEN 1 THEN 2 WHEN 2 THEN 1 END",
)
.unwrap_err();
assert!(
err.to_string().contains("UNIQUE constraint failed: t.rowid"),
"expected rowid UNIQUE error for swap, got: {}",
err
);
let rows = select(&mut db, "SELECT rowid, a FROM t ORDER BY rowid");
let got: Vec<(i64, i64)> =
rows.iter().map(|r| (int(&r.values[0]), int(&r.values[1]))).collect();
assert_eq!(got, vec![(1, 10), (2, 20)]);
}
#[test]
fn set_rowid_three_cycle_errors() {
let mut db = vibesql_storage::Database::new();
ddl(&mut db, "CREATE TABLE t(a)");
ddl(&mut db, "INSERT INTO t VALUES(10)");
ddl(&mut db, "INSERT INTO t VALUES(20)");
ddl(&mut db, "INSERT INTO t VALUES(30)");
let err = update(
&mut db,
"UPDATE t SET rowid = CASE rowid WHEN 1 THEN 2 WHEN 2 THEN 3 WHEN 3 THEN 1 END",
)
.unwrap_err();
assert!(
err.to_string().contains("UNIQUE constraint failed: t.rowid"),
"expected rowid UNIQUE error for 3-cycle, got: {}",
err
);
}
#[test]
fn set_rowid_plus_one_cascade_errors() {
let mut db = vibesql_storage::Database::new();
ddl(&mut db, "CREATE TABLE t(a)");
ddl(&mut db, "INSERT INTO t VALUES(10)");
ddl(&mut db, "INSERT INTO t VALUES(20)");
ddl(&mut db, "INSERT INTO t VALUES(30)");
let err = update(&mut db, "UPDATE t SET rowid = rowid + 1").unwrap_err();
assert!(
err.to_string().contains("UNIQUE constraint failed: t.rowid"),
"expected rowid UNIQUE error for +1 cascade, got: {}",
err
);
}
#[test]
fn set_rowid_minus_one_cascade_ok() {
let mut db = vibesql_storage::Database::new();
ddl(&mut db, "CREATE TABLE t(a)");
ddl(&mut db, "INSERT INTO t VALUES(10)");
ddl(&mut db, "INSERT INTO t VALUES(20)");
ddl(&mut db, "INSERT INTO t VALUES(30)");
assert_eq!(update(&mut db, "UPDATE t SET rowid = rowid - 1").unwrap(), 3);
let rows = select(&mut db, "SELECT rowid, a FROM t ORDER BY rowid");
let got: Vec<(i64, i64)> =
rows.iter().map(|r| (int(&r.values[0]), int(&r.values[1]))).collect();
assert_eq!(got, vec![(0, 10), (1, 20), (2, 30)]);
}
#[test]
fn set_rowid_move_to_gap_ok() {
let mut db = vibesql_storage::Database::new();
ddl(&mut db, "CREATE TABLE t(a)");
ddl(&mut db, "INSERT INTO t VALUES(10)");
ddl(&mut db, "INSERT INTO t VALUES(20)");
assert_eq!(update(&mut db, "UPDATE t SET rowid = rowid + 100 WHERE rowid = 1").unwrap(), 1);
let rows = select(&mut db, "SELECT rowid, a FROM t ORDER BY rowid");
let got: Vec<(i64, i64)> =
rows.iter().map(|r| (int(&r.values[0]), int(&r.values[1]))).collect();
assert_eq!(got, vec![(2, 20), (101, 10)]);
}
#[test]
fn set_rowid_self_noop_ok() {
let mut db = vibesql_storage::Database::new();
ddl(&mut db, "CREATE TABLE t(a)");
ddl(&mut db, "INSERT INTO t VALUES(10)");
ddl(&mut db, "INSERT INTO t VALUES(20)");
ddl(&mut db, "INSERT INTO t VALUES(30)");
assert_eq!(update(&mut db, "UPDATE t SET rowid = rowid").unwrap(), 3);
let rows = select(&mut db, "SELECT rowid, a FROM t ORDER BY rowid");
let got: Vec<(i64, i64)> =
rows.iter().map(|r| (int(&r.values[0]), int(&r.values[1]))).collect();
assert_eq!(got, vec![(1, 10), (2, 20), (3, 30)]);
}
#[test]
fn set_rowid_aliases_ipk() {
let mut db = vibesql_storage::Database::new();
ddl(&mut db, "CREATE TABLE t(k INTEGER PRIMARY KEY, v)");
ddl(&mut db, "INSERT INTO t VALUES(1, 'a')");
assert_eq!(update(&mut db, "UPDATE t SET rowid=7 WHERE k=1").unwrap(), 1);
let rows = select(&mut db, "SELECT rowid, k, v FROM t");
assert_eq!(int(&rows[0].values[0]), 7); assert_eq!(int(&rows[0].values[1]), 7); assert_eq!(text(&rows[0].values[2]), "a");
}
#[test]
fn set_rowid_ipk_conflict_errors() {
let mut db = vibesql_storage::Database::new();
ddl(&mut db, "CREATE TABLE t(k INTEGER PRIMARY KEY, v)");
ddl(&mut db, "INSERT INTO t VALUES(1, 'a')");
ddl(&mut db, "INSERT INTO t VALUES(2, 'b')");
let err = update(&mut db, "UPDATE t SET rowid=2 WHERE k=1").unwrap_err();
assert!(
err.to_string().contains("UNIQUE constraint failed: t.k"),
"expected IPK UNIQUE error, got: {}",
err
);
}
#[test]
fn set_ipk_swap_errors_intermediate_collision() {
let mut db = vibesql_storage::Database::new();
ddl(&mut db, "CREATE TABLE t(k INTEGER PRIMARY KEY, v)");
ddl(&mut db, "INSERT INTO t VALUES(1, 'a')");
ddl(&mut db, "INSERT INTO t VALUES(2, 'b')");
let err = update(&mut db, "UPDATE t SET k = CASE k WHEN 1 THEN 2 WHEN 2 THEN 1 END")
.unwrap_err();
assert!(
err.to_string().contains("UNIQUE constraint failed: t.k"),
"expected IPK UNIQUE error for swap, got: {}",
err
);
let rows = select(&mut db, "SELECT k, v FROM t ORDER BY k");
let got: Vec<(i64, String)> =
rows.iter().map(|r| (int(&r.values[0]), text(&r.values[1]))).collect();
assert_eq!(got, vec![(1, "a".to_string()), (2, "b".to_string())]);
}
#[test]
fn set_rowid_alias_ipk_swap_errors() {
let mut db = vibesql_storage::Database::new();
ddl(&mut db, "CREATE TABLE t(k INTEGER PRIMARY KEY, v)");
ddl(&mut db, "INSERT INTO t VALUES(1, 'a')");
ddl(&mut db, "INSERT INTO t VALUES(2, 'b')");
let err = update(
&mut db,
"UPDATE t SET rowid = CASE rowid WHEN 1 THEN 2 WHEN 2 THEN 1 END",
)
.unwrap_err();
assert!(
err.to_string().contains("UNIQUE constraint failed: t.k"),
"expected IPK UNIQUE error for rowid-alias swap, got: {}",
err
);
}
#[test]
fn set_ipk_three_cycle_errors() {
let mut db = vibesql_storage::Database::new();
ddl(&mut db, "CREATE TABLE t(k INTEGER PRIMARY KEY, v)");
ddl(&mut db, "INSERT INTO t VALUES(1, 'a')");
ddl(&mut db, "INSERT INTO t VALUES(2, 'b')");
ddl(&mut db, "INSERT INTO t VALUES(3, 'c')");
let err = update(
&mut db,
"UPDATE t SET k = CASE k WHEN 1 THEN 2 WHEN 2 THEN 3 WHEN 3 THEN 1 END",
)
.unwrap_err();
assert!(
err.to_string().contains("UNIQUE constraint failed: t.k"),
"expected IPK UNIQUE error for 3-cycle, got: {}",
err
);
}
#[test]
fn set_ipk_plus_one_cascade_errors() {
let mut db = vibesql_storage::Database::new();
ddl(&mut db, "CREATE TABLE t(k INTEGER PRIMARY KEY, v)");
ddl(&mut db, "INSERT INTO t VALUES(1, 'a')");
ddl(&mut db, "INSERT INTO t VALUES(2, 'b')");
ddl(&mut db, "INSERT INTO t VALUES(3, 'c')");
let err = update(&mut db, "UPDATE t SET k = k + 1").unwrap_err();
assert!(
err.to_string().contains("UNIQUE constraint failed: t.k"),
"expected IPK UNIQUE error for +1 cascade, got: {}",
err
);
}
#[test]
fn set_ipk_minus_one_cascade_ok() {
let mut db = vibesql_storage::Database::new();
ddl(&mut db, "CREATE TABLE t(k INTEGER PRIMARY KEY, v)");
ddl(&mut db, "INSERT INTO t VALUES(2, 'a')");
ddl(&mut db, "INSERT INTO t VALUES(3, 'b')");
ddl(&mut db, "INSERT INTO t VALUES(4, 'c')");
assert_eq!(update(&mut db, "UPDATE t SET k = k - 1").unwrap(), 3);
let rows = select(&mut db, "SELECT k, v FROM t ORDER BY k");
let got: Vec<(i64, String)> =
rows.iter().map(|r| (int(&r.values[0]), text(&r.values[1]))).collect();
assert_eq!(
got,
vec![(1, "a".to_string()), (2, "b".to_string()), (3, "c".to_string())]
);
}
#[test]
fn set_ipk_move_to_gap_ok() {
let mut db = vibesql_storage::Database::new();
ddl(&mut db, "CREATE TABLE t(k INTEGER PRIMARY KEY, v)");
ddl(&mut db, "INSERT INTO t VALUES(1, 'a')");
ddl(&mut db, "INSERT INTO t VALUES(2, 'b')");
assert_eq!(update(&mut db, "UPDATE t SET k = k + 100 WHERE k = 1").unwrap(), 1);
let rows = select(&mut db, "SELECT k, v FROM t ORDER BY k");
let got: Vec<(i64, String)> =
rows.iter().map(|r| (int(&r.values[0]), text(&r.values[1]))).collect();
assert_eq!(got, vec![(2, "b".to_string()), (101, "a".to_string())]);
}
#[test]
fn set_ipk_self_noop_ok() {
let mut db = vibesql_storage::Database::new();
ddl(&mut db, "CREATE TABLE t(k INTEGER PRIMARY KEY, v)");
ddl(&mut db, "INSERT INTO t VALUES(1, 'a')");
ddl(&mut db, "INSERT INTO t VALUES(2, 'b')");
ddl(&mut db, "INSERT INTO t VALUES(3, 'c')");
assert_eq!(update(&mut db, "UPDATE t SET k = k").unwrap(), 3);
let rows = select(&mut db, "SELECT k, v FROM t ORDER BY k");
let got: Vec<(i64, String)> =
rows.iter().map(|r| (int(&r.values[0]), text(&r.values[1]))).collect();
assert_eq!(
got,
vec![(1, "a".to_string()), (2, "b".to_string()), (3, "c".to_string())]
);
}
#[test]
fn regular_unique_column_swap_errors_intermediate_collision() {
let mut db = vibesql_storage::Database::new();
ddl(&mut db, "CREATE TABLE u(id INTEGER PRIMARY KEY, k INT UNIQUE)");
ddl(&mut db, "INSERT INTO u VALUES(1, 10)");
ddl(&mut db, "INSERT INTO u VALUES(2, 20)");
let err = update(&mut db, "UPDATE u SET k = CASE k WHEN 10 THEN 20 WHEN 20 THEN 10 END")
.unwrap_err();
assert!(
err.to_string().contains("UNIQUE constraint failed: u.k"),
"expected regular-UNIQUE swap to error immediately, got: {}",
err
);
let rows = select(&mut db, "SELECT id, k FROM u ORDER BY id");
let got: Vec<(i64, i64)> =
rows.iter().map(|r| (int(&r.values[0]), int(&r.values[1]))).collect();
assert_eq!(got, vec![(1, 10), (2, 20)]);
}
#[test]
fn regular_unique_column_shift_still_allowed_deferred() {
let mut db = vibesql_storage::Database::new();
ddl(&mut db, "CREATE TABLE u(id INTEGER PRIMARY KEY, k INT UNIQUE)");
ddl(&mut db, "INSERT INTO u VALUES(1, 10)");
ddl(&mut db, "INSERT INTO u VALUES(2, 20)");
ddl(&mut db, "INSERT INTO u VALUES(3, 30)");
assert_eq!(update(&mut db, "UPDATE u SET k = k - 10").unwrap(), 3);
let rows = select(&mut db, "SELECT id, k FROM u ORDER BY id");
let got: Vec<(i64, i64)> =
rows.iter().map(|r| (int(&r.values[0]), int(&r.values[1]))).collect();
assert_eq!(got, vec![(1, 0), (2, 10), (3, 20)]);
}
#[test]
fn index_consistent_after_relocate() {
let mut db = vibesql_storage::Database::new();
ddl(&mut db, "CREATE TABLE t(x, y)");
ddl(&mut db, "CREATE INDEX iy ON t(y)");
ddl(&mut db, "INSERT INTO t VALUES(1, 'aa')");
ddl(&mut db, "INSERT INTO t VALUES(2, 'bb')");
assert_eq!(update(&mut db, "UPDATE t SET rowid=99 WHERE rowid=1").unwrap(), 1);
let rows = select(&mut db, "SELECT rowid, x, y FROM t WHERE y='aa'");
assert_eq!(rows.len(), 1);
assert_eq!(int(&rows[0].values[0]), 99);
assert_eq!(int(&rows[0].values[1]), 1);
assert_eq!(text(&rows[0].values[2]), "aa");
}
#[test]
fn triggers_see_old_and_new_rowid() {
let mut db = vibesql_storage::Database::new();
ddl(&mut db, "CREATE TABLE t8(x)");
ddl(&mut db, "CREATE TABLE t7(a, b)");
ddl(&mut db, "INSERT INTO t7 VALUES(1, 2)");
ddl(
&mut db,
"CREATE TRIGGER t7tb BEFORE UPDATE ON t7 BEGIN \
INSERT INTO t8 VALUES('before ' || old.rowid || '->' || new.rowid); END",
);
ddl(
&mut db,
"CREATE TRIGGER t7ta AFTER UPDATE ON t7 BEGIN \
INSERT INTO t8 VALUES('after ' || old.rowid || '->' || new.rowid); END",
);
assert_eq!(update(&mut db, "UPDATE t7 SET rowid=8 WHERE rowid=1").unwrap(), 1);
let t7 = select(&mut db, "SELECT rowid, a, b FROM t7");
assert_eq!(int(&t7[0].values[0]), 8);
assert_eq!(int(&t7[0].values[1]), 1);
assert_eq!(int(&t7[0].values[2]), 2);
let t8 = select(&mut db, "SELECT x FROM t8 ORDER BY rowid");
let log: Vec<String> = t8.iter().map(|r| text(&r.values[0])).collect();
assert_eq!(log, vec!["before 1->8".to_string(), "after 1->8".to_string()]);
}
#[test]
fn triggerc_7_5_relocate_in_before_trigger() {
let mut db = vibesql_storage::Database::new();
ddl(&mut db, "CREATE TABLE t8(x)");
ddl(&mut db, "CREATE TABLE t7(a, b)");
ddl(&mut db, "INSERT INTO t7 VALUES(1, 2)");
ddl(&mut db, "INSERT INTO t7 VALUES(3, 4)");
ddl(&mut db, "INSERT INTO t7 VALUES(5, 6)");
ddl(
&mut db,
"CREATE TRIGGER t7t BEFORE UPDATE ON t7 WHEN (old.rowid!=1 OR new.rowid!=8) \
BEGIN UPDATE t7 SET rowid = 8 WHERE rowid=1; END",
);
ddl(
&mut db,
"CREATE TRIGGER t7ta AFTER UPDATE ON t7 BEGIN \
INSERT INTO t8 VALUES('after fired ' || old.rowid || '->' || new.rowid); END",
);
update(&mut db, "UPDATE t7 SET b=7 WHERE a = 5").unwrap();
let t7 = select(&mut db, "SELECT rowid, a, b FROM t7 ORDER BY rowid");
let got: Vec<(i64, i64, i64)> = t7
.iter()
.map(|r| (int(&r.values[0]), int(&r.values[1]), int(&r.values[2])))
.collect();
assert_eq!(got, vec![(2, 3, 4), (3, 5, 7), (8, 1, 2)]);
let t8 = select(&mut db, "SELECT x FROM t8 ORDER BY rowid");
let log: Vec<String> = t8.iter().map(|r| text(&r.values[0])).collect();
assert_eq!(
log,
vec![
"after fired 1->8".to_string(),
"after fired 3->3".to_string()
]
);
}
#[test]
fn shadow_column_named_rowid_reflected_in_triggers() {
let mut db = vibesql_storage::Database::new();
ddl(&mut db, "CREATE TABLE t1(rowid, oid, _rowid_, x)");
ddl(&mut db, "CREATE TABLE log(a, b, c, d, e)");
ddl(
&mut db,
"CREATE TRIGGER r3 BEFORE UPDATE ON t1 BEGIN \
INSERT INTO log VALUES('r3.old', old.rowid, old.oid, old._rowid_, old.x); \
INSERT INTO log VALUES('r3.new', new.rowid, new.oid, new._rowid_, new.x); END",
);
ddl(
&mut db,
"CREATE TRIGGER r4 AFTER UPDATE ON t1 BEGIN \
INSERT INTO log VALUES('r4.old', old.rowid, old.oid, old._rowid_, old.x); \
INSERT INTO log VALUES('r4.new', new.rowid, new.oid, new._rowid_, new.x); END",
);
ddl(
&mut db,
"CREATE TRIGGER r6 AFTER DELETE ON t1 BEGIN \
INSERT INTO log VALUES('r6', old.rowid, old.oid, old._rowid_, old.x); END",
);
ddl(&mut db, "INSERT INTO t1 VALUES(100, 200, 300, 400)");
assert_eq!(update(&mut db, "UPDATE t1 SET rowid=rowid+1").unwrap(), 1);
let t1 = select(&mut db, "SELECT rowid, oid, _rowid_, x FROM t1");
assert_eq!(int(&t1[0].values[0]), 101);
assert_eq!(int(&t1[0].values[1]), 200);
assert_eq!(int(&t1[0].values[2]), 300);
assert_eq!(int(&t1[0].values[3]), 400);
let rows = select(&mut db, "SELECT a, b, c, d, e FROM log ORDER BY _rowid_");
let log: Vec<(String, i64, i64, i64, i64)> = rows
.iter()
.map(|r| {
(
text(&r.values[0]),
int(&r.values[1]),
int(&r.values[2]),
int(&r.values[3]),
int(&r.values[4]),
)
})
.collect();
assert_eq!(
log,
vec![
("r3.old".to_string(), 100, 200, 300, 400),
("r3.new".to_string(), 101, 200, 300, 400),
("r4.old".to_string(), 100, 200, 300, 400),
("r4.new".to_string(), 101, 200, 300, 400),
]
);
delete(&mut db, "DELETE FROM log");
delete(&mut db, "DELETE FROM t1");
let after_delete = select(&mut db, "SELECT a, b, c, d, e FROM log");
assert_eq!(after_delete.len(), 1);
assert_eq!(text(&after_delete[0].values[0]), "r6");
assert_eq!(int(&after_delete[0].values[1]), 101);
assert_eq!(int(&after_delete[0].values[2]), 200);
assert_eq!(int(&after_delete[0].values[3]), 300);
assert_eq!(int(&after_delete[0].values[4]), 400);
}
#[test]
fn regular_unique_plus_ten_cascade_errors() {
let mut db = vibesql_storage::Database::new();
ddl(&mut db, "CREATE TABLE u(id INTEGER PRIMARY KEY, k INT UNIQUE)");
ddl(&mut db, "INSERT INTO u VALUES(1, 10)");
ddl(&mut db, "INSERT INTO u VALUES(2, 20)");
ddl(&mut db, "INSERT INTO u VALUES(3, 30)");
let err = update(&mut db, "UPDATE u SET k = k + 10").unwrap_err();
assert!(
err.to_string().contains("UNIQUE constraint failed: u.k"),
"expected ascending-shift UNIQUE error, got: {}",
err
);
let rows = select(&mut db, "SELECT id, k FROM u ORDER BY id");
let got: Vec<(i64, i64)> =
rows.iter().map(|r| (int(&r.values[0]), int(&r.values[1]))).collect();
assert_eq!(got, vec![(1, 10), (2, 20), (3, 30)]);
}
#[test]
fn regular_unique_minus_ten_cascade_ok() {
let mut db = vibesql_storage::Database::new();
ddl(&mut db, "CREATE TABLE u(id INTEGER PRIMARY KEY, k INT UNIQUE)");
ddl(&mut db, "INSERT INTO u VALUES(1, 10)");
ddl(&mut db, "INSERT INTO u VALUES(2, 20)");
ddl(&mut db, "INSERT INTO u VALUES(3, 30)");
assert_eq!(update(&mut db, "UPDATE u SET k = k - 10").unwrap(), 3);
let rows = select(&mut db, "SELECT id, k FROM u ORDER BY id");
let got: Vec<(i64, i64)> =
rows.iter().map(|r| (int(&r.values[0]), int(&r.values[1]))).collect();
assert_eq!(got, vec![(1, 0), (2, 10), (3, 20)]);
}
#[test]
fn regular_unique_three_cycle_errors() {
let mut db = vibesql_storage::Database::new();
ddl(&mut db, "CREATE TABLE u(id INTEGER PRIMARY KEY, k INT UNIQUE)");
ddl(&mut db, "INSERT INTO u VALUES(1, 10)");
ddl(&mut db, "INSERT INTO u VALUES(2, 20)");
ddl(&mut db, "INSERT INTO u VALUES(3, 30)");
let err = update(
&mut db,
"UPDATE u SET k = CASE k WHEN 10 THEN 20 WHEN 20 THEN 30 WHEN 30 THEN 10 END",
)
.unwrap_err();
assert!(
err.to_string().contains("UNIQUE constraint failed: u.k"),
"expected 3-cycle UNIQUE error, got: {}",
err
);
}
#[test]
fn regular_unique_shift_into_occupied_errors() {
let mut db = vibesql_storage::Database::new();
ddl(&mut db, "CREATE TABLE u(id INTEGER PRIMARY KEY, k INT UNIQUE)");
ddl(&mut db, "INSERT INTO u VALUES(1, 10)");
ddl(&mut db, "INSERT INTO u VALUES(2, 20)");
let err = update(&mut db, "UPDATE u SET k = 20 WHERE k = 10").unwrap_err();
assert!(
err.to_string().contains("UNIQUE constraint failed: u.k"),
"expected shift-into-occupied UNIQUE error, got: {}",
err
);
}
#[test]
fn regular_unique_move_to_gap_ok() {
let mut db = vibesql_storage::Database::new();
ddl(&mut db, "CREATE TABLE u(id INTEGER PRIMARY KEY, k INT UNIQUE)");
ddl(&mut db, "INSERT INTO u VALUES(1, 10)");
ddl(&mut db, "INSERT INTO u VALUES(2, 20)");
assert_eq!(update(&mut db, "UPDATE u SET k = 99 WHERE k = 10").unwrap(), 1);
let rows = select(&mut db, "SELECT id, k FROM u ORDER BY id");
let got: Vec<(i64, i64)> =
rows.iter().map(|r| (int(&r.values[0]), int(&r.values[1]))).collect();
assert_eq!(got, vec![(1, 99), (2, 20)]);
}
#[test]
fn regular_unique_negation_ok() {
let mut db = vibesql_storage::Database::new();
ddl(&mut db, "CREATE TABLE u(id INTEGER PRIMARY KEY, k INT UNIQUE)");
ddl(&mut db, "INSERT INTO u VALUES(1, 10)");
ddl(&mut db, "INSERT INTO u VALUES(2, 20)");
assert_eq!(update(&mut db, "UPDATE u SET k = -k").unwrap(), 2);
let rows = select(&mut db, "SELECT id, k FROM u ORDER BY id");
let got: Vec<(i64, i64)> =
rows.iter().map(|r| (int(&r.values[0]), int(&r.values[1]))).collect();
assert_eq!(got, vec![(1, -10), (2, -20)]);
}
#[test]
fn regular_unique_free_before_reuse_ok() {
let mut db = vibesql_storage::Database::new();
ddl(&mut db, "CREATE TABLE u(id INTEGER PRIMARY KEY, k INT UNIQUE)");
ddl(&mut db, "INSERT INTO u VALUES(1, 10)");
ddl(&mut db, "INSERT INTO u VALUES(2, 20)");
assert_eq!(
update(&mut db, "UPDATE u SET k = CASE k WHEN 10 THEN 99 WHEN 20 THEN 10 END").unwrap(),
2
);
let rows = select(&mut db, "SELECT id, k FROM u ORDER BY id");
let got: Vec<(i64, i64)> =
rows.iter().map(|r| (int(&r.values[0]), int(&r.values[1]))).collect();
assert_eq!(got, vec![(1, 99), (2, 10)]);
}
#[test]
fn regular_unique_reuse_before_free_errors() {
let mut db = vibesql_storage::Database::new();
ddl(&mut db, "CREATE TABLE u(id INTEGER PRIMARY KEY, k INT UNIQUE)");
ddl(&mut db, "INSERT INTO u VALUES(1, 20)");
ddl(&mut db, "INSERT INTO u VALUES(2, 10)");
let err = update(&mut db, "UPDATE u SET k = CASE k WHEN 20 THEN 10 WHEN 10 THEN 99 END")
.unwrap_err();
assert!(
err.to_string().contains("UNIQUE constraint failed: u.k"),
"expected reuse-before-free UNIQUE error, got: {}",
err
);
}
#[test]
fn regular_unique_to_null_ok() {
let mut db = vibesql_storage::Database::new();
ddl(&mut db, "CREATE TABLE u(id INTEGER PRIMARY KEY, k INT UNIQUE)");
ddl(&mut db, "INSERT INTO u VALUES(1, 10)");
ddl(&mut db, "INSERT INTO u VALUES(2, 20)");
assert_eq!(update(&mut db, "UPDATE u SET k = NULL").unwrap(), 2);
let rows = select(&mut db, "SELECT id, k FROM u ORDER BY id");
assert_eq!(rows.len(), 2);
assert!(matches!(rows[0].values[1], SqlValue::Null));
assert!(matches!(rows[1].values[1], SqlValue::Null));
}
#[test]
fn regular_composite_unique_swap_errors() {
let mut db = vibesql_storage::Database::new();
ddl(&mut db, "CREATE TABLE c(id INTEGER PRIMARY KEY, a INT, b INT, UNIQUE(a, b))");
ddl(&mut db, "INSERT INTO c VALUES(1, 1, 1)");
ddl(&mut db, "INSERT INTO c VALUES(2, 2, 2)");
let err = update(
&mut db,
"UPDATE c SET a = CASE a WHEN 1 THEN 2 WHEN 2 THEN 1 END, \
b = CASE b WHEN 1 THEN 2 WHEN 2 THEN 1 END",
)
.unwrap_err();
assert!(
err.to_string().contains("UNIQUE constraint failed: c.a, c.b"),
"expected composite-UNIQUE swap error, got: {}",
err
);
}
#[test]
fn composite_pk_swap_errors() {
let mut db = vibesql_storage::Database::new();
ddl(&mut db, "CREATE TABLE c(a INT, b INT, v TEXT, PRIMARY KEY(a, b))");
ddl(&mut db, "INSERT INTO c VALUES(1, 1, 'r1')");
ddl(&mut db, "INSERT INTO c VALUES(2, 2, 'r2')");
let err = update(
&mut db,
"UPDATE c SET a = CASE a WHEN 1 THEN 2 WHEN 2 THEN 1 END, \
b = CASE b WHEN 1 THEN 2 WHEN 2 THEN 1 END",
)
.unwrap_err();
assert!(
err.to_string().contains("UNIQUE constraint failed: c.a, c.b"),
"expected composite-PK swap error, got: {}",
err
);
}
#[test]
fn composite_pk_descending_shift_ok() {
let mut db = vibesql_storage::Database::new();
ddl(&mut db, "CREATE TABLE c(a INT, b INT, v TEXT, PRIMARY KEY(a, b))");
ddl(&mut db, "INSERT INTO c VALUES(1, 0, 'r1')");
ddl(&mut db, "INSERT INTO c VALUES(1, 1, 'r2')");
ddl(&mut db, "INSERT INTO c VALUES(1, 2, 'r3')");
assert_eq!(update(&mut db, "UPDATE c SET b = b - 1").unwrap(), 3);
let rows = select(&mut db, "SELECT a, b FROM c ORDER BY b");
let got: Vec<(i64, i64)> =
rows.iter().map(|r| (int(&r.values[0]), int(&r.values[1]))).collect();
assert_eq!(got, vec![(1, -1), (1, 0), (1, 1)]);
}
#[test]
fn user_unique_index_swap_errors() {
let mut db = vibesql_storage::Database::new();
ddl(&mut db, "CREATE TABLE u(id INTEGER PRIMARY KEY, k INT)");
ddl(&mut db, "CREATE UNIQUE INDEX u_k ON u(k)");
ddl(&mut db, "INSERT INTO u VALUES(1, 10)");
ddl(&mut db, "INSERT INTO u VALUES(2, 20)");
let err = update(&mut db, "UPDATE u SET k = CASE k WHEN 10 THEN 20 WHEN 20 THEN 10 END")
.unwrap_err();
assert!(
err.to_string().contains("UNIQUE constraint failed: u.k"),
"expected user-UNIQUE-index swap error, got: {}",
err
);
}
#[test]
fn user_unique_index_descending_shift_ok() {
let mut db = vibesql_storage::Database::new();
ddl(&mut db, "CREATE TABLE u(id INTEGER PRIMARY KEY, k INT)");
ddl(&mut db, "CREATE UNIQUE INDEX u_k ON u(k)");
ddl(&mut db, "INSERT INTO u VALUES(1, 10)");
ddl(&mut db, "INSERT INTO u VALUES(2, 20)");
ddl(&mut db, "INSERT INTO u VALUES(3, 30)");
assert_eq!(update(&mut db, "UPDATE u SET k = k - 10").unwrap(), 3);
let rows = select(&mut db, "SELECT id, k FROM u ORDER BY id");
let got: Vec<(i64, i64)> =
rows.iter().map(|r| (int(&r.values[0]), int(&r.values[1]))).collect();
assert_eq!(got, vec![(1, 0), (2, 10), (3, 20)]);
}
#[test]
fn regular_unique_swap_errors_virtual_rowid_table() {
let mut db = vibesql_storage::Database::new();
ddl(&mut db, "CREATE TABLE u(x TEXT, k INT UNIQUE)");
ddl(&mut db, "INSERT INTO u VALUES('a', 10)");
ddl(&mut db, "INSERT INTO u VALUES('b', 20)");
let err = update(&mut db, "UPDATE u SET k = CASE k WHEN 10 THEN 20 WHEN 20 THEN 10 END")
.unwrap_err();
assert!(
err.to_string().contains("UNIQUE constraint failed: u.k"),
"expected virtual-rowid table UNIQUE swap error, got: {}",
err
);
}