Files
smartdb/rust/crates/rustdb-storage/src/oplog.rs
T

121 lines
3.1 KiB
Rust
Raw Normal View History

//! Operation log (OpLog) for tracking mutations.
//!
//! The OpLog records every write operation so that changes can be replayed,
//! replicated, or used for change-stream style notifications.
use std::sync::atomic::{AtomicU64, Ordering};
use std::time::{SystemTime, UNIX_EPOCH};
use bson::Document;
use dashmap::DashMap;
use serde::{Deserialize, Serialize};
/// The type of operation recorded in the oplog.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub enum OpType {
Insert,
Update,
Delete,
}
/// A single oplog entry.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OpLogEntry {
/// Monotonically increasing sequence number.
pub seq: u64,
/// Timestamp in milliseconds since UNIX epoch.
pub timestamp_ms: i64,
/// Operation type.
pub op: OpType,
/// Database name.
pub db: String,
/// Collection name.
pub collection: String,
/// Document id (hex string).
pub document_id: String,
/// The document snapshot (for insert/update; None for delete).
pub document: Option<Document>,
}
/// In-memory operation log.
pub struct OpLog {
/// All entries keyed by sequence number.
entries: DashMap<u64, OpLogEntry>,
/// Next sequence number.
next_seq: AtomicU64,
}
impl OpLog {
pub fn new() -> Self {
Self {
entries: DashMap::new(),
next_seq: AtomicU64::new(1),
}
}
/// Append an operation to the log and return its sequence number.
pub fn append(
&self,
op: OpType,
db: &str,
collection: &str,
document_id: &str,
document: Option<Document>,
) -> u64 {
let seq = self.next_seq.fetch_add(1, Ordering::SeqCst);
let entry = OpLogEntry {
seq,
timestamp_ms: SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_millis() as i64,
op,
db: db.to_string(),
collection: collection.to_string(),
document_id: document_id.to_string(),
document,
};
self.entries.insert(seq, entry);
seq
}
/// Get all entries with sequence number >= `since`.
pub fn entries_since(&self, since: u64) -> Vec<OpLogEntry> {
let mut result: Vec<_> = self
.entries
.iter()
.filter(|e| *e.key() >= since)
.map(|e| e.value().clone())
.collect();
result.sort_by_key(|e| e.seq);
result
}
/// Get the current (latest) sequence number. Returns 0 if empty.
pub fn current_seq(&self) -> u64 {
self.next_seq.load(Ordering::SeqCst).saturating_sub(1)
}
/// Clear all entries.
pub fn clear(&self) {
self.entries.clear();
self.next_seq.store(1, Ordering::SeqCst);
}
/// Number of entries in the log.
pub fn len(&self) -> usize {
self.entries.len()
}
/// Whether the log is empty.
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
}
impl Default for OpLog {
fn default() -> Self {
Self::new()
}
}