Review comments: another test, add unreversed().

This commit is contained in:
Nick Alexander 2017-06-07 13:37:40 -07:00
parent eb220528bf
commit 59a710f80f
5 changed files with 96 additions and 49 deletions

View file

@ -2170,6 +2170,27 @@ mod tests {
"{\"s\" 65542 "{\"s\" 65542
\"t\" 65543}"); \"t\" 65543}");
// Check that we can use the same attribute in both forward and backward form in the same
// transaction.
let report = assert_transact!(conn, "[[:db/add 888 :test/dangling 889]
[:db/add 888 :test/_dangling 889]]");
assert_matches!(conn.last_transaction(),
"[[888 :test/dangling 889 ?tx true]
[889 :test/dangling 888 ?tx true]
[?tx :db/txInstant ?ms ?tx true]]");
assert_matches!(tempids(&report),
"{}");
// Check that we can use the same attribute in both forward and backward form in the same
// transaction in map notation.
let report = assert_transact!(conn, "[{:db/id 998 :test/dangling 999 :test/_dangling 999}]");
assert_matches!(conn.last_transaction(),
"[[998 :test/dangling 999 ?tx true]
[999 :test/dangling 998 ?tx true]
[?tx :db/txInstant ?ms ?tx true]]");
assert_matches!(tempids(&report),
"{}");
// Verify that we can't explode direct reverse notation with nested value maps. // Verify that we can't explode direct reverse notation with nested value maps.
assert_transact!(conn, assert_transact!(conn,
"[[:db/add \"t\" :test/_dangling {:test/many 11}]]", "[[:db/add \"t\" :test/_dangling {:test/many 11}]]",

View file

@ -257,13 +257,12 @@ impl<'conn, 'a> Tx<'conn, 'a> {
} }
} }
fn entity_a_into_term_a(&mut self, x: entmod::Entid) -> Result<(Entid, &'a Attribute)> { fn entity_a_into_term_a(&mut self, x: entmod::Entid) -> Result<Entid> {
let a = match x { let a = match x {
entmod::Entid::Entid(ref a) => *a, entmod::Entid::Entid(ref a) => *a,
entmod::Entid::Ident(ref a) => self.schema.require_entid(&a)?, entmod::Entid::Ident(ref a) => self.schema.require_entid(&a)?,
}; };
let attribute: &Attribute = self.schema.require_attribute_for_entid(a)?; Ok(a)
Ok((a, attribute))
} }
fn entity_e_into_term_v(&mut self, x: entmod::EntidOrLookupRefOrTempId) -> Result<TypedValueOr<LookupRefOrTempId>> { fn entity_e_into_term_v(&mut self, x: entmod::EntidOrLookupRefOrTempId) -> Result<TypedValueOr<LookupRefOrTempId>> {
@ -271,19 +270,15 @@ impl<'conn, 'a> Tx<'conn, 'a> {
} }
fn entity_v_into_term_e(&mut self, x: entmod::AtomOrLookupRefOrVectorOrMapNotation, backward_a: &entmod::Entid) -> Result<EntidOr<LookupRefOrTempId>> { fn entity_v_into_term_e(&mut self, x: entmod::AtomOrLookupRefOrVectorOrMapNotation, backward_a: &entmod::Entid) -> Result<EntidOr<LookupRefOrTempId>> {
let reversed_a = match backward_a { match backward_a.unreversed() {
&entmod::Entid::Entid(a) => { None => {
// Programmer error. bail!(ErrorKind::NotYetImplemented(format!("Cannot explode map notation value in :attr/_reversed notation for forward attribute")));
unreachable!("Expected ident backward attribute but got entid {}", a)
}, },
&entmod::Entid::Ident(ref a) => { Some(forward_a) => {
entmod::Entid::Ident(a.to_reversed()) let forward_a = self.entity_a_into_term_a(forward_a)?;
}, let forward_attribute = self.schema.require_attribute_for_entid(forward_a)?;
}; if forward_attribute.value_type != ValueType::Ref {
let (reversed_a, reversed_attribute) = self.entity_a_into_term_a(reversed_a)?; bail!(ErrorKind::NotYetImplemented(format!("Cannot use :attr/_reversed notation for attribute {} that is not :db/valueType :db.type/ref", forward_a)))
if reversed_attribute.value_type != ValueType::Ref {
bail!(ErrorKind::NotYetImplemented(format!("Cannot use :attr/_reversed notation for attribute {} that is not :db/valueType :db.type/ref", reversed_a)))
} }
match x { match x {
@ -294,14 +289,11 @@ impl<'conn, 'a> Tx<'conn, 'a> {
if let Some(text) = v.inner.as_text() { if let Some(text) = v.inner.as_text() {
Ok(Either::Right(LookupRefOrTempId::TempId(self.intern_temp_id(TempId::External(text.clone()))))) Ok(Either::Right(LookupRefOrTempId::TempId(self.intern_temp_id(TempId::External(text.clone())))))
} else { } else {
// Here is where we do schema-aware typechecking: we either assert that if let TypedValue::Ref(entid) = self.schema.to_typed_value(&v.clone().without_spans(), ValueType::Ref)? {
// the given value is in the attribute's value set, or (in limited
// cases) coerce the value into the attribute's value set.
if let TypedValue::Ref(entid) = self.schema.to_typed_value(&v.clone().without_spans(), reversed_attribute.value_type)? {
Ok(Either::Left(entid)) Ok(Either::Left(entid))
} else { } else {
// reversed_attribute is :db.type/ref, so this shouldn't happen. // The given value is expected to be :db.type/ref, so this shouldn't happen.
unreachable!() bail!(ErrorKind::NotYetImplemented(format!("Cannot use :attr/_reversed notation for attribute {} with value that is not :db.valueType :db.type/ref", forward_a)))
} }
} }
}, },
@ -310,10 +302,12 @@ impl<'conn, 'a> Tx<'conn, 'a> {
Ok(Either::Right(LookupRefOrTempId::LookupRef(self.intern_lookup_ref(lookup_ref)?))), Ok(Either::Right(LookupRefOrTempId::LookupRef(self.intern_lookup_ref(lookup_ref)?))),
entmod::AtomOrLookupRefOrVectorOrMapNotation::Vector(_) => entmod::AtomOrLookupRefOrVectorOrMapNotation::Vector(_) =>
bail!(ErrorKind::NotYetImplemented(format!("Cannot explode vector value in :attr/_reversed notation for attribute {}", reversed_a))), bail!(ErrorKind::NotYetImplemented(format!("Cannot explode vector value in :attr/_reversed notation for attribute {}", forward_a))),
entmod::AtomOrLookupRefOrVectorOrMapNotation::MapNotation(_) => entmod::AtomOrLookupRefOrVectorOrMapNotation::MapNotation(_) =>
bail!(ErrorKind::NotYetImplemented(format!("Cannot explode map notation value in :attr/_reversed notation for attribute {}", reversed_a))), bail!(ErrorKind::NotYetImplemented(format!("Cannot explode map notation value in :attr/_reversed notation for attribute {}", forward_a))),
}
},
} }
} }
} }
@ -348,13 +342,14 @@ impl<'conn, 'a> Tx<'conn, 'a> {
}, },
Entity::AddOrRetract { op, e, a, v } => { Entity::AddOrRetract { op, e, a, v } => {
if a.is_backward() { if let Some(reversed_a) = a.unreversed() {
let reversed_e = in_process.entity_v_into_term_e(v, &a)?; let reversed_e = in_process.entity_v_into_term_e(v, &a)?;
let (reversed_a, _reversed_attribute) = in_process.entity_a_into_term_a(a.to_reversed().unwrap())?; let reversed_a = in_process.entity_a_into_term_a(reversed_a)?;
let reversed_v = in_process.entity_e_into_term_v(e)?; let reversed_v = in_process.entity_e_into_term_v(e)?;
terms.push(Term::AddOrRetract(OpType::Add, reversed_e, reversed_a, reversed_v)); terms.push(Term::AddOrRetract(OpType::Add, reversed_e, reversed_a, reversed_v));
} else { } else {
let (a, attribute) = in_process.entity_a_into_term_a(a)?; let a = in_process.entity_a_into_term_a(a)?;
let attribute = self.schema.require_attribute_for_entid(a)?;
let v = match v { let v = match v {
entmod::AtomOrLookupRefOrVectorOrMapNotation::Atom(v) => { entmod::AtomOrLookupRefOrVectorOrMapNotation::Atom(v) => {
@ -426,21 +421,22 @@ impl<'conn, 'a> Tx<'conn, 'a> {
} }
for (inner_a, inner_v) in map_notation { for (inner_a, inner_v) in map_notation {
if inner_a.is_backward() { if let Some(reversed_a) = inner_a.unreversed() {
// We definitely have a reference. The reference might be // We definitely have a reference. The reference might be
// dangling (a bare entid, for example), but we don't yet // dangling (a bare entid, for example), but we don't yet
// support nested maps and reverse notation simultaneously // support nested maps and reverse notation simultaneously
// (i.e., we don't accept {:reverse/_attribute {:nested map}}) // (i.e., we don't accept {:reverse/_attribute {:nested map}})
// so we don't need to check that the nested map reference isn't // so we don't need to check that the nested map reference isn't
// danglign. // dangling.
dangling = false; dangling = false;
let reversed_e = in_process.entity_v_into_term_e(inner_v, &inner_a)?; let reversed_e = in_process.entity_v_into_term_e(inner_v, &inner_a)?;
let (reversed_a, _reversed_attribute) = in_process.entity_a_into_term_a(inner_a.to_reversed().unwrap())?; let reversed_a = in_process.entity_a_into_term_a(reversed_a)?;
let reversed_v = in_process.entity_e_into_term_v(db_id.clone())?; let reversed_v = in_process.entity_e_into_term_v(db_id.clone())?;
terms.push(Term::AddOrRetract(OpType::Add, reversed_e, reversed_a, reversed_v)); terms.push(Term::AddOrRetract(OpType::Add, reversed_e, reversed_a, reversed_v));
} else { } else {
let (inner_a, inner_attribute) = in_process.entity_a_into_term_a(inner_a)?; let inner_a = in_process.entity_a_into_term_a(inner_a)?;
let inner_attribute = self.schema.require_attribute_for_entid(inner_a)?;
if inner_attribute.unique == Some(attribute::Unique::Identity) { if inner_attribute.unique == Some(attribute::Unique::Identity) {
dangling = false; dangling = false;
} }

View file

@ -214,6 +214,30 @@ impl NamespacedKeyword {
namespace: self.namespace.clone(), namespace: self.namespace.clone(),
} }
} }
/// If this `NamespacedKeyword` is 'backward' (see `symbols::NamespacedKeyword::is_backward`),
/// return `Some('forward name')`; otherwise, return `None`.
///
/// # Examples
///
/// ```rust
/// # use edn::symbols::NamespacedKeyword;
/// let nsk = NamespacedKeyword::new("foo", "bar");
/// assert_eq!(None, nsk.unreversed());
///
/// let reversed = nsk.to_reversed();
/// assert_eq!(Some(nsk), reversed.unreversed());
/// ```
pub fn unreversed(&self) -> Option<NamespacedKeyword> {
if self.is_backward() {
Some(NamespacedKeyword {
name: self.name[1..].to_string(),
namespace: self.namespace.clone(),
})
} else {
None
}
}
} }
// //

View file

@ -594,7 +594,6 @@ mod test {
use chrono::{ use chrono::{
DateTime, DateTime,
TimeZone,
UTC, UTC,
}; };
use num::BigInt; use num::BigInt;

View file

@ -70,6 +70,13 @@ impl Entid {
&Entid::Ident(ref a) => Some(Entid::Ident(a.to_reversed())), &Entid::Ident(ref a) => Some(Entid::Ident(a.to_reversed())),
} }
} }
pub fn unreversed(&self) -> Option<Entid> {
match self {
&Entid::Entid(_) => None,
&Entid::Ident(ref a) => a.unreversed().map(Entid::Ident),
}
}
} }
#[derive(Clone, Debug, Eq, Hash, Ord, PartialOrd, PartialEq)] #[derive(Clone, Debug, Eq, Hash, Ord, PartialOrd, PartialEq)]