use super::{LogOp, LogRecord};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FileOperation {
Create,
Delete,
Rename,
IndexInsert,
IndexDelete,
AttributeCreate,
AttributeDelete,
Resize,
DataWrite,
BitmapAllocation,
TransactionControl,
TableDump,
Noop,
Unknown(u16, u16),
}
impl FileOperation {
#[must_use]
pub fn classify(record: &LogRecord) -> Self {
classify(record.redo_op, record.undo_op)
}
}
#[must_use]
pub fn classify(redo: LogOp, undo: LogOp) -> FileOperation {
use FileOperation as F;
use LogOp as O;
match redo {
O::InitializeFileRecordSegment => F::Create,
O::DeallocateFileRecordSegment => F::Delete,
O::UpdateFileNameRoot | O::UpdateFileNameAllocation => F::Rename,
O::AddIndexEntryRoot | O::AddIndexEntryAllocation => F::IndexInsert,
O::DeleteIndexEntryRoot | O::DeleteIndexEntryAllocation => F::IndexDelete,
O::CreateAttribute => F::AttributeCreate,
O::DeleteAttribute => F::AttributeDelete,
O::SetNewAttributeSizes => F::Resize,
O::UpdateResidentValue
| O::UpdateNonResidentValue
| O::UpdateMappingPairs
| O::WriteEndOfFileRecordSegment
| O::WriteEndOfIndexBuffer
| O::SetIndexEntryVcnRoot
| O::SetIndexEntryVcnAllocation
| O::UpdateRecordDataRoot
| O::UpdateRecordDataAllocation => F::DataWrite,
O::SetBitsInNonResidentBitMap
| O::ClearBitsInNonResidentBitMap
| O::DeleteDirtyClusters => F::BitmapAllocation,
O::EndTopLevelAction
| O::PrepareTransaction
| O::CommitTransaction
| O::ForgetTransaction
| O::CompensationLogRecord => F::TransactionControl,
O::HotFix
| O::OpenNonResidentAttribute
| O::OpenAttributeTableDump
| O::AttributeNamesDump
| O::DirtyPageTableDump
| O::TransactionTableDump => F::TableDump,
O::Noop => match undo {
O::Noop => F::Noop,
O::DeallocateFileRecordSegment => F::Delete,
O::DeleteIndexEntryRoot | O::DeleteIndexEntryAllocation => F::IndexDelete,
O::DeleteAttribute => F::AttributeDelete,
O::Unknown(u) => F::Unknown(0x00, u),
other => F::Unknown(0x00, other.code()),
},
O::Unknown(r) => F::Unknown(r, undo.code()),
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::logfile::RecordPage;
#[test]
fn initialize_file_record_segment_is_create() {
assert_eq!(
classify(LogOp::InitializeFileRecordSegment, LogOp::Noop),
FileOperation::Create
);
}
#[test]
fn deallocate_file_record_segment_is_delete() {
assert_eq!(
classify(LogOp::DeallocateFileRecordSegment, LogOp::Noop),
FileOperation::Delete
);
}
#[test]
fn noop_redo_dealloc_undo_is_delete() {
assert_eq!(
classify(LogOp::Noop, LogOp::DeallocateFileRecordSegment),
FileOperation::Delete
);
}
#[test]
fn noop_redo_delete_index_undo_is_index_delete() {
assert_eq!(
classify(LogOp::Noop, LogOp::DeleteIndexEntryRoot),
FileOperation::IndexDelete
);
assert_eq!(
classify(LogOp::Noop, LogOp::DeleteIndexEntryAllocation),
FileOperation::IndexDelete
);
}
#[test]
fn noop_redo_delete_attribute_undo_is_attribute_delete() {
assert_eq!(
classify(LogOp::Noop, LogOp::DeleteAttribute),
FileOperation::AttributeDelete
);
}
#[test]
fn update_file_name_is_rename() {
assert_eq!(
classify(LogOp::UpdateFileNameRoot, LogOp::UpdateFileNameRoot),
FileOperation::Rename
);
assert_eq!(
classify(
LogOp::UpdateFileNameAllocation,
LogOp::UpdateFileNameAllocation
),
FileOperation::Rename
);
}
#[test]
fn add_index_entry_is_index_insert() {
assert_eq!(
classify(LogOp::AddIndexEntryRoot, LogOp::DeleteIndexEntryRoot),
FileOperation::IndexInsert
);
assert_eq!(
classify(
LogOp::AddIndexEntryAllocation,
LogOp::DeleteIndexEntryAllocation
),
FileOperation::IndexInsert
);
}
#[test]
fn delete_index_entry_is_index_delete() {
assert_eq!(
classify(LogOp::DeleteIndexEntryRoot, LogOp::AddIndexEntryRoot),
FileOperation::IndexDelete
);
assert_eq!(
classify(
LogOp::DeleteIndexEntryAllocation,
LogOp::AddIndexEntryAllocation
),
FileOperation::IndexDelete
);
}
#[test]
fn create_and_delete_attribute() {
assert_eq!(
classify(LogOp::CreateAttribute, LogOp::DeleteAttribute),
FileOperation::AttributeCreate
);
assert_eq!(
classify(LogOp::DeleteAttribute, LogOp::CreateAttribute),
FileOperation::AttributeDelete
);
}
#[test]
fn set_new_attribute_sizes_is_resize() {
assert_eq!(
classify(LogOp::SetNewAttributeSizes, LogOp::SetNewAttributeSizes),
FileOperation::Resize
);
}
#[test]
fn value_and_mapping_updates_are_data_write() {
for redo in [
LogOp::UpdateResidentValue,
LogOp::UpdateNonResidentValue,
LogOp::UpdateMappingPairs,
LogOp::WriteEndOfFileRecordSegment,
LogOp::WriteEndOfIndexBuffer,
LogOp::SetIndexEntryVcnRoot,
LogOp::SetIndexEntryVcnAllocation,
LogOp::UpdateRecordDataRoot,
LogOp::UpdateRecordDataAllocation,
] {
assert_eq!(classify(redo, redo), FileOperation::DataWrite, "{redo:?}");
}
}
#[test]
fn bitmap_and_dirty_clusters_are_bitmap_allocation() {
assert_eq!(
classify(
LogOp::SetBitsInNonResidentBitMap,
LogOp::ClearBitsInNonResidentBitMap
),
FileOperation::BitmapAllocation
);
assert_eq!(
classify(
LogOp::ClearBitsInNonResidentBitMap,
LogOp::SetBitsInNonResidentBitMap
),
FileOperation::BitmapAllocation
);
assert_eq!(
classify(LogOp::DeleteDirtyClusters, LogOp::Noop),
FileOperation::BitmapAllocation
);
}
#[test]
fn transaction_boundaries_are_transaction_control() {
for redo in [
LogOp::EndTopLevelAction,
LogOp::PrepareTransaction,
LogOp::CommitTransaction,
LogOp::ForgetTransaction,
LogOp::CompensationLogRecord,
] {
assert_eq!(
classify(redo, LogOp::Noop),
FileOperation::TransactionControl,
"{redo:?}"
);
}
}
#[test]
fn dumps_and_hotfix_are_table_dump() {
for redo in [
LogOp::HotFix,
LogOp::OpenNonResidentAttribute,
LogOp::OpenAttributeTableDump,
LogOp::AttributeNamesDump,
LogOp::DirtyPageTableDump,
LogOp::TransactionTableDump,
] {
assert_eq!(
classify(redo, LogOp::Noop),
FileOperation::TableDump,
"{redo:?}"
);
}
}
#[test]
fn noop_pair_is_noop() {
assert_eq!(classify(LogOp::Noop, LogOp::Noop), FileOperation::Noop);
}
#[test]
fn unknown_redo_surfaces_both_raw_codes() {
assert_eq!(
classify(LogOp::Unknown(0x40), LogOp::CommitTransaction),
FileOperation::Unknown(0x40, 0x1A)
);
}
#[test]
fn noop_redo_unknown_undo_surfaces_codes() {
assert_eq!(
classify(LogOp::Noop, LogOp::Unknown(0x55)),
FileOperation::Unknown(0x00, 0x55)
);
}
#[test]
fn noop_redo_unmapped_known_undo_surfaces_codes() {
assert_eq!(
classify(LogOp::Noop, LogOp::UpdateResidentValue),
FileOperation::Unknown(0x00, 0x07)
);
}
#[test]
fn classify_over_log_record() {
let rec = LogRecord {
page_offset: 0x40,
this_lsn: 1,
client_previous_lsn: 0,
client_undo_next_lsn: 0,
record_type: 1,
transaction_id: 0,
redo_op: LogOp::InitializeFileRecordSegment,
undo_op: LogOp::Noop,
target_attribute: 0,
mft_cluster_index: 0,
target_vcn: 0,
};
assert_eq!(FileOperation::classify(&rec), FileOperation::Create);
let _ = RecordPage {
offset: 0,
last_lsn: 1,
data: vec![],
};
}
}