From a87e5a3ec7f865f9bc010c8f1c21d04d7003cd91 Mon Sep 17 00:00:00 2001 From: Richard Newman Date: Mon, 13 Feb 2017 19:20:20 -0800 Subject: [PATCH] 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. --- core/src/lib.rs | 60 +++++++++++++++++++++++++++++++++++++++++++++ db/src/bootstrap.rs | 9 +++++-- db/src/db.rs | 28 ++++++++++++++------- db/src/debug.rs | 2 +- db/src/schema.rs | 50 ++++++++++++++++++++----------------- db/src/types.rs | 35 +------------------------- 6 files changed, 116 insertions(+), 68 deletions(-) diff --git a/core/src/lib.rs b/core/src/lib.rs index 82ddd7b1..b03a997f 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -9,6 +9,8 @@ // specific language governing permissions and limitations under the License. extern crate ordered_float; + +use std::collections::BTreeMap; use self::ordered_float::OrderedFloat; /// 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; + +/// Map positive integer entids (`1`) to `String` idents (`:db/ident`). +pub type EntidMap = BTreeMap; + +/// Map attribute entids to `Attribute` instances. +pub type SchemaMap = BTreeMap; + +/// 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 { + 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)] mod test { use super::*; diff --git a/db/src/bootstrap.rs b/db/src/bootstrap.rs index 51943091..e7efd07b 100644 --- a/db/src/bootstrap.rs +++ b/db/src/bootstrap.rs @@ -18,8 +18,13 @@ use entids; use db::TypedSQLValue; use mentat_tx::entities::Entity; use mentat_tx_parser; -use mentat_core::TypedValue; -use types::{IdentMap, Partition, PartitionMap, Schema}; +use mentat_core::{ + IdentMap, + Schema, + TypedValue, +}; +use schema::SchemaBuilding; +use types::{Partition, PartitionMap}; use values; /// The first transaction ID applied to the knowledge base. diff --git a/db/src/db.rs b/db/src/db.rs index 11afd6a3..788c8898 100644 --- a/db/src/db.rs +++ b/db/src/db.rs @@ -23,10 +23,20 @@ use ::{repeat_values, to_namespaced_keyword}; use bootstrap; use edn::types::Value; use entids; +use mentat_core::{ + Attribute, + AttributeBitFlags, + Entid, + IdentMap, + Schema, + TypedValue, + ValueType, +}; use mentat_tx::entities as entmod; use mentat_tx::entities::{Entity, OpType}; 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(uri: T) -> rusqlite::Result where T: AsRef { let conn = match uri.as_ref().to_string_lossy().len() { @@ -446,7 +456,7 @@ impl DB { (&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.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. (value_type, _) => bail!(ErrorKind::BadEDNValuePair(value.clone(), value_type.clone())), } @@ -535,7 +545,7 @@ impl DB { /* added0 */ bool, /* flags0 */ u8)>> = chunk.map(|&(e, a, ref typed_value, added)| { 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. let (value, value_type_tag): (ToSqlOutput, i32) = typed_value.to_sql_value_pair(); @@ -725,15 +735,15 @@ impl DB { let e: i64 = match 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_ { &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 { bail!(ErrorKind::NotYetImplemented(format!("Transacting :db/fulltext entities is not yet implemented: {:?}", entity))) } @@ -760,15 +770,15 @@ impl DB { let e: i64 = match 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_ { &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 { bail!(ErrorKind::NotYetImplemented(format!("Transacting :db/fulltext entities is not yet implemented: {:?}", entity))) } diff --git a/db/src/debug.rs b/db/src/debug.rs index b4f64527..65fb8bf2 100644 --- a/db/src/debug.rs +++ b/db/src/debug.rs @@ -107,7 +107,7 @@ impl Transactions { /// Convert a numeric entid to an ident `Entid` if possible, otherwise a numeric `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 diff --git a/db/src/schema.rs b/db/src/schema.rs index f3afb022..7e7aa9c0 100644 --- a/db/src/schema.rs +++ b/db/src/schema.rs @@ -12,7 +12,16 @@ use entids; 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. 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(()) } -impl Schema { - pub fn get_ident(&self, x: &Entid) -> Option<&String> { - self.entid_map.get(x) +pub trait SchemaBuilding { + fn require_ident(&self, entid: Entid) -> Result<&String>; + fn require_entid(&self, ident: &String) -> Result; + fn require_attribute_for_entid(&self, entid: Entid) -> Result<&Attribute>; + fn from_ident_map_and_schema_map(ident_map: IdentMap, schema_map: SchemaMap) -> Result; + fn from_ident_map_and_triples(ident_map: IdentMap, assertions: U) -> Result + where U: IntoIterator; +} + +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> { - 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> { + fn require_entid(&self, ident: &String) -> Result { self.get_entid(&ident).ok_or(ErrorKind::UnrecognizedIdent(ident.clone()).into()) } - pub fn require_attribute_for_entid(&self, entid: &Entid) -> Result<&Attribute> { - self.attribute_for_entid(entid).ok_or(ErrorKind::UnrecognizedEntid(*entid).into()) + fn require_attribute_for_entid(&self, entid: Entid) -> Result<&Attribute> { + self.attribute_for_entid(entid).ok_or(ErrorKind::UnrecognizedEntid(entid).into()) } /// Create a valid `Schema` from the constituent maps. - pub fn from(ident_map: IdentMap, schema_map: SchemaMap) -> Result { + fn from_ident_map_and_schema_map(ident_map: IdentMap, schema_map: SchemaMap) -> Result { let entid_map: EntidMap = ident_map.iter().map(|(k, v)| (v.clone(), k.clone())).collect(); 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`. - pub fn from_ident_map_and_triples(ident_map: IdentMap, assertions: U) -> Result + fn from_ident_map_and_triples(ident_map: IdentMap, assertions: U) -> Result where U: IntoIterator{ let mut schema_map = SchemaMap::new(); 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) } } diff --git a/db/src/types.rs b/db/src/types.rs index 465a61c6..dbc3823c 100644 --- a/db/src/types.rs +++ b/db/src/types.rs @@ -20,6 +20,7 @@ pub use self::mentat_core::{ TypedValue, Attribute, AttributeBitFlags, + Schema, }; /// Represents one partition of the entid space. @@ -40,40 +41,6 @@ impl Partition { /// Map partition names to `Partition` instances. pub type PartitionMap = BTreeMap; -/// Map `String` idents (`:db/ident`) to positive integer entids (`1`). -pub type IdentMap = BTreeMap; - -/// Map positive integer entids (`1`) to `String` idents (`:db/ident`). -pub type EntidMap = BTreeMap; - -/// Map attribute entids to `Attribute` instances. -pub type SchemaMap = BTreeMap; - -/// 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. ///