Files
turso/core/mvcc/cursor.rs
Pere Diaz Bou f26e442597 core/mvcc: fix new rowid
next rowid was being tracked globally for all tables and restarted to 0
every time database was opened
2025-08-04 12:31:17 +02:00

155 lines
5.3 KiB
Rust

use crate::mvcc::clock::LogicalClock;
use crate::mvcc::database::{MvStore, Row, RowID};
use crate::Pager;
use crate::Result;
use std::fmt::Debug;
use std::rc::Rc;
use std::sync::Arc;
#[derive(Debug, Copy, Clone)]
enum CursorPosition {
/// We haven't loaded any row yet.
BeforeFirst,
/// We have loaded a row.
Loaded(RowID),
/// We have reached the end of the table.
End,
}
#[derive(Debug)]
pub struct MvccLazyCursor<Clock: LogicalClock> {
pub db: Arc<MvStore<Clock>>,
current_pos: CursorPosition,
table_id: u64,
tx_id: u64,
}
impl<Clock: LogicalClock> MvccLazyCursor<Clock> {
pub fn new(
db: Arc<MvStore<Clock>>,
tx_id: u64,
table_id: u64,
pager: Rc<Pager>,
) -> Result<MvccLazyCursor<Clock>> {
db.maybe_initialize_table(table_id, pager)?;
let cursor = Self {
db,
tx_id,
current_pos: CursorPosition::BeforeFirst,
table_id,
};
Ok(cursor)
}
/// Insert a row into the table.
/// Sets the cursor to the inserted row.
pub fn insert(&mut self, row: Row) -> Result<()> {
self.current_pos = CursorPosition::Loaded(row.id);
self.db.insert(self.tx_id, row).inspect_err(|_| {
self.current_pos = CursorPosition::BeforeFirst;
})?;
Ok(())
}
pub fn current_row_id(&mut self) -> Option<RowID> {
match self.current_pos {
CursorPosition::Loaded(id) => Some(id),
CursorPosition::BeforeFirst => {
// If we are before first, we need to try and find the first row.
let maybe_rowid = self.db.get_next_row_id_for_table(self.table_id, i64::MIN);
if let Some(id) = maybe_rowid {
self.current_pos = CursorPosition::Loaded(id);
Some(id)
} else {
self.current_pos = CursorPosition::BeforeFirst;
None
}
}
CursorPosition::End => None,
}
}
pub fn current_row(&mut self) -> Result<Option<Row>> {
match self.current_pos {
CursorPosition::Loaded(id) => self.db.read(self.tx_id, id),
CursorPosition::BeforeFirst => {
// If we are before first, we need to try and find the first row.
let maybe_rowid = self.db.get_next_row_id_for_table(self.table_id, i64::MIN);
if let Some(id) = maybe_rowid {
self.current_pos = CursorPosition::Loaded(id);
self.db.read(self.tx_id, id)
} else {
Ok(None)
}
}
CursorPosition::End => Ok(None),
}
}
pub fn close(self) -> Result<()> {
Ok(())
}
/// Move the cursor to the next row. Returns true if the cursor moved to the next row, false if the cursor is at the end of the table.
pub fn forward(&mut self) -> bool {
let before_first = matches!(self.current_pos, CursorPosition::BeforeFirst);
let min_id = match self.current_pos {
CursorPosition::Loaded(id) => id.row_id + 1,
// TODO: do we need to forward twice?
CursorPosition::BeforeFirst => i64::MIN, // we need to find first row, so we look from the first id,
CursorPosition::End => {
// let's keep same state, we reached the end so no point in moving forward.
return false;
}
};
self.current_pos = match self.db.get_next_row_id_for_table(self.table_id, min_id) {
Some(id) => CursorPosition::Loaded(id),
None => {
if before_first {
// if it wasn't loaded and we didn't find anything, it means the table is empty.
CursorPosition::BeforeFirst
} else {
// if we had something loaded, and we didn't find next key then it means we are at the end.
CursorPosition::End
}
}
};
matches!(self.current_pos, CursorPosition::Loaded(_))
}
/// Returns true if the is not pointing to any row.
pub fn is_empty(&self) -> bool {
// If we reached the end of the table, it means we traversed the whole table therefore there must be something in the table.
// If we have loaded a row, it means there is something in the table.
match self.current_pos {
CursorPosition::Loaded(_) => false,
CursorPosition::BeforeFirst => true,
CursorPosition::End => true,
}
}
pub fn rewind(&mut self) {
self.current_pos = CursorPosition::BeforeFirst;
}
pub fn last(&mut self) {
let last_rowid = self.db.get_last_rowid(self.table_id);
if let Some(last_rowid) = last_rowid {
self.current_pos = CursorPosition::Loaded(RowID {
table_id: self.table_id,
row_id: last_rowid,
});
} else {
self.current_pos = CursorPosition::BeforeFirst;
}
}
pub fn get_next_rowid(&mut self) -> i64 {
self.last();
match self.current_pos {
CursorPosition::Loaded(id) => id.row_id + 1,
CursorPosition::BeforeFirst => i64::MIN,
CursorPosition::End => i64::MAX,
}
}
}