use std::collections::HashMap;
use std::sync::atomic::Ordering;
use std::time::Instant;
use super::TransactionContext;
use crate::error::TransactionError;
use crate::types::*;
use crate::undo_log::{UndoLogEntry, UndoTarget};
use linkrs_core::types::StagedWriteMark;
pub(crate) struct SavepointManager {
savepoints: HashMap<SavepointId, SavepointInfo>,
next_id: SavepointId,
next_sequence: u64,
}
impl SavepointManager {
pub(super) fn new() -> Self {
Self {
savepoints: HashMap::new(),
next_id: 1,
next_sequence: 1,
}
}
pub(super) fn create_savepoint(&mut self, params: SavepointParams) -> SavepointId {
let id = self.next_id;
self.next_id += 1;
let sequence = self.next_sequence;
self.next_sequence += 1;
let info = SavepointInfo {
id,
name: params.name,
created_at: Instant::now(),
sequence,
undo_log_index: params.undo_log_index,
sync_sequence: params.sync_sequence,
write_set: params.write_set,
read_set: params.read_set,
modified_tables: params.modified_tables,
journal_len: params.journal_len,
journal_next_sequence: params.journal_next_sequence,
staged_write_mark: None,
};
self.savepoints.insert(id, info);
id
}
pub(super) fn set_staging_mark(
&mut self,
id: SavepointId,
mark: Option<StagedWriteMark>,
) -> Result<(), TransactionError> {
let savepoint = self
.savepoints
.get_mut(&id)
.ok_or(TransactionError::savepoint_not_found(id))?;
savepoint.staged_write_mark = mark;
Ok(())
}
pub(super) fn get_savepoint(&self, id: SavepointId) -> Option<&SavepointInfo> {
self.savepoints.get(&id)
}
pub(super) fn remove_savepoint(&mut self, id: SavepointId) -> Option<SavepointInfo> {
self.savepoints.remove(&id)
}
pub(super) fn clear(&mut self) {
self.savepoints.clear();
}
pub(super) fn find_by_name(&self, name: &str) -> Option<SavepointInfo> {
self.savepoints
.values()
.filter(|sp| sp.name.as_deref() == Some(name))
.max_by_key(|sp| sp.sequence)
.cloned()
}
}
impl TransactionContext {
pub fn record_table_modification(&self, table_name: &str) {
let mut tables = self.modified_tables.lock();
if !tables.contains(&table_name.to_string()) {
tables.push(table_name.to_string());
}
}
pub fn get_modified_tables(&self) -> Vec<String> {
let tables = self.modified_tables.lock();
tables.clone()
}
pub fn create_savepoint(&self, name: Option<String>, sync_sequence: u64) -> SavepointId {
let (journal_len, journal_next) = {
let j = self.mutation_journal.read();
(j.len(), j.next_sequence())
};
let params = SavepointParams {
name,
undo_log_index: self.undo_log_len(),
sync_sequence,
write_set: self.get_write_set(),
read_set: self.get_read_set(),
modified_tables: self.get_modified_tables(),
journal_len,
journal_next_sequence: journal_next,
};
let mut manager = self.savepoint_manager.write();
manager.create_savepoint(params)
}
pub fn set_savepoint_staging_mark(
&self,
id: SavepointId,
mark: Option<StagedWriteMark>,
) -> Result<(), TransactionError> {
let mut manager = self.savepoint_manager.write();
manager.set_staging_mark(id, mark)
}
pub fn get_savepoint(&self, id: SavepointId) -> Option<SavepointInfo> {
let manager = self.savepoint_manager.read();
manager.get_savepoint(id).cloned()
}
pub fn find_savepoint_by_id(&self, id: SavepointId) -> Option<SavepointInfo> {
self.get_savepoint(id)
}
pub fn get_all_savepoints(&self) -> Vec<SavepointInfo> {
let manager = self.savepoint_manager.read();
manager.savepoints.values().cloned().collect()
}
pub fn find_savepoint_by_name(&self, name: &str) -> Option<SavepointInfo> {
let manager = self.savepoint_manager.read();
manager.find_by_name(name)
}
pub fn release_savepoint(&self, id: SavepointId) -> Result<(), TransactionError> {
let mut manager = self.savepoint_manager.write();
manager
.remove_savepoint(id)
.map(|_| ())
.ok_or(TransactionError::savepoint_not_found(id))
}
pub fn rollback_to_savepoint<T: UndoTarget + ?Sized>(
&self,
id: SavepointId,
target: &T,
) -> Result<(), TransactionError> {
let state = self.state.load();
if !state.can_execute() {
return Err(TransactionError::invalid_state_for_abort(state));
}
if self.is_expired() {
return Err(TransactionError::transaction_expired());
}
let savepoint_info = {
let manager = self.savepoint_manager.read();
manager
.get_savepoint(id)
.cloned()
.ok_or(TransactionError::savepoint_not_found(id))?
};
{
let journal = self.mutation_journal.read();
let position = crate::mutation_journal::MutationJournalPosition {
journal_len: savepoint_info.journal_len,
next_sequence: savepoint_info.journal_next_sequence,
undo_log_index: savepoint_info.undo_log_index,
modified_tables: savepoint_info.modified_tables.clone(),
write_set_snapshot: savepoint_info.write_set.clone(),
read_set_snapshot: savepoint_info.read_set.clone(),
sync_sequence: savepoint_info.sync_sequence,
savepoint_sequence: savepoint_info.sequence,
};
if let Err(e) = position.validate_against(&journal) {
return Err(TransactionError::rollback_failed(format!(
"savepoint journal invariant violated: {}",
e
)));
}
}
{
let mut manager = self.savepoint_manager.write();
let target_sequence = savepoint_info.sequence;
let savepoints_to_remove: Vec<SavepointId> = manager
.savepoints
.iter()
.filter(|(_, sp)| sp.sequence > target_sequence)
.map(|(&id, _)| id)
.collect();
for sp_id in savepoints_to_remove {
manager.remove_savepoint(sp_id);
}
}
self.execute_undo_logs_from_index(target, savepoint_info.undo_log_index)
.map_err(|e| TransactionError::rollback_failed(e.to_string()))?;
if let Some(mark) = savepoint_info.staged_write_mark {
target
.rollback_staged_writes(self.id, mark)
.map_err(|e| TransactionError::rollback_failed(e.to_string()))?;
}
self.restore_write_set(savepoint_info.write_set);
self.restore_read_set(savepoint_info.read_set);
{
let mut tables = self.modified_tables.lock();
*tables = savepoint_info.modified_tables.clone();
}
{
let mut journal = self.mutation_journal.write();
if savepoint_info.journal_len > journal.len() {
return Err(TransactionError::rollback_failed(
"savepoint journal length exceeds current journal",
));
}
journal.truncate(savepoint_info.journal_len);
if cfg!(debug_assertions) {
if let Err(e) = journal.check_invariants() {
log::error!("journal invariant after savepoint rollback: {}", e);
}
}
}
self.rebuild_derived_logs();
{
let journal = self.mutation_journal.read();
self.mutation_count
.store(journal.len() as u64, Ordering::Relaxed);
let remaining = journal.len() as u64 * 64;
self.undo_bytes.store(remaining, Ordering::Relaxed);
}
Ok(())
}
pub fn add_undo_log(&self, log: UndoLogEntry) -> Result<(), TransactionError> {
let mut undo_logs = self.undo_logs.write();
undo_logs
.add(log)
.map_err(|error| TransactionError::internal(error.to_string()))
}
pub fn undo_log_len(&self) -> usize {
let undo_logs = self.undo_logs.read();
undo_logs.len()
}
pub fn clear_undo_logs(&self) -> Result<(), TransactionError> {
let mut undo_logs = self.undo_logs.write();
undo_logs
.clear()
.map_err(|error| TransactionError::internal(error.to_string()))
}
pub fn execute_undo_logs<T: UndoTarget + ?Sized>(
&self,
target: &T,
) -> Result<(), TransactionError> {
let mut undo_logs = self.undo_logs.write();
undo_logs
.execute_undo(target, self.start_timestamp)
.map_err(|e| TransactionError::rollback_failed(e.to_string()))
}
pub fn execute_undo_logs_from_index<T: UndoTarget + ?Sized>(
&self,
target: &T,
start_index: usize,
) -> Result<(), TransactionError> {
let mut undo_logs = self.undo_logs.write();
undo_logs
.execute_undo_from_index(target, self.start_timestamp, start_index)
.map_err(|e| TransactionError::rollback_failed(e.to_string()))
}
}