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.
This commit is contained in:
parent
0dcb7df1c7
commit
01d9b83a9b
2 changed files with 79 additions and 4 deletions
|
@ -54,7 +54,9 @@
|
||||||
// the transactor -- is intimately tied to EDN and to spanned values.
|
// the transactor -- is intimately tied to EDN and to spanned values.
|
||||||
|
|
||||||
use mentat_core::{
|
use mentat_core::{
|
||||||
|
HasSchema,
|
||||||
KnownEntid,
|
KnownEntid,
|
||||||
|
NamespacedKeyword,
|
||||||
TypedValue,
|
TypedValue,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -83,6 +85,7 @@ use conn::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use errors::{
|
use errors::{
|
||||||
|
ErrorKind,
|
||||||
Result,
|
Result,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -99,6 +102,7 @@ pub struct EntityBuilder<T: BuildTerms + Sized> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait BuildTerms where Self: Sized {
|
pub trait BuildTerms where Self: Sized {
|
||||||
|
fn named_tempid(&mut self, name: String) -> TempIdHandle;
|
||||||
fn describe_tempid(self, name: &str) -> EntityBuilder<Self>;
|
fn describe_tempid(self, name: &str) -> EntityBuilder<Self>;
|
||||||
fn describe<E>(self, entity: E) -> EntityBuilder<Self> where E: IntoThing<KnownEntidOr<TempIdHandle>>;
|
fn describe<E>(self, entity: E) -> EntityBuilder<Self> where E: IntoThing<KnownEntidOr<TempIdHandle>>;
|
||||||
fn add<E, V>(&mut self, e: E, a: KnownEntid, v: V) -> Result<()>
|
fn add<E, V>(&mut self, e: E, a: KnownEntid, v: V) -> Result<()>
|
||||||
|
@ -110,6 +114,10 @@ pub trait BuildTerms where Self: Sized {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BuildTerms for TermBuilder {
|
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<Self> {
|
fn describe_tempid(mut self, name: &str) -> EntityBuilder<Self> {
|
||||||
let e = self.named_tempid(name.into());
|
let e = self.named_tempid(name.into());
|
||||||
self.describe(e)
|
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)]
|
#[allow(dead_code)]
|
||||||
pub fn numbered_tempid(&mut self, id: i64) -> TempIdHandle {
|
pub fn numbered_tempid(&mut self, id: i64) -> TempIdHandle {
|
||||||
self.tempids.intern(TempId::Internal(id))
|
self.tempids.intern(TempId::Internal(id))
|
||||||
|
@ -217,6 +221,10 @@ impl<'a, 'c> InProgressBuilder<'a, 'c> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, 'c> BuildTerms for 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<InProgressBuilder<'a, 'c>> {
|
fn describe_tempid(mut self, name: &str) -> EntityBuilder<InProgressBuilder<'a, 'c>> {
|
||||||
let e = self.builder.named_tempid(name.into());
|
let e = self.builder.named_tempid(name.into());
|
||||||
self.describe(e)
|
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<E, V>(&mut self, e: E, a: &NamespacedKeyword, v: V) -> Result<()>
|
||||||
|
where E: IntoThing<KnownEntidOr<TempIdHandle>>,
|
||||||
|
V: IntoThing<TypedValueOr<TempIdHandle>> {
|
||||||
|
let attribute: KnownEntid;
|
||||||
|
let value: TypedValueOr<TempIdHandle>;
|
||||||
|
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<InProgressBuilder<'a, 'c>> {
|
impl<'a, 'c> EntityBuilder<InProgressBuilder<'a, 'c>> {
|
||||||
|
pub fn add_kw<V>(&mut self, a: &NamespacedKeyword, v: V) -> Result<()>
|
||||||
|
where V: IntoThing<TypedValueOr<TempIdHandle>> {
|
||||||
|
self.builder.add_kw(self.entity.clone(), a, v)
|
||||||
|
}
|
||||||
|
|
||||||
/// Build the terms from this builder and transact them against the current
|
/// Build the terms from this builder and transact them against the current
|
||||||
/// `InProgress`. This method _always_ returns the `InProgress` -- failure doesn't
|
/// `InProgress`. This method _always_ returns the `InProgress` -- failure doesn't
|
||||||
/// imply an automatic rollback.
|
/// 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]
|
#[test]
|
||||||
fn test_entity_builder() {
|
fn test_entity_builder() {
|
||||||
let mut sqlite = mentat_db::db::new_connection("").unwrap();
|
let mut sqlite = mentat_db::db::new_connection("").unwrap();
|
||||||
|
|
|
@ -17,6 +17,7 @@ use std::collections::BTreeSet;
|
||||||
use edn;
|
use edn;
|
||||||
use mentat_core::{
|
use mentat_core::{
|
||||||
Attribute,
|
Attribute,
|
||||||
|
ValueType,
|
||||||
};
|
};
|
||||||
use mentat_db;
|
use mentat_db;
|
||||||
use mentat_query;
|
use mentat_query;
|
||||||
|
@ -92,5 +93,10 @@ error_chain! {
|
||||||
description("schema changed since query was prepared")
|
description("schema changed since query was prepared")
|
||||||
display("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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue