Convert db/ to failure.

This commit is contained in:
Grisha Kruglov 2018-06-05 21:23:59 -04:00 committed by Nick Alexander
parent 0adfa6aae6
commit 31de5be64f
14 changed files with 326 additions and 262 deletions

View file

@ -6,6 +6,7 @@ workspace = ".."
[dependencies] [dependencies]
chrono = { version = "0.4", features = ["serde"] } chrono = { version = "0.4", features = ["serde"] }
enum-set = { git = "https://github.com/rnewman/enum-set" } enum-set = { git = "https://github.com/rnewman/enum-set" }
failure = "0.1.1"
indexmap = "1" indexmap = "1"
lazy_static = "0.2" lazy_static = "0.2"
num = "0.1" num = "0.1"

View file

@ -10,6 +10,8 @@
/// Cache traits. /// Cache traits.
use failure;
use std::collections::{ use std::collections::{
BTreeSet, BTreeSet,
}; };
@ -33,8 +35,7 @@ pub trait CachedAttributes {
fn get_entids_for_value(&self, attribute: Entid, value: &TypedValue) -> Option<&BTreeSet<Entid>>; fn get_entids_for_value(&self, attribute: Entid, value: &TypedValue) -> Option<&BTreeSet<Entid>>;
} }
pub trait UpdateableCache { pub trait UpdateableCache<Error=failure::Error> {
type Error; fn update<I>(&mut self, schema: &Schema, retractions: I, assertions: I) -> Result<(), Error>
fn update<I>(&mut self, schema: &Schema, retractions: I, assertions: I) -> Result<(), Self::Error>
where I: Iterator<Item=(Entid, Entid, TypedValue)>; where I: Iterator<Item=(Entid, Entid, TypedValue)>;
} }

View file

@ -10,6 +10,7 @@
extern crate chrono; extern crate chrono;
extern crate enum_set; extern crate enum_set;
extern crate failure;
extern crate indexmap; extern crate indexmap;
extern crate ordered_float; extern crate ordered_float;
extern crate uuid; extern crate uuid;

View file

@ -8,7 +8,8 @@ default = []
sqlcipher = ["rusqlite/sqlcipher"] sqlcipher = ["rusqlite/sqlcipher"]
[dependencies] [dependencies]
error-chain = { git = "https://github.com/rnewman/error-chain", branch = "rnewman/sync" } failure = "0.1.1"
failure_derive = "0.1.1"
indexmap = "1" indexmap = "1"
itertools = "0.7" itertools = "0.7"
lazy_static = "0.2" lazy_static = "0.2"

View file

@ -11,7 +11,10 @@
#![allow(dead_code)] #![allow(dead_code)]
use edn; use edn;
use errors::{ErrorKind, Result}; use errors::{
DbError,
Result,
};
use edn::types::Value; use edn::types::Value;
use edn::symbols; use edn::symbols;
use entids; use entids;
@ -156,7 +159,7 @@ lazy_static! {
:db/cardinality :db.cardinality/many}}"#; :db/cardinality :db.cardinality/many}}"#;
edn::parse::value(s) edn::parse::value(s)
.map(|v| v.without_spans()) .map(|v| v.without_spans())
.map_err(|_| ErrorKind::BadBootstrapDefinition("Unable to parse V1_SYMBOLIC_SCHEMA".into())) .map_err(|_| DbError::BadBootstrapDefinition("Unable to parse V1_SYMBOLIC_SCHEMA".into()))
.unwrap() .unwrap()
}; };
} }
@ -207,14 +210,14 @@ fn symbolic_schema_to_triples(ident_map: &IdentMap, symbolic_schema: &Value) ->
for (ident, mp) in m { for (ident, mp) in m {
let ident = match ident { let ident = match ident {
&Value::Keyword(ref ident) => ident, &Value::Keyword(ref ident) => ident,
_ => bail!(ErrorKind::BadBootstrapDefinition(format!("Expected namespaced keyword for ident but got '{:?}'", ident))), _ => bail!(DbError::BadBootstrapDefinition(format!("Expected namespaced keyword for ident but got '{:?}'", ident))),
}; };
match *mp { match *mp {
Value::Map(ref mpp) => { Value::Map(ref mpp) => {
for (attr, value) in mpp { for (attr, value) in mpp {
let attr = match attr { let attr = match attr {
&Value::Keyword(ref attr) => attr, &Value::Keyword(ref attr) => attr,
_ => bail!(ErrorKind::BadBootstrapDefinition(format!("Expected namespaced keyword for attr but got '{:?}'", attr))), _ => bail!(DbError::BadBootstrapDefinition(format!("Expected namespaced keyword for attr but got '{:?}'", attr))),
}; };
// We have symbolic idents but the transactor handles entids. Ad-hoc // We have symbolic idents but the transactor handles entids. Ad-hoc
@ -229,20 +232,20 @@ fn symbolic_schema_to_triples(ident_map: &IdentMap, symbolic_schema: &Value) ->
Some(TypedValue::Keyword(ref k)) => { Some(TypedValue::Keyword(ref k)) => {
ident_map.get(k) ident_map.get(k)
.map(|entid| TypedValue::Ref(*entid)) .map(|entid| TypedValue::Ref(*entid))
.ok_or(ErrorKind::UnrecognizedIdent(k.to_string()))? .ok_or(DbError::UnrecognizedIdent(k.to_string()))?
}, },
Some(v) => v, Some(v) => v,
_ => bail!(ErrorKind::BadBootstrapDefinition(format!("Expected Mentat typed value for value but got '{:?}'", value))) _ => bail!(DbError::BadBootstrapDefinition(format!("Expected Mentat typed value for value but got '{:?}'", value)))
}; };
triples.push((ident.clone(), attr.clone(), typed_value)); triples.push((ident.clone(), attr.clone(), typed_value));
} }
}, },
_ => bail!(ErrorKind::BadBootstrapDefinition("Expected {:db/ident {:db/attr value ...} ...}".into())) _ => bail!(DbError::BadBootstrapDefinition("Expected {:db/ident {:db/attr value ...} ...}".into()))
} }
} }
}, },
_ => bail!(ErrorKind::BadBootstrapDefinition("Expected {...}".into())) _ => bail!(DbError::BadBootstrapDefinition("Expected {...}".into()))
} }
Ok(triples) Ok(triples)
} }
@ -263,11 +266,11 @@ fn symbolic_schema_to_assertions(symbolic_schema: &Value) -> Result<Vec<Value>>
value.clone()])); value.clone()]));
} }
}, },
_ => bail!(ErrorKind::BadBootstrapDefinition("Expected {:db/ident {:db/attr value ...} ...}".into())) _ => bail!(DbError::BadBootstrapDefinition("Expected {:db/ident {:db/attr value ...} ...}".into()))
} }
} }
}, },
_ => bail!(ErrorKind::BadBootstrapDefinition("Expected {...}".into())) _ => bail!(DbError::BadBootstrapDefinition("Expected {...}".into()))
} }
Ok(assertions) Ok(assertions)
} }

View file

@ -106,7 +106,7 @@ use db::{
}; };
use errors::{ use errors::{
ErrorKind, DbError,
Result, Result,
}; };
@ -1155,15 +1155,14 @@ impl CachedAttributes for AttributeCaches {
} }
impl UpdateableCache for AttributeCaches { impl UpdateableCache for AttributeCaches {
type Error = ::errors::Error; fn update<I>(&mut self, schema: &Schema, retractions: I, assertions: I) -> Result<()>
fn update<I>(&mut self, schema: &Schema, retractions: I, assertions: I) -> ::std::result::Result<(), Self::Error>
where I: Iterator<Item=(Entid, Entid, TypedValue)> { where I: Iterator<Item=(Entid, Entid, TypedValue)> {
self.update_with_fallback(None, schema, retractions, assertions) self.update_with_fallback(None, schema, retractions, assertions)
} }
} }
impl AttributeCaches { impl AttributeCaches {
fn update_with_fallback<I>(&mut self, fallback: Option<&AttributeCaches>, schema: &Schema, retractions: I, assertions: I) -> ::std::result::Result<(), ::errors::Error> fn update_with_fallback<I>(&mut self, fallback: Option<&AttributeCaches>, schema: &Schema, retractions: I, assertions: I) -> Result<()>
where I: Iterator<Item=(Entid, Entid, TypedValue)> { where I: Iterator<Item=(Entid, Entid, TypedValue)> {
let r_aevs = retractions.peekable(); let r_aevs = retractions.peekable();
self.accumulate_into_cache(fallback, schema, r_aevs, AccumulationBehavior::Remove)?; self.accumulate_into_cache(fallback, schema, r_aevs, AccumulationBehavior::Remove)?;
@ -1237,7 +1236,7 @@ impl SQLiteAttributeCache {
let a = attribute.into(); let a = attribute.into();
// The attribute must exist! // The attribute must exist!
let _ = schema.attribute_for_entid(a).ok_or_else(|| ErrorKind::UnknownAttribute(a))?; let _ = schema.attribute_for_entid(a).ok_or_else(|| DbError::UnknownAttribute(a))?;
let caches = self.make_mut(); let caches = self.make_mut();
caches.forward_cached_attributes.insert(a); caches.forward_cached_attributes.insert(a);
caches.repopulate(schema, sqlite, a) caches.repopulate(schema, sqlite, a)
@ -1248,7 +1247,7 @@ impl SQLiteAttributeCache {
let a = attribute.into(); let a = attribute.into();
// The attribute must exist! // The attribute must exist!
let _ = schema.attribute_for_entid(a).ok_or_else(|| ErrorKind::UnknownAttribute(a))?; let _ = schema.attribute_for_entid(a).ok_or_else(|| DbError::UnknownAttribute(a))?;
let caches = self.make_mut(); let caches = self.make_mut();
caches.reverse_cached_attributes.insert(a); caches.reverse_cached_attributes.insert(a);
@ -1278,8 +1277,7 @@ impl SQLiteAttributeCache {
} }
impl UpdateableCache for SQLiteAttributeCache { impl UpdateableCache for SQLiteAttributeCache {
type Error = ::errors::Error; fn update<I>(&mut self, schema: &Schema, retractions: I, assertions: I) -> Result<()>
fn update<I>(&mut self, schema: &Schema, retractions: I, assertions: I) -> ::std::result::Result<(), Self::Error>
where I: Iterator<Item=(Entid, Entid, TypedValue)> { where I: Iterator<Item=(Entid, Entid, TypedValue)> {
self.make_mut().update(schema, retractions, assertions) self.make_mut().update(schema, retractions, assertions)
} }
@ -1356,7 +1354,7 @@ impl InProgressSQLiteAttributeCache {
let a = attribute.into(); let a = attribute.into();
// The attribute must exist! // The attribute must exist!
let _ = schema.attribute_for_entid(a).ok_or_else(|| ErrorKind::UnknownAttribute(a))?; let _ = schema.attribute_for_entid(a).ok_or_else(|| DbError::UnknownAttribute(a))?;
if self.is_attribute_cached_forward(a) { if self.is_attribute_cached_forward(a) {
return Ok(()); return Ok(());
@ -1372,7 +1370,7 @@ impl InProgressSQLiteAttributeCache {
let a = attribute.into(); let a = attribute.into();
// The attribute must exist! // The attribute must exist!
let _ = schema.attribute_for_entid(a).ok_or_else(|| ErrorKind::UnknownAttribute(a))?; let _ = schema.attribute_for_entid(a).ok_or_else(|| DbError::UnknownAttribute(a))?;
if self.is_attribute_cached_reverse(a) { if self.is_attribute_cached_reverse(a) {
return Ok(()); return Ok(());
@ -1388,7 +1386,7 @@ impl InProgressSQLiteAttributeCache {
let a = attribute.into(); let a = attribute.into();
// The attribute must exist! // The attribute must exist!
let _ = schema.attribute_for_entid(a).ok_or_else(|| ErrorKind::UnknownAttribute(a))?; let _ = schema.attribute_for_entid(a).ok_or_else(|| DbError::UnknownAttribute(a))?;
// TODO: reverse-index unique by default? // TODO: reverse-index unique by default?
let reverse_done = self.is_attribute_cached_reverse(a); let reverse_done = self.is_attribute_cached_reverse(a);
@ -1427,8 +1425,7 @@ impl InProgressSQLiteAttributeCache {
} }
impl UpdateableCache for InProgressSQLiteAttributeCache { impl UpdateableCache for InProgressSQLiteAttributeCache {
type Error = ::errors::Error; fn update<I>(&mut self, schema: &Schema, retractions: I, assertions: I) -> Result<()>
fn update<I>(&mut self, schema: &Schema, retractions: I, assertions: I) -> ::std::result::Result<(), Self::Error>
where I: Iterator<Item=(Entid, Entid, TypedValue)> { where I: Iterator<Item=(Entid, Entid, TypedValue)> {
self.overlay.update_with_fallback(Some(&self.inner), schema, retractions, assertions) self.overlay.update_with_fallback(Some(&self.inner), schema, retractions, assertions)
} }

View file

@ -10,6 +10,8 @@
#![allow(dead_code)] #![allow(dead_code)]
use failure::ResultExt;
use std::borrow::Borrow; use std::borrow::Borrow;
use std::collections::HashMap; use std::collections::HashMap;
use std::collections::hash_map::{ use std::collections::hash_map::{
@ -53,7 +55,11 @@ use mentat_core::{
ValueRc, ValueRc,
}; };
use errors::{ErrorKind, Result, ResultExt}; use errors::{
DbError,
Result,
DbSqlErrorKind,
};
use metadata; use metadata;
use schema::{ use schema::{
SchemaBuilding, SchemaBuilding,
@ -251,8 +257,8 @@ lazy_static! {
/// documentation](https://www.sqlite.org/pragma.html#pragma_user_version). /// documentation](https://www.sqlite.org/pragma.html#pragma_user_version).
fn set_user_version(conn: &rusqlite::Connection, version: i32) -> Result<()> { fn set_user_version(conn: &rusqlite::Connection, version: i32) -> Result<()> {
conn.execute(&format!("PRAGMA user_version = {}", version), &[]) conn.execute(&format!("PRAGMA user_version = {}", version), &[])
.chain_err(|| "Could not set_user_version") .context(DbSqlErrorKind::CouldNotSetVersionPragma)?;
.map(|_| ()) Ok(())
} }
/// Get the SQLite user version. /// Get the SQLite user version.
@ -260,10 +266,10 @@ fn set_user_version(conn: &rusqlite::Connection, version: i32) -> Result<()> {
/// Mentat manages its own SQL schema version using the user version. See the [SQLite /// Mentat manages its own SQL schema version using the user version. See the [SQLite
/// documentation](https://www.sqlite.org/pragma.html#pragma_user_version). /// documentation](https://www.sqlite.org/pragma.html#pragma_user_version).
fn get_user_version(conn: &rusqlite::Connection) -> Result<i32> { fn get_user_version(conn: &rusqlite::Connection) -> Result<i32> {
conn.query_row("PRAGMA user_version", &[], |row| { let v = conn.query_row("PRAGMA user_version", &[], |row| {
row.get(0) row.get(0)
}) }).context(DbSqlErrorKind::CouldNotGetVersionPragma)?;
.chain_err(|| "Could not get_user_version") Ok(v)
} }
/// Do just enough work that either `create_current_version` or sync can populate the DB. /// Do just enough work that either `create_current_version` or sync can populate the DB.
@ -303,8 +309,7 @@ pub fn create_current_version(conn: &mut rusqlite::Connection) -> Result<DB> {
// TODO: validate metadata mutations that aren't schema related, like additional partitions. // TODO: validate metadata mutations that aren't schema related, like additional partitions.
if let Some(next_schema) = next_schema { if let Some(next_schema) = next_schema {
if next_schema != db.schema { if next_schema != db.schema {
// TODO Use custom ErrorKind https://github.com/brson/error-chain/issues/117 bail!(DbError::NotYetImplemented(format!("Initial bootstrap transaction did not produce expected bootstrap schema")));
bail!(ErrorKind::NotYetImplemented(format!("Initial bootstrap transaction did not produce expected bootstrap schema")));
} }
} }
@ -326,7 +331,7 @@ pub fn ensure_current_version(conn: &mut rusqlite::Connection) -> Result<DB> {
CURRENT_VERSION => read_db(conn), CURRENT_VERSION => read_db(conn),
// TODO: support updating an existing store. // TODO: support updating an existing store.
v => bail!(ErrorKind::NotYetImplemented(format!("Opening databases with Mentat version: {}", v))), v => bail!(DbError::NotYetImplemented(format!("Opening databases with Mentat version: {}", v))),
} }
} }
@ -356,7 +361,7 @@ impl TypedSQLValue for TypedValue {
let u = Uuid::from_bytes(x.as_slice()); let u = Uuid::from_bytes(x.as_slice());
if u.is_err() { if u.is_err() {
// Rather than exposing Uuid's ParseError… // Rather than exposing Uuid's ParseError…
bail!(ErrorKind::BadSQLValuePair(rusqlite::types::Value::Blob(x), bail!(DbError::BadSQLValuePair(rusqlite::types::Value::Blob(x),
value_type_tag)); value_type_tag));
} }
Ok(TypedValue::Uuid(u.unwrap())) Ok(TypedValue::Uuid(u.unwrap()))
@ -364,7 +369,7 @@ impl TypedSQLValue for TypedValue {
(13, rusqlite::types::Value::Text(x)) => { (13, rusqlite::types::Value::Text(x)) => {
to_namespaced_keyword(&x).map(|k| k.into()) to_namespaced_keyword(&x).map(|k| k.into())
}, },
(_, value) => bail!(ErrorKind::BadSQLValuePair(value, value_type_tag)), (_, value) => bail!(DbError::BadSQLValuePair(value, value_type_tag)),
} }
} }
@ -447,12 +452,12 @@ fn read_ident_map(conn: &rusqlite::Connection) -> Result<IdentMap> {
let v = read_materialized_view(conn, "idents")?; let v = read_materialized_view(conn, "idents")?;
v.into_iter().map(|(e, a, typed_value)| { v.into_iter().map(|(e, a, typed_value)| {
if a != entids::DB_IDENT { if a != entids::DB_IDENT {
bail!(ErrorKind::NotYetImplemented(format!("bad idents materialized view: expected :db/ident but got {}", a))); bail!(DbError::NotYetImplemented(format!("bad idents materialized view: expected :db/ident but got {}", a)));
} }
if let TypedValue::Keyword(keyword) = typed_value { if let TypedValue::Keyword(keyword) = typed_value {
Ok((keyword.as_ref().clone(), e)) Ok((keyword.as_ref().clone(), e))
} else { } else {
bail!(ErrorKind::NotYetImplemented(format!("bad idents materialized view: expected [entid :db/ident keyword] but got [entid :db/ident {:?}]", typed_value))); bail!(DbError::NotYetImplemented(format!("bad idents materialized view: expected [entid :db/ident keyword] but got [entid :db/ident {:?}]", typed_value)));
} }
}).collect() }).collect()
} }
@ -546,9 +551,8 @@ fn search(conn: &rusqlite::Connection) -> Result<()> {
t.a0 = d.a"#; t.a0 = d.a"#;
let mut stmt = conn.prepare_cached(s)?; let mut stmt = conn.prepare_cached(s)?;
stmt.execute(&[]) stmt.execute(&[]).context(DbSqlErrorKind::CouldNotSearch)?;
.map(|_c| ()) Ok(())
.chain_err(|| "Could not search!")
} }
/// Insert the new transaction into the `transactions` table. /// Insert the new transaction into the `transactions` table.
@ -570,9 +574,7 @@ fn insert_transaction(conn: &rusqlite::Connection, tx: Entid) -> Result<()> {
WHERE added0 IS 1 AND ((rid IS NULL) OR ((rid IS NOT NULL) AND (v0 IS NOT v)))"#; WHERE added0 IS 1 AND ((rid IS NULL) OR ((rid IS NOT NULL) AND (v0 IS NOT v)))"#;
let mut stmt = conn.prepare_cached(s)?; let mut stmt = conn.prepare_cached(s)?;
stmt.execute(&[&tx]) stmt.execute(&[&tx]).context(DbSqlErrorKind::TxInsertFailedToAddMissingDatoms)?;
.map(|_c| ())
.chain_err(|| "Could not insert transaction: failed to add datoms not already present")?;
let s = r#" let s = r#"
INSERT INTO transactions (e, a, v, tx, added, value_type_tag) INSERT INTO transactions (e, a, v, tx, added, value_type_tag)
@ -583,9 +585,7 @@ fn insert_transaction(conn: &rusqlite::Connection, tx: Entid) -> Result<()> {
(added0 IS 1 AND search_type IS ':db.cardinality/one' AND v0 IS NOT v))"#; (added0 IS 1 AND search_type IS ':db.cardinality/one' AND v0 IS NOT v))"#;
let mut stmt = conn.prepare_cached(s)?; let mut stmt = conn.prepare_cached(s)?;
stmt.execute(&[&tx]) stmt.execute(&[&tx]).context(DbSqlErrorKind::TxInsertFailedToRetractDatoms)?;
.map(|_c| ())
.chain_err(|| "Could not insert transaction: failed to retract datoms already present")?;
Ok(()) Ok(())
} }
@ -607,9 +607,7 @@ fn update_datoms(conn: &rusqlite::Connection, tx: Entid) -> Result<()> {
DELETE FROM datoms WHERE rowid IN ids"#; DELETE FROM datoms WHERE rowid IN ids"#;
let mut stmt = conn.prepare_cached(s)?; let mut stmt = conn.prepare_cached(s)?;
stmt.execute(&[]) stmt.execute(&[]).context(DbSqlErrorKind::DatomsUpdateFailedToRetract)?;
.map(|_c| ())
.chain_err(|| "Could not update datoms: failed to retract datoms already present")?;
// Insert datoms that were added and not already present. We also must expand our bitfield into // Insert datoms that were added and not already present. We also must expand our bitfield into
// flags. Since Mentat follows Datomic and treats its input as a set, it is okay to transact // flags. Since Mentat follows Datomic and treats its input as a set, it is okay to transact
@ -632,10 +630,7 @@ fn update_datoms(conn: &rusqlite::Connection, tx: Entid) -> Result<()> {
AttributeBitFlags::UniqueValue as u8); AttributeBitFlags::UniqueValue as u8);
let mut stmt = conn.prepare_cached(&s)?; let mut stmt = conn.prepare_cached(&s)?;
stmt.execute(&[&tx]) stmt.execute(&[&tx]).context(DbSqlErrorKind::DatomsUpdateFailedToAdd)?;
.map(|_c| ())
.chain_err(|| "Could not update datoms: failed to add datoms not already present")?;
Ok(()) Ok(())
} }
@ -762,9 +757,7 @@ impl MentatStoring for rusqlite::Connection {
for statement in &statements { for statement in &statements {
let mut stmt = self.prepare_cached(statement)?; let mut stmt = self.prepare_cached(statement)?;
stmt.execute(&[]) stmt.execute(&[]).context(DbSqlErrorKind::FailedToCreateTempTables)?;
.map(|_c| ())
.chain_err(|| "Failed to create temporary tables")?;
} }
Ok(()) Ok(())
@ -827,8 +820,9 @@ impl MentatStoring for rusqlite::Connection {
// TODO: consider ensuring we inserted the expected number of rows. // TODO: consider ensuring we inserted the expected number of rows.
let mut stmt = self.prepare_cached(s.as_str())?; let mut stmt = self.prepare_cached(s.as_str())?;
stmt.execute(&params) stmt.execute(&params)
.context(DbSqlErrorKind::NonFtsInsertionIntoTempSearchTableFailed)
.map_err(|e| e.into())
.map(|_c| ()) .map(|_c| ())
.chain_err(|| "Could not insert non-fts one statements into temporary search table!")
}).collect::<Result<Vec<()>>>(); }).collect::<Result<Vec<()>>>();
results.map(|_| ()) results.map(|_| ())
@ -886,7 +880,7 @@ impl MentatStoring for rusqlite::Connection {
} }
}, },
_ => { _ => {
bail!("Cannot transact a fulltext assertion with a typed value that is not :db/valueType :db.type/string"); bail!(DbError::WrongTypeValueForFtsAssertion);
}, },
} }
@ -913,9 +907,7 @@ impl MentatStoring for rusqlite::Connection {
// TODO: consider ensuring we inserted the expected number of rows. // TODO: consider ensuring we inserted the expected number of rows.
let mut stmt = self.prepare_cached(fts_s.as_str())?; let mut stmt = self.prepare_cached(fts_s.as_str())?;
stmt.execute(&fts_params) stmt.execute(&fts_params).context(DbSqlErrorKind::FtsInsertionFailed)?;
.map(|_c| ())
.chain_err(|| "Could not insert fts values into fts table!")?;
// Second, insert searches. // Second, insert searches.
// `params` reference computed values in `block`. // `params` reference computed values in `block`.
@ -943,17 +935,14 @@ impl MentatStoring for rusqlite::Connection {
// TODO: consider ensuring we inserted the expected number of rows. // TODO: consider ensuring we inserted the expected number of rows.
let mut stmt = self.prepare_cached(s.as_str())?; let mut stmt = self.prepare_cached(s.as_str())?;
stmt.execute(&params) stmt.execute(&params).context(DbSqlErrorKind::FtsInsertionIntoTempSearchTableFailed)
.map_err(|e| e.into())
.map(|_c| ()) .map(|_c| ())
.chain_err(|| "Could not insert FTS statements into temporary search table!")
}).collect::<Result<Vec<()>>>(); }).collect::<Result<Vec<()>>>();
// Finally, clean up temporary searchids. // Finally, clean up temporary searchids.
let mut stmt = self.prepare_cached("UPDATE fulltext_values SET searchid = NULL WHERE searchid IS NOT NULL")?; let mut stmt = self.prepare_cached("UPDATE fulltext_values SET searchid = NULL WHERE searchid IS NOT NULL")?;
stmt.execute(&[]) stmt.execute(&[]).context(DbSqlErrorKind::FtsFailedToDropSearchIds)?;
.map(|_c| ())
.chain_err(|| "Could not drop FTS search ids!")?;
results.map(|_| ()) results.map(|_| ())
} }
@ -985,7 +974,7 @@ pub fn update_partition_map(conn: &rusqlite::Connection, partition_map: &Partiti
let max_vars = conn.limit(Limit::SQLITE_LIMIT_VARIABLE_NUMBER) as usize; let max_vars = conn.limit(Limit::SQLITE_LIMIT_VARIABLE_NUMBER) as usize;
let max_partitions = max_vars / values_per_statement; let max_partitions = max_vars / values_per_statement;
if partition_map.len() > max_partitions { if partition_map.len() > max_partitions {
bail!(ErrorKind::NotYetImplemented(format!("No more than {} partitions are supported", max_partitions))); bail!(DbError::NotYetImplemented(format!("No more than {} partitions are supported", max_partitions)));
} }
// Like "UPDATE parts SET idx = CASE WHEN part = ? THEN ? WHEN part = ? THEN ? ELSE idx END". // Like "UPDATE parts SET idx = CASE WHEN part = ? THEN ? WHEN part = ? THEN ? ELSE idx END".
@ -1001,9 +990,8 @@ pub fn update_partition_map(conn: &rusqlite::Connection, partition_map: &Partiti
// supported in the Clojure implementation at all, and might not be supported in Mentat soon, // supported in the Clojure implementation at all, and might not be supported in Mentat soon,
// so this is very low priority. // so this is very low priority.
let mut stmt = conn.prepare_cached(s.as_str())?; let mut stmt = conn.prepare_cached(s.as_str())?;
stmt.execute(&params[..]) stmt.execute(&params[..]).context(DbSqlErrorKind::FailedToUpdatePartitionMap)?;
.map(|_c| ()) Ok(())
.chain_err(|| "Could not update partition map")
} }
/// Update the metadata materialized views based on the given metadata report. /// Update the metadata materialized views based on the given metadata report.
@ -1062,8 +1050,8 @@ SELECT EXISTS
// error message in this case. // error message in this case.
if unique_value_stmt.execute(&[to_bool_ref(attribute.unique.is_some()), &entid as &ToSql]).is_err() { if unique_value_stmt.execute(&[to_bool_ref(attribute.unique.is_some()), &entid as &ToSql]).is_err() {
match attribute.unique { match attribute.unique {
Some(attribute::Unique::Value) => bail!(ErrorKind::SchemaAlterationFailed(format!("Cannot alter schema attribute {} to be :db.unique/value", entid))), Some(attribute::Unique::Value) => bail!(DbError::SchemaAlterationFailed(format!("Cannot alter schema attribute {} to be :db.unique/value", entid))),
Some(attribute::Unique::Identity) => bail!(ErrorKind::SchemaAlterationFailed(format!("Cannot alter schema attribute {} to be :db.unique/identity", entid))), Some(attribute::Unique::Identity) => bail!(DbError::SchemaAlterationFailed(format!("Cannot alter schema attribute {} to be :db.unique/identity", entid))),
None => unreachable!(), // This shouldn't happen, even after we support removing :db/unique. None => unreachable!(), // This shouldn't happen, even after we support removing :db/unique.
} }
} }
@ -1077,7 +1065,7 @@ SELECT EXISTS
if !attribute.multival { if !attribute.multival {
let mut rows = cardinality_stmt.query(&[&entid as &ToSql])?; let mut rows = cardinality_stmt.query(&[&entid as &ToSql])?;
if rows.next().is_some() { if rows.next().is_some() {
bail!(ErrorKind::SchemaAlterationFailed(format!("Cannot alter schema attribute {} to be :db.cardinality/one", entid))); bail!(DbError::SchemaAlterationFailed(format!("Cannot alter schema attribute {} to be :db.cardinality/one", entid)));
} }
} }
}, },
@ -2684,8 +2672,8 @@ mod tests {
let report = conn.transact_simple_terms(terms, InternSet::new()); let report = conn.transact_simple_terms(terms, InternSet::new());
match report.unwrap_err() { match report.unwrap_err().downcast() {
errors::Error(ErrorKind::SchemaConstraintViolation(errors::SchemaConstraintViolation::TypeDisagreements { conflicting_datoms }), _) => { Ok(DbError::SchemaConstraintViolation(errors::SchemaConstraintViolation::TypeDisagreements { conflicting_datoms })) => {
let mut map = BTreeMap::default(); let mut map = BTreeMap::default();
map.insert((100, entids::DB_TX_INSTANT, TypedValue::Long(-1)), ValueType::Instant); map.insert((100, entids::DB_TX_INSTANT, TypedValue::Long(-1)), ValueType::Instant);
map.insert((200, entids::DB_IDENT, TypedValue::typed_string("test")), ValueType::Keyword); map.insert((200, entids::DB_IDENT, TypedValue::typed_string("test")), ValueType::Keyword);

View file

@ -10,6 +10,13 @@
#![allow(dead_code)] #![allow(dead_code)]
use failure::{
Backtrace,
Context,
Error,
Fail,
};
use std::collections::{ use std::collections::{
BTreeMap, BTreeMap,
BTreeSet, BTreeSet,
@ -29,6 +36,16 @@ use types::{
ValueType, ValueType,
}; };
#[macro_export]
macro_rules! bail {
($e:expr) => (
return Err($e.into());
)
}
pub type Result<T> = ::std::result::Result<T, Error>;
// TODO Error/ErrorKind pair
#[derive(Clone, Debug, Eq, PartialEq)] #[derive(Clone, Debug, Eq, PartialEq)]
pub enum CardinalityConflict { pub enum CardinalityConflict {
/// A cardinality one attribute has multiple assertions `[e a v1], [e a v2], ...`. /// A cardinality one attribute has multiple assertions `[e a v1], [e a v2], ...`.
@ -46,7 +63,8 @@ pub enum CardinalityConflict {
}, },
} }
#[derive(Clone, Debug, Eq, PartialEq)] // TODO Error/ErrorKind pair
#[derive(Clone, Debug, Eq, PartialEq, Fail)]
pub enum SchemaConstraintViolation { pub enum SchemaConstraintViolation {
/// A transaction tried to assert datoms where one tempid upserts to two (or more) distinct /// A transaction tried to assert datoms where one tempid upserts to two (or more) distinct
/// entids. /// entids.
@ -125,95 +143,145 @@ impl ::std::fmt::Display for InputError {
} }
} }
error_chain! { #[derive(Debug, Fail)]
types { pub enum DbError {
Error, ErrorKind, ResultExt, Result;
}
foreign_links {
Rusqlite(rusqlite::Error);
}
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.
NotYetImplemented(t: String) { #[fail(display = "not yet implemented: {}", _0)]
description("not yet implemented") NotYetImplemented(String),
display("not yet implemented: {}", t)
}
/// We've been given a value that isn't the correct Mentat type. /// We've been given a value that isn't the correct Mentat type.
BadValuePair(value: String, value_type: ValueType) { #[fail(display = "value '{}' is not the expected Mentat value type {:?}", _0, _1)]
description("value is not the expected Mentat value type") BadValuePair(String, ValueType),
display("value '{}' is not the expected Mentat value type {:?}", value, value_type)
}
/// We've got corrupt data in the SQL store: a value and value_type_tag don't line up. /// We've got corrupt data in the SQL store: a value and value_type_tag don't line up.
BadSQLValuePair(value: rusqlite::types::Value, value_type_tag: i32) { /// TODO _1.data_type()
description("bad SQL (value_type_tag, value) pair") #[fail(display = "bad SQL (value_type_tag, value) pair: ({:?}, {:?})", _0, _1)]
display("bad SQL (value_type_tag, value) pair: ({}, {:?})", value_type_tag, value.data_type()) BadSQLValuePair(rusqlite::types::Value, i32),
}
// /// The SQLite store user_version isn't recognized. This could be an old version of Mentat // /// The SQLite store user_version isn't recognized. This could be an old version of Mentat
// /// trying to open a newer version SQLite store; or it could be a corrupt file; or ... // /// trying to open a newer version SQLite store; or it could be a corrupt file; or ...
// BadSQLiteStoreVersion(version: i32) { // #[fail(display = "bad SQL store user_version: {}", _0)]
// description("bad SQL store user_version") // BadSQLiteStoreVersion(i32),
// display("bad SQL store user_version: {}", version)
// }
/// A bootstrap definition couldn't be parsed or installed. This is a programmer error, not /// A bootstrap definition couldn't be parsed or installed. This is a programmer error, not
/// a runtime error. /// a runtime error.
BadBootstrapDefinition(t: String) { #[fail(display = "bad bootstrap definition: {}", _0)]
description("bad bootstrap definition") BadBootstrapDefinition(String),
display("bad bootstrap definition: {}", t)
}
/// A schema assertion couldn't be parsed. /// A schema assertion couldn't be parsed.
BadSchemaAssertion(t: String) { #[fail(display = "bad schema assertion: {}", _0)]
description("bad schema assertion") BadSchemaAssertion(String),
display("bad schema assertion: {}", t)
}
/// An ident->entid mapping failed. /// An ident->entid mapping failed.
UnrecognizedIdent(ident: String) { #[fail(display = "no entid found for ident: {}", _0)]
description("no entid found for ident") UnrecognizedIdent(String),
display("no entid found for ident: {}", ident)
}
/// An entid->ident mapping failed. /// An entid->ident mapping failed.
/// We also use this error if you try to transact an entid that we didn't allocate, /// We also use this error if you try to transact an entid that we didn't allocate,
/// in part because we blow the stack in error_chain if we define a new enum! /// in part because we blow the stack in error_chain if we define a new enum!
UnrecognizedEntid(entid: Entid) { #[fail(display = "unrecognized or no ident found for entid: {}", _0)]
description("unrecognized or no ident found for entid") UnrecognizedEntid(Entid),
display("unrecognized or no ident found for entid: {}", entid)
}
UnknownAttribute(attr: Entid) { #[fail(display = "unknown attribute for entid: {}", _0)]
description("unknown attribute") UnknownAttribute(Entid),
display("unknown attribute for entid: {}", attr)
}
CannotCacheNonUniqueAttributeInReverse(attr: Entid) { #[fail(display = "cannot reverse-cache non-unique attribute: {}", _0)]
description("cannot reverse-cache non-unique attribute") CannotCacheNonUniqueAttributeInReverse(Entid),
display("cannot reverse-cache non-unique attribute: {}", attr)
}
SchemaAlterationFailed(t: String) { #[fail(display = "schema alteration failed: {}", _0)]
description("schema alteration failed") SchemaAlterationFailed(String),
display("schema alteration failed: {}", t)
}
/// A transaction tried to violate a constraint of the schema of the Mentat store. /// A transaction tried to violate a constraint of the schema of the Mentat store.
SchemaConstraintViolation(violation: SchemaConstraintViolation) { #[fail(display = "schema constraint violation: {}", _0)]
description("schema constraint violation") SchemaConstraintViolation(SchemaConstraintViolation),
display("schema constraint violation: {}", violation)
}
/// The transaction was malformed in some way (that was not recognized at parse time; for /// The transaction was malformed in some way (that was not recognized at parse time; for
/// example, in a way that is schema-dependent). /// example, in a way that is schema-dependent).
InputError(error: InputError) { #[fail(display = "transaction input error: {}", _0)]
description("transaction input error") InputError(InputError),
display("transaction input error: {}", error)
#[fail(display = "Cannot transact a fulltext assertion with a typed value that is not :db/valueType :db.type/string")]
WrongTypeValueForFtsAssertion,
}
#[derive(Debug)]
pub struct DbSqlError {
inner: Context<DbSqlErrorKind>,
}
impl Fail for DbSqlError {
fn cause(&self) -> Option<&Fail> {
self.inner.cause()
} }
fn backtrace(&self) -> Option<&Backtrace> {
self.inner.backtrace()
} }
} }
impl ::std::fmt::Display for DbSqlError {
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
::std::fmt::Display::fmt(&self.inner, f)
}
}
impl DbSqlError {
pub fn kind(&self) -> DbSqlErrorKind {
*self.inner.get_context()
}
}
impl From<DbSqlErrorKind> for DbSqlError {
fn from(kind: DbSqlErrorKind) -> DbSqlError {
DbSqlError { inner: Context::new(kind) }
}
}
impl From<Context<DbSqlErrorKind>> for DbSqlError {
fn from(inner: Context<DbSqlErrorKind>) -> DbSqlError {
DbSqlError { inner: inner }
}
}
#[derive(Copy, Clone, Eq, PartialEq, Debug, Fail)]
pub enum DbSqlErrorKind {
#[fail(display = "Could not set_user_version")]
CouldNotSetVersionPragma,
#[fail(display = "Could not get_user_version")]
CouldNotGetVersionPragma,
#[fail(display = "Could not search!")]
CouldNotSearch,
#[fail(display = "Could not insert transaction: failed to add datoms not already present")]
TxInsertFailedToAddMissingDatoms,
#[fail(display = "Could not insert transaction: failed to retract datoms already present")]
TxInsertFailedToRetractDatoms,
#[fail(display = "Could not update datoms: failed to retract datoms already present")]
DatomsUpdateFailedToRetract,
#[fail(display = "Could not update datoms: failed to add datoms not already present")]
DatomsUpdateFailedToAdd,
#[fail(display = "Failed to create temporary tables")]
FailedToCreateTempTables,
#[fail(display = "Could not insert non-fts one statements into temporary search table!")]
NonFtsInsertionIntoTempSearchTableFailed,
#[fail(display = "Could not insert fts values into fts table!")]
FtsInsertionFailed,
#[fail(display = "Could not insert FTS statements into temporary search table!")]
FtsInsertionIntoTempSearchTableFailed,
#[fail(display = "Could not drop FTS search ids!")]
FtsFailedToDropSearchIds,
#[fail(display = "Could not update partition map")]
FailedToUpdatePartitionMap,
}

View file

@ -31,7 +31,7 @@ use edn::{
use errors; use errors;
use errors::{ use errors::{
ErrorKind, DbError,
Result, Result,
}; };
use schema::{ use schema::{
@ -69,7 +69,7 @@ impl TransactableValue for ValueAndSpan {
Ok(EntityPlace::Entid(entities::Entid::Ident(v))) Ok(EntityPlace::Entid(entities::Entid::Ident(v)))
} else { } else {
// We only allow namespaced idents. // We only allow namespaced idents.
bail!(ErrorKind::InputError(errors::InputError::BadEntityPlace)) bail!(DbError::InputError(errors::InputError::BadEntityPlace))
} }
}, },
Text(v) => Ok(EntityPlace::TempId(TempId::External(v))), Text(v) => Ok(EntityPlace::TempId(TempId::External(v))),
@ -86,10 +86,10 @@ impl TransactableValue for ValueAndSpan {
EntityPlace::Entid(a) => Ok(EntityPlace::LookupRef(entities::LookupRef { a: entities::AttributePlace::Entid(a), v: v.clone() })), EntityPlace::Entid(a) => Ok(EntityPlace::LookupRef(entities::LookupRef { a: entities::AttributePlace::Entid(a), v: v.clone() })),
EntityPlace::TempId(_) | EntityPlace::TempId(_) |
EntityPlace::TxFunction(_) | EntityPlace::TxFunction(_) |
EntityPlace::LookupRef(_) => bail!(ErrorKind::InputError(errors::InputError::BadEntityPlace)), EntityPlace::LookupRef(_) => bail!(DbError::InputError(errors::InputError::BadEntityPlace)),
} }
}, },
_ => bail!(ErrorKind::InputError(errors::InputError::BadEntityPlace)), _ => bail!(DbError::InputError(errors::InputError::BadEntityPlace)),
} }
}, },
Nil | Nil |
@ -102,7 +102,7 @@ impl TransactableValue for ValueAndSpan {
NamespacedSymbol(_) | NamespacedSymbol(_) |
Vector(_) | Vector(_) |
Set(_) | Set(_) |
Map(_) => bail!(ErrorKind::InputError(errors::InputError::BadEntityPlace)), Map(_) => bail!(DbError::InputError(errors::InputError::BadEntityPlace)),
} }
} }
@ -114,7 +114,7 @@ impl TransactableValue for ValueAndSpan {
impl TransactableValue for TypedValue { impl TransactableValue for TypedValue {
fn into_typed_value(self, _schema: &Schema, value_type: ValueType) -> Result<TypedValue> { fn into_typed_value(self, _schema: &Schema, value_type: ValueType) -> Result<TypedValue> {
if self.value_type() != value_type { if self.value_type() != value_type {
bail!(ErrorKind::BadValuePair(format!("{:?}", self), value_type)); bail!(DbError::BadValuePair(format!("{:?}", self), value_type));
} }
Ok(self) Ok(self)
} }
@ -128,7 +128,7 @@ impl TransactableValue for TypedValue {
TypedValue::Long(_) | TypedValue::Long(_) |
TypedValue::Double(_) | TypedValue::Double(_) |
TypedValue::Instant(_) | TypedValue::Instant(_) |
TypedValue::Uuid(_) => bail!(ErrorKind::InputError(errors::InputError::BadEntityPlace)), TypedValue::Uuid(_) => bail!(DbError::InputError(errors::InputError::BadEntityPlace)),
} }
} }
@ -199,7 +199,7 @@ pub fn replace_lookup_ref<T, U>(lookup_map: &AVMap, desired_or: Either<T, Lookup
LookupRefOrTempId::LookupRef(av) => lookup_map.get(&*av) LookupRefOrTempId::LookupRef(av) => lookup_map.get(&*av)
.map(|x| lift(*x)).map(Left) .map(|x| lift(*x)).map(Left)
// XXX TODO: fix this error kind! // XXX TODO: fix this error kind!
.ok_or_else(|| ErrorKind::UnrecognizedIdent(format!("couldn't lookup [a v]: {:?}", (*av).clone())).into()), .ok_or_else(|| DbError::UnrecognizedIdent(format!("couldn't lookup [a v]: {:?}", (*av).clone())).into()),
} }
} }
} }

View file

@ -8,10 +8,8 @@
// 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.
// Oh, error_chain. extern crate failure;
#![recursion_limit="128"] #[macro_use] extern crate failure_derive;
#[macro_use] extern crate error_chain;
extern crate indexmap; extern crate indexmap;
extern crate itertools; extern crate itertools;
#[macro_use] extern crate lazy_static; #[macro_use] extern crate lazy_static;
@ -31,7 +29,12 @@ use std::iter::repeat;
use itertools::Itertools; use itertools::Itertools;
pub use errors::{Error, ErrorKind, ResultExt, Result}; pub use errors::{
DbError,
Result,
SchemaConstraintViolation,
};
#[macro_use] pub mod errors;
mod add_retract_alter_set; mod add_retract_alter_set;
pub mod cache; pub mod cache;
@ -39,7 +42,6 @@ pub mod db;
mod bootstrap; mod bootstrap;
pub mod debug; pub mod debug;
pub mod entids; pub mod entids;
pub mod errors;
pub mod internal_types; // pub because we need them for building entities programmatically. pub mod internal_types; // pub because we need them for building entities programmatically.
mod metadata; mod metadata;
mod schema; mod schema;
@ -114,8 +116,7 @@ pub fn to_namespaced_keyword(s: &str) -> Result<symbols::Keyword> {
_ => None, _ => None,
}; };
// TODO Use custom ErrorKind https://github.com/brson/error-chain/issues/117 nsk.ok_or(DbError::NotYetImplemented(format!("InvalidKeyword: {}", s)).into())
nsk.ok_or(ErrorKind::NotYetImplemented(format!("InvalidKeyword: {}", s)).into())
} }
/// Prepare an SQL `VALUES` block, like (?, ?, ?), (?, ?, ?). /// Prepare an SQL `VALUES` block, like (?, ?, ?), (?, ?, ?).

View file

@ -24,6 +24,8 @@
//! //!
//! This module recognizes, validates, applies, and reports on these mutations. //! This module recognizes, validates, applies, and reports on these mutations.
use failure::ResultExt;
use std::collections::{BTreeMap, BTreeSet}; use std::collections::{BTreeMap, BTreeSet};
use std::collections::btree_map::Entry; use std::collections::btree_map::Entry;
@ -33,9 +35,8 @@ use add_retract_alter_set::{
use edn::symbols; use edn::symbols;
use entids; use entids;
use errors::{ use errors::{
ErrorKind, DbError,
Result, Result,
ResultExt,
}; };
use mentat_core::{ use mentat_core::{
attribute, attribute,
@ -131,7 +132,7 @@ pub fn update_attribute_map_from_entid_triples<A, R>(attribute_map: &mut Attribu
builder.component(false); builder.component(false);
}, },
v => { v => {
bail!(ErrorKind::BadSchemaAssertion(format!("Attempted to retract :db/isComponent with the wrong value {:?}.", v))); bail!(DbError::BadSchemaAssertion(format!("Attempted to retract :db/isComponent with the wrong value {:?}.", v)));
}, },
} }
}, },
@ -146,15 +147,15 @@ pub fn update_attribute_map_from_entid_triples<A, R>(attribute_map: &mut Attribu
builder.non_unique(); builder.non_unique();
}, },
v => { v => {
bail!(ErrorKind::BadSchemaAssertion(format!("Attempted to retract :db/unique with the wrong value {}.", v))); bail!(DbError::BadSchemaAssertion(format!("Attempted to retract :db/unique with the wrong value {}.", v)));
}, },
} }
}, },
_ => bail!(ErrorKind::BadSchemaAssertion(format!("Expected [:db/retract _ :db/unique :db.unique/_] but got [:db/retract {} :db/unique {:?}]", entid, value))) _ => bail!(DbError::BadSchemaAssertion(format!("Expected [:db/retract _ :db/unique :db.unique/_] but got [:db/retract {} :db/unique {:?}]", entid, value)))
} }
}, },
_ => { _ => {
bail!(ErrorKind::BadSchemaAssertion(format!("Retracting attribute {} for entity {} not permitted.", attr, entid))); bail!(DbError::BadSchemaAssertion(format!("Retracting attribute {} for entity {} not permitted.", attr, entid)));
}, },
} }
} }
@ -168,7 +169,7 @@ pub fn update_attribute_map_from_entid_triples<A, R>(attribute_map: &mut Attribu
entids::DB_DOC => { entids::DB_DOC => {
match *value { match *value {
TypedValue::String(_) => {}, TypedValue::String(_) => {},
_ => bail!(ErrorKind::BadSchemaAssertion(format!("Expected [... :db/doc \"string value\"] but got [... :db/doc {:?}] for entid {} and attribute {}", value, entid, attr))) _ => bail!(DbError::BadSchemaAssertion(format!("Expected [... :db/doc \"string value\"] but got [... :db/doc {:?}] for entid {} and attribute {}", value, entid, attr)))
} }
}, },
@ -182,7 +183,7 @@ pub fn update_attribute_map_from_entid_triples<A, R>(attribute_map: &mut Attribu
TypedValue::Ref(entids::DB_TYPE_REF) => { builder.value_type(ValueType::Ref); }, TypedValue::Ref(entids::DB_TYPE_REF) => { builder.value_type(ValueType::Ref); },
TypedValue::Ref(entids::DB_TYPE_STRING) => { builder.value_type(ValueType::String); }, TypedValue::Ref(entids::DB_TYPE_STRING) => { builder.value_type(ValueType::String); },
TypedValue::Ref(entids::DB_TYPE_UUID) => { builder.value_type(ValueType::Uuid); }, TypedValue::Ref(entids::DB_TYPE_UUID) => { builder.value_type(ValueType::Uuid); },
_ => bail!(ErrorKind::BadSchemaAssertion(format!("Expected [... :db/valueType :db.type/*] but got [... :db/valueType {:?}] for entid {} and attribute {}", value, entid, attr))) _ => bail!(DbError::BadSchemaAssertion(format!("Expected [... :db/valueType :db.type/*] but got [... :db/valueType {:?}] for entid {} and attribute {}", value, entid, attr)))
} }
}, },
@ -190,7 +191,7 @@ pub fn update_attribute_map_from_entid_triples<A, R>(attribute_map: &mut Attribu
match *value { match *value {
TypedValue::Ref(entids::DB_CARDINALITY_MANY) => { builder.multival(true); }, TypedValue::Ref(entids::DB_CARDINALITY_MANY) => { builder.multival(true); },
TypedValue::Ref(entids::DB_CARDINALITY_ONE) => { builder.multival(false); }, TypedValue::Ref(entids::DB_CARDINALITY_ONE) => { builder.multival(false); },
_ => bail!(ErrorKind::BadSchemaAssertion(format!("Expected [... :db/cardinality :db.cardinality/many|:db.cardinality/one] but got [... :db/cardinality {:?}]", value))) _ => bail!(DbError::BadSchemaAssertion(format!("Expected [... :db/cardinality :db.cardinality/many|:db.cardinality/one] but got [... :db/cardinality {:?}]", value)))
} }
}, },
@ -198,40 +199,40 @@ pub fn update_attribute_map_from_entid_triples<A, R>(attribute_map: &mut Attribu
match *value { match *value {
TypedValue::Ref(entids::DB_UNIQUE_VALUE) => { builder.unique(attribute::Unique::Value); }, TypedValue::Ref(entids::DB_UNIQUE_VALUE) => { builder.unique(attribute::Unique::Value); },
TypedValue::Ref(entids::DB_UNIQUE_IDENTITY) => { builder.unique(attribute::Unique::Identity); }, TypedValue::Ref(entids::DB_UNIQUE_IDENTITY) => { builder.unique(attribute::Unique::Identity); },
_ => bail!(ErrorKind::BadSchemaAssertion(format!("Expected [... :db/unique :db.unique/value|:db.unique/identity] but got [... :db/unique {:?}]", value))) _ => bail!(DbError::BadSchemaAssertion(format!("Expected [... :db/unique :db.unique/value|:db.unique/identity] but got [... :db/unique {:?}]", value)))
} }
}, },
entids::DB_INDEX => { entids::DB_INDEX => {
match *value { match *value {
TypedValue::Boolean(x) => { builder.index(x); }, TypedValue::Boolean(x) => { builder.index(x); },
_ => bail!(ErrorKind::BadSchemaAssertion(format!("Expected [... :db/index true|false] but got [... :db/index {:?}]", value))) _ => bail!(DbError::BadSchemaAssertion(format!("Expected [... :db/index true|false] but got [... :db/index {:?}]", value)))
} }
}, },
entids::DB_FULLTEXT => { entids::DB_FULLTEXT => {
match *value { match *value {
TypedValue::Boolean(x) => { builder.fulltext(x); }, TypedValue::Boolean(x) => { builder.fulltext(x); },
_ => bail!(ErrorKind::BadSchemaAssertion(format!("Expected [... :db/fulltext true|false] but got [... :db/fulltext {:?}]", value))) _ => bail!(DbError::BadSchemaAssertion(format!("Expected [... :db/fulltext true|false] but got [... :db/fulltext {:?}]", value)))
} }
}, },
entids::DB_IS_COMPONENT => { entids::DB_IS_COMPONENT => {
match *value { match *value {
TypedValue::Boolean(x) => { builder.component(x); }, TypedValue::Boolean(x) => { builder.component(x); },
_ => bail!(ErrorKind::BadSchemaAssertion(format!("Expected [... :db/isComponent true|false] but got [... :db/isComponent {:?}]", value))) _ => bail!(DbError::BadSchemaAssertion(format!("Expected [... :db/isComponent true|false] but got [... :db/isComponent {:?}]", value)))
} }
}, },
entids::DB_NO_HISTORY => { entids::DB_NO_HISTORY => {
match *value { match *value {
TypedValue::Boolean(x) => { builder.no_history(x); }, TypedValue::Boolean(x) => { builder.no_history(x); },
_ => bail!(ErrorKind::BadSchemaAssertion(format!("Expected [... :db/noHistory true|false] but got [... :db/noHistory {:?}]", value))) _ => bail!(DbError::BadSchemaAssertion(format!("Expected [... :db/noHistory true|false] but got [... :db/noHistory {:?}]", value)))
} }
}, },
_ => { _ => {
bail!(ErrorKind::BadSchemaAssertion(format!("Do not recognize attribute {} for entid {}", attr, entid))) bail!(DbError::BadSchemaAssertion(format!("Do not recognize attribute {} for entid {}", attr, entid)))
} }
} }
}; };
@ -243,8 +244,7 @@ pub fn update_attribute_map_from_entid_triples<A, R>(attribute_map: &mut Attribu
match attribute_map.entry(entid) { match attribute_map.entry(entid) {
Entry::Vacant(entry) => { Entry::Vacant(entry) => {
// Validate once… // Validate once…
builder.validate_install_attribute() builder.validate_install_attribute().context(DbError::BadSchemaAssertion(format!("Schema alteration for new attribute with entid {} is not valid", entid)))?;
.chain_err(|| ErrorKind::BadSchemaAssertion(format!("Schema alteration for new attribute with entid {} is not valid", entid)))?;
// … and twice, now we have the Attribute. // … and twice, now we have the Attribute.
let a = builder.build(); let a = builder.build();
@ -254,8 +254,7 @@ pub fn update_attribute_map_from_entid_triples<A, R>(attribute_map: &mut Attribu
}, },
Entry::Occupied(mut entry) => { Entry::Occupied(mut entry) => {
builder.validate_alter_attribute() builder.validate_alter_attribute().context(DbError::BadSchemaAssertion(format!("Schema alteration for existing attribute with entid {} is not valid", entid)))?;
.chain_err(|| ErrorKind::BadSchemaAssertion(format!("Schema alteration for existing attribute with entid {} is not valid", entid)))?;
let mutations = builder.mutate(entry.get_mut()); let mutations = builder.mutate(entry.get_mut());
attributes_altered.insert(entid, mutations); attributes_altered.insert(entid, mutations);
}, },

View file

@ -12,7 +12,10 @@
use db::TypedSQLValue; use db::TypedSQLValue;
use edn; use edn;
use errors::{ErrorKind, Result}; use errors::{
DbError,
Result,
};
use edn::symbols; use edn::symbols;
use mentat_core::{ use mentat_core::{
attribute, attribute,
@ -39,19 +42,19 @@ pub trait AttributeValidation {
impl AttributeValidation for Attribute { impl AttributeValidation for Attribute {
fn validate<F>(&self, ident: F) -> Result<()> where F: Fn() -> String { fn validate<F>(&self, ident: F) -> Result<()> where F: Fn() -> String {
if self.unique == Some(attribute::Unique::Value) && !self.index { if self.unique == Some(attribute::Unique::Value) && !self.index {
bail!(ErrorKind::BadSchemaAssertion(format!(":db/unique :db/unique_value without :db/index true for entid: {}", ident()))) bail!(DbError::BadSchemaAssertion(format!(":db/unique :db/unique_value without :db/index true for entid: {}", ident())))
} }
if self.unique == Some(attribute::Unique::Identity) && !self.index { if self.unique == Some(attribute::Unique::Identity) && !self.index {
bail!(ErrorKind::BadSchemaAssertion(format!(":db/unique :db/unique_identity without :db/index true for entid: {}", ident()))) bail!(DbError::BadSchemaAssertion(format!(":db/unique :db/unique_identity without :db/index true for entid: {}", ident())))
} }
if self.fulltext && self.value_type != ValueType::String { if self.fulltext && self.value_type != ValueType::String {
bail!(ErrorKind::BadSchemaAssertion(format!(":db/fulltext true without :db/valueType :db.type/string for entid: {}", ident()))) bail!(DbError::BadSchemaAssertion(format!(":db/fulltext true without :db/valueType :db.type/string for entid: {}", ident())))
} }
if self.fulltext && !self.index { if self.fulltext && !self.index {
bail!(ErrorKind::BadSchemaAssertion(format!(":db/fulltext true without :db/index true for entid: {}", ident()))) bail!(DbError::BadSchemaAssertion(format!(":db/fulltext true without :db/index true for entid: {}", ident())))
} }
if self.component && self.value_type != ValueType::Ref { if self.component && self.value_type != ValueType::Ref {
bail!(ErrorKind::BadSchemaAssertion(format!(":db/isComponent true without :db/valueType :db.type/ref for entid: {}", ident()))) bail!(DbError::BadSchemaAssertion(format!(":db/isComponent true without :db/valueType :db.type/ref for entid: {}", ident())))
} }
// TODO: consider warning if we have :db/index true for :db/valueType :db.type/string, // TODO: consider warning if we have :db/index true for :db/valueType :db.type/string,
// since this may be inefficient. More generally, we should try to drive complex // since this may be inefficient. More generally, we should try to drive complex
@ -150,17 +153,17 @@ impl AttributeBuilder {
pub fn validate_install_attribute(&self) -> Result<()> { pub fn validate_install_attribute(&self) -> Result<()> {
if self.value_type.is_none() { if self.value_type.is_none() {
bail!(ErrorKind::BadSchemaAssertion("Schema attribute for new attribute does not set :db/valueType".into())); bail!(DbError::BadSchemaAssertion("Schema attribute for new attribute does not set :db/valueType".into()));
} }
Ok(()) Ok(())
} }
pub fn validate_alter_attribute(&self) -> Result<()> { pub fn validate_alter_attribute(&self) -> Result<()> {
if self.value_type.is_some() { if self.value_type.is_some() {
bail!(ErrorKind::BadSchemaAssertion("Schema alteration must not set :db/valueType".into())); bail!(DbError::BadSchemaAssertion("Schema alteration must not set :db/valueType".into()));
} }
if self.fulltext.is_some() { if self.fulltext.is_some() {
bail!(ErrorKind::BadSchemaAssertion("Schema alteration must not set :db/fulltext".into())); bail!(DbError::BadSchemaAssertion("Schema alteration must not set :db/fulltext".into()));
} }
Ok(()) Ok(())
} }
@ -247,15 +250,15 @@ pub trait SchemaBuilding {
impl SchemaBuilding for Schema { impl SchemaBuilding for Schema {
fn require_ident(&self, entid: Entid) -> Result<&symbols::Keyword> { fn require_ident(&self, entid: Entid) -> Result<&symbols::Keyword> {
self.get_ident(entid).ok_or(ErrorKind::UnrecognizedEntid(entid).into()) self.get_ident(entid).ok_or(DbError::UnrecognizedEntid(entid).into())
} }
fn require_entid(&self, ident: &symbols::Keyword) -> Result<KnownEntid> { fn require_entid(&self, ident: &symbols::Keyword) -> Result<KnownEntid> {
self.get_entid(&ident).ok_or(ErrorKind::UnrecognizedIdent(ident.to_string()).into()) self.get_entid(&ident).ok_or(DbError::UnrecognizedIdent(ident.to_string()).into())
} }
fn require_attribute_for_entid(&self, entid: Entid) -> Result<&Attribute> { fn require_attribute_for_entid(&self, entid: Entid) -> Result<&Attribute> {
self.attribute_for_entid(entid).ok_or(ErrorKind::UnrecognizedEntid(entid).into()) self.attribute_for_entid(entid).ok_or(DbError::UnrecognizedEntid(entid).into())
} }
/// Create a valid `Schema` from the constituent maps. /// Create a valid `Schema` from the constituent maps.
@ -271,8 +274,8 @@ impl SchemaBuilding for Schema {
where U: IntoIterator<Item=(symbols::Keyword, symbols::Keyword, TypedValue)>{ where U: IntoIterator<Item=(symbols::Keyword, symbols::Keyword, TypedValue)>{
let entid_assertions: Result<Vec<(Entid, Entid, TypedValue)>> = assertions.into_iter().map(|(symbolic_ident, symbolic_attr, value)| { let entid_assertions: Result<Vec<(Entid, Entid, TypedValue)>> = assertions.into_iter().map(|(symbolic_ident, symbolic_attr, value)| {
let ident: i64 = *ident_map.get(&symbolic_ident).ok_or(ErrorKind::UnrecognizedIdent(symbolic_ident.to_string()))?; let ident: i64 = *ident_map.get(&symbolic_ident).ok_or(DbError::UnrecognizedIdent(symbolic_ident.to_string()))?;
let attr: i64 = *ident_map.get(&symbolic_attr).ok_or(ErrorKind::UnrecognizedIdent(symbolic_attr.to_string()))?; let attr: i64 = *ident_map.get(&symbolic_attr).ok_or(DbError::UnrecognizedIdent(symbolic_attr.to_string()))?;
Ok((ident, attr, value)) Ok((ident, attr, value))
}).collect(); }).collect();
@ -305,7 +308,7 @@ impl SchemaTypeChecking for Schema {
// wrapper function. // wrapper function.
match TypedValue::from_edn_value(&value.clone().without_spans()) { match TypedValue::from_edn_value(&value.clone().without_spans()) {
// We don't recognize this EDN at all. Get out! // We don't recognize this EDN at all. Get out!
None => bail!(ErrorKind::BadValuePair(format!("{}", value), value_type)), None => bail!(DbError::BadValuePair(format!("{}", value), value_type)),
Some(typed_value) => match (value_type, typed_value) { Some(typed_value) => match (value_type, typed_value) {
// Most types don't coerce at all. // Most types don't coerce at all.
(ValueType::Boolean, tv @ TypedValue::Boolean(_)) => Ok(tv), (ValueType::Boolean, tv @ TypedValue::Boolean(_)) => Ok(tv),
@ -331,7 +334,7 @@ impl SchemaTypeChecking for Schema {
(vt @ ValueType::Instant, _) | (vt @ ValueType::Instant, _) |
(vt @ ValueType::Keyword, _) | (vt @ ValueType::Keyword, _) |
(vt @ ValueType::Ref, _) (vt @ ValueType::Ref, _)
=> bail!(ErrorKind::BadValuePair(format!("{}", value), vt)), => bail!(DbError::BadValuePair(format!("{}", value), vt)),
} }
} }
} }
@ -343,7 +346,6 @@ impl SchemaTypeChecking for Schema {
mod test { mod test {
use super::*; use super::*;
use self::edn::Keyword; use self::edn::Keyword;
use errors::Error;
fn add_attribute(schema: &mut Schema, fn add_attribute(schema: &mut Schema,
ident: Keyword, ident: Keyword,
@ -435,8 +437,8 @@ mod test {
let err = validate_attribute_map(&schema.entid_map, &schema.attribute_map).err(); let err = validate_attribute_map(&schema.entid_map, &schema.attribute_map).err();
assert!(err.is_some()); assert!(err.is_some());
match err.unwrap() { match err.unwrap().downcast() {
Error(ErrorKind::BadSchemaAssertion(message), _) => { assert_eq!(message, ":db/unique :db/unique_value without :db/index true for entid: :foo/bar"); }, Ok(DbError::BadSchemaAssertion(message)) => { assert_eq!(message, ":db/unique :db/unique_value without :db/index true for entid: :foo/bar"); },
x => panic!("expected Bad Schema Assertion error, got {:?}", x), x => panic!("expected Bad Schema Assertion error, got {:?}", x),
} }
} }
@ -458,8 +460,8 @@ mod test {
let err = validate_attribute_map(&schema.entid_map, &schema.attribute_map).err(); let err = validate_attribute_map(&schema.entid_map, &schema.attribute_map).err();
assert!(err.is_some()); assert!(err.is_some());
match err.unwrap() { match err.unwrap().downcast() {
Error(ErrorKind::BadSchemaAssertion(message), _) => { assert_eq!(message, ":db/unique :db/unique_identity without :db/index true for entid: :foo/bar"); }, Ok(DbError::BadSchemaAssertion(message)) => { assert_eq!(message, ":db/unique :db/unique_identity without :db/index true for entid: :foo/bar"); },
x => panic!("expected Bad Schema Assertion error, got {:?}", x), x => panic!("expected Bad Schema Assertion error, got {:?}", x),
} }
} }
@ -481,8 +483,8 @@ mod test {
let err = validate_attribute_map(&schema.entid_map, &schema.attribute_map).err(); let err = validate_attribute_map(&schema.entid_map, &schema.attribute_map).err();
assert!(err.is_some()); assert!(err.is_some());
match err.unwrap() { match err.unwrap().downcast() {
Error(ErrorKind::BadSchemaAssertion(message), _) => { assert_eq!(message, ":db/isComponent true without :db/valueType :db.type/ref for entid: :foo/bar"); }, Ok(DbError::BadSchemaAssertion(message)) => { assert_eq!(message, ":db/isComponent true without :db/valueType :db.type/ref for entid: :foo/bar"); },
x => panic!("expected Bad Schema Assertion error, got {:?}", x), x => panic!("expected Bad Schema Assertion error, got {:?}", x),
} }
} }
@ -504,8 +506,8 @@ mod test {
let err = validate_attribute_map(&schema.entid_map, &schema.attribute_map).err(); let err = validate_attribute_map(&schema.entid_map, &schema.attribute_map).err();
assert!(err.is_some()); assert!(err.is_some());
match err.unwrap() { match err.unwrap().downcast() {
Error(ErrorKind::BadSchemaAssertion(message), _) => { assert_eq!(message, ":db/fulltext true without :db/index true for entid: :foo/bar"); }, Ok(DbError::BadSchemaAssertion(message)) => { assert_eq!(message, ":db/fulltext true without :db/index true for entid: :foo/bar"); },
x => panic!("expected Bad Schema Assertion error, got {:?}", x), x => panic!("expected Bad Schema Assertion error, got {:?}", x),
} }
} }
@ -526,8 +528,8 @@ mod test {
let err = validate_attribute_map(&schema.entid_map, &schema.attribute_map).err(); let err = validate_attribute_map(&schema.entid_map, &schema.attribute_map).err();
assert!(err.is_some()); assert!(err.is_some());
match err.unwrap() { match err.unwrap().downcast() {
Error(ErrorKind::BadSchemaAssertion(message), _) => { assert_eq!(message, ":db/fulltext true without :db/valueType :db.type/string for entid: :foo/bar"); }, Ok(DbError::BadSchemaAssertion(message)) => { assert_eq!(message, ":db/fulltext true without :db/valueType :db.type/string for entid: :foo/bar"); },
x => panic!("expected Bad Schema Assertion error, got {:?}", x), x => panic!("expected Bad Schema Assertion error, got {:?}", x),
} }
} }

View file

@ -71,7 +71,7 @@ use edn::{
use entids; use entids;
use errors; use errors;
use errors::{ use errors::{
ErrorKind, DbError,
Result, Result,
}; };
use internal_types::{ use internal_types::{
@ -179,7 +179,7 @@ pub fn remove_db_id<V: TransactableValue>(map: &mut entmod::MapNotation<V>) -> R
entmod::ValuePlace::Atom(v) => Some(v.into_entity_place()?), entmod::ValuePlace::Atom(v) => Some(v.into_entity_place()?),
entmod::ValuePlace::Vector(_) | entmod::ValuePlace::Vector(_) |
entmod::ValuePlace::MapNotation(_) => { entmod::ValuePlace::MapNotation(_) => {
bail!(ErrorKind::InputError(errors::InputError::BadDbId)) bail!(DbError::InputError(errors::InputError::BadDbId))
}, },
} }
} else { } else {
@ -244,7 +244,7 @@ impl<'conn, 'a, W> Tx<'conn, 'a, W> where W: TransactWatcher {
} }
if !conflicting_upserts.is_empty() { if !conflicting_upserts.is_empty() {
bail!(ErrorKind::SchemaConstraintViolation(errors::SchemaConstraintViolation::ConflictingUpserts { conflicting_upserts })); bail!(DbError::SchemaConstraintViolation(errors::SchemaConstraintViolation::ConflictingUpserts { conflicting_upserts }));
} }
Ok(tempids) Ok(tempids)
@ -281,7 +281,7 @@ impl<'conn, 'a, W> Tx<'conn, 'a, W> where W: TransactWatcher {
if self.partition_map.contains_entid(e) { if self.partition_map.contains_entid(e) {
Ok(KnownEntid(e)) Ok(KnownEntid(e))
} else { } else {
bail!(ErrorKind::UnrecognizedEntid(e)) bail!(DbError::UnrecognizedEntid(e))
} }
} }
@ -298,7 +298,7 @@ impl<'conn, 'a, W> Tx<'conn, 'a, W> where W: TransactWatcher {
let lr_typed_value: TypedValue = lookup_ref.v.clone().into_typed_value(&self.schema, lr_attribute.value_type)?; let lr_typed_value: TypedValue = lookup_ref.v.clone().into_typed_value(&self.schema, lr_attribute.value_type)?;
if lr_attribute.unique.is_none() { if lr_attribute.unique.is_none() {
bail!(ErrorKind::NotYetImplemented(format!("Cannot resolve (lookup-ref {} {:?}) with attribute that is not :db/unique", lr_a, lr_typed_value))) bail!(DbError::NotYetImplemented(format!("Cannot resolve (lookup-ref {} {:?}) with attribute that is not :db/unique", lr_a, lr_typed_value)))
} }
Ok(self.lookup_refs.intern((lr_a, lr_typed_value))) Ok(self.lookup_refs.intern((lr_a, lr_typed_value)))
@ -336,7 +336,7 @@ impl<'conn, 'a, W> Tx<'conn, 'a, W> where W: TransactWatcher {
entmod::EntityPlace::TxFunction(ref tx_function) => { entmod::EntityPlace::TxFunction(ref tx_function) => {
match tx_function.op.0.as_str() { match tx_function.op.0.as_str() {
"transaction-tx" => Ok(Either::Left(self.tx_id)), "transaction-tx" => Ok(Either::Left(self.tx_id)),
unknown @ _ => bail!(ErrorKind::NotYetImplemented(format!("Unknown transaction function {}", unknown))), unknown @ _ => bail!(DbError::NotYetImplemented(format!("Unknown transaction function {}", unknown))),
} }
}, },
} }
@ -357,13 +357,13 @@ impl<'conn, 'a, W> Tx<'conn, 'a, W> where W: TransactWatcher {
fn entity_v_into_term_e<W: TransactableValue>(&mut self, x: entmod::ValuePlace<W>, backward_a: &entmod::Entid) -> Result<KnownEntidOr<LookupRefOrTempId>> { fn entity_v_into_term_e<W: TransactableValue>(&mut self, x: entmod::ValuePlace<W>, backward_a: &entmod::Entid) -> Result<KnownEntidOr<LookupRefOrTempId>> {
match backward_a.unreversed() { match backward_a.unreversed() {
None => { None => {
bail!(ErrorKind::NotYetImplemented(format!("Cannot explode map notation value in :attr/_reversed notation for forward attribute"))); bail!(DbError::NotYetImplemented(format!("Cannot explode map notation value in :attr/_reversed notation for forward attribute")));
}, },
Some(forward_a) => { Some(forward_a) => {
let forward_a = self.entity_a_into_term_a(forward_a)?; let forward_a = self.entity_a_into_term_a(forward_a)?;
let forward_attribute = self.schema.require_attribute_for_entid(forward_a)?; let forward_attribute = self.schema.require_attribute_for_entid(forward_a)?;
if forward_attribute.value_type != ValueType::Ref { if forward_attribute.value_type != ValueType::Ref {
bail!(ErrorKind::NotYetImplemented(format!("Cannot use :attr/_reversed notation for attribute {} that is not :db/valueType :db.type/ref", forward_a))) bail!(DbError::NotYetImplemented(format!("Cannot use :attr/_reversed notation for attribute {} that is not :db/valueType :db.type/ref", forward_a)))
} }
match x { match x {
@ -378,7 +378,7 @@ impl<'conn, 'a, W> Tx<'conn, 'a, W> where W: TransactWatcher {
Ok(Either::Left(KnownEntid(entid))) Ok(Either::Left(KnownEntid(entid)))
} else { } else {
// The given value is expected to be :db.type/ref, so this shouldn't happen. // The given value is expected to be :db.type/ref, so this shouldn't happen.
bail!(ErrorKind::NotYetImplemented(format!("Cannot use :attr/_reversed notation for attribute {} with value that is not :db.valueType :db.type/ref", forward_a))) bail!(DbError::NotYetImplemented(format!("Cannot use :attr/_reversed notation for attribute {} with value that is not :db.valueType :db.type/ref", forward_a)))
} }
} }
} }
@ -396,15 +396,15 @@ impl<'conn, 'a, W> Tx<'conn, 'a, W> where W: TransactWatcher {
entmod::ValuePlace::TxFunction(ref tx_function) => { entmod::ValuePlace::TxFunction(ref tx_function) => {
match tx_function.op.0.as_str() { match tx_function.op.0.as_str() {
"transaction-tx" => Ok(Either::Left(KnownEntid(self.tx_id.0))), "transaction-tx" => Ok(Either::Left(KnownEntid(self.tx_id.0))),
unknown @ _ => bail!(ErrorKind::NotYetImplemented(format!("Unknown transaction function {}", unknown))), unknown @ _ => bail!(DbError::NotYetImplemented(format!("Unknown transaction function {}", unknown))),
} }
}, },
entmod::ValuePlace::Vector(_) => entmod::ValuePlace::Vector(_) =>
bail!(ErrorKind::NotYetImplemented(format!("Cannot explode vector value in :attr/_reversed notation for attribute {}", forward_a))), bail!(DbError::NotYetImplemented(format!("Cannot explode vector value in :attr/_reversed notation for attribute {}", forward_a))),
entmod::ValuePlace::MapNotation(_) => entmod::ValuePlace::MapNotation(_) =>
bail!(ErrorKind::NotYetImplemented(format!("Cannot explode map notation value in :attr/_reversed notation for attribute {}", forward_a))), bail!(DbError::NotYetImplemented(format!("Cannot explode map notation value in :attr/_reversed notation for attribute {}", forward_a))),
} }
}, },
} }
@ -475,7 +475,7 @@ impl<'conn, 'a, W> Tx<'conn, 'a, W> where W: TransactWatcher {
entmod::ValuePlace::LookupRef(ref lookup_ref) => { entmod::ValuePlace::LookupRef(ref lookup_ref) => {
if attribute.value_type != ValueType::Ref { if attribute.value_type != ValueType::Ref {
bail!(ErrorKind::NotYetImplemented(format!("Cannot resolve value lookup ref for attribute {} that is not :db/valueType :db.type/ref", a))) bail!(DbError::NotYetImplemented(format!("Cannot resolve value lookup ref for attribute {} that is not :db/valueType :db.type/ref", a)))
} }
Either::Right(LookupRefOrTempId::LookupRef(in_process.intern_lookup_ref(lookup_ref)?)) Either::Right(LookupRefOrTempId::LookupRef(in_process.intern_lookup_ref(lookup_ref)?))
@ -484,7 +484,7 @@ impl<'conn, 'a, W> Tx<'conn, 'a, W> where W: TransactWatcher {
entmod::ValuePlace::TxFunction(ref tx_function) => { entmod::ValuePlace::TxFunction(ref tx_function) => {
let typed_value = match tx_function.op.0.as_str() { let typed_value = match tx_function.op.0.as_str() {
"transaction-tx" => TypedValue::Ref(self.tx_id), "transaction-tx" => TypedValue::Ref(self.tx_id),
unknown @ _ => bail!(ErrorKind::NotYetImplemented(format!("Unknown transaction function {}", unknown))), unknown @ _ => bail!(DbError::NotYetImplemented(format!("Unknown transaction function {}", unknown))),
}; };
// Here we do schema-aware typechecking: we assert that the computed // Here we do schema-aware typechecking: we assert that the computed
@ -494,7 +494,7 @@ impl<'conn, 'a, W> Tx<'conn, 'a, W> where W: TransactWatcher {
// value can be used where a double is expected. See also // value can be used where a double is expected. See also
// `SchemaTypeChecking.to_typed_value(...)`. // `SchemaTypeChecking.to_typed_value(...)`.
if attribute.value_type != typed_value.value_type() { if attribute.value_type != typed_value.value_type() {
bail!(ErrorKind::NotYetImplemented(format!("Transaction function {} produced value of type {} but expected type {}", bail!(DbError::NotYetImplemented(format!("Transaction function {} produced value of type {} but expected type {}",
tx_function.op.0.as_str(), typed_value.value_type(), attribute.value_type))); tx_function.op.0.as_str(), typed_value.value_type(), attribute.value_type)));
} }
@ -503,7 +503,7 @@ impl<'conn, 'a, W> Tx<'conn, 'a, W> where W: TransactWatcher {
entmod::ValuePlace::Vector(vs) => { entmod::ValuePlace::Vector(vs) => {
if !attribute.multival { if !attribute.multival {
bail!(ErrorKind::NotYetImplemented(format!("Cannot explode vector value for attribute {} that is not :db.cardinality :db.cardinality/many", a))); bail!(DbError::NotYetImplemented(format!("Cannot explode vector value for attribute {} that is not :db.cardinality :db.cardinality/many", a)));
} }
for vv in vs { for vv in vs {
@ -523,11 +523,11 @@ impl<'conn, 'a, W> Tx<'conn, 'a, W> where W: TransactWatcher {
// AddOrRetract, which proliferates types and code, or only handling // AddOrRetract, which proliferates types and code, or only handling
// nested maps rather than map values, like Datomic does. // nested maps rather than map values, like Datomic does.
if op != OpType::Add { if op != OpType::Add {
bail!(ErrorKind::NotYetImplemented(format!("Cannot explode nested map value in :db/retract for attribute {}", a))); bail!(DbError::NotYetImplemented(format!("Cannot explode nested map value in :db/retract for attribute {}", a)));
} }
if attribute.value_type != ValueType::Ref { if attribute.value_type != ValueType::Ref {
bail!(ErrorKind::NotYetImplemented(format!("Cannot explode nested map value for attribute {} that is not :db/valueType :db.type/ref", a))) bail!(DbError::NotYetImplemented(format!("Cannot explode nested map value for attribute {} that is not :db/valueType :db.type/ref", a)))
} }
// :db/id is optional; if it's not given, we generate a special internal tempid // :db/id is optional; if it's not given, we generate a special internal tempid
@ -580,7 +580,7 @@ impl<'conn, 'a, W> Tx<'conn, 'a, W> where W: TransactWatcher {
} }
if dangling { if dangling {
bail!(ErrorKind::NotYetImplemented(format!("Cannot explode nested map value that would lead to dangling entity for attribute {}", a))); bail!(DbError::NotYetImplemented(format!("Cannot explode nested map value that would lead to dangling entity for attribute {}", a)));
} }
in_process.entity_e_into_term_v(db_id)? in_process.entity_e_into_term_v(db_id)?
@ -668,7 +668,7 @@ impl<'conn, 'a, W> Tx<'conn, 'a, W> where W: TransactWatcher {
} }
if !conflicting_upserts.is_empty() { if !conflicting_upserts.is_empty() {
bail!(ErrorKind::SchemaConstraintViolation(errors::SchemaConstraintViolation::ConflictingUpserts { conflicting_upserts })); bail!(DbError::SchemaConstraintViolation(errors::SchemaConstraintViolation::ConflictingUpserts { conflicting_upserts }));
} }
debug!("tempids {:?}", tempids); debug!("tempids {:?}", tempids);
@ -742,12 +742,12 @@ impl<'conn, 'a, W> Tx<'conn, 'a, W> where W: TransactWatcher {
let errors = tx_checking::type_disagreements(&aev_trie); let errors = tx_checking::type_disagreements(&aev_trie);
if !errors.is_empty() { if !errors.is_empty() {
bail!(ErrorKind::SchemaConstraintViolation(errors::SchemaConstraintViolation::TypeDisagreements { conflicting_datoms: errors })); bail!(DbError::SchemaConstraintViolation(errors::SchemaConstraintViolation::TypeDisagreements { conflicting_datoms: errors }));
} }
let errors = tx_checking::cardinality_conflicts(&aev_trie); let errors = tx_checking::cardinality_conflicts(&aev_trie);
if !errors.is_empty() { if !errors.is_empty() {
bail!(ErrorKind::SchemaConstraintViolation(errors::SchemaConstraintViolation::CardinalityConflicts { conflicts: errors })); bail!(DbError::SchemaConstraintViolation(errors::SchemaConstraintViolation::CardinalityConflicts { conflicts: errors }));
} }
// Pipeline stage 4: final terms (after rewriting) -> DB insertions. // Pipeline stage 4: final terms (after rewriting) -> DB insertions.

View file

@ -21,8 +21,10 @@ use std::collections::{
use indexmap; use indexmap;
use petgraph::unionfind; use petgraph::unionfind;
use errors; use errors::{
use errors::ErrorKind; DbError,
Result,
};
use types::{ use types::{
AVPair, AVPair,
}; };
@ -100,11 +102,11 @@ pub(crate) struct FinalPopulations {
impl Generation { impl Generation {
/// Split entities into a generation of populations that need to evolve to have their tempids /// Split entities into a generation of populations that need to evolve to have their tempids
/// resolved or allocated, and a population of inert entities that do not reference tempids. /// resolved or allocated, and a population of inert entities that do not reference tempids.
pub(crate) fn from<I>(terms: I, schema: &Schema) -> errors::Result<(Generation, Population)> where I: IntoIterator<Item=TermWithTempIds> { pub(crate) fn from<I>(terms: I, schema: &Schema) -> Result<(Generation, Population)> where I: IntoIterator<Item=TermWithTempIds> {
let mut generation = Generation::default(); let mut generation = Generation::default();
let mut inert = vec![]; let mut inert = vec![];
let is_unique = |a: Entid| -> errors::Result<bool> { let is_unique = |a: Entid| -> Result<bool> {
let attribute: &Attribute = schema.require_attribute_for_entid(a)?; let attribute: &Attribute = schema.require_attribute_for_entid(a)?;
Ok(attribute.unique == Some(attribute::Unique::Identity)) Ok(attribute.unique == Some(attribute::Unique::Identity))
}; };
@ -223,7 +225,7 @@ impl Generation {
} }
/// Evolve potential upserts that haven't resolved into allocations. /// Evolve potential upserts that haven't resolved into allocations.
pub(crate) fn allocate_unresolved_upserts(&mut self) -> errors::Result<()> { pub(crate) fn allocate_unresolved_upserts(&mut self) -> Result<()> {
let mut upserts_ev = vec![]; let mut upserts_ev = vec![];
::std::mem::swap(&mut self.upserts_ev, &mut upserts_ev); ::std::mem::swap(&mut self.upserts_ev, &mut upserts_ev);
@ -236,7 +238,7 @@ impl Generation {
/// ///
/// Some of the tempids may be identified, so we also provide a map from tempid to a dense set /// Some of the tempids may be identified, so we also provide a map from tempid to a dense set
/// of contiguous integer labels. /// of contiguous integer labels.
pub(crate) fn temp_ids_in_allocations(&self, schema: &Schema) -> errors::Result<BTreeMap<TempIdHandle, usize>> { pub(crate) fn temp_ids_in_allocations(&self, schema: &Schema) -> Result<BTreeMap<TempIdHandle, usize>> {
assert!(self.upserts_e.is_empty(), "All upserts should have been upserted, resolved, or moved to the allocated population!"); assert!(self.upserts_e.is_empty(), "All upserts should have been upserted, resolved, or moved to the allocated population!");
assert!(self.upserts_ev.is_empty(), "All upserts should have been upserted, resolved, or moved to the allocated population!"); assert!(self.upserts_ev.is_empty(), "All upserts should have been upserted, resolved, or moved to the allocated population!");
@ -313,7 +315,7 @@ impl Generation {
/// After evolution is complete, use the provided allocated entids to segment `self` into /// After evolution is complete, use the provided allocated entids to segment `self` into
/// populations, each with no references to tempids. /// populations, each with no references to tempids.
pub(crate) fn into_final_populations(self, temp_id_map: &TempIdMap) -> errors::Result<FinalPopulations> { pub(crate) fn into_final_populations(self, temp_id_map: &TempIdMap) -> Result<FinalPopulations> {
assert!(self.upserts_e.is_empty()); assert!(self.upserts_e.is_empty());
assert!(self.upserts_ev.is_empty()); assert!(self.upserts_ev.is_empty());
@ -329,21 +331,21 @@ impl Generation {
match (op, temp_id_map.get(&*t1), temp_id_map.get(&*t2)) { match (op, temp_id_map.get(&*t1), temp_id_map.get(&*t2)) {
(op, Some(&n1), Some(&n2)) => Term::AddOrRetract(op, n1, a, TypedValue::Ref(n2.0)), (op, Some(&n1), Some(&n2)) => Term::AddOrRetract(op, n1, a, TypedValue::Ref(n2.0)),
(OpType::Add, _, _) => unreachable!(), // This is a coding error -- every tempid in a :db/add entity should resolve or be allocated. (OpType::Add, _, _) => unreachable!(), // This is a coding error -- every tempid in a :db/add entity should resolve or be allocated.
(OpType::Retract, _, _) => bail!(ErrorKind::NotYetImplemented(format!("[:db/retract ...] entity referenced tempid that did not upsert: one of {}, {}", t1, t2))), (OpType::Retract, _, _) => bail!(DbError::NotYetImplemented(format!("[:db/retract ...] entity referenced tempid that did not upsert: one of {}, {}", t1, t2))),
} }
}, },
Term::AddOrRetract(op, Right(t), a, Left(v)) => { Term::AddOrRetract(op, Right(t), a, Left(v)) => {
match (op, temp_id_map.get(&*t)) { match (op, temp_id_map.get(&*t)) {
(op, Some(&n)) => Term::AddOrRetract(op, n, a, v), (op, Some(&n)) => Term::AddOrRetract(op, n, a, v),
(OpType::Add, _) => unreachable!(), // This is a coding error. (OpType::Add, _) => unreachable!(), // This is a coding error.
(OpType::Retract, _) => bail!(ErrorKind::NotYetImplemented(format!("[:db/retract ...] entity referenced tempid that did not upsert: {}", t))), (OpType::Retract, _) => bail!(DbError::NotYetImplemented(format!("[:db/retract ...] entity referenced tempid that did not upsert: {}", t))),
} }
}, },
Term::AddOrRetract(op, Left(e), a, Right(t)) => { Term::AddOrRetract(op, Left(e), a, Right(t)) => {
match (op, temp_id_map.get(&*t)) { match (op, temp_id_map.get(&*t)) {
(op, Some(&n)) => Term::AddOrRetract(op, e, a, TypedValue::Ref(n.0)), (op, Some(&n)) => Term::AddOrRetract(op, e, a, TypedValue::Ref(n.0)),
(OpType::Add, _) => unreachable!(), // This is a coding error. (OpType::Add, _) => unreachable!(), // This is a coding error.
(OpType::Retract, _) => bail!(ErrorKind::NotYetImplemented(format!("[:db/retract ...] entity referenced tempid that did not upsert: {}", t))), (OpType::Retract, _) => bail!(DbError::NotYetImplemented(format!("[:db/retract ...] entity referenced tempid that did not upsert: {}", t))),
} }
}, },
Term::AddOrRetract(_, Left(_), _, Left(_)) => unreachable!(), // This is a coding error -- these should not be in allocations. Term::AddOrRetract(_, Left(_), _, Left(_)) => unreachable!(), // This is a coding error -- these should not be in allocations.