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:
parent
2e303f4837
commit
a87e5a3ec7
6 changed files with 116 additions and 68 deletions
|
@ -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::*;
|
||||||
|
|
|
@ -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.
|
||||||
|
|
28
db/src/db.rs
28
db/src/db.rs
|
@ -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)))
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_entid(&self, x: &String) -> Option<&Entid> {
|
impl SchemaBuilding for Schema {
|
||||||
self.ident_map.get(x)
|
fn require_ident(&self, entid: Entid) -> Result<&String> {
|
||||||
|
self.get_ident(entid).ok_or(ErrorKind::UnrecognizedEntid(entid).into())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn attribute_for_entid(&self, x: &Entid) -> Option<&Attribute> {
|
fn require_entid(&self, ident: &String) -> Result<Entid> {
|
||||||
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.
|
||||||
///
|
///
|
||||||
|
|
Loading…
Reference in a new issue