1use std::collections::HashMap;
11use std::sync::Arc;
12
13use crate::api::UniInner;
14use crate::api::impl_locy::LocyRuleRegistry;
15use crate::api::locy_result::LocyResult;
16use uni_common::{Result, UniError, Value};
17use uni_locy::LocyConfig;
18use uni_query::QueryResult;
19
20struct PreparedQueryInner {
24 ast: uni_query::CypherQuery,
25 plan: uni_query::LogicalPlan,
26 schema_version: u32,
27}
28
29pub struct PreparedQuery {
38 db: Arc<UniInner>,
39 query_text: String,
40 inner: std::sync::RwLock<PreparedQueryInner>,
41}
42
43impl PreparedQuery {
44 pub(crate) async fn new(db: Arc<UniInner>, cypher: &str) -> Result<Self> {
46 let ast = uni_cypher::parse(cypher).map_err(|e| UniError::Parse {
47 message: e.to_string(),
48 position: None,
49 line: None,
50 column: None,
51 context: Some(cypher.to_string()),
52 })?;
53
54 let schema_version = db.schema.schema().schema_version;
55 let planner = uni_query::QueryPlanner::new(db.schema.schema().clone());
56 let plan = planner.plan(ast.clone()).map_err(|e| UniError::Query {
57 message: e.to_string(),
58 query: Some(cypher.to_string()),
59 })?;
60
61 Ok(Self {
62 db,
63 query_text: cypher.to_string(),
64 inner: std::sync::RwLock::new(PreparedQueryInner {
65 ast,
66 plan,
67 schema_version,
68 }),
69 })
70 }
71
72 pub async fn execute(&self, params: &[(&str, Value)]) -> Result<QueryResult> {
77 self.ensure_plan_fresh()?;
78
79 let param_map: HashMap<String, Value> = params
80 .iter()
81 .map(|(k, v)| (k.to_string(), v.clone()))
82 .collect();
83
84 let plan = {
85 let inner = self.inner.read().unwrap();
86 inner.plan.clone()
87 };
88
89 self.db
90 .execute_plan_internal(
91 plan,
92 &self.query_text,
93 param_map,
94 self.db.config.clone(),
95 None,
96 )
97 .await
98 }
99
100 pub fn bind(&self) -> PreparedQueryBinder<'_> {
102 PreparedQueryBinder {
103 prepared: self,
104 params: HashMap::new(),
105 }
106 }
107
108 pub fn query_text(&self) -> &str {
110 &self.query_text
111 }
112
113 fn ensure_plan_fresh(&self) -> Result<()> {
115 let current_version = self.db.schema.schema().schema_version;
116
117 {
119 let inner = self.inner.read().unwrap();
120 if inner.schema_version == current_version {
121 return Ok(());
122 }
123 }
124
125 let mut inner = self.inner.write().unwrap();
127 if inner.schema_version == current_version {
128 return Ok(());
129 }
130
131 let planner = uni_query::QueryPlanner::new(self.db.schema.schema().clone());
132 inner.plan = planner
133 .plan(inner.ast.clone())
134 .map_err(|e| UniError::Query {
135 message: e.to_string(),
136 query: Some(self.query_text.clone()),
137 })?;
138 inner.schema_version = current_version;
139 Ok(())
140 }
141}
142
143pub struct PreparedQueryBinder<'a> {
145 prepared: &'a PreparedQuery,
146 params: HashMap<String, Value>,
147}
148
149impl<'a> PreparedQueryBinder<'a> {
150 pub fn param<K: Into<String>, V: Into<Value>>(mut self, key: K, value: V) -> Self {
152 self.params.insert(key.into(), value.into());
153 self
154 }
155
156 pub async fn execute(self) -> Result<QueryResult> {
158 self.prepared.ensure_plan_fresh()?;
159
160 let plan = {
161 let inner = self.prepared.inner.read().unwrap();
162 inner.plan.clone()
163 };
164
165 self.prepared
166 .db
167 .execute_plan_internal(
168 plan,
169 &self.prepared.query_text,
170 self.params,
171 self.prepared.db.config.clone(),
172 None,
173 )
174 .await
175 }
176}
177
178struct PreparedLocyInner {
182 compiled: uni_locy::CompiledProgram,
183 schema_version: u32,
184}
185
186pub struct PreparedLocy {
195 db: Arc<UniInner>,
196 rule_registry: Arc<std::sync::RwLock<LocyRuleRegistry>>,
197 program_text: String,
198 inner: std::sync::RwLock<PreparedLocyInner>,
199}
200
201impl PreparedLocy {
202 pub(crate) fn new(
204 db: Arc<UniInner>,
205 rule_registry: Arc<std::sync::RwLock<LocyRuleRegistry>>,
206 program: &str,
207 ) -> Result<Self> {
208 let compiled = compile_locy_with_registry(program, &rule_registry)?;
209 let schema_version = db.schema.schema().schema_version;
210
211 Ok(Self {
212 db,
213 rule_registry,
214 program_text: program.to_string(),
215 inner: std::sync::RwLock::new(PreparedLocyInner {
216 compiled,
217 schema_version,
218 }),
219 })
220 }
221
222 pub async fn execute(&self, params: &[(&str, Value)]) -> Result<LocyResult> {
227 let param_map: HashMap<String, Value> = params
228 .iter()
229 .map(|(k, v)| (k.to_string(), v.clone()))
230 .collect();
231 self.execute_internal(param_map).await
232 }
233
234 pub fn bind(&self) -> PreparedLocyBinder<'_> {
236 PreparedLocyBinder {
237 prepared: self,
238 params: HashMap::new(),
239 }
240 }
241
242 pub fn program_text(&self) -> &str {
244 &self.program_text
245 }
246
247 async fn execute_internal(&self, params: HashMap<String, Value>) -> Result<LocyResult> {
249 self.ensure_compiled_fresh()?;
250
251 let mut compiled = {
253 let inner = self.inner.read().unwrap();
254 inner.compiled.clone()
255 };
256
257 {
259 let registry = self.rule_registry.read().unwrap();
260 if !registry.rules.is_empty() {
261 for (name, rule) in ®istry.rules {
262 compiled
263 .rule_catalog
264 .entry(name.clone())
265 .or_insert_with(|| rule.clone());
266 }
267 let base_id = registry.strata.len();
268 for stratum in &mut compiled.strata {
269 stratum.id += base_id;
270 stratum.depends_on = stratum.depends_on.iter().map(|d| d + base_id).collect();
271 }
272 let mut merged_strata = registry.strata.clone();
273 merged_strata.append(&mut compiled.strata);
274 compiled.strata = merged_strata;
275 }
276 }
277
278 let config = LocyConfig {
280 params,
281 ..LocyConfig::default()
282 };
283
284 let engine = crate::api::impl_locy::LocyEngine {
285 db: &self.db,
286 tx_l0_override: None,
287 locy_l0: None,
288 collect_derive: true,
289 };
290 engine
291 .evaluate_compiled_with_config(compiled, &config)
292 .await
293 }
294
295 fn ensure_compiled_fresh(&self) -> Result<()> {
297 let current_version = self.db.schema.schema().schema_version;
298
299 {
301 let inner = self.inner.read().unwrap();
302 if inner.schema_version == current_version {
303 return Ok(());
304 }
305 }
306
307 let mut inner = self.inner.write().unwrap();
309 if inner.schema_version == current_version {
310 return Ok(());
311 }
312
313 inner.compiled = compile_locy_with_registry(&self.program_text, &self.rule_registry)?;
314 inner.schema_version = current_version;
315 Ok(())
316 }
317}
318
319fn compile_locy_with_registry(
323 program: &str,
324 rule_registry: &std::sync::RwLock<LocyRuleRegistry>,
325) -> Result<uni_locy::CompiledProgram> {
326 let ast = uni_cypher::parse_locy(program).map_err(|e| UniError::Parse {
327 message: format!("LocyParseError: {e}"),
328 position: None,
329 line: None,
330 column: None,
331 context: None,
332 })?;
333
334 let registry = rule_registry.read().unwrap();
335 if registry.rules.is_empty() {
336 drop(registry);
337 uni_locy::compile(&ast).map_err(|e| UniError::Query {
338 message: format!("LocyCompileError: {e}"),
339 query: None,
340 })
341 } else {
342 let external_names: Vec<String> = registry.rules.keys().cloned().collect();
343 drop(registry);
344 uni_locy::compile_with_external_rules(&ast, &external_names).map_err(|e| UniError::Query {
345 message: format!("LocyCompileError: {e}"),
346 query: None,
347 })
348 }
349}
350
351pub struct PreparedLocyBinder<'a> {
353 prepared: &'a PreparedLocy,
354 params: HashMap<String, Value>,
355}
356
357impl<'a> PreparedLocyBinder<'a> {
358 pub fn param<K: Into<String>, V: Into<Value>>(mut self, key: K, value: V) -> Self {
360 self.params.insert(key.into(), value.into());
361 self
362 }
363
364 pub async fn execute(self) -> Result<LocyResult> {
366 self.prepared.execute_internal(self.params).await
367 }
368}