json_eval_rs/rlogic/
compiled_logic_store.rs1use once_cell::sync::Lazy;
7use dashmap::DashMap;
8use std::sync::atomic::{AtomicU64, Ordering};
9use ahash::AHasher;
10use std::hash::{Hash, Hasher};
11use super::CompiledLogic;
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
15pub struct CompiledLogicId(u64);
16
17impl CompiledLogicId {
18 pub fn as_u64(&self) -> u64 {
20 self.0
21 }
22
23 pub fn from_u64(id: u64) -> Self {
25 Self(id)
26 }
27}
28
29static COMPILED_LOGIC_STORE: Lazy<CompiledLogicStore> = Lazy::new(|| {
31 CompiledLogicStore {
32 store: DashMap::new(),
33 id_map: DashMap::new(),
34 next_id: AtomicU64::new(1), }
36});
37
38struct CompiledLogicStore {
40 store: DashMap<u64, (CompiledLogicId, CompiledLogic)>,
42 id_map: DashMap<u64, CompiledLogic>,
44 next_id: AtomicU64,
46}
47
48impl CompiledLogicStore {
49 fn compile_value(&self, logic: &serde_json::Value) -> Result<CompiledLogicId, String> {
52 let logic_str = serde_json::to_string(logic)
54 .map_err(|e| format!("Failed to serialize logic: {}", e))?;
55 let mut hasher = AHasher::default();
56 logic_str.hash(&mut hasher);
57 let hash = hasher.finish();
58
59 if let Some(entry) = self.store.get(&hash) {
61 return Ok(entry.0);
62 }
63
64 let compiled = CompiledLogic::compile(logic)?;
66
67 let id = CompiledLogicId(self.next_id.fetch_add(1, Ordering::SeqCst));
69
70 self.store.insert(hash, (id, compiled.clone()));
72 self.id_map.insert(id.0, compiled);
73
74 Ok(id)
75 }
76
77 fn compile(&self, logic_json: &str) -> Result<CompiledLogicId, String> {
80 let logic: serde_json::Value = serde_json::from_str(logic_json)
82 .map_err(|e| format!("Failed to parse logic JSON: {}", e))?;
83
84 self.compile_value(&logic)
86 }
87
88 fn get(&self, id: CompiledLogicId) -> Option<CompiledLogic> {
90 self.id_map.get(&id.0).map(|v| v.clone())
91 }
92
93 fn stats(&self) -> CompiledLogicStoreStats {
95 CompiledLogicStoreStats {
96 compiled_count: self.store.len(),
97 next_id: self.next_id.load(Ordering::SeqCst),
98 }
99 }
100
101 #[allow(dead_code)]
103 fn clear(&self) {
104 self.store.clear();
105 self.id_map.clear();
106 self.next_id.store(1, Ordering::SeqCst);
107 }
108}
109
110#[derive(Debug, Clone)]
112pub struct CompiledLogicStoreStats {
113 pub compiled_count: usize,
115 pub next_id: u64,
117}
118
119pub fn compile_logic(logic_json: &str) -> Result<CompiledLogicId, String> {
124 COMPILED_LOGIC_STORE.compile(logic_json)
125}
126
127pub fn compile_logic_value(logic: &serde_json::Value) -> Result<CompiledLogicId, String> {
132 COMPILED_LOGIC_STORE.compile_value(logic)
133}
134
135pub fn get_compiled_logic(id: CompiledLogicId) -> Option<CompiledLogic> {
137 COMPILED_LOGIC_STORE.get(id)
138}
139
140pub fn get_store_stats() -> CompiledLogicStoreStats {
142 COMPILED_LOGIC_STORE.stats()
143}
144
145#[cfg(test)]
149pub fn clear_store() {
150 COMPILED_LOGIC_STORE.clear()
151}
152
153#[cfg(test)]
154mod tests {
155 use super::*;
156 use std::sync::Mutex;
157
158 static TEST_MUTEX: Mutex<()> = Mutex::new(());
160
161 #[test]
162 fn test_compile_and_get() {
163 let _lock = TEST_MUTEX.lock().unwrap();
164 clear_store(); let logic = r#"{"==": [{"var": "x"}, 10]}"#;
167 let id = compile_logic(logic).expect("Failed to compile");
168
169 let compiled = get_compiled_logic(id);
170 assert!(compiled.is_some());
171 }
172
173 #[test]
174 fn test_deduplication() {
175 let _lock = TEST_MUTEX.lock().unwrap();
176 clear_store(); let logic = r#"{"*": [{"var": "a"}, 2]}"#;
179
180 let id1 = compile_logic(logic).expect("Failed to compile");
181 let id2 = compile_logic(logic).expect("Failed to compile");
182
183 assert_eq!(id1, id2);
185 }
186
187 #[test]
188 fn test_different_logic() {
189 let _lock = TEST_MUTEX.lock().unwrap();
190 clear_store(); let logic1 = r#"{"*": [{"var": "a"}, 2]}"#;
193 let logic2 = r#"{"*": [{"var": "b"}, 3]}"#;
194
195 let id1 = compile_logic(logic1).expect("Failed to compile");
196 let id2 = compile_logic(logic2).expect("Failed to compile");
197
198 assert_ne!(id1, id2);
200 }
201
202 #[test]
203 fn test_stats() {
204 let _lock = TEST_MUTEX.lock().unwrap();
205 clear_store(); let logic = r#"{"+": [1, 2, 3]}"#;
209 let _ = compile_logic(logic).expect("Failed to compile");
210
211 let stats = get_store_stats();
212 assert_eq!(stats.compiled_count, 1);
213 assert_eq!(stats.next_id, 2);
214 }
215}