Skip to main content

oxihuman_core/
persistent_map.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5use std::collections::HashMap;
6
7/// A persistent-style map that supports snapshotting.
8#[allow(dead_code)]
9pub struct PersistentMap {
10    current: HashMap<String, String>,
11    snapshots: Vec<HashMap<String, String>>,
12    version: u64,
13}
14
15#[allow(dead_code)]
16impl PersistentMap {
17    pub fn new() -> Self {
18        Self {
19            current: HashMap::new(),
20            snapshots: Vec::new(),
21            version: 0,
22        }
23    }
24    pub fn insert(&mut self, key: &str, value: &str) {
25        self.current.insert(key.to_string(), value.to_string());
26        self.version += 1;
27    }
28    pub fn get(&self, key: &str) -> Option<&str> {
29        self.current.get(key).map(|s| s.as_str())
30    }
31    pub fn remove(&mut self, key: &str) -> bool {
32        let removed = self.current.remove(key).is_some();
33        if removed {
34            self.version += 1;
35        }
36        removed
37    }
38    pub fn contains(&self, key: &str) -> bool {
39        self.current.contains_key(key)
40    }
41    pub fn snapshot(&mut self) {
42        self.snapshots.push(self.current.clone());
43    }
44    pub fn restore(&mut self, idx: usize) -> bool {
45        if let Some(snap) = self.snapshots.get(idx) {
46            self.current = snap.clone();
47            self.version += 1;
48            true
49        } else {
50            false
51        }
52    }
53    pub fn snapshot_count(&self) -> usize {
54        self.snapshots.len()
55    }
56    pub fn version(&self) -> u64 {
57        self.version
58    }
59    pub fn len(&self) -> usize {
60        self.current.len()
61    }
62    pub fn is_empty(&self) -> bool {
63        self.current.is_empty()
64    }
65    pub fn clear(&mut self) {
66        self.current.clear();
67        self.version += 1;
68    }
69    pub fn keys(&self) -> Vec<&str> {
70        self.current.keys().map(|s| s.as_str()).collect()
71    }
72    pub fn diff_from_snapshot(&self, idx: usize) -> Vec<String> {
73        if let Some(snap) = self.snapshots.get(idx) {
74            self.current
75                .keys()
76                .filter(|k| snap.get(*k) != self.current.get(*k))
77                .cloned()
78                .collect()
79        } else {
80            Vec::new()
81        }
82    }
83}
84
85impl Default for PersistentMap {
86    fn default() -> Self {
87        Self::new()
88    }
89}
90
91#[allow(dead_code)]
92pub fn new_persistent_map() -> PersistentMap {
93    PersistentMap::new()
94}
95#[allow(dead_code)]
96pub fn pm_insert(m: &mut PersistentMap, k: &str, v: &str) {
97    m.insert(k, v);
98}
99#[allow(dead_code)]
100pub fn pm_get<'a>(m: &'a PersistentMap, k: &str) -> Option<&'a str> {
101    m.get(k)
102}
103#[allow(dead_code)]
104pub fn pm_remove(m: &mut PersistentMap, k: &str) -> bool {
105    m.remove(k)
106}
107#[allow(dead_code)]
108pub fn pm_contains(m: &PersistentMap, k: &str) -> bool {
109    m.contains(k)
110}
111#[allow(dead_code)]
112pub fn pm_snapshot(m: &mut PersistentMap) {
113    m.snapshot();
114}
115#[allow(dead_code)]
116pub fn pm_restore(m: &mut PersistentMap, idx: usize) -> bool {
117    m.restore(idx)
118}
119#[allow(dead_code)]
120pub fn pm_snapshot_count(m: &PersistentMap) -> usize {
121    m.snapshot_count()
122}
123#[allow(dead_code)]
124pub fn pm_version(m: &PersistentMap) -> u64 {
125    m.version()
126}
127#[allow(dead_code)]
128pub fn pm_len(m: &PersistentMap) -> usize {
129    m.len()
130}
131#[allow(dead_code)]
132pub fn pm_is_empty(m: &PersistentMap) -> bool {
133    m.is_empty()
134}
135#[allow(dead_code)]
136pub fn pm_clear(m: &mut PersistentMap) {
137    m.clear();
138}
139
140#[cfg(test)]
141mod tests {
142    use super::*;
143    #[test]
144    fn test_insert_get() {
145        let mut m = new_persistent_map();
146        pm_insert(&mut m, "a", "1");
147        assert_eq!(pm_get(&m, "a"), Some("1"));
148    }
149    #[test]
150    fn test_remove() {
151        let mut m = new_persistent_map();
152        pm_insert(&mut m, "x", "v");
153        assert!(pm_remove(&mut m, "x"));
154        assert!(!pm_contains(&m, "x"));
155    }
156    #[test]
157    fn test_snapshot_restore() {
158        let mut m = new_persistent_map();
159        pm_insert(&mut m, "k", "before");
160        pm_snapshot(&mut m);
161        pm_insert(&mut m, "k", "after");
162        pm_restore(&mut m, 0);
163        assert_eq!(pm_get(&m, "k"), Some("before"));
164    }
165    #[test]
166    fn test_snapshot_count() {
167        let mut m = new_persistent_map();
168        pm_snapshot(&mut m);
169        pm_snapshot(&mut m);
170        assert_eq!(pm_snapshot_count(&m), 2);
171    }
172    #[test]
173    fn test_version_increments() {
174        let mut m = new_persistent_map();
175        let v0 = pm_version(&m);
176        pm_insert(&mut m, "a", "1");
177        assert!(pm_version(&m) > v0);
178    }
179    #[test]
180    fn test_len() {
181        let mut m = new_persistent_map();
182        pm_insert(&mut m, "a", "1");
183        pm_insert(&mut m, "b", "2");
184        assert_eq!(pm_len(&m), 2);
185    }
186    #[test]
187    fn test_is_empty() {
188        let m = new_persistent_map();
189        assert!(pm_is_empty(&m));
190    }
191    #[test]
192    fn test_clear() {
193        let mut m = new_persistent_map();
194        pm_insert(&mut m, "a", "1");
195        pm_clear(&mut m);
196        assert!(pm_is_empty(&m));
197    }
198    #[test]
199    fn test_diff_from_snapshot() {
200        let mut m = new_persistent_map();
201        pm_insert(&mut m, "k", "old");
202        pm_snapshot(&mut m);
203        pm_insert(&mut m, "k", "new");
204        let diff = m.diff_from_snapshot(0);
205        assert!(diff.contains(&"k".to_string()));
206    }
207    #[test]
208    fn test_restore_invalid_idx() {
209        let mut m = new_persistent_map();
210        assert!(!pm_restore(&mut m, 99));
211    }
212}