Skip to main content
Version: 1.6.0

Errors API Reference

Quark never returns a bare string error you have to pattern-match. Every failure mode it knows about wraps a package-level sentinel, so you branch with errors.Is and keep the original message for logs. This page lists the sentinels and explains, one by one, exactly what causes the non-obvious ones.

Sentinel Errors

var (
ErrNotFound = errors.New("record not found")
ErrInvalidModel = errors.New("invalid model")
ErrInvalidQuery = errors.New("invalid query")
ErrInvalidIdentifier = errors.New("invalid identifier")
ErrInvalidJSONPath = errors.New("invalid JSON path")
ErrInvalidJoin = errors.New("invalid JOIN ON clause")
ErrStaleEntity = errors.New("stale entity (optimistic-locking conflict)")
ErrUnsupportedFeature = errors.New("feature not supported by dialect")
ErrInvalidTimezone = errors.New("invalid column timezone")
ErrDialectNotSupported = errors.New("dialect not supported")
ErrConnection = errors.New("database connection error")
ErrTimeout = errors.New("query timeout")
ErrConstraintViolation = errors.New("constraint violation")

// Event bus + inbound LISTEN/NOTIFY listener
ErrEventEmitFailed = errors.New("event emit failed after commit")
ErrListenerClosed = errors.New("event listener closed")
ErrNoSubscription = errors.New("event listener has no channel subscribed")
)

ErrInvalidIdentifier

A table or column name was rejected before any SQL was assembled. It covers every identifier you can pass in: a Where / OrderBy / GroupBy column, a table name, a CTE name, an event channel, a migration target.

To be accepted, a name must match ^[a-zA-Z_][a-zA-Z0-9_]*$, be at most 64 characters, and not be a reserved SQL keyword. Identifiers cannot be bound as parameters, which is why they are checked instead: a rejected one never reaches the database. The sentinel is wrapped at the point of rejection, so errors.Is(err, quark.ErrInvalidIdentifier) holds no matter which call site produced it.

ErrInvalidJSONPath

WhereJSON received a path outside its dotted-identifier grammar: ^[a-zA-Z_][a-zA-Z0-9_]*(\.[a-zA-Z_][a-zA-Z0-9_]*)*$, up to 256 characters. Quark binds the path as a parameter on every dialect, so a malformed one is not an injection risk — it is rejected at the API surface so you get a clear error instead of surprising SQL.

ErrUnsupportedFeature

The active dialect does not implement what the builder asked for — for example ForUpdate on SQLite, or NoWait on SQL Server. Quark returns this rather than emitting SQL the engine will reject.

The message names the dialect and the specific gap, so you can branch by engine or fall back to another strategy — dropping down to a transaction-level lock, for instance.

ErrStaleEntity

An Update, UpdateFields, or Tracked.Save on a model with a quark:"version" field tried to write a row that had already moved on. The version predicate did not match, so no rows were written. Reload and replay, or surface the conflict to the caller. See Optimistic Locking for the tag contract and a retry example.

ErrInvalidJoin

The ON clause of a Join / LeftJoin / RightJoin fell outside the identifier-only grammar Quark validates at execution time. This applies to both .On(left, op, right) and .OnRaw(clause).

The grammar accepts identifier-to-identifier comparisons (a.b = c.d), joined by AND / OR, using the operators =, !=, <>, <, <=, >, >=. Literals, function calls, subqueries, and parentheses are rejected. Prefer the structured .On(...) form; .OnRaw is the escape hatch for clauses that don't fit the simple binary shape.

ErrInvalidTimezone

A model field carries a quark:"tz=..." tag whose value is not a valid IANA timezone name. Client.RegisterModel and Client.Migrate reject the model immediately rather than failing later on the first query that binds or scans the column, and the wrapped error names the field, the column, and the offending string. See Timezones for the per-column contract.

Event errors

Three sentinels come from the event subsystem:

  • ErrEventEmitFailed wraps an EventBus.Publish failure that happens after the transaction commits. The commit stands; only the post-commit emit could not be delivered.
  • ErrListenerClosed — you called Listen after closing the inbound PostgreSQL LISTEN/NOTIFY listener.
  • ErrNoSubscription — you called Receive before subscribing to any channel.

See Events for the bus and listener contracts.

Error Checking

user, err := quark.For[User](ctx, client).Find(id)
if errors.Is(err, quark.ErrNotFound) {
return nil, fmt.Errorf("user %d not found", id)
}
if errors.Is(err, quark.ErrTimeout) {
// Retry with backoff
}
if errors.Is(err, quark.ErrConstraintViolation) {
// Handle duplicate key, FK violation, etc.
}

Error Wrapping

Quark wraps database errors with context:

// Timeout errors wrap context.DeadlineExceeded
if errors.Is(err, context.DeadlineExceeded) {
// Detected as ErrTimeout
}

// Constraint violations detected across dialects
// PostgreSQL: "unique constraint", "foreign key constraint"
// MySQL: "duplicate entry", "foreign key constraint fails"
// SQLite: "unique constraint failed"