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 {
320 self.batch_size = Some(size);
321 self
322 }
323 pub fn flush_interval(mut self, interval: Duration) -> Self {
325 self.flush_interval = Some(interval);
326 self
327 }
328 pub fn wal_path(mut self, path: PathBuf) -> Self {
330 self.wal_path = Some(path);
331 self
332 }
333 pub fn encryption_key(mut self, key: Vec<u8>) -> Self {
335 self.encryption_key = Some(key);
336 self
337 }
338 pub fn fallback_to_sync(mut self, fallback: bool) -> Self {
340 self.fallback_to_sync = Some(fallback);
341 self
342 }
343 pub fn build(self) -> WriteBehindConfig {
345 WriteBehindConfig {
346 batch_size: self.batch_size.unwrap_or(100),
347 flush_interval: self
348 .flush_interval
349 .unwrap_or_else(|| Duration::from_millis(100)),
350 wal_path: self
351 .wal_path
352 .unwrap_or_else(|| PathBuf::from("wal/sz-orm-wal.log")),
353 encryption_key: self.encryption_key.unwrap_or_default(),
354 fallback_to_sync: self.fallback_to_sync.unwrap_or(true),
355 }
356 }
357}
358
359#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
361pub enum WriteOpType {
362 Insert,
364 Update,
366 Delete,
368}
369
370#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
372pub struct WriteOp {
373 pub op_type: WriteOpType,
375 pub table: String,
377 pub pk: Value,
379 pub data: Vec<(String, Value)>,
381 pub timestamp: i64,
383 pub sequence: u64,
385}
386
387impl WriteOp {
388 pub fn new(op_type: WriteOpType, table: impl Into<String>, pk: Value) -> Self {
390 Self {
391 op_type,
392 table: table.into(),
393 pk,
394 data: Vec::new(),
395 timestamp: chrono::Utc::now().timestamp(),
396 sequence: 0,
397 }
398 }
399
400 pub fn with_data(mut self, data: Vec<(String, Value)>) -> Self {
402 self.data = data;
403 self
404 }
405}
406
407pub struct WriteBehindQueue {
412 wal: parking_lot::Mutex<WalFile>,
414 pending: crossbeam_queue::ArrayQueue<WriteOp>,
416 sequence: AtomicU64,
418 config: WriteBehindConfig,
420}
421
422impl WriteBehindQueue {
423 pub fn new(config: WriteBehindConfig) -> std::io::Result<Self> {
425 let wal = WalFile::open(&config.wal_path, &config.encryption_key)?;
426 let capacity = (config.batch_size * 10) as usize;
427 Ok(Self {
428 wal: parking_lot::Mutex::new(wal),
429 pending: crossbeam_queue::ArrayQueue::new(capacity.max(1024)),
430 sequence: AtomicU64::new(0),
431 config,
432 })
433 }
434
435 pub fn enqueue(&self, mut op: WriteOp) -> std::io::Result<()> {
439 op.sequence = self.sequence.fetch_add(1, Ordering::SeqCst);
440 self.wal.lock().append(&op)?;
442 let _ = self.pending.push(op);
444 Ok(())
446 }
447
448 pub fn drain_batch(&self) -> Vec<WriteOp> {
450 let batch_size = self.config.batch_size as usize;
451 let mut batch = Vec::with_capacity(batch_size);
452 for _ in 0..batch_size {
453 match self.pending.pop() {
454 Some(op) => batch.push(op),
455 None => break,
456 }
457 }
458 batch.sort_by_key(|op| op.sequence);
460 batch
461 }
462
463 pub fn truncate_wal(&self) -> std::io::Result<()> {
465 self.wal.lock().truncate()
466 }
467
468 pub fn replay(&self) -> std::io::Result<Vec<WriteOp>> {
472 self.wal.lock().read_all()
473 }
474
475 pub fn config(&self) -> &WriteBehindConfig {
477 &self.config
478 }
479
480 pub fn pending_count(&self) -> usize {
482 self.pending.len()
483 }
484}
485
486struct WalFile {
492 path: PathBuf,
493 encryption_key: Vec<u8>,
494}
495
496impl WalFile {
497 fn open(path: &std::path::Path, encryption_key: &[u8]) -> std::io::Result<Self> {
498 if let Some(parent) = path.parent() {
499 std::fs::create_dir_all(parent)?;
500 }
501 Ok(Self {
502 path: path.to_path_buf(),
503 encryption_key: encryption_key.to_vec(),
504 })
505 }
506
507 fn append(&mut self, op: &WriteOp) -> std::io::Result<()> {
508 let json = serde_json::to_string(op)
509 .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e.to_string()))?;
510 let payload = json.as_bytes();
511
512 let encrypted = if self.encryption_key.is_empty() {
513 payload.to_vec()
514 } else {
515 self.encrypt(payload)
516 };
517
518 let crc = crc64(&encrypted);
519
520 let mut record = Vec::with_capacity(4 + encrypted.len() + 8);
521 record.extend_from_slice(&(encrypted.len() as u32).to_le_bytes());
522 record.extend_from_slice(&encrypted);
523 record.extend_from_slice(&crc.to_le_bytes());
524
525 let mut file = std::fs::OpenOptions::new()
526 .create(true)
527 .append(true)
528 .open(&self.path)?;
529 use std::io::Write;
530 file.write_all(&record)?;
531 file.flush()?;
532 Ok(())
533 }
534
535 fn read_all(&self) -> std::io::Result<Vec<WriteOp>> {
536 let data = match std::fs::read(&self.path) {
537 Ok(d) => d,
538 Err(e) if e.kind() == std::io::ErrorKind::NotFound => return Ok(Vec::new()),
539 Err(e) => return Err(e),
540 };
541 let mut ops = Vec::new();
542 let mut pos = 0;
543 while pos + 4 <= data.len() {
544 let len = u32::from_le_bytes([data[pos], data[pos + 1], data[pos + 2], data[pos + 3]])
545 as usize;
546 pos += 4;
547 if pos + len + 8 > data.len() {
548 break;
549 }
550 let encrypted = &data[pos..pos + len];
551 pos += len;
552 let expected_crc = u64::from_le_bytes([
553 data[pos],
554 data[pos + 1],
555 data[pos + 2],
556 data[pos + 3],
557 data[pos + 4],
558 data[pos + 5],
559 data[pos + 6],
560 data[pos + 7],
561 ]);
562 pos += 8;
563
564 if crc64(encrypted) != expected_crc {
565 continue;
566 }
567
568 let decrypted = if self.encryption_key.is_empty() {
569 encrypted.to_vec()
570 } else {
571 self.decrypt(encrypted)
572 };
573
574 if let Ok(op) = serde_json::from_slice::<WriteOp>(&decrypted) {
575 ops.push(op);
576 }
577 }
578 ops.sort_by_key(|op| op.sequence);
579 Ok(ops)
580 }
581
582 fn truncate(&mut self) -> std::io::Result<()> {
583 std::fs::write(&self.path, b"")?;
584 Ok(())
585 }
586
587 fn encrypt(&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 .encrypt_with_aad(data, &[])
593 .unwrap_or_else(|_| data.to_vec())
594 }
595
596 fn decrypt(&self, data: &[u8]) -> Vec<u8> {
597 let crypter = sz_orm_crypto::AesGcmCrypter::from_key_str(
598 std::str::from_utf8(&self.encryption_key).unwrap_or("default-key"),
599 );
600 crypter
601 .decrypt_with_aad(data, &[])
602 .unwrap_or_else(|_| data.to_vec())
603 }
604}
605
606fn crc64(data: &[u8]) -> u64 {
607 let mut crc: u64 = 0;
608 for &byte in data {
609 crc ^= byte as u64;
610 for _ in 0..8 {
611 if crc & 1 != 0 {
612 crc = (crc >> 1) ^ 0xC96E_8607_EAFC_E6CD;
613 } else {
614 crc >>= 1;
615 }
616 }
617 }
618 crc
619}
620
621pub struct BloomFilterGuard {
627 filter: parking_lot::RwLock<bloomfilter::Bloom<String>>,
628 capacity: usize,
629 false_positive_rate: f64,
630 count: AtomicU64,
631}
632
633impl BloomFilterGuard {
634 pub fn new(capacity: usize, false_positive_rate: f64) -> Self {
636 let filter = bloomfilter::Bloom::new_for_fp_rate(capacity, false_positive_rate);
637 Self {
638 filter: parking_lot::RwLock::new(filter),
639 capacity,
640 false_positive_rate,
641 count: AtomicU64::new(0),
642 }
643 }
644
645 pub fn default_config() -> Self {
647 Self::new(100_000, 0.01)
648 }
649
650 pub fn add(&self, key: &str) {
652 self.filter.write().set(&key.to_string());
653 self.count.fetch_add(1, Ordering::Relaxed);
654 }
655
656 pub fn might_contain(&self, key: &str) -> bool {
658 self.filter.read().check(&key.to_string())
659 }
660
661 pub fn rebuild(&self, keys: impl Iterator<Item = String>) {
663 let mut filter =
664 bloomfilter::Bloom::new_for_fp_rate(self.capacity, self.false_positive_rate);
665 for key in keys {
666 filter.set(&key);
667 self.count.fetch_add(1, Ordering::Relaxed);
668 }
669 *self.filter.write() = filter;
670 }
671
672 pub fn count(&self) -> u64 {
674 self.count.load(Ordering::Relaxed)
675 }
676}
677
678pub struct CacheMutexGuard {
684 mutexes: parking_lot::Mutex<HashMap<String, Arc<tokio::sync::Mutex<()>>>>,
685}
686
687impl CacheMutexGuard {
688 pub fn new() -> Self {
690 Self {
691 mutexes: parking_lot::Mutex::new(HashMap::new()),
692 }
693 }
694
695 pub fn get_mutex(&self, key: &str) -> Arc<tokio::sync::Mutex<()>> {
697 let mut map = self.mutexes.lock();
698 map.entry(key.to_string())
699 .or_insert_with(|| Arc::new(tokio::sync::Mutex::new(())))
700 .clone()
701 }
702
703 pub async fn with_guard<F, R>(&self, key: &str, f: F) -> R
705 where
706 F: std::future::Future<Output = R>,
707 {
708 let mutex = self.get_mutex(key);
709 let _guard = mutex.lock().await;
710 f.await
711 }
712}
713
714impl Default for CacheMutexGuard {
715 fn default() -> Self {
716 Self::new()
717 }
718}
719
720pub struct RandomTtlJitter;
726
727impl RandomTtlJitter {
728 pub fn jitter(base_ttl: Duration, jitter_range: f64) -> Duration {
733 use rand::Rng;
734 let mut rng = rand::thread_rng();
735 let random: f64 = rng.gen_range(-1.0..=1.0);
736 let factor = 1.0 + jitter_range * random;
737 let jittered_ms = (base_ttl.as_millis() as f64 * factor) as u64;
738 Duration::from_millis(jittered_ms.max(1))
739 }
740
741 pub fn default_jitter(base_ttl: Duration) -> Duration {
743 Self::jitter(base_ttl, 0.2)
744 }
745}
746
747#[cfg(test)]
748mod tests {
749 use super::*;
750
751 #[test]
754 fn test_redis_pubsub_serialize_message_key() {
755 let msg = InvalidationMessage::InvalidateKey("user:42".to_string());
756 let json = RedisPubSubInvalidationBus::serialize_message(&msg, "instance-1");
757 assert!(json.contains("\"type\":\"key\""));
758 assert!(json.contains("\"key\":\"user:42\""));
759 assert!(json.contains("\"src\":\"instance-1\""));
760 assert!(json.len() <= 1024, "消息应 ≤1KB: {} bytes", json.len());
761 }
762
763 #[test]
764 fn test_redis_pubsub_serialize_message_table() {
765 let msg = InvalidationMessage::InvalidateTable("users".to_string());
766 let json = RedisPubSubInvalidationBus::serialize_message(&msg, "instance-1");
767 assert!(json.contains("\"type\":\"table\""));
768 assert!(json.contains("\"table\":\"users\""));
769 assert!(json.len() <= 1024);
770 }
771
772 #[test]
773 fn test_redis_pubsub_serialize_message_all() {
774 let msg = InvalidationMessage::InvalidateAll;
775 let json = RedisPubSubInvalidationBus::serialize_message(&msg, "instance-1");
776 assert!(json.contains("\"type\":\"all\""));
777 assert!(json.len() <= 1024);
778 }
779
780 #[test]
781 fn test_redis_pubsub_deserialize_skips_self() {
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-1");
786 assert!(result.is_none(), "应跳过自回环");
787 }
788
789 #[test]
790 fn test_redis_pubsub_deserialize_other_instance() {
791 let msg = InvalidationMessage::InvalidateKey("user:42".to_string());
792 let json = RedisPubSubInvalidationBus::serialize_message(&msg, "instance-1");
793 let result = RedisPubSubInvalidationBus::deserialize_message(&json, "instance-2");
795 assert!(result.is_some(), "应接收其他实例消息");
796 }
797
798 #[test]
799 fn test_redis_pubsub_disconnected_publish() {
800 let bus = RedisPubSubInvalidationBus::disconnected("instance-1");
801 bus.publish(InvalidationMessage::InvalidateAll);
803 }
804
805 #[test]
806 fn test_redis_pubsub_subscribe_drain() {
807 let bus = RedisPubSubInvalidationBus::disconnected("instance-1");
808 bus.push_received(InvalidationMessage::InvalidateTable("users".to_string()));
809 bus.push_received(InvalidationMessage::InvalidateAll);
810 let messages: Vec<_> = bus.subscribe().collect();
811 assert_eq!(messages.len(), 2);
812 let messages2: Vec<_> = bus.subscribe().collect();
814 assert_eq!(messages2.len(), 0);
815 }
816
817 #[test]
820 fn test_gossip_publish_and_subscribe() {
821 let bus = GossipInvalidationBus::new(
822 vec![NodeAddr::new("127.0.0.1", 8080)],
823 b"secret-key".to_vec(),
824 "instance-1",
825 );
826 bus.publish(InvalidationMessage::InvalidateTable("users".to_string()));
827 bus.publish(InvalidationMessage::InvalidateAll);
828 let messages: Vec<_> = bus.subscribe().collect();
829 assert_eq!(messages.len(), 2);
830 }
831
832 #[test]
833 fn test_gossip_hmac_authentication() {
834 let bus = GossipInvalidationBus::new(
835 vec![NodeAddr::new("127.0.0.1", 8080)],
836 b"secret-key".to_vec(),
837 "instance-1",
838 );
839 let msg = InvalidationMessage::InvalidateKey("user:42".to_string());
840 let tag = bus.compute_hmac(&msg);
841 assert!(bus.verify_hmac(&msg, &tag));
842 assert!(!bus.verify_hmac(&msg, &[0u8; 32]));
844 }
845
846 #[test]
847 fn test_gossip_receive_dedup() {
848 let bus = GossipInvalidationBus::new(
849 vec![NodeAddr::new("127.0.0.1", 8080)],
850 b"secret-key".to_vec(),
851 "instance-1",
852 );
853 let msg = InvalidationMessage::InvalidateKey("user:42".to_string());
854 let tag = bus.compute_hmac(&msg);
855 assert!(bus.receive(msg.clone(), 1, &tag));
857 assert!(!bus.receive(msg, 1, &tag));
859 }
860
861 #[test]
862 fn test_gossip_receive_unauthenticated() {
863 let bus = GossipInvalidationBus::new(
864 vec![NodeAddr::new("127.0.0.1", 8080)],
865 b"secret-key".to_vec(),
866 "instance-1",
867 );
868 let msg = InvalidationMessage::InvalidateKey("user:42".to_string());
869 assert!(!bus.receive(msg, 1, &[0u8; 32]));
871 }
872
873 #[test]
876 fn test_write_behind_config_default() {
877 let config = WriteBehindConfig::default();
878 assert_eq!(config.batch_size, 100);
879 assert_eq!(config.flush_interval, Duration::from_millis(100));
880 assert!(config.fallback_to_sync);
881 }
882
883 #[test]
884 fn test_write_behind_config_builder() {
885 let config = WriteBehindConfig::builder()
886 .batch_size(50)
887 .flush_interval(Duration::from_millis(200))
888 .fallback_to_sync(false)
889 .build();
890 assert_eq!(config.batch_size, 50);
891 assert_eq!(config.flush_interval, Duration::from_millis(200));
892 assert!(!config.fallback_to_sync);
893 }
894
895 #[test]
896 fn test_write_op_new() {
897 let op = WriteOp::new(WriteOpType::Insert, "users", Value::I64(42));
898 assert_eq!(op.op_type, WriteOpType::Insert);
899 assert_eq!(op.table, "users");
900 assert_eq!(op.pk, Value::I64(42));
901 assert_eq!(op.sequence, 0);
902 }
903
904 #[test]
905 fn test_write_behind_queue_enqueue_and_drain() {
906 let temp_dir = std::env::temp_dir().join("sz-orm-test-wal");
907 let _ = std::fs::remove_dir_all(&temp_dir);
908 let config = WriteBehindConfig::builder()
909 .batch_size(10)
910 .wal_path(temp_dir.join("test.log"))
911 .build();
912 let queue = WriteBehindQueue::new(config).unwrap();
913
914 for i in 0..3 {
916 let op = WriteOp::new(WriteOpType::Update, "users", Value::I64(i));
917 queue.enqueue(op).unwrap();
918 }
919
920 assert_eq!(queue.pending_count(), 3);
921
922 let batch = queue.drain_batch();
924 assert_eq!(batch.len(), 3);
925 assert!(batch.windows(2).all(|w| w[0].sequence <= w[1].sequence));
927
928 let _ = std::fs::remove_dir_all(&temp_dir);
930 }
931
932 #[test]
933 fn test_write_behind_queue_replay() {
934 let temp_dir = std::env::temp_dir().join("sz-orm-test-wal-replay");
935 let _ = std::fs::remove_dir_all(&temp_dir);
936 let config = WriteBehindConfig::builder()
937 .batch_size(10)
938 .wal_path(temp_dir.join("test.log"))
939 .build();
940 let queue = WriteBehindQueue::new(config).unwrap();
941
942 for i in 0..5 {
943 let op = WriteOp::new(WriteOpType::Insert, "orders", Value::I64(i)).with_data(vec![(
944 "status".to_string(),
945 Value::String("pending".to_string()),
946 )]);
947 queue.enqueue(op).unwrap();
948 }
949
950 let replayed = queue.replay().unwrap();
952 assert_eq!(replayed.len(), 5);
953 assert!(replayed.windows(2).all(|w| w[0].sequence <= w[1].sequence));
955
956 let _ = std::fs::remove_dir_all(&temp_dir);
958 }
959
960 #[test]
963 fn test_bloom_filter_basic() {
964 let guard = BloomFilterGuard::new(1000, 0.01);
965 guard.add("user:1");
966 guard.add("user:2");
967 assert!(guard.might_contain("user:1"));
968 assert!(guard.might_contain("user:2"));
969 assert_eq!(guard.count(), 2);
970 }
971
972 #[test]
973 fn test_bloom_filter_false_positive_rate() {
974 let guard = BloomFilterGuard::new(10_000, 0.01);
975 for i in 0..1000 {
976 guard.add(&format!("user:{}", i));
977 }
978 let mut false_positives = 0;
979 for i in 1000..2000 {
980 if guard.might_contain(&format!("user:{}", i)) {
981 false_positives += 1;
982 }
983 }
984 let fp_rate = false_positives as f64 / 1000.0;
985 assert!(fp_rate < 0.05, "假阳性率应 < 5%(实际 {})", fp_rate);
986 }
987
988 #[tokio::test]
989 async fn test_cache_mutex_guard() {
990 let guard = CacheMutexGuard::new();
991 guard
992 .with_guard("user:42", async {
993 })
995 .await;
996 guard
997 .with_guard("user:43", async {
998 })
1000 .await;
1001 }
1002
1003 #[test]
1004 fn test_random_ttl_jitter_range() {
1005 let base = Duration::from_millis(1000);
1006 for _ in 0..100 {
1007 let jittered = RandomTtlJitter::default_jitter(base);
1008 let ms = jittered.as_millis();
1009 assert!(
1011 (800..=1200).contains(&ms),
1012 "TTL 抖动应在 ±20% 范围内: {}ms",
1013 ms
1014 );
1015 }
1016 }
1017
1018 #[test]
1019 fn test_consistency_level_default() {
1020 assert_eq!(ConsistencyLevel::default(), ConsistencyLevel::Eventual);
1021 }
1022
1023 #[test]
1024 fn test_crc64() {
1025 let crc1 = crc64(b"hello");
1026 let crc2 = crc64(b"hello");
1027 assert_eq!(crc1, crc2);
1028 let crc3 = crc64(b"world");
1029 assert_ne!(crc1, crc3);
1030 }
1031}