From 8e6f37e709cf25e3fc2c88ca946743a1f210a450 Mon Sep 17 00:00:00 2001 From: Emily Toop Date: Thu, 30 Mar 2017 11:08:36 +0100 Subject: [PATCH] #260 Convert Schema into edn::Value (#384) r=nalexander, r=rnewman * Part 1 - Create as_edn_value function. * Do not include defaults inside output. * Pretty-printed by default. Do we want to make that a flag? * Includes simple test just to make sure it works. * Part 2 - only include ident if available. * Part 3 - Remove spacing and newlines as unnecessary. * Update function to build edn::Value directly rather than parsing from string * Update test to actually test the functionality. * Address review comments ncalexan. * Rename `as_edn_value` to `to_edn_value`. * Move `db/src/values.rs` to `core/src/values.rs` so we can reference inside `core/src/ib.rs`. * Add `lazy-static` crate to core `Cargo.toml` * Expose `values` as a public module from `core`. * Update references to values in `db/src/bootstrap.rs` & `db/src/lib.rs`. * Add new static vars for `DB_FULLTEXT`, `DB_INDEX` & `DB_IS_COMPONENT`. * Use static vars exposed in `values` inside `to_edn_value`. * Remove `db/id` as key in attribute output and use `entid` as `db/ident` if no `ident` is found for that `entid`. * Update test to match new expected output. * Add doc comment for function * Address review comments ncalexan. * Update function docstring to give clearer description of function. * Do not all entid at all to output. * Clean up code fetching ident (make it rustier). * Address review comments rnewman. * Extract out to new `to_edn_value` functions code for creating `edn::Value`\'s for `ValueType` and `Attribute`. * Use `map()` to create schema edn value rather than a loop. * Address review comments rnewman. * pass cloned instance of ident to `Attribute::get_edn_value`. * update `use` import for `edn`. * remove unnecessary call when using ident as key on `associate_ident`. * Fixed bug whereby we didn't differentiate between `db.index/value` and `db.index/identity` when generating `edn::Value` * Add extra assert at the end to ensure we get the same output when we convert the same schema to edn multiple times * Move check for type of uniqueness to `match` statement. * Also use `iter` instead of `into_iter` when iterating schema map. --- core/Cargo.toml | 1 + core/src/lib.rs | 131 ++++++++++++++++++++++++++++++++++++- {db => core}/src/values.rs | 3 + db/src/bootstrap.rs | 2 +- db/src/lib.rs | 1 - 5 files changed, 135 insertions(+), 3 deletions(-) rename {db => core}/src/values.rs (94%) diff --git a/core/Cargo.toml b/core/Cargo.toml index 9b6c9673..a4f93198 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -6,6 +6,7 @@ workspace = ".." [dependencies] num = "0.1.35" ordered-float = "0.4.0" +lazy_static = "0.2.2" [dependencies.edn] path = "../edn" diff --git a/core/src/lib.rs b/core/src/lib.rs index 0b4bf235..aac31a71 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -8,9 +8,14 @@ // 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; +#[macro_use] +extern crate lazy_static; extern crate ordered_float; +extern crate edn; + +pub mod values; + use std::collections::BTreeMap; use self::ordered_float::OrderedFloat; use self::edn::NamespacedKeyword; @@ -37,6 +42,20 @@ pub enum ValueType { Keyword, } +impl ValueType { + pub fn to_edn_value(&self) -> edn::Value { + match self { + &ValueType::Ref => values::DB_TYPE_REF.clone(), + &ValueType::Boolean => values::DB_TYPE_BOOLEAN.clone(), + &ValueType::Instant => values::DB_TYPE_INSTANT.clone(), + &ValueType::Long => values::DB_TYPE_LONG.clone(), + &ValueType::Double => values::DB_TYPE_DOUBLE.clone(), + &ValueType::String => values::DB_TYPE_STRING.clone(), + &ValueType::Keyword => values::DB_TYPE_KEYWORD.clone(), + } + } +} + /// Represents a Mentat value in a particular value set. // TODO: expand to include :db.type/{instant,url,uuid}. // TODO: BigInt? @@ -213,6 +232,37 @@ impl Attribute { } flags } + + pub fn to_edn_value(&self, ident: Option) -> edn::Value { + let mut attribute_map: BTreeMap = BTreeMap::default(); + if let Some(ident) = ident { + attribute_map.insert(values::DB_IDENT.clone(), edn::Value::NamespacedKeyword(ident)); + } + + attribute_map.insert(values::DB_VALUE_TYPE.clone(), self.value_type.to_edn_value()); + + attribute_map.insert(values::DB_CARDINALITY.clone(), if self.multival { values::DB_CARDINALITY_MANY.clone() } else { values::DB_CARDINALITY_ONE.clone() }); + + match self.unique { + Some(attribute::Unique::Value) => { attribute_map.insert(values::DB_UNIQUE.clone(), values::DB_UNIQUE_VALUE.clone()); }, + Some(attribute::Unique::Identity) => { attribute_map.insert(values::DB_UNIQUE.clone(), values::DB_UNIQUE_IDENTITY.clone()); }, + None => (), + } + + if self.index { + attribute_map.insert(values::DB_INDEX.clone(), edn::Value::Boolean(true)); + } + + if self.fulltext { + attribute_map.insert(values::DB_FULLTEXT.clone(), edn::Value::Boolean(true)); + } + + if self.component { + attribute_map.insert(values::DB_IS_COMPONENT.clone(), edn::Value::Boolean(true)); + } + + edn::Value::Map(attribute_map) + } } impl Default for Attribute { @@ -291,12 +341,29 @@ impl Schema { pub fn identifies_attribute(&self, x: &NamespacedKeyword) -> bool { self.get_entid(x).map(|e| self.is_attribute(e)).unwrap_or(false) } + + /// 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.schema_map).iter() + .map(|(entid, attribute)| + attribute.to_edn_value(self.get_ident(*entid).cloned())) + .collect()) + } } #[cfg(test)] mod test { use super::*; + fn associate_ident(schema: &mut Schema, i: NamespacedKeyword, 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.schema_map.insert(e, a); + } + #[test] fn test_attribute_flags() { let attr1 = Attribute { @@ -341,6 +408,68 @@ mod test { assert!(attr3.flags() & AttributeBitFlags::IndexFulltext as u8 != 0); assert!(attr3.flags() & AttributeBitFlags::UniqueValue as u8 != 0); } + + #[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, + }; + associate_ident(&mut schema, NamespacedKeyword::new("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, + }; + associate_ident(&mut schema, NamespacedKeyword::new("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, + }; + + associate_ident(&mut schema, NamespacedKeyword::new("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/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/component 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); + } } pub mod intern_set; diff --git a/db/src/values.rs b/core/src/values.rs similarity index 94% rename from db/src/values.rs rename to core/src/values.rs index 958816a0..3a5fceba 100644 --- a/db/src/values.rs +++ b/core/src/values.rs @@ -42,8 +42,11 @@ lazy_static_namespaced_keyword_value!(DB_ALTER_ATTRIBUTE, "db.alter", "attribute lazy_static_namespaced_keyword_value!(DB_CARDINALITY, "db", "cardinality"); lazy_static_namespaced_keyword_value!(DB_CARDINALITY_MANY, "db.cardinality", "many"); lazy_static_namespaced_keyword_value!(DB_CARDINALITY_ONE, "db.cardinality", "one"); +lazy_static_namespaced_keyword_value!(DB_FULLTEXT, "db", "fulltext"); lazy_static_namespaced_keyword_value!(DB_IDENT, "db", "ident"); +lazy_static_namespaced_keyword_value!(DB_INDEX, "db", "index"); lazy_static_namespaced_keyword_value!(DB_INSTALL_ATTRIBUTE, "db.install", "attribute"); +lazy_static_namespaced_keyword_value!(DB_IS_COMPONENT, "db", "component"); lazy_static_namespaced_keyword_value!(DB_PART_DB, "db.part", "db"); lazy_static_namespaced_keyword_value!(DB_RETRACT, "db", "retract"); lazy_static_namespaced_keyword_value!(DB_TYPE_BOOLEAN, "db.type", "boolean"); diff --git a/db/src/bootstrap.rs b/db/src/bootstrap.rs index 921b8571..7e676dc3 100644 --- a/db/src/bootstrap.rs +++ b/db/src/bootstrap.rs @@ -22,10 +22,10 @@ use mentat_core::{ IdentMap, Schema, TypedValue, + values, }; use schema::SchemaBuilding; use types::{Partition, PartitionMap}; -use values; /// The first transaction ID applied to the knowledge base. /// diff --git a/db/src/lib.rs b/db/src/lib.rs index 5065b7e5..2767d84b 100644 --- a/db/src/lib.rs +++ b/db/src/lib.rs @@ -39,7 +39,6 @@ mod schema; mod types; mod internal_types; mod upsert_resolution; -mod values; mod tx; pub use db::{