Skip to main content

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}