Viewing on GitHub? The contributing guide lives in CONTRIBUTING.md. This stub is served by the pg_trickle docs site — the include below renders there.

Contributing to pg_trickle

Thank you for your interest in contributing! pg_trickle is an Apache 2.0-licensed open-source project and welcomes contributions of all kinds.

Before You Start

  • Check the open issues and discussions to avoid duplicating work.
  • For non-trivial changes, open an issue first to discuss the approach.
  • Read AGENTS.md — it is the authoritative guide for all coding conventions, error handling rules, module layout, and test requirements.
  • Read docs/ARCHITECTURE.md to understand the system.
  • Read ROADMAP.md to see what work is planned.

Ways to Contribute

TypeWhere to start
Bug reportOpen an issue
Feature requestOpen an issue or start a discussion
Documentation fixOpen a PR directly — no issue needed for typos/clarity
Code fix or featureOpen an issue first, then a PR
Performance improvementInclude benchmark numbers (see just bench)

Development Setup

# Install pgrx
cargo install cargo-pgrx --version "=0.18.0"
cargo pgrx init --pg18 /usr/lib/postgresql/18/bin/pg_config

# Build
cargo build

# Format + lint (required before every PR)
just fmt
just lint

# Run tests
just test-unit          # fast, no DB
just test-integration   # Testcontainers
just test-light-e2e     # PR-equivalent Light E2E tier (stock postgres)
just test-e2e           # full E2E (builds Docker image)
just test-pgbouncer     # PgBouncer transaction-pool compatibility tests

Full setup instructions are in INSTALL.md.

Devcontainer / Containerized Development

If you are developing in a devcontainer, use the default non-root vscode user and run the normal commands from the workspace root:

just fmt
just lint
just test-unit

just test-unit uses scripts/run_unit_tests.sh, which now selects a writable and cache-friendly target directory in this order:

  1. target/ (preferred)
  2. .cargo-target/ (project-local fallback)
  3. $HOME/.cache/pg_trickle-target
  4. ${TMPDIR:-/tmp}/pg_trickle-target (last resort)

This avoids permission failures on bind mounts and preserves incremental builds when source or test files change.

If you see permission errors in containerized runs, verify you are not forcing a different container user/UID than expected by your workspace mount.

Run E2E tests in devcontainer

E2E tests use Testcontainers and require Docker access from inside the devcontainer (provided by the Docker-in-Docker feature in .devcontainer/devcontainer.json).

Run from the workspace root inside the devcontainer:

just build-e2e-image
just test-e2e

Notes:

  • The E2E harness starts containers via testcontainers (tests/e2e/mod.rs).
  • The default E2E image is pg_trickle_e2e:latest (built by tests/build_e2e_image.sh).
  • A plain docker run of the dev image is not equivalent to a full VS Code devcontainer session with features/lifecycle hooks enabled.

Making a Pull Request

  1. Fork the repository and create a branch: git checkout -b fix/my-fix
  2. Make your changes following the conventions in AGENTS.md
  3. Run just fmt && just lint — both must pass with zero warnings
  4. Add or update tests — see AGENTS.md § Testing
  5. Open a PR against main

The PR template will walk you through the checklist.

CI Coverage on PRs

PR CI runs a three-tier gate:

  • Unit tests (Linux only)
  • Integration tests
  • Light E2E — curated PR-friendly end-to-end coverage split across three shards and executed against stock postgres:18.3

Full E2E, TPC-H tests, benchmarks, dbt, CNPG smoke, and the extra macOS / Windows unit jobs stay off the PR critical path and run on push-to-main, schedule, or manual dispatch. This keeps typical PR feedback closer to the single-digit-minute range while preserving broader scheduled coverage.

To trigger the full CI matrix on your PR branch (recommended for DVM engine, refresh, or CDC changes):

gh workflow run ci.yml --ref <your-branch>

To run all tests locally before pushing:

just test-all          # unit + integration + e2e

# PR-equivalent fast path:
just test-unit
just test-integration
just test-light-e2e

# TPC-H correctness tests (requires e2e Docker image):
cargo test --test e2e_tpch_tests -- --ignored --test-threads=1 --nocapture

See AGENTS.md § Testing for the full CI coverage matrix.

Coding Conventions (summary)

  • No unwrap() or panic!() in non-test code
  • All unsafe blocks require a // SAFETY: comment
  • Errors go through PgTrickleError in src/error.rs
  • New SQL functions use #[pg_extern(schema = "pgtrickle")]
  • Tests use Testcontainers — never a local PostgreSQL instance

Full details are in AGENTS.md.

Commit Messages

Use Conventional Commits:

fix: correct pgoutput action parsing for tables named INSERT_LOG
feat: add CUBE explosion guard (max 64 UNION ALL branches)
docs: document JOIN key change limitation in SQL_REFERENCE
test: add E2E test for keyless table duplicate-row behaviour

Fuzz Testing

pg_trickle uses cargo-fuzz (libFuzzer) to exercise core parser and pipeline logic. Fuzz targets live in fuzz/fuzz_targets/.

Available targets

TargetWhat it exercisesAdded in
parser_fuzzDVM SQL parser (OpTree construction)v0.1.0
cron_fuzzCron expression parserv0.26.0
guc_fuzzGUC string→enum coercionv0.26.0
cdc_fuzzCDC trigger payload decodingv0.26.0
wal_fuzzWAL/SQLSTATE error classifierv0.39.0
dag_fuzzDAG/merge SQL and snapshot column-listv0.39.0
sql_builder_fuzzSQL builder + typed parser facadev0.44.0
merge_sql_fuzzMerge SQL codegen with random change streamsv0.49.0
row_id_fuzzRow identity tracking with random operator treesv0.49.0

Running all fuzz targets

# Run each target for 60 s (requires nightly toolchain):
just fuzz-all

# Run for a custom duration (seconds):
just fuzz-all 120

# Run a single target:
cargo +nightly fuzz run merge_sql_fuzz -- -max_total_time=60

Corpus directories are at fuzz/corpus/<target_name>/. Regression cases are stored in proptest-regressions/.

Every new feature ships with documentation. This is a hard requirement, not a nice-to-have.

  • New SQL functions → entry in docs/SQL_REFERENCE.md.
  • New GUC variable → entry in docs/CONFIGURATION.md.
  • New user-facing capability → at minimum a paragraph in the relevant chapter; for headline features, a dedicated page.
  • New page → add it to docs/SUMMARY.md in the correct chapter.

PRs that introduce a #[pg_extern] function or a new GUC without documentation will be asked to add it before merge.

Updating base image digests

Docker base images are pinned to exact SHA256 digests in all Dockerfiles (Dockerfile.demo, Dockerfile.ghcr, tests/Dockerfile.e2e) for reproducibility and supply-chain security (OPS-10-03).

To update the digests when a new PostgreSQL patch release is available:

# Requires docker with manifest support
scripts/update_base_image_digests.sh

The script resolves the current linux/amd64 digest for postgres:18.3-bookworm, patches all Dockerfiles in-place, and prints the commit command. Run this script quarterly or when a PostgreSQL patch release is needed. Include the digest-update commit in the release PR.

If you are building for linux/arm64 or another platform, edit the TARGET_PLATFORM variable in the script or pin to the manifest index digest (returned by docker manifest inspect postgres:18.3-bookworm --verbose).

License

By contributing you agree that your contributions will be licensed under the Apache License 2.0.