Skip to main content

lora_database/
database.rs

1use std::collections::BTreeMap;
2use std::sync::{Arc, Mutex, MutexGuard};
3
4use anyhow::Result;
5use lora_analyzer::Analyzer;
6use lora_ast::Document;
7use lora_compiler::{CompiledQuery, Compiler};
8use lora_executor::{
9    ExecuteOptions, LoraValue, MutableExecutionContext, MutableExecutor, QueryResult,
10};
11use lora_parser::parse_query;
12use lora_store::{GraphStorage, GraphStorageMut, InMemoryGraph};
13
14/// Minimal abstraction any transport can depend on to run Lora queries.
15pub trait QueryRunner: Send + Sync + 'static {
16    fn execute(&self, query: &str, options: Option<ExecuteOptions>) -> Result<QueryResult>;
17}
18
19/// Owns the graph store and orchestrates parse → analyze → compile → execute.
20pub struct Database<S> {
21    store: Arc<Mutex<S>>,
22}
23
24impl Database<InMemoryGraph> {
25    /// Convenience constructor: a fresh, empty in-memory graph database.
26    pub fn in_memory() -> Self {
27        Self::from_graph(InMemoryGraph::new())
28    }
29}
30
31impl<S> Database<S>
32where
33    S: GraphStorage + GraphStorageMut,
34{
35    /// Build a database from a pre-wrapped, shared store.
36    pub fn new(store: Arc<Mutex<S>>) -> Self {
37        Self { store }
38    }
39
40    /// Build a database by taking ownership of a bare graph store.
41    pub fn from_graph(graph: S) -> Self {
42        Self::new(Arc::new(Mutex::new(graph)))
43    }
44
45    /// Handle to the underlying shared store — useful for callers that need
46    /// to snapshot or share the graph across multiple databases.
47    pub fn store(&self) -> &Arc<Mutex<S>> {
48        &self.store
49    }
50
51    /// Parse a query string into an AST without executing it.
52    pub fn parse(&self, query: &str) -> Result<Document> {
53        Ok(parse_query(query)?)
54    }
55
56    fn lock_store(&self) -> MutexGuard<'_, S> {
57        self.store
58            .lock()
59            .unwrap_or_else(|poisoned| poisoned.into_inner())
60    }
61
62    fn compile_query(&self, query: &str) -> Result<(MutexGuard<'_, S>, CompiledQuery)> {
63        let document = self.parse(query)?;
64        let store = self.lock_store();
65
66        let resolved = {
67            let mut analyzer = Analyzer::new(&*store);
68            analyzer.analyze(&document)?
69        };
70
71        let compiled = Compiler::compile(&resolved);
72        Ok((store, compiled))
73    }
74
75    /// Execute a query and return its result.
76    pub fn execute(&self, query: &str, options: Option<ExecuteOptions>) -> Result<QueryResult> {
77        self.execute_with_params(query, options, BTreeMap::new())
78    }
79
80    /// Execute a query with bound parameters.
81    pub fn execute_with_params(
82        &self,
83        query: &str,
84        options: Option<ExecuteOptions>,
85        params: BTreeMap<String, LoraValue>,
86    ) -> Result<QueryResult> {
87        let (mut store, compiled) = self.compile_query(query)?;
88
89        let mut executor = MutableExecutor::new(MutableExecutionContext {
90            storage: &mut *store,
91            params,
92        });
93
94        Ok(executor.execute_compiled(&compiled, options)?)
95    }
96}
97
98impl<S> QueryRunner for Database<S>
99where
100    S: GraphStorage + GraphStorageMut + Send + 'static,
101{
102    fn execute(&self, query: &str, options: Option<ExecuteOptions>) -> Result<QueryResult> {
103        Database::execute(self, query, options)
104    }
105}