Contributions are welcome! This guide covers the basics of adding checks and working with the codebase. For a comprehensive development reference (architecture, patterns, SQL conventions, testing), see AGENTS.md.
mkdir checks/mycheckCreate checks/mycheck/query.sql with sqlc annotations:
-- name: MyQuery :many
SELECT
schemaname || '.' || relname AS table_name,
some_metric
FROM pg_stat_user_tables
WHERE some_condition;All queries must be:
- Read-only (no writes, no locks)
- Production-safe (execute in < 1 second)
- Using
pg_catalogtables directly (neverinformation_schema)
Create checks/mycheck/README.md covering what the check validates, why it matters, and how to fix issues. This is embedded into the binary and shown via pgdoctor explain.
Create checks/mycheck/check.go. Each check package exports:
Metadata()returningcheck.MetadataNew(queryer)returningcheck.Checker
See any existing check (e.g., checks/pgversion/) for the full pattern.
Add your check directory to sqlc.yaml, then run code generation:
# Add to sqlc.yaml queries list:
# - "checks/mycheck"
# Generate database code
sqlc generate
# Register the check in checks.go
go generate ./...Create checks/mycheck/check_test.go using table-driven tests with mock query interfaces. See existing checks for the pattern.
go build -o pgdoctor ./cmd/pgdoctor
./pgdoctor list # Check appears
./pgdoctor explain my-check # Documentation renders
go test ./checks/mycheck/... # Tests pass
go test ./... # Full suite passespgdoctor requires Go 1.22+ and a PostgreSQL instance for sqlc code generation.
git clone https://github.com/emancu/pgdoctor.git
cd pgdoctor
go build -o pgdoctor ./cmd/pgdoctor
go test ./...Choose severity carefully — it determines how users prioritize their work:
- FAIL: Requires action. Security risk, data loss potential, or imminent outage.
- WARN: Should address. Performance issue, technical debt, or best practice violation.
- INFO: Worth knowing. Relevant information with no expected action; never escalates the report severity.
- PASS: Passing. Always include at least one PASS finding when no issues are detected.
- SKIP: The check could not run — timeout, permission error, or a missing extension. Runner-injected, never author-assigned, and ordered below PASS so it never escalates the report.
When in doubt, prefer WARN over FAIL. A noisy tool that cries wolf loses trust.
Consumers switching over Severity with a default branch should add an explicit SeverityInfo case before surfacing INFO, or it silently falls through to the default bucket.
- Never edit generated files (
db/,checks.go) - Embed SQL and README via
//go:embeddirectives - Use
check.NewReport(Metadata())to create reports - Access metadata via promoted fields (
report.CheckID, not local variables) - Always report
SeverityPasswhen no issues are found - Use table-driven tests with
testify
Checks belong to one of five categories:
| Category | What it covers |
|---|---|
configs |
Database configuration, settings, infrastructure health |
indexes |
Index health and optimization |
vacuum |
Autovacuum, maintenance, and bloat |
schema |
Schema design choices and capacity planning |
performance |
Runtime performance and query optimization |
By contributing, you agree that your contributions will be licensed under the MIT License.