working with tests

This commit is contained in:
Grisha Kruglov 2018-02-01 14:31:27 -05:00
parent 4f243fbc32
commit 5c2c29bf26
2 changed files with 117 additions and 83 deletions

66
tests/tolstoy.rs Normal file
View file

@ -0,0 +1,66 @@
extern crate mentat;
extern crate mentat_core;
extern crate mentat_tolstoy;
use mentat::conn::Conn;
use mentat::new_connection;
use mentat_tolstoy::tx_client::{
Tx,
TxReader,
TxClient
};
use mentat_core::{
ValueType,
TypedValue
};
#[test]
fn test_reader() {
let mut c = new_connection("").expect("Couldn't open conn.");
let mut conn = Conn::connect(&mut c).expect("Couldn't open DB.");
let txes = TxClient::all(&c).expect("bootstrap transactions");
// Don't inspect the bootstrap, but we'd like to see it's there.
assert_eq!(1, txes.len());
assert_eq!(76, txes[0].parts.len());
let ids = conn.transact(&mut c, r#"[
[:db/add "s" :db/ident :foo/numba]
[:db/add "s" :db/valueType :db.type/long]
[:db/add "s" :db/cardinality :db.cardinality/one]
]"#).expect("successful transaction").tempids;
let numba_entity_id = ids.get("s").unwrap();
let txes = TxClient::all(&c).expect("got transactions");
// Expect to see one more transaction of three parts.
assert_eq!(2, txes.len());
assert_eq!(3, txes[1].parts.len());
println!("{:?}", txes[1]);
let ids = conn.transact(&mut c, r#"[
[:db/add "b" :foo/numba 123]
]"#).expect("successful transaction").tempids;
let asserted_e = ids.get("b").unwrap();
let txes = TxClient::all(&c).expect("got transactions");
// Expect to see a single part transactions
// TODO verify that tx itself looks sane
assert_eq!(3, txes.len());
assert_eq!(1, txes[2].parts.len());
// Inspect the transaction part.
let part = &txes[2].parts[0];
assert_eq!(asserted_e, &part.e);
assert_eq!(numba_entity_id, &part.a);
assert!(part.v.matches_type(ValueType::Long));
assert_eq!(TypedValue::Long(123), part.v);
assert_eq!(1, part.added);
// TODO retractions
}

View file

@ -8,29 +8,14 @@
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
// specific language governing permissions and limitations under the License.
// read all txs from the database
// return a list of structures that represent all we need to know about transactions
// so, what do we need here then?
// we need a "way in"!
// could just query the transactions database directly, read stuff in, and represent
// it as some data structure on the way out
// will need to weed out transactions as we work through the records
// -> and then associate with it the "chunks"
// "transaction" then is a meta-concept, it's a label for a collection of concrete changes
// perhaps mentat has useful primitives, but let's begin by just "doing the work"
use std::collections::HashMap;
use std::collections::hash_map::Entry;
use std::collections::BTreeMap;
use std::collections::btree_map::Entry;
use rusqlite;
use errors::{
Result
Result,
ErrorKind
};
use mentat_db::types::{
@ -43,103 +28,86 @@ use mentat_db::{
};
use mentat_core::{
DateTime,
Utc,
TypedValue
};
use edn::FromMicros;
#[derive(Debug)]
pub struct TxPart {
pub e: Entid,
pub a: i64,
pub v: TypedValue,
pub added: i32
}
#[derive(Debug)]
pub struct Tx {
pub tx: Entid,
pub tx_instant: TypedValue,
pub parts: Vec<TxPart>
}
struct RawTx {
e: Entid,
a: i64,
v: TypedValue,
tx: Entid,
added: i32
}
pub struct Tx {
tx: Entid,
tx_instant: DateTime<Utc>,
parts: Vec<TxPart>
pub trait TxReader {
fn all(sqlite: &rusqlite::Connection) -> Result<Vec<Tx>>;
}
trait TxReader {
fn txs(&self) -> Result<Vec<Tx>>;
}
struct TxClient {
conn: rusqlite::Connection
}
// TODO This needs to take a mentat connection, as we're making assumptions about
// what that connection will provide (a transactions table).
impl TxClient {
fn new(conn: rusqlite::Connection) -> Self {
TxClient {
conn: conn
}
}
}
pub struct TxClient {}
impl TxReader for TxClient {
fn txs(&self) -> Result<Vec<Tx>> {
let mut txes_by_tx = HashMap::new();
fn all(sqlite: &rusqlite::Connection) -> Result<Vec<Tx>> {
// Make sure a=txInstant rows are first, so that we process
// all transactions before we process any transaction parts.
let mut stmt = self.conn.prepare(
let mut stmt = sqlite.prepare(
"SELECT
e, a, v, tx, added, value_type_tag,
CASE a WHEN :txInstant THEN 1 ELSE 0 END is_transaction
FROM transactions ORDER BY is_transaction DESC"
FROM transactions ORDER BY is_transaction DESC, tx ASC"
)?;
let _ = stmt.query_and_then_named(&[(":txInstant", &entids::DB_TX_INSTANT)], |row| {
let e = row.get(0);
let a = row.get(1);
let v_instant: i64 = row.get(2); // TODO unify this and typed_value below
let tx = row.get(3);
let added = row.get(4);
let value_type_tag = row.get(5);
let raw_value: rusqlite::types::Value = row.get(2);
let typed_value = match TypedValue::from_sql_value_pair(raw_value, value_type_tag) {
Ok(v) => v,
Err(e) => return Err(e)
};
let rows: Vec<Result<RawTx>> = stmt.query_and_then_named(&[(":txInstant", &entids::DB_TX_INSTANT)], |row| -> Result<RawTx> {
Ok(RawTx {
e: row.get(0),
a: row.get(1),
v: TypedValue::from_sql_value_pair(row.get(2), row.get(5))?,
tx: row.get(3),
added: row.get(4)
})
})?.collect();
// It's convenient to have a consistently ordered set of results,
// so we use a sorting map.
let mut txes_by_tx = BTreeMap::new();
for row_result in rows {
let row = row_result?;
// Row represents a transaction.
if a == entids::DB_TX_INSTANT {
txes_by_tx.insert(tx, Tx {
tx: tx,
// TODO enforce correct type of v and return ErrorKind::BadSQLValuePair
// otherwise.
tx_instant: DateTime::<Utc>::from_micros(v_instant),
if row.a == entids::DB_TX_INSTANT {
txes_by_tx.insert(row.tx, Tx {
tx: row.tx,
tx_instant: row.v,
parts: Vec::new()
});
Ok(())
// Row represents part of a transaction. Our query statement above guarantees
// that we've already processed corresponding transaction at this point.
} else {
if let Entry::Occupied(mut t) = txes_by_tx.entry(tx) {
if let Entry::Occupied(mut t) = txes_by_tx.entry(row.tx) {
t.get_mut().parts.push(TxPart {
e: e,
a: a,
v: typed_value,
added: added
e: row.e,
a: row.a,
v: row.v,
added: row.added
});
Ok(())
} else {
// TODO not ok... ErrorKind::UnexpectedError
Ok(())
bail!(ErrorKind::UnexpectedState(format!("Encountered transaction part before transaction {:?}", row.tx)))
}
}
})?;
}
Ok(txes_by_tx.into_iter().map(|(_, tx)| tx).collect())
}
}
#[cfg(test)]
mod tests {
use super::*;
}