Move Schema from mentat_db to mentat_core, improve API. (#290)

* Move Schema from mentat_db to mentat_core.
* Define SchemaMap in terms of Entid, not i64.
* Add Schema::{is_attribute,identifies_attribute}.
* Add pointer to #291.
* Don't pass around 64-bit pointers to 64-bit integers.
This commit is contained in:
Richard Newman 2017-02-13 19:20:20 -08:00 committed by GitHub
parent 2e303f4837
commit a87e5a3ec7
6 changed files with 116 additions and 68 deletions

View file

@ -9,6 +9,8 @@
// specific language governing permissions and limitations under the License. // specific language governing permissions and limitations under the License.
extern crate ordered_float; extern crate ordered_float;
use std::collections::BTreeMap;
use self::ordered_float::OrderedFloat; use self::ordered_float::OrderedFloat;
/// Core types defining a Mentat knowledge base. /// Core types defining a Mentat knowledge base.
@ -173,6 +175,64 @@ impl Default for Attribute {
} }
} }
/// Map `String` idents (`:db/ident`) to positive integer entids (`1`).
/// TODO: these should all be parsed into NamespacedKeywords on entry. #291.
pub type IdentMap = BTreeMap<String, Entid>;
/// Map positive integer entids (`1`) to `String` idents (`:db/ident`).
pub type EntidMap = BTreeMap<Entid, String>;
/// Map attribute entids to `Attribute` instances.
pub type SchemaMap = BTreeMap<Entid, Attribute>;
/// Represents a Mentat schema.
///
/// Maintains the mapping between string idents and positive integer entids; and exposes the schema
/// flags associated to a given entid (equivalently, ident).
///
/// TODO: consider a single bi-directional map instead of separate ident->entid and entid->ident
/// maps.
#[derive(Clone, Debug, Default, Eq, Hash, Ord, PartialOrd, PartialEq)]
pub struct Schema {
/// Map entid->ident.
///
/// Invariant: is the inverse map of `ident_map`.
pub entid_map: EntidMap,
/// Map ident->entid.
///
/// Invariant: is the inverse map of `entid_map`.
pub ident_map: IdentMap,
/// Map entid->attribute flags.
///
/// Invariant: key-set is the same as the key-set of `entid_map` (equivalently, the value-set of
/// `ident_map`).
pub schema_map: SchemaMap,
}
impl Schema {
pub fn get_ident(&self, x: Entid) -> Option<&String> {
self.entid_map.get(&x)
}
pub fn get_entid(&self, x: &String) -> Option<Entid> {
self.ident_map.get(x).map(|x| *x)
}
pub fn attribute_for_entid(&self, x: Entid) -> Option<&Attribute> {
self.schema_map.get(&x)
}
pub fn is_attribute(&self, x: Entid) -> bool {
self.schema_map.contains_key(&x)
}
pub fn identifies_attribute(&self, x: &String) -> bool {
self.get_entid(x).map(|e| self.is_attribute(e)).unwrap_or(false)
}
}
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::*; use super::*;

View file

@ -18,8 +18,13 @@ use entids;
use db::TypedSQLValue; use db::TypedSQLValue;
use mentat_tx::entities::Entity; use mentat_tx::entities::Entity;
use mentat_tx_parser; use mentat_tx_parser;
use mentat_core::TypedValue; use mentat_core::{
use types::{IdentMap, Partition, PartitionMap, Schema}; IdentMap,
Schema,
TypedValue,
};
use schema::SchemaBuilding;
use types::{Partition, PartitionMap};
use values; use values;
/// The first transaction ID applied to the knowledge base. /// The first transaction ID applied to the knowledge base.

View file

@ -23,10 +23,20 @@ use ::{repeat_values, to_namespaced_keyword};
use bootstrap; use bootstrap;
use edn::types::Value; use edn::types::Value;
use entids; use entids;
use mentat_core::{
Attribute,
AttributeBitFlags,
Entid,
IdentMap,
Schema,
TypedValue,
ValueType,
};
use mentat_tx::entities as entmod; use mentat_tx::entities as entmod;
use mentat_tx::entities::{Entity, OpType}; use mentat_tx::entities::{Entity, OpType};
use errors::{ErrorKind, Result, ResultExt}; use errors::{ErrorKind, Result, ResultExt};
use types::{Attribute, AttributeBitFlags, DB, Entid, IdentMap, Partition, PartitionMap, Schema, TypedValue, ValueType}; use types::{DB, Partition, PartitionMap};
use schema::SchemaBuilding;
pub fn new_connection<T>(uri: T) -> rusqlite::Result<rusqlite::Connection> where T: AsRef<Path> { pub fn new_connection<T>(uri: T) -> rusqlite::Result<rusqlite::Connection> where T: AsRef<Path> {
let conn = match uri.as_ref().to_string_lossy().len() { let conn = match uri.as_ref().to_string_lossy().len() {
@ -446,7 +456,7 @@ impl DB {
(&ValueType::Keyword, tv @ TypedValue::Keyword(_)) => Ok(tv), (&ValueType::Keyword, tv @ TypedValue::Keyword(_)) => Ok(tv),
// Ref coerces a little: we interpret some things depending on the schema as a Ref. // 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::Long(x)) => Ok(TypedValue::Ref(x)),
(&ValueType::Ref, TypedValue::Keyword(ref x)) => self.schema.require_entid(&x.to_string()).map(|&entid| TypedValue::Ref(entid)), (&ValueType::Ref, TypedValue::Keyword(ref x)) => self.schema.require_entid(&x.to_string()).map(|entid| TypedValue::Ref(entid)),
// Otherwise, we have a type mismatch. // Otherwise, we have a type mismatch.
(value_type, _) => bail!(ErrorKind::BadEDNValuePair(value.clone(), value_type.clone())), (value_type, _) => bail!(ErrorKind::BadEDNValuePair(value.clone(), value_type.clone())),
} }
@ -535,7 +545,7 @@ impl DB {
/* added0 */ bool, /* added0 */ bool,
/* flags0 */ u8)>> = chunk.map(|&(e, a, ref typed_value, added)| { /* flags0 */ u8)>> = chunk.map(|&(e, a, ref typed_value, added)| {
count += 1; count += 1;
let attribute: &Attribute = self.schema.require_attribute_for_entid(&a)?; let attribute: &Attribute = self.schema.require_attribute_for_entid(a)?;
// Now we can represent the typed value as an SQL value. // Now we can represent the typed value as an SQL value.
let (value, value_type_tag): (ToSqlOutput, i32) = typed_value.to_sql_value_pair(); let (value, value_type_tag): (ToSqlOutput, i32) = typed_value.to_sql_value_pair();
@ -725,15 +735,15 @@ impl DB {
let e: i64 = match e_ { let e: i64 = match e_ {
&entmod::Entid::Entid(ref e__) => *e__, &entmod::Entid::Entid(ref e__) => *e__,
&entmod::Entid::Ident(ref e__) => *self.schema.require_entid(&e__.to_string())?, &entmod::Entid::Ident(ref e__) => self.schema.require_entid(&e__.to_string())?,
}; };
let a: i64 = match a_ { let a: i64 = match a_ {
&entmod::Entid::Entid(ref a__) => *a__, &entmod::Entid::Entid(ref a__) => *a__,
&entmod::Entid::Ident(ref a__) => *self.schema.require_entid(&a__.to_string())?, &entmod::Entid::Ident(ref a__) => self.schema.require_entid(&a__.to_string())?,
}; };
let attribute: &Attribute = self.schema.require_attribute_for_entid(&a)?; let attribute: &Attribute = self.schema.require_attribute_for_entid(a)?;
if attribute.fulltext { if attribute.fulltext {
bail!(ErrorKind::NotYetImplemented(format!("Transacting :db/fulltext entities is not yet implemented: {:?}", entity))) bail!(ErrorKind::NotYetImplemented(format!("Transacting :db/fulltext entities is not yet implemented: {:?}", entity)))
} }
@ -760,15 +770,15 @@ impl DB {
let e: i64 = match e_ { let e: i64 = match e_ {
&entmod::Entid::Entid(ref e__) => *e__, &entmod::Entid::Entid(ref e__) => *e__,
&entmod::Entid::Ident(ref e__) => *self.schema.require_entid(&e__.to_string())?, &entmod::Entid::Ident(ref e__) => self.schema.require_entid(&e__.to_string())?,
}; };
let a: i64 = match a_ { let a: i64 = match a_ {
&entmod::Entid::Entid(ref a__) => *a__, &entmod::Entid::Entid(ref a__) => *a__,
&entmod::Entid::Ident(ref a__) => *self.schema.require_entid(&a__.to_string())?, &entmod::Entid::Ident(ref a__) => self.schema.require_entid(&a__.to_string())?,
}; };
let attribute: &Attribute = self.schema.require_attribute_for_entid(&a)?; let attribute: &Attribute = self.schema.require_attribute_for_entid(a)?;
if attribute.fulltext { if attribute.fulltext {
bail!(ErrorKind::NotYetImplemented(format!("Transacting :db/fulltext entities is not yet implemented: {:?}", entity))) bail!(ErrorKind::NotYetImplemented(format!("Transacting :db/fulltext entities is not yet implemented: {:?}", entity)))
} }

View file

@ -107,7 +107,7 @@ impl Transactions {
/// Convert a numeric entid to an ident `Entid` if possible, otherwise a numeric `Entid`. /// Convert a numeric entid to an ident `Entid` if possible, otherwise a numeric `Entid`.
fn to_entid(db: &DB, entid: i64) -> Entid { fn to_entid(db: &DB, entid: i64) -> Entid {
db.schema.get_ident(&entid).and_then(|ident| to_namespaced_keyword(&ident)).map_or(Entid::Entid(entid), Entid::Ident) db.schema.get_ident(entid).and_then(|ident| to_namespaced_keyword(&ident)).map_or(Entid::Entid(entid), Entid::Ident)
} }
/// Return the set of datoms in the store, ordered by (e, a, v, tx), but not including any datoms of /// Return the set of datoms in the store, ordered by (e, a, v, tx), but not including any datoms of

View file

@ -12,7 +12,16 @@
use entids; use entids;
use errors::*; use errors::*;
use types::{Attribute, Entid, EntidMap, IdentMap, Schema, SchemaMap, TypedValue, ValueType}; use mentat_core::{
Attribute,
Entid,
EntidMap,
IdentMap,
Schema,
SchemaMap,
TypedValue,
ValueType,
};
/// Return `Ok(())` if `schema_map` defines a valid Mentat schema. /// Return `Ok(())` if `schema_map` defines a valid Mentat schema.
fn validate_schema_map(entid_map: &EntidMap, schema_map: &SchemaMap) -> Result<()> { fn validate_schema_map(entid_map: &EntidMap, schema_map: &SchemaMap) -> Result<()> {
@ -40,33 +49,30 @@ fn validate_schema_map(entid_map: &EntidMap, schema_map: &SchemaMap) -> Result<(
Ok(()) Ok(())
} }
impl Schema { pub trait SchemaBuilding {
pub fn get_ident(&self, x: &Entid) -> Option<&String> { fn require_ident(&self, entid: Entid) -> Result<&String>;
self.entid_map.get(x) fn require_entid(&self, ident: &String) -> Result<Entid>;
fn require_attribute_for_entid(&self, entid: Entid) -> Result<&Attribute>;
fn from_ident_map_and_schema_map(ident_map: IdentMap, schema_map: SchemaMap) -> Result<Schema>;
fn from_ident_map_and_triples<U>(ident_map: IdentMap, assertions: U) -> Result<Schema>
where U: IntoIterator<Item=(String, String, TypedValue)>;
}
impl SchemaBuilding for Schema {
fn require_ident(&self, entid: Entid) -> Result<&String> {
self.get_ident(entid).ok_or(ErrorKind::UnrecognizedEntid(entid).into())
} }
pub fn get_entid(&self, x: &String) -> Option<&Entid> { fn require_entid(&self, ident: &String) -> Result<Entid> {
self.ident_map.get(x)
}
pub fn attribute_for_entid(&self, x: &Entid) -> Option<&Attribute> {
self.schema_map.get(x)
}
pub fn require_ident(&self, entid: &Entid) -> Result<&String> {
self.get_ident(&entid).ok_or(ErrorKind::UnrecognizedEntid(*entid).into())
}
pub fn require_entid(&self, ident: &String) -> Result<&Entid> {
self.get_entid(&ident).ok_or(ErrorKind::UnrecognizedIdent(ident.clone()).into()) self.get_entid(&ident).ok_or(ErrorKind::UnrecognizedIdent(ident.clone()).into())
} }
pub fn require_attribute_for_entid(&self, entid: &Entid) -> Result<&Attribute> { fn require_attribute_for_entid(&self, entid: Entid) -> Result<&Attribute> {
self.attribute_for_entid(entid).ok_or(ErrorKind::UnrecognizedEntid(*entid).into()) self.attribute_for_entid(entid).ok_or(ErrorKind::UnrecognizedEntid(entid).into())
} }
/// Create a valid `Schema` from the constituent maps. /// Create a valid `Schema` from the constituent maps.
pub fn from(ident_map: IdentMap, schema_map: SchemaMap) -> Result<Schema> { fn from_ident_map_and_schema_map(ident_map: IdentMap, schema_map: SchemaMap) -> Result<Schema> {
let entid_map: EntidMap = ident_map.iter().map(|(k, v)| (v.clone(), k.clone())).collect(); let entid_map: EntidMap = ident_map.iter().map(|(k, v)| (v.clone(), k.clone())).collect();
validate_schema_map(&entid_map, &schema_map)?; validate_schema_map(&entid_map, &schema_map)?;
@ -79,7 +85,7 @@ impl Schema {
} }
/// Turn vec![(String(:ident), String(:key), TypedValue(:value)), ...] into a Mentat `Schema`. /// Turn vec![(String(:ident), String(:key), TypedValue(:value)), ...] into a Mentat `Schema`.
pub fn from_ident_map_and_triples<U>(ident_map: IdentMap, assertions: U) -> Result<Schema> fn from_ident_map_and_triples<U>(ident_map: IdentMap, assertions: U) -> Result<Schema>
where U: IntoIterator<Item=(String, String, TypedValue)>{ where U: IntoIterator<Item=(String, String, TypedValue)>{
let mut schema_map = SchemaMap::new(); let mut schema_map = SchemaMap::new();
for (ref symbolic_ident, ref symbolic_attr, ref value) in assertions.into_iter() { for (ref symbolic_ident, ref symbolic_attr, ref value) in assertions.into_iter() {
@ -167,6 +173,6 @@ impl Schema {
} }
}; };
Schema::from(ident_map.clone(), schema_map) Schema::from_ident_map_and_schema_map(ident_map.clone(), schema_map)
} }
} }

View file

@ -20,6 +20,7 @@ pub use self::mentat_core::{
TypedValue, TypedValue,
Attribute, Attribute,
AttributeBitFlags, AttributeBitFlags,
Schema,
}; };
/// Represents one partition of the entid space. /// Represents one partition of the entid space.
@ -40,40 +41,6 @@ impl Partition {
/// Map partition names to `Partition` instances. /// Map partition names to `Partition` instances.
pub type PartitionMap = BTreeMap<String, Partition>; pub type PartitionMap = BTreeMap<String, Partition>;
/// Map `String` idents (`:db/ident`) to positive integer entids (`1`).
pub type IdentMap = BTreeMap<String, Entid>;
/// Map positive integer entids (`1`) to `String` idents (`:db/ident`).
pub type EntidMap = BTreeMap<Entid, String>;
/// Map attribute entids to `Attribute` instances.
pub type SchemaMap = BTreeMap<i64, Attribute>;
/// Represents a Mentat schema.
///
/// Maintains the mapping between string idents and positive integer entids; and exposes the schema
/// flags associated to a given entid (equivalently, ident).
///
/// TODO: consider a single bi-directional map instead of separate ident->entid and entid->ident
/// maps.
#[derive(Clone,Debug,Default,Eq,Hash,Ord,PartialOrd,PartialEq)]
pub struct Schema {
/// Map entid->ident.
///
/// Invariant: is the inverse map of `ident_map`.
pub entid_map: EntidMap,
/// Map ident->entid.
///
/// Invariant: is the inverse map of `entid_map`.
pub ident_map: IdentMap,
/// Map entid->attribute flags.
///
/// Invariant: key-set is the same as the key-set of `entid_map` (equivalently, the value-set of
/// `ident_map`).
pub schema_map: SchemaMap,
}
/// Represents the metadata required to query from, or apply transactions to, a Mentat store. /// Represents the metadata required to query from, or apply transactions to, a Mentat store.
/// ///