InProgressRead, KnownEntid. r=nalexander,emily
Improve naming of read-only transactions. Implement entid_for_type. Simplify get_attribute. Name ignored var in algebrizer. Comment attribute_for_ident. Make KnownEntid a core concept. Expose lookup_value_for_attribute. Implement HasSchema and a new query encapsulation on Conn. Pre: export Queryable.
This commit is contained in:
parent
6797a606b5
commit
4acc6d0658
13 changed files with 251 additions and 94 deletions
|
@ -55,6 +55,23 @@ pub use edn::{
|
|||
/// use i64 rather than manually truncating u64 to u63 and casting to i64 throughout the codebase.
|
||||
pub type Entid = i64;
|
||||
|
||||
/// An entid that's either already in the store, or newly allocated to a tempid.
|
||||
/// TODO: we'd like to link this in some way to the lifetime of a particular PartitionMap.
|
||||
#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)]
|
||||
pub struct KnownEntid(pub Entid);
|
||||
|
||||
impl From<KnownEntid> for Entid {
|
||||
fn from(k: KnownEntid) -> Entid {
|
||||
k.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<KnownEntid> for TypedValue {
|
||||
fn from(k: KnownEntid) -> TypedValue {
|
||||
TypedValue::Ref(k.0)
|
||||
}
|
||||
}
|
||||
|
||||
/// The attribute of each Mentat assertion has a :db/valueType constraining the value to a
|
||||
/// particular set. Mentat recognizes the following :db/valueType values.
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialOrd, PartialEq)]
|
||||
|
@ -673,13 +690,17 @@ pub struct Schema {
|
|||
}
|
||||
|
||||
pub trait HasSchema {
|
||||
fn get_ident(&self, x: Entid) -> Option<&NamespacedKeyword>;
|
||||
fn get_entid(&self, x: &NamespacedKeyword) -> Option<Entid>;
|
||||
fn attribute_for_entid(&self, x: Entid) -> Option<&Attribute>;
|
||||
fn attribute_for_ident(&self, ident: &NamespacedKeyword) -> Option<&Attribute>;
|
||||
fn entid_for_type(&self, t: ValueType) -> Option<KnownEntid>;
|
||||
|
||||
fn get_ident<T>(&self, x: T) -> Option<&NamespacedKeyword> where T: Into<Entid>;
|
||||
fn get_entid(&self, x: &NamespacedKeyword) -> Option<KnownEntid>;
|
||||
fn attribute_for_entid<T>(&self, x: T) -> Option<&Attribute> where T: Into<Entid>;
|
||||
|
||||
// Returns the attribute and the entid named by the provided ident.
|
||||
fn attribute_for_ident(&self, ident: &NamespacedKeyword) -> Option<(&Attribute, KnownEntid)>;
|
||||
|
||||
/// Return true if the provided entid identifies an attribute in this schema.
|
||||
fn is_attribute(&self, x: Entid) -> bool;
|
||||
fn is_attribute<T>(&self, x: T) -> bool where T: Into<Entid>;
|
||||
|
||||
/// Return true if the provided ident identifies an attribute in this schema.
|
||||
fn identifies_attribute(&self, x: &NamespacedKeyword) -> bool;
|
||||
|
@ -693,34 +714,45 @@ impl Schema {
|
|||
attribute.to_edn_value(self.get_ident(*entid).cloned()))
|
||||
.collect())
|
||||
}
|
||||
|
||||
fn get_raw_entid(&self, x: &NamespacedKeyword) -> Option<Entid> {
|
||||
self.ident_map.get(x).map(|x| *x)
|
||||
}
|
||||
}
|
||||
|
||||
impl HasSchema for Schema {
|
||||
fn get_ident(&self, x: Entid) -> Option<&NamespacedKeyword> {
|
||||
self.entid_map.get(&x)
|
||||
fn entid_for_type(&self, t: ValueType) -> Option<KnownEntid> {
|
||||
// TODO: this can be made more efficient.
|
||||
self.get_entid(&t.into_keyword())
|
||||
}
|
||||
|
||||
fn get_entid(&self, x: &NamespacedKeyword) -> Option<Entid> {
|
||||
self.ident_map.get(x).map(|x| *x)
|
||||
fn get_ident<T>(&self, x: T) -> Option<&NamespacedKeyword> where T: Into<Entid> {
|
||||
self.entid_map.get(&x.into())
|
||||
}
|
||||
|
||||
fn attribute_for_entid(&self, x: Entid) -> Option<&Attribute> {
|
||||
self.attribute_map.get(&x)
|
||||
fn get_entid(&self, x: &NamespacedKeyword) -> Option<KnownEntid> {
|
||||
self.get_raw_entid(x).map(KnownEntid)
|
||||
}
|
||||
|
||||
fn attribute_for_ident(&self, ident: &NamespacedKeyword) -> Option<&Attribute> {
|
||||
self.get_entid(&ident)
|
||||
.and_then(|x| self.attribute_for_entid(x))
|
||||
fn attribute_for_entid<T>(&self, x: T) -> Option<&Attribute> where T: Into<Entid> {
|
||||
self.attribute_map.get(&x.into())
|
||||
}
|
||||
|
||||
fn attribute_for_ident(&self, ident: &NamespacedKeyword) -> Option<(&Attribute, KnownEntid)> {
|
||||
self.get_raw_entid(&ident)
|
||||
.and_then(|entid| {
|
||||
self.attribute_for_entid(entid).map(|a| (a, KnownEntid(entid)))
|
||||
})
|
||||
}
|
||||
|
||||
/// Return true if the provided entid identifies an attribute in this schema.
|
||||
fn is_attribute(&self, x: Entid) -> bool {
|
||||
self.attribute_map.contains_key(&x)
|
||||
fn is_attribute<T>(&self, x: T) -> bool where T: Into<Entid> {
|
||||
self.attribute_map.contains_key(&x.into())
|
||||
}
|
||||
|
||||
/// Return true if the provided ident identifies an attribute in this schema.
|
||||
fn identifies_attribute(&self, x: &NamespacedKeyword) -> bool {
|
||||
self.get_entid(x).map(|e| self.is_attribute(e)).unwrap_or(false)
|
||||
self.get_raw_entid(x).map(|e| self.is_attribute(e)).unwrap_or(false)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -15,6 +15,8 @@
|
|||
use std::collections::HashMap;
|
||||
use std::rc::Rc;
|
||||
|
||||
use mentat_core::KnownEntid;
|
||||
|
||||
use mentat_core::util::Either;
|
||||
|
||||
use errors;
|
||||
|
@ -37,10 +39,6 @@ pub enum Term<E, V> {
|
|||
|
||||
use self::Either::*;
|
||||
|
||||
/// An entid that's either already in the store, or newly allocated to a tempid.
|
||||
#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)]
|
||||
pub struct KnownEntid(pub Entid);
|
||||
|
||||
pub type KnownEntidOr<T> = Either<KnownEntid, T>;
|
||||
pub type TypedValueOr<T> = Either<TypedValue, T>;
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@ use mentat_core::{
|
|||
EntidMap,
|
||||
HasSchema,
|
||||
IdentMap,
|
||||
KnownEntid,
|
||||
Schema,
|
||||
AttributeMap,
|
||||
TypedValue,
|
||||
|
@ -173,7 +174,7 @@ impl AttributeBuilder {
|
|||
|
||||
pub trait SchemaBuilding {
|
||||
fn require_ident(&self, entid: Entid) -> Result<&symbols::NamespacedKeyword>;
|
||||
fn require_entid(&self, ident: &symbols::NamespacedKeyword) -> Result<Entid>;
|
||||
fn require_entid(&self, ident: &symbols::NamespacedKeyword) -> Result<KnownEntid>;
|
||||
fn require_attribute_for_entid(&self, entid: Entid) -> Result<&Attribute>;
|
||||
fn from_ident_map_and_attribute_map(ident_map: IdentMap, attribute_map: AttributeMap) -> Result<Schema>;
|
||||
fn from_ident_map_and_triples<U>(ident_map: IdentMap, assertions: U) -> Result<Schema>
|
||||
|
@ -185,7 +186,7 @@ impl SchemaBuilding for Schema {
|
|||
self.get_ident(entid).ok_or(ErrorKind::UnrecognizedEntid(entid).into())
|
||||
}
|
||||
|
||||
fn require_entid(&self, ident: &symbols::NamespacedKeyword) -> Result<Entid> {
|
||||
fn require_entid(&self, ident: &symbols::NamespacedKeyword) -> Result<KnownEntid> {
|
||||
self.get_entid(&ident).ok_or(ErrorKind::UnrecognizedIdent(ident.to_string()).into())
|
||||
}
|
||||
|
||||
|
@ -248,7 +249,7 @@ impl SchemaTypeChecking for Schema {
|
|||
(ValueType::Keyword, tv @ TypedValue::Keyword(_)) => Ok(tv),
|
||||
// Ref coerces a little: we interpret some things depending on the schema as a Ref.
|
||||
(ValueType::Ref, TypedValue::Long(x)) => Ok(TypedValue::Ref(x)),
|
||||
(ValueType::Ref, TypedValue::Keyword(ref x)) => self.require_entid(&x).map(|entid| TypedValue::Ref(entid)),
|
||||
(ValueType::Ref, TypedValue::Keyword(ref x)) => self.require_entid(&x).map(|entid| entid.into()),
|
||||
|
||||
// Otherwise, we have a type mismatch.
|
||||
// Enumerate all of the types here to allow the compiler to help us.
|
||||
|
|
|
@ -64,7 +64,6 @@ use edn::{
|
|||
use entids;
|
||||
use errors::{ErrorKind, Result};
|
||||
use internal_types::{
|
||||
KnownEntid,
|
||||
KnownEntidOr,
|
||||
LookupRef,
|
||||
LookupRefOrTempId,
|
||||
|
@ -82,6 +81,7 @@ use mentat_core::util::Either;
|
|||
|
||||
use mentat_core::{
|
||||
DateTime,
|
||||
KnownEntid,
|
||||
Schema,
|
||||
Utc,
|
||||
attribute,
|
||||
|
@ -230,14 +230,13 @@ impl<'conn, 'a> Tx<'conn, 'a> {
|
|||
}
|
||||
|
||||
fn ensure_ident_exists(&self, e: &NamespacedKeyword) -> Result<KnownEntid> {
|
||||
let entid = self.schema.require_entid(e)?;
|
||||
Ok(KnownEntid(entid))
|
||||
self.schema.require_entid(e)
|
||||
}
|
||||
|
||||
fn intern_lookup_ref(&mut self, lookup_ref: &entmod::LookupRef) -> Result<LookupRef> {
|
||||
let lr_a: i64 = match lookup_ref.a {
|
||||
entmod::Entid::Entid(ref a) => *a,
|
||||
entmod::Entid::Ident(ref a) => self.schema.require_entid(&a)?,
|
||||
entmod::Entid::Ident(ref a) => self.schema.require_entid(&a)?.into(),
|
||||
};
|
||||
let lr_attribute: &Attribute = self.schema.require_attribute_for_entid(lr_a)?;
|
||||
|
||||
|
@ -283,7 +282,7 @@ impl<'conn, 'a> Tx<'conn, 'a> {
|
|||
fn entity_a_into_term_a(&mut self, x: entmod::Entid) -> Result<Entid> {
|
||||
let a = match x {
|
||||
entmod::Entid::Entid(ref a) => *a,
|
||||
entmod::Entid::Ident(ref a) => self.schema.require_entid(&a)?,
|
||||
entmod::Entid::Ident(ref a) => self.schema.require_entid(&a)?.into(),
|
||||
};
|
||||
Ok(a)
|
||||
}
|
||||
|
|
|
@ -170,7 +170,7 @@ impl ConjoiningClauses {
|
|||
},
|
||||
(true, false) => {
|
||||
// This can only be an ident. Look it up!
|
||||
match schema.get_entid(&x).map(TypedValue::Ref) {
|
||||
match schema.get_entid(&x).map(|k| k.into()) {
|
||||
Some(e) => Ok(Val(e)),
|
||||
None => Ok(Impossible(EmptyBecause::UnresolvedIdent(x.clone()))),
|
||||
}
|
||||
|
|
|
@ -102,7 +102,7 @@ impl ConjoiningClauses {
|
|||
//
|
||||
// TODO: improve the expression of this matching, possibly by using attribute_for_* uniformly.
|
||||
let a = match args.next().unwrap() {
|
||||
FnArg::IdentOrKeyword(i) => schema.get_entid(&i),
|
||||
FnArg::IdentOrKeyword(i) => schema.get_entid(&i).map(|k| k.into()),
|
||||
// Must be an entid.
|
||||
FnArg::EntidOrInteger(e) => Some(e),
|
||||
FnArg::Variable(v) => {
|
||||
|
|
|
@ -26,6 +26,7 @@ use mentat_core::{
|
|||
Attribute,
|
||||
Entid,
|
||||
HasSchema,
|
||||
KnownEntid,
|
||||
Schema,
|
||||
TypedValue,
|
||||
ValueType,
|
||||
|
@ -441,7 +442,7 @@ impl ConjoiningClauses {
|
|||
match bound_val {
|
||||
TypedValue::Keyword(ref kw) => {
|
||||
if let Some(entid) = self.entid_for_ident(schema, kw) {
|
||||
self.constrain_column_to_entity(table, column, entid);
|
||||
self.constrain_column_to_entity(table, column, entid.into());
|
||||
} else {
|
||||
// Impossible.
|
||||
// For attributes this shouldn't occur, because we check the binding in
|
||||
|
@ -649,7 +650,7 @@ impl ConjoiningClauses {
|
|||
self.empty_because = Some(why);
|
||||
}
|
||||
|
||||
fn entid_for_ident<'s, 'a>(&self, schema: &'s Schema, ident: &'a NamespacedKeyword) -> Option<Entid> {
|
||||
fn entid_for_ident<'s, 'a>(&self, schema: &'s Schema, ident: &'a NamespacedKeyword) -> Option<KnownEntid> {
|
||||
schema.get_entid(&ident)
|
||||
}
|
||||
|
||||
|
@ -714,7 +715,7 @@ impl ConjoiningClauses {
|
|||
&PatternNonValuePlace::Ident(ref kw) =>
|
||||
schema.attribute_for_ident(kw)
|
||||
.ok_or_else(|| EmptyBecause::InvalidAttributeIdent(kw.cloned()))
|
||||
.and_then(|attribute| self.table_for_attribute_and_value(attribute, value)),
|
||||
.and_then(|(attribute, _entid)| self.table_for_attribute_and_value(attribute, value)),
|
||||
&PatternNonValuePlace::Entid(id) =>
|
||||
schema.attribute_for_entid(id)
|
||||
.ok_or_else(|| EmptyBecause::InvalidAttributeEntid(id))
|
||||
|
@ -737,7 +738,7 @@ impl ConjoiningClauses {
|
|||
// Don't recurse: avoid needing to clone the keyword.
|
||||
schema.attribute_for_ident(kw)
|
||||
.ok_or_else(|| EmptyBecause::InvalidAttributeIdent(kw.cloned()))
|
||||
.and_then(|attribute| self.table_for_attribute_and_value(attribute, value)),
|
||||
.and_then(|(attribute, _entid)| self.table_for_attribute_and_value(attribute, value)),
|
||||
Some(v) => {
|
||||
// This pattern cannot match: the caller has bound a non-entity value to an
|
||||
// attribute place.
|
||||
|
@ -772,8 +773,9 @@ impl ConjoiningClauses {
|
|||
|
||||
fn get_attribute_for_value<'s>(&self, schema: &'s Schema, value: &TypedValue) -> Option<&'s Attribute> {
|
||||
match value {
|
||||
// We know this one is known if the attribute lookup succeeds…
|
||||
&TypedValue::Ref(id) => schema.attribute_for_entid(id),
|
||||
&TypedValue::Keyword(ref kw) => schema.attribute_for_ident(kw),
|
||||
&TypedValue::Keyword(ref kw) => schema.attribute_for_ident(kw).map(|(a, _id)| a),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
@ -781,9 +783,10 @@ impl ConjoiningClauses {
|
|||
fn get_attribute<'s, 'a>(&self, schema: &'s Schema, pattern: &'a Pattern) -> Option<&'s Attribute> {
|
||||
match pattern.attribute {
|
||||
PatternNonValuePlace::Entid(id) =>
|
||||
// We know this one is known if the attribute lookup succeeds…
|
||||
schema.attribute_for_entid(id),
|
||||
PatternNonValuePlace::Ident(ref kw) =>
|
||||
schema.attribute_for_ident(kw),
|
||||
schema.attribute_for_ident(kw).map(|(a, _id)| a),
|
||||
PatternNonValuePlace::Variable(ref var) =>
|
||||
// If the pattern has a variable, we've already determined that the binding -- if
|
||||
// any -- is acceptable and yields a table. Here, simply look to see if it names
|
||||
|
@ -796,7 +799,7 @@ impl ConjoiningClauses {
|
|||
}
|
||||
|
||||
fn get_value_type<'s, 'a>(&self, schema: &'s Schema, pattern: &'a Pattern) -> Option<ValueType> {
|
||||
self.get_attribute(schema, pattern).map(|x| x.value_type)
|
||||
self.get_attribute(schema, pattern).map(|a| a.value_type)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -100,7 +100,7 @@ impl ConjoiningClauses {
|
|||
self.constrain_column_to_entity(col.clone(), DatomsColumn::Entity, entid),
|
||||
PatternNonValuePlace::Ident(ref ident) => {
|
||||
if let Some(entid) = self.entid_for_ident(schema, ident.as_ref()) {
|
||||
self.constrain_column_to_entity(col.clone(), DatomsColumn::Entity, entid)
|
||||
self.constrain_column_to_entity(col.clone(), DatomsColumn::Entity, entid.into())
|
||||
} else {
|
||||
// A resolution failure means we're done here.
|
||||
self.mark_known_empty(EmptyBecause::UnresolvedIdent(ident.cloned()));
|
||||
|
@ -125,7 +125,7 @@ impl ConjoiningClauses {
|
|||
},
|
||||
PatternNonValuePlace::Ident(ref ident) => {
|
||||
if let Some(entid) = self.entid_for_ident(schema, ident) {
|
||||
self.constrain_attribute(col.clone(), entid);
|
||||
self.constrain_attribute(col.clone(), entid.into());
|
||||
|
||||
if !schema.is_attribute(entid) {
|
||||
self.mark_known_empty(EmptyBecause::InvalidAttributeIdent(ident.cloned()));
|
||||
|
@ -191,7 +191,7 @@ impl ConjoiningClauses {
|
|||
// such.
|
||||
if let Some(ValueType::Ref) = value_type {
|
||||
if let Some(entid) = self.entid_for_ident(schema, kw) {
|
||||
self.constrain_column_to_entity(col.clone(), DatomsColumn::Value, entid)
|
||||
self.constrain_column_to_entity(col.clone(), DatomsColumn::Value, entid.into())
|
||||
} else {
|
||||
// A resolution failure means we're done here: this attribute must have an
|
||||
// entity value.
|
||||
|
@ -252,7 +252,7 @@ impl ConjoiningClauses {
|
|||
},
|
||||
PatternNonValuePlace::Ident(ref ident) => {
|
||||
if let Some(entid) = self.entid_for_ident(schema, ident.as_ref()) {
|
||||
self.constrain_column_to_entity(col.clone(), DatomsColumn::Tx, entid)
|
||||
self.constrain_column_to_entity(col.clone(), DatomsColumn::Tx, entid.into())
|
||||
} else {
|
||||
// A resolution failure means we're done here.
|
||||
self.mark_known_empty(EmptyBecause::UnresolvedIdent(ident.cloned()));
|
||||
|
|
171
src/conn.rs
171
src/conn.rs
|
@ -20,9 +20,14 @@ use rusqlite::{
|
|||
use edn;
|
||||
|
||||
use mentat_core::{
|
||||
Attribute,
|
||||
Entid,
|
||||
HasSchema,
|
||||
KnownEntid,
|
||||
NamespacedKeyword,
|
||||
Schema,
|
||||
TypedValue,
|
||||
ValueType,
|
||||
};
|
||||
|
||||
use mentat_db::db;
|
||||
|
@ -39,6 +44,7 @@ use mentat_tx_parser;
|
|||
use errors::*;
|
||||
use query::{
|
||||
lookup_value_for_attribute,
|
||||
lookup_values_for_attribute,
|
||||
q_once,
|
||||
q_explain,
|
||||
QueryExplanation,
|
||||
|
@ -88,6 +94,15 @@ pub struct Conn {
|
|||
// the schema changes. #315.
|
||||
}
|
||||
|
||||
pub trait Queryable {
|
||||
fn q_once<T>(&self, query: &str, inputs: T) -> Result<QueryResults>
|
||||
where T: Into<Option<QueryInputs>>;
|
||||
fn lookup_values_for_attribute<E>(&self, entity: E, attribute: &edn::NamespacedKeyword) -> Result<Vec<TypedValue>>
|
||||
where E: Into<Entid>;
|
||||
fn lookup_value_for_attribute<E>(&self, entity: E, attribute: &edn::NamespacedKeyword) -> Result<Option<TypedValue>>
|
||||
where E: Into<Entid>;
|
||||
}
|
||||
|
||||
/// Represents an in-progress, not yet committed, set of changes to the store.
|
||||
/// Call `commit` to commit your changes, or `rollback` to discard them.
|
||||
/// A transaction is held open until you do so.
|
||||
|
@ -101,6 +116,112 @@ pub struct InProgress<'a, 'c> {
|
|||
last_report: Option<TxReport>, // For now we track only the last, but we could accumulate all.
|
||||
}
|
||||
|
||||
/// Represents an in-progress set of reads to the store. Just like `InProgress`,
|
||||
/// which is read-write, but only allows for reads.
|
||||
pub struct InProgressRead<'a, 'c>(InProgress<'a, 'c>);
|
||||
|
||||
impl<'a, 'c> Queryable for InProgressRead<'a, 'c> {
|
||||
fn q_once<T>(&self, query: &str, inputs: T) -> Result<QueryResults>
|
||||
where T: Into<Option<QueryInputs>> {
|
||||
self.0.q_once(query, inputs)
|
||||
}
|
||||
|
||||
fn lookup_values_for_attribute<E>(&self, entity: E, attribute: &edn::NamespacedKeyword) -> Result<Vec<TypedValue>>
|
||||
where E: Into<Entid> {
|
||||
self.0.lookup_values_for_attribute(entity, attribute)
|
||||
}
|
||||
|
||||
fn lookup_value_for_attribute<E>(&self, entity: E, attribute: &edn::NamespacedKeyword) -> Result<Option<TypedValue>>
|
||||
where E: Into<Entid> {
|
||||
self.0.lookup_value_for_attribute(entity, attribute)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'c> Queryable for InProgress<'a, 'c> {
|
||||
fn q_once<T>(&self, query: &str, inputs: T) -> Result<QueryResults>
|
||||
where T: Into<Option<QueryInputs>> {
|
||||
|
||||
q_once(&*(self.transaction),
|
||||
&self.schema,
|
||||
query,
|
||||
inputs)
|
||||
}
|
||||
|
||||
fn lookup_values_for_attribute<E>(&self, entity: E, attribute: &edn::NamespacedKeyword) -> Result<Vec<TypedValue>>
|
||||
where E: Into<Entid> {
|
||||
lookup_values_for_attribute(&*(self.transaction), &self.schema, entity, attribute)
|
||||
}
|
||||
|
||||
fn lookup_value_for_attribute<E>(&self, entity: E, attribute: &edn::NamespacedKeyword) -> Result<Option<TypedValue>>
|
||||
where E: Into<Entid> {
|
||||
lookup_value_for_attribute(&*(self.transaction), &self.schema, entity, attribute)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'c> HasSchema for InProgressRead<'a, 'c> {
|
||||
fn entid_for_type(&self, t: ValueType) -> Option<KnownEntid> {
|
||||
self.0.entid_for_type(t)
|
||||
}
|
||||
|
||||
fn get_ident<T>(&self, x: T) -> Option<&NamespacedKeyword> where T: Into<Entid> {
|
||||
self.0.get_ident(x)
|
||||
}
|
||||
|
||||
fn get_entid(&self, x: &NamespacedKeyword) -> Option<KnownEntid> {
|
||||
self.0.get_entid(x)
|
||||
}
|
||||
|
||||
fn attribute_for_entid<T>(&self, x: T) -> Option<&Attribute> where T: Into<Entid> {
|
||||
self.0.attribute_for_entid(x)
|
||||
}
|
||||
|
||||
fn attribute_for_ident(&self, ident: &NamespacedKeyword) -> Option<(&Attribute, KnownEntid)> {
|
||||
self.0.attribute_for_ident(ident)
|
||||
}
|
||||
|
||||
/// Return true if the provided entid identifies an attribute in this schema.
|
||||
fn is_attribute<T>(&self, x: T) -> bool where T: Into<Entid> {
|
||||
self.0.is_attribute(x)
|
||||
}
|
||||
|
||||
/// Return true if the provided ident identifies an attribute in this schema.
|
||||
fn identifies_attribute(&self, x: &NamespacedKeyword) -> bool {
|
||||
self.0.identifies_attribute(x)
|
||||
}
|
||||
}
|
||||
|
||||
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<&NamespacedKeyword> where T: Into<Entid> {
|
||||
self.schema.get_ident(x)
|
||||
}
|
||||
|
||||
fn get_entid(&self, x: &NamespacedKeyword) -> 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: &NamespacedKeyword) -> Option<(&Attribute, KnownEntid)> {
|
||||
self.schema.attribute_for_ident(ident)
|
||||
}
|
||||
|
||||
/// Return true if the provided entid identifies an attribute in this schema.
|
||||
fn is_attribute<T>(&self, x: T) -> bool where T: Into<Entid> {
|
||||
self.schema.is_attribute(x)
|
||||
}
|
||||
|
||||
/// Return true if the provided ident identifies an attribute in this schema.
|
||||
fn identifies_attribute(&self, x: &NamespacedKeyword) -> bool {
|
||||
self.schema.identifies_attribute(x)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'c> InProgress<'a, 'c> {
|
||||
pub fn transact_entities<I>(&mut self, entities: I) -> Result<()> where I: IntoIterator<Item=mentat_tx::entities::Entity> {
|
||||
// We clone the partition map here, rather than trying to use a Cell or using a mutable
|
||||
|
@ -120,25 +241,6 @@ impl<'a, 'c> InProgress<'a, 'c> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Query the Mentat store, using the given connection and the current metadata.
|
||||
pub fn q_once<T>(&self,
|
||||
query: &str,
|
||||
inputs: T) -> Result<QueryResults>
|
||||
where T: Into<Option<QueryInputs>>
|
||||
{
|
||||
|
||||
q_once(&*(self.transaction),
|
||||
&self.schema,
|
||||
query,
|
||||
inputs)
|
||||
}
|
||||
|
||||
pub fn lookup_value_for_attribute(&self,
|
||||
entity: Entid,
|
||||
attribute: &edn::NamespacedKeyword) -> Result<Option<TypedValue>> {
|
||||
lookup_value_for_attribute(&*(self.transaction), &self.schema, entity, attribute)
|
||||
}
|
||||
|
||||
pub fn transact(&mut self, transaction: &str) -> Result<()> {
|
||||
let assertion_vector = edn::parse::value(transaction)?;
|
||||
let entities = mentat_tx_parser::Tx::parse(&assertion_vector)?;
|
||||
|
@ -193,7 +295,7 @@ impl Conn {
|
|||
Ok(Conn::new(db.partition_map, db.schema))
|
||||
}
|
||||
|
||||
/// Yield the current `Schema` instance.
|
||||
/// Yield a clone of the current `Schema` instance.
|
||||
pub fn current_schema(&self) -> Arc<Schema> {
|
||||
// We always unwrap the mutex lock: if it's poisoned, this will propogate panics to all
|
||||
// accessing threads. This is perhaps not reasonable; we expect the mutex to be held for
|
||||
|
@ -215,11 +317,11 @@ impl Conn {
|
|||
sqlite: &rusqlite::Connection,
|
||||
query: &str,
|
||||
inputs: T) -> Result<QueryResults>
|
||||
where T: Into<Option<QueryInputs>>
|
||||
{
|
||||
where T: Into<Option<QueryInputs>> {
|
||||
|
||||
let metadata = self.metadata.lock().unwrap();
|
||||
q_once(sqlite,
|
||||
&*self.current_schema(),
|
||||
&*metadata.schema, // Doesn't clone, unlike `current_schema`.
|
||||
query,
|
||||
inputs)
|
||||
}
|
||||
|
@ -241,12 +343,8 @@ impl Conn {
|
|||
}
|
||||
|
||||
/// Take a SQLite transaction.
|
||||
/// IMMEDIATE means 'start the transaction now, but don't exclude readers'. It prevents other
|
||||
/// connections from taking immediate or exclusive transactions. This is appropriate for our
|
||||
/// writes and `InProgress`: it means we are ready to write whenever we want to, and nobody else
|
||||
/// can start a transaction that's not `DEFERRED`, but we don't need exclusivity yet.
|
||||
pub fn begin_transaction<'m, 'conn>(&'m mut self, sqlite: &'conn mut rusqlite::Connection) -> Result<InProgress<'m, 'conn>> {
|
||||
let tx = sqlite.transaction_with_behavior(TransactionBehavior::Immediate)?;
|
||||
fn begin_transaction_with_behavior<'m, 'conn>(&'m mut self, sqlite: &'conn mut rusqlite::Connection, behavior: TransactionBehavior) -> Result<InProgress<'m, 'conn>> {
|
||||
let tx = sqlite.transaction_with_behavior(behavior)?;
|
||||
let (current_generation, current_partition_map, current_schema) =
|
||||
{
|
||||
// The mutex is taken during this block.
|
||||
|
@ -268,6 +366,21 @@ impl Conn {
|
|||
})
|
||||
}
|
||||
|
||||
// Helper to avoid passing connections around.
|
||||
// Make both args mutable so that we can't have parallel access.
|
||||
pub fn begin_read<'m, 'conn>(&'m mut self, sqlite: &'conn mut rusqlite::Connection) -> Result<InProgressRead<'m, 'conn>> {
|
||||
self.begin_transaction_with_behavior(sqlite, TransactionBehavior::Deferred)
|
||||
.map(InProgressRead)
|
||||
}
|
||||
|
||||
/// IMMEDIATE means 'start the transaction now, but don't exclude readers'. It prevents other
|
||||
/// connections from taking immediate or exclusive transactions. This is appropriate for our
|
||||
/// writes and `InProgress`: it means we are ready to write whenever we want to, and nobody else
|
||||
/// can start a transaction that's not `DEFERRED`, but we don't need exclusivity yet.
|
||||
pub fn begin_transaction<'m, 'conn>(&'m mut self, sqlite: &'conn mut rusqlite::Connection) -> Result<InProgress<'m, 'conn>> {
|
||||
self.begin_transaction_with_behavior(sqlite, TransactionBehavior::Immediate)
|
||||
}
|
||||
|
||||
/// Transact entities against the Mentat store, using the given connection and the current
|
||||
/// metadata.
|
||||
pub fn transact(&mut self,
|
||||
|
|
|
@ -16,7 +16,6 @@ use std::collections::BTreeSet;
|
|||
|
||||
use edn;
|
||||
use mentat_db;
|
||||
use mentat_query;
|
||||
use mentat_query_algebrizer;
|
||||
use mentat_query_parser;
|
||||
use mentat_query_projector;
|
||||
|
@ -55,9 +54,14 @@ error_chain! {
|
|||
display("invalid argument name: '{}'", name)
|
||||
}
|
||||
|
||||
UnknownAttribute(kw: mentat_query::NamespacedKeyword) {
|
||||
UnknownAttribute(name: String) {
|
||||
description("unknown attribute")
|
||||
display("unknown attribute: '{}'", kw)
|
||||
display("unknown attribute: '{}'", name)
|
||||
}
|
||||
|
||||
InvalidVocabularyVersion {
|
||||
description("invalid vocabulary version")
|
||||
display("invalid vocabulary version")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -68,6 +68,7 @@ pub use query::{
|
|||
pub use conn::{
|
||||
Conn,
|
||||
Metadata,
|
||||
Queryable,
|
||||
};
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
41
src/query.rs
41
src/query.rs
|
@ -16,6 +16,7 @@ use std::rc::Rc;
|
|||
use mentat_core::{
|
||||
Entid,
|
||||
HasSchema,
|
||||
KnownEntid,
|
||||
Schema,
|
||||
TypedValue,
|
||||
};
|
||||
|
@ -159,49 +160,53 @@ fn fetch_values<'sqlite, 'schema>
|
|||
run_algebrized_query(sqlite, algebrized)
|
||||
}
|
||||
|
||||
fn lookup_attribute(schema: &Schema, attribute: &NamespacedKeyword) -> Result<Entid> {
|
||||
fn lookup_attribute(schema: &Schema, attribute: &NamespacedKeyword) -> Result<KnownEntid> {
|
||||
schema.get_entid(attribute)
|
||||
.ok_or_else(|| ErrorKind::UnknownAttribute(attribute.clone()).into())
|
||||
.ok_or_else(|| ErrorKind::UnknownAttribute(attribute.name.clone()).into())
|
||||
}
|
||||
|
||||
/// Return a single value for the provided entity and attribute.
|
||||
/// If the attribute is multi-valued, an arbitrary value is returned.
|
||||
/// If no value is present for that entity, `None` is returned.
|
||||
/// If `attribute` isn't an attribute, `None` is returned.
|
||||
pub fn lookup_value<'sqlite, 'schema>
|
||||
pub fn lookup_value<'sqlite, 'schema, E, A>
|
||||
(sqlite: &'sqlite rusqlite::Connection,
|
||||
schema: &'schema Schema,
|
||||
entity: Entid,
|
||||
attribute: Entid) -> Result<Option<TypedValue>> {
|
||||
fetch_values(sqlite, schema, entity, attribute, true).into_scalar_result()
|
||||
entity: E,
|
||||
attribute: A) -> Result<Option<TypedValue>>
|
||||
where E: Into<Entid>, A: Into<Entid> {
|
||||
fetch_values(sqlite, schema, entity.into(), attribute.into(), true).into_scalar_result()
|
||||
}
|
||||
|
||||
pub fn lookup_values<'sqlite, 'schema>
|
||||
pub fn lookup_values<'sqlite, 'schema, E, A>
|
||||
(sqlite: &'sqlite rusqlite::Connection,
|
||||
schema: &'schema Schema,
|
||||
entity: Entid,
|
||||
attribute: Entid) -> Result<Vec<TypedValue>> {
|
||||
fetch_values(sqlite, schema, entity, attribute, false).into_coll_result()
|
||||
entity: E,
|
||||
attribute: A) -> Result<Vec<TypedValue>>
|
||||
where E: Into<Entid>, A: Into<Entid> {
|
||||
fetch_values(sqlite, schema, entity.into(), attribute.into(), false).into_coll_result()
|
||||
}
|
||||
|
||||
/// Return a single value for the provided entity and attribute.
|
||||
/// If the attribute is multi-valued, an arbitrary value is returned.
|
||||
/// If no value is present for that entity, `None` is returned.
|
||||
/// If `attribute` doesn't name an attribute, an error is returned.
|
||||
pub fn lookup_value_for_attribute<'sqlite, 'schema, 'attribute>
|
||||
pub fn lookup_value_for_attribute<'sqlite, 'schema, 'attribute, E>
|
||||
(sqlite: &'sqlite rusqlite::Connection,
|
||||
schema: &'schema Schema,
|
||||
entity: Entid,
|
||||
attribute: &'attribute NamespacedKeyword) -> Result<Option<TypedValue>> {
|
||||
lookup_value(sqlite, schema, entity, lookup_attribute(schema, attribute)?)
|
||||
entity: E,
|
||||
attribute: &'attribute NamespacedKeyword) -> Result<Option<TypedValue>>
|
||||
where E: Into<Entid> {
|
||||
lookup_value(sqlite, schema, entity.into(), lookup_attribute(schema, attribute)?)
|
||||
}
|
||||
|
||||
pub fn lookup_values_for_attribute<'sqlite, 'schema, 'attribute>
|
||||
pub fn lookup_values_for_attribute<'sqlite, 'schema, 'attribute, E>
|
||||
(sqlite: &'sqlite rusqlite::Connection,
|
||||
schema: &'schema Schema,
|
||||
entity: Entid,
|
||||
attribute: &'attribute NamespacedKeyword) -> Result<Vec<TypedValue>> {
|
||||
lookup_values(sqlite, schema, entity, lookup_attribute(schema, attribute)?)
|
||||
entity: E,
|
||||
attribute: &'attribute NamespacedKeyword) -> Result<Vec<TypedValue>>
|
||||
where E: Into<Entid> {
|
||||
lookup_values(sqlite, schema, entity.into(), lookup_attribute(schema, attribute)?)
|
||||
}
|
||||
|
||||
fn run_statement<'sqlite, 'stmt, 'bound>
|
||||
|
|
|
@ -23,6 +23,7 @@ use chrono::FixedOffset;
|
|||
use mentat_core::{
|
||||
DateTime,
|
||||
HasSchema,
|
||||
KnownEntid,
|
||||
TypedValue,
|
||||
ValueType,
|
||||
Utc,
|
||||
|
@ -115,7 +116,7 @@ fn test_scalar() {
|
|||
if let QueryResults::Scalar(Some(TypedValue::Keyword(ref rc))) = results {
|
||||
// Should be '24'.
|
||||
assert_eq!(&NamespacedKeyword::new("db.type", "keyword"), rc.as_ref());
|
||||
assert_eq!(24,
|
||||
assert_eq!(KnownEntid(24),
|
||||
db.schema.get_entid(rc).unwrap());
|
||||
} else {
|
||||
panic!("Expected scalar.");
|
||||
|
@ -146,7 +147,7 @@ fn test_tuple() {
|
|||
let cardinality_one = NamespacedKeyword::new("db.cardinality", "one");
|
||||
assert_eq!(tuple.len(), 2);
|
||||
assert_eq!(tuple[0], TypedValue::Boolean(true));
|
||||
assert_eq!(tuple[1], TypedValue::Ref(db.schema.get_entid(&cardinality_one).unwrap()));
|
||||
assert_eq!(tuple[1], db.schema.get_entid(&cardinality_one).expect("c1").into());
|
||||
} else {
|
||||
panic!("Expected tuple.");
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue