BREAKING CHANGE(core): replace the TypeScript database engine with a Rust-backed embedded server and bridge
This commit is contained in:
@@ -0,0 +1,120 @@
|
||||
//! 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()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user