// Copyright 2016 Mozilla // // Licensed under the Apache License, Version 2.0 (the "License"); you may not use // this file except in compliance with the License. You may obtain a copy of the // License at http://www.apache.org/licenses/LICENSE-2.0 // Unless required by applicable law or agreed to in writing, software distributed // under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR // CONDITIONS OF ANY KIND, either express or implied. See the License for the // specific language governing permissions and limitations under the License. extern crate edn; extern crate ordered_float; use std::collections::BTreeMap; use self::ordered_float::OrderedFloat; use self::edn::NamespacedKeyword; /// Core types defining a Mentat knowledge base. /// Represents one entid in the entid space. /// /// Per https://www.sqlite.org/datatype3.html (see also http://stackoverflow.com/a/8499544), SQLite /// stores signed integers up to 64 bits in size. Since u32 is not appropriate for our use case, we /// use i64 rather than manually truncating u64 to u63 and casting to i64 throughout the codebase. pub type Entid = i64; /// 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)] pub enum ValueType { Ref, Boolean, Instant, Long, Double, String, Keyword, } /// Represents a Mentat value in a particular value set. // TODO: expand to include :db.type/{instant,url,uuid}. // TODO: BigInt? #[derive(Clone,Debug,Eq,Hash,Ord,PartialOrd,PartialEq)] pub enum TypedValue { Ref(Entid), Boolean(bool), Long(i64), Double(OrderedFloat), // TODO: &str throughout? String(String), Keyword(String), } impl TypedValue { /// Returns true if the provided type is `Some` and matches this value's type, or if the /// provided type is `None`. #[inline] pub fn is_congruent_with>>(&self, t: T) -> bool { t.into().map_or(true, |x| self.matches_type(x)) } #[inline] pub fn matches_type(&self, t: ValueType) -> bool { self.value_type() == t } pub fn value_type(&self) -> ValueType { match self { &TypedValue::Ref(_) => ValueType::Ref, &TypedValue::Boolean(_) => ValueType::Boolean, &TypedValue::Long(_) => ValueType::Long, &TypedValue::Double(_) => ValueType::Double, &TypedValue::String(_) => ValueType::String, &TypedValue::Keyword(_) => ValueType::Keyword, } } } #[test] fn test_typed_value() { assert!(TypedValue::Boolean(false).is_congruent_with(None)); assert!(TypedValue::Boolean(false).is_congruent_with(ValueType::Boolean)); assert!(!TypedValue::String("foo".to_string()).is_congruent_with(ValueType::Boolean)); assert!(TypedValue::String("foo".to_string()).is_congruent_with(ValueType::String)); assert!(TypedValue::String("foo".to_string()).is_congruent_with(None)); } /// Bit flags used in `flags0` column in temporary tables created during search, /// such as the `search_results`, `inexact_searches` and `exact_searches` tables. /// When moving to a more concrete table, such as `datoms`, they are expanded out /// via these flags and put into their own column rather than a bit field. pub enum AttributeBitFlags { IndexAVET = 1 << 0, IndexVAET = 1 << 1, IndexFulltext = 1 << 2, UniqueValue = 1 << 3, } /// A Mentat schema attribute has a value type and several other flags determining how assertions /// with the attribute are interpreted. /// /// TODO: consider packing this into a bitfield or similar. #[derive(Clone,Debug,Eq,Hash,Ord,PartialOrd,PartialEq)] pub struct Attribute { /// The associated value type, i.e., `:db/valueType`? pub value_type: ValueType, /// `true` if this attribute is multi-valued, i.e., it is `:db/cardinality /// :db.cardinality/many`. `false` if this attribute is single-valued (the default), i.e., it /// is `:db/cardinality :db.cardinality/one`. pub multival: bool, /// `true` if this attribute is unique-value, i.e., it is `:db/unique :db.unique/value`. /// /// *Unique-value* means that there is at most one assertion with the attribute and a /// particular value in the datom store. pub unique_value: bool, /// `true` if this attribute is unique-identity, i.e., it is `:db/unique :db.unique/identity`. /// /// Unique-identity attributes always have value type `Ref`. /// /// *Unique-identity* means that the attribute is *unique-value* and that they can be used in /// lookup-refs and will automatically upsert where appropriate. pub unique_identity: bool, /// `true` if this attribute is automatically indexed, i.e., it is `:db/indexing true`. pub index: bool, /// `true` if this attribute is automatically fulltext indexed, i.e., it is `:db/fulltext true`. /// /// Fulltext attributes always have string values. pub fulltext: bool, /// `true` if this attribute is a component, i.e., it is `:db/isComponent true`. /// /// Component attributes always have value type `Ref`. /// /// They are used to compose entities from component sub-entities: they are fetched recursively /// by pull expressions, and they are automatically recursively deleted where appropriate. pub component: bool, } impl Attribute { /// Combine several attribute flags into a bitfield used in temporary search tables. pub fn flags(&self) -> u8 { let mut flags: u8 = 0; if self.index { flags |= AttributeBitFlags::IndexAVET as u8; } if self.value_type == ValueType::Ref { flags |= AttributeBitFlags::IndexVAET as u8; } if self.fulltext { flags |= AttributeBitFlags::IndexFulltext as u8; } if self.unique_value { flags |= AttributeBitFlags::UniqueValue as u8; } flags } } impl Default for Attribute { fn default() -> Attribute { Attribute { // There's no particular reason to favour one value type, so Ref it is. value_type: ValueType::Ref, fulltext: false, index: false, multival: false, unique_value: false, unique_identity: false, component: false, } } } /// Map `NamespacedKeyword` idents (`:db/ident`) to positive integer entids (`1`). pub type IdentMap = BTreeMap; /// Map positive integer entids (`1`) to `NamespacedKeyword` 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<&NamespacedKeyword> { self.entid_map.get(&x) } pub fn get_entid(&self, x: &NamespacedKeyword) -> 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 attribute_for_ident(&self, ident: &NamespacedKeyword) -> Option<&Attribute> { self.get_entid(&ident) .and_then(|x| self.attribute_for_entid(x)) } /// Return true if the provided entid identifies an attribute in this schema. pub fn is_attribute(&self, x: Entid) -> bool { self.schema_map.contains_key(&x) } /// Return true if the provided ident identifies an attribute in this schema. pub fn identifies_attribute(&self, x: &NamespacedKeyword) -> bool { self.get_entid(x).map(|e| self.is_attribute(e)).unwrap_or(false) } } #[cfg(test)] mod test { use super::*; #[test] fn test_attribute_flags() { let attr1 = Attribute { index: true, value_type: ValueType::Ref, fulltext: false, unique_value: false, multival: false, unique_identity: false, component: false, }; assert!(attr1.flags() & AttributeBitFlags::IndexAVET as u8 != 0); assert!(attr1.flags() & AttributeBitFlags::IndexVAET as u8 != 0); assert!(attr1.flags() & AttributeBitFlags::IndexFulltext as u8 == 0); assert!(attr1.flags() & AttributeBitFlags::UniqueValue as u8 == 0); let attr2 = Attribute { index: false, value_type: ValueType::Boolean, fulltext: true, unique_value: true, multival: false, unique_identity: false, component: false, }; assert!(attr2.flags() & AttributeBitFlags::IndexAVET as u8 == 0); assert!(attr2.flags() & AttributeBitFlags::IndexVAET as u8 == 0); assert!(attr2.flags() & AttributeBitFlags::IndexFulltext as u8 != 0); assert!(attr2.flags() & AttributeBitFlags::UniqueValue as u8 != 0); } } pub mod intern_set;