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:
parent
e437944d94
commit
cbffe5e545
21 changed files with 297 additions and 70 deletions
|
@ -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`
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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 }
|
||||||
|
|
|
@ -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>),
|
|
@ -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;
|
||||||
|
|
|
@ -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)
|
||||||
|
|
20
src/conn.rs
20
src/conn.rs
|
@ -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),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
81
tx-parser/benches.txt
Normal 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)
|
|
@ -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
17
tx-parser/benches2.txt
Normal 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
|
|
@ -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;
|
||||||
|
|
Loading…
Reference in a new issue