diff --git a/core/src/cache.rs b/core/src/cache.rs index 07e8cea1..61b23656 100644 --- a/core/src/cache.rs +++ b/core/src/cache.rs @@ -10,8 +10,6 @@ /// Cache traits. -use failure; - use std::collections::{ BTreeSet, }; @@ -35,7 +33,7 @@ pub trait CachedAttributes { fn get_entids_for_value(&self, attribute: Entid, value: &TypedValue) -> Option<&BTreeSet>; } -pub trait UpdateableCache { - fn update(&mut self, schema: &Schema, retractions: I, assertions: I) -> Result<(), Error> +pub trait UpdateableCache { + fn update(&mut self, schema: &Schema, retractions: I, assertions: I) -> Result<(), E> where I: Iterator; } diff --git a/db/src/bootstrap.rs b/db/src/bootstrap.rs index a5df3578..a070b9e4 100644 --- a/db/src/bootstrap.rs +++ b/db/src/bootstrap.rs @@ -12,7 +12,7 @@ use edn; use errors::{ - DbError, + DbErrorKind, Result, }; use edn::types::Value; @@ -159,7 +159,7 @@ lazy_static! { :db/cardinality :db.cardinality/many}}"#; edn::parse::value(s) .map(|v| v.without_spans()) - .map_err(|_| DbError::BadBootstrapDefinition("Unable to parse V1_SYMBOLIC_SCHEMA".into())) + .map_err(|_| DbErrorKind::BadBootstrapDefinition("Unable to parse V1_SYMBOLIC_SCHEMA".into())) .unwrap() }; } @@ -210,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!(DbError::BadBootstrapDefinition(format!("Expected namespaced keyword for ident but got '{:?}'", ident))), + _ => bail!(DbErrorKind::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!(DbError::BadBootstrapDefinition(format!("Expected namespaced keyword for attr but got '{:?}'", attr))), + _ => bail!(DbErrorKind::BadBootstrapDefinition(format!("Expected namespaced keyword for attr but got '{:?}'", attr))), }; // We have symbolic idents but the transactor handles entids. Ad-hoc @@ -232,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(DbError::UnrecognizedIdent(k.to_string()))? + .ok_or(DbErrorKind::UnrecognizedIdent(k.to_string()))? }, Some(v) => v, - _ => bail!(DbError::BadBootstrapDefinition(format!("Expected Mentat typed value for value but got '{:?}'", value))) + _ => bail!(DbErrorKind::BadBootstrapDefinition(format!("Expected Mentat typed value for value but got '{:?}'", value))) }; triples.push((ident.clone(), attr.clone(), typed_value)); } }, - _ => bail!(DbError::BadBootstrapDefinition("Expected {:db/ident {:db/attr value ...} ...}".into())) + _ => bail!(DbErrorKind::BadBootstrapDefinition("Expected {:db/ident {:db/attr value ...} ...}".into())) } } }, - _ => bail!(DbError::BadBootstrapDefinition("Expected {...}".into())) + _ => bail!(DbErrorKind::BadBootstrapDefinition("Expected {...}".into())) } Ok(triples) } @@ -266,11 +266,11 @@ fn symbolic_schema_to_assertions(symbolic_schema: &Value) -> Result> value.clone()])); } }, - _ => bail!(DbError::BadBootstrapDefinition("Expected {:db/ident {:db/attr value ...} ...}".into())) + _ => bail!(DbErrorKind::BadBootstrapDefinition("Expected {:db/ident {:db/attr value ...} ...}".into())) } } }, - _ => bail!(DbError::BadBootstrapDefinition("Expected {...}".into())) + _ => bail!(DbErrorKind::BadBootstrapDefinition("Expected {...}".into())) } Ok(assertions) } diff --git a/db/src/cache.rs b/db/src/cache.rs index 52b06ef5..144e746d 100644 --- a/db/src/cache.rs +++ b/db/src/cache.rs @@ -72,6 +72,10 @@ use std::sync::Arc; use std::iter::Peekable; +use failure::{ + ResultExt, +}; + use num; use rusqlite; @@ -107,6 +111,7 @@ use db::{ use errors::{ DbError, + DbErrorKind, Result, }; @@ -895,7 +900,7 @@ impl AttributeCaches { let table = if is_fulltext { "fulltext_datoms" } else { "datoms" }; let sql = format!("SELECT a, e, v, value_type_tag FROM {} WHERE a = ? ORDER BY a ASC, e ASC", table); let args: Vec<&rusqlite::types::ToSql> = vec![&attribute]; - let mut stmt = sqlite.prepare(&sql)?; + let mut stmt = sqlite.prepare(&sql).context(DbErrorKind::CacheUpdateFailed)?; let replacing = true; self.repopulate_from_aevt(schema, &mut stmt, args, replacing) } @@ -1154,7 +1159,7 @@ impl CachedAttributes for AttributeCaches { } } -impl UpdateableCache for AttributeCaches { +impl UpdateableCache for AttributeCaches { fn update(&mut self, schema: &Schema, retractions: I, assertions: I) -> Result<()> where I: Iterator { self.update_with_fallback(None, schema, retractions, assertions) @@ -1236,7 +1241,7 @@ impl SQLiteAttributeCache { let a = attribute.into(); // The attribute must exist! - let _ = schema.attribute_for_entid(a).ok_or_else(|| DbError::UnknownAttribute(a))?; + let _ = schema.attribute_for_entid(a).ok_or_else(|| DbErrorKind::UnknownAttribute(a))?; let caches = self.make_mut(); caches.forward_cached_attributes.insert(a); caches.repopulate(schema, sqlite, a) @@ -1247,7 +1252,7 @@ impl SQLiteAttributeCache { let a = attribute.into(); // The attribute must exist! - let _ = schema.attribute_for_entid(a).ok_or_else(|| DbError::UnknownAttribute(a))?; + let _ = schema.attribute_for_entid(a).ok_or_else(|| DbErrorKind::UnknownAttribute(a))?; let caches = self.make_mut(); caches.reverse_cached_attributes.insert(a); @@ -1276,7 +1281,7 @@ impl SQLiteAttributeCache { } } -impl UpdateableCache for SQLiteAttributeCache { +impl UpdateableCache for SQLiteAttributeCache { fn update(&mut self, schema: &Schema, retractions: I, assertions: I) -> Result<()> where I: Iterator { self.make_mut().update(schema, retractions, assertions) @@ -1354,7 +1359,7 @@ impl InProgressSQLiteAttributeCache { let a = attribute.into(); // The attribute must exist! - let _ = schema.attribute_for_entid(a).ok_or_else(|| DbError::UnknownAttribute(a))?; + let _ = schema.attribute_for_entid(a).ok_or_else(|| DbErrorKind::UnknownAttribute(a))?; if self.is_attribute_cached_forward(a) { return Ok(()); @@ -1370,7 +1375,7 @@ impl InProgressSQLiteAttributeCache { let a = attribute.into(); // The attribute must exist! - let _ = schema.attribute_for_entid(a).ok_or_else(|| DbError::UnknownAttribute(a))?; + let _ = schema.attribute_for_entid(a).ok_or_else(|| DbErrorKind::UnknownAttribute(a))?; if self.is_attribute_cached_reverse(a) { return Ok(()); @@ -1386,7 +1391,7 @@ impl InProgressSQLiteAttributeCache { let a = attribute.into(); // The attribute must exist! - let _ = schema.attribute_for_entid(a).ok_or_else(|| DbError::UnknownAttribute(a))?; + let _ = schema.attribute_for_entid(a).ok_or_else(|| DbErrorKind::UnknownAttribute(a))?; // TODO: reverse-index unique by default? let reverse_done = self.is_attribute_cached_reverse(a); @@ -1424,7 +1429,7 @@ impl InProgressSQLiteAttributeCache { } } -impl UpdateableCache for InProgressSQLiteAttributeCache { +impl UpdateableCache for InProgressSQLiteAttributeCache { 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 f675fe17..327ef4ce 100644 --- a/db/src/db.rs +++ b/db/src/db.rs @@ -10,7 +10,9 @@ #![allow(dead_code)] -use failure::ResultExt; +use failure::{ + ResultExt, +}; use std::borrow::Borrow; use std::collections::HashMap; @@ -56,9 +58,8 @@ use mentat_core::{ }; use errors::{ - DbError, + DbErrorKind, Result, - DbSqlErrorKind, }; use metadata; use schema::{ @@ -257,7 +258,7 @@ 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), &[]) - .context(DbSqlErrorKind::CouldNotSetVersionPragma)?; + .context(DbErrorKind::CouldNotSetVersionPragma)?; Ok(()) } @@ -268,7 +269,7 @@ fn set_user_version(conn: &rusqlite::Connection, version: i32) -> Result<()> { fn get_user_version(conn: &rusqlite::Connection) -> Result { let v = conn.query_row("PRAGMA user_version", &[], |row| { row.get(0) - }).context(DbSqlErrorKind::CouldNotGetVersionPragma)?; + }).context(DbErrorKind::CouldNotGetVersionPragma)?; Ok(v) } @@ -309,7 +310,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 { - bail!(DbError::NotYetImplemented(format!("Initial bootstrap transaction did not produce expected bootstrap schema"))); + bail!(DbErrorKind::NotYetImplemented(format!("Initial bootstrap transaction did not produce expected bootstrap schema"))); } } @@ -331,7 +332,7 @@ pub fn ensure_current_version(conn: &mut rusqlite::Connection) -> Result { CURRENT_VERSION => read_db(conn), // TODO: support updating an existing store. - v => bail!(DbError::NotYetImplemented(format!("Opening databases with Mentat version: {}", v))), + v => bail!(DbErrorKind::NotYetImplemented(format!("Opening databases with Mentat version: {}", v))), } } @@ -361,7 +362,7 @@ impl TypedSQLValue for TypedValue { let u = Uuid::from_bytes(x.as_slice()); if u.is_err() { // Rather than exposing Uuid's ParseError… - bail!(DbError::BadSQLValuePair(rusqlite::types::Value::Blob(x), + bail!(DbErrorKind::BadSQLValuePair(rusqlite::types::Value::Blob(x), value_type_tag)); } Ok(TypedValue::Uuid(u.unwrap())) @@ -369,7 +370,7 @@ impl TypedSQLValue for TypedValue { (13, rusqlite::types::Value::Text(x)) => { to_namespaced_keyword(&x).map(|k| k.into()) }, - (_, value) => bail!(DbError::BadSQLValuePair(value, value_type_tag)), + (_, value) => bail!(DbErrorKind::BadSQLValuePair(value, value_type_tag)), } } @@ -452,12 +453,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!(DbError::NotYetImplemented(format!("bad idents materialized view: expected :db/ident but got {}", a))); + bail!(DbErrorKind::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!(DbError::NotYetImplemented(format!("bad idents materialized view: expected [entid :db/ident keyword] but got [entid :db/ident {:?}]", typed_value))); + bail!(DbErrorKind::NotYetImplemented(format!("bad idents materialized view: expected [entid :db/ident keyword] but got [entid :db/ident {:?}]", typed_value))); } }).collect() } @@ -551,7 +552,7 @@ fn search(conn: &rusqlite::Connection) -> Result<()> { t.a0 = d.a"#; let mut stmt = conn.prepare_cached(s)?; - stmt.execute(&[]).context(DbSqlErrorKind::CouldNotSearch)?; + stmt.execute(&[]).context(DbErrorKind::CouldNotSearch)?; Ok(()) } @@ -574,7 +575,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]).context(DbSqlErrorKind::TxInsertFailedToAddMissingDatoms)?; + stmt.execute(&[&tx]).context(DbErrorKind::TxInsertFailedToAddMissingDatoms)?; let s = r#" INSERT INTO transactions (e, a, v, tx, added, value_type_tag) @@ -585,7 +586,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]).context(DbSqlErrorKind::TxInsertFailedToRetractDatoms)?; + stmt.execute(&[&tx]).context(DbErrorKind::TxInsertFailedToRetractDatoms)?; Ok(()) } @@ -607,7 +608,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(&[]).context(DbSqlErrorKind::DatomsUpdateFailedToRetract)?; + stmt.execute(&[]).context(DbErrorKind::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 @@ -630,7 +631,7 @@ fn update_datoms(conn: &rusqlite::Connection, tx: Entid) -> Result<()> { AttributeBitFlags::UniqueValue as u8); let mut stmt = conn.prepare_cached(&s)?; - stmt.execute(&[&tx]).context(DbSqlErrorKind::DatomsUpdateFailedToAdd)?; + stmt.execute(&[&tx]).context(DbErrorKind::DatomsUpdateFailedToAdd)?; Ok(()) } @@ -757,7 +758,7 @@ impl MentatStoring for rusqlite::Connection { for statement in &statements { let mut stmt = self.prepare_cached(statement)?; - stmt.execute(&[]).context(DbSqlErrorKind::FailedToCreateTempTables)?; + stmt.execute(&[]).context(DbErrorKind::FailedToCreateTempTables)?; } Ok(()) @@ -820,7 +821,7 @@ 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) + .context(DbErrorKind::NonFtsInsertionIntoTempSearchTableFailed) .map_err(|e| e.into()) .map(|_c| ()) }).collect::>>(); @@ -880,7 +881,7 @@ impl MentatStoring for rusqlite::Connection { } }, _ => { - bail!(DbError::WrongTypeValueForFtsAssertion); + bail!(DbErrorKind::WrongTypeValueForFtsAssertion); }, } @@ -907,7 +908,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).context(DbSqlErrorKind::FtsInsertionFailed)?; + stmt.execute(&fts_params).context(DbErrorKind::FtsInsertionFailed)?; // Second, insert searches. // `params` reference computed values in `block`. @@ -935,14 +936,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).context(DbSqlErrorKind::FtsInsertionIntoTempSearchTableFailed) + stmt.execute(¶ms).context(DbErrorKind::FtsInsertionIntoTempSearchTableFailed) .map_err(|e| e.into()) .map(|_c| ()) }).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(&[]).context(DbSqlErrorKind::FtsFailedToDropSearchIds)?; + stmt.execute(&[]).context(DbErrorKind::FtsFailedToDropSearchIds)?; results.map(|_| ()) } @@ -974,7 +975,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!(DbError::NotYetImplemented(format!("No more than {} partitions are supported", max_partitions))); + bail!(DbErrorKind::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". @@ -990,7 +991,7 @@ 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[..]).context(DbSqlErrorKind::FailedToUpdatePartitionMap)?; + stmt.execute(¶ms[..]).context(DbErrorKind::FailedToUpdatePartitionMap)?; Ok(()) } @@ -1050,8 +1051,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!(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))), + Some(attribute::Unique::Value) => bail!(DbErrorKind::SchemaAlterationFailed(format!("Cannot alter schema attribute {} to be :db.unique/value", entid))), + Some(attribute::Unique::Identity) => bail!(DbErrorKind::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. } } @@ -1065,7 +1066,7 @@ SELECT EXISTS if !attribute.multival { let mut rows = cardinality_stmt.query(&[&entid as &ToSql])?; if rows.next().is_some() { - bail!(DbError::SchemaAlterationFailed(format!("Cannot alter schema attribute {} to be :db.cardinality/one", entid))); + bail!(DbErrorKind::SchemaAlterationFailed(format!("Cannot alter schema attribute {} to be :db.cardinality/one", entid))); } } }, @@ -2672,13 +2673,13 @@ mod tests { let report = conn.transact_simple_terms(terms, InternSet::new()); - match report.unwrap_err().downcast() { - Ok(DbError::SchemaConstraintViolation(errors::SchemaConstraintViolation::TypeDisagreements { conflicting_datoms })) => { + match report.err().map(|e| e.kind()) { + Some(DbErrorKind::SchemaConstraintViolation(errors::SchemaConstraintViolation::TypeDisagreements { ref 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); - assert_eq!(conflicting_datoms, map); + assert_eq!(conflicting_datoms, &map); }, x => panic!("expected schema constraint violation, got {:?}", x), } diff --git a/db/src/errors.rs b/db/src/errors.rs index 0f172cee..6e9dd539 100644 --- a/db/src/errors.rs +++ b/db/src/errors.rs @@ -13,7 +13,6 @@ use failure::{ Backtrace, Context, - Error, Fail, }; @@ -43,7 +42,7 @@ macro_rules! bail { ) } -pub type Result = ::std::result::Result; +pub type Result = ::std::result::Result; // TODO Error/ErrorKind pair #[derive(Clone, Debug, Eq, PartialEq)] @@ -119,7 +118,7 @@ impl ::std::fmt::Display for SchemaConstraintViolation { } } -#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Copy, Clone, Eq, PartialEq, Debug, Fail)] pub enum InputError { /// Map notation included a bad `:db/id` value. BadDbId, @@ -143,8 +142,53 @@ impl ::std::fmt::Display for InputError { } } -#[derive(Debug, Fail)] -pub enum DbError { +#[derive(Debug)] +pub struct DbError { + inner: Context, +} + +impl ::std::fmt::Display for DbError { + fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + ::std::fmt::Display::fmt(&self.inner, f) + } +} + +impl Fail for DbError { + fn cause(&self) -> Option<&Fail> { + self.inner.cause() + } + + fn backtrace(&self) -> Option<&Backtrace> { + self.inner.backtrace() + } +} + +impl DbError { + pub fn kind(&self) -> DbErrorKind { + self.inner.get_context().clone() + } +} + +impl From for DbError { + fn from(kind: DbErrorKind) -> DbError { + DbError { inner: Context::new(kind) } + } +} + +impl From> for DbError { + fn from(inner: Context) -> DbError { + DbError { inner: inner } + } +} + +impl From for DbError { + fn from(error: rusqlite::Error) -> DbError { + DbError { inner: Context::new(DbErrorKind::RusqliteError(error.to_string())) } + } +} + +#[derive(Clone, PartialEq, Debug, Fail)] +pub enum DbErrorKind { /// We're just not done yet. Message that the feature is recognized but not yet /// implemented. #[fail(display = "not yet implemented: {}", _0)] @@ -203,49 +247,11 @@ pub enum DbError { #[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, -} + // SQL errors. + #[fail(display = "could not update a cache")] + CacheUpdateFailed, -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 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, @@ -284,4 +290,9 @@ pub enum DbSqlErrorKind { #[fail(display = "Could not update partition map")] FailedToUpdatePartitionMap, + + // It would be better to capture the underlying `rusqlite::Error`, but that type doesn't + // implement many useful traits, including `Clone`, `Eq`, and `PartialEq`. + #[fail(display = "SQL error: _0")] + RusqliteError(String), } diff --git a/db/src/internal_types.rs b/db/src/internal_types.rs index 6ccbd4cd..72dda98d 100644 --- a/db/src/internal_types.rs +++ b/db/src/internal_types.rs @@ -31,7 +31,7 @@ use edn::{ use errors; use errors::{ - DbError, + DbErrorKind, Result, }; use schema::{ @@ -69,7 +69,7 @@ impl TransactableValue for ValueAndSpan { Ok(EntityPlace::Entid(entities::EntidOrIdent::Ident(v))) } else { // We only allow namespaced idents. - bail!(DbError::InputError(errors::InputError::BadEntityPlace)) + bail!(DbErrorKind::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!(DbError::InputError(errors::InputError::BadEntityPlace)), + EntityPlace::LookupRef(_) => bail!(DbErrorKind::InputError(errors::InputError::BadEntityPlace)), } }, - _ => bail!(DbError::InputError(errors::InputError::BadEntityPlace)), + _ => bail!(DbErrorKind::InputError(errors::InputError::BadEntityPlace)), } }, Nil | @@ -102,7 +102,7 @@ impl TransactableValue for ValueAndSpan { NamespacedSymbol(_) | Vector(_) | Set(_) | - Map(_) => bail!(DbError::InputError(errors::InputError::BadEntityPlace)), + Map(_) => bail!(DbErrorKind::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!(DbError::BadValuePair(format!("{:?}", self), value_type)); + bail!(DbErrorKind::BadValuePair(format!("{:?}", self), value_type)); } Ok(self) } @@ -128,7 +128,7 @@ impl TransactableValue for TypedValue { TypedValue::Long(_) | TypedValue::Double(_) | TypedValue::Instant(_) | - TypedValue::Uuid(_) => bail!(DbError::InputError(errors::InputError::BadEntityPlace)), + TypedValue::Uuid(_) => bail!(DbErrorKind::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(|| DbError::UnrecognizedIdent(format!("couldn't lookup [a v]: {:?}", (*av).clone())).into()), + .ok_or_else(|| DbErrorKind::UnrecognizedIdent(format!("couldn't lookup [a v]: {:?}", (*av).clone())).into()), } } } diff --git a/db/src/lib.rs b/db/src/lib.rs index a2a8088b..c7cf40eb 100644 --- a/db/src/lib.rs +++ b/db/src/lib.rs @@ -31,6 +31,7 @@ use itertools::Itertools; pub use errors::{ DbError, + DbErrorKind, Result, SchemaConstraintViolation, }; @@ -116,7 +117,7 @@ pub fn to_namespaced_keyword(s: &str) -> Result { _ => None, }; - nsk.ok_or(DbError::NotYetImplemented(format!("InvalidKeyword: {}", s)).into()) + nsk.ok_or(DbErrorKind::NotYetImplemented(format!("InvalidKeyword: {}", s)).into()) } /// Prepare an SQL `VALUES` block, like (?, ?, ?), (?, ?, ?). diff --git a/db/src/metadata.rs b/db/src/metadata.rs index 24d678c3..651c5054 100644 --- a/db/src/metadata.rs +++ b/db/src/metadata.rs @@ -35,7 +35,7 @@ use add_retract_alter_set::{ use edn::symbols; use entids; use errors::{ - DbError, + DbErrorKind, Result, }; use mentat_core::{ @@ -132,7 +132,7 @@ pub fn update_attribute_map_from_entid_triples(attribute_map: &mut Attribu builder.component(false); }, v => { - bail!(DbError::BadSchemaAssertion(format!("Attempted to retract :db/isComponent with the wrong value {:?}.", v))); + bail!(DbErrorKind::BadSchemaAssertion(format!("Attempted to retract :db/isComponent with the wrong value {:?}.", v))); }, } }, @@ -147,15 +147,15 @@ pub fn update_attribute_map_from_entid_triples(attribute_map: &mut Attribu builder.non_unique(); }, v => { - bail!(DbError::BadSchemaAssertion(format!("Attempted to retract :db/unique with the wrong value {}.", v))); + bail!(DbErrorKind::BadSchemaAssertion(format!("Attempted to retract :db/unique with the wrong value {}.", v))); }, } }, - _ => bail!(DbError::BadSchemaAssertion(format!("Expected [:db/retract _ :db/unique :db.unique/_] but got [:db/retract {} :db/unique {:?}]", entid, value))) + _ => bail!(DbErrorKind::BadSchemaAssertion(format!("Expected [:db/retract _ :db/unique :db.unique/_] but got [:db/retract {} :db/unique {:?}]", entid, value))) } }, _ => { - bail!(DbError::BadSchemaAssertion(format!("Retracting attribute {} for entity {} not permitted.", attr, entid))); + bail!(DbErrorKind::BadSchemaAssertion(format!("Retracting attribute {} for entity {} not permitted.", attr, entid))); }, } } @@ -169,7 +169,7 @@ pub fn update_attribute_map_from_entid_triples(attribute_map: &mut Attribu entids::DB_DOC => { match *value { TypedValue::String(_) => {}, - _ => bail!(DbError::BadSchemaAssertion(format!("Expected [... :db/doc \"string value\"] but got [... :db/doc {:?}] for entid {} and attribute {}", value, entid, attr))) + _ => bail!(DbErrorKind::BadSchemaAssertion(format!("Expected [... :db/doc \"string value\"] but got [... :db/doc {:?}] for entid {} and attribute {}", value, entid, attr))) } }, @@ -183,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!(DbError::BadSchemaAssertion(format!("Expected [... :db/valueType :db.type/*] but got [... :db/valueType {:?}] for entid {} and attribute {}", value, entid, attr))) + _ => bail!(DbErrorKind::BadSchemaAssertion(format!("Expected [... :db/valueType :db.type/*] but got [... :db/valueType {:?}] for entid {} and attribute {}", value, entid, attr))) } }, @@ -191,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!(DbError::BadSchemaAssertion(format!("Expected [... :db/cardinality :db.cardinality/many|:db.cardinality/one] but got [... :db/cardinality {:?}]", value))) + _ => bail!(DbErrorKind::BadSchemaAssertion(format!("Expected [... :db/cardinality :db.cardinality/many|:db.cardinality/one] but got [... :db/cardinality {:?}]", value))) } }, @@ -199,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!(DbError::BadSchemaAssertion(format!("Expected [... :db/unique :db.unique/value|:db.unique/identity] but got [... :db/unique {:?}]", value))) + _ => bail!(DbErrorKind::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!(DbError::BadSchemaAssertion(format!("Expected [... :db/index true|false] but got [... :db/index {:?}]", value))) + _ => bail!(DbErrorKind::BadSchemaAssertion(format!("Expected [... :db/index true|false] but got [... :db/index {:?}]", value))) } }, entids::DB_FULLTEXT => { match *value { TypedValue::Boolean(x) => { builder.fulltext(x); }, - _ => bail!(DbError::BadSchemaAssertion(format!("Expected [... :db/fulltext true|false] but got [... :db/fulltext {:?}]", value))) + _ => bail!(DbErrorKind::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!(DbError::BadSchemaAssertion(format!("Expected [... :db/isComponent true|false] but got [... :db/isComponent {:?}]", value))) + _ => bail!(DbErrorKind::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!(DbError::BadSchemaAssertion(format!("Expected [... :db/noHistory true|false] but got [... :db/noHistory {:?}]", value))) + _ => bail!(DbErrorKind::BadSchemaAssertion(format!("Expected [... :db/noHistory true|false] but got [... :db/noHistory {:?}]", value))) } }, _ => { - bail!(DbError::BadSchemaAssertion(format!("Do not recognize attribute {} for entid {}", attr, entid))) + bail!(DbErrorKind::BadSchemaAssertion(format!("Do not recognize attribute {} for entid {}", attr, entid))) } } }; @@ -244,7 +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().context(DbError::BadSchemaAssertion(format!("Schema alteration for new attribute with entid {} is not valid", entid)))?; + builder.validate_install_attribute().context(DbErrorKind::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,7 +254,7 @@ pub fn update_attribute_map_from_entid_triples(attribute_map: &mut Attribu }, Entry::Occupied(mut entry) => { - builder.validate_alter_attribute().context(DbError::BadSchemaAssertion(format!("Schema alteration for existing attribute with entid {} is not valid", entid)))?; + builder.validate_alter_attribute().context(DbErrorKind::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 90f32f97..a112ca08 100644 --- a/db/src/schema.rs +++ b/db/src/schema.rs @@ -13,7 +13,7 @@ use db::TypedSQLValue; use edn; use errors::{ - DbError, + DbErrorKind, Result, }; use edn::symbols; @@ -42,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!(DbError::BadSchemaAssertion(format!(":db/unique :db/unique_value without :db/index true for entid: {}", ident()))) + bail!(DbErrorKind::BadSchemaAssertion(format!(":db/unique :db/unique_value without :db/index true for entid: {}", ident()))) } if self.unique == Some(attribute::Unique::Identity) && !self.index { - bail!(DbError::BadSchemaAssertion(format!(":db/unique :db/unique_identity without :db/index true for entid: {}", ident()))) + bail!(DbErrorKind::BadSchemaAssertion(format!(":db/unique :db/unique_identity without :db/index true for entid: {}", ident()))) } if self.fulltext && self.value_type != ValueType::String { - bail!(DbError::BadSchemaAssertion(format!(":db/fulltext true without :db/valueType :db.type/string for entid: {}", ident()))) + bail!(DbErrorKind::BadSchemaAssertion(format!(":db/fulltext true without :db/valueType :db.type/string for entid: {}", ident()))) } if self.fulltext && !self.index { - bail!(DbError::BadSchemaAssertion(format!(":db/fulltext true without :db/index true for entid: {}", ident()))) + bail!(DbErrorKind::BadSchemaAssertion(format!(":db/fulltext true without :db/index true for entid: {}", ident()))) } if self.component && self.value_type != ValueType::Ref { - bail!(DbError::BadSchemaAssertion(format!(":db/isComponent true without :db/valueType :db.type/ref for entid: {}", ident()))) + bail!(DbErrorKind::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 @@ -153,17 +153,17 @@ impl AttributeBuilder { pub fn validate_install_attribute(&self) -> Result<()> { if self.value_type.is_none() { - bail!(DbError::BadSchemaAssertion("Schema attribute for new attribute does not set :db/valueType".into())); + bail!(DbErrorKind::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!(DbError::BadSchemaAssertion("Schema alteration must not set :db/valueType".into())); + bail!(DbErrorKind::BadSchemaAssertion("Schema alteration must not set :db/valueType".into())); } if self.fulltext.is_some() { - bail!(DbError::BadSchemaAssertion("Schema alteration must not set :db/fulltext".into())); + bail!(DbErrorKind::BadSchemaAssertion("Schema alteration must not set :db/fulltext".into())); } Ok(()) } @@ -250,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(DbError::UnrecognizedEntid(entid).into()) + self.get_ident(entid).ok_or(DbErrorKind::UnrecognizedEntid(entid).into()) } fn require_entid(&self, ident: &symbols::Keyword) -> Result { - self.get_entid(&ident).ok_or(DbError::UnrecognizedIdent(ident.to_string()).into()) + self.get_entid(&ident).ok_or(DbErrorKind::UnrecognizedIdent(ident.to_string()).into()) } fn require_attribute_for_entid(&self, entid: Entid) -> Result<&Attribute> { - self.attribute_for_entid(entid).ok_or(DbError::UnrecognizedEntid(entid).into()) + self.attribute_for_entid(entid).ok_or(DbErrorKind::UnrecognizedEntid(entid).into()) } /// Create a valid `Schema` from the constituent maps. @@ -274,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(DbError::UnrecognizedIdent(symbolic_ident.to_string()))?; - let attr: i64 = *ident_map.get(&symbolic_attr).ok_or(DbError::UnrecognizedIdent(symbolic_attr.to_string()))?; + let ident: i64 = *ident_map.get(&symbolic_ident).ok_or(DbErrorKind::UnrecognizedIdent(symbolic_ident.to_string()))?; + let attr: i64 = *ident_map.get(&symbolic_attr).ok_or(DbErrorKind::UnrecognizedIdent(symbolic_attr.to_string()))?; Ok((ident, attr, value)) }).collect(); @@ -308,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!(DbError::BadValuePair(format!("{}", value), value_type)), + None => bail!(DbErrorKind::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), @@ -334,7 +334,7 @@ impl SchemaTypeChecking for Schema { (vt @ ValueType::Instant, _) | (vt @ ValueType::Keyword, _) | (vt @ ValueType::Ref, _) - => bail!(DbError::BadValuePair(format!("{}", value), vt)), + => bail!(DbErrorKind::BadValuePair(format!("{}", value), vt)), } } } @@ -434,13 +434,8 @@ mod test { no_history: false, }); - let err = validate_attribute_map(&schema.entid_map, &schema.attribute_map).err(); - assert!(err.is_some()); - - 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), - } + let err = validate_attribute_map(&schema.entid_map, &schema.attribute_map).err().map(|e| e.kind()); + assert_eq!(err, Some(DbErrorKind::BadSchemaAssertion(":db/unique :db/unique_value without :db/index true for entid: :foo/bar".into()))); } #[test] @@ -457,13 +452,8 @@ mod test { no_history: false, }); - let err = validate_attribute_map(&schema.entid_map, &schema.attribute_map).err(); - assert!(err.is_some()); - - 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), - } + let err = validate_attribute_map(&schema.entid_map, &schema.attribute_map).err().map(|e| e.kind()); + assert_eq!(err, Some(DbErrorKind::BadSchemaAssertion(":db/unique :db/unique_identity without :db/index true for entid: :foo/bar".into()))); } #[test] @@ -480,13 +470,8 @@ mod test { no_history: false, }); - let err = validate_attribute_map(&schema.entid_map, &schema.attribute_map).err(); - assert!(err.is_some()); - - 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), - } + let err = validate_attribute_map(&schema.entid_map, &schema.attribute_map).err().map(|e| e.kind()); + assert_eq!(err, Some(DbErrorKind::BadSchemaAssertion(":db/isComponent true without :db/valueType :db.type/ref for entid: :foo/bar".into()))); } #[test] @@ -503,13 +488,8 @@ mod test { no_history: false, }); - let err = validate_attribute_map(&schema.entid_map, &schema.attribute_map).err(); - assert!(err.is_some()); - - 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), - } + let err = validate_attribute_map(&schema.entid_map, &schema.attribute_map).err().map(|e| e.kind()); + assert_eq!(err, Some(DbErrorKind::BadSchemaAssertion(":db/fulltext true without :db/index true for entid: :foo/bar".into()))); } fn invalid_schema_fulltext_index_not_string() { @@ -525,12 +505,7 @@ mod test { no_history: false, }); - let err = validate_attribute_map(&schema.entid_map, &schema.attribute_map).err(); - assert!(err.is_some()); - - 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), - } + let err = validate_attribute_map(&schema.entid_map, &schema.attribute_map).err().map(|e| e.kind()); + assert_eq!(err, Some(DbErrorKind::BadSchemaAssertion(":db/fulltext true without :db/valueType :db.type/string for entid: :foo/bar".into()))); } } diff --git a/db/src/tx.rs b/db/src/tx.rs index ab48bb0c..dc777c34 100644 --- a/db/src/tx.rs +++ b/db/src/tx.rs @@ -71,7 +71,7 @@ use edn::{ use entids; use errors; use errors::{ - DbError, + DbErrorKind, 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!(DbError::InputError(errors::InputError::BadDbId)) + bail!(DbErrorKind::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!(DbError::SchemaConstraintViolation(errors::SchemaConstraintViolation::ConflictingUpserts { conflicting_upserts })); + bail!(DbErrorKind::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!(DbError::UnrecognizedEntid(e)) + bail!(DbErrorKind::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!(DbError::NotYetImplemented(format!("Cannot resolve (lookup-ref {} {:?}) with attribute that is not :db/unique", lr_a, lr_typed_value))) + bail!(DbErrorKind::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!(DbError::NotYetImplemented(format!("Unknown transaction function {}", unknown))), + unknown @ _ => bail!(DbErrorKind::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::EntidOrIdent) -> Result> { match backward_a.unreversed() { None => { - bail!(DbError::NotYetImplemented(format!("Cannot explode map notation value in :attr/_reversed notation for forward attribute"))); + bail!(DbErrorKind::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!(DbError::NotYetImplemented(format!("Cannot use :attr/_reversed notation for attribute {} that is not :db/valueType :db.type/ref", forward_a))) + bail!(DbErrorKind::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!(DbError::NotYetImplemented(format!("Cannot use :attr/_reversed notation for attribute {} with value that is not :db.valueType :db.type/ref", forward_a))) + bail!(DbErrorKind::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!(DbError::NotYetImplemented(format!("Unknown transaction function {}", unknown))), + unknown @ _ => bail!(DbErrorKind::NotYetImplemented(format!("Unknown transaction function {}", unknown))), } }, entmod::ValuePlace::Vector(_) => - bail!(DbError::NotYetImplemented(format!("Cannot explode vector value in :attr/_reversed notation for attribute {}", forward_a))), + bail!(DbErrorKind::NotYetImplemented(format!("Cannot explode vector value in :attr/_reversed notation for attribute {}", forward_a))), entmod::ValuePlace::MapNotation(_) => - bail!(DbError::NotYetImplemented(format!("Cannot explode map notation value in :attr/_reversed notation for attribute {}", forward_a))), + bail!(DbErrorKind::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!(DbError::NotYetImplemented(format!("Cannot resolve value lookup ref for attribute {} that is not :db/valueType :db.type/ref", a))) + bail!(DbErrorKind::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!(DbError::NotYetImplemented(format!("Unknown transaction function {}", unknown))), + unknown @ _ => bail!(DbErrorKind::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!(DbError::NotYetImplemented(format!("Transaction function {} produced value of type {} but expected type {}", + bail!(DbErrorKind::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!(DbError::NotYetImplemented(format!("Cannot explode vector value for attribute {} that is not :db.cardinality :db.cardinality/many", a))); + bail!(DbErrorKind::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!(DbError::NotYetImplemented(format!("Cannot explode nested map value in :db/retract for attribute {}", a))); + bail!(DbErrorKind::NotYetImplemented(format!("Cannot explode nested map value in :db/retract for attribute {}", a))); } if attribute.value_type != ValueType::Ref { - bail!(DbError::NotYetImplemented(format!("Cannot explode nested map value for attribute {} that is not :db/valueType :db.type/ref", a))) + bail!(DbErrorKind::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!(DbError::NotYetImplemented(format!("Cannot explode nested map value that would lead to dangling entity for attribute {}", a))); + bail!(DbErrorKind::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!(DbError::SchemaConstraintViolation(errors::SchemaConstraintViolation::ConflictingUpserts { conflicting_upserts })); + bail!(DbErrorKind::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!(DbError::SchemaConstraintViolation(errors::SchemaConstraintViolation::TypeDisagreements { conflicting_datoms: errors })); + bail!(DbErrorKind::SchemaConstraintViolation(errors::SchemaConstraintViolation::TypeDisagreements { conflicting_datoms: errors })); } let errors = tx_checking::cardinality_conflicts(&aev_trie); if !errors.is_empty() { - bail!(DbError::SchemaConstraintViolation(errors::SchemaConstraintViolation::CardinalityConflicts { conflicts: errors })); + bail!(DbErrorKind::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 bd0b854e..4aef115c 100644 --- a/db/src/upsert_resolution.rs +++ b/db/src/upsert_resolution.rs @@ -22,7 +22,7 @@ use indexmap; use petgraph::unionfind; use errors::{ - DbError, + DbErrorKind, Result, }; use types::{ @@ -331,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!(DbError::NotYetImplemented(format!("[:db/retract ...] entity referenced tempid that did not upsert: one of {}, {}", t1, t2))), + (OpType::Retract, _, _) => bail!(DbErrorKind::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!(DbError::NotYetImplemented(format!("[:db/retract ...] entity referenced tempid that did not upsert: {}", t))), + (OpType::Retract, _) => bail!(DbErrorKind::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!(DbError::NotYetImplemented(format!("[:db/retract ...] entity referenced tempid that did not upsert: {}", t))), + (OpType::Retract, _) => bail!(DbErrorKind::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. diff --git a/query-algebrizer/src/clauses/fulltext.rs b/query-algebrizer/src/clauses/fulltext.rs index 5e97fb52..6f5159f4 100644 --- a/query-algebrizer/src/clauses/fulltext.rs +++ b/query-algebrizer/src/clauses/fulltext.rs @@ -32,7 +32,6 @@ use clauses::{ use errors::{ AlgebrizerError, BindingError, - InvalidBinding, Result, }; @@ -59,12 +58,12 @@ impl ConjoiningClauses { if where_fn.binding.is_empty() { // The binding must introduce at least one bound variable. - bail!(InvalidBinding::new(where_fn.operator.clone(), BindingError::NoBoundVariable)); + bail!(AlgebrizerError::InvalidBinding(where_fn.operator.clone(), BindingError::NoBoundVariable)); } if !where_fn.binding.is_valid() { // The binding must not duplicate bound variables. - bail!(InvalidBinding::new(where_fn.operator.clone(), BindingError::RepeatedBoundVariable)); + bail!(AlgebrizerError::InvalidBinding(where_fn.operator.clone(), BindingError::RepeatedBoundVariable)); } // We should have exactly four bindings. Destructure them now. @@ -72,7 +71,7 @@ impl ConjoiningClauses { Binding::BindRel(bindings) => { let bindings_count = bindings.len(); if bindings_count < 1 || bindings_count > 4 { - bail!(InvalidBinding::new(where_fn.operator.clone(), + bail!(AlgebrizerError::InvalidBinding(where_fn.operator.clone(), BindingError::InvalidNumberOfBindings { number: bindings.len(), expected: 4, @@ -83,7 +82,7 @@ impl ConjoiningClauses { }, Binding::BindScalar(_) | Binding::BindTuple(_) | - Binding::BindColl(_) => bail!(InvalidBinding::new(where_fn.operator.clone(), BindingError::ExpectedBindRel)), + Binding::BindColl(_) => bail!(AlgebrizerError::InvalidBinding(where_fn.operator.clone(), BindingError::ExpectedBindRel)), }; let mut bindings = bindings.into_iter(); let b_entity = bindings.next().unwrap(); @@ -246,7 +245,7 @@ impl ConjoiningClauses { // We do not allow the score to be bound. if self.value_bindings.contains_key(var) || self.input_variables.contains(var) { - bail!(InvalidBinding::new(var.name(), BindingError::UnexpectedBinding)); + bail!(AlgebrizerError::InvalidBinding(var.name(), BindingError::UnexpectedBinding)); } // We bind the value ourselves. This handily takes care of substituting into existing uses. diff --git a/query-algebrizer/src/clauses/ground.rs b/query-algebrizer/src/clauses/ground.rs index 18f68d64..2e18ef7e 100644 --- a/query-algebrizer/src/clauses/ground.rs +++ b/query-algebrizer/src/clauses/ground.rs @@ -33,7 +33,6 @@ use clauses::convert::ValueConversion; use errors::{ AlgebrizerError, BindingError, - InvalidBinding, Result, }; @@ -125,12 +124,12 @@ impl ConjoiningClauses { if where_fn.binding.is_empty() { // The binding must introduce at least one bound variable. - bail!(InvalidBinding::new(where_fn.operator.clone(), BindingError::NoBoundVariable)); + bail!(AlgebrizerError::InvalidBinding(where_fn.operator.clone(), BindingError::NoBoundVariable)); } if !where_fn.binding.is_valid() { // The binding must not duplicate bound variables. - bail!(InvalidBinding::new(where_fn.operator.clone(), BindingError::RepeatedBoundVariable)); + bail!(AlgebrizerError::InvalidBinding(where_fn.operator.clone(), BindingError::RepeatedBoundVariable)); } let schema = known.schema; diff --git a/query-algebrizer/src/clauses/not.rs b/query-algebrizer/src/clauses/not.rs index 05b88330..7330342f 100644 --- a/query-algebrizer/src/clauses/not.rs +++ b/query-algebrizer/src/clauses/not.rs @@ -553,7 +553,7 @@ mod testing { :where (not [?x :foo/knows ?y])]"#; let parsed = parse_find_string(query).expect("parse failed"); let err = algebrize(known, parsed).expect_err("algebrization should have failed"); - match err.downcast().expect("expected AlgebrizerError") { + match err { AlgebrizerError::UnboundVariable(var) => { assert_eq!(var, PlainSymbol("?x".to_string())); }, x => panic!("expected Unbound Variable error, got {:?}", x), } diff --git a/query-algebrizer/src/clauses/tx_log_api.rs b/query-algebrizer/src/clauses/tx_log_api.rs index d7dbb686..9c487be8 100644 --- a/query-algebrizer/src/clauses/tx_log_api.rs +++ b/query-algebrizer/src/clauses/tx_log_api.rs @@ -27,7 +27,6 @@ use clauses::{ use errors::{ AlgebrizerError, BindingError, - InvalidBinding, Result, }; @@ -66,12 +65,12 @@ impl ConjoiningClauses { if where_fn.binding.is_empty() { // The binding must introduce at least one bound variable. - bail!(InvalidBinding::new(where_fn.operator.clone(), BindingError::NoBoundVariable)); + bail!(AlgebrizerError::InvalidBinding(where_fn.operator.clone(), BindingError::NoBoundVariable)); } if !where_fn.binding.is_valid() { // The binding must not duplicate bound variables. - bail!(InvalidBinding::new(where_fn.operator.clone(), BindingError::RepeatedBoundVariable)); + bail!(AlgebrizerError::InvalidBinding(where_fn.operator.clone(), BindingError::RepeatedBoundVariable)); } // We should have exactly one binding. Destructure it now. @@ -79,7 +78,7 @@ impl ConjoiningClauses { Binding::BindRel(bindings) => { let bindings_count = bindings.len(); if bindings_count != 1 { - bail!(InvalidBinding::new(where_fn.operator.clone(), + bail!(AlgebrizerError::InvalidBinding(where_fn.operator.clone(), BindingError::InvalidNumberOfBindings { number: bindings_count, expected: 1, @@ -93,7 +92,7 @@ impl ConjoiningClauses { Binding::BindColl(v) => v, Binding::BindScalar(_) | Binding::BindTuple(_) => { - bail!(InvalidBinding::new(where_fn.operator.clone(), BindingError::ExpectedBindRelOrBindColl)) + bail!(AlgebrizerError::InvalidBinding(where_fn.operator.clone(), BindingError::ExpectedBindRelOrBindColl)) }, }; @@ -144,12 +143,12 @@ impl ConjoiningClauses { if where_fn.binding.is_empty() { // The binding must introduce at least one bound variable. - bail!(InvalidBinding::new(where_fn.operator.clone(), BindingError::NoBoundVariable)); + bail!(AlgebrizerError::InvalidBinding(where_fn.operator.clone(), BindingError::NoBoundVariable)); } if !where_fn.binding.is_valid() { // The binding must not duplicate bound variables. - bail!(InvalidBinding::new(where_fn.operator.clone(), BindingError::RepeatedBoundVariable)); + bail!(AlgebrizerError::InvalidBinding(where_fn.operator.clone(), BindingError::RepeatedBoundVariable)); } // We should have at most five bindings. Destructure them now. @@ -157,7 +156,7 @@ impl ConjoiningClauses { Binding::BindRel(bindings) => { let bindings_count = bindings.len(); if bindings_count < 1 || bindings_count > 5 { - bail!(InvalidBinding::new(where_fn.operator.clone(), + bail!(AlgebrizerError::InvalidBinding(where_fn.operator.clone(), BindingError::InvalidNumberOfBindings { number: bindings.len(), expected: 5, @@ -167,7 +166,7 @@ impl ConjoiningClauses { }, Binding::BindScalar(_) | Binding::BindTuple(_) | - Binding::BindColl(_) => bail!(InvalidBinding::new(where_fn.operator.clone(), BindingError::ExpectedBindRel)), + Binding::BindColl(_) => bail!(AlgebrizerError::InvalidBinding(where_fn.operator.clone(), BindingError::ExpectedBindRel)), }; let mut bindings = bindings.into_iter(); let b_e = bindings.next().unwrap_or(VariableOrPlaceholder::Placeholder); diff --git a/query-algebrizer/src/errors.rs b/query-algebrizer/src/errors.rs index bba74686..20f0979a 100644 --- a/query-algebrizer/src/errors.rs +++ b/query-algebrizer/src/errors.rs @@ -11,17 +11,9 @@ extern crate mentat_query; use std; // To refer to std::result::Result. -use std::fmt; -use std::fmt::Display; - -use failure::{ - Backtrace, - Context, - Error, - Fail, -}; use mentat_core::{ + EdnParseError, ValueType, ValueTypeSet, }; @@ -30,7 +22,7 @@ use self::mentat_query::{ PlainSymbol, }; -pub type Result = std::result::Result; +pub type Result = std::result::Result; #[macro_export] macro_rules! bail { @@ -39,44 +31,7 @@ macro_rules! bail { ) } -#[derive(Debug)] -pub struct InvalidBinding { - pub function: PlainSymbol, - pub inner: Context -} - -impl InvalidBinding { - pub fn new(function: PlainSymbol, inner: BindingError) -> InvalidBinding { - InvalidBinding { - function: function, - inner: Context::new(inner) - } - } -} - -impl Fail for InvalidBinding { - fn cause(&self) -> Option<&Fail> { - self.inner.cause() - } - - fn backtrace(&self) -> Option<&Backtrace> { - self.inner.backtrace() - } -} - -impl Display for InvalidBinding { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "invalid binding for {}: {:?}", self.function, self.inner) - } -} - -impl Display for BindingError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "BindingError: {:?}", self) - } -} - -#[derive(Clone, Debug, Eq, PartialEq, Fail)] +#[derive(Clone, Debug, Eq, PartialEq)] pub enum BindingError { NoBoundVariable, UnexpectedBinding, @@ -96,7 +51,7 @@ pub enum BindingError { InvalidNumberOfBindings { number: usize, expected: usize }, } -#[derive(Debug, Fail)] +#[derive(Clone, Debug, Eq, Fail, PartialEq)] pub enum AlgebrizerError { #[fail(display = "{} var {} is duplicated", _0, _1)] DuplicateVariableError(PlainSymbol, &'static str), @@ -144,4 +99,16 @@ pub enum AlgebrizerError { #[fail(display = "non-matching variables in 'not' clause")] NonMatchingVariablesInNotClause, + + #[fail(display = "binding error in {}: {:?}", _0, _1)] + InvalidBinding(PlainSymbol, BindingError), + + #[fail(display = "{}", _0)] + EdnParseError(#[cause] EdnParseError), +} + +impl From for AlgebrizerError { + fn from(error: EdnParseError) -> AlgebrizerError { + AlgebrizerError::EdnParseError(error) + } } diff --git a/query-algebrizer/src/lib.rs b/query-algebrizer/src/lib.rs index 0a137110..09e18bdc 100644 --- a/query-algebrizer/src/lib.rs +++ b/query-algebrizer/src/lib.rs @@ -51,7 +51,6 @@ pub use errors::{ AlgebrizerError, BindingError, Result, - InvalidBinding, }; pub use clauses::{ diff --git a/query-algebrizer/tests/ground.rs b/query-algebrizer/tests/ground.rs index d3e74faf..a78c9a09 100644 --- a/query-algebrizer/tests/ground.rs +++ b/query-algebrizer/tests/ground.rs @@ -33,7 +33,6 @@ use mentat_query_algebrizer::{ AlgebrizerError, BindingError, ComputedTable, - InvalidBinding, Known, QueryInputs, }; @@ -255,14 +254,8 @@ fn test_ground_coll_heterogeneous_types() { let q = r#"[:find ?x :where [?x _ ?v] [(ground [false 8.5]) [?v ...]]]"#; let schema = prepopulated_schema(); let known = Known::for_schema(&schema); - let e = bails(known, &q); - match e.downcast().expect("proper error") { - AlgebrizerError::InvalidGroundConstant => { - }, - _ => { - panic!(); - }, - } + assert_eq!(bails(known, &q), + AlgebrizerError::InvalidGroundConstant); } #[test] @@ -270,14 +263,8 @@ fn test_ground_rel_heterogeneous_types() { let q = r#"[:find ?x :where [?x _ ?v] [(ground [[false] [5]]) [[?v]]]]"#; let schema = prepopulated_schema(); let known = Known::for_schema(&schema); - let e = bails(known, &q); - match e.downcast().expect("proper error") { - AlgebrizerError::InvalidGroundConstant => { - }, - _ => { - panic!(); - }, - } + assert_eq!(bails(known, &q), + AlgebrizerError::InvalidGroundConstant); } #[test] @@ -285,15 +272,8 @@ fn test_ground_tuple_duplicate_vars() { let q = r#"[:find ?x :where [?x :foo/age ?v] [(ground [8 10]) [?x ?x]]]"#; let schema = prepopulated_schema(); let known = Known::for_schema(&schema); - let e: InvalidBinding = bails(known, &q).downcast().expect("proper error"); - assert_eq!(e.function, PlainSymbol::plain("ground")); - match e.inner.get_context() { - &BindingError::RepeatedBoundVariable => { - }, - _ => { - panic!(); - }, - } + assert_eq!(bails(known, &q), + AlgebrizerError::InvalidBinding(PlainSymbol::plain("ground"), BindingError::RepeatedBoundVariable)); } #[test] @@ -301,15 +281,8 @@ fn test_ground_rel_duplicate_vars() { let q = r#"[:find ?x :where [?x :foo/age ?v] [(ground [[8 10]]) [[?x ?x]]]]"#; let schema = prepopulated_schema(); let known = Known::for_schema(&schema); - let e: InvalidBinding = bails(known, &q).downcast().expect("expected InvalidBinding"); - assert_eq!(e.function, PlainSymbol::plain("ground")); - match e.inner.get_context() { - &BindingError::RepeatedBoundVariable => { - }, - _ => { - panic!(); - }, - } + assert_eq!(bails(known, &q), + AlgebrizerError::InvalidBinding(PlainSymbol::plain("ground"), BindingError::RepeatedBoundVariable)); } #[test] @@ -317,15 +290,8 @@ fn test_ground_nonexistent_variable_invalid() { let q = r#"[:find ?x ?e :where [?e _ ?x] (not [(ground 17) ?v])]"#; let schema = prepopulated_schema(); let known = Known::for_schema(&schema); - let e = bails(known, &q).downcast().expect("proper error"); - match e { - AlgebrizerError::UnboundVariable(PlainSymbol(v)) => { - assert_eq!(v, "?v".to_string()); - }, - _ => { - panic!(); - }, - } + assert_eq!(bails(known, &q), + AlgebrizerError::UnboundVariable(PlainSymbol::plain("?v"))); } #[test] @@ -341,13 +307,6 @@ fn test_unbound_input_variable_invalid() { let i = QueryInputs::new(types, BTreeMap::default()).expect("valid QueryInputs"); - let e = bails_with_inputs(known, &q, i).downcast().expect("proper error"); - match e { - AlgebrizerError::UnboundVariable(v) => { - assert_eq!(v.0, "?x"); - }, - _ => { - panic!(); - }, - } + assert_eq!(bails_with_inputs(known, &q, i), + AlgebrizerError::UnboundVariable(PlainSymbol::plain("?x"))); } diff --git a/query-algebrizer/tests/predicate.rs b/query-algebrizer/tests/predicate.rs index 7a2e4591..8724b145 100644 --- a/query-algebrizer/tests/predicate.rs +++ b/query-algebrizer/tests/predicate.rs @@ -78,27 +78,21 @@ fn test_instant_predicates_require_instants() { :where [?e :foo/date ?t] [(> ?t "2017-06-16T00:56:41.257Z")]]"#; - match bails(known, query).downcast().expect("proper cause") { - AlgebrizerError::InvalidArgumentType(op, why, idx) => { - assert_eq!(op, PlainSymbol::plain(">")); - assert_eq!(why, ValueTypeSet::of_numeric_and_instant_types()); - assert_eq!(idx, 1); - }, - _ => panic!("Expected InvalidArgument."), - } + assert_eq!(bails(known, query), + AlgebrizerError::InvalidArgumentType( + PlainSymbol::plain(">"), + ValueTypeSet::of_numeric_and_instant_types(), + 1)); let query = r#"[:find ?e :where [?e :foo/date ?t] [(> "2017-06-16T00:56:41.257Z", ?t)]]"#; - match bails(known, query).downcast().expect("proper cause") { - AlgebrizerError::InvalidArgumentType(op, why, idx) => { - assert_eq!(op, PlainSymbol::plain(">")); - assert_eq!(why, ValueTypeSet::of_numeric_and_instant_types()); - assert_eq!(idx, 0); // We get this right. - }, - _ => panic!("Expected InvalidArgument."), - } + assert_eq!(bails(known, query), + AlgebrizerError::InvalidArgumentType( + PlainSymbol::plain(">"), + ValueTypeSet::of_numeric_and_instant_types(), + 0)); // We get this right. // You can try using a number, which is valid input to a numeric predicate. // In this store and query, though, that means we expect `?t` to be both diff --git a/query-algebrizer/tests/utils/mod.rs b/query-algebrizer/tests/utils/mod.rs index 88bae48c..fb4e5c4f 100644 --- a/query-algebrizer/tests/utils/mod.rs +++ b/query-algebrizer/tests/utils/mod.rs @@ -13,9 +13,6 @@ // this module will get warnings otherwise). #![allow(dead_code)] -extern crate failure; -use self::failure::Error; - use mentat_core::{ Attribute, Entid, @@ -28,6 +25,7 @@ use mentat_query::{ }; use mentat_query_algebrizer::{ + AlgebrizerError, ConjoiningClauses, Known, QueryInputs, @@ -83,12 +81,12 @@ impl SchemaBuilder { } } -pub fn bails(known: Known, input: &str) -> Error { +pub fn bails(known: Known, input: &str) -> AlgebrizerError { let parsed = parse_find_string(input).expect("query input to have parsed"); algebrize(known, parsed).expect_err("algebrize to have failed") } -pub fn bails_with_inputs(known: Known, input: &str, inputs: QueryInputs) -> Error { +pub fn bails_with_inputs(known: Known, input: &str, inputs: QueryInputs) -> AlgebrizerError { let parsed = parse_find_string(input).expect("query input to have parsed"); algebrize_with_inputs(known, parsed, 0, inputs).expect_err("algebrize to have failed") } diff --git a/query-projector/src/errors.rs b/query-projector/src/errors.rs index d25366eb..0d500c64 100644 --- a/query-projector/src/errors.rs +++ b/query-projector/src/errors.rs @@ -10,17 +10,16 @@ use std; // To refer to std::result::Result. -use failure::{ - Error, -}; +use rusqlite; use mentat_core::{ ValueTypeSet, }; - +use mentat_db; use mentat_query::{ PlainSymbol, }; +use mentat_query_pull; use aggregates::{ SimpleAggregationOp, @@ -33,7 +32,7 @@ macro_rules! bail { ) } -pub type Result = std::result::Result; +pub type Result = std::result::Result; #[derive(Debug, Fail)] pub enum ProjectorError { @@ -62,4 +61,33 @@ pub enum ProjectorError { #[fail(display = "min/max expressions: {} (max 1), corresponding: {}", _0, _1)] AmbiguousAggregates(usize, usize), + + // It would be better to capture the underlying `rusqlite::Error`, but that type doesn't + // implement many useful traits, including `Clone`, `Eq`, and `PartialEq`. + #[fail(display = "SQL error: _0")] + RusqliteError(String), + + #[fail(display = "{}", _0)] + DbError(#[cause] mentat_db::DbError), + + #[fail(display = "{}", _0)] + PullError(#[cause] mentat_query_pull::PullError), +} + +impl From for ProjectorError { + fn from(error: rusqlite::Error) -> ProjectorError { + ProjectorError::RusqliteError(error.to_string()) + } +} + +impl From for ProjectorError { + fn from(error: mentat_db::DbError) -> ProjectorError { + ProjectorError::DbError(error) + } +} + +impl From for ProjectorError { + fn from(error: mentat_query_pull::PullError) -> ProjectorError { + ProjectorError::PullError(error) + } } diff --git a/query-projector/src/lib.rs b/query-projector/src/lib.rs index c74fcc31..de80ca8e 100644 --- a/query-projector/src/lib.rs +++ b/query-projector/src/lib.rs @@ -112,7 +112,7 @@ pub use relresult::{ StructuredRelResult, }; -use errors::{ +pub use errors::{ ProjectorError, Result, }; diff --git a/query-projector/tests/aggregates.rs b/query-projector/tests/aggregates.rs index 584f6431..d2d676ea 100644 --- a/query-projector/tests/aggregates.rs +++ b/query-projector/tests/aggregates.rs @@ -101,7 +101,7 @@ fn test_the_without_max_or_min() { use ::mentat_query_projector::errors::{ ProjectorError, }; - match projection.err().expect("expected failure").downcast().expect("expected specific error") { + match projection.err().expect("expected failure") { ProjectorError::InvalidProjection(s) => { assert_eq!(s.as_str(), "Warning: used `the` without `min` or `max`."); }, diff --git a/query-pull/src/errors.rs b/query-pull/src/errors.rs index 628a1170..4ec4f221 100644 --- a/query-pull/src/errors.rs +++ b/query-pull/src/errors.rs @@ -10,15 +10,15 @@ use std; // To refer to std::result::Result. +use mentat_db::{ + DbError, +}; + use mentat_core::{ Entid, }; -use failure::{ - Error, -}; - -pub type Result = std::result::Result; +pub type Result = std::result::Result; #[derive(Debug, Fail)] pub enum PullError { @@ -27,4 +27,13 @@ pub enum PullError { #[fail(display = ":db/id repeated")] RepeatedDbId, + + #[fail(display = "{}", _0)] + DbError(#[cause] DbError), +} + +impl From for PullError { + fn from(error: DbError) -> PullError { + PullError::DbError(error) + } } diff --git a/query-pull/src/lib.rs b/query-pull/src/lib.rs index 22b48de6..39b6e903 100644 --- a/query-pull/src/lib.rs +++ b/query-pull/src/lib.rs @@ -102,7 +102,7 @@ use mentat_query::{ pub mod errors; -use errors::{ +pub use errors::{ PullError, Result, }; diff --git a/query-translator/src/lib.rs b/query-translator/src/lib.rs index b122c670..b91cc258 100644 --- a/query-translator/src/lib.rs +++ b/query-translator/src/lib.rs @@ -9,6 +9,7 @@ // specific language governing permissions and limitations under the License. extern crate failure; + extern crate mentat_core; extern crate mentat_query; extern crate mentat_query_algebrizer; @@ -16,8 +17,6 @@ extern crate mentat_query_projector; extern crate mentat_query_sql; extern crate mentat_sql; -use failure::Error; - mod translate; pub use mentat_query_sql::{ @@ -30,4 +29,6 @@ pub use translate::{ query_to_select, }; -type Result = std::result::Result; +// query-translator could be folded into query-projector; for now, just type alias the errors. +pub type TranslatorError = mentat_query_projector::ProjectorError; +pub type Result = std::result::Result; diff --git a/query-translator/src/translate.rs b/query-translator/src/translate.rs index ac35548d..687e3b03 100644 --- a/query-translator/src/translate.rs +++ b/query-translator/src/translate.rs @@ -521,5 +521,5 @@ pub fn query_to_select(schema: &Schema, query: AlgebraicQuery) -> Result Result<()>; + fn sync(&mut self, server_uri: &String, user_uuid: &String) -> ::std::result::Result<(), ::failure::Error>; } /// Represents an in-progress, not yet committed, set of changes to the store. @@ -871,9 +871,9 @@ mod tests { .partition_map[":db.part/user"].index; let t = format!("[[:db/add {} :db.schema/attribute \"tempid\"]]", next + 1); - match conn.transact(&mut sqlite, t.as_str()).expect_err("expected transact error").downcast() { - Ok(::mentat_db::DbError::UnrecognizedEntid(e)) => { - assert_eq!(e, next + 1); + match conn.transact(&mut sqlite, t.as_str()) { + Err(MentatError::DbError(e)) => { + assert_eq!(e.kind(), ::mentat_db::DbErrorKind::UnrecognizedEntid(next + 1)); }, x => panic!("expected db error, got {:?}", x), } @@ -898,10 +898,10 @@ mod tests { // we should reject this, because the first ID was provided by the user! let t = format!("[[:db/add {} :db.schema/attribute \"tempid\"]]", next); - match conn.transact(&mut sqlite, t.as_str()).expect_err("expected transact error").downcast() { - Ok(::mentat_db::DbError::UnrecognizedEntid(e)) => { + match conn.transact(&mut sqlite, t.as_str()) { + Err(MentatError::DbError(e)) => { // All this, despite this being the ID we were about to allocate! - assert_eq!(e, next); + assert_eq!(e.kind(), ::mentat_db::DbErrorKind::UnrecognizedEntid(next)); }, x => panic!("expected db error, got {:?}", x), } @@ -1059,9 +1059,9 @@ mod tests { // Bad EDN: missing closing ']'. let report = conn.transact(&mut sqlite, "[[:db/add \"t\" :db/ident :a/keyword]"); - match report.expect_err("expected transact to fail for bad edn").downcast() { - Ok(edn::ParseError { .. }) => { }, - Err(x) => panic!("expected EDN parse error, got {:?}", x), + match report.expect_err("expected transact to fail for bad edn") { + MentatError::EdnParseError(_) => { }, + x => panic!("expected EDN parse error, got {:?}", x), } // Good EDN. @@ -1070,9 +1070,9 @@ mod tests { // Bad transaction data: missing leading :db/add. let report = conn.transact(&mut sqlite, "[[\"t\" :db/ident :b/keyword]]"); - match report.expect_err("expected transact error").downcast() { - Ok(edn::ParseError { .. }) => { }, - Err(x) => panic!("expected EDN parse error, got {:?}", x), + match report.expect_err("expected transact error") { + MentatError::EdnParseError(_) => { }, + x => panic!("expected EDN parse error, got {:?}", x), } // Good transaction data. @@ -1082,9 +1082,14 @@ mod tests { // Bad transaction based on state of store: conflicting upsert. let report = conn.transact(&mut sqlite, "[[:db/add \"u\" :db/ident :a/keyword] [:db/add \"u\" :db/ident :b/keyword]]"); - match report.expect_err("expected transact error").downcast() { - Ok(::mentat_db::DbError::SchemaConstraintViolation(_)) => { }, - x => panic!("expected schema constraint violation, got {:?}", x), + match report.expect_err("expected transact error") { + MentatError::DbError(e) => { + match e.kind() { + ::mentat_db::DbErrorKind::SchemaConstraintViolation(_) => {}, + _ => panic!("expected SchemaConstraintViolation"), + } + }, + x => panic!("expected db error, got {:?}", x), } } @@ -1101,8 +1106,8 @@ mod tests { let kw = kw!(:foo/bat); let schema = conn.current_schema(); let res = conn.cache(&mut sqlite, &schema, &kw, CacheDirection::Forward, CacheAction::Register); - match res.expect_err("expected cache to fail").downcast() { - Ok(MentatError::UnknownAttribute(msg)) => assert_eq!(msg, ":foo/bat"), + match res.expect_err("expected cache to fail") { + MentatError::UnknownAttribute(msg) => assert_eq!(msg, ":foo/bat"), x => panic!("expected UnknownAttribute error, got {:?}", x), } } diff --git a/src/entity_builder.rs b/src/entity_builder.rs index 524b32b3..c273b8ca 100644 --- a/src/entity_builder.rs +++ b/src/entity_builder.rs @@ -380,9 +380,6 @@ impl FromThing for TypedValueOr { mod testing { extern crate mentat_db; - // For matching inside a test. - use mentat_db::DbError; - use ::{ Conn, Entid, @@ -422,10 +419,11 @@ mod testing { let mut in_progress = conn.begin_transaction(&mut sqlite).expect("begun successfully"); // This should fail: unrecognized entid. - if let Ok(DbError::UnrecognizedEntid(e)) = in_progress.transact_terms(terms, tempids).expect_err("expected transact to fail").downcast() { - assert_eq!(e, 999); - } else { - panic!("Should have rejected the entid."); + match in_progress.transact_terms(terms, tempids).expect_err("expected transact to fail") { + MentatError::DbError(e) => { + assert_eq!(e.kind(), mentat_db::DbErrorKind::UnrecognizedEntid(999)); + }, + _ => panic!("Should have rejected the entid."), } } diff --git a/src/errors.rs b/src/errors.rs index 29622c5c..337aad52 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -14,16 +14,23 @@ use std; // To refer to std::result::Result. use std::collections::BTreeSet; -use failure::Error; +use rusqlite; + +use edn; use mentat_core::{ Attribute, ValueType, }; +use mentat_db; use mentat_query; +use mentat_query_algebrizer; +use mentat_query_projector; +use mentat_query_pull; +use mentat_sql; -pub type Result = std::result::Result; +pub type Result = std::result::Result; #[macro_export] macro_rules! bail { @@ -34,6 +41,9 @@ macro_rules! bail { #[derive(Debug, Fail)] pub enum MentatError { + #[fail(display = "bad uuid {}", _0)] + BadUuid(String), + #[fail(display = "path {} already exists", _0)] PathAlreadyExists(String), @@ -69,4 +79,78 @@ pub enum MentatError { #[fail(display = "provided value of type {} doesn't match attribute value type {}", _0, _1)] ValueTypeMismatch(ValueType, ValueType), + + #[fail(display = "{}", _0)] + IoError(#[cause] std::io::Error), + + // It would be better to capture the underlying `rusqlite::Error`, but that type doesn't + // implement many useful traits, including `Clone`, `Eq`, and `PartialEq`. + #[fail(display = "SQL error: _0")] + RusqliteError(String), + + #[fail(display = "{}", _0)] + EdnParseError(#[cause] edn::ParseError), + + #[fail(display = "{}", _0)] + DbError(#[cause] mentat_db::DbError), + + #[fail(display = "{}", _0)] + AlgebrizerError(#[cause] mentat_query_algebrizer::AlgebrizerError), + + #[fail(display = "{}", _0)] + ProjectorError(#[cause] mentat_query_projector::ProjectorError), + + #[fail(display = "{}", _0)] + PullError(#[cause] mentat_query_pull::PullError), + + #[fail(display = "{}", _0)] + SQLError(#[cause] mentat_sql::SQLError), +} + +impl From for MentatError { + fn from(error: std::io::Error) -> MentatError { + MentatError::IoError(error) + } +} + +impl From for MentatError { + fn from(error: rusqlite::Error) -> MentatError { + MentatError::RusqliteError(error.to_string()) + } +} + +impl From for MentatError { + fn from(error: edn::ParseError) -> MentatError { + MentatError::EdnParseError(error) + } +} + +impl From for MentatError { + fn from(error: mentat_db::DbError) -> MentatError { + MentatError::DbError(error) + } +} + +impl From for MentatError { + fn from(error: mentat_query_algebrizer::AlgebrizerError) -> MentatError { + MentatError::AlgebrizerError(error) + } +} + +impl From for MentatError { + fn from(error: mentat_query_projector::ProjectorError) -> MentatError { + MentatError::ProjectorError(error) + } +} + +impl From for MentatError { + fn from(error: mentat_query_pull::PullError) -> MentatError { + MentatError::PullError(error) + } +} + +impl From for MentatError { + fn from(error: mentat_sql::SQLError) -> MentatError { + MentatError::SQLError(error) + } } diff --git a/src/lib.rs b/src/lib.rs index c62f3af8..b8e83cd2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -104,6 +104,18 @@ macro_rules! kw { #[macro_use] pub mod errors; +pub use errors::{ + MentatError, + Result, +}; + +pub use edn::ParseError; +pub use mentat_db::DbError; +pub use mentat_query_algebrizer::AlgebrizerError; +pub use mentat_query_projector::ProjectorError; +pub use mentat_query_pull::PullError; +pub use mentat_sql::SQLError; + pub mod conn; pub mod entity_builder; pub mod query; diff --git a/src/store.rs b/src/store.rs index 30856683..52a37d5c 100644 --- a/src/store.rs +++ b/src/store.rs @@ -234,8 +234,8 @@ impl Pullable for Store { } impl Syncable for Store { - fn sync(&mut self, server_uri: &String, user_uuid: &String) -> Result<()> { - let uuid = Uuid::parse_str(&user_uuid)?; + fn sync(&mut self, server_uri: &String, user_uuid: &String) -> ::std::result::Result<(), ::failure::Error> { + let uuid = Uuid::parse_str(&user_uuid).map_err(|_| MentatError::BadUuid(user_uuid.clone()))?; Ok(Syncer::flow(&mut self.sqlite, server_uri, &uuid)?) } } diff --git a/tests/query.rs b/tests/query.rs index 8b3b012e..6cbe188e 100644 --- a/tests/query.rs +++ b/tests/query.rs @@ -233,7 +233,7 @@ fn test_unbound_inputs() { let results = q_uncached(&c, &db.schema, "[:find ?i . :in ?e :where [?e :db/ident ?i]]", inputs); - match results.expect_err("expected unbound variables").downcast().expect("expected specific error") { + match results.expect_err("expected unbound variables") { MentatError::UnboundVariables(vars) => { assert_eq!(vars, vec!["?e".to_string()].into_iter().collect()); }, @@ -411,8 +411,8 @@ fn test_fulltext() { [?a :foo/term ?term] ]"#; let r = conn.q_once(&mut c, query, None); - match r.expect_err("expected query to fail").downcast() { - Ok(mentat_query_algebrizer::AlgebrizerError::InvalidArgument(PlainSymbol(s), ty, i)) => { + match r.expect_err("expected query to fail") { + MentatError::AlgebrizerError(mentat_query_algebrizer::AlgebrizerError::InvalidArgument(PlainSymbol(s), ty, i)) => { assert_eq!(s, "fulltext"); assert_eq!(ty, "string"); assert_eq!(i, 2); @@ -426,8 +426,8 @@ fn test_fulltext() { [?a :foo/term ?term] [(fulltext $ :foo/fts ?a) [[?x ?val]]]]"#; let r = conn.q_once(&mut c, query, None); - match r.expect_err("expected query to fail").downcast() { - Ok(mentat_query_algebrizer::AlgebrizerError::InvalidArgument(PlainSymbol(s), ty, i)) => { + match r.expect_err("expected query to fail") { + MentatError::AlgebrizerError(mentat_query_algebrizer::AlgebrizerError::InvalidArgument(PlainSymbol(s), ty, i)) => { assert_eq!(s, "fulltext"); assert_eq!(ty, "string"); assert_eq!(i, 2); @@ -582,8 +582,8 @@ fn test_aggregates_type_handling() { // No type limits => can't do it. let r = store.q_once(r#"[:find (sum ?v) . :where [_ _ ?v]]"#, None); let all_types = ValueTypeSet::any(); - match r.expect_err("expected query to fail").downcast() { - Ok(::mentat_query_projector::errors::ProjectorError::CannotApplyAggregateOperationToTypes( + match r.expect_err("expected query to fail") { + MentatError::ProjectorError(::mentat_query_projector::errors::ProjectorError::CannotApplyAggregateOperationToTypes( SimpleAggregationOp::Sum, types)) => { assert_eq!(types, all_types); }, @@ -594,8 +594,8 @@ fn test_aggregates_type_handling() { let r = store.q_once(r#"[:find (sum ?v) . :where [_ _ ?v] [(type ?v :db.type/instant)]]"#, None); - match r.expect_err("expected query to fail").downcast() { - Ok(::mentat_query_projector::errors::ProjectorError::CannotApplyAggregateOperationToTypes( + match r.expect_err("expected query to fail") { + MentatError::ProjectorError(::mentat_query_projector::errors::ProjectorError::CannotApplyAggregateOperationToTypes( SimpleAggregationOp::Sum, types)) => { assert_eq!(types, ValueTypeSet::of_one(ValueType::Instant)); @@ -1336,8 +1336,8 @@ fn test_aggregation_implicit_grouping() { [?person :foo/play ?game] [?person :foo/is-vegetarian true] [?person :foo/name ?name]]"#, None); - match res.expect_err("expected query to fail").downcast() { - Ok(::mentat_query_projector::errors::ProjectorError::AmbiguousAggregates(mmc, cc)) => { + match res.expect_err("expected query to fail") { + MentatError::ProjectorError(::mentat_query_projector::errors::ProjectorError::AmbiguousAggregates(mmc, cc)) => { assert_eq!(mmc, 2); assert_eq!(cc, 1); }, diff --git a/tests/vocabulary.rs b/tests/vocabulary.rs index 9e4841d2..e8f7ce5d 100644 --- a/tests/vocabulary.rs +++ b/tests/vocabulary.rs @@ -286,8 +286,8 @@ fn test_add_vocab() { // Scoped borrow of `conn`. { let mut in_progress = conn.begin_transaction(&mut sqlite).expect("begun successfully"); - match in_progress.ensure_vocabulary(&foo_v1_malformed).expect_err("expected vocabulary to fail").downcast() { - Ok(MentatError::ConflictingAttributeDefinitions(vocab, version, attr, theirs, ours)) => { + match in_progress.ensure_vocabulary(&foo_v1_malformed).expect_err("expected vocabulary to fail") { + MentatError::ConflictingAttributeDefinitions(vocab, version, attr, theirs, ours) => { assert_eq!(vocab.as_str(), ":org.mozilla/foo"); assert_eq!(attr.as_str(), ":foo/baz"); assert_eq!(version, 1); diff --git a/tools/cli/src/mentat_cli/repl.rs b/tools/cli/src/mentat_cli/repl.rs index ce08286a..6c3564e0 100644 --- a/tools/cli/src/mentat_cli/repl.rs +++ b/tools/cli/src/mentat_cli/repl.rs @@ -11,7 +11,6 @@ use std::io::Write; use failure::{ - err_msg, Error, }; @@ -404,7 +403,8 @@ impl Repl { if self.path.is_empty() || path != self.path { let next = match encryption_key { #[cfg(not(feature = "sqlcipher"))] - Some(_) => return Err(err_msg(".open_encrypted and .empty_encrypted require the sqlcipher Mentat feature")), + Some(_) => return Err(::mentat::MentatError::RusqliteError(".open_encrypted and .empty_encrypted require the sqlcipher Mentat feature".into())), + #[cfg(feature = "sqlcipher")] Some(k) => { if empty {