From 01d9b83a9b7c1309cd0a98320e800f280d273ad7 Mon Sep 17 00:00:00 2001 From: Richard Newman Date: Thu, 15 Feb 2018 08:22:34 -0800 Subject: [PATCH] Add EntityBuilder.add_kw. (#558) r=emily * Add EntityBuilder.add_kw. This allows you to skip your own attribute lookups, at the cost of potentially doing the work more than once. Also does value type checking. --- src/entity_builder.rs | 77 ++++++++++++++++++++++++++++++++++++++++--- src/errors.rs | 6 ++++ 2 files changed, 79 insertions(+), 4 deletions(-) diff --git a/src/entity_builder.rs b/src/entity_builder.rs index c46328b0..a449a52b 100644 --- a/src/entity_builder.rs +++ b/src/entity_builder.rs @@ -54,7 +54,9 @@ // the transactor -- is intimately tied to EDN and to spanned values. use mentat_core::{ + HasSchema, KnownEntid, + NamespacedKeyword, TypedValue, }; @@ -83,6 +85,7 @@ use conn::{ }; use errors::{ + ErrorKind, Result, }; @@ -99,6 +102,7 @@ pub struct EntityBuilder { } pub trait BuildTerms where Self: Sized { + fn named_tempid(&mut self, name: String) -> TempIdHandle; fn describe_tempid(self, name: &str) -> EntityBuilder; fn describe(self, entity: E) -> EntityBuilder where E: IntoThing>; fn add(&mut self, e: E, a: KnownEntid, v: V) -> Result<()> @@ -110,6 +114,10 @@ pub trait BuildTerms where Self: Sized { } impl BuildTerms for TermBuilder { + fn named_tempid(&mut self, name: String) -> TempIdHandle { + self.tempids.intern(TempId::External(name)) + } + fn describe_tempid(mut self, name: &str) -> EntityBuilder { let e = self.named_tempid(name.into()); self.describe(e) @@ -153,10 +161,6 @@ impl TermBuilder { } } - pub fn named_tempid(&mut self, name: String) -> TempIdHandle { - self.tempids.intern(TempId::External(name)) - } - #[allow(dead_code)] pub fn numbered_tempid(&mut self, id: i64) -> TempIdHandle { self.tempids.intern(TempId::Internal(id)) @@ -217,6 +221,10 @@ impl<'a, 'c> InProgressBuilder<'a, 'c> { } impl<'a, 'c> BuildTerms for InProgressBuilder<'a, 'c> { + fn named_tempid(&mut self, name: String) -> TempIdHandle { + self.builder.named_tempid(name) + } + fn describe_tempid(mut self, name: &str) -> EntityBuilder> { let e = self.builder.named_tempid(name.into()); self.describe(e) @@ -242,7 +250,36 @@ impl<'a, 'c> BuildTerms for InProgressBuilder<'a, 'c> { } } +impl<'a, 'c> InProgressBuilder<'a, 'c> { + pub fn add_kw(&mut self, e: E, a: &NamespacedKeyword, v: V) -> Result<()> + where E: IntoThing>, + V: IntoThing> { + let attribute: KnownEntid; + let value: TypedValueOr; + if let Some((attr, aa)) = self.in_progress.attribute_for_ident(a) { + let vv = v.into_thing(); + if let Either::Left(ref tv) = vv { + let provided = tv.value_type(); + let expected = attr.value_type; + if provided != expected { + bail!(ErrorKind::ValueTypeMismatch(provided, expected)); + } + } + attribute = aa; + value = vv; + } else { + bail!(ErrorKind::UnknownAttribute(a.to_string())); + } + self.add(e, attribute, value) + } +} + impl<'a, 'c> EntityBuilder> { + pub fn add_kw(&mut self, a: &NamespacedKeyword, v: V) -> Result<()> + where V: IntoThing> { + self.builder.add_kw(self.entity.clone(), a, v) + } + /// Build the terms from this builder and transact them against the current /// `InProgress`. This method _always_ returns the `InProgress` -- failure doesn't /// imply an automatic rollback. @@ -380,6 +417,38 @@ mod testing { } } + #[test] + fn test_in_progress_builder() { + let mut sqlite = mentat_db::db::new_connection("").unwrap(); + let mut conn = Conn::connect(&mut sqlite).unwrap(); + + // Give ourselves a schema to work with! + conn.transact(&mut sqlite, r#"[ + [:db/add "o" :db/ident :foo/one] + [:db/add "o" :db/valueType :db.type/long] + [:db/add "o" :db/cardinality :db.cardinality/one] + [:db/add "m" :db/ident :foo/many] + [:db/add "m" :db/valueType :db.type/string] + [:db/add "m" :db/cardinality :db.cardinality/many] + [:db/add "r" :db/ident :foo/ref] + [:db/add "r" :db/valueType :db.type/ref] + [:db/add "r" :db/cardinality :db.cardinality/one] + ]"#).unwrap(); + + let in_progress = conn.begin_transaction(&mut sqlite).expect("begun successfully"); + + // We can use this or not! + let a_many = in_progress.get_entid(&kw!(:foo/many)).expect(":foo/many"); + + let mut builder = in_progress.builder(); + let e_x = builder.named_tempid("x".into()); + let v_many_1 = TypedValue::typed_string("Some text"); + let v_many_2 = TypedValue::typed_string("Other text"); + builder.add_kw(e_x.clone(), &kw!(:foo/many), v_many_1).expect("add succeeded"); + builder.add(e_x.clone(), a_many, v_many_2).expect("add succeeded"); + builder.commit().expect("commit succeeded"); + } + #[test] fn test_entity_builder() { let mut sqlite = mentat_db::db::new_connection("").unwrap(); diff --git a/src/errors.rs b/src/errors.rs index 99b16643..256f914f 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -17,6 +17,7 @@ use std::collections::BTreeSet; use edn; use mentat_core::{ Attribute, + ValueType, }; use mentat_db; use mentat_query; @@ -92,5 +93,10 @@ error_chain! { description("schema changed since query was prepared") display("schema changed since query was prepared") } + + ValueTypeMismatch(provided: ValueType, expected: ValueType) { + description("provided value doesn't match value type") + display("provided value of type {} doesn't match attribute value type {}", provided, expected) + } } }