hdds_persistence/store.rs
1// SPDX-License-Identifier: Apache-2.0 OR MIT
2// Copyright (c) 2025-2026 naskel.com
3
4//! Persistence store abstraction
5//!
6//! Defines the trait for storage backends (SQLite, RocksDB, etc.).
7
8use anyhow::Result;
9use serde::{Deserialize, Serialize};
10
11/// A persisted DDS sample
12#[derive(Debug, Clone, Serialize, Deserialize)]
13pub struct Sample {
14 /// Topic name
15 pub topic: String,
16
17 /// Type name
18 pub type_name: String,
19
20 /// Serialized payload (CDR)
21 pub payload: Vec<u8>,
22
23 /// Timestamp (Unix nanoseconds)
24 pub timestamp_ns: u64,
25
26 /// Sequence number
27 pub sequence: u64,
28
29 /// Source GUID (participant ID)
30 pub source_guid: [u8; 16],
31}
32
33/// Retention policy for persisted samples.
34#[derive(Debug, Clone, Copy)]
35pub struct RetentionPolicy {
36 /// Number of recent samples to keep per topic (0 = unlimited).
37 pub keep_count: usize,
38 /// Maximum sample age in nanoseconds (None = unlimited).
39 pub max_age_ns: Option<u64>,
40 /// Maximum total bytes per topic (None = unlimited).
41 pub max_bytes: Option<u64>,
42}
43
44impl RetentionPolicy {
45 /// Returns true if no retention limits are configured.
46 pub fn is_noop(&self) -> bool {
47 self.keep_count == 0 && self.max_age_ns.is_none() && self.max_bytes.is_none()
48 }
49}
50
51/// Persistence store trait
52///
53/// Backend-agnostic interface for storing and retrieving DDS samples.
54///
55/// # Implementations
56///
57/// - `SqliteStore` -- Default, zero-dependency
58/// - `RocksDbStore` -- High-performance (feature flag)
59pub trait PersistenceStore {
60 /// Save a sample to persistent storage
61 fn save(&self, sample: &Sample) -> Result<()>;
62
63 /// Load all samples for a topic
64 fn load(&self, topic: &str) -> Result<Vec<Sample>>;
65
66 /// Query samples within a time range
67 ///
68 /// # Arguments
69 ///
70 /// - `topic` -- Topic name (supports wildcards: "State/*")
71 /// - `start_ns` -- Start timestamp (Unix nanoseconds)
72 /// - `end_ns` -- End timestamp (Unix nanoseconds)
73 fn query_range(&self, topic: &str, start_ns: u64, end_ns: u64) -> Result<Vec<Sample>>;
74
75 /// Delete old samples to enforce retention policy
76 ///
77 /// # Arguments
78 ///
79 /// - `topic` -- Topic name
80 /// - `keep_count` -- Number of recent samples to keep
81 fn apply_retention(&self, topic: &str, keep_count: usize) -> Result<()>;
82
83 /// Apply retention policy with optional age/size constraints.
84 fn apply_retention_policy(&self, topic: &str, policy: &RetentionPolicy) -> Result<()> {
85 self.apply_retention(topic, policy.keep_count)
86 }
87
88 /// Get total number of samples stored
89 fn count(&self) -> Result<usize>;
90
91 /// Clear all samples (for testing)
92 fn clear(&self) -> Result<()>;
93}
94
95#[cfg(test)]
96mod tests {
97 use super::*;
98
99 #[test]
100 fn test_sample_serialization() {
101 let sample = Sample {
102 topic: "test/topic".to_string(),
103 type_name: "TestType".to_string(),
104 payload: vec![0x01, 0x02, 0x03],
105 timestamp_ns: 1234567890,
106 sequence: 42,
107 source_guid: [0xAA; 16],
108 };
109
110 let json = serde_json::to_string(&sample).unwrap();
111 let deserialized: Sample = serde_json::from_str(&json).unwrap();
112
113 assert_eq!(sample.topic, deserialized.topic);
114 assert_eq!(sample.sequence, deserialized.sequence);
115 }
116}