macro_rules! bail {
($e:expr) => {
return ::std::result::Result::Err(::std::convert::From::from($e))
};
}
extern crate rusqlite;
extern crate core_traits;
extern crate db_traits;
extern crate edn;
extern crate mentat_core;
extern crate mentat_db;
extern crate mentat_query_algebrizer;
extern crate mentat_query_projector;
extern crate mentat_query_pull;
extern crate mentat_sql;
extern crate public_traits;
use std::sync::{Arc, Mutex};
use std::io::Read;
use std::borrow::Borrow;
use std::collections::BTreeMap;
use std::fs::File;
use std::path::Path;
use edn::entities::{OpType, TempId};
use edn::{InternSet, Keyword};
use core_traits::{Attribute, Entid, KnownEntid, StructuredMap, TypedValue, ValueType};
use public_traits::errors::{MentatError, Result};
use mentat_core::{HasSchema, Schema, TxReport, ValueRc};
use mentat_query_pull::{pull_attributes_for_entities, pull_attributes_for_entity};
use mentat_db::{
transact, transact_terms, InProgressObserverTransactWatcher, PartitionMap, TransactWatcher,
TransactableValue, TxObservationService,
};
use mentat_db::internal_types::TermWithTempIds;
use mentat_db::cache::{InProgressCacheTransactWatcher, InProgressSQLiteAttributeCache};
pub mod entity_builder;
pub mod metadata;
pub mod options;
pub mod query;
pub use crate::entity_builder::{InProgressBuilder, TermBuilder};
pub use crate::metadata::Metadata;
use crate::query::{
lookup_value_for_attribute, lookup_values_for_attribute, q_explain, q_once, q_prepare,
q_uncached, Known, PreparedResult, QueryExplanation, QueryInputs, QueryOutput,
};
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum CacheDirection {
Forward,
Reverse,
Both,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum CacheAction {
Register,
Deregister,
}
pub struct InProgress<'a, 'c> {
pub transaction: rusqlite::Transaction<'c>,
pub mutex: &'a Mutex<Metadata>,
pub generation: u64,
pub partition_map: PartitionMap,
pub schema: Schema,
pub cache: InProgressSQLiteAttributeCache,
pub use_caching: bool,
pub tx_observer: &'a Mutex<TxObservationService>,
pub tx_observer_watcher: InProgressObserverTransactWatcher,
}
pub struct InProgressRead<'a, 'c> {
pub in_progress: InProgress<'a, 'c>,
}
pub trait Queryable {
fn q_explain<T>(&self, query: &str, inputs: T) -> Result<QueryExplanation>
where
T: Into<Option<QueryInputs>>;
fn q_once<T>(&self, query: &str, inputs: T) -> Result<QueryOutput>
where
T: Into<Option<QueryInputs>>;
fn q_prepare<T>(&self, query: &str, inputs: T) -> PreparedResult<'_>
where
T: Into<Option<QueryInputs>>;
fn lookup_values_for_attribute<E>(
&self,
entity: E,
attribute: &edn::Keyword,
) -> Result<Vec<TypedValue>>
where
E: Into<Entid>;
fn lookup_value_for_attribute<E>(
&self,
entity: E,
attribute: &edn::Keyword,
) -> Result<Option<TypedValue>>
where
E: Into<Entid>;
}
pub trait Pullable {
fn pull_attributes_for_entities<E, A>(
&self,
entities: E,
attributes: A,
) -> Result<BTreeMap<Entid, ValueRc<StructuredMap>>>
where
E: IntoIterator<Item = Entid>,
A: IntoIterator<Item = Entid>;
fn pull_attributes_for_entity<A>(&self, entity: Entid, attributes: A) -> Result<StructuredMap>
where
A: IntoIterator<Item = Entid>;
}
impl<'a, 'c> InProgress<'a, 'c> {
pub fn builder(self) -> InProgressBuilder<'a, 'c> {
InProgressBuilder::new(self)
}
pub fn use_caching(&mut self, yesno: bool) {
self.use_caching = yesno;
}
pub fn transact_builder(&mut self, builder: TermBuilder) -> Result<TxReport> {
builder
.build()
.and_then(|(terms, _tempid_set)| self.transact_entities(terms))
}
pub fn transact_terms<I>(&mut self, terms: I, tempid_set: InternSet<TempId>) -> Result<TxReport>
where
I: IntoIterator<Item = TermWithTempIds>,
{
let w = InProgressTransactWatcher::new(
&mut self.tx_observer_watcher,
self.cache.transact_watcher(),
);
let (report, next_partition_map, next_schema, _watcher) = transact_terms(
&self.transaction,
self.partition_map.clone(),
&self.schema,
&self.schema,
w,
terms,
tempid_set,
)?;
self.partition_map = next_partition_map;
if let Some(schema) = next_schema {
self.schema = schema;
}
Ok(report)
}
pub fn transact_entities<I, V: TransactableValue>(&mut self, entities: I) -> Result<TxReport>
where
I: IntoIterator<Item = edn::entities::Entity<V>>,
{
let w = InProgressTransactWatcher::new(
&mut self.tx_observer_watcher,
self.cache.transact_watcher(),
);
let (report, next_partition_map, next_schema, _watcher) = transact(
&self.transaction,
self.partition_map.clone(),
&self.schema,
&self.schema,
w,
entities,
)?;
self.partition_map = next_partition_map;
if let Some(schema) = next_schema {
self.schema = schema;
}
Ok(report)
}
pub fn transact<B>(&mut self, transaction: B) -> Result<TxReport>
where
B: Borrow<str>,
{
let entities = edn::parse::entities(transaction.borrow())?;
self.transact_entities(entities)
}
pub fn transact_speculative<B>(&mut self, transaction: B) -> Result<TxReport>
where
B: Borrow<str>,
{
let entities = edn::parse::entities(transaction.borrow())?;
self.transact_entities_speculative(entities)
}
pub fn transact_entities_speculative<I, V: TransactableValue>(
&mut self,
entities: I,
) -> Result<TxReport>
where
I: IntoIterator<Item = edn::entities::Entity<V>>,
{
const SAVEPOINT: &str = "mentat_speculative";
let saved_partition_map = self.partition_map.clone();
let saved_schema = self.schema.clone();
self.savepoint(SAVEPOINT)?;
let result = self.transact_entities(entities);
let rollback = self
.rollback_savepoint(SAVEPOINT)
.and_then(|()| self.release_savepoint(SAVEPOINT));
self.partition_map = saved_partition_map;
self.schema = saved_schema;
rollback?;
result
}
pub fn import<P>(&mut self, path: P) -> Result<TxReport>
where
P: AsRef<Path>,
{
let mut file = File::open(path)?;
let mut text: String = String::new();
file.read_to_string(&mut text)?;
self.transact(text.as_str())
}
pub fn rollback(self) -> Result<()> {
self.transaction.rollback().map_err(|e| e.into())
}
pub fn commit(self) -> Result<()> {
let mut metadata = self
.mutex
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
if self.generation != metadata.generation {
bail!(MentatError::UnexpectedLostTransactRace);
}
let conn = unsafe { rusqlite::Connection::from_handle(self.transaction.handle()) }?;
self.transaction.commit()?;
mentat_db::db::restart_wal_if_large(&conn);
drop(conn);
metadata.generation += 1;
metadata.partition_map = self.partition_map;
self.cache.commit_to(&mut metadata.attribute_cache)?;
if self.schema != *(metadata.schema) {
metadata.schema = Arc::new(self.schema);
}
let txes = self.tx_observer_watcher.txes;
self.tx_observer
.lock()
.unwrap()
.in_progress_did_commit(txes);
Ok(())
}
pub fn cache(
&mut self,
attribute: &Keyword,
cache_direction: CacheDirection,
cache_action: CacheAction,
) -> Result<()> {
let attribute_entid: Entid = self
.schema
.attribute_for_ident(attribute)
.ok_or_else(|| MentatError::UnknownAttribute(attribute.to_string()))?
.1
.into();
match cache_action {
CacheAction::Register => match cache_direction {
CacheDirection::Both => {
self.cache
.register(&self.schema, &self.transaction, attribute_entid)
}
CacheDirection::Forward => {
self.cache
.register_forward(&self.schema, &self.transaction, attribute_entid)
}
CacheDirection::Reverse => {
self.cache
.register_reverse(&self.schema, &self.transaction, attribute_entid)
}
}
.map_err(|e| e.into()),
CacheAction::Deregister => {
self.cache.unregister(attribute_entid);
Ok(())
}
}
}
pub fn last_tx_id(&self) -> Entid {
self.partition_map[":db.part/tx"].next_entid() - 1
}
pub fn savepoint(&self, name: &str) -> Result<()> {
self.transaction
.execute(&format!("SAVEPOINT {}", name), rusqlite::params![])?;
Ok(())
}
pub fn rollback_savepoint(&self, name: &str) -> Result<()> {
self.transaction
.execute(&format!("ROLLBACK TO {}", name), rusqlite::params![])?;
Ok(())
}
pub fn release_savepoint(&self, name: &str) -> Result<()> {
self.transaction
.execute(&format!("RELEASE {}", name), rusqlite::params![])?;
Ok(())
}
}
impl<'a, 'c> InProgressRead<'a, 'c> {
pub fn last_tx_id(&self) -> Entid {
self.in_progress.last_tx_id()
}
}
impl<'a, 'c> Queryable for InProgressRead<'a, 'c> {
fn q_once<T>(&self, query: &str, inputs: T) -> Result<QueryOutput>
where
T: Into<Option<QueryInputs>>,
{
self.in_progress.q_once(query, inputs)
}
fn q_prepare<T>(&self, query: &str, inputs: T) -> PreparedResult<'_>
where
T: Into<Option<QueryInputs>>,
{
self.in_progress.q_prepare(query, inputs)
}
fn q_explain<T>(&self, query: &str, inputs: T) -> Result<QueryExplanation>
where
T: Into<Option<QueryInputs>>,
{
self.in_progress.q_explain(query, inputs)
}
fn lookup_values_for_attribute<E>(
&self,
entity: E,
attribute: &edn::Keyword,
) -> Result<Vec<TypedValue>>
where
E: Into<Entid>,
{
self.in_progress
.lookup_values_for_attribute(entity, attribute)
}
fn lookup_value_for_attribute<E>(
&self,
entity: E,
attribute: &edn::Keyword,
) -> Result<Option<TypedValue>>
where
E: Into<Entid>,
{
self.in_progress
.lookup_value_for_attribute(entity, attribute)
}
}
impl<'a, 'c> Pullable for InProgressRead<'a, 'c> {
fn pull_attributes_for_entities<E, A>(
&self,
entities: E,
attributes: A,
) -> Result<BTreeMap<Entid, ValueRc<StructuredMap>>>
where
E: IntoIterator<Item = Entid>,
A: IntoIterator<Item = Entid>,
{
self.in_progress
.pull_attributes_for_entities(entities, attributes)
}
fn pull_attributes_for_entity<A>(&self, entity: Entid, attributes: A) -> Result<StructuredMap>
where
A: IntoIterator<Item = Entid>,
{
self.in_progress
.pull_attributes_for_entity(entity, attributes)
}
}
impl<'a, 'c> Queryable for InProgress<'a, 'c> {
fn q_once<T>(&self, query: &str, inputs: T) -> Result<QueryOutput>
where
T: Into<Option<QueryInputs>>,
{
if self.use_caching {
let known = Known::new(&self.schema, Some(&self.cache));
q_once(&(self.transaction), known, query, inputs)
} else {
q_uncached(&(self.transaction), &self.schema, query, inputs)
}
}
fn q_prepare<T>(&self, query: &str, inputs: T) -> PreparedResult<'_>
where
T: Into<Option<QueryInputs>>,
{
let known = Known::new(&self.schema, Some(&self.cache));
q_prepare(&(self.transaction), known, query, inputs)
}
fn q_explain<T>(&self, query: &str, inputs: T) -> Result<QueryExplanation>
where
T: Into<Option<QueryInputs>>,
{
let known = Known::new(&self.schema, Some(&self.cache));
q_explain(&(self.transaction), known, query, inputs)
}
fn lookup_values_for_attribute<E>(
&self,
entity: E,
attribute: &edn::Keyword,
) -> Result<Vec<TypedValue>>
where
E: Into<Entid>,
{
let known = Known::new(&self.schema, Some(&self.cache));
lookup_values_for_attribute(&(self.transaction), known, entity, attribute)
}
fn lookup_value_for_attribute<E>(
&self,
entity: E,
attribute: &edn::Keyword,
) -> Result<Option<TypedValue>>
where
E: Into<Entid>,
{
let known = Known::new(&self.schema, Some(&self.cache));
lookup_value_for_attribute(&(self.transaction), known, entity, attribute)
}
}
impl<'a, 'c> Pullable for InProgress<'a, 'c> {
fn pull_attributes_for_entities<E, A>(
&self,
entities: E,
attributes: A,
) -> Result<BTreeMap<Entid, ValueRc<StructuredMap>>>
where
E: IntoIterator<Item = Entid>,
A: IntoIterator<Item = Entid>,
{
pull_attributes_for_entities(
&self.schema,
&mentat_sql::sqlite(&self.transaction),
entities,
attributes,
)
.map_err(|e| e.into())
}
fn pull_attributes_for_entity<A>(&self, entity: Entid, attributes: A) -> Result<StructuredMap>
where
A: IntoIterator<Item = Entid>,
{
pull_attributes_for_entity(
&self.schema,
&mentat_sql::sqlite(&self.transaction),
entity,
attributes,
)
.map_err(|e| e.into())
}
}
impl<'a, 'c> HasSchema for InProgressRead<'a, 'c> {
fn entid_for_type(&self, t: ValueType) -> Option<KnownEntid> {
self.in_progress.entid_for_type(t)
}
fn get_ident<T>(&self, x: T) -> Option<&Keyword>
where
T: Into<Entid>,
{
self.in_progress.get_ident(x)
}
fn get_entid(&self, x: &Keyword) -> Option<KnownEntid> {
self.in_progress.get_entid(x)
}
fn attribute_for_entid<T>(&self, x: T) -> Option<&Attribute>
where
T: Into<Entid>,
{
self.in_progress.attribute_for_entid(x)
}
fn attribute_for_ident(&self, ident: &Keyword) -> Option<(&Attribute, KnownEntid)> {
self.in_progress.attribute_for_ident(ident)
}
fn is_attribute<T>(&self, x: T) -> bool
where
T: Into<Entid>,
{
self.in_progress.is_attribute(x)
}
fn identifies_attribute(&self, x: &Keyword) -> bool {
self.in_progress.identifies_attribute(x)
}
fn component_attributes(&self) -> &[Entid] {
self.in_progress.component_attributes()
}
}
impl<'a, 'c> HasSchema for InProgress<'a, 'c> {
fn entid_for_type(&self, t: ValueType) -> Option<KnownEntid> {
self.schema.entid_for_type(t)
}
fn get_ident<T>(&self, x: T) -> Option<&Keyword>
where
T: Into<Entid>,
{
self.schema.get_ident(x)
}
fn get_entid(&self, x: &Keyword) -> Option<KnownEntid> {
self.schema.get_entid(x)
}
fn attribute_for_entid<T>(&self, x: T) -> Option<&Attribute>
where
T: Into<Entid>,
{
self.schema.attribute_for_entid(x)
}
fn attribute_for_ident(&self, ident: &Keyword) -> Option<(&Attribute, KnownEntid)> {
self.schema.attribute_for_ident(ident)
}
fn is_attribute<T>(&self, x: T) -> bool
where
T: Into<Entid>,
{
self.schema.is_attribute(x)
}
fn identifies_attribute(&self, x: &Keyword) -> bool {
self.schema.identifies_attribute(x)
}
fn component_attributes(&self) -> &[Entid] {
self.schema.component_attributes()
}
}
struct InProgressTransactWatcher<'a, 'o> {
cache_watcher: InProgressCacheTransactWatcher<'a>,
observer_watcher: &'o mut InProgressObserverTransactWatcher,
tx_id: Option<Entid>,
}
impl<'a, 'o> InProgressTransactWatcher<'a, 'o> {
fn new(
observer_watcher: &'o mut InProgressObserverTransactWatcher,
cache_watcher: InProgressCacheTransactWatcher<'a>,
) -> Self {
InProgressTransactWatcher {
cache_watcher,
observer_watcher,
tx_id: None,
}
}
}
impl<'a, 'o> TransactWatcher for InProgressTransactWatcher<'a, 'o> {
fn datom(&mut self, op: OpType, e: Entid, a: Entid, v: &TypedValue) {
self.cache_watcher.datom(op, e, a, v);
self.observer_watcher.datom(op, e, a, v);
}
fn done(&mut self, t: &Entid, schema: &Schema) -> ::db_traits::errors::Result<()> {
self.cache_watcher.done(t, schema)?;
self.observer_watcher.done(t, schema)?;
self.tx_id = Some(*t);
Ok(())
}
}