Skip to content

Hooks and Options

Ahmad Faiz Kamaludin edited this page Oct 18, 2026 · 2 revisions

Options are passed to db.New or db.Use.

Option Effect
db.WithPrefix(prefix) Table prefix for table names, aliases and qualified columns
db.WithHook(fn) Calls fn(ctx, db.QueryEvent) after every statement
db.WithLogArgs() Puts bind values in QueryEvent.Args (off by default)

Table prefix

conn := db.New(sqlDB, db.Postgres, db.WithPrefix("app_"))

conn.Select().From("users as u").Where("u.id", 1)
// SELECT * FROM "app_users" AS "app_u" WHERE "app_u"."id" = $1

The prefix also applies to CTE names, since a CTE name is a table reference. For a single builder, use Prefix("p_").

Hooks

A hook runs after every statement, including raw conn.Query and conn.Exec.

conn := db.New(sqlDB, db.Postgres, db.WithHook(func(ctx context.Context, e db.QueryEvent) {
    slog.InfoContext(ctx, "sql",
        "query", e.SQL,
        "args", e.NumArgs,
        "took", e.Duration,
        "err", e.Err,
    )
}))

QueryEvent

Field Meaning
SQL The statement with placeholders
NumArgs How many arguments were bound
Args The bound values. Only set with db.WithLogArgs()
Duration Time spent in the driver call
Err The driver error, if any

Use cases: structured logging, latency metrics, slow-query warnings, tracing spans (the hook receives the request ctx).

db.WithHook(func(ctx context.Context, e db.QueryEvent) {
    if e.Duration > 500*time.Millisecond {
        slog.WarnContext(ctx, "slow query", "query", e.SQL, "took", e.Duration)
    }
})

Logging and personal data

Bind values routinely hold personal data (names, emails, identity numbers, card or account numbers), so they are left out by default. The hook sees the SQL text and the argument count.

  • Enable db.WithLogArgs() only where that data may be logged, stored and access-controlled accordingly.
  • Driver and database errors can echo values back. Treat QueryEvent.Err as sensitive too.

Risk area: data exposure. More in Security.

Clone this wiki locally