// 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 chrono; extern crate enum_set; extern crate failure; extern crate indexmap; extern crate ordered_float; extern crate uuid; extern crate core_traits; extern crate edn; use core_traits::{ Attribute, Entid, KnownEntid, ValueType, }; mod cache; use std::collections::{ BTreeMap, }; pub use uuid::Uuid; pub use chrono::{ DateTime, Timelike, // For truncation. }; pub use edn::{ Cloned, FromMicros, FromRc, Keyword, ToMicros, Utc, ValueRc, }; pub use edn::parse::{ parse_query, }; pub use cache::{ CachedAttributes, UpdateableCache, }; /// Core types defining a Mentat knowledge base. mod types; mod tx_report; mod sql_types; pub use tx_report::{ TxReport, }; pub use types::{ ValueTypeTag, }; pub use sql_types::{ SQLTypeAffinity, SQLValueType, SQLValueTypeSet, }; /// Map `Keyword` idents (`:db/ident`) to positive integer entids (`1`). pub type IdentMap = BTreeMap; /// Map positive integer entids (`1`) to `Keyword` idents (`:db/ident`). pub type EntidMap = BTreeMap; /// Map attribute entids to `Attribute` instances. pub type AttributeMap = 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 attribute_map: AttributeMap, /// Maintain a vec of unique attribute IDs for which the corresponding attribute in `attribute_map` /// has `.component == true`. pub component_attributes: Vec, } pub trait HasSchema { fn entid_for_type(&self, t: ValueType) -> Option; fn get_ident(&self, x: T) -> Option<&Keyword> where T: Into; fn get_entid(&self, x: &Keyword) -> Option; fn attribute_for_entid(&self, x: T) -> Option<&Attribute> where T: Into; // Returns the attribute and the entid named by the provided ident. fn attribute_for_ident(&self, ident: &Keyword) -> Option<(&Attribute, KnownEntid)>; /// Return true if the provided entid identifies an attribute in this schema. fn is_attribute(&self, x: T) -> bool where T: Into; /// Return true if the provided ident identifies an attribute in this schema. fn identifies_attribute(&self, x: &Keyword) -> bool; fn component_attributes(&self) -> &[Entid]; } impl Schema { pub fn new(ident_map: IdentMap, entid_map: EntidMap, attribute_map: AttributeMap) -> Schema { let mut s = Schema { ident_map, entid_map, attribute_map, component_attributes: Vec::new() }; s.update_component_attributes(); s } /// Returns an symbolic representation of the schema suitable for applying across Mentat stores. pub fn to_edn_value(&self) -> edn::Value { edn::Value::Vector((&self.attribute_map).iter() .map(|(entid, attribute)| attribute.to_edn_value(self.get_ident(*entid).cloned())) .collect()) } fn get_raw_entid(&self, x: &Keyword) -> Option { self.ident_map.get(x).map(|x| *x) } pub fn update_component_attributes(&mut self) { let mut components: Vec; components = self.attribute_map .iter() .filter_map(|(k, v)| if v.component { Some(*k) } else { None }) .collect(); components.sort_unstable(); self.component_attributes = components; } } impl HasSchema for Schema { fn entid_for_type(&self, t: ValueType) -> Option { // TODO: this can be made more efficient. self.get_entid(&t.into_keyword()) } fn get_ident(&self, x: T) -> Option<&Keyword> where T: Into { self.entid_map.get(&x.into()) } fn get_entid(&self, x: &Keyword) -> Option { self.get_raw_entid(x).map(KnownEntid) } fn attribute_for_entid(&self, x: T) -> Option<&Attribute> where T: Into { self.attribute_map.get(&x.into()) } fn attribute_for_ident(&self, ident: &Keyword) -> 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: T) -> bool where T: Into { self.attribute_map.contains_key(&x.into()) } /// Return true if the provided ident identifies an attribute in this schema. fn identifies_attribute(&self, x: &Keyword) -> bool { self.get_raw_entid(x).map(|e| self.is_attribute(e)).unwrap_or(false) } fn component_attributes(&self) -> &[Entid] { &self.component_attributes } } pub mod counter; pub mod util; /// A helper macro to sequentially process an iterable sequence, /// evaluating a block between each pair of items. /// /// This is used to simply and efficiently produce output like /// /// ```sql /// 1, 2, 3 /// ``` /// /// or /// /// ```sql /// x = 1 AND y = 2 /// ``` /// /// without producing an intermediate string sequence. #[macro_export] macro_rules! interpose { ( $name: pat, $across: expr, $body: block, $inter: block ) => { interpose_iter!($name, $across.iter(), $body, $inter) } } /// A helper to bind `name` to values in `across`, running `body` for each value, /// and running `inter` between each value. See `interpose` for examples. #[macro_export] macro_rules! interpose_iter { ( $name: pat, $across: expr, $body: block, $inter: block ) => { let mut seq = $across; if let Some($name) = seq.next() { $body; for $name in seq { $inter; $body; } } } } #[cfg(test)] mod test { use super::*; use std::str::FromStr; use core_traits::{ attribute, TypedValue, }; fn associate_ident(schema: &mut Schema, i: Keyword, e: Entid) { schema.entid_map.insert(e, i.clone()); schema.ident_map.insert(i, e); } fn add_attribute(schema: &mut Schema, e: Entid, a: Attribute) { schema.attribute_map.insert(e, a); } #[test] fn test_datetime_truncation() { let dt: DateTime = DateTime::from_str("2018-01-11T00:34:09.273457004Z").expect("parsed"); let expected: DateTime = DateTime::from_str("2018-01-11T00:34:09.273457Z").expect("parsed"); let tv: TypedValue = dt.into(); if let TypedValue::Instant(roundtripped) = tv { assert_eq!(roundtripped, expected); } else { panic!(); } } #[test] fn test_as_edn_value() { let mut schema = Schema::default(); let attr1 = Attribute { index: true, value_type: ValueType::Ref, fulltext: false, unique: None, multival: false, component: false, no_history: true, }; associate_ident(&mut schema, Keyword::namespaced("foo", "bar"), 97); add_attribute(&mut schema, 97, attr1); let attr2 = Attribute { index: false, value_type: ValueType::String, fulltext: true, unique: Some(attribute::Unique::Value), multival: true, component: false, no_history: false, }; associate_ident(&mut schema, Keyword::namespaced("foo", "bas"), 98); add_attribute(&mut schema, 98, attr2); let attr3 = Attribute { index: false, value_type: ValueType::Boolean, fulltext: false, unique: Some(attribute::Unique::Identity), multival: false, component: true, no_history: false, }; associate_ident(&mut schema, Keyword::namespaced("foo", "bat"), 99); add_attribute(&mut schema, 99, attr3); let value = schema.to_edn_value(); let expected_output = r#"[ { :db/ident :foo/bar :db/valueType :db.type/ref :db/cardinality :db.cardinality/one :db/index true :db/noHistory true }, { :db/ident :foo/bas :db/valueType :db.type/string :db/cardinality :db.cardinality/many :db/unique :db.unique/value :db/fulltext true }, { :db/ident :foo/bat :db/valueType :db.type/boolean :db/cardinality :db.cardinality/one :db/unique :db.unique/identity :db/isComponent true }, ]"#; let expected_value = edn::parse::value(&expected_output).expect("to be able to parse").without_spans(); assert_eq!(expected_value, value); // let's compare the whole thing again, just to make sure we are not changing anything when we convert to edn. let value2 = schema.to_edn_value(); assert_eq!(expected_value, value2); } }