Installation
Quark is a regular Go module built on database/sql. You install the ORM once,
then add the driver module for the database engine you use — Quark doesn't
wrap or replace driver DSNs.
go get github.com/jcsvwinston/quark
go get github.com/jcsvwinston/quark/drivers/sqlite # or postgres, mysql, mssql, oracle
| Requirement | Notes |
|---|---|
| Go 1.25+ | Quark declares go 1.25.7 in go.mod and pins a toolchain; with Go 1.25 installed the Go command downloads that toolchain on first build (GOTOOLCHAIN=auto, the default). |
| A driver module | Registers the database/sql driver (wire protocol, DSN format) and tells Quark how to classify that driver's errors. Added with a blank import. |
Driver name for quark.New | The dialect is auto-detected from it; override with WithDialect. |
Quark is on the stable v1.x line. There's also a quark CLI for migrations,
model generation, schema inspection, and tenant jobs —
go install github.com/jcsvwinston/quark/cmd/quark@latest (see
Operational Workflows) — but you don't need it to use the library.
Pick a driver
Each engine ships as its own module under drivers/. Install exactly the one
you need and import it for its side effect; the module pulls in the underlying
database/sql driver, so you don't add that one yourself:
| Engine | Module (go get + import _) | Name for quark.New | Underlying driver | Dialect |
|---|---|---|---|---|
| SQLite | github.com/jcsvwinston/quark/drivers/sqlite | sqlite | modernc.org/sqlite | quark.SQLite() |
| PostgreSQL | github.com/jcsvwinston/quark/drivers/postgres | pgx | github.com/jackc/pgx/v5/stdlib | quark.PostgreSQL() |
| MySQL | github.com/jcsvwinston/quark/drivers/mysql | mysql | github.com/go-sql-driver/mysql | quark.MySQL() |
| MariaDB | github.com/jcsvwinston/quark/drivers/mysql | mysql | github.com/go-sql-driver/mysql | quark.MariaDB() (auto-detected) |
| SQL Server | github.com/jcsvwinston/quark/drivers/mssql | sqlserver | github.com/microsoft/go-mssqldb | quark.MSSQL() |
| Oracle | github.com/jcsvwinston/quark/drivers/oracle | oracle | github.com/sijms/go-ora/v2 | quark.Oracle() |
Why a module and not the bare driver: Quark classifies errors by the codes the
driver reports, and naming a driver's error type means importing that driver.
Keeping those types in a per-engine module is what lets a program link only
the engine it talks to. If you import the bare driver instead
(_ "modernc.org/sqlite"), the database still opens, but Quark cannot tell a
duplicate key from a deadlock from a dropped connection: IsUniqueViolation,
WithDeadlockRetry and read-replica failover all answer false for every
error. quark.New logs a WARN (quark.driver.no_classifier) when it detects
that, naming the module to import. Opening without any driver at all fails
with an error that names the go get and the import line.
SQLite is the easiest place to start — there's no external service to run. Three engines have a wrinkle worth knowing before you pick a driver:
- PostgreSQL. The module registers pgx, so the driver name is
"pgx". Quark reads PostgreSQL error codes through theSQLState()method every PostgreSQL driver exposes, so a client opened withlib/pqandquark.New("postgres", dsn)also classifies errors correctly — it is the one engine where the bare driver loses nothing except the inboundLISTEN/NOTIFYlistener (Events), which lives indrivers/postgresand needs the pgx connection. If you expect to use it, start with the module. - MySQL. Add
parseTime=trueto the DSN when you scantime.Timefields, or the driver hands back raw bytes. - MariaDB. It speaks the MySQL wire protocol through the same
"mysql"driver, soquark.New("mysql", dsn)is correct for both. Quark checks the server version once atNew()and switches to the MariaDB dialect when it finds one.
A minimal program
package main
import (
"context"
"log"
"github.com/jcsvwinston/quark"
_ "github.com/jcsvwinston/quark/drivers/sqlite"
)
type User struct {
ID int64 `db:"id" pk:"true"`
Email string `db:"email" quark:"unique,not_null"`
Name string `db:"name" quark:"not_null"`
}
func main() {
client, err := quark.New("sqlite", "file:quark.db?cache=shared")
if err != nil {
log.Fatal(err)
}
defer client.Close()
ctx := context.Background()
if err := client.Migrate(ctx, &User{}); err != nil {
log.Fatal(err)
}
user := User{Email: "alice@example.com", Name: "Alice"}
if err := quark.For[User](ctx, client).Create(&user); err != nil {
log.Fatal(err)
}
log.Printf("created user id=%d", user.ID)
// => created user id=1
}
quark.New pings the database during construction, so treat a returned error as
a startup failure: wrong DSN, unreachable database, rejected credentials, or a
driver/dialect mismatch.
Switching engines
Only the import and the driver name change — your models and queries don't:
import (
"github.com/jcsvwinston/quark"
_ "github.com/jcsvwinston/quark/drivers/postgres"
)
client, err := quark.New("pgx", "postgres://user:pass@localhost/app?sslmode=disable")
The auto-detected dialect drives placeholder syntax, identifier quoting,
RETURNING, upsert fragments, JSON expressions, pagination, and DDL.
Optional packages
| Package | Purpose |
|---|---|
…/quark/drivers/<engine> | Driver module per engine (see Pick a driver). |
…/quark/cache/memory | In-process CacheStore with tag invalidation. |
…/quark/cache/redis | Redis-backed CacheStore. |
…/quark/otel | OpenTelemetry middleware. |
…/quark/migrate | Versioned migration registry and migrator. |
The core package has no framework dependency — use it from HTTP handlers,
workers, CLIs, or any layer that can pass a context.Context.
Pool configuration
Quark owns the *sql.DB it creates; tune the pool with options:
client, err := quark.New("postgres", dsn,
quark.WithMaxOpenConns(25),
quark.WithMaxIdleConns(25),
quark.WithConnMaxLifetime(30*time.Minute),
)
For database-per-tenant routing, each tenant can own its own pool — see Multi-Tenant before setting high limits.
Smoke test
A quick check that the driver, dialect, migration helper, insert, and PK write-back all work end to end:
func Smoke(ctx context.Context, client *quark.Client) error {
type HealthCheck struct {
ID int64 `db:"id" pk:"true"`
Name string `db:"name" quark:"not_null"`
}
if err := client.Migrate(ctx, &HealthCheck{}); err != nil {
return err
}
row := HealthCheck{Name: "ok"}
if err := quark.For[HealthCheck](ctx, client).Create(&row); err != nil {
return err
}
_, err := quark.For[HealthCheck](ctx, client).Find(row.ID)
return err
}
Running the test suite
If you're building Quark itself — contributing a patch, or reproducing a bug against a real engine — the test suite reads its connections from environment variables. SQLite tests run offline with no setup:
export QUARK_TEST_POSTGRES_DSN="postgres://user:pass@localhost:5432/testdb?sslmode=disable"
export QUARK_TEST_MYSQL_DSN="user:pass@tcp(localhost:3306)/testdb?parseTime=true"
export QUARK_TEST_MSSQL_DSN="sqlserver://sa:Pass@localhost:1433?database=testdb"
export QUARK_TEST_ORACLE_DSN="oracle://user:pass@localhost:1521/XE"
The same applies to any local tool you write that sets up a schema. If it runs
raw DDL through client.Exec, build its client with AllowRawQueries: true —
raw statements are disabled by default. The high-level Migrate, Sync,
CreateIndex, and AddForeignKey helpers need no such opt-in.