#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.
This commit is contained in:
Emily Toop 2017-03-30 11:08:36 +01:00 committed by GitHub
parent b24db01744
commit 8e6f37e709
5 changed files with 135 additions and 3 deletions

View file

@ -6,6 +6,7 @@ workspace = ".."
[dependencies]
num = "0.1.35"
ordered-float = "0.4.0"
lazy_static = "0.2.2"
[dependencies.edn]
path = "../edn"

View file

@ -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<NamespacedKeyword>) -> edn::Value {
let mut attribute_map: BTreeMap<edn::Value, edn::Value> = 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;

View file

@ -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");

View file

@ -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.
///

View file

@ -39,7 +39,6 @@ mod schema;
mod types;
mod internal_types;
mod upsert_resolution;
mod values;
mod tx;
pub use db::{