Skip to content

Commit d595721

Browse files
authored
Merge 'core/mvcc: fix exists to use BTreeCursor as fallback' from Pere Diaz Bou
Closes #4026
2 parents fd6ef66 + 6d12fa3 commit d595721

File tree

2 files changed

+76
-29
lines changed

2 files changed

+76
-29
lines changed

core/mvcc/cursor.rs

Lines changed: 56 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,17 @@ enum PrevState {
4343
new_position_in_mvcc: CursorPosition,
4444
},
4545
}
46+
47+
#[derive(Debug, Clone)]
48+
enum ExistsState {
49+
ExistsBtree,
50+
}
51+
4652
#[derive(Debug, Clone)]
4753
enum MvccLazyCursorState {
4854
Next(NextState),
4955
Prev(PrevState),
56+
Exists(ExistsState),
5057
}
5158

5259
pub struct MvccLazyCursor<Clock: LogicalClock> {
@@ -648,39 +655,59 @@ impl<Clock: LogicalClock + 'static> CursorTrait for MvccLazyCursor<Clock> {
648655
}
649656

650657
fn exists(&mut self, key: &Value) -> Result<IOResult<bool>> {
651-
self.invalidate_record();
652-
let int_key = match key {
653-
Value::Integer(i) => i,
654-
_ => unreachable!("btree tables are indexed by integers!"),
655-
};
656-
let rowid = self.db.seek_rowid(
657-
Bound::Included(&RowID {
658-
table_id: self.table_id,
659-
row_id: RowKey::Int(*int_key),
660-
}),
661-
true,
662-
self.tx_id,
663-
);
664-
tracing::trace!("found {rowid:?}");
665-
let exists = if let Some(rowid) = rowid {
666-
let RowKey::Int(rowid) = rowid.row_id else {
667-
panic!("Rowid is not an integer in mvcc table cursor");
658+
if self.state.borrow().is_none() {
659+
self.invalidate_record();
660+
let int_key = match key {
661+
Value::Integer(i) => i,
662+
_ => unreachable!("btree tables are indexed by integers!"),
668663
};
669-
rowid == *int_key
670-
} else {
671-
false
672-
};
673-
if exists {
674-
self.current_pos.replace(CursorPosition::Loaded {
675-
row_id: RowID {
664+
let rowid = self.db.seek_rowid(
665+
Bound::Included(&RowID {
676666
table_id: self.table_id,
677667
row_id: RowKey::Int(*int_key),
678-
},
679-
in_btree: false,
680-
btree_consumed: false,
681-
});
668+
}),
669+
true,
670+
self.tx_id,
671+
);
672+
tracing::trace!("found {rowid:?}");
673+
let exists = if let Some(rowid) = rowid {
674+
let RowKey::Int(rowid) = rowid.row_id else {
675+
panic!("Rowid is not an integer in mvcc table cursor");
676+
};
677+
rowid == *int_key
678+
} else {
679+
false
680+
};
681+
if exists {
682+
self.current_pos.replace(CursorPosition::Loaded {
683+
row_id: RowID {
684+
table_id: self.table_id,
685+
row_id: RowKey::Int(*int_key),
686+
},
687+
in_btree: false,
688+
btree_consumed: false,
689+
});
690+
return Ok(IOResult::Done(exists));
691+
} else if self.is_btree_allocated() {
692+
self.state
693+
.replace(Some(MvccLazyCursorState::Exists(ExistsState::ExistsBtree)));
694+
} else {
695+
return Ok(IOResult::Done(false));
696+
}
682697
}
683-
Ok(IOResult::Done(exists))
698+
699+
let Some(MvccLazyCursorState::Exists(ExistsState::ExistsBtree)) =
700+
self.state.borrow().clone()
701+
else {
702+
panic!("Invalid state {:?}", self.state.borrow());
703+
};
704+
assert!(
705+
self.is_btree_allocated(),
706+
"BTree should be allocated when we are in ExistsBtree state"
707+
);
708+
self.state.replace(None);
709+
let found = return_if_io!(self.btree_cursor.exists(key));
710+
Ok(IOResult::Done(found))
684711
}
685712

686713
fn clear_btree(&mut self) -> Result<IOResult<Option<usize>>> {

core/mvcc/database/tests.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1843,3 +1843,23 @@ pub fn rng_from_time_or_env() -> (ChaCha8Rng, u64) {
18431843
let rng = ChaCha8Rng::seed_from_u64(seed as u64);
18441844
(rng, seed as u64)
18451845
}
1846+
1847+
#[test]
1848+
fn test_cursor_with_btree_and_mvcc_insert_after_checkpoint_repeated_key() {
1849+
let mut db = MvccTestDbNoConn::new_with_random_db();
1850+
// First write some rows and checkpoint so data is flushed to BTree file (.db)
1851+
{
1852+
let conn = db.connect();
1853+
conn.execute("CREATE TABLE t(x integer primary key)")
1854+
.unwrap();
1855+
conn.execute("INSERT INTO t VALUES (1)").unwrap();
1856+
conn.execute("INSERT INTO t VALUES (2)").unwrap();
1857+
conn.execute("PRAGMA wal_checkpoint(TRUNCATE)").unwrap();
1858+
}
1859+
// Now restart so new connection will have to read data from BTree instead of MVCC.
1860+
db.restart();
1861+
let conn = db.connect();
1862+
// Insert a new row so that we have a gap in the BTree.
1863+
let res = conn.execute("INSERT INTO t VALUES (2)");
1864+
assert!(res.is_err(), "Expected error because key 2 already exists");
1865+
}

0 commit comments

Comments
 (0)