Error handling

July 10, 2026 ยท View on GitHub

Every method that can fail takes an NSError ** out-parameter. On failure the error's domain is MongrelDBErrorDomain and its code is one of the MongrelDBErrorCode constants. The localizedDescription carries the daemon's message when one was supplied.


The error model

The client uses two complementary mechanisms:

  1. Error codes - MongrelDBErrorAuth, MongrelDBErrorNotFound, MongrelDBErrorConflict, MongrelDBErrorQuery, MongrelDBErrorNetwork, MongrelDBErrorJSON, MongrelDBErrorInvalidArg. Switch on these to branch on the category of failure.
  2. localizedDescription - a human-readable message for the failure, including the daemon's structured error code when the server supplied one.

Error code reference

CodeValueMeaningTypical cause
(success)0successmethod returned YES / a value with nil error
MongrelDBErrorAuth-1HTTP 401 or 403Missing/bad credentials against an auth-enabled daemon
MongrelDBErrorNotFound-2HTTP 404Missing table, missing schema, dropped resource
MongrelDBErrorConflict-3HTTP 409Unique, foreign-key, check, or trigger violation at commit
MongrelDBErrorQuery-4HTTP 400 or 5xxMalformed request, server-side failure, everything else
MongrelDBErrorNetwork-5transport errorConnection refused, timeout, DNS failure
MongrelDBErrorJSON-6client-sideMalformed JSON response from the server
MongrelDBErrorInvalidArg-8client-sidenil or otherwise invalid argument

The daemon's error envelope

When the daemon rejects a request, it returns a JSON envelope decoded into the NSError's localizedDescription:

{
  "status": "aborted",
  "error": {
    "code": "UNIQUE_VIOLATION",
    "message": "duplicate key in column 1",
    "op_index": 0
  }
}

Structured codes you will commonly see in the message:

codeMeaning
UNIQUE_VIOLATIONA unique/PK constraint rejected the commit
FK_VIOLATIONA foreign-key reference was missing
CHECK_VIOLATIONA check constraint or trigger rejected the commit
NOT_FOUNDA named resource (table, schema) does not exist

HTTP status -> code mapping

HTTP statusCodeNotes
401, 403MongrelDBErrorAuthBad/missing credentials
404MongrelDBErrorNotFoundResource not found
409MongrelDBErrorConflictConstraint violation at commit
400MongrelDBErrorQueryMalformed request / bad query
5xxMongrelDBErrorQueryDaemon-side failure
other non-2xxMongrelDBErrorQueryCatch-all
2xx(success)No error

Discriminating errors

Switch on the error code:

NSDictionary *body = [db schemaForTable:@"missing_table" error:&e];
if (e) {
    switch (e.code) {
        case MongrelDBErrorNotFound:
            NSLog(@"table does not exist: %@", e.localizedDescription);
            break;
        case MongrelDBErrorConflict:
            NSLog(@"unexpected conflict on a read: %@", e.localizedDescription);
            break;
        case MongrelDBErrorAuth:
            NSLog(@"bad credentials: %@", e.localizedDescription);
            break;
        case MongrelDBErrorQuery:
            NSLog(@"server error: %@", e.localizedDescription);
            break;
        case MongrelDBErrorNetwork:
            NSLog(@"can't reach daemon: %@", e.localizedDescription);
            break;
        default:
            NSLog(@"error: %@", e.localizedDescription);
            break;
    }
    e = nil; /* reset before the next call */
}

Recovery patterns

Auth failure - do not retry blindly

A retry will not fix bad credentials. Surface the error to the caller or operator.

Not found - fall back, do not crash

For lookups by primary key, a 404 may be a normal "absent" result (when the table itself is missing). Treat it accordingly.

Constraint conflict - the engine already rolled back

if (e.code == MongrelDBErrorConflict) {
    NSLog(@"constraint violated: %@", e.localizedDescription);
    /* The engine already discarded the whole batch. Nothing to undo. */
}

Transient failure - retry with an idempotency key

MongrelDBErrorNetwork and MongrelDBErrorQuery (for 5xx) cover transport and transient server failures. With an idempotency key, retrying a transaction is safe (see transactions.md).

Next steps