From 31de5be64f6eef35551215835bb035d28fe35a51 Mon Sep 17 00:00:00 2001 From: Grisha Kruglov Date: Tue, 5 Jun 2018 21:23:59 -0400 Subject: [PATCH] Convert db/ to failure. --- core/Cargo.toml | 1 + core/src/cache.rs | 7 +- core/src/lib.rs | 1 + db/Cargo.toml | 3 +- db/src/bootstrap.rs | 23 ++-- db/src/cache.rs | 23 ++-- db/src/db.rs | 92 ++++++-------- db/src/errors.rs | 246 +++++++++++++++++++++++------------- db/src/internal_types.rs | 16 +-- db/src/lib.rs | 17 +-- db/src/metadata.rs | 37 +++--- db/src/schema.rs | 56 ++++---- db/src/tx.rs | 44 +++---- db/src/upsert_resolution.rs | 22 ++-- 14 files changed, 326 insertions(+), 262 deletions(-) diff --git a/core/Cargo.toml b/core/Cargo.toml index 7b75353e..4ff68bc9 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -6,6 +6,7 @@ workspace = ".." [dependencies] chrono = { version = "0.4", features = ["serde"] } enum-set = { git = "https://github.com/rnewman/enum-set" } +failure = "0.1.1" indexmap = "1" lazy_static = "0.2" num = "0.1" diff --git a/core/src/cache.rs b/core/src/cache.rs index 32eda719..07e8cea1 100644 --- a/core/src/cache.rs +++ b/core/src/cache.rs @@ -10,6 +10,8 @@ /// Cache traits. +use failure; + use std::collections::{ BTreeSet, }; @@ -33,8 +35,7 @@ pub trait CachedAttributes { fn get_entids_for_value(&self, attribute: Entid, value: &TypedValue) -> Option<&BTreeSet>; } -pub trait UpdateableCache { - type Error; - fn update(&mut self, schema: &Schema, retractions: I, assertions: I) -> Result<(), Self::Error> +pub trait UpdateableCache { + fn update(&mut self, schema: &Schema, retractions: I, assertions: I) -> Result<(), Error> where I: Iterator; } diff --git a/core/src/lib.rs b/core/src/lib.rs index 5ec155f4..af9c71fe 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -10,6 +10,7 @@ extern crate chrono; extern crate enum_set; +extern crate failure; extern crate indexmap; extern crate ordered_float; extern crate uuid; diff --git a/db/Cargo.toml b/db/Cargo.toml index 6a9a166b..a356fb2c 100644 --- a/db/Cargo.toml +++ b/db/Cargo.toml @@ -8,7 +8,8 @@ default = [] sqlcipher = ["rusqlite/sqlcipher"] [dependencies] -error-chain = { git = "https://github.com/rnewman/error-chain", branch = "rnewman/sync" } +failure = "0.1.1" +failure_derive = "0.1.1" indexmap = "1" itertools = "0.7" lazy_static = "0.2" diff --git a/db/src/bootstrap.rs b/db/src/bootstrap.rs index d133f26f..a5df3578 100644 --- a/db/src/bootstrap.rs +++ b/db/src/bootstrap.rs @@ -11,7 +11,10 @@ #![allow(dead_code)] use edn; -use errors::{ErrorKind, Result}; +use errors::{ + DbError, + Result, +}; use edn::types::Value; use edn::symbols; use entids; @@ -156,7 +159,7 @@ lazy_static! { :db/cardinality :db.cardinality/many}}"#; edn::parse::value(s) .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() }; } @@ -207,14 +210,14 @@ fn symbolic_schema_to_triples(ident_map: &IdentMap, symbolic_schema: &Value) -> for (ident, mp) in m { let ident = match 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 { Value::Map(ref mpp) => { for (attr, value) in mpp { let attr = match 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 @@ -229,20 +232,20 @@ fn symbolic_schema_to_triples(ident_map: &IdentMap, symbolic_schema: &Value) -> Some(TypedValue::Keyword(ref k)) => { ident_map.get(k) .map(|entid| TypedValue::Ref(*entid)) - .ok_or(ErrorKind::UnrecognizedIdent(k.to_string()))? + .ok_or(DbError::UnrecognizedIdent(k.to_string()))? }, 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)); } }, - _ => 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) } @@ -263,11 +266,11 @@ fn symbolic_schema_to_assertions(symbolic_schema: &Value) -> Result> 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) } diff --git a/db/src/cache.rs b/db/src/cache.rs index dad957bd..52b06ef5 100644 --- a/db/src/cache.rs +++ b/db/src/cache.rs @@ -106,7 +106,7 @@ use db::{ }; use errors::{ - ErrorKind, + DbError, Result, }; @@ -1155,15 +1155,14 @@ impl CachedAttributes for AttributeCaches { } impl UpdateableCache for AttributeCaches { - type Error = ::errors::Error; - fn update(&mut self, schema: &Schema, retractions: I, assertions: I) -> ::std::result::Result<(), Self::Error> + fn update(&mut self, schema: &Schema, retractions: I, assertions: I) -> Result<()> where I: Iterator { self.update_with_fallback(None, schema, retractions, assertions) } } impl AttributeCaches { - fn update_with_fallback(&mut self, fallback: Option<&AttributeCaches>, schema: &Schema, retractions: I, assertions: I) -> ::std::result::Result<(), ::errors::Error> + fn update_with_fallback(&mut self, fallback: Option<&AttributeCaches>, schema: &Schema, retractions: I, assertions: I) -> Result<()> where I: Iterator { let r_aevs = retractions.peekable(); self.accumulate_into_cache(fallback, schema, r_aevs, AccumulationBehavior::Remove)?; @@ -1237,7 +1236,7 @@ impl SQLiteAttributeCache { let a = attribute.into(); // 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(); caches.forward_cached_attributes.insert(a); caches.repopulate(schema, sqlite, a) @@ -1248,7 +1247,7 @@ impl SQLiteAttributeCache { let a = attribute.into(); // 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(); caches.reverse_cached_attributes.insert(a); @@ -1278,8 +1277,7 @@ impl SQLiteAttributeCache { } impl UpdateableCache for SQLiteAttributeCache { - type Error = ::errors::Error; - fn update(&mut self, schema: &Schema, retractions: I, assertions: I) -> ::std::result::Result<(), Self::Error> + fn update(&mut self, schema: &Schema, retractions: I, assertions: I) -> Result<()> where I: Iterator { self.make_mut().update(schema, retractions, assertions) } @@ -1356,7 +1354,7 @@ impl InProgressSQLiteAttributeCache { let a = attribute.into(); // 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) { return Ok(()); @@ -1372,7 +1370,7 @@ impl InProgressSQLiteAttributeCache { let a = attribute.into(); // 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) { return Ok(()); @@ -1388,7 +1386,7 @@ impl InProgressSQLiteAttributeCache { let a = attribute.into(); // 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? let reverse_done = self.is_attribute_cached_reverse(a); @@ -1427,8 +1425,7 @@ impl InProgressSQLiteAttributeCache { } impl UpdateableCache for InProgressSQLiteAttributeCache { - type Error = ::errors::Error; - fn update(&mut self, schema: &Schema, retractions: I, assertions: I) -> ::std::result::Result<(), Self::Error> + fn update(&mut self, schema: &Schema, retractions: I, assertions: I) -> Result<()> where I: Iterator { self.overlay.update_with_fallback(Some(&self.inner), schema, retractions, assertions) } diff --git a/db/src/db.rs b/db/src/db.rs index bfde80a9..f675fe17 100644 --- a/db/src/db.rs +++ b/db/src/db.rs @@ -10,6 +10,8 @@ #![allow(dead_code)] +use failure::ResultExt; + use std::borrow::Borrow; use std::collections::HashMap; use std::collections::hash_map::{ @@ -53,7 +55,11 @@ use mentat_core::{ ValueRc, }; -use errors::{ErrorKind, Result, ResultExt}; +use errors::{ + DbError, + Result, + DbSqlErrorKind, +}; use metadata; use schema::{ SchemaBuilding, @@ -251,8 +257,8 @@ lazy_static! { /// documentation](https://www.sqlite.org/pragma.html#pragma_user_version). fn set_user_version(conn: &rusqlite::Connection, version: i32) -> Result<()> { conn.execute(&format!("PRAGMA user_version = {}", version), &[]) - .chain_err(|| "Could not set_user_version") - .map(|_| ()) + .context(DbSqlErrorKind::CouldNotSetVersionPragma)?; + Ok(()) } /// 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 /// documentation](https://www.sqlite.org/pragma.html#pragma_user_version). fn get_user_version(conn: &rusqlite::Connection) -> Result { - conn.query_row("PRAGMA user_version", &[], |row| { + let v = conn.query_row("PRAGMA user_version", &[], |row| { row.get(0) - }) - .chain_err(|| "Could not get_user_version") + }).context(DbSqlErrorKind::CouldNotGetVersionPragma)?; + Ok(v) } /// 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 { // TODO: validate metadata mutations that aren't schema related, like additional partitions. if let Some(next_schema) = next_schema { if next_schema != db.schema { - // TODO Use custom ErrorKind https://github.com/brson/error-chain/issues/117 - bail!(ErrorKind::NotYetImplemented(format!("Initial bootstrap transaction did not produce expected bootstrap schema"))); + bail!(DbError::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 { CURRENT_VERSION => read_db(conn), // 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()); if u.is_err() { // 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)); } Ok(TypedValue::Uuid(u.unwrap())) @@ -364,7 +369,7 @@ impl TypedSQLValue for TypedValue { (13, rusqlite::types::Value::Text(x)) => { 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 { let v = read_materialized_view(conn, "idents")?; v.into_iter().map(|(e, a, typed_value)| { 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 { Ok((keyword.as_ref().clone(), e)) } 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() } @@ -546,9 +551,8 @@ fn search(conn: &rusqlite::Connection) -> Result<()> { t.a0 = d.a"#; let mut stmt = conn.prepare_cached(s)?; - stmt.execute(&[]) - .map(|_c| ()) - .chain_err(|| "Could not search!") + stmt.execute(&[]).context(DbSqlErrorKind::CouldNotSearch)?; + Ok(()) } /// 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)))"#; let mut stmt = conn.prepare_cached(s)?; - stmt.execute(&[&tx]) - .map(|_c| ()) - .chain_err(|| "Could not insert transaction: failed to add datoms not already present")?; + stmt.execute(&[&tx]).context(DbSqlErrorKind::TxInsertFailedToAddMissingDatoms)?; let s = r#" 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))"#; let mut stmt = conn.prepare_cached(s)?; - stmt.execute(&[&tx]) - .map(|_c| ()) - .chain_err(|| "Could not insert transaction: failed to retract datoms already present")?; + stmt.execute(&[&tx]).context(DbSqlErrorKind::TxInsertFailedToRetractDatoms)?; Ok(()) } @@ -607,9 +607,7 @@ fn update_datoms(conn: &rusqlite::Connection, tx: Entid) -> Result<()> { DELETE FROM datoms WHERE rowid IN ids"#; let mut stmt = conn.prepare_cached(s)?; - stmt.execute(&[]) - .map(|_c| ()) - .chain_err(|| "Could not update datoms: failed to retract datoms already present")?; + stmt.execute(&[]).context(DbSqlErrorKind::DatomsUpdateFailedToRetract)?; // 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 @@ -632,10 +630,7 @@ fn update_datoms(conn: &rusqlite::Connection, tx: Entid) -> Result<()> { AttributeBitFlags::UniqueValue as u8); let mut stmt = conn.prepare_cached(&s)?; - stmt.execute(&[&tx]) - .map(|_c| ()) - .chain_err(|| "Could not update datoms: failed to add datoms not already present")?; - + stmt.execute(&[&tx]).context(DbSqlErrorKind::DatomsUpdateFailedToAdd)?; Ok(()) } @@ -762,9 +757,7 @@ impl MentatStoring for rusqlite::Connection { for statement in &statements { let mut stmt = self.prepare_cached(statement)?; - stmt.execute(&[]) - .map(|_c| ()) - .chain_err(|| "Failed to create temporary tables")?; + stmt.execute(&[]).context(DbSqlErrorKind::FailedToCreateTempTables)?; } Ok(()) @@ -827,8 +820,9 @@ impl MentatStoring for rusqlite::Connection { // TODO: consider ensuring we inserted the expected number of rows. let mut stmt = self.prepare_cached(s.as_str())?; stmt.execute(¶ms) + .context(DbSqlErrorKind::NonFtsInsertionIntoTempSearchTableFailed) + .map_err(|e| e.into()) .map(|_c| ()) - .chain_err(|| "Could not insert non-fts one statements into temporary search table!") }).collect::>>(); 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. let mut stmt = self.prepare_cached(fts_s.as_str())?; - stmt.execute(&fts_params) - .map(|_c| ()) - .chain_err(|| "Could not insert fts values into fts table!")?; + stmt.execute(&fts_params).context(DbSqlErrorKind::FtsInsertionFailed)?; // Second, insert searches. // `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. let mut stmt = self.prepare_cached(s.as_str())?; - stmt.execute(¶ms) + stmt.execute(¶ms).context(DbSqlErrorKind::FtsInsertionIntoTempSearchTableFailed) + .map_err(|e| e.into()) .map(|_c| ()) - .chain_err(|| "Could not insert FTS statements into temporary search table!") }).collect::>>(); // Finally, clean up temporary searchids. let mut stmt = self.prepare_cached("UPDATE fulltext_values SET searchid = NULL WHERE searchid IS NOT NULL")?; - stmt.execute(&[]) - .map(|_c| ()) - .chain_err(|| "Could not drop FTS search ids!")?; - + stmt.execute(&[]).context(DbSqlErrorKind::FtsFailedToDropSearchIds)?; 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_partitions = max_vars / values_per_statement; 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". @@ -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, // so this is very low priority. let mut stmt = conn.prepare_cached(s.as_str())?; - stmt.execute(¶ms[..]) - .map(|_c| ()) - .chain_err(|| "Could not update partition map") + stmt.execute(¶ms[..]).context(DbSqlErrorKind::FailedToUpdatePartitionMap)?; + Ok(()) } /// Update the metadata materialized views based on the given metadata report. @@ -1062,8 +1050,8 @@ SELECT EXISTS // error message in this case. if unique_value_stmt.execute(&[to_bool_ref(attribute.unique.is_some()), &entid as &ToSql]).is_err() { match attribute.unique { - Some(attribute::Unique::Value) => bail!(ErrorKind::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::Value) => bail!(DbError::SchemaAlterationFailed(format!("Cannot alter schema attribute {} to be :db.unique/value", 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. } } @@ -1077,7 +1065,7 @@ SELECT EXISTS if !attribute.multival { let mut rows = cardinality_stmt.query(&[&entid as &ToSql])?; 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()); - match report.unwrap_err() { - errors::Error(ErrorKind::SchemaConstraintViolation(errors::SchemaConstraintViolation::TypeDisagreements { conflicting_datoms }), _) => { + match report.unwrap_err().downcast() { + Ok(DbError::SchemaConstraintViolation(errors::SchemaConstraintViolation::TypeDisagreements { conflicting_datoms })) => { let mut map = BTreeMap::default(); map.insert((100, entids::DB_TX_INSTANT, TypedValue::Long(-1)), ValueType::Instant); map.insert((200, entids::DB_IDENT, TypedValue::typed_string("test")), ValueType::Keyword); diff --git a/db/src/errors.rs b/db/src/errors.rs index d198081b..0f172cee 100644 --- a/db/src/errors.rs +++ b/db/src/errors.rs @@ -10,6 +10,13 @@ #![allow(dead_code)] +use failure::{ + Backtrace, + Context, + Error, + Fail, +}; + use std::collections::{ BTreeMap, BTreeSet, @@ -29,6 +36,16 @@ use types::{ ValueType, }; +#[macro_export] +macro_rules! bail { + ($e:expr) => ( + return Err($e.into()); + ) +} + +pub type Result = ::std::result::Result; + +// TODO Error/ErrorKind pair #[derive(Clone, Debug, Eq, PartialEq)] pub enum CardinalityConflict { /// 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 { /// A transaction tried to assert datoms where one tempid upserts to two (or more) distinct /// entids. @@ -125,95 +143,145 @@ impl ::std::fmt::Display for InputError { } } -error_chain! { - types { - Error, ErrorKind, ResultExt, Result; +#[derive(Debug, Fail)] +pub enum DbError { + /// We're just not done yet. Message that the feature is recognized but not yet + /// implemented. + #[fail(display = "not yet implemented: {}", _0)] + NotYetImplemented(String), + + /// We've been given a value that isn't the correct Mentat type. + #[fail(display = "value '{}' is not the expected Mentat value type {:?}", _0, _1)] + BadValuePair(String, ValueType), + + /// We've got corrupt data in the SQL store: a value and value_type_tag don't line up. + /// TODO _1.data_type() + #[fail(display = "bad SQL (value_type_tag, value) pair: ({:?}, {:?})", _0, _1)] + BadSQLValuePair(rusqlite::types::Value, i32), + + // /// 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 ... + // #[fail(display = "bad SQL store user_version: {}", _0)] + // BadSQLiteStoreVersion(i32), + + /// A bootstrap definition couldn't be parsed or installed. This is a programmer error, not + /// a runtime error. + #[fail(display = "bad bootstrap definition: {}", _0)] + BadBootstrapDefinition(String), + + /// A schema assertion couldn't be parsed. + #[fail(display = "bad schema assertion: {}", _0)] + BadSchemaAssertion(String), + + /// An ident->entid mapping failed. + #[fail(display = "no entid found for ident: {}", _0)] + UnrecognizedIdent(String), + + /// An entid->ident mapping failed. + /// 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! + #[fail(display = "unrecognized or no ident found for entid: {}", _0)] + UnrecognizedEntid(Entid), + + #[fail(display = "unknown attribute for entid: {}", _0)] + UnknownAttribute(Entid), + + #[fail(display = "cannot reverse-cache non-unique attribute: {}", _0)] + CannotCacheNonUniqueAttributeInReverse(Entid), + + #[fail(display = "schema alteration failed: {}", _0)] + SchemaAlterationFailed(String), + + /// A transaction tried to violate a constraint of the schema of the Mentat store. + #[fail(display = "schema constraint violation: {}", _0)] + SchemaConstraintViolation(SchemaConstraintViolation), + + /// The transaction was malformed in some way (that was not recognized at parse time; for + /// example, in a way that is schema-dependent). + #[fail(display = "transaction input error: {}", _0)] + InputError(InputError), + + #[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, +} + +impl Fail for DbSqlError { + fn cause(&self) -> Option<&Fail> { + self.inner.cause() } - foreign_links { - Rusqlite(rusqlite::Error); - } - - errors { - /// We're just not done yet. Message that the feature is recognized but not yet - /// implemented. - NotYetImplemented(t: String) { - description("not yet implemented") - display("not yet implemented: {}", t) - } - - /// We've been given a value that isn't the correct Mentat type. - BadValuePair(value: String, value_type: ValueType) { - description("value is not the expected Mentat value type") - 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. - BadSQLValuePair(value: rusqlite::types::Value, value_type_tag: i32) { - description("bad SQL (value_type_tag, value) pair") - display("bad SQL (value_type_tag, value) pair: ({}, {:?})", value_type_tag, value.data_type()) - } - - // /// 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 ... - // BadSQLiteStoreVersion(version: i32) { - // description("bad SQL store user_version") - // display("bad SQL store user_version: {}", version) - // } - - /// A bootstrap definition couldn't be parsed or installed. This is a programmer error, not - /// a runtime error. - BadBootstrapDefinition(t: String) { - description("bad bootstrap definition") - display("bad bootstrap definition: {}", t) - } - - /// A schema assertion couldn't be parsed. - BadSchemaAssertion(t: String) { - description("bad schema assertion") - display("bad schema assertion: {}", t) - } - - /// An ident->entid mapping failed. - UnrecognizedIdent(ident: String) { - description("no entid found for ident") - display("no entid found for ident: {}", ident) - } - - /// An entid->ident mapping failed. - /// 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! - UnrecognizedEntid(entid: Entid) { - description("unrecognized or no ident found for entid") - display("unrecognized or no ident found for entid: {}", entid) - } - - UnknownAttribute(attr: Entid) { - description("unknown attribute") - display("unknown attribute for entid: {}", attr) - } - - CannotCacheNonUniqueAttributeInReverse(attr: Entid) { - description("cannot reverse-cache non-unique attribute") - display("cannot reverse-cache non-unique attribute: {}", attr) - } - - SchemaAlterationFailed(t: String) { - description("schema alteration failed") - display("schema alteration failed: {}", t) - } - - /// A transaction tried to violate a constraint of the schema of the Mentat store. - SchemaConstraintViolation(violation: SchemaConstraintViolation) { - description("schema constraint violation") - display("schema constraint violation: {}", violation) - } - - /// The transaction was malformed in some way (that was not recognized at parse time; for - /// example, in a way that is schema-dependent). - InputError(error: InputError) { - description("transaction input error") - display("transaction input error: {}", error) - } + 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 for DbSqlError { + fn from(kind: DbSqlErrorKind) -> DbSqlError { + DbSqlError { inner: Context::new(kind) } + } +} + +impl From> for DbSqlError { + fn from(inner: Context) -> 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, +} diff --git a/db/src/internal_types.rs b/db/src/internal_types.rs index 34f73d09..b61add26 100644 --- a/db/src/internal_types.rs +++ b/db/src/internal_types.rs @@ -31,7 +31,7 @@ use edn::{ use errors; use errors::{ - ErrorKind, + DbError, Result, }; use schema::{ @@ -69,7 +69,7 @@ impl TransactableValue for ValueAndSpan { Ok(EntityPlace::Entid(entities::Entid::Ident(v))) } else { // 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))), @@ -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::TempId(_) | 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 | @@ -102,7 +102,7 @@ impl TransactableValue for ValueAndSpan { NamespacedSymbol(_) | Vector(_) | 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 { fn into_typed_value(self, _schema: &Schema, value_type: ValueType) -> Result { if self.value_type() != value_type { - bail!(ErrorKind::BadValuePair(format!("{:?}", self), value_type)); + bail!(DbError::BadValuePair(format!("{:?}", self), value_type)); } Ok(self) } @@ -128,7 +128,7 @@ impl TransactableValue for TypedValue { TypedValue::Long(_) | TypedValue::Double(_) | 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(lookup_map: &AVMap, desired_or: Either lookup_map.get(&*av) .map(|x| lift(*x)).map(Left) // 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()), } } } diff --git a/db/src/lib.rs b/db/src/lib.rs index 87813828..a2a8088b 100644 --- a/db/src/lib.rs +++ b/db/src/lib.rs @@ -8,10 +8,8 @@ // CONDITIONS OF ANY KIND, either express or implied. See the License for the // specific language governing permissions and limitations under the License. -// Oh, error_chain. -#![recursion_limit="128"] - -#[macro_use] extern crate error_chain; +extern crate failure; +#[macro_use] extern crate failure_derive; extern crate indexmap; extern crate itertools; #[macro_use] extern crate lazy_static; @@ -31,7 +29,12 @@ use std::iter::repeat; 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; pub mod cache; @@ -39,7 +42,6 @@ pub mod db; mod bootstrap; pub mod debug; pub mod entids; -pub mod errors; pub mod internal_types; // pub because we need them for building entities programmatically. mod metadata; mod schema; @@ -114,8 +116,7 @@ pub fn to_namespaced_keyword(s: &str) -> Result { _ => None, }; - // TODO Use custom ErrorKind https://github.com/brson/error-chain/issues/117 - nsk.ok_or(ErrorKind::NotYetImplemented(format!("InvalidKeyword: {}", s)).into()) + nsk.ok_or(DbError::NotYetImplemented(format!("InvalidKeyword: {}", s)).into()) } /// Prepare an SQL `VALUES` block, like (?, ?, ?), (?, ?, ?). diff --git a/db/src/metadata.rs b/db/src/metadata.rs index 9ac02067..24d678c3 100644 --- a/db/src/metadata.rs +++ b/db/src/metadata.rs @@ -24,6 +24,8 @@ //! //! This module recognizes, validates, applies, and reports on these mutations. +use failure::ResultExt; + use std::collections::{BTreeMap, BTreeSet}; use std::collections::btree_map::Entry; @@ -33,9 +35,8 @@ use add_retract_alter_set::{ use edn::symbols; use entids; use errors::{ - ErrorKind, + DbError, Result, - ResultExt, }; use mentat_core::{ attribute, @@ -131,7 +132,7 @@ pub fn update_attribute_map_from_entid_triples(attribute_map: &mut Attribu builder.component(false); }, 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(attribute_map: &mut Attribu builder.non_unique(); }, 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(attribute_map: &mut Attribu entids::DB_DOC => { match *value { 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(attribute_map: &mut Attribu 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_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(attribute_map: &mut Attribu match *value { TypedValue::Ref(entids::DB_CARDINALITY_MANY) => { builder.multival(true); }, 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(attribute_map: &mut Attribu match *value { TypedValue::Ref(entids::DB_UNIQUE_VALUE) => { builder.unique(attribute::Unique::Value); }, 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 => { match *value { 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 => { match *value { 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 => { match *value { 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 => { match *value { 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(attribute_map: &mut Attribu match attribute_map.entry(entid) { Entry::Vacant(entry) => { // Validate once… - builder.validate_install_attribute() - .chain_err(|| ErrorKind::BadSchemaAssertion(format!("Schema alteration for new attribute with entid {} is not valid", entid)))?; + builder.validate_install_attribute().context(DbError::BadSchemaAssertion(format!("Schema alteration for new attribute with entid {} is not valid", entid)))?; // … and twice, now we have the Attribute. let a = builder.build(); @@ -254,8 +254,7 @@ pub fn update_attribute_map_from_entid_triples(attribute_map: &mut Attribu }, Entry::Occupied(mut entry) => { - builder.validate_alter_attribute() - .chain_err(|| ErrorKind::BadSchemaAssertion(format!("Schema alteration for existing attribute with entid {} is not valid", entid)))?; + builder.validate_alter_attribute().context(DbError::BadSchemaAssertion(format!("Schema alteration for existing attribute with entid {} is not valid", entid)))?; let mutations = builder.mutate(entry.get_mut()); attributes_altered.insert(entid, mutations); }, diff --git a/db/src/schema.rs b/db/src/schema.rs index 7f6d1c32..90f32f97 100644 --- a/db/src/schema.rs +++ b/db/src/schema.rs @@ -12,7 +12,10 @@ use db::TypedSQLValue; use edn; -use errors::{ErrorKind, Result}; +use errors::{ + DbError, + Result, +}; use edn::symbols; use mentat_core::{ attribute, @@ -39,19 +42,19 @@ pub trait AttributeValidation { impl AttributeValidation for Attribute { fn validate(&self, ident: F) -> Result<()> where F: Fn() -> String { 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 { - 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 { - 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 { - 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 { - 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, // 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<()> { 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(()) } pub fn validate_alter_attribute(&self) -> Result<()> { 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() { - bail!(ErrorKind::BadSchemaAssertion("Schema alteration must not set :db/fulltext".into())); + bail!(DbError::BadSchemaAssertion("Schema alteration must not set :db/fulltext".into())); } Ok(()) } @@ -247,15 +250,15 @@ pub trait SchemaBuilding { impl SchemaBuilding for Schema { 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 { - 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> { - 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. @@ -271,8 +274,8 @@ impl SchemaBuilding for Schema { where U: IntoIterator{ let entid_assertions: Result> = 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 attr: i64 = *ident_map.get(&symbolic_attr).ok_or(ErrorKind::UnrecognizedIdent(symbolic_attr.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(DbError::UnrecognizedIdent(symbolic_attr.to_string()))?; Ok((ident, attr, value)) }).collect(); @@ -305,7 +308,7 @@ impl SchemaTypeChecking for Schema { // wrapper function. match TypedValue::from_edn_value(&value.clone().without_spans()) { // 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) { // Most types don't coerce at all. (ValueType::Boolean, tv @ TypedValue::Boolean(_)) => Ok(tv), @@ -331,7 +334,7 @@ impl SchemaTypeChecking for Schema { (vt @ ValueType::Instant, _) | (vt @ ValueType::Keyword, _) | (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 { use super::*; use self::edn::Keyword; - use errors::Error; fn add_attribute(schema: &mut Schema, ident: Keyword, @@ -435,8 +437,8 @@ mod test { let err = validate_attribute_map(&schema.entid_map, &schema.attribute_map).err(); assert!(err.is_some()); - match err.unwrap() { - Error(ErrorKind::BadSchemaAssertion(message), _) => { assert_eq!(message, ":db/unique :db/unique_value without :db/index true for entid: :foo/bar"); }, + match err.unwrap().downcast() { + 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), } } @@ -458,8 +460,8 @@ mod test { let err = validate_attribute_map(&schema.entid_map, &schema.attribute_map).err(); assert!(err.is_some()); - match err.unwrap() { - Error(ErrorKind::BadSchemaAssertion(message), _) => { assert_eq!(message, ":db/unique :db/unique_identity without :db/index true for entid: :foo/bar"); }, + match err.unwrap().downcast() { + 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), } } @@ -481,8 +483,8 @@ mod test { let err = validate_attribute_map(&schema.entid_map, &schema.attribute_map).err(); assert!(err.is_some()); - match err.unwrap() { - Error(ErrorKind::BadSchemaAssertion(message), _) => { assert_eq!(message, ":db/isComponent true without :db/valueType :db.type/ref for entid: :foo/bar"); }, + match err.unwrap().downcast() { + 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), } } @@ -504,8 +506,8 @@ mod test { let err = validate_attribute_map(&schema.entid_map, &schema.attribute_map).err(); assert!(err.is_some()); - match err.unwrap() { - Error(ErrorKind::BadSchemaAssertion(message), _) => { assert_eq!(message, ":db/fulltext true without :db/index true for entid: :foo/bar"); }, + match err.unwrap().downcast() { + 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), } } @@ -526,8 +528,8 @@ mod test { let err = validate_attribute_map(&schema.entid_map, &schema.attribute_map).err(); assert!(err.is_some()); - match err.unwrap() { - Error(ErrorKind::BadSchemaAssertion(message), _) => { assert_eq!(message, ":db/fulltext true without :db/valueType :db.type/string for entid: :foo/bar"); }, + match err.unwrap().downcast() { + 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), } } diff --git a/db/src/tx.rs b/db/src/tx.rs index 028e0652..ad7f1746 100644 --- a/db/src/tx.rs +++ b/db/src/tx.rs @@ -71,7 +71,7 @@ use edn::{ use entids; use errors; use errors::{ - ErrorKind, + DbError, Result, }; use internal_types::{ @@ -179,7 +179,7 @@ pub fn remove_db_id(map: &mut entmod::MapNotation) -> R entmod::ValuePlace::Atom(v) => Some(v.into_entity_place()?), entmod::ValuePlace::Vector(_) | entmod::ValuePlace::MapNotation(_) => { - bail!(ErrorKind::InputError(errors::InputError::BadDbId)) + bail!(DbError::InputError(errors::InputError::BadDbId)) }, } } else { @@ -244,7 +244,7 @@ impl<'conn, 'a, W> Tx<'conn, 'a, W> where W: TransactWatcher { } if !conflicting_upserts.is_empty() { - bail!(ErrorKind::SchemaConstraintViolation(errors::SchemaConstraintViolation::ConflictingUpserts { conflicting_upserts })); + bail!(DbError::SchemaConstraintViolation(errors::SchemaConstraintViolation::ConflictingUpserts { conflicting_upserts })); } Ok(tempids) @@ -281,7 +281,7 @@ impl<'conn, 'a, W> Tx<'conn, 'a, W> where W: TransactWatcher { if self.partition_map.contains_entid(e) { Ok(KnownEntid(e)) } 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)?; 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))) @@ -336,7 +336,7 @@ impl<'conn, 'a, W> Tx<'conn, 'a, W> where W: TransactWatcher { entmod::EntityPlace::TxFunction(ref tx_function) => { match tx_function.op.0.as_str() { "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(&mut self, x: entmod::ValuePlace, backward_a: &entmod::Entid) -> Result> { match backward_a.unreversed() { 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) => { let forward_a = self.entity_a_into_term_a(forward_a)?; let forward_attribute = self.schema.require_attribute_for_entid(forward_a)?; 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 { @@ -378,7 +378,7 @@ impl<'conn, 'a, W> Tx<'conn, 'a, W> where W: TransactWatcher { Ok(Either::Left(KnownEntid(entid))) } else { // 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) => { match tx_function.op.0.as_str() { "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(_) => - 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(_) => - 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) => { 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)?)) @@ -484,7 +484,7 @@ impl<'conn, 'a, W> Tx<'conn, 'a, W> where W: TransactWatcher { entmod::ValuePlace::TxFunction(ref tx_function) => { let typed_value = match tx_function.op.0.as_str() { "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 @@ -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 // `SchemaTypeChecking.to_typed_value(...)`. 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))); } @@ -503,7 +503,7 @@ impl<'conn, 'a, W> Tx<'conn, 'a, W> where W: TransactWatcher { entmod::ValuePlace::Vector(vs) => { 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 { @@ -523,11 +523,11 @@ impl<'conn, 'a, W> Tx<'conn, 'a, W> where W: TransactWatcher { // AddOrRetract, which proliferates types and code, or only handling // nested maps rather than map values, like Datomic does. 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 { - 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 @@ -580,7 +580,7 @@ impl<'conn, 'a, W> Tx<'conn, 'a, W> where W: TransactWatcher { } 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)? @@ -668,7 +668,7 @@ impl<'conn, 'a, W> Tx<'conn, 'a, W> where W: TransactWatcher { } if !conflicting_upserts.is_empty() { - bail!(ErrorKind::SchemaConstraintViolation(errors::SchemaConstraintViolation::ConflictingUpserts { conflicting_upserts })); + bail!(DbError::SchemaConstraintViolation(errors::SchemaConstraintViolation::ConflictingUpserts { conflicting_upserts })); } 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); 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); 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. diff --git a/db/src/upsert_resolution.rs b/db/src/upsert_resolution.rs index 5f90821c..bd0b854e 100644 --- a/db/src/upsert_resolution.rs +++ b/db/src/upsert_resolution.rs @@ -21,8 +21,10 @@ use std::collections::{ use indexmap; use petgraph::unionfind; -use errors; -use errors::ErrorKind; +use errors::{ + DbError, + Result, +}; use types::{ AVPair, }; @@ -100,11 +102,11 @@ pub(crate) struct FinalPopulations { impl Generation { /// 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. - pub(crate) fn from(terms: I, schema: &Schema) -> errors::Result<(Generation, Population)> where I: IntoIterator { + pub(crate) fn from(terms: I, schema: &Schema) -> Result<(Generation, Population)> where I: IntoIterator { let mut generation = Generation::default(); let mut inert = vec![]; - let is_unique = |a: Entid| -> errors::Result { + let is_unique = |a: Entid| -> Result { let attribute: &Attribute = schema.require_attribute_for_entid(a)?; Ok(attribute.unique == Some(attribute::Unique::Identity)) }; @@ -223,7 +225,7 @@ impl Generation { } /// 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![]; ::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 /// of contiguous integer labels. - pub(crate) fn temp_ids_in_allocations(&self, schema: &Schema) -> errors::Result> { + pub(crate) fn temp_ids_in_allocations(&self, schema: &Schema) -> Result> { 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!"); @@ -313,7 +315,7 @@ impl Generation { /// After evolution is complete, use the provided allocated entids to segment `self` into /// populations, each with no references to tempids. - pub(crate) fn into_final_populations(self, temp_id_map: &TempIdMap) -> errors::Result { + pub(crate) fn into_final_populations(self, temp_id_map: &TempIdMap) -> Result { assert!(self.upserts_e.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)) { (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::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)) => { match (op, temp_id_map.get(&*t)) { (op, Some(&n)) => Term::AddOrRetract(op, n, a, v), (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)) => { match (op, temp_id_map.get(&*t)) { (op, Some(&n)) => Term::AddOrRetract(op, e, a, TypedValue::Ref(n.0)), (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.