use std::collections::HashMap;
use std::sync::Arc;
use crate::api::UniInner;
use crate::api::impl_locy::LocyRuleRegistry;
use crate::api::locy_result::LocyResult;
use uni_common::{Result, UniError, Value};
use uni_locy::LocyConfig;
use uni_query::QueryResult;
struct PreparedQueryInner {
ast: uni_query::CypherQuery,
plan: uni_query::LogicalPlan,
schema_version: u32,
}
pub struct PreparedQuery {
db: Arc<UniInner>,
query_text: String,
inner: std::sync::RwLock<PreparedQueryInner>,
}
impl PreparedQuery {
pub(crate) async fn new(db: Arc<UniInner>, cypher: &str) -> Result<Self> {
let ast = uni_cypher::parse(cypher).map_err(|e| UniError::Parse {
message: e.to_string(),
position: None,
line: None,
column: None,
context: Some(cypher.to_string()),
})?;
let schema_version = db.schema.schema().schema_version;
let planner = uni_query::QueryPlanner::new(db.schema.schema().clone());
let plan = planner.plan(ast.clone()).map_err(|e| UniError::Query {
message: e.to_string(),
query: Some(cypher.to_string()),
})?;
Ok(Self {
db,
query_text: cypher.to_string(),
inner: std::sync::RwLock::new(PreparedQueryInner {
ast,
plan,
schema_version,
}),
})
}
pub async fn execute(&self, params: &[(&str, Value)]) -> Result<QueryResult> {
self.ensure_plan_fresh()?;
let param_map: HashMap<String, Value> = params
.iter()
.map(|(k, v)| (k.to_string(), v.clone()))
.collect();
let plan = {
let inner = self.inner.read().unwrap();
inner.plan.clone()
};
self.db
.execute_plan_internal(
plan,
&self.query_text,
param_map,
self.db.config.clone(),
None,
)
.await
}
pub fn bind(&self) -> PreparedQueryBinder<'_> {
PreparedQueryBinder {
prepared: self,
params: HashMap::new(),
}
}
pub fn query_text(&self) -> &str {
&self.query_text
}
fn ensure_plan_fresh(&self) -> Result<()> {
let current_version = self.db.schema.schema().schema_version;
{
let inner = self.inner.read().unwrap();
if inner.schema_version == current_version {
return Ok(());
}
}
let mut inner = self.inner.write().unwrap();
if inner.schema_version == current_version {
return Ok(());
}
let planner = uni_query::QueryPlanner::new(self.db.schema.schema().clone());
inner.plan = planner
.plan(inner.ast.clone())
.map_err(|e| UniError::Query {
message: e.to_string(),
query: Some(self.query_text.clone()),
})?;
inner.schema_version = current_version;
Ok(())
}
}
pub struct PreparedQueryBinder<'a> {
prepared: &'a PreparedQuery,
params: HashMap<String, Value>,
}
impl<'a> PreparedQueryBinder<'a> {
pub fn param<K: Into<String>, V: Into<Value>>(mut self, key: K, value: V) -> Self {
self.params.insert(key.into(), value.into());
self
}
pub async fn execute(self) -> Result<QueryResult> {
self.prepared.ensure_plan_fresh()?;
let plan = {
let inner = self.prepared.inner.read().unwrap();
inner.plan.clone()
};
self.prepared
.db
.execute_plan_internal(
plan,
&self.prepared.query_text,
self.params,
self.prepared.db.config.clone(),
None,
)
.await
}
}
struct PreparedLocyInner {
compiled: uni_locy::CompiledProgram,
schema_version: u32,
}
pub struct PreparedLocy {
db: Arc<UniInner>,
rule_registry: Arc<std::sync::RwLock<LocyRuleRegistry>>,
program_text: String,
inner: std::sync::RwLock<PreparedLocyInner>,
}
impl PreparedLocy {
pub(crate) fn new(
db: Arc<UniInner>,
rule_registry: Arc<std::sync::RwLock<LocyRuleRegistry>>,
program: &str,
) -> Result<Self> {
let compiled = compile_locy_with_registry(program, &rule_registry)?;
let schema_version = db.schema.schema().schema_version;
Ok(Self {
db,
rule_registry,
program_text: program.to_string(),
inner: std::sync::RwLock::new(PreparedLocyInner {
compiled,
schema_version,
}),
})
}
pub async fn execute(&self, params: &[(&str, Value)]) -> Result<LocyResult> {
let param_map: HashMap<String, Value> = params
.iter()
.map(|(k, v)| (k.to_string(), v.clone()))
.collect();
self.execute_internal(param_map).await
}
pub fn bind(&self) -> PreparedLocyBinder<'_> {
PreparedLocyBinder {
prepared: self,
params: HashMap::new(),
}
}
pub fn program_text(&self) -> &str {
&self.program_text
}
async fn execute_internal(&self, params: HashMap<String, Value>) -> Result<LocyResult> {
self.ensure_compiled_fresh()?;
let mut compiled = {
let inner = self.inner.read().unwrap();
inner.compiled.clone()
};
{
let registry = self.rule_registry.read().unwrap();
if !registry.rules.is_empty() {
for (name, rule) in ®istry.rules {
compiled
.rule_catalog
.entry(name.clone())
.or_insert_with(|| rule.clone());
}
let base_id = registry.strata.len();
for stratum in &mut compiled.strata {
stratum.id += base_id;
stratum.depends_on = stratum.depends_on.iter().map(|d| d + base_id).collect();
}
let mut merged_strata = registry.strata.clone();
merged_strata.append(&mut compiled.strata);
compiled.strata = merged_strata;
}
}
let config = LocyConfig {
params,
..LocyConfig::default()
};
let engine = crate::api::impl_locy::LocyEngine {
db: &self.db,
tx_l0_override: None,
locy_l0: None,
collect_derive: true,
};
engine
.evaluate_compiled_with_config(compiled, &config)
.await
}
fn ensure_compiled_fresh(&self) -> Result<()> {
let current_version = self.db.schema.schema().schema_version;
{
let inner = self.inner.read().unwrap();
if inner.schema_version == current_version {
return Ok(());
}
}
let mut inner = self.inner.write().unwrap();
if inner.schema_version == current_version {
return Ok(());
}
inner.compiled = compile_locy_with_registry(&self.program_text, &self.rule_registry)?;
inner.schema_version = current_version;
Ok(())
}
}
fn compile_locy_with_registry(
program: &str,
rule_registry: &std::sync::RwLock<LocyRuleRegistry>,
) -> Result<uni_locy::CompiledProgram> {
let ast = uni_cypher::parse_locy(program).map_err(|e| UniError::Parse {
message: format!("LocyParseError: {e}"),
position: None,
line: None,
column: None,
context: None,
})?;
let registry = rule_registry.read().unwrap();
if registry.rules.is_empty() {
drop(registry);
uni_locy::compile(&ast).map_err(|e| UniError::Query {
message: format!("LocyCompileError: {e}"),
query: None,
})
} else {
let external_names: Vec<String> = registry.rules.keys().cloned().collect();
drop(registry);
uni_locy::compile_with_external_rules(&ast, &external_names).map_err(|e| UniError::Query {
message: format!("LocyCompileError: {e}"),
query: None,
})
}
}
pub struct PreparedLocyBinder<'a> {
prepared: &'a PreparedLocy,
params: HashMap<String, Value>,
}
impl<'a> PreparedLocyBinder<'a> {
pub fn param<K: Into<String>, V: Into<Value>>(mut self, key: K, value: V) -> Self {
self.params.insert(key.into(), value.into());
self
}
pub async fn execute(self) -> Result<LocyResult> {
self.prepared.execute_internal(self.params).await
}
}