fix updating single value

This commit is contained in:
pedrocarlo
2025-05-17 19:43:24 -03:00
parent 0fa4ebaec2
commit af1f9492ef
2 changed files with 41 additions and 17 deletions

View File

@@ -3589,12 +3589,15 @@ impl BTreeCursor {
Some(rowid) => rowid, Some(rowid) => rowid,
None => { None => {
self.state = CursorState::None; self.state = CursorState::None;
// I don't think its necessary to invalidate here, but its safer to do so if I'm wrong
self.invalidate_record();
return Ok(CursorResult::Ok(())); return Ok(CursorResult::Ok(()));
} }
}; };
} else { } else {
if self.reusable_immutable_record.borrow().is_none() { if self.reusable_immutable_record.borrow().is_none() {
self.state = CursorState::None; self.state = CursorState::None;
// Record is None here so no need to invalidate
return Ok(CursorResult::Ok(())); return Ok(CursorResult::Ok(()));
} }
} }
@@ -3778,6 +3781,7 @@ impl BTreeCursor {
} else { } else {
self.stack.retreat(); self.stack.retreat();
self.state = CursorState::None; self.state = CursorState::None;
self.invalidate_record();
return Ok(CursorResult::Ok(())); return Ok(CursorResult::Ok(()));
} }
// Only reaches this function call if state = DeleteState::WaitForBalancingToComplete // Only reaches this function call if state = DeleteState::WaitForBalancingToComplete
@@ -3834,12 +3838,18 @@ impl BTreeCursor {
return_if_io!(self.seek(key, SeekOp::EQ)); return_if_io!(self.seek(key, SeekOp::EQ));
self.state = CursorState::None; self.state = CursorState::None;
self.invalidate_record();
return Ok(CursorResult::Ok(())); return Ok(CursorResult::Ok(()));
} }
} }
} }
} }
fn invalidate_record(&mut self) {
let mut record = self.get_immutable_record();
record.as_mut().map(|record| record.invalidate());
}
/// In outer joins, whenever the right-side table has no matching row, the query must still return a row /// In outer joins, whenever the right-side table has no matching row, the query must still return a row
/// for each left-side row. In order to achieve this, we set the null flag on the right-side table cursor /// for each left-side row. In order to achieve this, we set the null flag on the right-side table cursor
/// so that it returns NULL for all columns until cleared. /// so that it returns NULL for all columns until cleared.
@@ -3863,21 +3873,24 @@ impl BTreeCursor {
// Existing record found — compare prefix // Existing record found — compare prefix
let existing_key = &record.get_values()[..record.count().saturating_sub(1)]; let existing_key = &record.get_values()[..record.count().saturating_sub(1)];
let inserted_key_vals = &key.get_values(); let inserted_key_vals = &key.get_values();
if existing_key // Need this check because .all returns True on an empty iterator,
.iter() // So when record_opt is invalidated, it would always indicate show up as a duplicate key
.zip(inserted_key_vals.iter()) if existing_key.len() != inserted_key_vals.len() {
.all(|(a, b)| a == b) return Ok(CursorResult::Ok(false));
{
return Ok(CursorResult::Ok(true)); // duplicate
} }
Ok(CursorResult::Ok(
existing_key
.iter()
.zip(inserted_key_vals.iter())
.all(|(a, b)| a == b),
))
} }
None => { None => {
// Cursor not pointing at a record — table is empty or past last // Cursor not pointing at a record — table is empty or past last
return Ok(CursorResult::Ok(false)); Ok(CursorResult::Ok(false))
} }
} }
Ok(CursorResult::Ok(false)) // not a duplicate
} }
pub fn exists(&mut self, key: &Value) -> Result<CursorResult<bool>> { pub fn exists(&mut self, key: &Value) -> Result<CursorResult<bool>> {

View File

@@ -347,6 +347,15 @@ def custom_test_2(limbo: TestLimboShell):
) )
# Issue #1482
def regression_test_update_single_key(limbo: TestLimboShell):
create = "CREATE TABLE t(a unique);"
first_insert = "INSERT INTO t VALUES (1);"
limbo.run_test("Create simple table with 1 unique value", create + first_insert, "")
update_single = "UPDATE t SET a=1 WHERE a=1;"
limbo.run_test("Update one single key to the same value", update_single, "")
def all_tests() -> list[ConstraintTest]: def all_tests() -> list[ConstraintTest]:
tests: list[ConstraintTest] = [] tests: list[ConstraintTest] = []
max_cols = 10 max_cols = 10
@@ -390,15 +399,17 @@ def main():
cleanup(db_path) cleanup(db_path)
db_path = "testing/constraint.db" db_path = "testing/constraint.db"
try: tests = [custom_test_2, regression_test_update_single_key]
with TestLimboShell("") as limbo: for test in tests:
limbo.execute_dot(f".open {db_path}") try:
custom_test_2(limbo) with TestLimboShell("") as limbo:
except Exception as e: limbo.execute_dot(f".open {db_path}")
console.error(f"Test FAILED: {e}") test(limbo)
except Exception as e:
console.error(f"Test FAILED: {e}")
cleanup(db_path)
exit(1)
cleanup(db_path) cleanup(db_path)
exit(1)
cleanup(db_path)
console.info("All tests passed successfully.") console.info("All tests passed successfully.")