Generalize Entity by value type. (#701) (#691) r=rnewman

* Part 3: Parameterize Entity by value type.

This isn't quite right, because after parsing, we shouldn't care
about` `edn::ValueAndSpan`, we should care only about edn::Value.
However, I think we can drop `ValueAndSpan` entirely if we just use
`rust-peg` (and its simpler error messages) rather than a mix of
`rust-peg` and `combine`.

In any case, this paves the way to transacting `Entity<TypedValue>`,
which is a nice step towards building general entities.

* Part 1: Add AttributePlace.

* Part 2: Name other places EntityPlace and ValuePlace.

Now we're consistent and closer to self-documenting.  Both matter more
as we expose `Entity` as the thing to build for programmatic usage.

* Part 4: Allow Ident and TempId in ValuePlace.

The parser will never produce these, since determining whether an
integer/keyword or string is an ident or a tempid, respectively, in
the value place requires the schema.

But a builder that produces `Entity` instances directly will want to
produce these.
This commit is contained in:
Richard Newman 2018-05-15 00:43:07 -07:00 committed by GitHub
parent 46c2a0801f
commit b2e98f44f6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 203 additions and 133 deletions

View file

@ -290,7 +290,7 @@ pub(crate) fn bootstrap_schema() -> Schema {
Schema::from_ident_map_and_triples(ident_map, bootstrap_triples).unwrap() Schema::from_ident_map_and_triples(ident_map, bootstrap_triples).unwrap()
} }
pub(crate) fn bootstrap_entities() -> Vec<Entity> { pub(crate) fn bootstrap_entities() -> Vec<Entity<edn::ValueAndSpan>> {
let bootstrap_assertions: Value = Value::Vector([ let bootstrap_assertions: Value = Value::Vector([
symbolic_schema_to_assertions(&V1_SYMBOLIC_SCHEMA).expect("symbolic schema"), symbolic_schema_to_assertions(&V1_SYMBOLIC_SCHEMA).expect("symbolic schema"),
idents_to_assertions(&V1_IDENTS[..]), idents_to_assertions(&V1_IDENTS[..]),
@ -299,6 +299,6 @@ pub(crate) fn bootstrap_entities() -> Vec<Entity> {
// Failure here is a coding error (since the inputs are fixed), not a runtime error. // Failure here is a coding error (since the inputs are fixed), not a runtime error.
// TODO: represent these bootstrap data errors rather than just panicing. // TODO: represent these bootstrap data errors rather than just panicing.
let bootstrap_entities: Vec<Entity> = edn::parse::entities(&bootstrap_assertions.to_string()).expect("bootstrap assertions"); let bootstrap_entities: Vec<Entity<edn::ValueAndSpan>> = edn::parse::entities(&bootstrap_assertions.to_string()).expect("bootstrap assertions");
return bootstrap_entities; return bootstrap_entities;
} }

View file

@ -2084,12 +2084,12 @@ mod tests {
// We cannot resolve lookup refs that aren't :db/unique. // We cannot resolve lookup refs that aren't :db/unique.
assert_transact!(conn, assert_transact!(conn,
"[[:db/add (lookup-ref :test/not_unique :test/keyword) :test/not_unique :test/keyword]]", "[[:db/add (lookup-ref :test/not_unique :test/keyword) :test/not_unique :test/keyword]]",
Err("not yet implemented: Cannot resolve (lookup-ref 333 :test/keyword) with attribute that is not :db/unique")); Err("not yet implemented: Cannot resolve (lookup-ref 333 Keyword(Keyword(NamespaceableName { namespace: Some(\"test\"), name: \"keyword\" }))) with attribute that is not :db/unique"));
// We type check the lookup ref's value against the lookup ref's attribute. // We type check the lookup ref's value against the lookup ref's attribute.
assert_transact!(conn, assert_transact!(conn,
"[[:db/add (lookup-ref :test/unique_value :test/not_a_string) :test/not_unique :test/keyword]]", "[[:db/add (lookup-ref :test/unique_value :test/not_a_string) :test/not_unique :test/keyword]]",
Err("EDN value \':test/not_a_string\' is not the expected Mentat value type String")); Err("value \':test/not_a_string\' is not the expected Mentat value type String"));
// Each lookup ref in the entity column must resolve // Each lookup ref in the entity column must resolve
assert_transact!(conn, assert_transact!(conn,
@ -2239,6 +2239,16 @@ mod tests {
assert_matches!(tempids(&report), assert_matches!(tempids(&report),
"{\"t\" 65537}"); "{\"t\" 65537}");
// Check that we can explode map notation with :db/id as a lookup-ref or tx-function.
let report = assert_transact!(conn, "[{:db/id (lookup-ref :db/ident :db/ident) :test/many 4}
{:db/id (transaction-tx) :test/many 5}]");
assert_matches!(conn.last_transaction(),
"[[1 :test/many 4 ?tx true]
[?tx :db/txInstant ?ms ?tx true]
[?tx :test/many 5 ?tx true]]");
assert_matches!(tempids(&report),
"{}");
// Check that we can explode map notation with nested vector values. // Check that we can explode map notation with nested vector values.
let report = assert_transact!(conn, "[{:test/many [1 2]}]"); let report = assert_transact!(conn, "[{:test/many [1 2]}]");
assert_matches!(conn.last_transaction(), assert_matches!(conn.last_transaction(),
@ -2434,7 +2444,7 @@ mod tests {
// And here, a float. // And here, a float.
assert_transact!(conn, assert_transact!(conn,
"[{:test/_dangling 1.23}]", "[{:test/_dangling 1.23}]",
Err("EDN value \'1.23\' is not the expected Mentat value type Ref")); Err("value \'1.23\' is not the expected Mentat value type Ref"));
} }
#[test] #[test]

View file

@ -15,7 +15,6 @@ use std::collections::{
BTreeSet, BTreeSet,
}; };
use edn;
use rusqlite; use rusqlite;
use mentat_tx::entities::{ use mentat_tx::entities::{
@ -143,10 +142,10 @@ error_chain! {
display("not yet implemented: {}", t) display("not yet implemented: {}", t)
} }
/// We've been given an EDN value that isn't the correct Mentat type. /// We've been given a value that isn't the correct Mentat type.
BadEDNValuePair(value: edn::types::Value, value_type: ValueType) { BadValuePair(value: String, value_type: ValueType) {
description("EDN value is not the expected Mentat value type") description("value is not the expected Mentat value type")
display("EDN value '{}' is not the expected Mentat value type {:?}", value, value_type) display("value '{}' is not the expected Mentat value type {:?}", value, value_type)
} }
/// We've got corrupt data in the SQL store: a value and value_type_tag don't line up. /// We've got corrupt data in the SQL store: a value and value_type_tag don't line up.

View file

@ -43,64 +43,50 @@ use types::{
AVPair, AVPair,
Entid, Entid,
Schema, Schema,
TransactableValue,
TypedValue, TypedValue,
ValueType, ValueType,
}; };
use mentat_tx::entities; use mentat_tx::entities;
use mentat_tx::entities::{ use mentat_tx::entities::{
EntidOrLookupRefOrTempId, EntityPlace,
OpType, OpType,
TempId, TempId,
TxFunction, TxFunction,
}; };
/// The transactor is tied to `edn::ValueAndSpan` right now, but in the future we'd like to support
/// `TypedValue` directly for programmatic use. `TransactableValue` encapsulates the interface
/// value types (i.e., values in the value place) need to support to be transacted.
pub trait TransactableValue {
/// Coerce this value place into the given type. This is where we perform schema-aware
/// coercion, for example coercing an integral value into a ref where appropriate.
fn into_typed_value(self, schema: &Schema, value_type: ValueType) -> Result<TypedValue>;
/// Make an entity place out of this value place. This is where we limit values in nested maps
/// to valid entity places.
fn into_entity_place(self) -> Result<EntidOrLookupRefOrTempId>;
fn as_tempid(&self) -> Option<TempId>;
}
impl TransactableValue for ValueAndSpan { impl TransactableValue for ValueAndSpan {
fn into_typed_value(self, schema: &Schema, value_type: ValueType) -> Result<TypedValue> { fn into_typed_value(self, schema: &Schema, value_type: ValueType) -> Result<TypedValue> {
schema.to_typed_value(&self.without_spans(), value_type) schema.to_typed_value(&self, value_type)
} }
fn into_entity_place(self) -> Result<EntidOrLookupRefOrTempId> { fn into_entity_place(self) -> Result<EntityPlace<Self>> {
use self::SpannedValue::*; use self::SpannedValue::*;
match self.inner { match self.inner {
Integer(v) => Ok(EntidOrLookupRefOrTempId::Entid(entities::Entid::Entid(v))), Integer(v) => Ok(EntityPlace::Entid(entities::Entid::Entid(v))),
Keyword(v) => { Keyword(v) => {
if v.is_namespaced() { if v.is_namespaced() {
Ok(EntidOrLookupRefOrTempId::Entid(entities::Entid::Ident(v))) Ok(EntityPlace::Entid(entities::Entid::Ident(v)))
} else { } else {
// We only allow namespaced idents. // We only allow namespaced idents.
bail!(ErrorKind::InputError(errors::InputError::BadEntityPlace)) bail!(ErrorKind::InputError(errors::InputError::BadEntityPlace))
} }
}, },
Text(v) => Ok(EntidOrLookupRefOrTempId::TempId(TempId::External(v))), Text(v) => Ok(EntityPlace::TempId(TempId::External(v))),
List(ls) => { List(ls) => {
let mut it = ls.iter(); let mut it = ls.iter();
match (it.next().map(|x| &x.inner), it.next(), it.next(), it.next()) { match (it.next().map(|x| &x.inner), it.next(), it.next(), it.next()) {
// Like "(transaction-id)". // Like "(transaction-id)".
(Some(&PlainSymbol(ref op)), None, None, None) => { (Some(&PlainSymbol(ref op)), None, None, None) => {
Ok(EntidOrLookupRefOrTempId::TxFunction(TxFunction { op: op.clone() })) Ok(EntityPlace::TxFunction(TxFunction { op: op.clone() }))
}, },
// Like "(lookup-ref)". // Like "(lookup-ref)".
(Some(&PlainSymbol(edn::PlainSymbol(ref s))), Some(a), Some(v), None) if s == "lookup-ref" => { (Some(&PlainSymbol(edn::PlainSymbol(ref s))), Some(a), Some(v), None) if s == "lookup-ref" => {
match a.clone().into_entity_place()? { match a.clone().into_entity_place()? {
EntidOrLookupRefOrTempId::Entid(a) => Ok(EntidOrLookupRefOrTempId::LookupRef(entities::LookupRef { a, v: v.clone().without_spans() })), EntityPlace::Entid(a) => Ok(EntityPlace::LookupRef(entities::LookupRef { a: entities::AttributePlace::Entid(a), v: v.clone() })),
EntidOrLookupRefOrTempId::TempId(_) | EntityPlace::TempId(_) |
EntidOrLookupRefOrTempId::TxFunction(_) | EntityPlace::TxFunction(_) |
EntidOrLookupRefOrTempId::LookupRef(_) => bail!(ErrorKind::InputError(errors::InputError::BadEntityPlace)), EntityPlace::LookupRef(_) => bail!(ErrorKind::InputError(errors::InputError::BadEntityPlace)),
} }
}, },
_ => bail!(ErrorKind::InputError(errors::InputError::BadEntityPlace)), _ => bail!(ErrorKind::InputError(errors::InputError::BadEntityPlace)),
@ -125,6 +111,35 @@ impl TransactableValue for ValueAndSpan {
} }
} }
impl TransactableValue for TypedValue {
fn into_typed_value(self, _schema: &Schema, value_type: ValueType) -> Result<TypedValue> {
if self.value_type() != value_type {
bail!(ErrorKind::BadValuePair(format!("{:?}", self), value_type));
}
Ok(self)
}
fn into_entity_place(self) -> Result<EntityPlace<Self>> {
match self {
TypedValue::Ref(x) => Ok(EntityPlace::Entid(entities::Entid::Entid(x))),
TypedValue::Keyword(x) => Ok(EntityPlace::Entid(entities::Entid::Ident((*x).clone()))),
TypedValue::String(x) => Ok(EntityPlace::TempId(TempId::External((*x).clone()))),
TypedValue::Boolean(_) |
TypedValue::Long(_) |
TypedValue::Double(_) |
TypedValue::Instant(_) |
TypedValue::Uuid(_) => bail!(ErrorKind::InputError(errors::InputError::BadEntityPlace)),
}
}
fn as_tempid(&self) -> Option<TempId> {
match self {
&TypedValue::String(ref s) => Some(TempId::External((**s).clone())),
_ => None,
}
}
}
#[derive(Clone, Debug, Eq, Hash, Ord, PartialOrd, PartialEq)] #[derive(Clone, Debug, Eq, Hash, Ord, PartialOrd, PartialEq)]
pub enum Term<E, V> { pub enum Term<E, V> {
AddOrRetract(OpType, E, Entid, V), AddOrRetract(OpType, E, Entid, V),

View file

@ -97,6 +97,7 @@ pub use types::{
AttributeSet, AttributeSet,
DB, DB,
PartitionMap, PartitionMap,
TransactableValue,
TxReport, TxReport,
}; };

View file

@ -295,17 +295,17 @@ pub trait SchemaTypeChecking {
/// ///
/// Either assert that the given value is in the value type's value set, or (in limited cases) /// Either assert that the given value is in the value type's value set, or (in limited cases)
/// coerce the given value into the value type's value set. /// coerce the given value into the value type's value set.
fn to_typed_value(&self, value: &edn::Value, value_type: ValueType) -> Result<TypedValue>; fn to_typed_value(&self, value: &edn::ValueAndSpan, value_type: ValueType) -> Result<TypedValue>;
} }
impl SchemaTypeChecking for Schema { impl SchemaTypeChecking for Schema {
fn to_typed_value(&self, value: &edn::Value, value_type: ValueType) -> Result<TypedValue> { fn to_typed_value(&self, value: &edn::ValueAndSpan, value_type: ValueType) -> Result<TypedValue> {
// TODO: encapsulate entid-ident-attribute for better error messages, perhaps by including // TODO: encapsulate entid-ident-attribute for better error messages, perhaps by including
// the attribute (rather than just the attribute's value type) into this function or a // the attribute (rather than just the attribute's value type) into this function or a
// wrapper function. // wrapper function.
match TypedValue::from_edn_value(value) { match TypedValue::from_edn_value(&value.clone().without_spans()) {
// We don't recognize this EDN at all. Get out! // We don't recognize this EDN at all. Get out!
None => bail!(ErrorKind::BadEDNValuePair(value.clone(), value_type)), None => bail!(ErrorKind::BadValuePair(format!("{}", value), value_type)),
Some(typed_value) => match (value_type, typed_value) { Some(typed_value) => match (value_type, typed_value) {
// Most types don't coerce at all. // Most types don't coerce at all.
(ValueType::Boolean, tv @ TypedValue::Boolean(_)) => Ok(tv), (ValueType::Boolean, tv @ TypedValue::Boolean(_)) => Ok(tv),
@ -331,7 +331,7 @@ impl SchemaTypeChecking for Schema {
(vt @ ValueType::Instant, _) | (vt @ ValueType::Instant, _) |
(vt @ ValueType::Keyword, _) | (vt @ ValueType::Keyword, _) |
(vt @ ValueType::Ref, _) (vt @ ValueType::Ref, _)
=> bail!(ErrorKind::BadEDNValuePair(value.clone(), vt)), => bail!(ErrorKind::BadValuePair(format!("{}", value), vt)),
} }
} }
} }

View file

@ -86,7 +86,6 @@ use internal_types::{
TermWithTempIds, TermWithTempIds,
TermWithTempIdsAndLookupRefs, TermWithTempIdsAndLookupRefs,
TermWithoutTempIds, TermWithoutTempIds,
TransactableValue,
TypedValueOr, TypedValueOr,
replace_lookup_ref, replace_lookup_ref,
}; };
@ -106,6 +105,7 @@ use mentat_core::intern_set::InternSet;
use mentat_tx::entities as entmod; use mentat_tx::entities as entmod;
use mentat_tx::entities::{ use mentat_tx::entities::{
AttributePlace,
Entity, Entity,
OpType, OpType,
TempId, TempId,
@ -114,17 +114,17 @@ use metadata;
use rusqlite; use rusqlite;
use schema::{ use schema::{
SchemaBuilding, SchemaBuilding,
SchemaTypeChecking,
}; };
use tx_checking; use tx_checking;
use types::{ use types::{
Attribute,
AVPair,
AVMap, AVMap,
AVPair,
Attribute,
Entid, Entid,
PartitionMap, PartitionMap,
TypedValue, TransactableValue,
TxReport, TxReport,
TypedValue,
ValueType, ValueType,
}; };
use upsert_resolution::{ use upsert_resolution::{
@ -166,17 +166,19 @@ pub struct Tx<'conn, 'a, W> where W: TransactWatcher {
/// Remove any :db/id value from the given map notation, converting the returned value into /// Remove any :db/id value from the given map notation, converting the returned value into
/// something suitable for the entity position rather than something suitable for a value position. /// something suitable for the entity position rather than something suitable for a value position.
pub fn remove_db_id(map: &mut entmod::MapNotation) -> Result<Option<entmod::EntidOrLookupRefOrTempId>> { pub fn remove_db_id<V: TransactableValue>(map: &mut entmod::MapNotation<V>) -> Result<Option<entmod::EntityPlace<V>>> {
// TODO: extract lazy defined constant. // TODO: extract lazy defined constant.
let db_id_key = entmod::Entid::Ident(Keyword::namespaced("db", "id")); let db_id_key = entmod::Entid::Ident(Keyword::namespaced("db", "id"));
let db_id: Option<entmod::EntidOrLookupRefOrTempId> = if let Some(id) = map.remove(&db_id_key) { let db_id: Option<entmod::EntityPlace<V>> = if let Some(id) = map.remove(&db_id_key) {
match id { match id {
entmod::AtomOrLookupRefOrVectorOrMapNotation::Atom(v) => Some(v.into_entity_place()?), entmod::ValuePlace::Entid(e) => Some(entmod::EntityPlace::Entid(e)),
entmod::AtomOrLookupRefOrVectorOrMapNotation::LookupRef(_) | entmod::ValuePlace::LookupRef(e) => Some(entmod::EntityPlace::LookupRef(e)),
entmod::AtomOrLookupRefOrVectorOrMapNotation::TxFunction(_) | entmod::ValuePlace::TempId(e) => Some(entmod::EntityPlace::TempId(e)),
entmod::AtomOrLookupRefOrVectorOrMapNotation::Vector(_) | entmod::ValuePlace::TxFunction(e) => Some(entmod::EntityPlace::TxFunction(e)),
entmod::AtomOrLookupRefOrVectorOrMapNotation::MapNotation(_) => { entmod::ValuePlace::Atom(v) => Some(v.into_entity_place()?),
entmod::ValuePlace::Vector(_) |
entmod::ValuePlace::MapNotation(_) => {
bail!(ErrorKind::InputError(errors::InputError::BadDbId)) bail!(ErrorKind::InputError(errors::InputError::BadDbId))
}, },
} }
@ -253,7 +255,7 @@ impl<'conn, 'a, W> Tx<'conn, 'a, W> where W: TransactWatcher {
/// ///
/// The `Term` instances produce share interned TempId and LookupRef handles, and we return the /// The `Term` instances produce share interned TempId and LookupRef handles, and we return the
/// interned handle sets so that consumers can ensure all handles are used appropriately. /// interned handle sets so that consumers can ensure all handles are used appropriately.
fn entities_into_terms_with_temp_ids_and_lookup_refs<I>(&self, entities: I) -> Result<(Vec<TermWithTempIdsAndLookupRefs>, InternSet<TempId>, InternSet<AVPair>)> where I: IntoIterator<Item=Entity> { fn entities_into_terms_with_temp_ids_and_lookup_refs<I, V: TransactableValue>(&self, entities: I) -> Result<(Vec<TermWithTempIdsAndLookupRefs>, InternSet<TempId>, InternSet<AVPair>)> where I: IntoIterator<Item=Entity<V>> {
struct InProcess<'a> { struct InProcess<'a> {
partition_map: &'a PartitionMap, partition_map: &'a PartitionMap,
schema: &'a Schema, schema: &'a Schema,
@ -287,18 +289,18 @@ impl<'conn, 'a, W> Tx<'conn, 'a, W> where W: TransactWatcher {
self.schema.require_entid(e) self.schema.require_entid(e)
} }
fn intern_lookup_ref(&mut self, lookup_ref: &entmod::LookupRef) -> Result<LookupRef> { fn intern_lookup_ref<W: TransactableValue>(&mut self, lookup_ref: &entmod::LookupRef<W>) -> Result<LookupRef> {
let lr_a: i64 = match lookup_ref.a { let lr_a: i64 = match lookup_ref.a {
entmod::Entid::Entid(ref a) => *a, AttributePlace::Entid(entmod::Entid::Entid(ref a)) => *a,
entmod::Entid::Ident(ref a) => self.schema.require_entid(&a)?.into(), AttributePlace::Entid(entmod::Entid::Ident(ref a)) => self.schema.require_entid(&a)?.into(),
}; };
let lr_attribute: &Attribute = self.schema.require_attribute_for_entid(lr_a)?; let lr_attribute: &Attribute = self.schema.require_attribute_for_entid(lr_a)?;
let lr_typed_value: TypedValue = lookup_ref.v.clone().into_typed_value(&self.schema, lr_attribute.value_type)?;
if lr_attribute.unique.is_none() { if lr_attribute.unique.is_none() {
bail!(ErrorKind::NotYetImplemented(format!("Cannot resolve (lookup-ref {} {}) with attribute that is not :db/unique", lr_a, lookup_ref.v))) bail!(ErrorKind::NotYetImplemented(format!("Cannot resolve (lookup-ref {} {:?}) with attribute that is not :db/unique", lr_a, lr_typed_value)))
} }
let lr_typed_value: TypedValue = self.schema.to_typed_value(&lookup_ref.v, lr_attribute.value_type)?;
Ok(self.lookup_refs.intern((lr_a, lr_typed_value))) Ok(self.lookup_refs.intern((lr_a, lr_typed_value)))
} }
@ -308,14 +310,14 @@ impl<'conn, 'a, W> Tx<'conn, 'a, W> where W: TransactWatcher {
/// Allocate private internal tempids reserved for Mentat. Internal tempids just need to be /// Allocate private internal tempids reserved for Mentat. Internal tempids just need to be
/// unique within one transaction; they should never escape a transaction. /// unique within one transaction; they should never escape a transaction.
fn allocate_mentat_id(&mut self) -> entmod::EntidOrLookupRefOrTempId { fn allocate_mentat_id<W: TransactableValue>(&mut self) -> entmod::EntityPlace<W> {
self.mentat_id_count += 1; self.mentat_id_count += 1;
entmod::EntidOrLookupRefOrTempId::TempId(TempId::Internal(self.mentat_id_count)) entmod::EntityPlace::TempId(TempId::Internal(self.mentat_id_count))
} }
fn entity_e_into_term_e(&mut self, x: entmod::EntidOrLookupRefOrTempId) -> Result<KnownEntidOr<LookupRefOrTempId>> { fn entity_e_into_term_e<W: TransactableValue>(&mut self, x: entmod::EntityPlace<W>) -> Result<KnownEntidOr<LookupRefOrTempId>> {
match x { match x {
entmod::EntidOrLookupRefOrTempId::Entid(e) => { entmod::EntityPlace::Entid(e) => {
let e = match e { let e = match e {
entmod::Entid::Entid(ref e) => self.ensure_entid_exists(*e)?, entmod::Entid::Entid(ref e) => self.ensure_entid_exists(*e)?,
entmod::Entid::Ident(ref e) => self.ensure_ident_exists(&e)?, entmod::Entid::Ident(ref e) => self.ensure_ident_exists(&e)?,
@ -323,15 +325,15 @@ impl<'conn, 'a, W> Tx<'conn, 'a, W> where W: TransactWatcher {
Ok(Either::Left(e)) Ok(Either::Left(e))
}, },
entmod::EntidOrLookupRefOrTempId::TempId(e) => { entmod::EntityPlace::TempId(e) => {
Ok(Either::Right(LookupRefOrTempId::TempId(self.intern_temp_id(e)))) Ok(Either::Right(LookupRefOrTempId::TempId(self.intern_temp_id(e))))
}, },
entmod::EntidOrLookupRefOrTempId::LookupRef(ref lookup_ref) => { entmod::EntityPlace::LookupRef(ref lookup_ref) => {
Ok(Either::Right(LookupRefOrTempId::LookupRef(self.intern_lookup_ref(lookup_ref)?))) Ok(Either::Right(LookupRefOrTempId::LookupRef(self.intern_lookup_ref(lookup_ref)?)))
}, },
entmod::EntidOrLookupRefOrTempId::TxFunction(ref tx_function) => { entmod::EntityPlace::TxFunction(ref tx_function) => {
match tx_function.op.0.as_str() { match tx_function.op.0.as_str() {
"transaction-tx" => Ok(Either::Left(self.tx_id)), "transaction-tx" => Ok(Either::Left(self.tx_id)),
unknown @ _ => bail!(ErrorKind::NotYetImplemented(format!("Unknown transaction function {}", unknown))), unknown @ _ => bail!(ErrorKind::NotYetImplemented(format!("Unknown transaction function {}", unknown))),
@ -348,11 +350,11 @@ impl<'conn, 'a, W> Tx<'conn, 'a, W> where W: TransactWatcher {
Ok(a) Ok(a)
} }
fn entity_e_into_term_v(&mut self, x: entmod::EntidOrLookupRefOrTempId) -> Result<TypedValueOr<LookupRefOrTempId>> { fn entity_e_into_term_v<W: TransactableValue>(&mut self, x: entmod::EntityPlace<W>) -> Result<TypedValueOr<LookupRefOrTempId>> {
self.entity_e_into_term_e(x).map(|r| r.map_left(|ke| TypedValue::Ref(ke.0))) self.entity_e_into_term_e(x).map(|r| r.map_left(|ke| TypedValue::Ref(ke.0)))
} }
fn entity_v_into_term_e(&mut self, x: entmod::AtomOrLookupRefOrVectorOrMapNotation, backward_a: &entmod::Entid) -> Result<KnownEntidOr<LookupRefOrTempId>> { fn entity_v_into_term_e<W: TransactableValue>(&mut self, x: entmod::ValuePlace<W>, backward_a: &entmod::Entid) -> Result<KnownEntidOr<LookupRefOrTempId>> {
match backward_a.unreversed() { match backward_a.unreversed() {
None => { None => {
bail!(ErrorKind::NotYetImplemented(format!("Cannot explode map notation value in :attr/_reversed notation for forward attribute"))); bail!(ErrorKind::NotYetImplemented(format!("Cannot explode map notation value in :attr/_reversed notation for forward attribute")));
@ -365,7 +367,7 @@ impl<'conn, 'a, W> Tx<'conn, 'a, W> where W: TransactWatcher {
} }
match x { match x {
entmod::AtomOrLookupRefOrVectorOrMapNotation::Atom(v) => { entmod::ValuePlace::Atom(v) => {
// Here is where we do schema-aware typechecking: we either assert // Here is where we do schema-aware typechecking: we either assert
// that the given value is in the attribute's value set, or (in // that the given value is in the attribute's value set, or (in
// limited cases) coerce the value into the attribute's value set. // limited cases) coerce the value into the attribute's value set.
@ -382,20 +384,26 @@ impl<'conn, 'a, W> Tx<'conn, 'a, W> where W: TransactWatcher {
} }
}, },
entmod::AtomOrLookupRefOrVectorOrMapNotation::LookupRef(ref lookup_ref) => entmod::ValuePlace::Entid(entid) =>
Ok(Either::Left(KnownEntid(self.entity_a_into_term_a(entid)?))),
entmod::ValuePlace::TempId(tempid) =>
Ok(Either::Right(LookupRefOrTempId::TempId(self.intern_temp_id(tempid)))),
entmod::ValuePlace::LookupRef(ref lookup_ref) =>
Ok(Either::Right(LookupRefOrTempId::LookupRef(self.intern_lookup_ref(lookup_ref)?))), Ok(Either::Right(LookupRefOrTempId::LookupRef(self.intern_lookup_ref(lookup_ref)?))),
entmod::AtomOrLookupRefOrVectorOrMapNotation::TxFunction(ref tx_function) => { entmod::ValuePlace::TxFunction(ref tx_function) => {
match tx_function.op.0.as_str() { match tx_function.op.0.as_str() {
"transaction-tx" => Ok(Either::Left(KnownEntid(self.tx_id.0))), "transaction-tx" => Ok(Either::Left(KnownEntid(self.tx_id.0))),
unknown @ _ => bail!(ErrorKind::NotYetImplemented(format!("Unknown transaction function {}", unknown))), unknown @ _ => bail!(ErrorKind::NotYetImplemented(format!("Unknown transaction function {}", unknown))),
} }
}, },
entmod::AtomOrLookupRefOrVectorOrMapNotation::Vector(_) => entmod::ValuePlace::Vector(_) =>
bail!(ErrorKind::NotYetImplemented(format!("Cannot explode vector value in :attr/_reversed notation for attribute {}", forward_a))), bail!(ErrorKind::NotYetImplemented(format!("Cannot explode vector value in :attr/_reversed notation for attribute {}", forward_a))),
entmod::AtomOrLookupRefOrVectorOrMapNotation::MapNotation(_) => entmod::ValuePlace::MapNotation(_) =>
bail!(ErrorKind::NotYetImplemented(format!("Cannot explode map notation value in :attr/_reversed notation for attribute {}", forward_a))), bail!(ErrorKind::NotYetImplemented(format!("Cannot explode map notation value in :attr/_reversed notation for attribute {}", forward_a))),
} }
}, },
@ -408,7 +416,7 @@ impl<'conn, 'a, W> Tx<'conn, 'a, W> where W: TransactWatcher {
// We want to handle entities in the order they're given to us, while also "exploding" some // We want to handle entities in the order they're given to us, while also "exploding" some
// entities into many. We therefore push the initial entities onto the back of the deque, // entities into many. We therefore push the initial entities onto the back of the deque,
// take from the front of the deque, and explode onto the front as well. // take from the front of the deque, and explode onto the front as well.
let mut deque: VecDeque<Entity> = VecDeque::default(); let mut deque: VecDeque<Entity<V>> = VecDeque::default();
deque.extend(entities); deque.extend(entities);
let mut terms: Vec<TermWithTempIdsAndLookupRefs> = Vec::with_capacity(deque.len()); let mut terms: Vec<TermWithTempIdsAndLookupRefs> = Vec::with_capacity(deque.len());
@ -418,7 +426,7 @@ impl<'conn, 'a, W> Tx<'conn, 'a, W> where W: TransactWatcher {
Entity::MapNotation(mut map_notation) => { Entity::MapNotation(mut map_notation) => {
// :db/id is optional; if it's not given, we generate a special internal tempid // :db/id is optional; if it's not given, we generate a special internal tempid
// to use for upserting. This tempid will not be reported in the TxReport. // to use for upserting. This tempid will not be reported in the TxReport.
let db_id: entmod::EntidOrLookupRefOrTempId = remove_db_id(&mut map_notation)?.unwrap_or_else(|| in_process.allocate_mentat_id()); let db_id: entmod::EntityPlace<V> = remove_db_id(&mut map_notation)?.unwrap_or_else(|| in_process.allocate_mentat_id());
// We're not nested, so :db/isComponent is not relevant. We just explode the // We're not nested, so :db/isComponent is not relevant. We just explode the
// map notation. // map notation.
@ -426,13 +434,15 @@ impl<'conn, 'a, W> Tx<'conn, 'a, W> where W: TransactWatcher {
deque.push_front(Entity::AddOrRetract { deque.push_front(Entity::AddOrRetract {
op: OpType::Add, op: OpType::Add,
e: db_id.clone(), e: db_id.clone(),
a: a, a: AttributePlace::Entid(a),
v: v, v: v,
}); });
} }
}, },
Entity::AddOrRetract { op, e, a, v } => { Entity::AddOrRetract { op, e, a, v } => {
let AttributePlace::Entid(a) = a;
if let Some(reversed_a) = a.unreversed() { 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 = in_process.entity_a_into_term_a(reversed_a)?; let reversed_a = in_process.entity_a_into_term_a(reversed_a)?;
@ -443,7 +453,7 @@ impl<'conn, 'a, W> Tx<'conn, 'a, W> where W: TransactWatcher {
let attribute = self.schema.require_attribute_for_entid(a)?; let attribute = self.schema.require_attribute_for_entid(a)?;
let v = match v { let v = match v {
entmod::AtomOrLookupRefOrVectorOrMapNotation::Atom(v) => { entmod::ValuePlace::Atom(v) => {
// Here is where we do schema-aware typechecking: we either assert // Here is where we do schema-aware typechecking: we either assert
// that the given value is in the attribute's value set, or (in // that the given value is in the attribute's value set, or (in
// limited cases) coerce the value into the attribute's value set. // limited cases) coerce the value into the attribute's value set.
@ -457,7 +467,13 @@ impl<'conn, 'a, W> Tx<'conn, 'a, W> where W: TransactWatcher {
} }
}, },
entmod::AtomOrLookupRefOrVectorOrMapNotation::LookupRef(ref lookup_ref) => { entmod::ValuePlace::Entid(entid) =>
Either::Left(TypedValue::Ref(in_process.entity_a_into_term_a(entid)?)),
entmod::ValuePlace::TempId(tempid) =>
Either::Right(LookupRefOrTempId::TempId(in_process.intern_temp_id(tempid))),
entmod::ValuePlace::LookupRef(ref lookup_ref) => {
if attribute.value_type != ValueType::Ref { if attribute.value_type != ValueType::Ref {
bail!(ErrorKind::NotYetImplemented(format!("Cannot resolve value lookup ref for attribute {} that is not :db/valueType :db.type/ref", a))) bail!(ErrorKind::NotYetImplemented(format!("Cannot resolve value lookup ref for attribute {} that is not :db/valueType :db.type/ref", a)))
} }
@ -465,7 +481,7 @@ impl<'conn, 'a, W> Tx<'conn, 'a, W> where W: TransactWatcher {
Either::Right(LookupRefOrTempId::LookupRef(in_process.intern_lookup_ref(lookup_ref)?)) Either::Right(LookupRefOrTempId::LookupRef(in_process.intern_lookup_ref(lookup_ref)?))
}, },
entmod::AtomOrLookupRefOrVectorOrMapNotation::TxFunction(ref tx_function) => { entmod::ValuePlace::TxFunction(ref tx_function) => {
let typed_value = match tx_function.op.0.as_str() { let typed_value = match tx_function.op.0.as_str() {
"transaction-tx" => TypedValue::Ref(self.tx_id), "transaction-tx" => TypedValue::Ref(self.tx_id),
unknown @ _ => bail!(ErrorKind::NotYetImplemented(format!("Unknown transaction function {}", unknown))), unknown @ _ => bail!(ErrorKind::NotYetImplemented(format!("Unknown transaction function {}", unknown))),
@ -485,7 +501,7 @@ impl<'conn, 'a, W> Tx<'conn, 'a, W> where W: TransactWatcher {
Either::Left(typed_value) Either::Left(typed_value)
}, },
entmod::AtomOrLookupRefOrVectorOrMapNotation::Vector(vs) => { entmod::ValuePlace::Vector(vs) => {
if !attribute.multival { if !attribute.multival {
bail!(ErrorKind::NotYetImplemented(format!("Cannot explode vector value for attribute {} that is not :db.cardinality :db.cardinality/many", a))); bail!(ErrorKind::NotYetImplemented(format!("Cannot explode vector value for attribute {} that is not :db.cardinality :db.cardinality/many", a)));
} }
@ -494,14 +510,14 @@ impl<'conn, 'a, W> Tx<'conn, 'a, W> where W: TransactWatcher {
deque.push_front(Entity::AddOrRetract { deque.push_front(Entity::AddOrRetract {
op: op.clone(), op: op.clone(),
e: e.clone(), e: e.clone(),
a: entmod::Entid::Entid(a), a: AttributePlace::Entid(entmod::Entid::Entid(a)),
v: vv, v: vv,
}); });
} }
continue continue
}, },
entmod::AtomOrLookupRefOrVectorOrMapNotation::MapNotation(mut map_notation) => { entmod::ValuePlace::MapNotation(mut map_notation) => {
// TODO: consider handling this at the tx-parser level. That would be // TODO: consider handling this at the tx-parser level. That would be
// more strict and expressive, but it would lead to splitting // more strict and expressive, but it would lead to splitting
// AddOrRetract, which proliferates types and code, or only handling // AddOrRetract, which proliferates types and code, or only handling
@ -516,9 +532,9 @@ impl<'conn, 'a, W> Tx<'conn, 'a, W> where W: TransactWatcher {
// :db/id is optional; if it's not given, we generate a special internal tempid // :db/id is optional; if it's not given, we generate a special internal tempid
// to use for upserting. This tempid will not be reported in the TxReport. // to use for upserting. This tempid will not be reported in the TxReport.
let db_id: Option<entmod::EntidOrLookupRefOrTempId> = remove_db_id(&mut map_notation)?; let db_id: Option<entmod::EntityPlace<V>> = remove_db_id(&mut map_notation)?;
let mut dangling = db_id.is_none(); let mut dangling = db_id.is_none();
let db_id: entmod::EntidOrLookupRefOrTempId = db_id.unwrap_or_else(|| in_process.allocate_mentat_id()); let db_id: entmod::EntityPlace<V> = db_id.unwrap_or_else(|| in_process.allocate_mentat_id());
// We're nested, so we want to ensure we're not creating "dangling" // We're nested, so we want to ensure we're not creating "dangling"
// entities that can't be reached. If we're :db/isComponent, then this // entities that can't be reached. If we're :db/isComponent, then this
@ -557,7 +573,7 @@ impl<'conn, 'a, W> Tx<'conn, 'a, W> where W: TransactWatcher {
deque.push_front(Entity::AddOrRetract { deque.push_front(Entity::AddOrRetract {
op: OpType::Add, op: OpType::Add,
e: db_id.clone(), e: db_id.clone(),
a: entmod::Entid::Entid(inner_a), a: AttributePlace::Entid(entmod::Entid::Entid(inner_a)),
v: inner_v, v: inner_v,
}); });
} }
@ -600,8 +616,8 @@ impl<'conn, 'a, W> Tx<'conn, 'a, W> where W: TransactWatcher {
/// ///
/// This approach is explained in https://github.com/mozilla/mentat/wiki/Transacting. /// This approach is explained in https://github.com/mozilla/mentat/wiki/Transacting.
// TODO: move this to the transactor layer. // TODO: move this to the transactor layer.
pub fn transact_entities<I>(&mut self, entities: I) -> Result<TxReport> pub fn transact_entities<I, V: TransactableValue>(&mut self, entities: I) -> Result<TxReport>
where I: IntoIterator<Item=Entity> { where I: IntoIterator<Item=Entity<V>> {
// Pipeline stage 1: entities -> terms with tempids and lookup refs. // Pipeline stage 1: entities -> terms with tempids and lookup refs.
let (terms_with_temp_ids_and_lookup_refs, tempid_set, lookup_ref_set) = self.entities_into_terms_with_temp_ids_and_lookup_refs(entities)?; let (terms_with_temp_ids_and_lookup_refs, tempid_set, lookup_ref_set) = self.entities_into_terms_with_temp_ids_and_lookup_refs(entities)?;
@ -841,13 +857,14 @@ where W: TransactWatcher {
/// ///
/// This approach is explained in https://github.com/mozilla/mentat/wiki/Transacting. /// This approach is explained in https://github.com/mozilla/mentat/wiki/Transacting.
// TODO: move this to the transactor layer. // TODO: move this to the transactor layer.
pub fn transact<'conn, 'a, I, W>(conn: &'conn rusqlite::Connection, pub fn transact<'conn, 'a, I, V, W>(conn: &'conn rusqlite::Connection,
partition_map: PartitionMap, partition_map: PartitionMap,
schema_for_mutation: &'a Schema, schema_for_mutation: &'a Schema,
schema: &'a Schema, schema: &'a Schema,
watcher: W, watcher: W,
entities: I) -> Result<(TxReport, PartitionMap, Option<Schema>, W)> entities: I) -> Result<(TxReport, PartitionMap, Option<Schema>, W)>
where I: IntoIterator<Item=Entity>, where I: IntoIterator<Item=Entity<V>>,
V: TransactableValue,
W: TransactWatcher { W: TransactWatcher {
let mut tx = start_tx(conn, partition_map, schema_for_mutation, schema, watcher)?; let mut tx = start_tx(conn, partition_map, schema_for_mutation, schema, watcher)?;

View file

@ -29,6 +29,13 @@ pub use self::mentat_core::{
ValueType, ValueType,
}; };
use mentat_tx::entities::{
EntityPlace,
TempId,
};
use errors;
/// Represents one partition of the entid space. /// Represents one partition of the entid space.
#[derive(Clone,Debug,Eq,Hash,Ord,PartialOrd,PartialEq)] #[derive(Clone,Debug,Eq,Hash,Ord,PartialOrd,PartialEq)]
pub struct Partition { pub struct Partition {
@ -104,3 +111,18 @@ pub struct TxReport {
/// literal tempids to all unify to a single freshly allocated entid.) /// literal tempids to all unify to a single freshly allocated entid.)
pub tempids: BTreeMap<String, Entid>, pub tempids: BTreeMap<String, Entid>,
} }
/// The transactor is tied to `edn::ValueAndSpan` right now, but in the future we'd like to support
/// `TypedValue` directly for programmatic use. `TransactableValue` encapsulates the interface
/// value types (i.e., values in the value place) need to support to be transacted.
pub trait TransactableValue: Clone {
/// Coerce this value place into the given type. This is where we perform schema-aware
/// coercion, for example coercing an integral value into a ref where appropriate.
fn into_typed_value(self, schema: &Schema, value_type: ValueType) -> errors::Result<TypedValue>;
/// Make an entity place out of this value place. This is where we limit values in nested maps
/// to valid entity places.
fn into_entity_place(self) -> errors::Result<EntityPlace<Self>>;
fn as_tempid(&self) -> Option<TempId>;
}

View file

@ -223,35 +223,35 @@ forward_entid -> Entid
backward_entid -> Entid backward_entid -> Entid
= v:raw_backward_keyword { Entid::Ident(v.to_reversed()) } = v:raw_backward_keyword { Entid::Ident(v.to_reversed()) }
lookup_ref -> LookupRef lookup_ref -> LookupRef<ValueAndSpan>
= "(" __ "lookup-ref" __ a:(entid) __ v:(value) __ ")" { LookupRef { a, v: v.without_spans() } } = "(" __ "lookup-ref" __ a:(entid) __ v:(value) __ ")" { LookupRef { a: AttributePlace::Entid(a), v } }
tx_function -> TxFunction tx_function -> TxFunction
= "(" __ n:$(symbol_name) __ ")" { TxFunction { op: PlainSymbol::plain(n) } } = "(" __ n:$(symbol_name) __ ")" { TxFunction { op: PlainSymbol::plain(n) } }
entity_place -> EntidOrLookupRefOrTempId entity_place -> EntityPlace<ValueAndSpan>
= v:raw_text { EntidOrLookupRefOrTempId::TempId(TempId::External(v)) } = v:raw_text { EntityPlace::TempId(TempId::External(v)) }
/ v:entid { EntidOrLookupRefOrTempId::Entid(v) } / v:entid { EntityPlace::Entid(v) }
/ v:lookup_ref { EntidOrLookupRefOrTempId::LookupRef(v) } / v:lookup_ref { EntityPlace::LookupRef(v) }
/ v:tx_function { EntidOrLookupRefOrTempId::TxFunction(v) } / v:tx_function { EntityPlace::TxFunction(v) }
value_place_pair -> (Entid, AtomOrLookupRefOrVectorOrMapNotation) value_place_pair -> (Entid, ValuePlace<ValueAndSpan>)
= k:(entid) __ v:(value_place) { (k, v) } = k:(entid) __ v:(value_place) { (k, v) }
map_notation -> MapNotation map_notation -> MapNotation<ValueAndSpan>
= "{" __ kvs:(value_place_pair*) __ "}" { kvs.into_iter().collect() } = "{" __ kvs:(value_place_pair*) __ "}" { kvs.into_iter().collect() }
value_place -> AtomOrLookupRefOrVectorOrMapNotation value_place -> ValuePlace<ValueAndSpan>
= __ v:lookup_ref __ { AtomOrLookupRefOrVectorOrMapNotation::LookupRef(v) } = __ v:lookup_ref __ { ValuePlace::LookupRef(v) }
/ __ v:tx_function __ { AtomOrLookupRefOrVectorOrMapNotation::TxFunction(v) } / __ v:tx_function __ { ValuePlace::TxFunction(v) }
/ __ "[" __ vs:(value_place*) __ "]" __ { AtomOrLookupRefOrVectorOrMapNotation::Vector(vs) } / __ "[" __ vs:(value_place*) __ "]" __ { ValuePlace::Vector(vs) }
/ __ v:map_notation __ { AtomOrLookupRefOrVectorOrMapNotation::MapNotation(v) } / __ v:map_notation __ { ValuePlace::MapNotation(v) }
/ __ v:atom __ { AtomOrLookupRefOrVectorOrMapNotation::Atom(v) } / __ v:atom __ { ValuePlace::Atom(v) }
pub entity -> Entity pub entity -> Entity<ValueAndSpan>
= __ "[" __ op:(op) __ e:(entity_place) __ a:(forward_entid) __ v:(value_place) __ "]" __ { Entity::AddOrRetract { op, e: e, a, v: v } } = __ "[" __ op:(op) __ e:(entity_place) __ a:(forward_entid) __ v:(value_place) __ "]" __ { Entity::AddOrRetract { op, e: e, a: AttributePlace::Entid(a), v: v } }
/ __ "[" __ op:(op) __ e:(value_place) __ a:(backward_entid) __ v:(entity_place) __ "]" __ { Entity::AddOrRetract { op, e: v, a, v: e } } / __ "[" __ op:(op) __ e:(value_place) __ a:(backward_entid) __ v:(entity_place) __ "]" __ { Entity::AddOrRetract { op, e: v, a: AttributePlace::Entid(a), v: e } }
/ __ map:map_notation __ { Entity::MapNotation(map) } / __ map:map_notation __ { Entity::MapNotation(map) }
pub entities -> Vec<Entity> pub entities -> Vec<Entity<ValueAndSpan>>
= __ "[" __ es:(entity*) __ "]" __ { es } = __ "[" __ es:(entity*) __ "]" __ { es }

View file

@ -17,10 +17,6 @@ use symbols::{
Keyword, Keyword,
PlainSymbol, PlainSymbol,
}; };
use types::{
Value,
ValueAndSpan,
};
/// A tempid, either an external tempid given in a transaction (usually as an `Value::Text`), /// A tempid, either an external tempid given in a transaction (usually as an `Value::Text`),
/// or an internal tempid allocated by Mentat itself. /// or an internal tempid allocated by Mentat itself.
@ -64,11 +60,11 @@ impl Entid {
} }
#[derive(Clone, Debug, Eq, Hash, Ord, PartialOrd, PartialEq)] #[derive(Clone, Debug, Eq, Hash, Ord, PartialOrd, PartialEq)]
pub struct LookupRef { pub struct LookupRef<V> {
pub a: Entid, pub a: AttributePlace,
// In theory we could allow nested lookup-refs. In practice this would require us to process // In theory we could allow nested lookup-refs. In practice this would require us to process
// lookup-refs in multiple phases, like how we resolve tempids, which isn't worth the effort. // lookup-refs in multiple phases, like how we resolve tempids, which isn't worth the effort.
pub v: Value, // An atom. pub v: V, // An atom.
} }
/// A "transaction function" that exposes some value determined by the current transaction. The /// A "transaction function" that exposes some value determined by the current transaction. The
@ -88,23 +84,34 @@ pub struct TxFunction {
pub op: PlainSymbol, pub op: PlainSymbol,
} }
pub type MapNotation = BTreeMap<Entid, AtomOrLookupRefOrVectorOrMapNotation>; pub type MapNotation<V> = BTreeMap<Entid, ValuePlace<V>>;
#[derive(Clone, Debug, Eq, Hash, Ord, PartialOrd, PartialEq)] #[derive(Clone, Debug, Eq, Hash, Ord, PartialOrd, PartialEq)]
pub enum AtomOrLookupRefOrVectorOrMapNotation { pub enum ValuePlace<V> {
Atom(ValueAndSpan), // We never know at parse-time whether an integer or ident is really an entid, but we will often
LookupRef(LookupRef), // know when building entities programmatically.
Entid(Entid),
// We never know at parse-time whether a string is really a tempid, but we will often know when
// building entities programmatically.
TempId(TempId),
LookupRef(LookupRef<V>),
TxFunction(TxFunction), TxFunction(TxFunction),
Vector(Vec<AtomOrLookupRefOrVectorOrMapNotation>), Vector(Vec<ValuePlace<V>>),
MapNotation(MapNotation), Atom(V),
MapNotation(MapNotation<V>),
} }
#[derive(Clone, Debug, Eq, Hash, Ord, PartialOrd, PartialEq)] #[derive(Clone, Debug, Eq, Hash, Ord, PartialOrd, PartialEq)]
pub enum EntidOrLookupRefOrTempId { pub enum EntityPlace<V> {
Entid(Entid), Entid(Entid),
LookupRef(LookupRef),
TxFunction(TxFunction),
TempId(TempId), TempId(TempId),
LookupRef(LookupRef<V>),
TxFunction(TxFunction),
}
#[derive(Clone, Debug, Eq, Hash, Ord, PartialOrd, PartialEq)]
pub enum AttributePlace {
Entid(Entid),
} }
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialOrd, PartialEq)] #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialOrd, PartialEq)]
@ -114,14 +121,14 @@ pub enum OpType {
} }
#[derive(Clone, Debug, Eq, Hash, Ord, PartialOrd, PartialEq)] #[derive(Clone, Debug, Eq, Hash, Ord, PartialOrd, PartialEq)]
pub enum Entity { pub enum Entity<V> {
// Like [:db/add|:db/retract e a v]. // Like [:db/add|:db/retract e a v].
AddOrRetract { AddOrRetract {
op: OpType, op: OpType,
e: EntidOrLookupRefOrTempId, e: EntityPlace<V>,
a: Entid, a: AttributePlace,
v: AtomOrLookupRefOrVectorOrMapNotation, v: ValuePlace<V>,
}, },
// Like {:db/id "tempid" a1 v1 a2 v2}. // Like {:db/id "tempid" a1 v1 a2 v2}.
MapNotation(MapNotation), MapNotation(MapNotation<V>),
} }

View file

@ -69,6 +69,7 @@ use mentat_db::{
transact_terms, transact_terms,
InProgressObserverTransactWatcher, InProgressObserverTransactWatcher,
PartitionMap, PartitionMap,
TransactableValue,
TransactWatcher, TransactWatcher,
TxObservationService, TxObservationService,
TxObserver, TxObserver,
@ -82,9 +83,7 @@ use mentat_query_pull::{
pull_attributes_for_entity, pull_attributes_for_entity,
}; };
use mentat_tx; use edn::entities::{
use mentat_tx::entities::{
TempId, TempId,
OpType, OpType,
}; };
@ -469,7 +468,7 @@ impl<'a, 'c> InProgress<'a, 'c> {
Ok(report) Ok(report)
} }
pub fn transact_entities<I>(&mut self, entities: I) -> Result<TxReport> where I: IntoIterator<Item=mentat_tx::entities::Entity> { pub fn transact_entities<I, V: TransactableValue>(&mut self, entities: I) -> Result<TxReport> where I: IntoIterator<Item=edn::entities::Entity<V>> {
// We clone the partition map here, rather than trying to use a Cell or using a mutable // We clone the partition map here, rather than trying to use a Cell or using a mutable
// reference, for two reasons: // reference, for two reasons:
// 1. `transact` allocates new IDs in partitions before and while doing work that might // 1. `transact` allocates new IDs in partitions before and while doing work that might