2017-01-13 00:08:29 +00:00
|
|
|
// 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;
|
Lookup refs, nested vector values, map notation. Fixes #180, fixes #183, fixes #284. (#382) r=rnewman
* Pre: Fix error in parser macros.
* Pre: Make test unwrapping more verbose.
* Pre: Make lookup refs be (lookup-ref a v) in the entity position.
This has the advantage of being explicit in all situations and
unambiguous at parse-time. This choice agrees with the Clojure
implementation but not with Datomic. Datomic treats [a v] as a lookup
ref, is ambiguous at parse-time, and is disambiguated in ways I do not
understand at transaction time. We mooted making lookup refs [[a v]]
and outlawing nested value vectors in transactions, but after
implementing that approach I decided it was better to handle lookup
refs at parse time and therefore outlawing nested value vectors is not
necessary.
* Handle lookup refs in the entity and value columns. Fixes #183.
* Pre 0a: Use a stack instead of into_iter.
* Pre 0b: Dedent.
* Pre 0c: Handle `e` after `v`.
This allows to use the original `e` while handling `v`.
* Explode value lists for :db.cardinality/many attributes. Fixes #284.
* Parse and accept map notation. Fixes #180.
* Pre: Modernize add() and retract() into one add_or_retract().
* Pre: Add is_collection and is_atom to edn::Value.
* Pre: Differentiate atoms from lookup-refs in value position.
Initially, I expected to accept arbitrary edn::Value instances in the
value position, and to differentiate in the transactor. However, the
implementation quickly became a two-stage parser, since we always
wanted to parse the resulting value position into some other known
thing using the tx-parser. To save calls into the parser and to allow
the parser to move forward with a smaller API surface, I push as much
of this parsing as possible into the initial parse.
* Pre: Modernize entities().
* Pre: Quote edn::Value::Text in Display.
* Review comment: Add and use edn::Value::into_atom.
* Review comment: Use skip(eof()) throughout.
* Review comment: VecDeque instead of Vec.
* Review comment: Part 0: Rename TempId to TempIdHandle.
* Review comment: Part 1: Differentiate internal and external tempids.
This breaks an abstraction boundary by pushing the Internal/External
split up to the Entity level in tx/ and tx-parser/. This just makes
it easier to explode Entity map notation instances into Entity
instances, taking an existing External tempid :db/id or generating a
new Internal tempid as appropriate. To do this without breaking the
abstraction boundary would require adding flexibility to the
transaction processor: we'd need to be able to turn Entity instances
into some internal enum and handle the two cases independently. It
wouldn't be too hard, but this reduces the combinatorial type
explosion.
2017-03-27 23:30:04 +00:00
|
|
|
use mentat_tx::entities::{
|
|
|
|
AtomOrLookupRefOrVectorOrMapNotation,
|
|
|
|
Entid,
|
|
|
|
EntidOrLookupRefOrTempId,
|
|
|
|
Entity,
|
|
|
|
OpType,
|
|
|
|
TempId,
|
|
|
|
};
|
2017-01-13 00:08:29 +00:00
|
|
|
use mentat_tx_parser::Tx;
|
|
|
|
|
2017-04-29 03:11:55 +00:00
|
|
|
#[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");
|
|
|
|
|
2017-05-04 20:40:41 +00:00
|
|
|
let result = Tx::parse(&edn);
|
2017-04-29 03:11:55 +00:00
|
|
|
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))),
|
|
|
|
},
|
|
|
|
]);
|
|
|
|
}
|
|
|
|
|
2017-01-13 00:08:29 +00:00
|
|
|
#[test]
|
|
|
|
fn test_entities() {
|
2017-02-15 00:50:40 +00:00
|
|
|
let input = r#"
|
|
|
|
[[:db/add 101 :test/a "v"]
|
|
|
|
[:db/add "tempid" :test/a "v"]
|
|
|
|
[:db/retract 102 :test/b "w"]]"#;
|
2017-01-13 00:08:29 +00:00
|
|
|
|
2017-04-06 17:06:28 +00:00
|
|
|
let edn = parse::value(input).expect("to parse test input");
|
2017-01-13 00:08:29 +00:00
|
|
|
|
2017-05-04 20:40:41 +00:00
|
|
|
let result = Tx::parse(&edn);
|
Extract partial storage abstraction; use error-chain throughout. Fixes #328. r=rnewman (#341)
* Pre: Drop unneeded tx0 from search results.
* Pre: Don't require a schema in some of the DB code.
The idea is to separate the transaction applying code, which is
schema-aware, from the concrete storage code, which is just concerned
with getting bits onto disk.
* Pre: Only reference Schema, not DB, in debug module.
This is part of a larger separation of the volatile PartitionMap,
which is modified every transaction, from the stable Schema, which is
infrequently modified.
* Pre: Fix indentation.
* Extract part of DB to new SchemaTypeChecking trait.
* Extract part of DB to new PartitionMapping trait.
* Pre: Don't expect :db.part/tx partition to advance when tx fails.
This fails right now, because we allocate tx IDs even when we shouldn't.
* Sketch a db interface without DB.
* Add ValueParseError; use error-chain in tx-parser.
This can be simplified when
https://github.com/Marwes/combine/issues/86 makes it to a published
release, but this unblocks us for now. This converts the `combine`
error type `ParseError<&'a [edn::Value]>` to a type with owned
`Vec<edn::Value>` collections, re-using `edn::Value::Vector` for
making them `Display`.
* Pre: Accept Borrow<Schema> instead of just &Schema in debug module.
This makes it easy to use Rc<Schema> or Arc<Schema> without inserting
&* sigils throughout the code.
* Use error-chain in query-parser.
There are a few things to point out here:
- the fine grained error types have been flattened into one crate-wide
error type; it's pretty easy to regain the granularity as needed.
- edn::ParseError is automatically lifted to
mentat_query_parser::errors::Error;
- we use mentat_parser_utils::ValueParser to maintain parsing error
information from `combine`.
* Patch up top-level.
* Review comment: Only `borrow()` once.
2017-02-24 23:32:41 +00:00
|
|
|
assert_eq!(result.unwrap(),
|
|
|
|
vec![
|
2017-02-08 23:45:09 +00:00
|
|
|
Entity::AddOrRetract {
|
2017-02-15 00:50:40 +00:00
|
|
|
op: OpType::Add,
|
|
|
|
e: EntidOrLookupRefOrTempId::Entid(Entid::Entid(101)),
|
2017-01-13 00:08:29 +00:00
|
|
|
a: Entid::Ident(NamespacedKeyword::new("test", "a")),
|
2017-04-06 17:06:28 +00:00
|
|
|
v: AtomOrLookupRefOrVectorOrMapNotation::Atom(edn::ValueAndSpan::new(edn::SpannedValue::Text("v".into()), edn::Span(23, 26))),
|
2017-02-15 00:50:40 +00:00
|
|
|
},
|
|
|
|
Entity::AddOrRetract {
|
2017-02-08 23:45:09 +00:00
|
|
|
op: OpType::Add,
|
Lookup refs, nested vector values, map notation. Fixes #180, fixes #183, fixes #284. (#382) r=rnewman
* Pre: Fix error in parser macros.
* Pre: Make test unwrapping more verbose.
* Pre: Make lookup refs be (lookup-ref a v) in the entity position.
This has the advantage of being explicit in all situations and
unambiguous at parse-time. This choice agrees with the Clojure
implementation but not with Datomic. Datomic treats [a v] as a lookup
ref, is ambiguous at parse-time, and is disambiguated in ways I do not
understand at transaction time. We mooted making lookup refs [[a v]]
and outlawing nested value vectors in transactions, but after
implementing that approach I decided it was better to handle lookup
refs at parse time and therefore outlawing nested value vectors is not
necessary.
* Handle lookup refs in the entity and value columns. Fixes #183.
* Pre 0a: Use a stack instead of into_iter.
* Pre 0b: Dedent.
* Pre 0c: Handle `e` after `v`.
This allows to use the original `e` while handling `v`.
* Explode value lists for :db.cardinality/many attributes. Fixes #284.
* Parse and accept map notation. Fixes #180.
* Pre: Modernize add() and retract() into one add_or_retract().
* Pre: Add is_collection and is_atom to edn::Value.
* Pre: Differentiate atoms from lookup-refs in value position.
Initially, I expected to accept arbitrary edn::Value instances in the
value position, and to differentiate in the transactor. However, the
implementation quickly became a two-stage parser, since we always
wanted to parse the resulting value position into some other known
thing using the tx-parser. To save calls into the parser and to allow
the parser to move forward with a smaller API surface, I push as much
of this parsing as possible into the initial parse.
* Pre: Modernize entities().
* Pre: Quote edn::Value::Text in Display.
* Review comment: Add and use edn::Value::into_atom.
* Review comment: Use skip(eof()) throughout.
* Review comment: VecDeque instead of Vec.
* Review comment: Part 0: Rename TempId to TempIdHandle.
* Review comment: Part 1: Differentiate internal and external tempids.
This breaks an abstraction boundary by pushing the Internal/External
split up to the Entity level in tx/ and tx-parser/. This just makes
it easier to explode Entity map notation instances into Entity
instances, taking an existing External tempid :db/id or generating a
new Internal tempid as appropriate. To do this without breaking the
abstraction boundary would require adding flexibility to the
transaction processor: we'd need to be able to turn Entity instances
into some internal enum and handle the two cases independently. It
wouldn't be too hard, but this reduces the combinatorial type
explosion.
2017-03-27 23:30:04 +00:00
|
|
|
e: EntidOrLookupRefOrTempId::TempId(TempId::External("tempid".into())),
|
2017-02-15 00:50:40 +00:00
|
|
|
a: Entid::Ident(NamespacedKeyword::new("test", "a")),
|
2017-04-06 17:06:28 +00:00
|
|
|
v: AtomOrLookupRefOrVectorOrMapNotation::Atom(edn::ValueAndSpan::new(edn::SpannedValue::Text("v".into()), edn::Span(55, 58))),
|
2017-01-13 00:08:29 +00:00
|
|
|
},
|
2017-02-08 23:45:09 +00:00
|
|
|
Entity::AddOrRetract {
|
|
|
|
op: OpType::Retract,
|
2017-02-15 00:50:40 +00:00
|
|
|
e: EntidOrLookupRefOrTempId::Entid(Entid::Entid(102)),
|
|
|
|
a: Entid::Ident(NamespacedKeyword::new("test", "b")),
|
2017-04-06 17:06:28 +00:00
|
|
|
v: AtomOrLookupRefOrVectorOrMapNotation::Atom(edn::ValueAndSpan::new(edn::SpannedValue::Text("w".into()), edn::Span(86, 89))),
|
2017-01-13 00:08:29 +00:00
|
|
|
},
|
Extract partial storage abstraction; use error-chain throughout. Fixes #328. r=rnewman (#341)
* Pre: Drop unneeded tx0 from search results.
* Pre: Don't require a schema in some of the DB code.
The idea is to separate the transaction applying code, which is
schema-aware, from the concrete storage code, which is just concerned
with getting bits onto disk.
* Pre: Only reference Schema, not DB, in debug module.
This is part of a larger separation of the volatile PartitionMap,
which is modified every transaction, from the stable Schema, which is
infrequently modified.
* Pre: Fix indentation.
* Extract part of DB to new SchemaTypeChecking trait.
* Extract part of DB to new PartitionMapping trait.
* Pre: Don't expect :db.part/tx partition to advance when tx fails.
This fails right now, because we allocate tx IDs even when we shouldn't.
* Sketch a db interface without DB.
* Add ValueParseError; use error-chain in tx-parser.
This can be simplified when
https://github.com/Marwes/combine/issues/86 makes it to a published
release, but this unblocks us for now. This converts the `combine`
error type `ParseError<&'a [edn::Value]>` to a type with owned
`Vec<edn::Value>` collections, re-using `edn::Value::Vector` for
making them `Display`.
* Pre: Accept Borrow<Schema> instead of just &Schema in debug module.
This makes it easy to use Rc<Schema> or Arc<Schema> without inserting
&* sigils throughout the code.
* Use error-chain in query-parser.
There are a few things to point out here:
- the fine grained error types have been flattened into one crate-wide
error type; it's pretty easy to regain the granularity as needed.
- edn::ParseError is automatically lifted to
mentat_query_parser::errors::Error;
- we use mentat_parser_utils::ValueParser to maintain parsing error
information from `combine`.
* Patch up top-level.
* Review comment: Only `borrow()` once.
2017-02-24 23:32:41 +00:00
|
|
|
]);
|
2017-01-13 00:08:29 +00:00
|
|
|
}
|
|
|
|
|
2017-06-07 21:17:56 +00:00
|
|
|
#[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());
|
|
|
|
}
|
|
|
|
|
2017-01-13 00:08:29 +00:00
|
|
|
// TODO: test error handling in select cases.
|