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 crypter(&self) -> sz_orm_crypto::AesGcmCrypter {
595 match std::str::from_utf8(&self.encryption_key) {
596 Ok(s) => sz_orm_crypto::AesGcmCrypter::from_key_str(s),
597 Err(_) => {
598 static FALLBACK_KEY: std::sync::OnceLock<[u8; 32]> = std::sync::OnceLock::new();
599 let key = FALLBACK_KEY.get_or_init(|| {
600 use rand::RngCore;
601 let mut k = [0u8; 32];
602 rand::rngs::OsRng.fill_bytes(&mut k);
603 k
604 });
605 sz_orm_crypto::AesGcmCrypter::new(key)
606 }
607 }
608 }
609
610 fn encrypt(&self, data: &[u8]) -> Vec<u8> {
611 self.crypter()
612 .encrypt_with_aad(data, &[])
613 .unwrap_or_default()
616 }
617
618 fn decrypt(&self, data: &[u8]) -> Vec<u8> {
619 self.crypter()
620 .decrypt_with_aad(data, &[])
621 .unwrap_or_default()
623 }
624}
625
626fn crc64(data: &[u8]) -> u64 {
627 let mut crc: u64 = 0;
628 for &byte in data {
629 crc ^= byte as u64;
630 for _ in 0..8 {
631 if crc & 1 != 0 {
632 crc = (crc >> 1) ^ 0xC96E_8607_EAFC_E6CD;
633 } else {
634 crc >>= 1;
635 }
636 }
637 }
638 crc
639}
640
641pub struct BloomFilterGuard {
648 filter: crate::bloom::BloomFilter,
649 count: AtomicU64,
650}
651
652impl BloomFilterGuard {
653 pub fn new(capacity: usize, false_positive_rate: f64) -> Self {
655 let filter = crate::bloom::BloomFilter::new(capacity, false_positive_rate);
656 Self {
657 filter,
658 count: AtomicU64::new(0),
659 }
660 }
661
662 pub fn default_config() -> Self {
664 Self::new(100_000, 0.01)
665 }
666
667 pub fn add(&self, key: &str) {
669 let _ = self.filter.add(key);
670 self.count.fetch_add(1, Ordering::Relaxed);
671 }
672
673 pub fn might_contain(&self, key: &str) -> bool {
675 self.filter.might_contain(key)
676 }
677
678 pub fn rebuild(&self, keys: impl Iterator<Item = String>) {
682 self.filter.clear();
683 for key in keys {
684 let _ = self.filter.add(&key);
685 self.count.fetch_add(1, Ordering::Relaxed);
686 }
687 }
688
689 pub fn count(&self) -> u64 {
691 self.count.load(Ordering::Relaxed)
692 }
693}
694
695pub struct CacheMutexGuard {
701 mutexes: parking_lot::Mutex<HashMap<String, Arc<tokio::sync::Mutex<()>>>>,
702}
703
704impl CacheMutexGuard {
705 pub fn new() -> Self {
707 Self {
708 mutexes: parking_lot::Mutex::new(HashMap::new()),
709 }
710 }
711
712 pub fn get_mutex(&self, key: &str) -> Arc<tokio::sync::Mutex<()>> {
714 let mut map = self.mutexes.lock();
715 map.entry(key.to_string())
716 .or_insert_with(|| Arc::new(tokio::sync::Mutex::new(())))
717 .clone()
718 }
719
720 pub async fn with_guard<F, R>(&self, key: &str, f: F) -> R
722 where
723 F: std::future::Future<Output = R>,
724 {
725 let mutex = self.get_mutex(key);
726 let _guard = mutex.lock().await;
727 f.await
728 }
729}
730
731impl Default for CacheMutexGuard {
732 fn default() -> Self {
733 Self::new()
734 }
735}
736
737pub struct RandomTtlJitter;
743
744impl RandomTtlJitter {
745 pub fn jitter(base_ttl: Duration, jitter_range: f64) -> Duration {
750 use rand::Rng;
751 let mut rng = rand::thread_rng();
752 let random: f64 = rng.gen_range(-1.0..=1.0);
753 let factor = 1.0 + jitter_range * random;
754 let jittered_ms = (base_ttl.as_millis() as f64 * factor) as u64;
755 Duration::from_millis(jittered_ms.max(1))
756 }
757
758 pub fn default_jitter(base_ttl: Duration) -> Duration {
760 Self::jitter(base_ttl, 0.2)
761 }
762}
763
764#[cfg(test)]
765mod tests {
766 use super::*;
767
768 #[test]
771 fn test_redis_pubsub_serialize_message_key() {
772 let msg = InvalidationMessage::InvalidateKey("user:42".to_string());
773 let json = RedisPubSubInvalidationBus::serialize_message(&msg, "instance-1");
774 assert!(json.contains("\"type\":\"key\""));
775 assert!(json.contains("\"key\":\"user:42\""));
776 assert!(json.contains("\"src\":\"instance-1\""));
777 assert!(json.len() <= 1024, "消息应 ≤1KB: {} bytes", json.len());
778 }
779
780 #[test]
781 fn test_redis_pubsub_serialize_message_table() {
782 let msg = InvalidationMessage::InvalidateTable("users".to_string());
783 let json = RedisPubSubInvalidationBus::serialize_message(&msg, "instance-1");
784 assert!(json.contains("\"type\":\"table\""));
785 assert!(json.contains("\"table\":\"users\""));
786 assert!(json.len() <= 1024);
787 }
788
789 #[test]
790 fn test_redis_pubsub_serialize_message_all() {
791 let msg = InvalidationMessage::InvalidateAll;
792 let json = RedisPubSubInvalidationBus::serialize_message(&msg, "instance-1");
793 assert!(json.contains("\"type\":\"all\""));
794 assert!(json.len() <= 1024);
795 }
796
797 #[test]
798 fn test_redis_pubsub_deserialize_skips_self() {
799 let msg = InvalidationMessage::InvalidateKey("user:42".to_string());
800 let json = RedisPubSubInvalidationBus::serialize_message(&msg, "instance-1");
801 let result = RedisPubSubInvalidationBus::deserialize_message(&json, "instance-1");
803 assert!(result.is_none(), "应跳过自回环");
804 }
805
806 #[test]
807 fn test_redis_pubsub_deserialize_other_instance() {
808 let msg = InvalidationMessage::InvalidateKey("user:42".to_string());
809 let json = RedisPubSubInvalidationBus::serialize_message(&msg, "instance-1");
810 let result = RedisPubSubInvalidationBus::deserialize_message(&json, "instance-2");
812 assert!(result.is_some(), "应接收其他实例消息");
813 }
814
815 #[test]
816 fn test_redis_pubsub_disconnected_publish() {
817 let bus = RedisPubSubInvalidationBus::disconnected("instance-1");
818 bus.publish(InvalidationMessage::InvalidateAll);
820 }
821
822 #[test]
823 fn test_redis_pubsub_subscribe_drain() {
824 let bus = RedisPubSubInvalidationBus::disconnected("instance-1");
825 bus.push_received(InvalidationMessage::InvalidateTable("users".to_string()));
826 bus.push_received(InvalidationMessage::InvalidateAll);
827 let messages: Vec<_> = bus.subscribe().collect();
828 assert_eq!(messages.len(), 2);
829 let messages2: Vec<_> = bus.subscribe().collect();
831 assert_eq!(messages2.len(), 0);
832 }
833
834 #[test]
837 fn test_gossip_publish_and_subscribe() {
838 let bus = GossipInvalidationBus::new(
839 vec![NodeAddr::new("127.0.0.1", 8080)],
840 b"secret-key".to_vec(),
841 "instance-1",
842 );
843 bus.publish(InvalidationMessage::InvalidateTable("users".to_string()));
844 bus.publish(InvalidationMessage::InvalidateAll);
845 let messages: Vec<_> = bus.subscribe().collect();
846 assert_eq!(messages.len(), 2);
847 }
848
849 #[test]
850 fn test_gossip_hmac_authentication() {
851 let bus = GossipInvalidationBus::new(
852 vec![NodeAddr::new("127.0.0.1", 8080)],
853 b"secret-key".to_vec(),
854 "instance-1",
855 );
856 let msg = InvalidationMessage::InvalidateKey("user:42".to_string());
857 let tag = bus.compute_hmac(&msg);
858 assert!(bus.verify_hmac(&msg, &tag));
859 assert!(!bus.verify_hmac(&msg, &[0u8; 32]));
861 }
862
863 #[test]
864 fn test_gossip_receive_dedup() {
865 let bus = GossipInvalidationBus::new(
866 vec![NodeAddr::new("127.0.0.1", 8080)],
867 b"secret-key".to_vec(),
868 "instance-1",
869 );
870 let msg = InvalidationMessage::InvalidateKey("user:42".to_string());
871 let tag = bus.compute_hmac(&msg);
872 assert!(bus.receive(msg.clone(), 1, &tag));
874 assert!(!bus.receive(msg, 1, &tag));
876 }
877
878 #[test]
879 fn test_gossip_receive_unauthenticated() {
880 let bus = GossipInvalidationBus::new(
881 vec![NodeAddr::new("127.0.0.1", 8080)],
882 b"secret-key".to_vec(),
883 "instance-1",
884 );
885 let msg = InvalidationMessage::InvalidateKey("user:42".to_string());
886 assert!(!bus.receive(msg, 1, &[0u8; 32]));
888 }
889
890 #[test]
893 fn test_write_behind_config_default() {
894 let config = WriteBehindConfig::default();
895 assert_eq!(config.batch_size, 100);
896 assert_eq!(config.flush_interval, Duration::from_millis(100));
897 assert!(config.fallback_to_sync);
898 }
899
900 #[test]
901 fn test_write_behind_config_builder() {
902 let config = WriteBehindConfig::builder()
903 .batch_size(50)
904 .flush_interval(Duration::from_millis(200))
905 .fallback_to_sync(false)
906 .build();
907 assert_eq!(config.batch_size, 50);
908 assert_eq!(config.flush_interval, Duration::from_millis(200));
909 assert!(!config.fallback_to_sync);
910 }
911
912 #[test]
913 fn test_write_op_new() {
914 let op = WriteOp::new(WriteOpType::Insert, "users", Value::I64(42));
915 assert_eq!(op.op_type, WriteOpType::Insert);
916 assert_eq!(op.table, "users");
917 assert_eq!(op.pk, Value::I64(42));
918 assert_eq!(op.sequence, 0);
919 }
920
921 #[test]
922 fn test_write_behind_queue_enqueue_and_drain() {
923 let temp_dir = std::env::temp_dir().join("sz-orm-test-wal");
924 let _ = std::fs::remove_dir_all(&temp_dir);
925 let config = WriteBehindConfig::builder()
926 .batch_size(10)
927 .wal_path(temp_dir.join("test.log"))
928 .build();
929 let queue = WriteBehindQueue::new(config).unwrap();
930
931 for i in 0..3 {
933 let op = WriteOp::new(WriteOpType::Update, "users", Value::I64(i));
934 queue.enqueue(op).unwrap();
935 }
936
937 assert_eq!(queue.pending_count(), 3);
938
939 let batch = queue.drain_batch();
941 assert_eq!(batch.len(), 3);
942 assert!(batch.windows(2).all(|w| w[0].sequence <= w[1].sequence));
944
945 let _ = std::fs::remove_dir_all(&temp_dir);
947 }
948
949 #[test]
950 fn test_write_behind_queue_replay() {
951 let temp_dir = std::env::temp_dir().join("sz-orm-test-wal-replay");
952 let _ = std::fs::remove_dir_all(&temp_dir);
953 let config = WriteBehindConfig::builder()
954 .batch_size(10)
955 .wal_path(temp_dir.join("test.log"))
956 .build();
957 let queue = WriteBehindQueue::new(config).unwrap();
958
959 for i in 0..5 {
960 let op = WriteOp::new(WriteOpType::Insert, "orders", Value::I64(i)).with_data(vec![(
961 "status".to_string(),
962 Value::String("pending".to_string()),
963 )]);
964 queue.enqueue(op).unwrap();
965 }
966
967 let replayed = queue.replay().unwrap();
969 assert_eq!(replayed.len(), 5);
970 assert!(replayed.windows(2).all(|w| w[0].sequence <= w[1].sequence));
972
973 let _ = std::fs::remove_dir_all(&temp_dir);
975 }
976
977 #[test]
980 fn test_bloom_filter_basic() {
981 let guard = BloomFilterGuard::new(1000, 0.01);
982 guard.add("user:1");
983 guard.add("user:2");
984 assert!(guard.might_contain("user:1"));
985 assert!(guard.might_contain("user:2"));
986 assert_eq!(guard.count(), 2);
987 }
988
989 #[test]
990 fn test_bloom_filter_false_positive_rate() {
991 let guard = BloomFilterGuard::new(10_000, 0.01);
992 for i in 0..1000 {
993 guard.add(&format!("user:{}", i));
994 }
995 let mut false_positives = 0;
996 for i in 1000..2000 {
997 if guard.might_contain(&format!("user:{}", i)) {
998 false_positives += 1;
999 }
1000 }
1001 let fp_rate = false_positives as f64 / 1000.0;
1002 assert!(fp_rate < 0.05, "假阳性率应 < 5%(实际 {})", fp_rate);
1003 }
1004
1005 #[tokio::test]
1006 async fn test_cache_mutex_guard() {
1007 let guard = CacheMutexGuard::new();
1008 guard
1009 .with_guard("user:42", async {
1010 })
1012 .await;
1013 guard
1014 .with_guard("user:43", async {
1015 })
1017 .await;
1018 }
1019
1020 #[test]
1021 fn test_random_ttl_jitter_range() {
1022 let base = Duration::from_millis(1000);
1023 for _ in 0..100 {
1024 let jittered = RandomTtlJitter::default_jitter(base);
1025 let ms = jittered.as_millis();
1026 assert!(
1028 (800..=1200).contains(&ms),
1029 "TTL 抖动应在 ±20% 范围内: {}ms",
1030 ms
1031 );
1032 }
1033 }
1034
1035 #[test]
1036 fn test_consistency_level_default() {
1037 assert_eq!(ConsistencyLevel::default(), ConsistencyLevel::Eventual);
1038 }
1039
1040 #[test]
1041 fn test_crc64() {
1042 let crc1 = crc64(b"hello");
1043 let crc2 = crc64(b"hello");
1044 assert_eq!(crc1, crc2);
1045 let crc3 = crc64(b"world");
1046 assert_ne!(crc1, crc3);
1047 }
1048}