Skip to main content

db_traits/
errors.rs

1// Copyright 2016-2018 Mozilla
2//
3// Licensed under the Apache License, Version 2.0 (the "License"); you may not use
4// this file except in compliance with the License. You may obtain a copy of the
5// License at http://www.apache.org/licenses/LICENSE-2.0
6// Unless required by applicable law or agreed to in writing, software distributed
7// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
8// CONDITIONS OF ANY KIND, either express or implied. See the License for the
9// specific language governing permissions and limitations under the License.
10
11#![allow(dead_code)]
12
13use thiserror::Error;
14
15use std::collections::{BTreeMap, BTreeSet};
16
17use rusqlite;
18
19use edn::entities::TempId;
20
21use core_traits::{Entid, KnownEntid, TypedValue, ValueType};
22
23pub type Result<T> = ::std::result::Result<T, DbError>;
24
25/// Return early with an error, converting the given error value into the
26/// function's error type via `From`. Replacement for `failure::bail!`.
27#[macro_export]
28macro_rules! bail {
29    ($e:expr) => {
30        return ::std::result::Result::Err(::std::convert::From::from($e))
31    };
32}
33
34/// Attach a `DbErrorKind` context to a `Result`, discarding the original
35/// error's payload (matching the prior `failure`-based behavior, which only
36/// surfaced the context kind). Replacement for `failure::ResultExt`.
37pub trait ResultExt<T> {
38    fn context(self, kind: DbErrorKind) -> Result<T>;
39}
40
41impl<T, E> ResultExt<T> for ::std::result::Result<T, E> {
42    fn context(self, kind: DbErrorKind) -> Result<T> {
43        self.map_err(|_| DbError::from(kind))
44    }
45}
46
47// TODO Error/ErrorKind pair
48#[derive(Clone, Debug, Eq, PartialEq)]
49pub enum CardinalityConflict {
50    /// A cardinality one attribute has multiple assertions `[e a v1], [e a v2], ...`.
51    CardinalityOneAddConflict {
52        e: Entid,
53        a: Entid,
54        vs: BTreeSet<TypedValue>,
55    },
56
57    /// A datom has been both asserted and retracted, like `[:db/add e a v]` and `[:db/retract e a v]`.
58    AddRetractConflict {
59        e: Entid,
60        a: Entid,
61        vs: BTreeSet<TypedValue>,
62    },
63}
64
65// TODO Error/ErrorKind pair
66#[derive(Clone, Debug, Eq, PartialEq)]
67pub enum SchemaConstraintViolation {
68    /// A transaction tried to assert datoms where one tempid upserts to two (or more) distinct
69    /// entids.
70    ConflictingUpserts {
71        /// A map from tempid to the entids it would upsert to.
72        ///
73        /// In the future, we might even be able to attribute the upserts to particular (reduced)
74        /// datoms, i.e., to particular `[e a v]` triples that caused the constraint violation.
75        /// Attributing constraint violations to input data is more difficult to the multiple
76        /// rewriting passes the input undergoes.
77        conflicting_upserts: BTreeMap<TempId, BTreeSet<KnownEntid>>,
78    },
79
80    /// A transaction tried to assert a datom or datoms with the wrong value `v` type(s).
81    TypeDisagreements {
82        /// The key (`[e a v]`) has an invalid value `v`: it is not of the expected value type.
83        conflicting_datoms: BTreeMap<(Entid, Entid, TypedValue), ValueType>,
84    },
85
86    /// A transaction tried to assert datoms that don't observe the schema's cardinality constraints.
87    CardinalityConflicts { conflicts: Vec<CardinalityConflict> },
88}
89
90impl ::std::fmt::Display for SchemaConstraintViolation {
91    fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
92        use self::SchemaConstraintViolation::*;
93        match self {
94            ConflictingUpserts {
95                ref conflicting_upserts,
96            } => {
97                writeln!(f, "conflicting upserts:")?;
98                for (tempid, entids) in conflicting_upserts {
99                    writeln!(f, "  tempid {:?} upserts to {:?}", tempid, entids)?;
100                }
101                Ok(())
102            }
103            TypeDisagreements {
104                ref conflicting_datoms,
105            } => {
106                writeln!(f, "type disagreements:")?;
107                for (datom, expected_type) in conflicting_datoms {
108                    writeln!(
109                        f,
110                        "  expected value of type {} but got datom [{} {} {:?}]",
111                        expected_type, datom.0, datom.1, datom.2
112                    )?;
113                }
114                Ok(())
115            }
116            CardinalityConflicts { ref conflicts } => {
117                writeln!(f, "cardinality conflicts:")?;
118                for conflict in conflicts {
119                    writeln!(f, "  {:?}", conflict)?;
120                }
121                Ok(())
122            }
123        }
124    }
125}
126
127#[derive(Copy, Clone, Eq, PartialEq, Debug)]
128pub enum InputError {
129    /// Map notation included a bad `:db/id` value.
130    BadDbId,
131
132    /// A value place cannot be interpreted as an entity place (for example, in nested map
133    /// notation).
134    BadEntityPlace,
135}
136
137impl ::std::fmt::Display for InputError {
138    fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
139        use self::InputError::*;
140        match self {
141            BadDbId => {
142                writeln!(f, ":db/id in map notation must either not be present or be an entid, an ident, or a tempid")
143            }
144            BadEntityPlace => {
145                writeln!(f, "cannot convert value place into entity place")
146            }
147        }
148    }
149}
150
151#[derive(Clone, Debug, Error)]
152#[error(transparent)]
153pub struct DbError {
154    inner: DbErrorKind,
155}
156
157impl DbError {
158    pub fn kind(&self) -> DbErrorKind {
159        self.inner.clone()
160    }
161}
162
163impl From<DbErrorKind> for DbError {
164    fn from(kind: DbErrorKind) -> Self {
165        DbError { inner: kind }
166    }
167}
168
169impl From<sql_traits::conn::SqlError> for DbError {
170    fn from(error: sql_traits::conn::SqlError) -> Self {
171        DbError {
172            inner: DbErrorKind::RusqliteError(error.message),
173        }
174    }
175}
176
177impl From<rusqlite::Error> for DbError {
178    fn from(error: rusqlite::Error) -> Self {
179        DbError {
180            inner: DbErrorKind::RusqliteError(error.to_string()),
181        }
182    }
183}
184
185#[derive(Clone, PartialEq, Debug, Error)]
186pub enum DbErrorKind {
187    /// We're just not done yet.  Recognized a feature that is not yet implemented.
188    #[error("not yet implemented: {0}")]
189    NotYetImplemented(String),
190
191    /// We've been given a value that isn't the correct Mentat type.
192    #[error("value '{0}' is not the expected Mentat value type {1:?}")]
193    BadValuePair(String, ValueType),
194
195    /// We've got corrupt data in the SQL store: a value and value_type_tag don't line up.
196    /// TODO _1.data_type()
197    #[error("bad SQL (value_type_tag, value) pair: ({0:?}, {1:?})")]
198    BadSQLValuePair(sql_traits::conn::SqlValue, i32),
199
200    /// The SQLite store user_version isn't recognized.  This could be an old version of Mentat
201    /// trying to open a newer version SQLite store; or it could be a corrupt file; or ...
202    /// #[fail(display = "bad SQL store user_version: {}", _0)]
203    /// BadSQLiteStoreVersion(i32),
204    /// A bootstrap definition couldn't be parsed or installed.  This is a programmer error, not
205    /// a runtime error.
206    #[error("bad bootstrap definition: {0}")]
207    BadBootstrapDefinition(String),
208
209    /// A schema assertion couldn't be parsed.
210    #[error("bad schema assertion: {0}")]
211    BadSchemaAssertion(String),
212
213    /// An ident->entid mapping failed.
214    #[error("no entid found for ident: {0}")]
215    UnrecognizedIdent(String),
216
217    /// An entid->ident mapping failed.
218    #[error("no ident found for entid: {0}")]
219    UnrecognizedEntid(Entid),
220
221    /// Tried to transact an entid that isn't allocated.
222    #[error("entid not allocated: {0}")]
223    UnallocatedEntid(Entid),
224
225    #[error("unknown attribute for entid: {0}")]
226    UnknownAttribute(Entid),
227
228    #[error("cannot reverse-cache non-unique attribute: {0}")]
229    CannotCacheNonUniqueAttributeInReverse(Entid),
230
231    #[error("schema alteration failed: {0}")]
232    SchemaAlterationFailed(String),
233
234    /// A transaction tried to violate a constraint of the schema of the Mentat store.
235    #[error("schema constraint violation: {0}")]
236    SchemaConstraintViolation(SchemaConstraintViolation),
237
238    /// The transaction was malformed in some way (that was not recognized at parse time; for
239    /// example, in a way that is schema-dependent).
240    #[error("transaction input error: {0}")]
241    InputError(InputError),
242
243    #[error(
244        "Cannot transact a fulltext assertion with a typed value that is not :db/valueType :db.type/string"
245    )]
246    WrongTypeValueForFtsAssertion,
247
248    // SQL errors.
249    #[error("could not update a cache")]
250    CacheUpdateFailed,
251
252    #[error("Could not set_user_version")]
253    CouldNotSetVersionPragma,
254
255    #[error("Could not get_user_version")]
256    CouldNotGetVersionPragma,
257
258    #[error("Could not search!")]
259    CouldNotSearch,
260
261    #[error("Could not insert transaction: failed to add datoms not already present")]
262    TxInsertFailedToAddMissingDatoms,
263
264    #[error("Could not insert transaction: failed to retract datoms already present")]
265    TxInsertFailedToRetractDatoms,
266
267    #[error("Could not update datoms: failed to retract datoms already present")]
268    DatomsUpdateFailedToRetract,
269
270    #[error("Could not update datoms: failed to add datoms not already present")]
271    DatomsUpdateFailedToAdd,
272
273    #[error("Failed to create temporary tables")]
274    FailedToCreateTempTables,
275
276    #[error("Could not insert non-fts one statements into temporary search table!")]
277    NonFtsInsertionIntoTempSearchTableFailed,
278
279    #[error("Could not insert fts values into fts table!")]
280    FtsInsertionFailed,
281
282    #[error("Could not insert FTS statements into temporary search table!")]
283    FtsInsertionIntoTempSearchTableFailed,
284
285    #[error("Could not drop FTS search ids!")]
286    FtsFailedToDropSearchIds,
287
288    #[error("Could not update partition map")]
289    FailedToUpdatePartitionMap,
290
291    #[error("Can't operate over mixed timelines")]
292    TimelinesMixed,
293
294    #[error("Can't move transactions to a non-empty timeline")]
295    TimelinesMoveToNonEmpty,
296
297    #[error("Supplied an invalid transaction range")]
298    TimelinesInvalidRange,
299
300    /// A `:db.fn/cas` (compare-and-swap) found the current value of `[e a]`
301    /// did not match the expected `old` value, so the swap was rejected and the
302    /// whole transaction aborts. `expected`/`actual` are `None` when the value
303    /// is nil/absent.
304    #[error("compare-and-swap failed for [{e} {a}]: expected {}, got {}", .expected.as_deref().unwrap_or("nil"), .actual.as_deref().unwrap_or("nil"))]
305    CasMismatch {
306        e: i64,
307        a: i64,
308        expected: Option<String>,
309        actual: Option<String>,
310    },
311
312    // It would be better to capture the underlying `rusqlite::Error`, but that type doesn't
313    // implement many useful traits, including `Clone`, `Eq`, and `PartialEq`.
314    #[error("SQL error: {0}")]
315    RusqliteError(String),
316}