Skip to main content

Errors

Errors are values you can check. Builders collect them along the way, and the call that runs the query returns them, joined together if there's more than one.

Common errors​

Match them with errors.Is, not by their text, which can change.

user, err := db.Users.Find(ctx, id)

if errors.Is(err, ant.ErrNotFound) {
return nil
}
ErrorMeans
ant.ErrNotFoundFirst, Single, Find, or Reload found no row
ant.ErrMultipleRowsSingle found more than one
ant.ErrNoConditionA bulk write with no condition
ant.ErrConcurrencyA version check failed
ant.ErrUnsupportedThis database lacks the feature
ant.ErrNotProbeA pointer that isn't a probe
ant.ErrWrongModelA probe of a model outside this query
ant.ErrKeyTypeA key of the wrong type
ant.ErrNoKeyThe model has no usable key for this
ant.ErrReadOnlyA write to a view
ant.ErrTxDoneThe transaction already ended

Database errors​

These classify a database's error without string matching, on all five databases.

err := db.Users.Insert(ctx, user)

switch {
case ant.IsUniqueViolation(err):
return errors.New("already exists")
case ant.IsForeignKeyViolation(err):
return errors.New("refers to something missing")
case ant.IsNotNullViolation(err), ant.IsCheckViolation(err):
return errors.New("invalid")
case ant.IsRetryable(err):
return errors.New("try again")
}

ant.SQLState(err) returns the code itself, such as 23505, translated so one value means one thing everywhere.

Finding the statement​

ant.FailedSQL reads the statement behind a query error, with placeholders and no values, so it's safe for an internal log.

err := db.Users.Insert(ctx, user)

slog.Error("insert failed", "sql", ant.FailedSQL(err))

ant.WithDetailedErrors() adds it to every error's message, which helps in development. Leave it off where errors reach users.

Strict scanning​

By default a result column with no field is ignored. ant.WithStrict() refuses it, which catches a model that has fallen behind its table.

db, err := ant.Open[AppDB](ctx, postgres.Dialect, dsn, ant.WithStrict())

ant.WithLooseScan() says the default out loud, where WithStrict is on for other contexts.