json_eval_rs/rlogic/
compiled_logic_store.rs1use super::CompiledLogic;
7use ahash::AHasher;
8use dashmap::DashMap;
9use once_cell::sync::Lazy;
10use std::hash::{Hash, Hasher};
11use std::sync::atomic::{AtomicU64, Ordering};
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)?;
67
68 match self.store.entry(hash) {
70 dashmap::mapref::entry::Entry::Occupied(o) => {
71 Ok(o.get().0)
73 }
74 dashmap::mapref::entry::Entry::Vacant(v) => {
75 let id = CompiledLogicId(self.next_id.fetch_add(1, Ordering::SeqCst));
77 self.id_map.insert(id.0, compiled.clone());
79 v.insert((id, compiled));
80 Ok(id)
81 }
82 }
83 }
84
85 fn compile(&self, logic_json: &str) -> Result<CompiledLogicId, String> {
88 let logic: serde_json::Value = serde_json::from_str(logic_json)
90 .map_err(|e| format!("Failed to parse logic JSON: {}", e))?;
91
92 self.compile_value(&logic)
94 }
95
96 fn get(&self, id: CompiledLogicId) -> Option<CompiledLogic> {
98 self.id_map.get(&id.0).map(|v| v.clone())
99 }
100
101 fn stats(&self) -> CompiledLogicStoreStats {
103 CompiledLogicStoreStats {
104 compiled_count: self.store.len(),
105 next_id: self.next_id.load(Ordering::SeqCst),
106 }
107 }
108
109 #[allow(dead_code)]
111 fn clear(&self) {
112 self.store.clear();
113 self.id_map.clear();
114 self.next_id.store(1, Ordering::SeqCst);
115 }
116}
117
118#[derive(Debug, Clone)]
120pub struct CompiledLogicStoreStats {
121 pub compiled_count: usize,
123 pub next_id: u64,
125}
126
127pub fn compile_logic(logic_json: &str) -> Result<CompiledLogicId, String> {
132 COMPILED_LOGIC_STORE.compile(logic_json)
133}
134
135pub fn compile_logic_value(logic: &serde_json::Value) -> Result<CompiledLogicId, String> {
140 COMPILED_LOGIC_STORE.compile_value(logic)
141}
142
143pub fn get_compiled_logic(id: CompiledLogicId) -> Option<CompiledLogic> {
145 COMPILED_LOGIC_STORE.get(id)
146}
147
148pub fn get_store_stats() -> CompiledLogicStoreStats {
150 COMPILED_LOGIC_STORE.stats()
151}
152
153#[cfg(test)]
157pub fn clear_store() {
158 COMPILED_LOGIC_STORE.clear()
159}
160
161#[cfg(test)]
162mod tests {
163 use super::*;
164
165 #[test]
166 fn test_compile_and_get() {
167 let logic = r#"{"==": [{"var": "x"}, 10]}"#;
170 let id = compile_logic(logic).expect("Failed to compile");
171
172 let compiled = get_compiled_logic(id);
173 assert!(compiled.is_some());
174 }
175
176 #[test]
177 fn test_deduplication() {
178 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 logic1 = r#"{"*": [{"var": "a"}, 2]}"#;
190 let logic2 = r#"{"*": [{"var": "b"}, 3]}"#;
191
192 let id1 = compile_logic(logic1).expect("Failed to compile");
193 let id2 = compile_logic(logic2).expect("Failed to compile");
194
195 assert_ne!(id1, id2);
197 }
198
199 #[test]
200 fn test_stats() {
201 let stats_before = get_store_stats();
203
204 let logic = r#"{"+": [1, 2, 3]}"#;
206 let _ = compile_logic(logic).expect("Failed to compile");
207
208 let stats_after = get_store_stats();
209 assert!(stats_after.compiled_count >= stats_before.compiled_count);
213 assert!(stats_after.next_id >= stats_before.next_id);
214 }
215}