From 4c4af46315511fff72620c64ecb68185aea1480d Mon Sep 17 00:00:00 2001 From: Nick Alexander Date: Mon, 7 May 2018 13:55:20 -0700 Subject: [PATCH 1/4] Add TransactableValue abstracting value places that can be transacted. This is a stepping stone to transacting entities that are not based on `edn::ValueAndSpan`. We need to turn some value places (general) into entity places (restricted), and those restrictions are captured in tx-parser right now. But for `TypedValue` value places, those restrictions are encoded in the type itself. This lays the track to accept other value types in value places, which is good for programmatic builder interfaces. --- db/src/internal_types.rs | 47 ++++++++++++++++++++++++++++- db/src/tx.rs | 64 ++++++++++++++++++++++++++++------------ tx-parser/src/lib.rs | 31 +------------------ 3 files changed, 92 insertions(+), 50 deletions(-) diff --git a/db/src/internal_types.rs b/db/src/internal_types.rs index 11104499..10b5bcdf 100644 --- a/db/src/internal_types.rs +++ b/db/src/internal_types.rs @@ -19,18 +19,63 @@ use mentat_core::KnownEntid; use mentat_core::util::Either; +use edn::{ + ValueAndSpan, +}; + use errors; -use errors::ErrorKind; +use errors::{ + ErrorKind, + Result, + ResultExt, +}; +use schema::{ + SchemaTypeChecking, +}; use types::{ AVMap, AVPair, Entid, + Schema, TypedValue, + ValueType, }; use mentat_tx::entities::{ + EntidOrLookupRefOrTempId, OpType, TempId, }; +use mentat_tx_parser; + +/// 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; + + /// 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; + + fn as_tempid(&self) -> Option; +} + +impl TransactableValue for ValueAndSpan { + fn into_typed_value(self, schema: &Schema, value_type: ValueType) -> Result { + schema.to_typed_value(&self.without_spans(), value_type) + } + + fn into_entity_place(self) -> Result { + mentat_tx_parser::Tx::parse_entid_or_lookup_ref_or_temp_id(self) + .chain_err(|| ErrorKind::NotYetImplemented("db id error".into())) + } + + fn as_tempid(&self) -> Option { + self.inner.as_text().cloned().map(TempId::External) + } +} #[derive(Clone, Debug, Eq, Hash, Ord, PartialOrd, PartialEq)] pub enum Term { diff --git a/db/src/tx.rs b/db/src/tx.rs index 6e03c400..15a001f4 100644 --- a/db/src/tx.rs +++ b/db/src/tx.rs @@ -82,6 +82,7 @@ use internal_types::{ TermWithTempIds, TermWithTempIdsAndLookupRefs, TermWithoutTempIds, + TransactableValue, TypedValueOr, replace_lookup_ref, }; @@ -105,7 +106,6 @@ use mentat_tx::entities::{ OpType, TempId, }; -use mentat_tx_parser; use metadata; use rusqlite; use schema::{ @@ -161,6 +161,29 @@ pub struct Tx<'conn, 'a, W> where W: TransactWatcher { tx_instant: Option>, } +/// 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. +pub fn remove_db_id(map: &mut entmod::MapNotation) -> Result> { + // TODO: extract lazy defined constant. + let db_id_key = entmod::Entid::Ident(NamespacedKeyword::new("db", "id")); + + let db_id: Option = if let Some(id) = map.remove(&db_id_key) { + match id { + entmod::AtomOrLookupRefOrVectorOrMapNotation::Atom(v) => Some(v.into_entity_place()?), + entmod::AtomOrLookupRefOrVectorOrMapNotation::LookupRef(_) | + entmod::AtomOrLookupRefOrVectorOrMapNotation::TxFunction(_) | + entmod::AtomOrLookupRefOrVectorOrMapNotation::Vector(_) | + entmod::AtomOrLookupRefOrVectorOrMapNotation::MapNotation(_) => { + bail!(ErrorKind::NotYetImplemented("db id error".into())) + }, + } + } else { + None + }; + + Ok(db_id) +} + impl<'conn, 'a, W> Tx<'conn, 'a, W> where W: TransactWatcher { pub fn new( store: &'conn rusqlite::Connection, @@ -340,18 +363,19 @@ impl<'conn, 'a, W> Tx<'conn, 'a, W> where W: TransactWatcher { } match x { - entmod::AtomOrLookupRefOrVectorOrMapNotation::Atom(ref v) => { + entmod::AtomOrLookupRefOrVectorOrMapNotation::Atom(v) => { // Here is where we do schema-aware typechecking: we either assert // that 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 Some(text) = v.inner.as_text() { - Ok(Either::Right(LookupRefOrTempId::TempId(self.intern_temp_id(TempId::External(text.clone()))))) - } else { - if let TypedValue::Ref(entid) = self.schema.to_typed_value(&v.clone().without_spans(), ValueType::Ref)? { - Ok(Either::Left(KnownEntid(entid))) - } else { - // The given value is expected to be :db.type/ref, so this shouldn't happen. - bail!(ErrorKind::NotYetImplemented(format!("Cannot use :attr/_reversed notation for attribute {} with value that is not :db.valueType :db.type/ref", forward_a))) + match v.as_tempid() { + Some(tempid) => Ok(Either::Right(LookupRefOrTempId::TempId(self.intern_temp_id(tempid)))), + None => { + if let TypedValue::Ref(entid) = v.into_typed_value(&self.schema, ValueType::Ref)? { + Ok(Either::Left(KnownEntid(entid))) + } else { + // The given value is expected to be :db.type/ref, so this shouldn't happen. + bail!(ErrorKind::NotYetImplemented(format!("Cannot use :attr/_reversed notation for attribute {} with value that is not :db.valueType :db.type/ref", forward_a))) + } } } }, @@ -392,7 +416,7 @@ impl<'conn, 'a, W> Tx<'conn, 'a, W> where W: TransactWatcher { Entity::MapNotation(mut map_notation) => { // :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. - let db_id: entmod::EntidOrLookupRefOrTempId = mentat_tx_parser::remove_db_id(&mut map_notation)?.unwrap_or_else(|| in_process.allocate_mentat_id()); + let db_id: entmod::EntidOrLookupRefOrTempId = 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 // map notation. @@ -418,14 +442,16 @@ impl<'conn, 'a, W> Tx<'conn, 'a, W> where W: TransactWatcher { let v = match v { entmod::AtomOrLookupRefOrVectorOrMapNotation::Atom(v) => { - if attribute.value_type == ValueType::Ref && v.inner.is_text() { - Either::Right(LookupRefOrTempId::TempId(in_process.intern_temp_id(v.inner.as_text().cloned().map(TempId::External).unwrap()))) + // Here is where we do schema-aware typechecking: we either assert + // that the given value is in the attribute's value set, or (in + // limited cases) coerce the value into the attribute's value set. + if attribute.value_type == ValueType::Ref { + match v.as_tempid() { + Some(tempid) => Either::Right(LookupRefOrTempId::TempId(in_process.intern_temp_id(tempid))), + None => v.into_typed_value(&self.schema, attribute.value_type).map(Either::Left)?, + } } else { - // Here is where we do schema-aware typechecking: we either assert that - // the given value is in the attribute's value set, or (in limited - // cases) coerce the value into the attribute's value set. - let typed_value: TypedValue = self.schema.to_typed_value(&v.without_spans(), attribute.value_type)?; - Either::Left(typed_value) + v.into_typed_value(&self.schema, attribute.value_type).map(Either::Left)? } }, @@ -488,7 +514,7 @@ 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 // to use for upserting. This tempid will not be reported in the TxReport. - let db_id: Option = mentat_tx_parser::remove_db_id(&mut map_notation)?; + let db_id: Option = remove_db_id(&mut map_notation)?; let mut dangling = db_id.is_none(); let db_id: entmod::EntidOrLookupRefOrTempId = db_id.unwrap_or_else(|| in_process.allocate_mentat_id()); diff --git a/tx-parser/src/lib.rs b/tx-parser/src/lib.rs index 37987bf0..e1af767c 100644 --- a/tx-parser/src/lib.rs +++ b/tx-parser/src/lib.rs @@ -177,7 +177,7 @@ impl<'a> Tx<'a> { .map_err(|e| Error::from_kind(ErrorKind::ParseError(e.into()))) } - fn parse_entid_or_lookup_ref_or_temp_id(input: edn::ValueAndSpan) -> std::result::Result { + pub fn parse_entid_or_lookup_ref_or_temp_id(input: edn::ValueAndSpan) -> std::result::Result { Tx::entid_or_lookup_ref_or_temp_id() .skip(eof()) .parse(input.atom_stream()) @@ -186,35 +186,6 @@ impl<'a> Tx<'a> { } } -/// 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. -/// -/// This is here simply to not expose some of the internal APIs of the tx-parser. -pub fn remove_db_id(map: &mut MapNotation) -> std::result::Result, errors::Error> { - // TODO: extract lazy defined constant. - let db_id_key = Entid::Ident(edn::NamespacedKeyword::new("db", "id")); - - let db_id: Option = if let Some(id) = map.remove(&db_id_key) { - match id { - AtomOrLookupRefOrVectorOrMapNotation::Atom(v) => { - let db_id = Tx::parse_entid_or_lookup_ref_or_temp_id(v) - .chain_err(|| Error::from(ErrorKind::DbIdError))?; - Some(db_id) - }, - AtomOrLookupRefOrVectorOrMapNotation::LookupRef(_) | - AtomOrLookupRefOrVectorOrMapNotation::TxFunction(_) | - AtomOrLookupRefOrVectorOrMapNotation::Vector(_) | - AtomOrLookupRefOrVectorOrMapNotation::MapNotation(_) => { - bail!(ErrorKind::DbIdError) - }, - } - } else { - None - }; - - Ok(db_id) -} - #[cfg(test)] mod tests { use super::*; From e437944d9491e379337382772d1ad5e6aea7e2f1 Mon Sep 17 00:00:00 2001 From: Nick Alexander Date: Mon, 7 May 2018 14:41:59 -0700 Subject: [PATCH 2/4] Pre: Don't use tx-parser for destructuring map notation. This was always a choice, but we've outgrown it: now we want to accept value types that don't come from EDN and/or tx-parser. --- db/src/internal_types.rs | 45 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 41 insertions(+), 4 deletions(-) diff --git a/db/src/internal_types.rs b/db/src/internal_types.rs index 10b5bcdf..beb17196 100644 --- a/db/src/internal_types.rs +++ b/db/src/internal_types.rs @@ -19,7 +19,9 @@ use mentat_core::KnownEntid; use mentat_core::util::Either; +use edn; use edn::{ + SpannedValue, ValueAndSpan, }; @@ -27,7 +29,6 @@ use errors; use errors::{ ErrorKind, Result, - ResultExt, }; use schema::{ SchemaTypeChecking, @@ -40,12 +41,13 @@ use types::{ TypedValue, ValueType, }; +use mentat_tx::entities; use mentat_tx::entities::{ EntidOrLookupRefOrTempId, OpType, TempId, + TxFunction, }; -use mentat_tx_parser; /// 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 @@ -68,8 +70,43 @@ impl TransactableValue for ValueAndSpan { } fn into_entity_place(self) -> Result { - mentat_tx_parser::Tx::parse_entid_or_lookup_ref_or_temp_id(self) - .chain_err(|| ErrorKind::NotYetImplemented("db id error".into())) + use self::SpannedValue::*; + match self.inner { + Integer(v) => Ok(EntidOrLookupRefOrTempId::Entid(entities::Entid::Entid(v))), + NamespacedKeyword(v) => Ok(EntidOrLookupRefOrTempId::Entid(entities::Entid::Ident(v))), + Text(v) => Ok(EntidOrLookupRefOrTempId::TempId(TempId::External(v))), + List(ls) => { + let mut it = ls.iter(); + match (it.next().map(|x| &x.inner), it.next(), it.next(), it.next()) { + // Like "(transaction-id)". + (Some(&PlainSymbol(ref op)), None, None, None) => { + Ok(EntidOrLookupRefOrTempId::TxFunction(TxFunction { op: op.clone() })) + }, + // Like "(lookup-ref)". + (Some(&PlainSymbol(edn::PlainSymbol(ref s))), Some(a), Some(v), None) if s == "lookup-ref" => { + match a.clone().into_entity_place()? { + EntidOrLookupRefOrTempId::Entid(a) => Ok(EntidOrLookupRefOrTempId::LookupRef(entities::LookupRef { a, v: v.clone().without_spans() })), + EntidOrLookupRefOrTempId::TempId(_) => bail!(""), + EntidOrLookupRefOrTempId::TxFunction(_) => bail!(""), + EntidOrLookupRefOrTempId::LookupRef(_) => bail!(""), + } + }, + _ => bail!(ErrorKind::NotYetImplemented("cannot convert value place into entity place".into())) + } + }, + Nil | + Boolean(_) | + Instant(_) | + BigInteger(_) | + Float(_) | + Uuid(_) | + PlainSymbol(_) | + NamespacedSymbol(_) | + Keyword(_) | + Vector(_) | + Set(_) | + Map(_) => bail!(ErrorKind::NotYetImplemented("cannot convert value place into entity place".into())) + } } fn as_tempid(&self) -> Option { From cbffe5e54509272c67a22418d068f3d19c8c8a6a Mon Sep 17 00:00:00 2001 From: Nick Alexander Date: Mon, 7 May 2018 10:32:28 -0700 Subject: [PATCH 3/4] Use rust-peg for tx parsing. There are few reasons to do this: - it's difficult to add symbol interning to combine-based parsers like tx-parser -- literally every type changes to reflect the interner, and that means every convenience macro we've built needs to chagne. It's trivial to add interning to rust-peg-based parsers. - combine has rolled forward to 3.2, and I spent a similar amount of time investigating how to upgrade tx-parser (to take advantage of the new parser! macros in combine that I think are necessary for adapting to changing types) as I did just converting to rust-peg. - it's easy to improve the error messages in rust-peg, where-as I have tried twice to improve the nested error messages in combine and am stumped. - it's roughly 4x faster to parse strings directly as opposed to edn::ValueAndSpan, and it'll be even better when we intern directly. --- README.md | 8 +-- db/Cargo.toml | 3 - db/src/bootstrap.rs | 3 +- db/src/db.rs | 4 +- db/src/errors.rs | 36 +++++++++-- db/src/internal_types.rs | 10 +-- db/src/lib.rs | 1 - db/src/tx.rs | 2 +- edn/src/edn.rustpeg | 105 +++++++++++++++++++++++++------ {tx => edn}/src/entities.rs | 19 +++--- edn/src/lib.rs | 1 + edn/src/types.rs | 4 ++ src/conn.rs | 20 +++--- src/errors.rs | 2 - src/lib.rs | 1 - tests/query.rs | 2 +- tools/cli/src/mentat_cli/repl.rs | 2 +- tx-parser/benches.txt | 81 ++++++++++++++++++++++++ tx-parser/benches/bench.rs | 42 ++++++++++++- tx-parser/benches2.txt | 17 +++++ tx/src/lib.rs | 4 +- 21 files changed, 297 insertions(+), 70 deletions(-) rename {tx => edn}/src/entities.rs (94%) create mode 100644 tx-parser/benches.txt create mode 100644 tx-parser/benches2.txt diff --git a/README.md b/README.md index 1e269b36..c9b964fb 100644 --- a/README.md +++ b/README.md @@ -157,6 +157,8 @@ So what are they? Our EDN parser. It uses `rust-peg` to parse [EDN](https://github.com/edn-format/edn), which is Clojure/Datomic's richer alternative to JSON. `edn`'s dependencies are all either for representing rich values (`chrono`, `uuid`, `ordered-float`) or for parsing (`serde`, `peg`). +In addition, this crate turns a stream of EDN values into a representation suitable to be transacted. + #### `mentat_core` This is the lowest-level Mentat crate. It collects together the following things: @@ -185,12 +187,6 @@ Similarly, this crate defines an abstract representation of a SQL query as under Mentat has two main inputs: reads (queries) and writes (transacts). Just as `mentat_query` defines the types produced by the query parser, `mentat_tx` defines the types produced by the tx parser. -### Transact processing - -#### `mentat_tx_parser` - -This is a `combine` parser that turns a stream of EDN values into a representation suitable to be transacted. - ### Query processing #### `mentat_query_parser` diff --git a/db/Cargo.toml b/db/Cargo.toml index 0dd92f43..438f558a 100644 --- a/db/Cargo.toml +++ b/db/Cargo.toml @@ -29,9 +29,6 @@ path = "../sql" [dependencies.mentat_tx] path = "../tx" -[dependencies.mentat_tx_parser] -path = "../tx-parser" - # Should be dev-dependencies. [dependencies.tabwriter] version = "1.0.3" diff --git a/db/src/bootstrap.rs b/db/src/bootstrap.rs index 1d8f4418..758c2127 100644 --- a/db/src/bootstrap.rs +++ b/db/src/bootstrap.rs @@ -17,7 +17,6 @@ use edn::symbols; use entids; use db::TypedSQLValue; use mentat_tx::entities::Entity; -use mentat_tx_parser; use mentat_core::{ IdentMap, Schema, @@ -300,6 +299,6 @@ pub(crate) fn bootstrap_entities() -> Vec { // 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. - let bootstrap_entities: Vec = mentat_tx_parser::Tx::parse(&bootstrap_assertions.with_spans()).unwrap(); + let bootstrap_entities: Vec = edn::parse::entities(&bootstrap_assertions.to_string()).unwrap(); return bootstrap_entities; } diff --git a/db/src/db.rs b/db/src/db.rs index 0be561d0..58729ccb 100644 --- a/db/src/db.rs +++ b/db/src/db.rs @@ -1159,7 +1159,6 @@ mod tests { Schema, attribute, }; - use mentat_tx_parser; use rusqlite; use std::collections::{ BTreeMap, @@ -1217,8 +1216,7 @@ mod tests { fn transact(&mut self, transaction: I) -> Result where I: Borrow { // Failure to parse the transaction is a coding error, so we unwrap. - let assertions = edn::parse::value(transaction.borrow()).expect(format!("to be able to parse {} into EDN", transaction.borrow()).as_str()); - let entities: Vec<_> = mentat_tx_parser::Tx::parse(&assertions).expect(format!("to be able to parse {} into entities", assertions).as_str()); + let entities = edn::parse::entities(transaction.borrow()).expect(format!("to be able to parse {} into entities", transaction.borrow()).as_str()); let details = { // The block scopes the borrow of self.sqlite. diff --git a/db/src/errors.rs b/db/src/errors.rs index a90982d0..4d7b20bf 100644 --- a/db/src/errors.rs +++ b/db/src/errors.rs @@ -21,7 +21,6 @@ use rusqlite; use mentat_tx::entities::{ TempId, }; -use mentat_tx_parser; use mentat_core::{ KnownEntid, }; @@ -60,6 +59,30 @@ impl ::std::fmt::Display for SchemaConstraintViolation { } } +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum InputError { + /// Map notation included a bad `:db/id` value. + BadDbId, + + /// A value place cannot be interpreted as an entity place (for example, in nested map + /// notation). + BadEntityPlace, +} + +impl ::std::fmt::Display for InputError { + fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + use self::InputError::*; + match self { + &BadDbId => { + writeln!(f, ":db/id in map notation must either not be present or be an entid, an ident, or a tempid") + }, + &BadEntityPlace => { + writeln!(f, "cannot convert value place into entity place") + }, + } + } +} + error_chain! { types { Error, ErrorKind, ResultExt, Result; @@ -69,10 +92,6 @@ error_chain! { Rusqlite(rusqlite::Error); } - links { - TxParseError(mentat_tx_parser::Error, mentat_tx_parser::ErrorKind); - } - errors { /// We're just not done yet. Message that the feature is recognized but not yet /// implemented. @@ -152,5 +171,12 @@ error_chain! { description("schema constraint violation") display("schema constraint violation: {}", violation) } + + /// The transaction was malformed in some way (that was not recognized at parse time; for + /// example, in a way that is schema-dependent). + InputError(error: InputError) { + description("transaction input error") + display("transaction input error: {}", error) + } } } diff --git a/db/src/internal_types.rs b/db/src/internal_types.rs index beb17196..6766d831 100644 --- a/db/src/internal_types.rs +++ b/db/src/internal_types.rs @@ -86,12 +86,12 @@ impl TransactableValue for ValueAndSpan { (Some(&PlainSymbol(edn::PlainSymbol(ref s))), Some(a), Some(v), None) if s == "lookup-ref" => { match a.clone().into_entity_place()? { EntidOrLookupRefOrTempId::Entid(a) => Ok(EntidOrLookupRefOrTempId::LookupRef(entities::LookupRef { a, v: v.clone().without_spans() })), - EntidOrLookupRefOrTempId::TempId(_) => bail!(""), - EntidOrLookupRefOrTempId::TxFunction(_) => bail!(""), - EntidOrLookupRefOrTempId::LookupRef(_) => bail!(""), + EntidOrLookupRefOrTempId::TempId(_) | + EntidOrLookupRefOrTempId::TxFunction(_) | + EntidOrLookupRefOrTempId::LookupRef(_) => bail!(ErrorKind::InputError(errors::InputError::BadEntityPlace)), } }, - _ => bail!(ErrorKind::NotYetImplemented("cannot convert value place into entity place".into())) + _ => bail!(ErrorKind::InputError(errors::InputError::BadEntityPlace)), } }, Nil | @@ -105,7 +105,7 @@ impl TransactableValue for ValueAndSpan { Keyword(_) | Vector(_) | Set(_) | - Map(_) => bail!(ErrorKind::NotYetImplemented("cannot convert value place into entity place".into())) + Map(_) => bail!(ErrorKind::InputError(errors::InputError::BadEntityPlace)), } } diff --git a/db/src/lib.rs b/db/src/lib.rs index b87619e4..9652a342 100644 --- a/db/src/lib.rs +++ b/db/src/lib.rs @@ -26,7 +26,6 @@ extern crate time; #[macro_use] extern crate mentat_core; extern crate mentat_sql; extern crate mentat_tx; -extern crate mentat_tx_parser; use std::iter::repeat; diff --git a/db/src/tx.rs b/db/src/tx.rs index 15a001f4..e48f132b 100644 --- a/db/src/tx.rs +++ b/db/src/tx.rs @@ -174,7 +174,7 @@ pub fn remove_db_id(map: &mut entmod::MapNotation) -> Result { - bail!(ErrorKind::NotYetImplemented("db id error".into())) + bail!(ErrorKind::InputError(errors::InputError::BadDbId)) }, } } else { diff --git a/edn/src/edn.rustpeg b/edn/src/edn.rustpeg index 729d8176..4d1d5d82 100644 --- a/edn/src/edn.rustpeg +++ b/edn/src/edn.rustpeg @@ -23,6 +23,8 @@ use num::BigInt; use ordered_float::OrderedFloat; use uuid::Uuid; +use entities::*; +use symbols::*; use types::{SpannedValue, Span, ValueAndSpan}; // Goal: Be able to parse https://github.com/edn-format/edn @@ -51,21 +53,25 @@ validbase = [3][0-6] / [12][0-9] / [2-9] hex = [0-9a-fA-F] sign = [+-] -pub bigint -> SpannedValue = b:$( sign? digit+ ) "N" - { SpannedValue::BigInteger(b.parse::().unwrap()) } -pub octalinteger -> SpannedValue = "0" i:$( octaldigit+ ) - { SpannedValue::Integer(i64::from_str_radix(i, 8).unwrap()) } -pub hexinteger -> SpannedValue = "0x" i:$( hex+ ) - { SpannedValue::Integer(i64::from_str_radix(i, 16).unwrap()) } +pub raw_bigint -> BigInt = b:$( sign? digit+ ) "N" + { b.parse::().unwrap() } +pub raw_octalinteger -> i64 = "0" i:$( octaldigit+ ) + { i64::from_str_radix(i, 8).unwrap() } +pub raw_hexinteger -> i64 = "0x" i:$( hex+ ) + { i64::from_str_radix(i, 16).unwrap() } +pub raw_basedinteger -> i64 = b:$( validbase ) "r" i:$( alphanumeric+ ) + { i64::from_str_radix(i, b.parse::().unwrap()).unwrap() } +pub raw_integer -> i64 = i:$( sign? digit+ ) !("." / ([eE])) + { i.parse::().unwrap() } +pub raw_float -> OrderedFloat = f:$(sign? digit+ ("." digit+)? ([eE] sign? digit+)?) + { OrderedFloat(f.parse::().unwrap()) } -pub basedinteger -> SpannedValue = b:$( validbase ) "r" i:$( alphanumeric+ ) - { SpannedValue::Integer(i64::from_str_radix(i, b.parse::().unwrap()).unwrap()) } - -pub integer -> SpannedValue = i:$( sign? digit+ ) !("." / ([eE])) - { SpannedValue::Integer(i.parse::().unwrap()) } - -pub float -> SpannedValue = f:$(sign? digit+ ("." digit+)? ([eE] sign? digit+)?) - { SpannedValue::Float(OrderedFloat(f.parse::().unwrap())) } +pub bigint -> SpannedValue = v:raw_bigint { SpannedValue::BigInteger(v) } +pub octalinteger -> SpannedValue = v:raw_octalinteger { SpannedValue::Integer(v) } +pub hexinteger -> SpannedValue = v:raw_hexinteger { SpannedValue::Integer(v) } +pub basedinteger -> SpannedValue = v:raw_basedinteger { SpannedValue::Integer(v) } +pub integer -> SpannedValue = v:raw_integer { SpannedValue::Integer(v) } +pub float -> SpannedValue = v:raw_float { SpannedValue::Float(v) } number -> SpannedValue = ( bigint / basedinteger / hexinteger / octalinteger / integer / float ) @@ -81,8 +87,11 @@ string_normal_chars -> &'input str = $([^"\\]+) // output = [quote, "foo", backslash, "bar", quote] // result = r#""foo\\bar""# // For the typical case, string_normal_chars will match multiple, leading to a single-element vec. -pub text -> SpannedValue = "\"" t:((string_special_char / string_normal_chars)*) "\"" - { SpannedValue::Text(t.join(&"").to_string()) } +pub raw_text -> String = "\"" t:((string_special_char / string_normal_chars)*) "\"" + { t.join(&"").to_string() } + +pub text -> SpannedValue + = v:raw_text { SpannedValue::Text(v) } // RFC 3339 timestamps. #inst "1985-04-12T23:20:50.52Z" // We accept an arbitrary depth of decimals. @@ -180,9 +189,69 @@ pub value -> ValueAndSpan = } } +atom -> ValueAndSpan + = v:value {? if v.is_atom() { Ok(v) } else { Err("expected atom") } } + // Clojure (and thus EDN) regards commas as whitespace, and thus the two-element vectors [1 2] and // [1,,,,2] are equivalent, as are the maps {:a 1, :b 2} and {:a 1 :b 2}. -whitespace = [ \r\n\t,] -comment = ";" [^\r\n]* [\r\n]? +whitespace = #quiet<[ \r\n\t,]> +comment = #quiet<";" [^\r\n]* [\r\n]?> __ = (whitespace / comment)* + +pub op -> OpType + = ":db/add" { OpType::Add } + / ":db/retract" { OpType::Retract } + +raw_keyword -> NamespacedKeyword + = keyword_prefix ns:$(symbol_namespace) namespace_separator n:$(symbol_name) { NamespacedKeyword::new(ns, n) } + +raw_forward_keyword -> NamespacedKeyword + = v:raw_keyword {? if v.is_forward() { Ok(v) } else { Err("expected :forward/keyword") } } + +raw_backward_keyword -> NamespacedKeyword + = v:raw_keyword {? if v.is_backward() { Ok(v) } else { Err("expected :backward/_keyword") } } + +entid -> Entid + = v:( raw_basedinteger / raw_hexinteger / raw_octalinteger / raw_integer ) { Entid::Entid(v) } + / v:raw_keyword { Entid::Ident(v) } + +forward_entid -> Entid + = v:( raw_basedinteger / raw_hexinteger / raw_octalinteger / raw_integer ) { Entid::Entid(v) } + / v:raw_forward_keyword { Entid::Ident(v) } + +backward_entid -> Entid + = v:raw_backward_keyword { Entid::Ident(v.to_reversed()) } + +lookup_ref -> LookupRef + = "(" __ "lookup-ref" __ a:(entid) __ v:(value) __ ")" { LookupRef { a, v: v.without_spans() } } + +tx_function -> TxFunction + = "(" __ n:$(symbol_name) __ ")" { TxFunction { op: PlainSymbol::new(n) } } + +entity_place -> EntidOrLookupRefOrTempId + = v:raw_text { EntidOrLookupRefOrTempId::TempId(TempId::External(v)) } + / v:entid { EntidOrLookupRefOrTempId::Entid(v) } + / v:lookup_ref { EntidOrLookupRefOrTempId::LookupRef(v) } + / v:tx_function { EntidOrLookupRefOrTempId::TxFunction(v) } + +value_place_pair -> (Entid, AtomOrLookupRefOrVectorOrMapNotation) + = k:(entid) __ v:(value_place) { (k, v) } + +map_notation -> MapNotation + = "{" __ kvs:(value_place_pair*) __ "}" { kvs.into_iter().collect() } + +value_place -> AtomOrLookupRefOrVectorOrMapNotation + = __ v:lookup_ref __ { AtomOrLookupRefOrVectorOrMapNotation::LookupRef(v) } + / __ v:tx_function __ { AtomOrLookupRefOrVectorOrMapNotation::TxFunction(v) } + / __ "[" __ vs:(value_place*) __ "]" __ { AtomOrLookupRefOrVectorOrMapNotation::Vector(vs) } + / __ v:map_notation __ { AtomOrLookupRefOrVectorOrMapNotation::MapNotation(v) } + / __ v:atom __ { AtomOrLookupRefOrVectorOrMapNotation::Atom(v) } + +pub entity -> Entity + = __ "[" __ op:(op) __ e:(entity_place) __ a:(forward_entid) __ v:(value_place) __ "]" __ { Entity::AddOrRetract { op, e: e, a, v: v } } + / __ "[" __ op:(op) __ e:(value_place) __ a:(backward_entid) __ v:(entity_place) __ "]" __ { Entity::AddOrRetract { op, e: v, a, v: e } } + / __ map:map_notation __ { Entity::MapNotation(map) } + +pub entities -> Vec + = __ "[" __ es:(entity*) __ "]" __ { es } diff --git a/tx/src/entities.rs b/edn/src/entities.rs similarity index 94% rename from tx/src/entities.rs rename to edn/src/entities.rs index 091ad1f0..4eba1912 100644 --- a/tx/src/entities.rs +++ b/edn/src/entities.rs @@ -10,14 +10,19 @@ //! This module defines core types that support the transaction processor. -extern crate edn; - use std::collections::BTreeMap; use std::fmt; -use self::edn::symbols::NamespacedKeyword; +use symbols::{ + NamespacedKeyword, + PlainSymbol, +}; +use types::{ + Value, + ValueAndSpan, +}; -/// A tempid, either an external tempid given in a transaction (usually as an `edn::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. #[derive(Clone, Debug, Eq, Hash, Ord, PartialOrd, PartialEq)] pub enum TempId { @@ -63,7 +68,7 @@ pub struct LookupRef { pub a: Entid, // 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. - pub v: edn::Value, // An atom. + pub v: Value, // An atom. } /// A "transaction function" that exposes some value determined by the current transaction. The @@ -80,14 +85,14 @@ pub struct LookupRef { /// generalization. #[derive(Clone, Debug, Eq, Hash, Ord, PartialOrd, PartialEq)] pub struct TxFunction { - pub op: edn::PlainSymbol, + pub op: PlainSymbol, } pub type MapNotation = BTreeMap; #[derive(Clone, Debug, Eq, Hash, Ord, PartialOrd, PartialEq)] pub enum AtomOrLookupRefOrVectorOrMapNotation { - Atom(edn::ValueAndSpan), + Atom(ValueAndSpan), LookupRef(LookupRef), TxFunction(TxFunction), Vector(Vec), diff --git a/edn/src/lib.rs b/edn/src/lib.rs index 53f461d7..d1e382e2 100644 --- a/edn/src/lib.rs +++ b/edn/src/lib.rs @@ -22,6 +22,7 @@ extern crate serde; #[macro_use] extern crate serde_derive; +pub mod entities; pub mod symbols; pub mod types; pub mod pretty_print; diff --git a/edn/src/types.rs b/edn/src/types.rs index 10419bf6..549dfb71 100644 --- a/edn/src/types.rs +++ b/edn/src/types.rs @@ -110,6 +110,10 @@ impl ValueAndSpan { } } + pub fn is_atom(&self) -> bool { + self.inner.is_atom() + } + pub fn as_atom(&self) -> Option<&ValueAndSpan> { if self.inner.is_atom() { Some(self) diff --git a/src/conn.rs b/src/conn.rs index f27d43f7..1a965f7d 100644 --- a/src/conn.rs +++ b/src/conn.rs @@ -10,6 +10,10 @@ #![allow(dead_code)] +use std::borrow::{ + Borrow, +}; + use std::collections::{ BTreeMap, }; @@ -85,8 +89,6 @@ use mentat_tx::entities::{ OpType, }; -use mentat_tx_parser; - use mentat_tolstoy::Syncer; use uuid::Uuid; @@ -494,9 +496,8 @@ impl<'a, 'c> InProgress<'a, 'c> { Ok(report) } - pub fn transact(&mut self, transaction: &str) -> Result { - let assertion_vector = edn::parse::value(transaction)?; - let entities = mentat_tx_parser::Tx::parse(&assertion_vector)?; + pub fn transact(&mut self, transaction: B) -> Result where B: Borrow { + let entities = edn::parse::entities(transaction.borrow())?; self.transact_entities(entities) } @@ -912,15 +913,14 @@ impl Conn { /// Transact entities against the Mentat store, using the given connection and the current /// metadata. - pub fn transact(&mut self, + pub fn transact(&mut self, sqlite: &mut rusqlite::Connection, - transaction: &str) -> Result { + transaction: B) -> Result where B: Borrow { // Parse outside the SQL transaction. This is a tradeoff: we are limiting the scope of the // transaction, and indeed we don't even create a SQL transaction if the provided input is // invalid, but it means SQLite errors won't be found until the parse is complete, and if // there's a race for the database (don't do that!) we are less likely to win it. - let assertion_vector = edn::parse::value(transaction)?; - let entities = mentat_tx_parser::Tx::parse(&assertion_vector)?; + let entities = edn::parse::entities(transaction.borrow())?; let mut in_progress = self.begin_transaction(sqlite)?; let report = in_progress.transact_entities(entities)?; @@ -1248,7 +1248,7 @@ mod tests { // Bad transaction data: missing leading :db/add. let report = conn.transact(&mut sqlite, "[[\"t\" :db/ident :b/keyword]]"); match report.unwrap_err() { - Error(ErrorKind::TxParseError(::mentat_tx_parser::errors::ErrorKind::ParseError(_)), _) => { }, + Error(ErrorKind::EdnParseError(_), _) => { }, x => panic!("expected EDN parse error, got {:?}", x), } diff --git a/src/errors.rs b/src/errors.rs index 42ea0c1c..679388fd 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -30,7 +30,6 @@ use mentat_query_pull; use mentat_query_translator; use mentat_sql; use mentat_tolstoy; -use mentat_tx_parser; error_chain! { types { @@ -52,7 +51,6 @@ error_chain! { PullError(mentat_query_pull::errors::Error, mentat_query_pull::errors::ErrorKind); TranslatorError(mentat_query_translator::Error, mentat_query_translator::ErrorKind); SqlError(mentat_sql::Error, mentat_sql::ErrorKind); - TxParseError(mentat_tx_parser::Error, mentat_tx_parser::ErrorKind); SyncError(mentat_tolstoy::Error, mentat_tolstoy::ErrorKind); } diff --git a/src/lib.rs b/src/lib.rs index 7bc81013..79029994 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -32,7 +32,6 @@ extern crate mentat_query_translator; extern crate mentat_sql; extern crate mentat_tolstoy; extern crate mentat_tx; -extern crate mentat_tx_parser; pub use mentat_core::{ Attribute, diff --git a/tests/query.rs b/tests/query.rs index 1ad0b02d..8a981bc8 100644 --- a/tests/query.rs +++ b/tests/query.rs @@ -742,7 +742,7 @@ fn test_type_reqs() { {:db/ident :test/long2 :db/valueType :db.type/long :db/cardinality :db.cardinality/one} ]"#).unwrap(); - conn.transact(&mut c, &format!("[[:db/add {} :test/long2 5]]", entid)).unwrap(); + conn.transact(&mut c, format!("[[:db/add {} :test/long2 5]]", entid)).unwrap(); let longs_query = r#"[:find [?v ...] :order (asc ?v) :in ?e diff --git a/tools/cli/src/mentat_cli/repl.rs b/tools/cli/src/mentat_cli/repl.rs index 119c7ebc..db88881a 100644 --- a/tools/cli/src/mentat_cli/repl.rs +++ b/tools/cli/src/mentat_cli/repl.rs @@ -511,7 +511,7 @@ impl Repl { fn transact(&mut self, transaction: String) -> ::mentat::errors::Result { let mut tx = self.store.begin_transaction()?; - let report = tx.transact(&transaction)?; + let report = tx.transact(transaction)?; tx.commit()?; Ok(report) } diff --git a/tx-parser/benches.txt b/tx-parser/benches.txt new file mode 100644 index 00000000..ee8eab4f --- /dev/null +++ b/tx-parser/benches.txt @@ -0,0 +1,81 @@ +Before slices, release: + +⋊> ~/M/mentat on ⨯ cargo bench --package mentat_tx_parser + Finished release [optimized] target(s) in 0.0 secs + Running target/release/deps/bench-0defa345d586a763 + +running 2 tests +test bench_parse1 ... bench: 7,745 ns/iter (+/- 2,515) +test bench_parse2 ... bench: 494,244 ns/iter (+/- 159,379) + +test result: ok. 0 passed; 0 failed; 0 ignored; 2 measured + + Running target/release/deps/mentat_tx_parser-c3f614f12d05a17a + +running 5 tests +test tests::test_add ... ignored +test tests::test_lookup_ref ... ignored +test tests::test_map_notation ... ignored +test tests::test_nested_vector ... ignored +test tests::test_retract ... ignored + +test result: ok. 0 passed; 0 failed; 5 ignored; 0 measured + +⋊> ~/M/mentat on ⨯ cargo bench --package mentat_tx_parser + Finished release [optimized] target(s) in 0.0 secs + Running target/release/deps/bench-0defa345d586a763 + +running 2 tests +test bench_parse1 ... bench: 7,793 ns/iter (+/- 1,258) +test bench_parse2 ... bench: 532,144 ns/iter (+/- 110,614) + +test result: ok. 0 passed; 0 failed; 0 ignored; 2 measured + +After slices, release: + +⋊> ~/M/mentat on parse-faster ⨯ cargo bench --package mentat_tx_parser 16:10:57 + Compiling mentat_tx_parser v0.0.1 (file:///Users/nalexander/Mozilla/mentat/tx-parser) + Finished release [optimized + debuginfo] target(s) in 2.25 secs + Running target/release/deps/bench-0defa345d586a763 + +running 3 tests +test bench_parse1 ... bench: 1,413 ns/iter (+/- 92) +test bench_parse2 ... bench: 26,190 ns/iter (+/- 4,167) +test bench_parse3 ... bench: 51,823 ns/iter (+/- 7,000) + +test result: ok. 0 passed; 0 failed; 0 ignored; 3 measured + + Running target/release/deps/mentat_tx_parser-c3f614f12d05a17a + +running 1 test +test tests::test_add ... ignored + +test result: ok. 0 passed; 0 failed; 1 ignored; 0 measured + +⋊> ~/M/mentat on parse-faster ⨯ cargo bench --package mentat_tx_parser 16:16:35 + Finished release [optimized + debuginfo] target(s) in 0.0 secs + Running target/release/deps/bench-0defa345d586a763 + +running 3 tests +test bench_parse1 ... bench: 1,410 ns/iter (+/- 164) +test bench_parse2 ... bench: 26,195 ns/iter (+/- 1,851) +test bench_parse3 ... bench: 51,680 ns/iter (+/- 12,190) + +test result: ok. 0 passed; 0 failed; 0 ignored; 3 measured + + Running target/release/deps/mentat_tx_parser-c3f614f12d05a17a + +running 1 test +test tests::test_add ... ignored + +test result: ok. 0 passed; 0 failed; 1 ignored; 0 measured + +test bench_parse1 ... bench: 7,690 ns/iter (+/- 3,035) +test bench_parse2 ... bench: 548,920 ns/iter (+/- 282,846) +test bench_parse3 ... bench: 1,757,897 ns/iter (+/- 301,719) +test bench_parse4 ... bench: 6,957,627 ns/iter (+/- 1,162,660) + +test bench_parse1 ... bench: 1,038 ns/iter (+/- 213) +test bench_parse2 ... bench: 18,647 ns/iter (+/- 3,971) +test bench_parse3 ... bench: 36,715 ns/iter (+/- 6,508) +test bench_parse4 ... bench: 79,502 ns/iter (+/- 19,525) diff --git a/tx-parser/benches/bench.rs b/tx-parser/benches/bench.rs index 9445b028..91605f83 100644 --- a/tx-parser/benches/bench.rs +++ b/tx-parser/benches/bench.rs @@ -14,7 +14,7 @@ use mentat_tx_parser::Tx; fn bench_parse1(b: &mut Bencher) { let input = r#"[[:db/add 1 :test/val "a"]]"#; let parsed_edn = edn::parse::value(input).expect("to parse test input"); - b.iter(|| Tx::parse(parsed_edn.clone())); + b.iter(|| Tx::parse(&parsed_edn)); } #[bench] @@ -46,6 +46,42 @@ fn bench_parse2(b: &mut Bencher) { [:db/add 24 :test/val "x"] [:db/add 25 :test/val "y"] [:db/add 26 :test/val "z"]]"#; - let parsed_edn = edn::parse::value(input).expect("to parse test input"); - b.iter(|| Tx::parse(parsed_edn.clone())); + b.iter(|| { + let parsed_edn = edn::parse::value(input).expect("to parse test input"); + Tx::parse(&parsed_edn).expect("to parse tx"); + }); +} + +#[bench] +fn bench_parse3(b: &mut Bencher) { + let input = r#" + [[:db/add 1 :test/val "a"] + [:db/add 2 :test/val "b"] + [:db/add 3 :test/val "c"] + [:db/add 4 :test/val "d"] + [:db/add 5 :test/val "e"] + [:db/add 6 :test/val "f"] + [:db/add 7 :test/val "g"] + [:db/add 8 :test/val "h"] + [:db/add 9 :test/val "i"] + [:db/add 10 :test/val "j"] + [:db/add 11 :test/val "k"] + [:db/add 12 :test/val "l"] + [:db/add 13 :test/val "m"] + [:db/add 14 :test/val "n"] + [:db/add 15 :test/val "o"] + [:db/add 16 :test/val "p"] + [:db/add 17 :test/val "q"] + [:db/add 18 :test/val "r"] + [:db/add 19 :test/val "s"] + [:db/add 20 :test/val "t"] + [:db/add 21 :test/val "u"] + [:db/add 22 :test/val "v"] + [:db/add 23 :test/val "w"] + [:db/add 24 :test/val "x"] + [:db/add 25 :test/val "y"] + [:db/add 26 :test/val "z"]]"#; + b.iter(|| { + edn::parse::entities(input).expect("to parse test input"); + }); } diff --git a/tx-parser/benches2.txt b/tx-parser/benches2.txt new file mode 100644 index 00000000..94a9c1f3 --- /dev/null +++ b/tx-parser/benches2.txt @@ -0,0 +1,17 @@ + +running 5 tests +test tests::test_add ... ignored +test tests::test_lookup_ref ... ignored +test tests::test_map_notation ... ignored +test tests::test_nested_vector ... ignored +test tests::test_retract ... ignored + +test result: ok. 0 passed; 0 failed; 5 ignored; 0 measured; 0 filtered out + + +running 3 tests +test bench_parse1 ... bench: 2,799 ns/iter (+/- 801) +test bench_parse2 ... bench: 191,856 ns/iter (+/- 19,331) +test bench_parse3 ... bench: 53,925 ns/iter (+/- 10,299) + +test result: ok. 0 passed; 0 failed; 0 ignored; 3 measured; 0 filtered out diff --git a/tx/src/lib.rs b/tx/src/lib.rs index 78eac748..ad503e05 100644 --- a/tx/src/lib.rs +++ b/tx/src/lib.rs @@ -8,4 +8,6 @@ // CONDITIONS OF ANY KIND, either express or implied. See the License for the // specific language governing permissions and limitations under the License. -pub mod entities; +#[allow(unused_imports)] +#[macro_use] extern crate edn; +pub use edn::entities; From 7a8c9d90c23b3f6276ee8630a6aa17bc75a97ce2 Mon Sep 17 00:00:00 2001 From: Nick Alexander Date: Mon, 7 May 2018 14:53:51 -0700 Subject: [PATCH 4/4] Post: Remove tx-parser crate entirely. --- Cargo.toml | 3 - tx-parser/Cargo.toml | 17 -- tx-parser/benches.txt | 81 --------- tx-parser/benches/bench.rs | 87 ---------- tx-parser/benches2.txt | 17 -- tx-parser/src/errors.rs | 31 ---- tx-parser/src/lib.rs | 326 ------------------------------------- tx-parser/tests/parser.rs | 106 ------------ 8 files changed, 668 deletions(-) delete mode 100644 tx-parser/Cargo.toml delete mode 100644 tx-parser/benches.txt delete mode 100644 tx-parser/benches/bench.rs delete mode 100644 tx-parser/benches2.txt delete mode 100644 tx-parser/src/errors.rs delete mode 100644 tx-parser/src/lib.rs delete mode 100644 tx-parser/tests/parser.rs diff --git a/Cargo.toml b/Cargo.toml index b9029f4e..a072e4bb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -76,9 +76,6 @@ path = "query-translator" [dependencies.mentat_tx] path = "tx" -[dependencies.mentat_tx_parser] -path = "tx-parser" - [dependencies.mentat_tolstoy] path = "tolstoy" diff --git a/tx-parser/Cargo.toml b/tx-parser/Cargo.toml deleted file mode 100644 index 8f7147ef..00000000 --- a/tx-parser/Cargo.toml +++ /dev/null @@ -1,17 +0,0 @@ -[package] -name = "mentat_tx_parser" -version = "0.0.1" -workspace = ".." - -[dependencies] -combine = "2.3.2" -error-chain = { git = "https://github.com/rnewman/error-chain", branch = "rnewman/sync" } - -[dependencies.edn] -path = "../edn" - -[dependencies.mentat_tx] -path = "../tx" - -[dependencies.mentat_parser_utils] -path = "../parser-utils" diff --git a/tx-parser/benches.txt b/tx-parser/benches.txt deleted file mode 100644 index ee8eab4f..00000000 --- a/tx-parser/benches.txt +++ /dev/null @@ -1,81 +0,0 @@ -Before slices, release: - -⋊> ~/M/mentat on ⨯ cargo bench --package mentat_tx_parser - Finished release [optimized] target(s) in 0.0 secs - Running target/release/deps/bench-0defa345d586a763 - -running 2 tests -test bench_parse1 ... bench: 7,745 ns/iter (+/- 2,515) -test bench_parse2 ... bench: 494,244 ns/iter (+/- 159,379) - -test result: ok. 0 passed; 0 failed; 0 ignored; 2 measured - - Running target/release/deps/mentat_tx_parser-c3f614f12d05a17a - -running 5 tests -test tests::test_add ... ignored -test tests::test_lookup_ref ... ignored -test tests::test_map_notation ... ignored -test tests::test_nested_vector ... ignored -test tests::test_retract ... ignored - -test result: ok. 0 passed; 0 failed; 5 ignored; 0 measured - -⋊> ~/M/mentat on ⨯ cargo bench --package mentat_tx_parser - Finished release [optimized] target(s) in 0.0 secs - Running target/release/deps/bench-0defa345d586a763 - -running 2 tests -test bench_parse1 ... bench: 7,793 ns/iter (+/- 1,258) -test bench_parse2 ... bench: 532,144 ns/iter (+/- 110,614) - -test result: ok. 0 passed; 0 failed; 0 ignored; 2 measured - -After slices, release: - -⋊> ~/M/mentat on parse-faster ⨯ cargo bench --package mentat_tx_parser 16:10:57 - Compiling mentat_tx_parser v0.0.1 (file:///Users/nalexander/Mozilla/mentat/tx-parser) - Finished release [optimized + debuginfo] target(s) in 2.25 secs - Running target/release/deps/bench-0defa345d586a763 - -running 3 tests -test bench_parse1 ... bench: 1,413 ns/iter (+/- 92) -test bench_parse2 ... bench: 26,190 ns/iter (+/- 4,167) -test bench_parse3 ... bench: 51,823 ns/iter (+/- 7,000) - -test result: ok. 0 passed; 0 failed; 0 ignored; 3 measured - - Running target/release/deps/mentat_tx_parser-c3f614f12d05a17a - -running 1 test -test tests::test_add ... ignored - -test result: ok. 0 passed; 0 failed; 1 ignored; 0 measured - -⋊> ~/M/mentat on parse-faster ⨯ cargo bench --package mentat_tx_parser 16:16:35 - Finished release [optimized + debuginfo] target(s) in 0.0 secs - Running target/release/deps/bench-0defa345d586a763 - -running 3 tests -test bench_parse1 ... bench: 1,410 ns/iter (+/- 164) -test bench_parse2 ... bench: 26,195 ns/iter (+/- 1,851) -test bench_parse3 ... bench: 51,680 ns/iter (+/- 12,190) - -test result: ok. 0 passed; 0 failed; 0 ignored; 3 measured - - Running target/release/deps/mentat_tx_parser-c3f614f12d05a17a - -running 1 test -test tests::test_add ... ignored - -test result: ok. 0 passed; 0 failed; 1 ignored; 0 measured - -test bench_parse1 ... bench: 7,690 ns/iter (+/- 3,035) -test bench_parse2 ... bench: 548,920 ns/iter (+/- 282,846) -test bench_parse3 ... bench: 1,757,897 ns/iter (+/- 301,719) -test bench_parse4 ... bench: 6,957,627 ns/iter (+/- 1,162,660) - -test bench_parse1 ... bench: 1,038 ns/iter (+/- 213) -test bench_parse2 ... bench: 18,647 ns/iter (+/- 3,971) -test bench_parse3 ... bench: 36,715 ns/iter (+/- 6,508) -test bench_parse4 ... bench: 79,502 ns/iter (+/- 19,525) diff --git a/tx-parser/benches/bench.rs b/tx-parser/benches/bench.rs deleted file mode 100644 index 91605f83..00000000 --- a/tx-parser/benches/bench.rs +++ /dev/null @@ -1,87 +0,0 @@ -#![feature(test)] - -// These benchmarks can be run from the project root with: -// > cargo bench --package mentat_tx_parser - -extern crate test; -extern crate edn; -extern crate mentat_tx_parser; - -use test::Bencher; -use mentat_tx_parser::Tx; - -#[bench] -fn bench_parse1(b: &mut Bencher) { - let input = r#"[[:db/add 1 :test/val "a"]]"#; - let parsed_edn = edn::parse::value(input).expect("to parse test input"); - b.iter(|| Tx::parse(&parsed_edn)); -} - -#[bench] -fn bench_parse2(b: &mut Bencher) { - let input = r#" - [[:db/add 1 :test/val "a"] - [:db/add 2 :test/val "b"] - [:db/add 3 :test/val "c"] - [:db/add 4 :test/val "d"] - [:db/add 5 :test/val "e"] - [:db/add 6 :test/val "f"] - [:db/add 7 :test/val "g"] - [:db/add 8 :test/val "h"] - [:db/add 9 :test/val "i"] - [:db/add 10 :test/val "j"] - [:db/add 11 :test/val "k"] - [:db/add 12 :test/val "l"] - [:db/add 13 :test/val "m"] - [:db/add 14 :test/val "n"] - [:db/add 15 :test/val "o"] - [:db/add 16 :test/val "p"] - [:db/add 17 :test/val "q"] - [:db/add 18 :test/val "r"] - [:db/add 19 :test/val "s"] - [:db/add 20 :test/val "t"] - [:db/add 21 :test/val "u"] - [:db/add 22 :test/val "v"] - [:db/add 23 :test/val "w"] - [:db/add 24 :test/val "x"] - [:db/add 25 :test/val "y"] - [:db/add 26 :test/val "z"]]"#; - b.iter(|| { - let parsed_edn = edn::parse::value(input).expect("to parse test input"); - Tx::parse(&parsed_edn).expect("to parse tx"); - }); -} - -#[bench] -fn bench_parse3(b: &mut Bencher) { - let input = r#" - [[:db/add 1 :test/val "a"] - [:db/add 2 :test/val "b"] - [:db/add 3 :test/val "c"] - [:db/add 4 :test/val "d"] - [:db/add 5 :test/val "e"] - [:db/add 6 :test/val "f"] - [:db/add 7 :test/val "g"] - [:db/add 8 :test/val "h"] - [:db/add 9 :test/val "i"] - [:db/add 10 :test/val "j"] - [:db/add 11 :test/val "k"] - [:db/add 12 :test/val "l"] - [:db/add 13 :test/val "m"] - [:db/add 14 :test/val "n"] - [:db/add 15 :test/val "o"] - [:db/add 16 :test/val "p"] - [:db/add 17 :test/val "q"] - [:db/add 18 :test/val "r"] - [:db/add 19 :test/val "s"] - [:db/add 20 :test/val "t"] - [:db/add 21 :test/val "u"] - [:db/add 22 :test/val "v"] - [:db/add 23 :test/val "w"] - [:db/add 24 :test/val "x"] - [:db/add 25 :test/val "y"] - [:db/add 26 :test/val "z"]]"#; - b.iter(|| { - edn::parse::entities(input).expect("to parse test input"); - }); -} diff --git a/tx-parser/benches2.txt b/tx-parser/benches2.txt deleted file mode 100644 index 94a9c1f3..00000000 --- a/tx-parser/benches2.txt +++ /dev/null @@ -1,17 +0,0 @@ - -running 5 tests -test tests::test_add ... ignored -test tests::test_lookup_ref ... ignored -test tests::test_map_notation ... ignored -test tests::test_nested_vector ... ignored -test tests::test_retract ... ignored - -test result: ok. 0 passed; 0 failed; 5 ignored; 0 measured; 0 filtered out - - -running 3 tests -test bench_parse1 ... bench: 2,799 ns/iter (+/- 801) -test bench_parse2 ... bench: 191,856 ns/iter (+/- 19,331) -test bench_parse3 ... bench: 53,925 ns/iter (+/- 10,299) - -test result: ok. 0 passed; 0 failed; 0 ignored; 3 measured; 0 filtered out diff --git a/tx-parser/src/errors.rs b/tx-parser/src/errors.rs deleted file mode 100644 index a4abd8fb..00000000 --- a/tx-parser/src/errors.rs +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright 2016 Mozilla -// -// Licensed under the Apache License, Version 2.0 (the "License"); you may not use -// this file except in compliance with the License. You may obtain a copy of the -// License at http://www.apache.org/licenses/LICENSE-2.0 -// Unless required by applicable law or agreed to in writing, software distributed -// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR -// CONDITIONS OF ANY KIND, either express or implied. See the License for the -// specific language governing permissions and limitations under the License. - -#![allow(dead_code)] - -use mentat_parser_utils::ValueParseError; - -error_chain! { - types { - Error, ErrorKind, ResultExt, Result; - } - - errors { - ParseError(parse_error: ValueParseError) { - description("error parsing edn values") - display("error parsing edn values:\n{}", parse_error) - } - - DbIdError { - description("bad :db/id in map notation") - display("bad :db/id in map notation: must either be not present or be an entid, an ident, or a tempid") - } - } -} diff --git a/tx-parser/src/lib.rs b/tx-parser/src/lib.rs deleted file mode 100644 index e1af767c..00000000 --- a/tx-parser/src/lib.rs +++ /dev/null @@ -1,326 +0,0 @@ -// Copyright 2016 Mozilla -// -// Licensed under the Apache License, Version 2.0 (the "License"); you may not use -// this file except in compliance with the License. You may obtain a copy of the -// License at http://www.apache.org/licenses/LICENSE-2.0 -// Unless required by applicable law or agreed to in writing, software distributed -// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR -// CONDITIONS OF ANY KIND, either express or implied. See the License for the -// specific language governing permissions and limitations under the License. - -#![allow(dead_code)] - -extern crate combine; -#[macro_use] -extern crate error_chain; - -extern crate edn; -extern crate mentat_tx; - -#[macro_use] -extern crate mentat_parser_utils; - -use combine::{ - choice, - eof, - many, - parser, - satisfy, - satisfy_map, - try, - Parser, - ParseResult, -}; -use mentat_tx::entities::{ - AtomOrLookupRefOrVectorOrMapNotation, - Entid, - EntidOrLookupRefOrTempId, - Entity, - LookupRef, - MapNotation, - OpType, - TempId, - TxFunction, -}; -use mentat_parser_utils::{ResultParser}; -use mentat_parser_utils::value_and_span::{ - Item, - OfExactlyParsing, - backward_keyword, - forward_keyword, - integer, - list, - map, - namespaced_keyword, - vector, -}; - -pub mod errors; -pub use errors::*; - -pub struct Tx<'a>(std::marker::PhantomData<&'a ()>); - -// Accepts entid, :attribute/forward, and :attribute/_backward. -def_parser!(Tx, entid, Entid, { - integer() - .map(|x| Entid::Entid(x)) - .or(namespaced_keyword().map(|x| Entid::Ident(x.clone()))) -}); - -// Accepts entid and :attribute/forward. -def_parser!(Tx, forward_entid, Entid, { - integer() - .map(|x| Entid::Entid(x)) - .or(forward_keyword().map(|x| Entid::Ident(x.clone()))) -}); - -// Accepts only :attribute/_backward. -def_parser!(Tx, backward_entid, Entid, { - backward_keyword().map(|x| Entid::Ident(x.to_reversed())) -}); - -def_matches_plain_symbol!(Tx, literal_lookup_ref, "lookup-ref"); - -def_parser!(Tx, lookup_ref, LookupRef, { - list().of_exactly( - Tx::literal_lookup_ref() - .with((Tx::entid(), - Tx::atom())) - .map(|(a, v)| LookupRef { a: a, v: v.clone().without_spans() })) -}); - -def_parser!(Tx, entid_or_lookup_ref_or_temp_id, EntidOrLookupRefOrTempId, { - Tx::temp_id().map(EntidOrLookupRefOrTempId::TempId) - .or(Tx::entid().map(EntidOrLookupRefOrTempId::Entid)) - .or(try(Tx::lookup_ref().map(EntidOrLookupRefOrTempId::LookupRef))) - .or(try(Tx::tx_function().map(EntidOrLookupRefOrTempId::TxFunction))) -}); - -def_matches_plain_symbol!(Tx, literal_transaction_tx, "transaction-tx"); - -def_parser!(Tx, tx_function, TxFunction, { - list().of_exactly( - Tx::literal_transaction_tx().map(|_| edn::PlainSymbol::new("transaction-tx")) - .map(|op| TxFunction { op: op })) -}); - -def_parser!(Tx, temp_id, TempId, { - satisfy_map(|x: &'a edn::ValueAndSpan| x.as_text().cloned().map(TempId::External)) -}); - -def_parser!(Tx, atom, &'a edn::ValueAndSpan, { - satisfy_map(|x: &'a edn::ValueAndSpan| x.as_atom()) -}); - -def_parser!(Tx, nested_vector, Vec, { - vector().of_exactly(many(Tx::atom_or_lookup_ref_or_vector())) -}); - -def_parser!(Tx, atom_or_lookup_ref_or_vector, AtomOrLookupRefOrVectorOrMapNotation, { - choice::<[&mut Parser; 5], _> - ([&mut try(Tx::lookup_ref().map(AtomOrLookupRefOrVectorOrMapNotation::LookupRef)), - &mut try(Tx::tx_function().map(AtomOrLookupRefOrVectorOrMapNotation::TxFunction)), - &mut Tx::nested_vector().map(AtomOrLookupRefOrVectorOrMapNotation::Vector), - &mut Tx::map_notation().map(AtomOrLookupRefOrVectorOrMapNotation::MapNotation), - &mut Tx::atom().map(|x| x.clone()).map(AtomOrLookupRefOrVectorOrMapNotation::Atom) - ]) -}); - -def_matches_namespaced_keyword!(Tx, literal_db_add, "db", "add"); - -def_matches_namespaced_keyword!(Tx, literal_db_retract, "db", "retract"); - -def_parser!(Tx, add_or_retract, Entity, { - vector().of_exactly( - // TODO: This commits as soon as it sees :db/{add,retract}, but could use an improved error message. - (Tx::literal_db_add().map(|_| OpType::Add).or(Tx::literal_db_retract().map(|_| OpType::Retract)), - try((Tx::entid_or_lookup_ref_or_temp_id(), - Tx::forward_entid(), - Tx::atom_or_lookup_ref_or_vector())) - .or(try((Tx::atom_or_lookup_ref_or_vector(), - Tx::backward_entid(), - Tx::entid_or_lookup_ref_or_temp_id())) - .map(|(v, a, e)| (e, a, v)))) - .map(|(op, (e, a, v))| { - Entity::AddOrRetract { - op: op, - e: e, - a: a, - v: v, - } - })) -}); - -def_parser!(Tx, map_notation, MapNotation, { - map() - .of_exactly(many((Tx::entid(), Tx::atom_or_lookup_ref_or_vector()))) - .map(|avs: Vec<(Entid, AtomOrLookupRefOrVectorOrMapNotation)>| -> MapNotation { - avs.into_iter().collect() - }) -}); - -def_parser!(Tx, entity, Entity, { - Tx::add_or_retract() - .or(Tx::map_notation().map(Entity::MapNotation)) -}); - -def_parser!(Tx, entities, Vec, { - vector().of_exactly(many(Tx::entity())) -}); - -impl<'a> Tx<'a> { - pub fn parse(input: &'a edn::ValueAndSpan) -> std::result::Result, errors::Error> { - Tx::entities() - .skip(eof()) - .parse(input.atom_stream()) - .map(|x| x.0) - .map_err(|e| Error::from_kind(ErrorKind::ParseError(e.into()))) - } - - pub fn parse_entid_or_lookup_ref_or_temp_id(input: edn::ValueAndSpan) -> std::result::Result { - Tx::entid_or_lookup_ref_or_temp_id() - .skip(eof()) - .parse(input.atom_stream()) - .map(|x| x.0) - .map_err(|e| Error::from_kind(ErrorKind::ParseError(e.into()))) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - use std::collections::BTreeMap; - - use combine::Parser; - use edn::{ - NamespacedKeyword, - PlainSymbol, - Span, - SpannedValue, - Value, - ValueAndSpan, - }; - use mentat_tx::entities::{ - Entid, - EntidOrLookupRefOrTempId, - Entity, - OpType, - AtomOrLookupRefOrVectorOrMapNotation, - }; - - fn kw(namespace: &str, name: &str) -> Value { - Value::NamespacedKeyword(NamespacedKeyword::new(namespace, name)) - } - - #[test] - fn test_add() { - let input = Value::Vector(vec![kw("db", "add"), - kw("test", "entid"), - kw("test", "a"), - Value::Text("v".into())]); - - let input = input.with_spans(); - let stream = input.atom_stream(); - let result = Tx::entity().parse(stream).map(|x| x.0); - - assert_eq!(result, - Ok(Entity::AddOrRetract { - op: OpType::Add, - e: EntidOrLookupRefOrTempId::Entid(Entid::Ident(NamespacedKeyword::new("test", - "entid"))), - a: Entid::Ident(NamespacedKeyword::new("test", "a")), - v: AtomOrLookupRefOrVectorOrMapNotation::Atom(ValueAndSpan::new(SpannedValue::Text("v".into()), Span(29, 32))), - })); - } - - #[test] - fn test_retract() { - let input = Value::Vector(vec![kw("db", "retract"), - Value::Integer(101), - kw("test", "a"), - Value::Text("v".into())]); - - let input = input.with_spans(); - let stream = input.atom_stream(); - let result = Tx::entity().parse(stream).map(|x| x.0); - - assert_eq!(result, - Ok(Entity::AddOrRetract { - op: OpType::Retract, - e: EntidOrLookupRefOrTempId::Entid(Entid::Entid(101)), - a: Entid::Ident(NamespacedKeyword::new("test", "a")), - v: AtomOrLookupRefOrVectorOrMapNotation::Atom(ValueAndSpan::new(SpannedValue::Text("v".into()), Span(25, 28))), - })); - } - - #[test] - fn test_lookup_ref() { - let input = Value::Vector(vec![kw("db", "add"), - Value::List(vec![Value::PlainSymbol(PlainSymbol::new("lookup-ref")), - kw("test", "a1"), - Value::Text("v1".into())].into_iter().collect()), - kw("test", "a"), - Value::Text("v".into())]); - - let input = input.with_spans(); - let stream = input.atom_stream(); - let result = Tx::entity().parse(stream).map(|x| x.0); - - assert_eq!(result, - Ok(Entity::AddOrRetract { - op: OpType::Add, - e: EntidOrLookupRefOrTempId::LookupRef(LookupRef { - a: Entid::Ident(NamespacedKeyword::new("test", "a1")), - v: Value::Text("v1".into()), - }), - a: Entid::Ident(NamespacedKeyword::new("test", "a")), - v: AtomOrLookupRefOrVectorOrMapNotation::Atom(ValueAndSpan::new(SpannedValue::Text("v".into()), Span(44, 47))), - })); - } - - #[test] - fn test_nested_vector() { - let input = Value::Vector(vec![kw("db", "add"), - Value::List(vec![Value::PlainSymbol(PlainSymbol::new("lookup-ref")), - kw("test", "a1"), - Value::Text("v1".into())].into_iter().collect()), - kw("test", "a"), - Value::Vector(vec![Value::Text("v1".into()), Value::Text("v2".into())])]); - - let input = input.with_spans(); - let stream = input.atom_stream(); - let result = Tx::entity().parse(stream).map(|x| x.0); - - assert_eq!(result, - Ok(Entity::AddOrRetract { - op: OpType::Add, - e: EntidOrLookupRefOrTempId::LookupRef(LookupRef { - a: Entid::Ident(NamespacedKeyword::new("test", "a1")), - v: Value::Text("v1".into()), - }), - a: Entid::Ident(NamespacedKeyword::new("test", "a")), - v: AtomOrLookupRefOrVectorOrMapNotation::Vector(vec![AtomOrLookupRefOrVectorOrMapNotation::Atom(ValueAndSpan::new(SpannedValue::Text("v1".into()), Span(45, 49))), - AtomOrLookupRefOrVectorOrMapNotation::Atom(ValueAndSpan::new(SpannedValue::Text("v2".into()), Span(50, 54)))]), - })); - } - - #[test] - fn test_map_notation() { - let mut expected: MapNotation = BTreeMap::default(); - expected.insert(Entid::Ident(NamespacedKeyword::new("db", "id")), AtomOrLookupRefOrVectorOrMapNotation::Atom(ValueAndSpan::new(SpannedValue::Text("t".to_string()), Span(8, 11)))); - expected.insert(Entid::Ident(NamespacedKeyword::new("db", "ident")), AtomOrLookupRefOrVectorOrMapNotation::Atom(ValueAndSpan::new(SpannedValue::NamespacedKeyword(NamespacedKeyword::new("test", "attribute")), Span(22, 37)))); - - let mut map: BTreeMap = BTreeMap::default(); - map.insert(kw("db", "id"), Value::Text("t".to_string())); - map.insert(kw("db", "ident"), kw("test", "attribute")); - let input = Value::Map(map.clone()); - - let input = input.with_spans(); - let stream = input.atom_stream(); - let result = Tx::entity().parse(stream).map(|x| x.0); - - assert_eq!(result, - Ok(Entity::MapNotation(expected))); - } -} diff --git a/tx-parser/tests/parser.rs b/tx-parser/tests/parser.rs deleted file mode 100644 index 8602487d..00000000 --- a/tx-parser/tests/parser.rs +++ /dev/null @@ -1,106 +0,0 @@ -// Copyright 2016 Mozilla -// -// Licensed under the Apache License, Version 2.0 (the "License"); you may not use -// this file except in compliance with the License. You may obtain a copy of the -// License at http://www.apache.org/licenses/LICENSE-2.0 -// Unless required by applicable law or agreed to in writing, software distributed -// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR -// 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; -extern crate combine; -extern crate mentat_tx; -extern crate mentat_tx_parser; - -use edn::parse; -use edn::symbols::NamespacedKeyword; -use mentat_tx::entities::{ - AtomOrLookupRefOrVectorOrMapNotation, - Entid, - EntidOrLookupRefOrTempId, - Entity, - OpType, - TempId, -}; -use mentat_tx_parser::Tx; - -#[test] -fn test_float_and_uuid() { - let expected_uuid = edn::Uuid::parse_str("267bab92-ee39-4ca2-b7f0-1163a85af1fb").expect("valid uuid"); - let input = r#" -[[:db/add 101 :test/a #uuid "267bab92-ee39-4ca2-b7f0-1163a85af1fb"] - [:db/add 102 :test/b #f NaN]] - "#; - let edn = parse::value(input).expect("to parse test input"); - - let result = Tx::parse(&edn); - assert_eq!(result.unwrap(), - vec![ - Entity::AddOrRetract { - op: OpType::Add, - e: EntidOrLookupRefOrTempId::Entid(Entid::Entid(101)), - a: Entid::Ident(NamespacedKeyword::new("test", "a")), - v: AtomOrLookupRefOrVectorOrMapNotation::Atom(edn::ValueAndSpan::new(edn::SpannedValue::Uuid(expected_uuid), edn::Span(23, 67))), - }, - Entity::AddOrRetract { - op: OpType::Add, - e: EntidOrLookupRefOrTempId::Entid(Entid::Entid(102)), - a: Entid::Ident(NamespacedKeyword::new("test", "b")), - v: AtomOrLookupRefOrVectorOrMapNotation::Atom(edn::ValueAndSpan::new(edn::SpannedValue::Float(edn::OrderedFloat(std::f64::NAN)), edn::Span(91, 97))), - }, - ]); -} - -#[test] -fn test_entities() { - let input = r#" -[[:db/add 101 :test/a "v"] - [:db/add "tempid" :test/a "v"] - [:db/retract 102 :test/b "w"]]"#; - - let edn = parse::value(input).expect("to parse test input"); - - let result = Tx::parse(&edn); - assert_eq!(result.unwrap(), - vec![ - Entity::AddOrRetract { - op: OpType::Add, - e: EntidOrLookupRefOrTempId::Entid(Entid::Entid(101)), - a: Entid::Ident(NamespacedKeyword::new("test", "a")), - v: AtomOrLookupRefOrVectorOrMapNotation::Atom(edn::ValueAndSpan::new(edn::SpannedValue::Text("v".into()), edn::Span(23, 26))), - }, - Entity::AddOrRetract { - op: OpType::Add, - e: EntidOrLookupRefOrTempId::TempId(TempId::External("tempid".into())), - a: Entid::Ident(NamespacedKeyword::new("test", "a")), - v: AtomOrLookupRefOrVectorOrMapNotation::Atom(edn::ValueAndSpan::new(edn::SpannedValue::Text("v".into()), edn::Span(55, 58))), - }, - Entity::AddOrRetract { - op: OpType::Retract, - e: EntidOrLookupRefOrTempId::Entid(Entid::Entid(102)), - a: Entid::Ident(NamespacedKeyword::new("test", "b")), - v: AtomOrLookupRefOrVectorOrMapNotation::Atom(edn::ValueAndSpan::new(edn::SpannedValue::Text("w".into()), edn::Span(86, 89))), - }, - ]); -} - -#[test] -fn test_reverse_notation_illegal_nested_values() { - // Verify that we refuse to parse direct reverse notation with nested value maps or vectors. - - let input = "[[:db/add 100 :test/_dangling {:test/many 13}]]"; - let edn = parse::value(input).expect("to parse test input"); - let result = Tx::parse(&edn); - // TODO: it would be much better to assert details about the error (here and below), but right - // now the error message isn't clear that the given value isn't valid for the backward attribute - // :test/_dangling. - assert!(result.is_err()); - - let input = "[[:db/add 100 :test/_dangling [:test/many 13]]]"; - let edn = parse::value(input).expect("to parse test input"); - let result = Tx::parse(&edn); - assert!(result.is_err()); -} - -// TODO: test error handling in select cases.