Skip to main content

jetro_core/
engine.rs

1//! Top-level execution handle.
2//!
3//! [`Engine`] wraps the bytecode [`VM`] behind a `Send + Sync` façade so
4//! long-lived services can share compile + pointer caches across threads.
5//!
6//! ```text
7//! Engine ──► Mutex<VM> ──► compile cache
8//!                      └─► path cache
9//! ```
10//!
11//! Use [`Engine::new`] once at boot, pass `Arc<Engine>` wherever you need
12//! to run queries, and the caches warm up organically. [`Jetro`] is the
13//! thread-local convenience built on the same VM type — pick whichever
14//! fits the call site.
15
16use std::sync::Arc;
17
18use parking_lot::Mutex;
19use serde_json::Value;
20
21use crate::eval::methods::MethodRegistry;
22use crate::vm::{PassConfig, VM};
23use crate::{Error, Result};
24
25/// Shared, thread-safe query engine.
26///
27/// Internally a `Mutex<VM>`; reads serialise but compilation and caching
28/// happen once per expression, so the lock is held only for the duration
29/// of an `execute` call. For single-threaded use prefer [`Jetro`], which
30/// keeps a dedicated VM per thread.
31pub struct Engine {
32    vm: Mutex<VM>,
33}
34
35impl Engine {
36    pub fn new() -> Arc<Self> {
37        Arc::new(Self { vm: Mutex::new(VM::new()) })
38    }
39
40    pub fn with_capacity(compile_cap: usize, path_cap: usize) -> Arc<Self> {
41        Arc::new(Self { vm: Mutex::new(VM::with_capacity(compile_cap, path_cap)) })
42    }
43
44    pub fn with_methods(registry: Arc<MethodRegistry>) -> Arc<Self> {
45        Arc::new(Self { vm: Mutex::new(VM::with_registry(registry)) })
46    }
47
48    /// Parse, compile (cached), and execute `expr` against `doc`.
49    pub fn run(&self, expr: &str, doc: &Value) -> Result<Value> {
50        self.vm.lock().run_str(expr, doc).map_err(Error::from)
51    }
52
53    pub fn register(
54        &self,
55        name: impl Into<String>,
56        method: impl crate::eval::methods::Method + 'static,
57    ) {
58        self.vm.lock().register(name, method);
59    }
60
61    pub fn set_pass_config(&self, config: PassConfig) {
62        self.vm.lock().set_pass_config(config);
63    }
64
65    pub fn pass_config(&self) -> PassConfig {
66        self.vm.lock().pass_config()
67    }
68}
69
70// ── Tests ─────────────────────────────────────────────────────────────────────
71
72#[cfg(test)]
73mod tests {
74    use super::*;
75    use serde_json::json;
76
77    #[test]
78    fn engine_runs_and_caches() {
79        let e = Engine::new();
80        let doc = json!({"x": [1, 2, 3, 4]});
81        assert_eq!(e.run("$.x.len()", &doc).unwrap(), json!(4));
82        // Rerun hits the compile cache — same answer, no panic.
83        assert_eq!(e.run("$.x.len()", &doc).unwrap(), json!(4));
84    }
85
86    #[test]
87    fn engine_is_shareable() {
88        let e = Engine::new();
89        let e2 = Arc::clone(&e);
90        let h = std::thread::spawn(move || {
91            let doc = json!({"n": 5});
92            e2.run("$.n", &doc).unwrap()
93        });
94        assert_eq!(h.join().unwrap(), json!(5));
95    }
96}