Skip to content

Contributing

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

A summary of the repository's CONTRIBUTING.md. The file in the repository is authoritative.

On this page: Repository layout · Running tests · Conventions · Adding a builder method · Adding a dialect

Repository layout

Path Contents
db/ The public package: builders, compiler, executor, typed API
internal/chaingen Generator for the builders' chain methods (db/chain_gen.go, run with go generate ./db)
internal/dialect Per-database Grammar implementations (quoting, placeholders, LIKE, dates, locks, index hints)
internal/rowmap Rows to structs, maps and scalars, and structs back to rows
internal/jsonpath col->a->b[0] selectors rendered into JSON path literals
internal/keyset The cursor token codec (decodes untrusted input)
internal/rawsql Argument interpolation for ToRawSQL
internal/errs Error values shared with db, which re-exports them
test/benchmark/ Separate module: quill vs sqlbuilder, goqu, squirrel, sqlx and GORM
test/integration/ Separate module: end-to-end tests on SQLite, PostgreSQL, MySQL and MariaDB
docs/ The guides

The root module has no third-party runtime dependencies and should stay that way. testify is used from _test.go files only. Anything that needs a driver or test infrastructure belongs in test/integration/.

Running tests

Run the same commands CI runs before you push:

go build ./...
go vet ./...
golangci-lint run ./...
go test -race ./...            # unit and golden tests

Integration suites (one per database):

cd test/integration
go test -race ./...                 # SQLite, PostgreSQL, MySQL (and MariaDB)
go test -run TestSQLite ./...       # no container needed
go test -run TestPostgres ./...     # starts a PostgreSQL container
  • PostgreSQL, MySQL and MariaDB suites use testcontainers and need Docker. Without a reachable Docker daemon they skip.
  • GOQUERY_SKIP_CONTAINERS=1 skips them on purpose.

The Makefile wraps these (make help lists targets: build, lint, test, bench, fuzz, ...).

What each test layer proves

Layer Proves
Golden SQL (db/*_test.go) Exact SQL and arguments for every dialect, no database needed
Parity (chain_gen.go) Query[T] methods always match SelectBuilder methods
Unit (internal/*) Each internal package in isolation
Examples (db/example_test.go) Documentation that is executed and checked
Fuzz Quoting, placeholder and untrusted-input code never misbehave
End to end (test/integration) Real databases accept the SQL and return the right rows

A failing fuzz input is written under testdata/fuzz/. Fix the cause, then commit that file. It becomes a regression case.

Conventions

  • Every SQL change gets a golden test for all dialects, in the file named after the source file it covers.
  • Behaviour that depends on the database also gets an integration test.
  • Errors are sentinels (db.ErrNoWhere, ...), wrapped with %w and context. Do not panic on bad input. Only the Must... functions panic.
  • Values are always bound. Never concatenate a value into SQL. Identifiers always go through the quoting helpers.
  • Be explicit about dialect gaps: return db.ErrUnsupported rather than emit SQL that silently differs.
  • Format with gofmt, keep golangci-lint clean, and write doc comments on exported names.

Adding a builder method

  1. Add the method on the internal query type in db/builder.go (or the file for its group). Record bad arguments with b.fail(err), do not panic.
  2. Add the clause or state, and compile it in db/grammar.go (or write.go, join.go, json.go). Write arguments through c.param / c.value so numbering stays right.
  3. Add a golden test for every dialect, plus an error test.
  4. Run go generate ./db. Never edit db/chain_gen.go by hand.
  5. Add the method to the relevant guide and the method reference.
  6. If a database could reject or reinterpret the SQL, add an integration case.

Adding a dialect

  1. Implement dialect.Grammar in internal/dialect/<name>.go with a constructor next to the others.
  2. Add the constant to Dialect in db/dialect.go, its String() case, and its branch in Dialect.grammar().
  3. Handle structural differences (update/delete with joins, upsert, truncate, RETURNING, JSON updates) in the switch statements in db/write.go and db/json.go, or return db.ErrUnsupported.
  4. Add golden cases to every table-driven test, then a test/integration/<dialect>_test.go with its container, schema and suite.
  5. Document it on the Dialect Differences page.

A variant of an existing dialect

When a database is mostly another one (MariaDB is mostly MySQL), do not copy the grammar. Embed the parent grammar and override only what differs. Probe the real server before copying another project's choices: a grammar written for a different project records what that project chose, not what is required.

Documentation

Examples in the guides are written to be correct as shown. When you change behaviour, update the guide and keep go doc comments accurate. Examples in db/example_test.go are executed by go test.

Clone this wiki locally