json_eval_rs/rlogic/
compiled_logic_store.rs1use super::CompiledLogic;
7use dashmap::DashMap;
8use once_cell::sync::Lazy;
9use rapidhash::fast::RapidHasher;
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
38#[inline]
41fn hash_value(value: &serde_json::Value, hasher: &mut RapidHasher) {
42 match value {
43 serde_json::Value::Null => 0u8.hash(hasher),
44 serde_json::Value::Bool(b) => {
45 1u8.hash(hasher);
46 b.hash(hasher);
47 }
48 serde_json::Value::Number(n) => {
49 2u8.hash(hasher);
50 n.as_f64().unwrap_or(0.0).to_bits().hash(hasher);
51 }
52 serde_json::Value::String(s) => {
53 3u8.hash(hasher);
54 s.hash(hasher);
55 }
56 serde_json::Value::Array(arr) => {
57 4u8.hash(hasher);
58 arr.len().hash(hasher);
59 for item in arr {
60 hash_value(item, hasher);
61 }
62 }
63 serde_json::Value::Object(obj) => {
64 5u8.hash(hasher);
65 obj.len().hash(hasher);
66 for (k, v) in obj {
67 k.hash(hasher);
68 hash_value(v, hasher);
69 }
70 }
71 }
72}
73
74struct CompiledLogicStore {
76 store: DashMap<u64, (CompiledLogicId, CompiledLogic)>,
78 id_map: DashMap<u64, CompiledLogic>,
80 next_id: AtomicU64,
82}
83
84impl CompiledLogicStore {
85 fn compile_value(&self, logic: &serde_json::Value) -> Result<CompiledLogicId, String> {
88 let mut hasher = RapidHasher::default();
89 hash_value(logic, &mut hasher);
90 let hash = hasher.finish();
91
92 if let Some(entry) = self.store.get(&hash) {
94 return Ok(entry.0);
95 }
96
97 let compiled = CompiledLogic::compile(logic)?;
100
101 match self.store.entry(hash) {
103 dashmap::mapref::entry::Entry::Occupied(o) => {
104 Ok(o.get().0)
106 }
107 dashmap::mapref::entry::Entry::Vacant(v) => {
108 let id = CompiledLogicId(self.next_id.fetch_add(1, Ordering::SeqCst));
110 self.id_map.insert(id.0, compiled.clone());
112 v.insert((id, compiled));
113 Ok(id)
114 }
115 }
116 }
117
118 fn compile(&self, logic_json: &str) -> Result<CompiledLogicId, String> {
121 let logic: serde_json::Value = serde_json::from_str(logic_json)
123 .map_err(|e| format!("Failed to parse logic JSON: {}", e))?;
124
125 self.compile_value(&logic)
127 }
128
129 fn get(&self, id: CompiledLogicId) -> Option<CompiledLogic> {
131 self.id_map.get(&id.0).map(|v| v.clone())
132 }
133
134 fn stats(&self) -> CompiledLogicStoreStats {
136 CompiledLogicStoreStats {
137 compiled_count: self.store.len(),
138 next_id: self.next_id.load(Ordering::SeqCst),
139 }
140 }
141
142 #[allow(dead_code)]
144 fn clear(&self) {
145 self.store.clear();
146 self.id_map.clear();
147 self.next_id.store(1, Ordering::SeqCst);
148 }
149}
150
151#[derive(Debug, Clone)]
153pub struct CompiledLogicStoreStats {
154 pub compiled_count: usize,
156 pub next_id: u64,
158}
159
160pub fn compile_logic(logic_json: &str) -> Result<CompiledLogicId, String> {
165 COMPILED_LOGIC_STORE.compile(logic_json)
166}
167
168pub fn compile_logic_value(logic: &serde_json::Value) -> Result<CompiledLogicId, String> {
173 COMPILED_LOGIC_STORE.compile_value(logic)
174}
175
176pub fn get_compiled_logic(id: CompiledLogicId) -> Option<CompiledLogic> {
178 COMPILED_LOGIC_STORE.get(id)
179}
180
181pub fn get_store_stats() -> CompiledLogicStoreStats {
183 COMPILED_LOGIC_STORE.stats()
184}
185
186#[cfg(test)]
190pub fn clear_store() {
191 COMPILED_LOGIC_STORE.clear()
192}
193
194#[cfg(test)]
195mod tests {
196 use super::*;
197
198 #[test]
199 fn test_compile_and_get() {
200 let logic = r#"{"==": [{"var": "x"}, 10]}"#;
203 let id = compile_logic(logic).expect("Failed to compile");
204
205 let compiled = get_compiled_logic(id);
206 assert!(compiled.is_some());
207 }
208
209 #[test]
210 fn test_deduplication() {
211 let logic = r#"{"*": [{"var": "a"}, 2]}"#;
212
213 let id1 = compile_logic(logic).expect("Failed to compile");
214 let id2 = compile_logic(logic).expect("Failed to compile");
215
216 assert_eq!(id1, id2);
218 }
219
220 #[test]
221 fn test_different_logic() {
222 let logic1 = r#"{"*": [{"var": "a"}, 2]}"#;
223 let logic2 = r#"{"*": [{"var": "b"}, 3]}"#;
224
225 let id1 = compile_logic(logic1).expect("Failed to compile");
226 let id2 = compile_logic(logic2).expect("Failed to compile");
227
228 assert_ne!(id1, id2);
230 }
231
232 #[test]
233 fn test_stats() {
234 let stats_before = get_store_stats();
236
237 let logic = r#"{"+": [1, 2, 3]}"#;
239 let _ = compile_logic(logic).expect("Failed to compile");
240
241 let stats_after = get_store_stats();
242 assert!(stats_after.compiled_count >= stats_before.compiled_count);
246 assert!(stats_after.next_id >= stats_before.next_id);
247 }
248}