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.
This commit is contained in:
Nick Alexander 2018-05-07 10:32:28 -07:00
parent e437944d94
commit cbffe5e545
21 changed files with 297 additions and 70 deletions

View file

@ -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`). 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` #### `mentat_core`
This is the lowest-level Mentat crate. It collects together the following things: 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. 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 ### Query processing
#### `mentat_query_parser` #### `mentat_query_parser`

View file

@ -29,9 +29,6 @@ path = "../sql"
[dependencies.mentat_tx] [dependencies.mentat_tx]
path = "../tx" path = "../tx"
[dependencies.mentat_tx_parser]
path = "../tx-parser"
# Should be dev-dependencies. # Should be dev-dependencies.
[dependencies.tabwriter] [dependencies.tabwriter]
version = "1.0.3" version = "1.0.3"

View file

@ -17,7 +17,6 @@ use edn::symbols;
use entids; use entids;
use db::TypedSQLValue; use db::TypedSQLValue;
use mentat_tx::entities::Entity; use mentat_tx::entities::Entity;
use mentat_tx_parser;
use mentat_core::{ use mentat_core::{
IdentMap, IdentMap,
Schema, Schema,
@ -300,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> = mentat_tx_parser::Tx::parse(&bootstrap_assertions.with_spans()).unwrap(); let bootstrap_entities: Vec<Entity> = edn::parse::entities(&bootstrap_assertions.to_string()).unwrap();
return bootstrap_entities; return bootstrap_entities;
} }

View file

@ -1159,7 +1159,6 @@ mod tests {
Schema, Schema,
attribute, attribute,
}; };
use mentat_tx_parser;
use rusqlite; use rusqlite;
use std::collections::{ use std::collections::{
BTreeMap, BTreeMap,
@ -1217,8 +1216,7 @@ mod tests {
fn transact<I>(&mut self, transaction: I) -> Result<TxReport> where I: Borrow<str> { fn transact<I>(&mut self, transaction: I) -> Result<TxReport> where I: Borrow<str> {
// Failure to parse the transaction is a coding error, so we unwrap. // 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 = edn::parse::entities(transaction.borrow()).expect(format!("to be able to parse {} into entities", 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 details = { let details = {
// The block scopes the borrow of self.sqlite. // The block scopes the borrow of self.sqlite.

View file

@ -21,7 +21,6 @@ use rusqlite;
use mentat_tx::entities::{ use mentat_tx::entities::{
TempId, TempId,
}; };
use mentat_tx_parser;
use mentat_core::{ use mentat_core::{
KnownEntid, 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! { error_chain! {
types { types {
Error, ErrorKind, ResultExt, Result; Error, ErrorKind, ResultExt, Result;
@ -69,10 +92,6 @@ error_chain! {
Rusqlite(rusqlite::Error); Rusqlite(rusqlite::Error);
} }
links {
TxParseError(mentat_tx_parser::Error, mentat_tx_parser::ErrorKind);
}
errors { errors {
/// We're just not done yet. Message that the feature is recognized but not yet /// We're just not done yet. Message that the feature is recognized but not yet
/// implemented. /// implemented.
@ -152,5 +171,12 @@ error_chain! {
description("schema constraint violation") description("schema constraint violation")
display("schema constraint violation: {}", 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)
}
} }
} }

View file

@ -86,12 +86,12 @@ impl TransactableValue for ValueAndSpan {
(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() })), EntidOrLookupRefOrTempId::Entid(a) => Ok(EntidOrLookupRefOrTempId::LookupRef(entities::LookupRef { a, v: v.clone().without_spans() })),
EntidOrLookupRefOrTempId::TempId(_) => bail!(""), EntidOrLookupRefOrTempId::TempId(_) |
EntidOrLookupRefOrTempId::TxFunction(_) => bail!(""), EntidOrLookupRefOrTempId::TxFunction(_) |
EntidOrLookupRefOrTempId::LookupRef(_) => bail!(""), 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 | Nil |
@ -105,7 +105,7 @@ impl TransactableValue for ValueAndSpan {
Keyword(_) | Keyword(_) |
Vector(_) | Vector(_) |
Set(_) | Set(_) |
Map(_) => bail!(ErrorKind::NotYetImplemented("cannot convert value place into entity place".into())) Map(_) => bail!(ErrorKind::InputError(errors::InputError::BadEntityPlace)),
} }
} }

View file

@ -26,7 +26,6 @@ extern crate time;
#[macro_use] extern crate mentat_core; #[macro_use] extern crate mentat_core;
extern crate mentat_sql; extern crate mentat_sql;
extern crate mentat_tx; extern crate mentat_tx;
extern crate mentat_tx_parser;
use std::iter::repeat; use std::iter::repeat;

View file

@ -174,7 +174,7 @@ pub fn remove_db_id(map: &mut entmod::MapNotation) -> Result<Option<entmod::Enti
entmod::AtomOrLookupRefOrVectorOrMapNotation::TxFunction(_) | entmod::AtomOrLookupRefOrVectorOrMapNotation::TxFunction(_) |
entmod::AtomOrLookupRefOrVectorOrMapNotation::Vector(_) | entmod::AtomOrLookupRefOrVectorOrMapNotation::Vector(_) |
entmod::AtomOrLookupRefOrVectorOrMapNotation::MapNotation(_) => { entmod::AtomOrLookupRefOrVectorOrMapNotation::MapNotation(_) => {
bail!(ErrorKind::NotYetImplemented("db id error".into())) bail!(ErrorKind::InputError(errors::InputError::BadDbId))
}, },
} }
} else { } else {

View file

@ -23,6 +23,8 @@ use num::BigInt;
use ordered_float::OrderedFloat; use ordered_float::OrderedFloat;
use uuid::Uuid; use uuid::Uuid;
use entities::*;
use symbols::*;
use types::{SpannedValue, Span, ValueAndSpan}; use types::{SpannedValue, Span, ValueAndSpan};
// Goal: Be able to parse https://github.com/edn-format/edn // 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] hex = [0-9a-fA-F]
sign = [+-] sign = [+-]
pub bigint -> SpannedValue = b:$( sign? digit+ ) "N" pub raw_bigint -> BigInt = b:$( sign? digit+ ) "N"
{ SpannedValue::BigInteger(b.parse::<BigInt>().unwrap()) } { b.parse::<BigInt>().unwrap() }
pub octalinteger -> SpannedValue = "0" i:$( octaldigit+ ) pub raw_octalinteger -> i64 = "0" i:$( octaldigit+ )
{ SpannedValue::Integer(i64::from_str_radix(i, 8).unwrap()) } { i64::from_str_radix(i, 8).unwrap() }
pub hexinteger -> SpannedValue = "0x" i:$( hex+ ) pub raw_hexinteger -> i64 = "0x" i:$( hex+ )
{ SpannedValue::Integer(i64::from_str_radix(i, 16).unwrap()) } { i64::from_str_radix(i, 16).unwrap() }
pub raw_basedinteger -> i64 = b:$( validbase ) "r" i:$( alphanumeric+ )
{ i64::from_str_radix(i, b.parse::<u32>().unwrap()).unwrap() }
pub raw_integer -> i64 = i:$( sign? digit+ ) !("." / ([eE]))
{ i.parse::<i64>().unwrap() }
pub raw_float -> OrderedFloat<f64> = f:$(sign? digit+ ("." digit+)? ([eE] sign? digit+)?)
{ OrderedFloat(f.parse::<f64>().unwrap()) }
pub basedinteger -> SpannedValue = b:$( validbase ) "r" i:$( alphanumeric+ ) pub bigint -> SpannedValue = v:raw_bigint { SpannedValue::BigInteger(v) }
{ SpannedValue::Integer(i64::from_str_radix(i, b.parse::<u32>().unwrap()).unwrap()) } pub octalinteger -> SpannedValue = v:raw_octalinteger { SpannedValue::Integer(v) }
pub hexinteger -> SpannedValue = v:raw_hexinteger { SpannedValue::Integer(v) }
pub integer -> SpannedValue = i:$( sign? digit+ ) !("." / ([eE])) pub basedinteger -> SpannedValue = v:raw_basedinteger { SpannedValue::Integer(v) }
{ SpannedValue::Integer(i.parse::<i64>().unwrap()) } pub integer -> SpannedValue = v:raw_integer { SpannedValue::Integer(v) }
pub float -> SpannedValue = v:raw_float { SpannedValue::Float(v) }
pub float -> SpannedValue = f:$(sign? digit+ ("." digit+)? ([eE] sign? digit+)?)
{ SpannedValue::Float(OrderedFloat(f.parse::<f64>().unwrap())) }
number -> SpannedValue = ( bigint / basedinteger / hexinteger / octalinteger / integer / float ) number -> SpannedValue = ( bigint / basedinteger / hexinteger / octalinteger / integer / float )
@ -81,8 +87,11 @@ string_normal_chars -> &'input str = $([^"\\]+)
// output = [quote, "foo", backslash, "bar", quote] // output = [quote, "foo", backslash, "bar", quote]
// result = r#""foo\\bar""# // result = r#""foo\\bar""#
// For the typical case, string_normal_chars will match multiple, leading to a single-element vec. // 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)*) "\"" pub raw_text -> String = "\"" t:((string_special_char / string_normal_chars)*) "\""
{ SpannedValue::Text(t.join(&"").to_string()) } { t.join(&"").to_string() }
pub text -> SpannedValue
= v:raw_text { SpannedValue::Text(v) }
// RFC 3339 timestamps. #inst "1985-04-12T23:20:50.52Z" // RFC 3339 timestamps. #inst "1985-04-12T23:20:50.52Z"
// We accept an arbitrary depth of decimals. // 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 // 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}. // [1,,,,2] are equivalent, as are the maps {:a 1, :b 2} and {:a 1 :b 2}.
whitespace = [ \r\n\t,] whitespace = #quiet<[ \r\n\t,]>
comment = ";" [^\r\n]* [\r\n]? comment = #quiet<";" [^\r\n]* [\r\n]?>
__ = (whitespace / comment)* __ = (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<Entity>
= __ "[" __ es:(entity*) __ "]" __ { es }

View file

@ -10,14 +10,19 @@
//! This module defines core types that support the transaction processor. //! This module defines core types that support the transaction processor.
extern crate edn;
use std::collections::BTreeMap; use std::collections::BTreeMap;
use std::fmt; 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. /// or an internal tempid allocated by Mentat itself.
#[derive(Clone, Debug, Eq, Hash, Ord, PartialOrd, PartialEq)] #[derive(Clone, Debug, Eq, Hash, Ord, PartialOrd, PartialEq)]
pub enum TempId { pub enum TempId {
@ -63,7 +68,7 @@ pub struct LookupRef {
pub a: Entid, pub a: Entid,
// 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: edn::Value, // An atom. pub v: Value, // 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
@ -80,14 +85,14 @@ pub struct LookupRef {
/// generalization. /// generalization.
#[derive(Clone, Debug, Eq, Hash, Ord, PartialOrd, PartialEq)] #[derive(Clone, Debug, Eq, Hash, Ord, PartialOrd, PartialEq)]
pub struct TxFunction { pub struct TxFunction {
pub op: edn::PlainSymbol, pub op: PlainSymbol,
} }
pub type MapNotation = BTreeMap<Entid, AtomOrLookupRefOrVectorOrMapNotation>; pub type MapNotation = BTreeMap<Entid, AtomOrLookupRefOrVectorOrMapNotation>;
#[derive(Clone, Debug, Eq, Hash, Ord, PartialOrd, PartialEq)] #[derive(Clone, Debug, Eq, Hash, Ord, PartialOrd, PartialEq)]
pub enum AtomOrLookupRefOrVectorOrMapNotation { pub enum AtomOrLookupRefOrVectorOrMapNotation {
Atom(edn::ValueAndSpan), Atom(ValueAndSpan),
LookupRef(LookupRef), LookupRef(LookupRef),
TxFunction(TxFunction), TxFunction(TxFunction),
Vector(Vec<AtomOrLookupRefOrVectorOrMapNotation>), Vector(Vec<AtomOrLookupRefOrVectorOrMapNotation>),

View file

@ -22,6 +22,7 @@ extern crate serde;
#[macro_use] #[macro_use]
extern crate serde_derive; extern crate serde_derive;
pub mod entities;
pub mod symbols; pub mod symbols;
pub mod types; pub mod types;
pub mod pretty_print; pub mod pretty_print;

View file

@ -110,6 +110,10 @@ impl ValueAndSpan {
} }
} }
pub fn is_atom(&self) -> bool {
self.inner.is_atom()
}
pub fn as_atom(&self) -> Option<&ValueAndSpan> { pub fn as_atom(&self) -> Option<&ValueAndSpan> {
if self.inner.is_atom() { if self.inner.is_atom() {
Some(self) Some(self)

View file

@ -10,6 +10,10 @@
#![allow(dead_code)] #![allow(dead_code)]
use std::borrow::{
Borrow,
};
use std::collections::{ use std::collections::{
BTreeMap, BTreeMap,
}; };
@ -85,8 +89,6 @@ use mentat_tx::entities::{
OpType, OpType,
}; };
use mentat_tx_parser;
use mentat_tolstoy::Syncer; use mentat_tolstoy::Syncer;
use uuid::Uuid; use uuid::Uuid;
@ -494,9 +496,8 @@ impl<'a, 'c> InProgress<'a, 'c> {
Ok(report) Ok(report)
} }
pub fn transact(&mut self, transaction: &str) -> Result<TxReport> { pub fn transact<B>(&mut self, transaction: B) -> Result<TxReport> where B: Borrow<str> {
let assertion_vector = edn::parse::value(transaction)?; let entities = edn::parse::entities(transaction.borrow())?;
let entities = mentat_tx_parser::Tx::parse(&assertion_vector)?;
self.transact_entities(entities) self.transact_entities(entities)
} }
@ -912,15 +913,14 @@ impl Conn {
/// Transact entities against the Mentat store, using the given connection and the current /// Transact entities against the Mentat store, using the given connection and the current
/// metadata. /// metadata.
pub fn transact(&mut self, pub fn transact<B>(&mut self,
sqlite: &mut rusqlite::Connection, sqlite: &mut rusqlite::Connection,
transaction: &str) -> Result<TxReport> { transaction: B) -> Result<TxReport> where B: Borrow<str> {
// Parse outside the SQL transaction. This is a tradeoff: we are limiting the scope of the // 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 // 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 // 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. // 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 = edn::parse::entities(transaction.borrow())?;
let entities = mentat_tx_parser::Tx::parse(&assertion_vector)?;
let mut in_progress = self.begin_transaction(sqlite)?; let mut in_progress = self.begin_transaction(sqlite)?;
let report = in_progress.transact_entities(entities)?; let report = in_progress.transact_entities(entities)?;
@ -1248,7 +1248,7 @@ mod tests {
// Bad transaction data: missing leading :db/add. // Bad transaction data: missing leading :db/add.
let report = conn.transact(&mut sqlite, "[[\"t\" :db/ident :b/keyword]]"); let report = conn.transact(&mut sqlite, "[[\"t\" :db/ident :b/keyword]]");
match report.unwrap_err() { match report.unwrap_err() {
Error(ErrorKind::TxParseError(::mentat_tx_parser::errors::ErrorKind::ParseError(_)), _) => { }, Error(ErrorKind::EdnParseError(_), _) => { },
x => panic!("expected EDN parse error, got {:?}", x), x => panic!("expected EDN parse error, got {:?}", x),
} }

View file

@ -30,7 +30,6 @@ use mentat_query_pull;
use mentat_query_translator; use mentat_query_translator;
use mentat_sql; use mentat_sql;
use mentat_tolstoy; use mentat_tolstoy;
use mentat_tx_parser;
error_chain! { error_chain! {
types { types {
@ -52,7 +51,6 @@ error_chain! {
PullError(mentat_query_pull::errors::Error, mentat_query_pull::errors::ErrorKind); PullError(mentat_query_pull::errors::Error, mentat_query_pull::errors::ErrorKind);
TranslatorError(mentat_query_translator::Error, mentat_query_translator::ErrorKind); TranslatorError(mentat_query_translator::Error, mentat_query_translator::ErrorKind);
SqlError(mentat_sql::Error, mentat_sql::ErrorKind); SqlError(mentat_sql::Error, mentat_sql::ErrorKind);
TxParseError(mentat_tx_parser::Error, mentat_tx_parser::ErrorKind);
SyncError(mentat_tolstoy::Error, mentat_tolstoy::ErrorKind); SyncError(mentat_tolstoy::Error, mentat_tolstoy::ErrorKind);
} }

View file

@ -32,7 +32,6 @@ extern crate mentat_query_translator;
extern crate mentat_sql; extern crate mentat_sql;
extern crate mentat_tolstoy; extern crate mentat_tolstoy;
extern crate mentat_tx; extern crate mentat_tx;
extern crate mentat_tx_parser;
pub use mentat_core::{ pub use mentat_core::{
Attribute, Attribute,

View file

@ -742,7 +742,7 @@ fn test_type_reqs() {
{:db/ident :test/long2 :db/valueType :db.type/long :db/cardinality :db.cardinality/one} {:db/ident :test/long2 :db/valueType :db.type/long :db/cardinality :db.cardinality/one}
]"#).unwrap(); ]"#).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 ...] let longs_query = r#"[:find [?v ...]
:order (asc ?v) :order (asc ?v)
:in ?e :in ?e

View file

@ -511,7 +511,7 @@ impl Repl {
fn transact(&mut self, transaction: String) -> ::mentat::errors::Result<TxReport> { fn transact(&mut self, transaction: String) -> ::mentat::errors::Result<TxReport> {
let mut tx = self.store.begin_transaction()?; let mut tx = self.store.begin_transaction()?;
let report = tx.transact(&transaction)?; let report = tx.transact(transaction)?;
tx.commit()?; tx.commit()?;
Ok(report) Ok(report)
} }

81
tx-parser/benches.txt Normal file
View file

@ -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)

View file

@ -14,7 +14,7 @@ use mentat_tx_parser::Tx;
fn bench_parse1(b: &mut Bencher) { fn bench_parse1(b: &mut Bencher) {
let input = r#"[[:db/add 1 :test/val "a"]]"#; let input = r#"[[:db/add 1 :test/val "a"]]"#;
let parsed_edn = edn::parse::value(input).expect("to parse test input"); 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] #[bench]
@ -46,6 +46,42 @@ fn bench_parse2(b: &mut Bencher) {
[:db/add 24 :test/val "x"] [:db/add 24 :test/val "x"]
[:db/add 25 :test/val "y"] [:db/add 25 :test/val "y"]
[:db/add 26 :test/val "z"]]"#; [:db/add 26 :test/val "z"]]"#;
b.iter(|| {
let parsed_edn = edn::parse::value(input).expect("to parse test input"); let parsed_edn = edn::parse::value(input).expect("to parse test input");
b.iter(|| Tx::parse(parsed_edn.clone())); 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");
});
} }

17
tx-parser/benches2.txt Normal file
View file

@ -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

View file

@ -8,4 +8,6 @@
// CONDITIONS OF ANY KIND, either express or implied. See the License for the // CONDITIONS OF ANY KIND, either express or implied. See the License for the
// specific language governing permissions and limitations under the License. // specific language governing permissions and limitations under the License.
pub mod entities; #[allow(unused_imports)]
#[macro_use] extern crate edn;
pub use edn::entities;