Configuration Reference
Quark configuration is per client. quark.New(driverName, dataSource, ...options)
does not modify global ORM state.
Minimal Client
client, err := quark.New("sqlite", "file:app.db?cache=shared")
quark.New opens the connection itself and pings it with a five-second startup
timeout. The dialect is auto-detected from the driver name.
Production Shape
limits := quark.DefaultLimits()
limits.MaxResults = 10_000
limits.MaxWhereConditions = 20
limits.MaxJoins = 5
limits.QueryTimeout = 30 * time.Second
limits.AllowRawQueries = false
limits.SafeMigrations = true
store := memory.New()
client, err := quark.New("postgres", dsn,
quark.WithLogger(logger),
quark.WithLimits(limits),
quark.WithCacheStore(store),
quark.WithMiddleware(quarkotel.New()),
quark.WithQueryObserver(metricsObserver),
)
Always start from DefaultLimits() and override fields selectively, as
above. A bare literal like quark.Limits{MaxResults: 500} silently leaves
SafeMigrations at false, which allows a destructive Sync. The full
zero-value rules are under Limits below.
Options
| Option | Purpose |
|---|---|
WithDialect(d Dialect) | Overrides the dialect that would be auto-detected from the driver name. |
WithLogger(l *slog.Logger) | Sets the client logger. |
WithLimits(l Limits) | Sets security and performance limits. |
WithCacheStore(s CacheStore) | Enables query cache storage. |
WithMiddleware(m Middleware) | Adds execution middleware. |
WithQueryObserver(o QueryObserver) | Adds post-execution query observers. |
WithDeadlockRetry(maxAttempts int) | Transparent retry of Client.Tx when the engine kills the transaction as a deadlock victim (PG 40P01, MySQL 1213, MSSQL 1205, Oracle ORA-00060). Retries the whole closure with backoff + jitter; disabled by default. |
WithSlowQueryThreshold(d time.Duration) | Logs any query/exec exceeding d at WARN with structured attributes (parameterised SQL only — never bind args). 0 (default) disables. |
WithStrictReads(mode StrictReadsMode) | Enforcement for unbounded reads: StrictReadsWarn logs a structured WARN when Iter/Cursor run without Limit; StrictReadsReject returns ErrInvalidQuery instead. Enables N+1 detection inside TrackReads contexts. Per-query opt-out: AllowUnbounded(). Off by default. |
WithStrictColumns() | Plain column references in Where/OrderBy/GroupBy/Select/Having and the aggregates must be columns the model declares; a typo fails with ErrInvalidQuery listing the valid columns instead of degrading at the engine's discretion. Joined queries, the expression AST, and raw paths are exempt. Off by default. |
WithDefaultTZ(loc *time.Location) | Fallback timezone for time.Time columns without a quark:"tz=..." tag. Values are stored as UTC; loc only affects how scanned values read in Go. |
WithCacheJitter(pct float64) | ±jitter factor applied to cache TTLs (anti-stampede). Default 0.1; clamped to [0, 1]. Needs WithCacheStore. |
WithCacheXFetchBeta(beta float64) | XFetch probabilistic early-refresh parameter. Default 1.0; 0 disables XFetch (singleflight + jitter stay on). Needs WithCacheStore. |
WithCacheCrossInstance() | Cross-instance cache-stampede coordination via a CacheLocker store. Off by default; falls back to in-process singleflight when the store can't lock. |
WithReplicas(dsns ...string) | Read-replica DSNs: reads route to replicas, writes stay on the primary; transient replica failures fail over to the primary. |
WithReplicaStrategy(s ReplicaStrategy) | Replica selection: ReplicaRoundRobin (default), ReplicaRandom, or ReplicaLeastConn. Needs WithReplicas. |
WithReplicaDownCooldown(d time.Duration) | How long a failed replica stays out of rotation before re-probing (default 5s). Needs WithReplicas. |
WithMaxOpenConns(n int) | Sets *sql.DB.SetMaxOpenConns. |
WithMaxIdleConns(n int) | Sets *sql.DB.SetMaxIdleConns. |
WithConnMaxLifetime(d time.Duration) | Sets *sql.DB.SetConnMaxLifetime. |
WithConnMaxIdleTime(d time.Duration) | Sets *sql.DB.SetConnMaxIdleTime. |
Dialect
quark.WithDialect(quark.PostgreSQL())
quark.WithDialect(quark.MySQL())
quark.WithDialect(quark.MariaDB())
quark.WithDialect(quark.SQLite())
quark.WithDialect(quark.MSSQL())
quark.WithDialect(quark.Oracle())
The dialect controls placeholders, identifier quoting, RETURNING, last-insert
ID behavior, upsert SQL, JSON extraction, pagination, routines, and schema DDL.
quark.New auto-detects the dialect from the driver name. Pass WithDialect
only when the driver name is ambiguous (e.g. pgx shared between PostgreSQL
and CockroachDB) or when registering a custom dialect.
Logger
logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
client, err := quark.New("postgres", dsn,
quark.WithLogger(logger),
)
The logger is used for client initialization, safe default limit warnings, schema sync messages, cache hits, and migration diagnostics.
Limits
type Limits struct {
MaxQueryLength int
MaxResults int
MaxJoins int
MaxWhereConditions int
QueryTimeout time.Duration
AllowRawQueries bool
SafeMigrations bool
}
Defaults:
| Field | Default | Notes |
|---|---|---|
MaxQueryLength | 10 * 1024 | Rejects generated SELECT SQL above this byte length. |
MaxResults | 10000 | Intended cap for list-style reads. Prefer explicit Limit. |
MaxJoins | 5 | Rejects SELECT queries with too many joins. |
MaxWhereConditions | 20 | Rejects excessive predicate chains. |
QueryTimeout | 30 * time.Second | Applied to ORM query contexts. |
AllowRawQueries | false | Required for RawQuery, Exec, and WhereSubquery. |
SafeMigrations | true | Prevents Sync from dropping columns. |
Zero-value semantics
WithLimits treats numeric and boolean fields differently, and the difference
matters:
- Numeric fields: zero means "not set".
WithLimitsreplaces each zero-valued numeric field with itsDefaultLimits()value before installing the struct, soquark.Limits{MaxResults: 500}raises one limit without zeroing the rest. - Negative values pass through untouched.
MaxQueryLength: -1disables the generated-SQL length check. - Boolean fields are never rewritten, because an explicit
falseis indistinguishable from an omitted one. A partial literal therefore carriesSafeMigrations: false— destructiveSyncallowed — even though the default istrue.
Because that last case is easy to miss, quark.New logs one warning
(event=quark.limits.partial_literal_safe_migrations_off) when a partial literal
ends up with SafeMigrations disabled. The warning does not change the installed
limits; a full literal that disables SafeMigrations on purpose stays silent.
Recommended pattern:
limits := quark.DefaultLimits()
limits.AllowRawQueries = true // only for migration/admin clients
limits.MaxWhereConditions = 30
client, err := quark.New("postgres", dsn,
quark.WithLimits(limits),
)
Use separate app and migration clients when possible: app clients can keep
AllowRawQueries disabled, while migration commands can opt in explicitly.
Cache Store
Memory:
import "github.com/jcsvwinston/quark/cache/memory"
store := memory.New()
defer store.Close()
client, err := quark.New("sqlite", "file:app.db?cache=shared",
quark.WithCacheStore(store),
)
Redis:
import rediscache "github.com/jcsvwinston/quark/cache/redis"
store := rediscache.New(rediscache.Options{
Addr: "localhost:6379",
Password: "",
DB: 0,
})
client, err := quark.New("postgres", dsn,
quark.WithCacheStore(store),
)
Queries are cached only when .Cache(ttl, tags...) is present.
Middleware
type AuditMiddleware struct {
quark.BaseMiddleware
}
func (m *AuditMiddleware) WrapQuery(next quark.QueryFunc) quark.QueryFunc {
return func(ctx context.Context, exec quark.Executor, sqlStr string, args []any) (*sql.Rows, error) {
rows, err := next(ctx, exec, sqlStr, args)
auditQuery(ctx, sqlStr, err)
return rows, err
}
}
client, err := quark.New("postgres", dsn,
quark.WithMiddleware(&AuditMiddleware{}),
)
Middleware can wrap:
| Method | Path |
|---|---|
WrapQuery | QueryContext, used by list and streaming reads. |
WrapQueryRow | QueryRowContext, used by counts, inserts with returning, aggregates. |
WrapExec | ExecContext, used by writes and DDL helpers. |
Multiple middleware are executed in registration order.
Query Observers
type MetricsObserver struct{}
func (m *MetricsObserver) ObserveQuery(event quark.QueryEvent) {
metrics.Record(event.Table, event.Operation, event.Duration, event.Rows, event.Error)
}
client, err := quark.New("postgres", dsn,
quark.WithQueryObserver(&MetricsObserver{}),
)
QueryEvent fields:
| Field | Type | Description |
|---|---|---|
SQL | string | SQL sent to the driver. |
Args | []any | Bound arguments. |
Duration | time.Duration | Execution duration measured by Quark. |
Rows | int64 | Rows returned or affected when known. |
Error | error | Nil on success. |
Table | string | Model table when known. |
Operation | string | SELECT, EXEC, QUERY_ROW, RAW_QUERY, RAW_EXEC, etc. |