2018-01-23 16:43:26 +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.
|
|
|
|
|
2018-02-01 17:17:07 +00:00
|
|
|
#![macro_use]
|
|
|
|
|
2018-01-23 16:43:26 +00:00
|
|
|
// We have a little bit of a dilemma in Mentat.
|
|
|
|
// The public data format for transacting is, fundamentally, a big string: EDN.
|
|
|
|
// The internal data format for transacting is required to encode the complexities of
|
|
|
|
// processing that format: temporary IDs, lookup refs, input spans, etc.
|
|
|
|
//
|
2018-05-31 22:10:49 +00:00
|
|
|
// See edn::entities::Entity and all of its child enums to see how complex this gets.
|
2018-01-23 16:43:26 +00:00
|
|
|
//
|
|
|
|
// A programmatic consumer doesn't want to build something that looks like:
|
|
|
|
//
|
|
|
|
// Entity::AddOrRetract {
|
|
|
|
// op: OpType::Add,
|
|
|
|
// e: EntidOrLookupRefOrTempId::LookupRef(LookupRef {
|
2018-05-11 16:52:17 +00:00
|
|
|
// a: Entid::Ident(Keyword::namespaced("test", "a1")),
|
2018-01-23 16:43:26 +00:00
|
|
|
// v: Value::Text("v1".into()),
|
|
|
|
// }),
|
2018-02-01 17:17:07 +00:00
|
|
|
// a: Entid::Ident(kw!(:test/a)),
|
2018-01-23 16:43:26 +00:00
|
|
|
// v: AtomOrLookupRefOrVectorOrMapNotation::Atom(ValueAndSpan::new(SpannedValue::Text("v".into()), Span(44, 47))),
|
|
|
|
// }));
|
|
|
|
//
|
|
|
|
// but neither do they want to pay the cost of parsing
|
|
|
|
//
|
|
|
|
// [[:test/a1 "v1"] :test/a "v"]
|
|
|
|
//
|
|
|
|
// at runtime.
|
|
|
|
//
|
|
|
|
// It's tempting to think that we can do something 'easy' here -- to skip the hard work of transacting
|
|
|
|
// tempids, for example -- but to do so will hobble the system for little payoff. It's also worth
|
|
|
|
// remembering that the transactor does significant validation work, which we don't want to
|
|
|
|
// reimplement here.
|
|
|
|
//
|
|
|
|
// The win we seek is to make it easier to _write_ these inputs without significantly restricting
|
|
|
|
// what can be said.
|
|
|
|
//
|
|
|
|
// There are two ways we could go from here.
|
|
|
|
//
|
|
|
|
// The first is to expose tx parsing as a macro: parse that string at compile time into the
|
|
|
|
// equivalent `Entity` data structure. That's fine for completely static input data.
|
|
|
|
//
|
|
|
|
// The second is to expose a declarative, programmatic builder pattern for constructing entities.
|
|
|
|
//
|
2018-07-03 19:45:02 +00:00
|
|
|
// We probably need both, but this file provides the latter.
|
2018-01-23 16:43:26 +00:00
|
|
|
|
2018-07-02 16:22:27 +00:00
|
|
|
use edn::{
|
|
|
|
InternSet,
|
2018-07-03 20:52:02 +00:00
|
|
|
PlainSymbol,
|
2018-07-03 19:45:02 +00:00
|
|
|
ValueRc,
|
2018-07-02 16:22:27 +00:00
|
|
|
};
|
|
|
|
use edn::entities::{
|
2018-07-03 19:45:02 +00:00
|
|
|
AttributePlace,
|
|
|
|
Entity,
|
|
|
|
EntityPlace,
|
2018-07-03 20:52:02 +00:00
|
|
|
LookupRef,
|
2018-07-02 16:22:27 +00:00
|
|
|
OpType,
|
|
|
|
TempId,
|
2018-07-03 20:52:02 +00:00
|
|
|
TxFunction,
|
2018-07-03 19:45:02 +00:00
|
|
|
ValuePlace,
|
2018-07-02 16:22:27 +00:00
|
|
|
};
|
|
|
|
|
2018-01-23 16:43:26 +00:00
|
|
|
use mentat_core::{
|
|
|
|
TxReport,
|
2018-07-03 20:18:02 +00:00
|
|
|
TypedValue,
|
2018-01-23 16:43:26 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
use conn::{
|
|
|
|
InProgress,
|
|
|
|
};
|
|
|
|
|
|
|
|
use errors::{
|
|
|
|
Result,
|
|
|
|
};
|
|
|
|
|
2018-07-03 19:45:02 +00:00
|
|
|
pub type Terms = (Vec<Entity<TypedValue>>, InternSet<TempId>);
|
2018-01-23 16:43:26 +00:00
|
|
|
|
|
|
|
pub struct TermBuilder {
|
|
|
|
tempids: InternSet<TempId>,
|
2018-07-03 19:45:02 +00:00
|
|
|
terms: Vec<Entity<TypedValue>>,
|
2018-01-23 16:43:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pub struct EntityBuilder<T: BuildTerms + Sized> {
|
|
|
|
builder: T,
|
2018-07-03 19:45:02 +00:00
|
|
|
entity: EntityPlace<TypedValue>,
|
2018-01-23 16:43:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pub trait BuildTerms where Self: Sized {
|
2018-07-03 20:52:02 +00:00
|
|
|
fn named_tempid<I>(&mut self, name: I) -> ValueRc<TempId> where I: Into<String>;
|
2018-01-23 16:43:26 +00:00
|
|
|
fn describe_tempid(self, name: &str) -> EntityBuilder<Self>;
|
2018-07-03 19:45:02 +00:00
|
|
|
fn describe<E>(self, entity: E) -> EntityBuilder<Self> where E: Into<EntityPlace<TypedValue>>;
|
|
|
|
fn add<E, A, V>(&mut self, e: E, a: A, v: V) -> Result<()>
|
|
|
|
where E: Into<EntityPlace<TypedValue>>,
|
|
|
|
A: Into<AttributePlace>,
|
|
|
|
V: Into<ValuePlace<TypedValue>>;
|
|
|
|
fn retract<E, A, V>(&mut self, e: E, a: A, v: V) -> Result<()>
|
|
|
|
where E: Into<EntityPlace<TypedValue>>,
|
|
|
|
A: Into<AttributePlace>,
|
|
|
|
V: Into<ValuePlace<TypedValue>>;
|
2018-01-23 16:43:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl BuildTerms for TermBuilder {
|
2018-07-03 20:52:02 +00:00
|
|
|
fn named_tempid<I>(&mut self, name: I) -> ValueRc<TempId> where I: Into<String> {
|
|
|
|
self.tempids.intern(TempId::External(name.into()))
|
2018-02-15 16:22:34 +00:00
|
|
|
}
|
|
|
|
|
2018-01-23 16:43:26 +00:00
|
|
|
fn describe_tempid(mut self, name: &str) -> EntityBuilder<Self> {
|
2018-07-03 20:52:02 +00:00
|
|
|
let e = self.named_tempid(name);
|
2018-01-23 16:43:26 +00:00
|
|
|
self.describe(e)
|
|
|
|
}
|
|
|
|
|
2018-07-03 19:45:02 +00:00
|
|
|
fn describe<E>(self, entity: E) -> EntityBuilder<Self> where E: Into<EntityPlace<TypedValue>> {
|
2018-01-23 16:43:26 +00:00
|
|
|
EntityBuilder {
|
|
|
|
builder: self,
|
2018-07-03 19:45:02 +00:00
|
|
|
entity: entity.into(),
|
2018-01-23 16:43:26 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-07-03 19:45:02 +00:00
|
|
|
fn add<E, A, V>(&mut self, e: E, a: A, v: V) -> Result<()>
|
|
|
|
where E: Into<EntityPlace<TypedValue>>,
|
|
|
|
A: Into<AttributePlace>,
|
|
|
|
V: Into<ValuePlace<TypedValue>> {
|
|
|
|
self.terms.push(Entity::AddOrRetract { op: OpType::Add, e: e.into(), a: a.into(), v: v.into() });
|
2018-01-23 16:43:26 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2018-07-03 19:45:02 +00:00
|
|
|
fn retract<E, A, V>(&mut self, e: E, a: A, v: V) -> Result<()>
|
|
|
|
where E: Into<EntityPlace<TypedValue>>,
|
|
|
|
A: Into<AttributePlace>,
|
|
|
|
V: Into<ValuePlace<TypedValue>> {
|
|
|
|
self.terms.push(Entity::AddOrRetract { op: OpType::Retract, e: e.into(), a: a.into(), v: v.into() });
|
2018-01-23 16:43:26 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl TermBuilder {
|
|
|
|
pub fn build(self) -> Result<Terms> {
|
|
|
|
Ok((self.terms, self.tempids))
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn new() -> TermBuilder {
|
|
|
|
TermBuilder {
|
|
|
|
tempids: InternSet::new(),
|
|
|
|
terms: vec![],
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-04-04 02:22:10 +00:00
|
|
|
pub fn is_empty(&self) -> bool {
|
|
|
|
self.terms.is_empty()
|
|
|
|
}
|
|
|
|
|
2018-01-23 16:43:26 +00:00
|
|
|
#[allow(dead_code)]
|
2018-07-03 19:45:02 +00:00
|
|
|
pub fn numbered_tempid(&mut self, id: i64) -> ValueRc<TempId> {
|
2018-01-23 16:43:26 +00:00
|
|
|
self.tempids.intern(TempId::Internal(id))
|
|
|
|
}
|
2018-07-03 20:52:02 +00:00
|
|
|
|
|
|
|
pub fn lookup_ref<A, V>(a: A, v: V) -> LookupRef<TypedValue>
|
|
|
|
where A: Into<AttributePlace>,
|
|
|
|
V: Into<TypedValue> {
|
|
|
|
LookupRef { a: a.into(), v: v.into() }
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn tx_function(op: &str) -> TxFunction {
|
|
|
|
TxFunction { op: PlainSymbol::plain(op) }
|
|
|
|
}
|
2018-01-23 16:43:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl<T> EntityBuilder<T> where T: BuildTerms {
|
2018-07-03 19:45:02 +00:00
|
|
|
pub fn finish(self) -> (T, EntityPlace<TypedValue>) {
|
2018-01-23 16:43:26 +00:00
|
|
|
(self.builder, self.entity)
|
|
|
|
}
|
|
|
|
|
2018-07-03 19:45:02 +00:00
|
|
|
pub fn add<A, V>(&mut self, a: A, v: V) -> Result<()>
|
|
|
|
where A: Into<AttributePlace>,
|
|
|
|
V: Into<ValuePlace<TypedValue>> {
|
2018-01-23 16:43:26 +00:00
|
|
|
self.builder.add(self.entity.clone(), a, v)
|
|
|
|
}
|
2018-02-15 17:44:06 +00:00
|
|
|
|
2018-07-03 19:45:02 +00:00
|
|
|
pub fn retract<A, V>(&mut self, a: A, v: V) -> Result<()>
|
|
|
|
where A: Into<AttributePlace>,
|
|
|
|
V: Into<ValuePlace<TypedValue>> {
|
2018-02-15 17:44:06 +00:00
|
|
|
self.builder.retract(self.entity.clone(), a, v)
|
|
|
|
}
|
2018-01-23 16:43:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pub struct InProgressBuilder<'a, 'c> {
|
|
|
|
in_progress: InProgress<'a, 'c>,
|
|
|
|
builder: TermBuilder,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'a, 'c> InProgressBuilder<'a, 'c> {
|
|
|
|
pub fn new(in_progress: InProgress<'a, 'c>) -> Self {
|
|
|
|
InProgressBuilder {
|
|
|
|
in_progress: in_progress,
|
|
|
|
builder: TermBuilder::new(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Build the terms from this builder and transact them against the current
|
|
|
|
/// `InProgress`. This method _always_ returns the `InProgress` -- failure doesn't
|
|
|
|
/// imply an automatic rollback.
|
|
|
|
pub fn transact(self) -> (InProgress<'a, 'c>, Result<TxReport>) {
|
|
|
|
let mut in_progress = self.in_progress;
|
|
|
|
let result = self.builder
|
|
|
|
.build()
|
2018-07-03 19:45:02 +00:00
|
|
|
.and_then(|(terms, _tempid_set)| {
|
|
|
|
in_progress.transact_entities(terms)
|
2018-01-23 16:43:26 +00:00
|
|
|
});
|
|
|
|
(in_progress, result)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Transact the contents of the builder and commit the `InProgress`. If any
|
|
|
|
/// step fails, roll back. Return the `TxReport`.
|
|
|
|
pub fn commit(self) -> Result<TxReport> {
|
|
|
|
let mut in_progress = self.in_progress;
|
2018-04-04 02:22:10 +00:00
|
|
|
in_progress.transact_builder(self.builder)
|
|
|
|
.and_then(|report| {
|
|
|
|
in_progress.commit()?;
|
|
|
|
Ok(report)
|
|
|
|
})
|
2018-01-23 16:43:26 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'a, 'c> BuildTerms for InProgressBuilder<'a, 'c> {
|
2018-07-03 20:52:02 +00:00
|
|
|
fn named_tempid<I>(&mut self, name: I) -> ValueRc<TempId> where I: Into<String> {
|
2018-02-15 16:22:34 +00:00
|
|
|
self.builder.named_tempid(name)
|
|
|
|
}
|
|
|
|
|
2018-01-23 16:43:26 +00:00
|
|
|
fn describe_tempid(mut self, name: &str) -> EntityBuilder<InProgressBuilder<'a, 'c>> {
|
2018-07-03 20:52:02 +00:00
|
|
|
let e = self.builder.named_tempid(name.to_string());
|
2018-01-23 16:43:26 +00:00
|
|
|
self.describe(e)
|
|
|
|
}
|
|
|
|
|
2018-07-03 19:45:02 +00:00
|
|
|
fn describe<E>(self, entity: E) -> EntityBuilder<InProgressBuilder<'a, 'c>> where E: Into<EntityPlace<TypedValue>> {
|
2018-01-23 16:43:26 +00:00
|
|
|
EntityBuilder {
|
|
|
|
builder: self,
|
2018-07-03 19:45:02 +00:00
|
|
|
entity: entity.into(),
|
2018-01-23 16:43:26 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-07-03 19:45:02 +00:00
|
|
|
fn add<E, A, V>(&mut self, e: E, a: A, v: V) -> Result<()>
|
|
|
|
where E: Into<EntityPlace<TypedValue>>,
|
|
|
|
A: Into<AttributePlace>,
|
|
|
|
V: Into<ValuePlace<TypedValue>> {
|
2018-01-23 16:43:26 +00:00
|
|
|
self.builder.add(e, a, v)
|
|
|
|
}
|
|
|
|
|
2018-07-03 19:45:02 +00:00
|
|
|
fn retract<E, A, V>(&mut self, e: E, a: A, v: V) -> Result<()>
|
|
|
|
where E: Into<EntityPlace<TypedValue>>,
|
|
|
|
A: Into<AttributePlace>,
|
|
|
|
V: Into<ValuePlace<TypedValue>> {
|
2018-01-23 16:43:26 +00:00
|
|
|
self.builder.retract(e, a, v)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'a, 'c> EntityBuilder<InProgressBuilder<'a, 'c>> {
|
|
|
|
/// Build the terms from this builder and transact them against the current
|
|
|
|
/// `InProgress`. This method _always_ returns the `InProgress` -- failure doesn't
|
|
|
|
/// imply an automatic rollback.
|
|
|
|
pub fn transact(self) -> (InProgress<'a, 'c>, Result<TxReport>) {
|
|
|
|
self.finish().0.transact()
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Transact the contents of the builder and commit the `InProgress`. If any
|
|
|
|
/// step fails, roll back. Return the `TxReport`.
|
|
|
|
pub fn commit(self) -> Result<TxReport> {
|
|
|
|
self.finish().0.commit()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod testing {
|
|
|
|
extern crate mentat_db;
|
|
|
|
|
|
|
|
use ::{
|
|
|
|
Conn,
|
2018-02-01 17:17:07 +00:00
|
|
|
Entid,
|
|
|
|
HasSchema,
|
2018-07-03 19:45:02 +00:00
|
|
|
KnownEntid,
|
|
|
|
MentatError,
|
2018-01-23 16:43:26 +00:00
|
|
|
Queryable,
|
2018-02-01 17:17:07 +00:00
|
|
|
TxReport,
|
2018-07-03 20:18:02 +00:00
|
|
|
TypedValue,
|
2018-01-23 16:43:26 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
// In reality we expect the store to hand these out safely.
|
|
|
|
fn fake_known_entid(e: Entid) -> KnownEntid {
|
|
|
|
KnownEntid(e)
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_entity_builder_bogus_entids() {
|
|
|
|
let mut builder = TermBuilder::new();
|
2018-07-03 20:52:02 +00:00
|
|
|
let e = builder.named_tempid("x");
|
2018-01-23 16:43:26 +00:00
|
|
|
let a1 = fake_known_entid(37); // :db/doc
|
|
|
|
let a2 = fake_known_entid(999);
|
|
|
|
let v = TypedValue::typed_string("Some attribute");
|
|
|
|
let ve = fake_known_entid(12345);
|
|
|
|
|
|
|
|
builder.add(e.clone(), a1, v).expect("add succeeded");
|
|
|
|
builder.add(e.clone(), a2, e.clone()).expect("add succeeded, even though it's meaningless");
|
|
|
|
builder.add(e.clone(), a2, ve).expect("add succeeded, even though it's meaningless");
|
|
|
|
let (terms, tempids) = builder.build().expect("build succeeded");
|
|
|
|
|
|
|
|
assert_eq!(tempids.len(), 1);
|
|
|
|
assert_eq!(terms.len(), 3); // TODO: check the contents?
|
|
|
|
|
|
|
|
// Now try to add them to a real store.
|
|
|
|
let mut sqlite = mentat_db::db::new_connection("").unwrap();
|
|
|
|
let mut conn = Conn::connect(&mut sqlite).unwrap();
|
|
|
|
let mut in_progress = conn.begin_transaction(&mut sqlite).expect("begun successfully");
|
|
|
|
|
|
|
|
// This should fail: unrecognized entid.
|
2018-07-03 19:45:02 +00:00
|
|
|
match in_progress.transact_entities(terms).expect_err("expected transact to fail") {
|
2018-06-27 20:19:40 +00:00
|
|
|
MentatError::DbError(e) => {
|
2018-06-27 00:17:01 +00:00
|
|
|
assert_eq!(e.kind(), mentat_db::DbErrorKind::UnrecognizedEntid(999));
|
|
|
|
},
|
|
|
|
_ => panic!("Should have rejected the entid."),
|
2018-01-23 16:43:26 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-02-15 16:22:34 +00:00
|
|
|
#[test]
|
|
|
|
fn test_in_progress_builder() {
|
|
|
|
let mut sqlite = mentat_db::db::new_connection("").unwrap();
|
|
|
|
let mut conn = Conn::connect(&mut sqlite).unwrap();
|
|
|
|
|
|
|
|
// Give ourselves a schema to work with!
|
|
|
|
conn.transact(&mut sqlite, r#"[
|
|
|
|
[:db/add "o" :db/ident :foo/one]
|
|
|
|
[:db/add "o" :db/valueType :db.type/long]
|
|
|
|
[:db/add "o" :db/cardinality :db.cardinality/one]
|
|
|
|
[:db/add "m" :db/ident :foo/many]
|
|
|
|
[:db/add "m" :db/valueType :db.type/string]
|
|
|
|
[:db/add "m" :db/cardinality :db.cardinality/many]
|
|
|
|
[:db/add "r" :db/ident :foo/ref]
|
|
|
|
[:db/add "r" :db/valueType :db.type/ref]
|
|
|
|
[:db/add "r" :db/cardinality :db.cardinality/one]
|
|
|
|
]"#).unwrap();
|
|
|
|
|
|
|
|
let in_progress = conn.begin_transaction(&mut sqlite).expect("begun successfully");
|
|
|
|
|
|
|
|
// We can use this or not!
|
|
|
|
let a_many = in_progress.get_entid(&kw!(:foo/many)).expect(":foo/many");
|
|
|
|
|
|
|
|
let mut builder = in_progress.builder();
|
2018-07-03 20:52:02 +00:00
|
|
|
let e_x = builder.named_tempid("x");
|
2018-02-15 16:22:34 +00:00
|
|
|
let v_many_1 = TypedValue::typed_string("Some text");
|
|
|
|
let v_many_2 = TypedValue::typed_string("Other text");
|
2018-07-03 19:45:02 +00:00
|
|
|
builder.add(e_x.clone(), kw!(:foo/many), v_many_1).expect("add succeeded");
|
2018-02-15 16:22:34 +00:00
|
|
|
builder.add(e_x.clone(), a_many, v_many_2).expect("add succeeded");
|
|
|
|
builder.commit().expect("commit succeeded");
|
|
|
|
}
|
|
|
|
|
2018-01-23 16:43:26 +00:00
|
|
|
#[test]
|
|
|
|
fn test_entity_builder() {
|
|
|
|
let mut sqlite = mentat_db::db::new_connection("").unwrap();
|
|
|
|
let mut conn = Conn::connect(&mut sqlite).unwrap();
|
|
|
|
|
2018-02-01 17:17:07 +00:00
|
|
|
let foo_one = kw!(:foo/one);
|
|
|
|
let foo_many = kw!(:foo/many);
|
|
|
|
let foo_ref = kw!(:foo/ref);
|
2018-01-23 16:43:26 +00:00
|
|
|
let report: TxReport;
|
|
|
|
|
|
|
|
// Give ourselves a schema to work with!
|
|
|
|
// Scoped borrow of conn.
|
|
|
|
{
|
|
|
|
conn.transact(&mut sqlite, r#"[
|
|
|
|
[:db/add "o" :db/ident :foo/one]
|
|
|
|
[:db/add "o" :db/valueType :db.type/long]
|
|
|
|
[:db/add "o" :db/cardinality :db.cardinality/one]
|
|
|
|
[:db/add "m" :db/ident :foo/many]
|
|
|
|
[:db/add "m" :db/valueType :db.type/string]
|
|
|
|
[:db/add "m" :db/cardinality :db.cardinality/many]
|
|
|
|
[:db/add "r" :db/ident :foo/ref]
|
|
|
|
[:db/add "r" :db/valueType :db.type/ref]
|
|
|
|
[:db/add "r" :db/cardinality :db.cardinality/one]
|
|
|
|
]"#).unwrap();
|
|
|
|
|
|
|
|
let mut in_progress = conn.begin_transaction(&mut sqlite).expect("begun successfully");
|
|
|
|
|
|
|
|
// Scoped borrow of in_progress.
|
|
|
|
{
|
|
|
|
let mut builder = TermBuilder::new();
|
2018-07-03 20:52:02 +00:00
|
|
|
let e_x = builder.named_tempid("x");
|
|
|
|
let e_y = builder.named_tempid("y");
|
2018-01-23 16:43:26 +00:00
|
|
|
let a_ref = in_progress.get_entid(&foo_ref).expect(":foo/ref");
|
|
|
|
let a_one = in_progress.get_entid(&foo_one).expect(":foo/one");
|
|
|
|
let a_many = in_progress.get_entid(&foo_many).expect(":foo/many");
|
|
|
|
let v_many_1 = TypedValue::typed_string("Some text");
|
|
|
|
let v_many_2 = TypedValue::typed_string("Other text");
|
|
|
|
let v_long: TypedValue = 123.into();
|
|
|
|
|
|
|
|
builder.add(e_x.clone(), a_many, v_many_1).expect("add succeeded");
|
|
|
|
builder.add(e_x.clone(), a_many, v_many_2).expect("add succeeded");
|
|
|
|
builder.add(e_y.clone(), a_ref, e_x.clone()).expect("add succeeded");
|
|
|
|
builder.add(e_x.clone(), a_one, v_long).expect("add succeeded");
|
|
|
|
|
|
|
|
let (terms, tempids) = builder.build().expect("build succeeded");
|
|
|
|
|
|
|
|
assert_eq!(tempids.len(), 2);
|
|
|
|
assert_eq!(terms.len(), 4);
|
|
|
|
|
2018-07-03 19:45:02 +00:00
|
|
|
report = in_progress.transact_entities(terms).expect("add succeeded");
|
2018-01-23 16:43:26 +00:00
|
|
|
let x = report.tempids.get("x").expect("our tempid has an ID");
|
|
|
|
let y = report.tempids.get("y").expect("our tempid has an ID");
|
|
|
|
assert_eq!(in_progress.lookup_value_for_attribute(*y, &foo_ref).expect("lookup succeeded"),
|
|
|
|
Some(TypedValue::Ref(*x)));
|
|
|
|
assert_eq!(in_progress.lookup_value_for_attribute(*x, &foo_one).expect("lookup succeeded"),
|
|
|
|
Some(TypedValue::Long(123)));
|
|
|
|
}
|
|
|
|
|
|
|
|
in_progress.commit().expect("commit succeeded");
|
|
|
|
}
|
|
|
|
|
|
|
|
// It's all still there after the commit.
|
|
|
|
let x = report.tempids.get("x").expect("our tempid has an ID");
|
|
|
|
let y = report.tempids.get("y").expect("our tempid has an ID");
|
|
|
|
assert_eq!(conn.lookup_value_for_attribute(&mut sqlite, *y, &foo_ref).expect("lookup succeeded"),
|
|
|
|
Some(TypedValue::Ref(*x)));
|
|
|
|
}
|
|
|
|
}
|