Skip to main content

DbError

Enum DbError 

Source
#[non_exhaustive]
pub enum DbError {
Show 33 variants Engine(Error), Migration { to: u32, reason: String, }, InvalidEdgeType(String), SingleOpenViolation { source_id: String, target_id: String, edge_type: String, }, NotFound(String), DimMismatch { got: usize, expected: usize, model: String, }, InvalidModelName(String), ModelNotRegistered { model: String, table: String, }, SubgraphTooLarge { n: usize, budget: usize, }, NegativeEdgeWeight { source_id: String, target_id: String, weight: f64, }, ReplayCorrupt { seq: i64, reason: String, }, SnapshotIncompatible { path: String, reason: String, }, SnapshotCorrupt { path: String, reason: String, }, PayloadVersion { got: u8, max: u8, }, ArchiveViolation { table: String, }, ArchiveSessionLeaked { marker: String, }, AttributeModeUnstated { instants: StatedInstants, }, DiagnosticConn { path: String, reason: String, }, ArchiveWindow { window: Duration, reason: String, }, HalfLifeWithoutInstant, RecordedInstantUnreachable { ts: String, }, InvalidTimestamp { value: String, reason: String, }, InvalidId { id: String, reason: String, }, OverlappingInterval { overlap: Box<Overlap>, }, CurrentDrift { n: usize, }, RebuildFailed { n: usize, }, RebuildInterrupted { reason: String, }, WriterUnavailable, WriterDroppedResponder, WriterStopped(String), RecordedAtRegression { got: String, had: String, }, FutureRecordedAt { stamp: String, limit: String, }, BulkCancelled,
}
Expand description

Central error type for the Macrame bitemporal ledger database.

Variants (Non-exhaustive)§

This enum is marked as non-exhaustive
Non-exhaustive enums could have additional variants added in future. Therefore, when matching against variants of non-exhaustive enums, an extra wildcard arm must be added to account for any future variants.
§

Engine(Error)

§

Migration

Fields

§to: u32
§reason: String
§

InvalidEdgeType(String)

§

SingleOpenViolation

Fields

§source_id: String
§target_id: String
§edge_type: String
§

NotFound(String)

§

DimMismatch

Fields

§got: usize
§expected: usize
§model: String
§

InvalidModelName(String)

A model name is spliced into DDL and queries as a table identifier, and identifiers cannot be bound as parameters. Validating the name is what makes that splice safe, so an invalid one is refused rather than escaped.

§

ModelNotRegistered

Fields

§model: String
§table: String
§

SubgraphTooLarge

Fields

§budget: usize
§

NegativeEdgeWeight

Dijkstra and A* settle a node permanently the first time they pop it, which is only sound when no later edge can reduce the distance — that is, when weights are non-negative. links.weight is a bare REAL NOT NULL with no CHECK, so the guarantee has to be established at load time. The alternative is a shortest-path result that is quietly just a path.

Fields

§source_id: String
§target_id: String
§weight: f64
§

ReplayCorrupt

Fields

§seq: i64
§reason: String
§

SnapshotIncompatible

A snapshot this build cannot read. Distinct from Self::ReplayCorrupt on purpose: corruption is a fault to report, an incompatible snapshot is the ordinary consequence of an upgrade, and the correct response is to discard the file and fold from the log instead (D-043).

Fields

§path: String
§reason: String
§

SnapshotCorrupt

A snapshot that is damaged rather than foreign (0.13.12, W8.2, D-185).

The third case in the same family, and it needed its own name for the reason D-069 gives: an error that names the wrong subject sends a caller to fix the wrong thing.

  • Self::SnapshotIncompatiblea different build wrote this. Ordinary after an upgrade.
  • Self::ReplayCorruptthe ledger is damaged. The log is the only authority in this system and this is the worst thing it can say.
  • This — the cache is damaged. The ledger is untouched. Deleting the file restores correctness and costs a slower reconstruction, because Doctrine VI makes a snapshot derivative and disposable.

Every failure of load_snapshot used to be ReplayCorrupt { seq: 0 }, which claimed the ledger was damaged and carried a sequence number that cannot exist — AUTOINCREMENT starts at 1. That is the same placeholder D-069 removed from InvalidTimestamp, left in place here because nothing had cause to look at it.

It carries the path and not a seq, because a snapshot is identified by its file. The reason names the check that failed, and the checks are ordered so that the earliest possible one fires: declared length, then checksum, then the decompressed size against what the header declared.

Fields

§path: String
§reason: String
§

PayloadVersion

Fields

§got: u8
§max: u8
§

ArchiveViolation

Fields

§table: String
§

ArchiveSessionLeaked

The archive-session marker exists as committed state (0.10.0, W2).

ArchiveViolation is this guard working. This variant is the guard having been silently switched off: while macrame_archive_session is present, trg_concepts_guard_delete, trg_links_guard_delete and trg_txlog_guard_delete all evaluate their WHEN to false and permit the deletes they exist to refuse, and trg_concepts_log_insert writes no transaction_log row for a concept insert. Doctrine IV and Doctrine V are both suspended, with no error and no counter — which is why the condition needs a name of its own.

It cannot be produced by an archive session, crashed or otherwise. archive() and archive_windowed() create and drop the marker inside the same transaction that does the work, so a commit drops it and a rollback discards it; and the check that raises this error — verify in src/schema/migrations.rs, which is private, hence the file reference rather than a link — reads committed state, so it cannot see an in-flight session. Reaching this error therefore means something wrote the table outside the write actor — the raw-writer case §4.7 concedes exists.

Not a Migration error: the schema is intact. What is wrong is the database’s contents, and saying “your schema is wrong” would send the reader to the migration ladder for a fault a DROP TABLE fixes.

Fields

§marker: String
§

AttributeModeUnstated

A traversal asked about the past without saying which text it wanted (T3.2, D-085).

An instant on either axis fixes the topology. Node attributes are a second, independent question, and the default answer — AttributeMode::Current — is live text. That combination returns the past’s graph wearing the present’s titles, which is a legitimate thing to want and a terrible thing to get by accident.

It used to be a tracing::warn!, which is invisible in any application that has not configured a subscriber. This is the same statement as a value the caller cannot miss.

Fix by stating the mode: .attribute_mode(AttributeMode::AtTime) for the past’s text, or .attribute_mode(AttributeMode::Current) to affirm that live text is what was meant.

§It carries StatedInstants rather than one string (0.13.10, W7.7, D-183)

The field was as_of: String and the message rendered it as as_of(…) — a method removed in 0.12.17 when D-174 split the axes. Both instants collapsed into it through an .or(), so a caller who set as_of_recorded was told about as_of, a caller who set both was told about one of them, and neither was told which clock they had asked about. Naming the axis is the whole remedy this error offers.

Fields

§

DiagnosticConn

crate::Database::diagnostic_conn could not open the file read-only (T5.1, D-091).

Its own variant rather than NotFound, which renders “node {0} not found” — naming the wrong subject is the defect D-069 was written to correct, and a file is not a node.

The case worth the sentence is a missing file: SQLITE_OPEN_READ_ONLY drops SQLITE_OPEN_CREATE with it, so a path that does not exist is SQLITE_CANTOPEN rather than a fresh empty database. That is the right behaviour and an opaque error to receive.

Fields

§path: String
§reason: String
§

ArchiveWindow

crate::Database::archive_windowed was given a window it cannot use (T1.1, D-080).

Carries a reason rather than the numbers as fields because the two cases it covers are not the same shape — a zero-length window never advances at all, while a merely narrow one produces a session count that has to be quoted against the limit to mean anything. A caller reading this needs the sentence, not the struct.

It is an error rather than a silent clamp on purpose. Rounding a one-second window up to something workable would archive over boundaries the caller did not choose, and the caller cannot see that it happened.

Fields

§window: Duration
§reason: String
§

HalfLifeWithoutInstant

A search asked for decay without saying what age is measured from (0.13.20, W9.5, D-193).

Decay ranks a hit by how old the thing it matched is, and old is only meaningful relative to an instant. The crate does not read a wall clock on a read path — that is what makes the suite’s FakeClock able to pin these answers at all — so the instant has to be stated, and the one to state is the one the search is already bounded by.

Refusing rather than defaulting to now: a default here would silently make every decayed search a search about the present, which is exactly the class of quiet substitution F-35 and D-175 were about.

§

RecordedInstantUnreachable

A read named a transaction-time instant the hot log can no longer answer for (0.13.2, W7.1, D-174; extended 0.13.16, W9.1, D-189).

A transaction-time read folds transaction_log, and crate::Database::archive removes superseded rows from it. Once anything has been archived, an instant below the cutoff is not before history, it is history that is in the other file — and these readers take a connection, not an archive path, so they cannot go and get it.

Two surfaces fold the log and both raise this. crate::graph::TraversalBuilder::as_of_recorded folds it for topology; crate::temporal::hydrate_attributes folds it for the text under crate::graph::AttributeMode::AtTime. The second was added in 0.13.16 (W9.1), where it had been returning a quietly shorter Vec — §3.2 of the review, and the same silence in the same wave as the first.

Conservative by one bit, deliberately. The test is hot_log_is_intact: whether anything was ever removed. It cannot ask whether this particular instant is above the archive cutoff, because the cutoff is not recorded in the hot log — that is exactly what the hot-side marker D-132 refused would have carried. So an archived database refuses every as_of_recorded, including instants it could in principle have answered. The alternative is answering some of them from a partial fold, which returns nearly the right topology, and on a ledger that is the worst failure available.

crate::temporal::reconstruct takes the archive path and answers the same question, which is why the message names it.

Fields

§

InvalidTimestamp

A timestamp that is not in canonical form (§4.1, D-029).

Distinct from Self::ReplayCorrupt, which is what this used to be (Wave 4.5). timestamp::normalize and timestamp::parse reported bad caller input as ReplayCorrupt { seq: 0 } — a claim that the ledger is damaged, carrying a sequence number that cannot exist because AUTOINCREMENT starts at 1. The same mistake as defect J: an error that names the wrong subject sends a caller to fix the wrong thing.

The value is reported rather than the provenance, because one function serves both directions — a caller passing 2026-01-01T00:00:00Z and a stored recorded_at that will not parse produce the same complaint about the same string. SystemClock::new is where the second case is interpreted, and it already logs and floors to the wall clock (D-027).

Fields

§value: String
§reason: String
§

InvalidId

An identifier the crate’s own encodings cannot represent (D-061).

Distinct from Self::NotFound, and the distinction is defect J: this id was refused, not looked up. validate_id used to return NotFound here, which tells a caller the thing is missing and invites them to create it — with the same id, which will be refused again.

Fields

§reason: String
§

OverlappingInterval

Two valid-time intervals for one relationship claim the same instant.

Distinct from Self::SingleOpenViolation, which is the storage layer’s guard and covers only the open sentinel. This is the general case, and it is refused at the API rather than by a trigger (D-060): raw SQL against the same file can still write an overlap, and §4.2 says so.

The consequence of allowing one is not an error later but a wrong answer: query_as_of_edges at an instant inside both returns the relationship twice, and every weighted algorithm downstream double-counts that edge.

Boxed, and it is the only variant that is (D-075). Seven Strings is 168 bytes, which made DbError — and therefore every Result in the crate, on the Ok path too — larger than clippy::result_large_err’s threshold the moment D-060 added it. The other variants are well under. Boxing the rarest one keeps the whole error small rather than trimming what a caller is told; matches!(err, OverlappingInterval { .. }) is unaffected, which is how every call site uses it.

Fields

§overlap: Box<Overlap>
§

CurrentDrift

Fields

§

RebuildFailed

Fields

§

RebuildInterrupted

A chunked shadow rebuild was abandoned rather than committed (T1.2, D-082).

Distinct from Self::RebuildFailed, and the distinction is the whole point: RebuildFailed means the repair ran and did not repair, which is a reason to distrust the ledger. This means the repair did not run — something invalidated the work in progress and it was discarded before it could be swapped in. links_current is untouched and whatever was true of it before is still true. The action is to retry.

Fields

§reason: String
§

WriterUnavailable

§

WriterDroppedResponder

§

WriterStopped(String)

The actor’s task did not join cleanly at crate::Database::close.

Distinct from Self::WriterUnavailable, which means the channel is gone while the handle is still in use. This is the shutdown path telling a caller that the write actor panicked — which close() used to swallow, so a database whose write path had died closed “successfully” (Wave 4.2).

§

RecordedAtRegression

Fields

§

FutureRecordedAt

The stored transaction-time floor is in the future (0.13.5, W7.4, §3.4).

The clock is raised to MAX(recorded_at) at open so that stamps stay strictly increasing across restarts. That makes a single row from the future — a skewed host, a bad import, a fixture that escaped — this process’s floor, and every stamp it issues lands at or after it. Those rows are then written, so the next open reads the same floor back: the damage is permanent, and it spreads.

Refused at open rather than absorbed, which is where the crate can still tell the difference between a stamp it wrote and one it did not. macrame::FutureStampPolicy widens or waives the bound; waiving it opens the file to be read, and does not repair it.

Fields

§stamp: String
§limit: String
§

BulkCancelled

A chunked bulk write stopped because its caller asked it to (0.13.8, W7.6, D-181).

Not a failure of the ledger, and the only DbError a caller can cause on purpose. Nothing is rolled back: the chunks that committed before the token was seen are committed, which is the same per-chunk boundary crate::Database::bulk_import already documents. How many rows those were is on BulkInterrupted::written, the error this arrives inside.

It carries no count of its own precisely so that there is one place to read the count from, whether the stop was a cancellation or a constraint.

Trait Implementations§

Source§

impl Debug for DbError

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Display for DbError

Source§

fn fmt(&self, __formatter: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Error for DbError

Source§

fn source(&self) -> Option<&(dyn Error + 'static)>

Returns the lower-level source of this error, if any. Read more
1.0.0 · Source§

fn description(&self) -> &str

👎Deprecated since 1.42.0:

use the Display impl or to_string()

1.0.0 · Source§

fn cause(&self) -> Option<&dyn Error>

👎Deprecated since 1.33.0:

replaced by Error::source, which can support downcasting

Source§

fn provide<'a>(&'a self, request: &mut Request<'a>)

🔬This is a nightly-only experimental API. (error_generic_member_access)
Provides type-based access to context intended for error reports. Read more
Source§

impl From<BulkInterrupted> for DbError

Source§

fn from(e: BulkInterrupted) -> Self

Discards written. That is the point: a caller writing ? into a function returning Result has decided the partial count is not something they will act on, and this puts that decision at the place it is taken rather than inside the crate.

Source§

impl From<Error> for DbError

Source§

fn from(source: Error) -> Self

Converts to this type from the input type.

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoRequest<T> for T

Source§

fn into_request(self) -> Request<T>

Wrap the input message T in a tonic::Request
Source§

impl<T> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToString for T
where T: Display + ?Sized,

Source§

fn to_string(&self) -> String

Converts the given value to a String. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V

Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more