1use crate::l2_cache::{InvalidationBus, InvalidationMessage};
13use crate::value::Value;
14use std::collections::{HashMap, HashSet, VecDeque};
15use std::path::PathBuf;
16use std::sync::atomic::{AtomicU64, Ordering};
17use std::sync::Arc;
18use std::time::Duration;
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
27pub enum ConsistencyLevel {
28 #[default]
30 Eventual,
31 Strong,
33}
34
35pub struct RedisPubSubInvalidationBus {
42 client: Option<redis::aio::ConnectionManager>,
44 channel: String,
46 local_buffer: parking_lot::Mutex<VecDeque<InvalidationMessage>>,
48 instance_id: String,
50}
51
52impl RedisPubSubInvalidationBus {
53 pub fn new(client: redis::aio::ConnectionManager, instance_id: impl Into<String>) -> Self {
55 Self {
56 client: Some(client),
57 channel: "sz-orm:invalidation".to_string(),
58 local_buffer: parking_lot::Mutex::new(VecDeque::new()),
59 instance_id: instance_id.into(),
60 }
61 }
62
63 pub fn disconnected(instance_id: impl Into<String>) -> Self {
65 Self {
66 client: None,
67 channel: "sz-orm:invalidation".to_string(),
68 local_buffer: parking_lot::Mutex::new(VecDeque::new()),
69 instance_id: instance_id.into(),
70 }
71 }
72
73 pub fn with_channel(mut self, channel: impl Into<String>) -> Self {
75 self.channel = channel.into();
76 self
77 }
78
79 pub fn instance_id(&self) -> &str {
81 &self.instance_id
82 }
83
84 fn serialize_message(message: &InvalidationMessage, instance_id: &str) -> String {
86 let payload = match message {
87 InvalidationMessage::InvalidateKey(key) => {
88 serde_json::json!({"type": "key", "key": key, "src": instance_id})
89 }
90 InvalidationMessage::InvalidateTable(table) => {
91 serde_json::json!({"type": "table", "table": table, "src": instance_id})
92 }
93 InvalidationMessage::InvalidateAll => {
94 serde_json::json!({"type": "all", "src": instance_id})
95 }
96 };
97 payload.to_string()
98 }
99
100 #[allow(dead_code)]
102 fn deserialize_message(json: &str, self_instance_id: &str) -> Option<InvalidationMessage> {
103 let v: serde_json::Value = serde_json::from_str(json).ok()?;
104 let src = v.get("src")?.as_str()?;
105 if src == self_instance_id {
107 return None;
108 }
109 match v.get("type")?.as_str()? {
110 "key" => {
111 let key = v.get("key")?.as_str()?;
112 Some(InvalidationMessage::InvalidateKey(key.to_string()))
113 }
114 "table" => {
115 let table = v.get("table")?.as_str()?;
116 Some(InvalidationMessage::InvalidateTable(table.to_string()))
117 }
118 "all" => Some(InvalidationMessage::InvalidateAll),
119 _ => None,
120 }
121 }
122
123 pub fn push_received(&self, message: InvalidationMessage) {
125 self.local_buffer.lock().push_back(message);
126 }
127}
128
129impl InvalidationBus for RedisPubSubInvalidationBus {
130 fn publish(&self, message: InvalidationMessage) {
131 if let Some(client) = &self.client {
132 let json = Self::serialize_message(&message, &self.instance_id);
133 let client = client.clone();
135 let channel = self.channel.clone();
136 tokio::spawn(async move {
137 let _: Result<(), _> = redis::cmd("PUBLISH")
138 .arg(&channel)
139 .arg(&json)
140 .query_async(&mut client.clone())
141 .await;
142 });
143 }
144 }
146
147 fn subscribe(&self) -> Box<dyn Iterator<Item = InvalidationMessage> + Send> {
148 let mut buffer = self.local_buffer.lock();
149 let drained: Vec<_> = buffer.drain(..).collect();
150 Box::new(drained.into_iter())
151 }
152}
153
154#[derive(Debug, Clone)]
158pub struct NodeAddr {
159 pub host: String,
161 pub port: u16,
163}
164
165impl NodeAddr {
166 pub fn new(host: impl Into<String>, port: u16) -> Self {
168 Self {
169 host: host.into(),
170 port,
171 }
172 }
173}
174
175pub struct GossipInvalidationBus {
180 #[allow(dead_code)]
182 nodes: Vec<NodeAddr>,
183 shared_secret: Vec<u8>,
185 local_buffer: parking_lot::Mutex<VecDeque<InvalidationMessage>>,
187 seen_messages: parking_lot::RwLock<HashSet<u64>>,
189 instance_id: String,
191 sequence: AtomicU64,
193}
194
195impl GossipInvalidationBus {
196 pub fn new(
198 nodes: Vec<NodeAddr>,
199 shared_secret: Vec<u8>,
200 instance_id: impl Into<String>,
201 ) -> Self {
202 Self {
203 nodes,
204 shared_secret,
205 local_buffer: parking_lot::Mutex::new(VecDeque::new()),
206 seen_messages: parking_lot::RwLock::new(HashSet::new()),
207 instance_id: instance_id.into(),
208 sequence: AtomicU64::new(0),
209 }
210 }
211
212 pub fn instance_id(&self) -> &str {
214 &self.instance_id
215 }
216
217 fn message_id(&self) -> u64 {
219 self.sequence.fetch_add(1, Ordering::SeqCst)
220 }
221
222 fn compute_hmac(&self, message: &InvalidationMessage) -> Vec<u8> {
224 let msg_bytes = format!("{:?}", message);
225 sz_orm_crypto::hmac_sha256(&self.shared_secret, msg_bytes.as_bytes()).to_vec()
226 }
227
228 fn verify_hmac(&self, message: &InvalidationMessage, tag: &[u8]) -> bool {
230 let expected = self.compute_hmac(message);
231 expected == tag
232 }
233
234 pub fn receive(&self, message: InvalidationMessage, msg_id: u64, hmac_tag: &[u8]) -> bool {
236 if !self.verify_hmac(&message, hmac_tag) {
238 return false;
239 }
240 let mut seen = self.seen_messages.write();
242 if !seen.insert(msg_id) {
243 return false; }
245 drop(seen);
246 self.local_buffer.lock().push_back(message);
248 true
249 }
250}
251
252impl InvalidationBus for GossipInvalidationBus {
253 fn publish(&self, message: InvalidationMessage) {
254 let msg_id = self.message_id();
255 let _hmac_tag = self.compute_hmac(&message);
256 let mut seen = self.seen_messages.write();
259 seen.insert(msg_id);
260 drop(seen);
261 self.local_buffer.lock().push_back(message);
262 }
263
264 fn subscribe(&self) -> Box<dyn Iterator<Item = InvalidationMessage> + Send> {
265 let mut buffer = self.local_buffer.lock();
266 let drained: Vec<_> = buffer.drain(..).collect();
267 Box::new(drained.into_iter())
268 }
269}
270
271#[derive(Debug, Clone)]
275pub struct WriteBehindConfig {
276 pub batch_size: u32,
278 pub flush_interval: Duration,
280 pub wal_path: PathBuf,
282 pub encryption_key: Vec<u8>,
284 pub fallback_to_sync: bool,
286}
287
288impl Default for WriteBehindConfig {
289 fn default() -> Self {
290 Self {
291 batch_size: 100,
292 flush_interval: Duration::from_millis(100),
293 wal_path: PathBuf::from("wal/sz-orm-wal.log"),
294 encryption_key: Vec::new(),
295 fallback_to_sync: true,
296 }
297 }
298}
299
300impl WriteBehindConfig {
301 pub fn builder() -> WriteBehindConfigBuilder {
303 WriteBehindConfigBuilder::default()
304 }
305}
306
307#[derive(Debug, Clone, Default)]
309pub struct WriteBehindConfigBuilder {
310 batch_size: Option<u32>,
311 flush_interval: Option<Duration>,
312 wal_path: Option<PathBuf>,
313 encryption_key: Option<Vec<u8>>,
314 fallback_to_sync: Option<bool>,
315}
316
317impl WriteBehindConfigBuilder {
318 pub fn batch_size(mut self, size: u32) -> Self {
319 self.batch_size = Some(size);
320 self
321 }
322 pub fn flush_interval(mut self, interval: Duration) -> Self {
323 self.flush_interval = Some(interval);
324 self
325 }
326 pub fn wal_path(mut self, path: PathBuf) -> Self {
327 self.wal_path = Some(path);
328 self
329 }
330 pub fn encryption_key(mut self, key: Vec<u8>) -> Self {
331 self.encryption_key = Some(key);
332 self
333 }
334 pub fn fallback_to_sync(mut self, fallback: bool) -> Self {
335 self.fallback_to_sync = Some(fallback);
336 self
337 }
338 pub fn build(self) -> WriteBehindConfig {
339 WriteBehindConfig {
340 batch_size: self.batch_size.unwrap_or(100),
341 flush_interval: self
342 .flush_interval
343 .unwrap_or_else(|| Duration::from_millis(100)),
344 wal_path: self
345 .wal_path
346 .unwrap_or_else(|| PathBuf::from("wal/sz-orm-wal.log")),
347 encryption_key: self.encryption_key.unwrap_or_default(),
348 fallback_to_sync: self.fallback_to_sync.unwrap_or(true),
349 }
350 }
351}
352
353#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
355pub enum WriteOpType {
356 Insert,
357 Update,
358 Delete,
359}
360
361#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
363pub struct WriteOp {
364 pub op_type: WriteOpType,
366 pub table: String,
368 pub pk: Value,
370 pub data: Vec<(String, Value)>,
372 pub timestamp: i64,
374 pub sequence: u64,
376}
377
378impl WriteOp {
379 pub fn new(op_type: WriteOpType, table: impl Into<String>, pk: Value) -> Self {
381 Self {
382 op_type,
383 table: table.into(),
384 pk,
385 data: Vec::new(),
386 timestamp: chrono::Utc::now().timestamp(),
387 sequence: 0,
388 }
389 }
390
391 pub fn with_data(mut self, data: Vec<(String, Value)>) -> Self {
393 self.data = data;
394 self
395 }
396}
397
398pub struct WriteBehindQueue {
403 wal: parking_lot::Mutex<WalFile>,
405 pending: crossbeam_queue::ArrayQueue<WriteOp>,
407 sequence: AtomicU64,
409 config: WriteBehindConfig,
411}
412
413impl WriteBehindQueue {
414 pub fn new(config: WriteBehindConfig) -> std::io::Result<Self> {
416 let wal = WalFile::open(&config.wal_path, &config.encryption_key)?;
417 let capacity = (config.batch_size * 10) as usize;
418 Ok(Self {
419 wal: parking_lot::Mutex::new(wal),
420 pending: crossbeam_queue::ArrayQueue::new(capacity.max(1024)),
421 sequence: AtomicU64::new(0),
422 config,
423 })
424 }
425
426 pub fn enqueue(&self, mut op: WriteOp) -> std::io::Result<()> {
430 op.sequence = self.sequence.fetch_add(1, Ordering::SeqCst);
431 self.wal.lock().append(&op)?;
433 let _ = self.pending.push(op);
435 Ok(())
437 }
438
439 pub fn drain_batch(&self) -> Vec<WriteOp> {
441 let batch_size = self.config.batch_size as usize;
442 let mut batch = Vec::with_capacity(batch_size);
443 for _ in 0..batch_size {
444 match self.pending.pop() {
445 Some(op) => batch.push(op),
446 None => break,
447 }
448 }
449 batch.sort_by_key(|op| op.sequence);
451 batch
452 }
453
454 pub fn truncate_wal(&self) -> std::io::Result<()> {
456 self.wal.lock().truncate()
457 }
458
459 pub fn replay(&self) -> std::io::Result<Vec<WriteOp>> {
463 self.wal.lock().read_all()
464 }
465
466 pub fn config(&self) -> &WriteBehindConfig {
468 &self.config
469 }
470
471 pub fn pending_count(&self) -> usize {
473 self.pending.len()
474 }
475}
476
477struct WalFile {
483 path: PathBuf,
484 encryption_key: Vec<u8>,
485}
486
487impl WalFile {
488 fn open(path: &std::path::Path, encryption_key: &[u8]) -> std::io::Result<Self> {
489 if let Some(parent) = path.parent() {
490 std::fs::create_dir_all(parent)?;
491 }
492 Ok(Self {
493 path: path.to_path_buf(),
494 encryption_key: encryption_key.to_vec(),
495 })
496 }
497
498 fn append(&mut self, op: &WriteOp) -> std::io::Result<()> {
499 let json = serde_json::to_string(op)
500 .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e.to_string()))?;
501 let payload = json.as_bytes();
502
503 let encrypted = if self.encryption_key.is_empty() {
504 payload.to_vec()
505 } else {
506 self.encrypt(payload)
507 };
508
509 let crc = crc64(&encrypted);
510
511 let mut record = Vec::with_capacity(4 + encrypted.len() + 8);
512 record.extend_from_slice(&(encrypted.len() as u32).to_le_bytes());
513 record.extend_from_slice(&encrypted);
514 record.extend_from_slice(&crc.to_le_bytes());
515
516 let mut file = std::fs::OpenOptions::new()
517 .create(true)
518 .append(true)
519 .open(&self.path)?;
520 use std::io::Write;
521 file.write_all(&record)?;
522 file.flush()?;
523 Ok(())
524 }
525
526 fn read_all(&self) -> std::io::Result<Vec<WriteOp>> {
527 let data = match std::fs::read(&self.path) {
528 Ok(d) => d,
529 Err(e) if e.kind() == std::io::ErrorKind::NotFound => return Ok(Vec::new()),
530 Err(e) => return Err(e),
531 };
532 let mut ops = Vec::new();
533 let mut pos = 0;
534 while pos + 4 <= data.len() {
535 let len = u32::from_le_bytes([data[pos], data[pos + 1], data[pos + 2], data[pos + 3]])
536 as usize;
537 pos += 4;
538 if pos + len + 8 > data.len() {
539 break;
540 }
541 let encrypted = &data[pos..pos + len];
542 pos += len;
543 let expected_crc = u64::from_le_bytes([
544 data[pos],
545 data[pos + 1],
546 data[pos + 2],
547 data[pos + 3],
548 data[pos + 4],
549 data[pos + 5],
550 data[pos + 6],
551 data[pos + 7],
552 ]);
553 pos += 8;
554
555 if crc64(encrypted) != expected_crc {
556 continue;
557 }
558
559 let decrypted = if self.encryption_key.is_empty() {
560 encrypted.to_vec()
561 } else {
562 self.decrypt(encrypted)
563 };
564
565 if let Ok(op) = serde_json::from_slice::<WriteOp>(&decrypted) {
566 ops.push(op);
567 }
568 }
569 ops.sort_by_key(|op| op.sequence);
570 Ok(ops)
571 }
572
573 fn truncate(&mut self) -> std::io::Result<()> {
574 std::fs::write(&self.path, b"")?;
575 Ok(())
576 }
577
578 fn encrypt(&self, data: &[u8]) -> Vec<u8> {
579 let crypter = sz_orm_crypto::AesGcmCrypter::from_key_str(
580 std::str::from_utf8(&self.encryption_key).unwrap_or("default-key"),
581 );
582 crypter
583 .encrypt_with_aad(data, &[])
584 .unwrap_or_else(|_| data.to_vec())
585 }
586
587 fn decrypt(&self, data: &[u8]) -> Vec<u8> {
588 let crypter = sz_orm_crypto::AesGcmCrypter::from_key_str(
589 std::str::from_utf8(&self.encryption_key).unwrap_or("default-key"),
590 );
591 crypter
592 .decrypt_with_aad(data, &[])
593 .unwrap_or_else(|_| data.to_vec())
594 }
595}
596
597fn crc64(data: &[u8]) -> u64 {
598 let mut crc: u64 = 0;
599 for &byte in data {
600 crc ^= byte as u64;
601 for _ in 0..8 {
602 if crc & 1 != 0 {
603 crc = (crc >> 1) ^ 0xC96E_8607_EAFC_E6CD;
604 } else {
605 crc >>= 1;
606 }
607 }
608 }
609 crc
610}
611
612pub struct BloomFilterGuard {
618 filter: parking_lot::RwLock<bloomfilter::Bloom<String>>,
619 capacity: usize,
620 false_positive_rate: f64,
621 count: AtomicU64,
622}
623
624impl BloomFilterGuard {
625 pub fn new(capacity: usize, false_positive_rate: f64) -> Self {
627 let filter = bloomfilter::Bloom::new_for_fp_rate(capacity, false_positive_rate);
628 Self {
629 filter: parking_lot::RwLock::new(filter),
630 capacity,
631 false_positive_rate,
632 count: AtomicU64::new(0),
633 }
634 }
635
636 pub fn default_config() -> Self {
638 Self::new(100_000, 0.01)
639 }
640
641 pub fn add(&self, key: &str) {
643 self.filter.write().set(&key.to_string());
644 self.count.fetch_add(1, Ordering::Relaxed);
645 }
646
647 pub fn might_contain(&self, key: &str) -> bool {
649 self.filter.read().check(&key.to_string())
650 }
651
652 pub fn rebuild(&self, keys: impl Iterator<Item = String>) {
654 let mut filter =
655 bloomfilter::Bloom::new_for_fp_rate(self.capacity, self.false_positive_rate);
656 for key in keys {
657 filter.set(&key);
658 self.count.fetch_add(1, Ordering::Relaxed);
659 }
660 *self.filter.write() = filter;
661 }
662
663 pub fn count(&self) -> u64 {
665 self.count.load(Ordering::Relaxed)
666 }
667}
668
669pub struct CacheMutexGuard {
675 mutexes: parking_lot::Mutex<HashMap<String, Arc<tokio::sync::Mutex<()>>>>,
676}
677
678impl CacheMutexGuard {
679 pub fn new() -> Self {
681 Self {
682 mutexes: parking_lot::Mutex::new(HashMap::new()),
683 }
684 }
685
686 pub fn get_mutex(&self, key: &str) -> Arc<tokio::sync::Mutex<()>> {
688 let mut map = self.mutexes.lock();
689 map.entry(key.to_string())
690 .or_insert_with(|| Arc::new(tokio::sync::Mutex::new(())))
691 .clone()
692 }
693
694 pub async fn with_guard<F, R>(&self, key: &str, f: F) -> R
696 where
697 F: std::future::Future<Output = R>,
698 {
699 let mutex = self.get_mutex(key);
700 let _guard = mutex.lock().await;
701 f.await
702 }
703}
704
705impl Default for CacheMutexGuard {
706 fn default() -> Self {
707 Self::new()
708 }
709}
710
711pub struct RandomTtlJitter;
717
718impl RandomTtlJitter {
719 pub fn jitter(base_ttl: Duration, jitter_range: f64) -> Duration {
724 use rand::Rng;
725 let mut rng = rand::thread_rng();
726 let random: f64 = rng.gen_range(-1.0..=1.0);
727 let factor = 1.0 + jitter_range * random;
728 let jittered_ms = (base_ttl.as_millis() as f64 * factor) as u64;
729 Duration::from_millis(jittered_ms.max(1))
730 }
731
732 pub fn default_jitter(base_ttl: Duration) -> Duration {
734 Self::jitter(base_ttl, 0.2)
735 }
736}
737
738#[cfg(test)]
739mod tests {
740 use super::*;
741
742 #[test]
745 fn test_redis_pubsub_serialize_message_key() {
746 let msg = InvalidationMessage::InvalidateKey("user:42".to_string());
747 let json = RedisPubSubInvalidationBus::serialize_message(&msg, "instance-1");
748 assert!(json.contains("\"type\":\"key\""));
749 assert!(json.contains("\"key\":\"user:42\""));
750 assert!(json.contains("\"src\":\"instance-1\""));
751 assert!(json.len() <= 1024, "消息应 ≤1KB: {} bytes", json.len());
752 }
753
754 #[test]
755 fn test_redis_pubsub_serialize_message_table() {
756 let msg = InvalidationMessage::InvalidateTable("users".to_string());
757 let json = RedisPubSubInvalidationBus::serialize_message(&msg, "instance-1");
758 assert!(json.contains("\"type\":\"table\""));
759 assert!(json.contains("\"table\":\"users\""));
760 assert!(json.len() <= 1024);
761 }
762
763 #[test]
764 fn test_redis_pubsub_serialize_message_all() {
765 let msg = InvalidationMessage::InvalidateAll;
766 let json = RedisPubSubInvalidationBus::serialize_message(&msg, "instance-1");
767 assert!(json.contains("\"type\":\"all\""));
768 assert!(json.len() <= 1024);
769 }
770
771 #[test]
772 fn test_redis_pubsub_deserialize_skips_self() {
773 let msg = InvalidationMessage::InvalidateKey("user:42".to_string());
774 let json = RedisPubSubInvalidationBus::serialize_message(&msg, "instance-1");
775 let result = RedisPubSubInvalidationBus::deserialize_message(&json, "instance-1");
777 assert!(result.is_none(), "应跳过自回环");
778 }
779
780 #[test]
781 fn test_redis_pubsub_deserialize_other_instance() {
782 let msg = InvalidationMessage::InvalidateKey("user:42".to_string());
783 let json = RedisPubSubInvalidationBus::serialize_message(&msg, "instance-1");
784 let result = RedisPubSubInvalidationBus::deserialize_message(&json, "instance-2");
786 assert!(result.is_some(), "应接收其他实例消息");
787 }
788
789 #[test]
790 fn test_redis_pubsub_disconnected_publish() {
791 let bus = RedisPubSubInvalidationBus::disconnected("instance-1");
792 bus.publish(InvalidationMessage::InvalidateAll);
794 }
795
796 #[test]
797 fn test_redis_pubsub_subscribe_drain() {
798 let bus = RedisPubSubInvalidationBus::disconnected("instance-1");
799 bus.push_received(InvalidationMessage::InvalidateTable("users".to_string()));
800 bus.push_received(InvalidationMessage::InvalidateAll);
801 let messages: Vec<_> = bus.subscribe().collect();
802 assert_eq!(messages.len(), 2);
803 let messages2: Vec<_> = bus.subscribe().collect();
805 assert_eq!(messages2.len(), 0);
806 }
807
808 #[test]
811 fn test_gossip_publish_and_subscribe() {
812 let bus = GossipInvalidationBus::new(
813 vec![NodeAddr::new("127.0.0.1", 8080)],
814 b"secret-key".to_vec(),
815 "instance-1",
816 );
817 bus.publish(InvalidationMessage::InvalidateTable("users".to_string()));
818 bus.publish(InvalidationMessage::InvalidateAll);
819 let messages: Vec<_> = bus.subscribe().collect();
820 assert_eq!(messages.len(), 2);
821 }
822
823 #[test]
824 fn test_gossip_hmac_authentication() {
825 let bus = GossipInvalidationBus::new(
826 vec![NodeAddr::new("127.0.0.1", 8080)],
827 b"secret-key".to_vec(),
828 "instance-1",
829 );
830 let msg = InvalidationMessage::InvalidateKey("user:42".to_string());
831 let tag = bus.compute_hmac(&msg);
832 assert!(bus.verify_hmac(&msg, &tag));
833 assert!(!bus.verify_hmac(&msg, &[0u8; 32]));
835 }
836
837 #[test]
838 fn test_gossip_receive_dedup() {
839 let bus = GossipInvalidationBus::new(
840 vec![NodeAddr::new("127.0.0.1", 8080)],
841 b"secret-key".to_vec(),
842 "instance-1",
843 );
844 let msg = InvalidationMessage::InvalidateKey("user:42".to_string());
845 let tag = bus.compute_hmac(&msg);
846 assert!(bus.receive(msg.clone(), 1, &tag));
848 assert!(!bus.receive(msg, 1, &tag));
850 }
851
852 #[test]
853 fn test_gossip_receive_unauthenticated() {
854 let bus = GossipInvalidationBus::new(
855 vec![NodeAddr::new("127.0.0.1", 8080)],
856 b"secret-key".to_vec(),
857 "instance-1",
858 );
859 let msg = InvalidationMessage::InvalidateKey("user:42".to_string());
860 assert!(!bus.receive(msg, 1, &[0u8; 32]));
862 }
863
864 #[test]
867 fn test_write_behind_config_default() {
868 let config = WriteBehindConfig::default();
869 assert_eq!(config.batch_size, 100);
870 assert_eq!(config.flush_interval, Duration::from_millis(100));
871 assert!(config.fallback_to_sync);
872 }
873
874 #[test]
875 fn test_write_behind_config_builder() {
876 let config = WriteBehindConfig::builder()
877 .batch_size(50)
878 .flush_interval(Duration::from_millis(200))
879 .fallback_to_sync(false)
880 .build();
881 assert_eq!(config.batch_size, 50);
882 assert_eq!(config.flush_interval, Duration::from_millis(200));
883 assert!(!config.fallback_to_sync);
884 }
885
886 #[test]
887 fn test_write_op_new() {
888 let op = WriteOp::new(WriteOpType::Insert, "users", Value::I64(42));
889 assert_eq!(op.op_type, WriteOpType::Insert);
890 assert_eq!(op.table, "users");
891 assert_eq!(op.pk, Value::I64(42));
892 assert_eq!(op.sequence, 0);
893 }
894
895 #[test]
896 fn test_write_behind_queue_enqueue_and_drain() {
897 let temp_dir = std::env::temp_dir().join("sz-orm-test-wal");
898 let _ = std::fs::remove_dir_all(&temp_dir);
899 let config = WriteBehindConfig::builder()
900 .batch_size(10)
901 .wal_path(temp_dir.join("test.log"))
902 .build();
903 let queue = WriteBehindQueue::new(config).unwrap();
904
905 for i in 0..3 {
907 let op = WriteOp::new(WriteOpType::Update, "users", Value::I64(i));
908 queue.enqueue(op).unwrap();
909 }
910
911 assert_eq!(queue.pending_count(), 3);
912
913 let batch = queue.drain_batch();
915 assert_eq!(batch.len(), 3);
916 assert!(batch.windows(2).all(|w| w[0].sequence <= w[1].sequence));
918
919 let _ = std::fs::remove_dir_all(&temp_dir);
921 }
922
923 #[test]
924 fn test_write_behind_queue_replay() {
925 let temp_dir = std::env::temp_dir().join("sz-orm-test-wal-replay");
926 let _ = std::fs::remove_dir_all(&temp_dir);
927 let config = WriteBehindConfig::builder()
928 .batch_size(10)
929 .wal_path(temp_dir.join("test.log"))
930 .build();
931 let queue = WriteBehindQueue::new(config).unwrap();
932
933 for i in 0..5 {
934 let op = WriteOp::new(WriteOpType::Insert, "orders", Value::I64(i)).with_data(vec![(
935 "status".to_string(),
936 Value::String("pending".to_string()),
937 )]);
938 queue.enqueue(op).unwrap();
939 }
940
941 let replayed = queue.replay().unwrap();
943 assert_eq!(replayed.len(), 5);
944 assert!(replayed.windows(2).all(|w| w[0].sequence <= w[1].sequence));
946
947 let _ = std::fs::remove_dir_all(&temp_dir);
949 }
950
951 #[test]
954 fn test_bloom_filter_basic() {
955 let guard = BloomFilterGuard::new(1000, 0.01);
956 guard.add("user:1");
957 guard.add("user:2");
958 assert!(guard.might_contain("user:1"));
959 assert!(guard.might_contain("user:2"));
960 assert_eq!(guard.count(), 2);
961 }
962
963 #[test]
964 fn test_bloom_filter_false_positive_rate() {
965 let guard = BloomFilterGuard::new(10_000, 0.01);
966 for i in 0..1000 {
967 guard.add(&format!("user:{}", i));
968 }
969 let mut false_positives = 0;
970 for i in 1000..2000 {
971 if guard.might_contain(&format!("user:{}", i)) {
972 false_positives += 1;
973 }
974 }
975 let fp_rate = false_positives as f64 / 1000.0;
976 assert!(fp_rate < 0.05, "假阳性率应 < 5%(实际 {})", fp_rate);
977 }
978
979 #[tokio::test]
980 async fn test_cache_mutex_guard() {
981 let guard = CacheMutexGuard::new();
982 guard
983 .with_guard("user:42", async {
984 })
986 .await;
987 guard
988 .with_guard("user:43", async {
989 })
991 .await;
992 }
993
994 #[test]
995 fn test_random_ttl_jitter_range() {
996 let base = Duration::from_millis(1000);
997 for _ in 0..100 {
998 let jittered = RandomTtlJitter::default_jitter(base);
999 let ms = jittered.as_millis();
1000 assert!(
1002 (800..=1200).contains(&ms),
1003 "TTL 抖动应在 ±20% 范围内: {}ms",
1004 ms
1005 );
1006 }
1007 }
1008
1009 #[test]
1010 fn test_consistency_level_default() {
1011 assert_eq!(ConsistencyLevel::default(), ConsistencyLevel::Eventual);
1012 }
1013
1014 #[test]
1015 fn test_crc64() {
1016 let crc1 = crc64(b"hello");
1017 let crc2 = crc64(b"hello");
1018 assert_eq!(crc1, crc2);
1019 let crc3 = crc64(b"world");
1020 assert_ne!(crc1, crc3);
1021 }
1022}