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:
ErrEventEmitFailedwraps anEventBus.Publishfailure that happens after the transaction commits. The commit stands; only the post-commit emit could not be delivered.ErrListenerClosed— you calledListenafter closing the inbound PostgreSQLLISTEN/NOTIFYlistener.ErrNoSubscription— you calledReceivebefore 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"