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::{