use std::collections::HashMap;
use std::path::Path;
use std::time::Instant;
pub use marsdb_graph::IntegrityReport;
pub use marsdb_graph::PropertyValue;
pub use marsdb_graph::TzId;
pub use marsdb_query::{
temporal, CancellationToken, ExecutionEvent, ExecutionObserver, ExecutionOptions,
ExecutionOutcome, Literal, PathElem, ProcedureProvider, ProcedureSignature, Procedures,
QueryResult, Value,
};
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("graph error: {0}")]
Graph(#[from] marsdb_graph::GraphError),
#[error("query error: {0}")]
Query(#[from] marsdb_query::QueryError),
#[error("transaction is no longer active")]
TransactionClosed,
}
pub struct Database {
store: marsdb_graph::GraphStore,
}
impl Database {
pub fn open(path: impl AsRef<Path>) -> Result<Self, Error> {
Ok(Self {
store: marsdb_graph::GraphStore::open_file(path)?,
})
}
pub fn in_memory() -> Result<Self, Error> {
Ok(Self {
store: marsdb_graph::GraphStore::open_memory()?,
})
}
pub fn begin_transaction(&self) -> Result<Transaction<'_>, Error> {
Ok(Transaction {
db: self,
inner: Some(self.store.begin_write()?),
})
}
pub fn backup_to(&self, path: impl AsRef<Path>) -> Result<(), Error> {
self.store.backup_to(path)?;
Ok(())
}
pub fn check_integrity(&mut self) -> Result<IntegrityReport, Error> {
Ok(self.store.check_integrity()?)
}
pub fn execute(&self, cypher: &str) -> Result<QueryResult, Error> {
self.execute_with_params(cypher, &HashMap::new())
}
pub fn execute_with_options(
&self,
cypher: &str,
options: &ExecutionOptions,
) -> Result<QueryResult, Error> {
self.execute_with_params_and_options(cypher, &HashMap::new(), options)
}
pub fn execute_with_params(
&self,
cypher: &str,
params: &HashMap<String, PropertyValue>,
) -> Result<QueryResult, Error> {
self.execute_with_params_and_options(cypher, params, &ExecutionOptions::default())
}
pub fn execute_with_params_and_options(
&self,
cypher: &str,
params: &HashMap<String, PropertyValue>,
options: &ExecutionOptions,
) -> Result<QueryResult, Error> {
let stmt = prepare_statement(cypher, params, options)?;
let options = with_call_params(options, params);
let result =
marsdb_query::Executor::new(&self.store).execute_with_options(&stmt, &options)?;
Ok(result)
}
pub fn execute_batch(&self, cypher: &str) -> Result<Vec<QueryResult>, Error> {
let stmts = marsdb_query::parse_many(cypher)?;
let executor = marsdb_query::Executor::new(&self.store);
stmts
.iter()
.map(|stmt| Ok(executor.execute(stmt)?))
.collect()
}
pub fn execute_batch_grouped(
&self,
cypher: &str,
group_size: usize,
) -> Result<Vec<QueryResult>, Error> {
let stmts = marsdb_query::parse_many(cypher)?;
let executor = marsdb_query::Executor::new(&self.store);
let mut results = Vec::with_capacity(stmts.len());
for group in stmts.chunks(group_size.max(1)) {
let write_txn = self.store.begin_write()?;
let mut group_results = Vec::with_capacity(group.len());
for stmt in group {
match executor.execute_in_write_transaction(stmt, &write_txn) {
Ok(result) => group_results.push(result),
Err(e) => {
let _ = marsdb_graph::GraphStore::abort(write_txn);
return Err(e.into());
}
}
}
marsdb_graph::GraphStore::commit(write_txn)?;
results.extend(group_results);
}
Ok(results)
}
}
pub struct Transaction<'db> {
db: &'db Database,
inner: Option<marsdb_graph::WriteTransaction>,
}
impl Transaction<'_> {
pub fn execute(&mut self, cypher: &str) -> Result<QueryResult, Error> {
self.execute_with_params_and_options(cypher, &HashMap::new(), &ExecutionOptions::default())
}
pub fn execute_with_params(
&mut self,
cypher: &str,
params: &HashMap<String, PropertyValue>,
) -> Result<QueryResult, Error> {
self.execute_with_params_and_options(cypher, params, &ExecutionOptions::default())
}
pub fn execute_with_options(
&mut self,
cypher: &str,
options: &ExecutionOptions,
) -> Result<QueryResult, Error> {
self.execute_with_params_and_options(cypher, &HashMap::new(), options)
}
pub fn execute_with_params_and_options(
&mut self,
cypher: &str,
params: &HashMap<String, PropertyValue>,
options: &ExecutionOptions,
) -> Result<QueryResult, Error> {
let Some(write_txn) = self.inner.as_ref() else {
return Err(Error::TransactionClosed);
};
let outcome = (|| {
let stmt = prepare_statement(cypher, params, options)?;
let options = with_call_params(options, params);
Ok(marsdb_query::Executor::new(&self.db.store)
.execute_in_write_transaction_with_options(&stmt, write_txn, &options)?)
})();
if outcome.is_err() {
if let Some(write_txn) = self.inner.take() {
marsdb_graph::GraphStore::abort(write_txn)?;
}
}
outcome
}
pub fn commit(mut self) -> Result<(), Error> {
let write_txn = self.inner.take().ok_or(Error::TransactionClosed)?;
marsdb_graph::GraphStore::commit(write_txn)?;
Ok(())
}
pub fn rollback(mut self) -> Result<(), Error> {
let write_txn = self.inner.take().ok_or(Error::TransactionClosed)?;
marsdb_graph::GraphStore::abort(write_txn)?;
Ok(())
}
}
fn with_call_params(
options: &ExecutionOptions,
params: &HashMap<String, PropertyValue>,
) -> ExecutionOptions {
let mut options = options.clone();
options.params = params.clone();
options
}
fn prepare_statement(
cypher: &str,
params: &HashMap<String, PropertyValue>,
options: &ExecutionOptions,
) -> Result<marsdb_query::Statement, Error> {
let started = Instant::now();
let mut stmt = match marsdb_query::parse(cypher) {
Ok(stmt) => stmt,
Err(error) => {
observe_rejected_statement(options, started, None, &error);
return Err(error.into());
}
};
if let Err(error) = marsdb_query::substitute_params(&mut stmt, params) {
observe_rejected_statement(
options,
started,
Some(marsdb_query::is_read_only(&stmt)),
&error,
);
return Err(error.into());
}
Ok(stmt)
}
fn observe_rejected_statement(
options: &ExecutionOptions,
started: Instant,
statement_read_only: Option<bool>,
error: &marsdb_query::QueryError,
) {
if let Some(observer) = &options.observer {
observer.observe(&ExecutionEvent {
elapsed: started.elapsed(),
statement_read_only,
result_rows: None,
relationship_expansions: 0,
outcome: ExecutionOutcome::from_error(error),
});
}
}