Contents
Releasing pg_tre
This document describes the release process for pg_tre.
Version Scheme
Semantic versioning: MAJOR.MINOR.PATCH.
- Release versions are plain triples (e.g.
1.1.0,1.1.1,1.2.0). - In-flight development between releases uses the
-devsuffix on the next planned version (e.g.1.2.0-dev). - Patch releases ship bug fixes and library-vendor refreshes that don’t change the on-disk format or the SQL surface.
- Minor releases may add SQL surface or new GUCs but keep the on-disk format compatible (no REINDEX required).
- Major releases may break the on-disk format. Document the required user action in the release notes.
Cutting a Release
1. Audit the upgrade SQL script
This is the one piece of the release that cannot be automated, and the piece most likely to be wrong.
The upgrade script sql/pg_tre--PREV--CURRENT.sql must recreate,
for an installation still on PREV, every catalog object that
exists in the current main install file but not in the previous
release’s main install file. To enumerate what the upgrade script
must cover, diff the two main install files:
git show vPREV:sql/pg_tre--PREV.sql > /tmp/prev.sql
diff /tmp/prev.sql sql/pg_tre--CURRENT.sql
For every statement that is new in the current main file, verify the upgrade script has a matching statement:
| New in main file | Required in upgrade script |
|---|---|
CREATE FUNCTION |
CREATE FUNCTION |
CREATE OPERATOR |
CREATE OPERATOR |
CREATE OPERATOR CLASS |
CREATE OPERATOR CLASS |
CREATE TYPE |
CREATE TYPE |
CREATE CAST |
CREATE CAST |
ALTER OPERATOR FAMILY |
ALTER OPERATOR FAMILY |
Catalog UPDATE pg_catalog.* |
Same UPDATE |
Renamed, signature-changed, or removed objects need matching
DROP / ALTER statements in the upgrade script.
The upgrade-tests CI workflow exercises the upgrade against
every version in its old_version matrix and is the primary
safety net for gaps — but it can only catch what the regression
suite touches. A new operator that has no test coverage will pass
CI and ship broken. The audit catches what the workflow can’t.
2. Run the bump script
./scripts/bump-version.sh CURRENT-dev CURRENT
The script auto-detects mode from the version-string syntax:
OLD=A.B.C-dev,NEW=A.B.C(same triple) → release bump.OLD=A.B.C,NEW=X.Y.Z-dev→ next-cycle dev bump.
It renames the SQL files, updates every version reference across
pg_tre.control, Makefile, META.json,
include/pg_tre/pg_tre.h, STATUS.md, CHANGELOG.md,
debian/changelog, and packaging/pg_tre.spec, and emits a
“stragglers” warning for any literal version strings it didn’t
expect to find. Read the warning before committing.
3. Update STATUS.md and CHANGELOG.md
The bump script updates the version header. The release notes themselves are content; you write them.
CHANGELOG.md follows the Keep a Changelog format.
Group entries under ### Added, ### Changed, ### Fixed,
### Removed, ### Security. Always include:
- A one-paragraph summary at the top of the section.
- The on-disk-format compatibility statement (no re-index required, REINDEX required, etc.).
- Acknowledgements for upstream contributions you depended on.
4. Add the previous release to the upgrade-tests matrix
In .github/workflows/upgrade-tests.yml, add PREV to the
old_version matrix so future releases are tested for upgrade
compatibility from this version. The matrix grows by one entry
per release; do not skip this step.
5. Run the release-check script locally
PG_CONFIG=$HOME/.pgrx/18.3/pgrx-install/bin/pg_config \
bash scripts/release-check.sh
Verifies a clean build with zero warnings, the regression suite
passes, the bench smoke completes, no committed binaries landed,
the flake’s vendored-source pins match the submodules, and
nix build .#pg18 succeeds.
The flake build is not redundant with the PGXS build. The flake
pins vendor/tre and vendor/lime as its own inputs (it does not
use ?submodules=1), so each vendored rev has two sources of
truth, and it applies patches/tre-progress-hook.patch with
patch(1) rather than the Makefile’s git apply. v3.2.3 shipped
unbuildable-by-flake while passing 41/41 tests twice under make,
because a TRE submodule bump was not mirrored into flake.nix.
If you change anything under vendor/, patches/, or
flake.nix, run the flake build before tagging even if the test
suite is green.
6. Open the PR and tag
git checkout -b release-CURRENT
git add -A
git commit -m "Release pg_tre CURRENT"
git push origin release-CURRENT
# Open PR, get review, merge.
# After merge:
git checkout main && git pull
git tag -a vCURRENT -m "pg_tre CURRENT"
git push origin vCURRENT
Codeberg picks up the tag and creates a release entry with the
auto-generated tarball. When the tag reaches the GitHub mirror,
.github/workflows/pgxn-release.yml uploads make dist’s tarball to
PGXN (account gregburd; the
PGXN_USERNAME/PGXN_PASSWORD repository secrets). Check that the run
succeeded and that https://api.pgxn.org/dist/pg_tre.json lists the
version. bump-version.sh bumps META.json, including
provides.pg_tre.file; PGXN does not check that the file exists, and
prereqs may only name PGXN or core extensions.
Bumping to the Next Dev Version
After a release is published, open a follow-up PR:
git checkout main && git pull
git checkout -b bump-to-NEXT-dev
./scripts/bump-version.sh CURRENT NEXT-dev
git add -A
git commit -m "chore: bump version to NEXT-dev"
The script creates the new main SQL file (renamed from the
previous release’s), creates a stub upgrade file
sql/pg_tre--CURRENT--NEXT-dev.sql containing only a header
comment, and updates every other version reference. Contributors
append catalog DDL to the upgrade file as features land in the
dev cycle.
SQL Upgrade Path Requirements
Upgrade scripts must form a single linear chain. Every version connects to exactly one next version — no shortcuts that skip intermediate steps. This keeps the number of upgrade scripts minimal and the path predictable.
0.1.0 → 1.0.0 → 1.1.0 → 1.1.1 → 1.2.0 → ...
Every release must have an upgrade path from the previous stable
release (e.g. 1.1.0--1.1.1.sql). Dev versions are not supported
for direct upgrades; users on dev versions reinstall.
Upgrade Compatibility Matrix
Not all version transitions are compatible with ALTER EXTENSION
UPDATE. Breaking changes that require index recreation or server
restart:
| Change Type | Impact | User Action |
|---|---|---|
| On-disk format version bump | Existing indexes incompatible | REINDEX |
| WAL record layout change | Standby recovery breaks on mixed versions | Restart standbys after upgrade, verify wal_consistency_checking |
| GUC default change | Behavior change after restart | Document in CHANGELOG |
| Vendored library API change | No user-visible effect | None |
Current compatibility matrix:
| From | To | Compatible? | Notes |
|---|---|---|---|
| 0.1.0 | 1.0.0 | ❌ No | UDF-only → native AM; recreate |
| 1.0.0 | 1.1.0 | ✅ Yes | Same on-disk format |
| 1.1.0 | 1.1.1 | ✅ Yes | sparsemap hardening only |
| 3.2.1 | 3.2.2 | ⚠️ Yes, but | sparsemap wire format unchanged and no REINDEX needed, but ~*/ILIKE anchored queries return zero rows on this version (pre-existing 3.2.0 defect) — go straight to 3.2.3 |
| 3.2.2 | 3.2.3 | ⚠️ Yes, but | Read-path fix, no REINDEX — but v3.2.3 cannot be built via the Nix flake; go straight to 3.2.4 |
| 3.2.3 | 3.2.4 | ✅ Yes | Packaging fix only; no C/SQL/on-disk change, no REINDEX |
| 3.2.4 | 3.2.5 | ✅ Yes | Tokenizer fix; no on-disk change, no REINDEX |
| 3.2.5 | 3.2.6 | ✅ Yes | Page-leak fix; no on-disk change, no REINDEX |
| 3.2.6 | 4.0.0 | ⚠️ Yes, with care | No on-disk change, no REINDEX — but WAL format changed. Shut down cleanly; rebuild physical standbys from a fresh base backup (no rolling upgrade); replace wal_consistency_checking='pg_tre' with 'all' |
| 4.0.0 | 4.0.1 | ✅ Yes | Storage fix; no on-disk change, no REINDEX |
| 4.0.1 | 4.0.2 | ✅ Yes | Scan-path correctness fix; no on-disk change, no REINDEX |
| 4.0.2 | 4.1.0 | ✅ Yes | Vendored sparsemap v5.6.0 + corruption detection; no on-disk change, no REINDEX |
| 4.1.0 | 4.2.0 | ✅ Yes | sparsemap v5.7.0 + similarity crash fix; no REINDEX to upgrade; downgrade to 4.1.0 needs REINDEX |
| 4.2.0 | 4.2.1 | ⚠️ Yes, but | No on-disk change, no REINDEX to upgrade — but REINDEX any index that already raised could not read blocks or grew far past a fresh build: earlier versions left dead TIDs merged in, which the fix does not remove |
| 4.2.1 | 4.2.2 | ✅ Yes | No on-disk change, no REINDEX either way. UTF-8 databases with a C ctype: fuzzy queries over non-ASCII text now count characters and may return more (correct) rows |
| 4.2.2 | 4.2.3 | ✅ Yes | No on-disk change, no REINDEX either way. Fixes a backend crash on an ERROR during a pending-list scan (clang/flake builds) |
| 4.2.3 | 4.3.0 | ⚠️ Yes, but | No on-disk change. UTF-8 databases: no REINDEX. Non-UTF-8 databases: REINDEX every tre index holding non-ASCII text (its trigrams were decoded as UTF-8). Class / (?i) answers over non-ASCII text now follow core regex for the call’s collation; index scans no longer drop rows (see CHANGELOG); nondeterministic collations refused at index build |
| 4.3.0 | 4.3.1 | ✅ Yes | No on-disk change, no REINDEX either way. CREATE INDEX no longer leaks memory and builds posting trees in parallel; 'ab+c' no longer drops rows from the index; patterns core regex accepts no longer fail on the index path |
| 4.3.1 | 4.4.0 | ⚠️ Yes, but | On-disk format v11 (dense TID packing). No REINDEX to upgrade: old indexes keep working; REINDEX makes them 1.5-1.8x smaller (pg_tre_index_tid_packing() tells which). Downgrade to 4.3.x needs REINDEX of every tre index 4.4.0 wrote to. REINDEX any UNLOGGED tre index that fails “meta page magic mismatch” after a crash |
| 4.4.0 | 4.4.1 | ✅ Yes | No on-disk change, no REINDEX either way. Builds on PostgreSQL 19; on 19 character classes follow core (ICU included) |
| 4.4.1 | 4.5.0 | ⚠️ Yes, but | No on-disk change, no REINDEX to upgrade or downgrade. REINDEX any index built with pg_tre.min_trigram_freq > 1 (it misses rare words). Anchored-prefix queries on pre-4.5.0 indexes no longer use the SuRF prefilter until REINDEX (they were missing rows). Indexes with an explicit pending_list_limit start merging on INSERT; fastupdate = off is refused |
| 4.5.0 | 4.5.1 | ✅ Yes | No on-disk change, no REINDEX either way. Security fix: pg_tre_index_check() pins its search_path (4.5.0’s could run lookalike functions from a schema on the caller’s path) |
When releasing a version with breaking changes:
- Update
.github/workflows/upgrade-tests.ymlto exclude incompatible versions from the matrix (set theexclude:list). - Document the required action in
CHANGELOG.mdunder### Breaking changes. - Provide a migration guide for major version bumps in
doc/migration-from-X.Y.Z.md.
On-Disk Format Versions
If any on-disk format changed during a release cycle, bump the corresponding version constant before tagging:
| Constant | Header | Purpose |
|---|---|---|
PG_TRE_FORMAT_VERSION |
include/pg_tre/page.h |
Index meta + page format |
PG_TRE_RMGR_VERSION |
include/pg_tre/xlog.h |
Custom rmgr WAL record format |
Version bumps break upgrade compatibility — exclude incompatible old versions from the upgrade-tests matrix and document the breaking change in release notes.
Automated Workflows
| Workflow | Trigger | Purpose |
|---|---|---|
ci.yml |
PR, push to main | Build + test on PG18 (gcc, clang) |
formatting.yml |
PR (src/**) | clang-format check |
pgspot.yml |
push to main, PR (sql/**) | Extension SQL security scan |
upgrade-tests.yml |
PR (sql/** or pg_tre.control), weekly | ALTER EXTENSION UPDATE matrix |
sanitizer-build-and-test.yml |
push to main | Regression suite in an ASan-built PostgreSQL (scripts/asan-check.sh) |
nightly-stress.yml |
daily | stress / wal_audit / replication, plain and under ASan |
pgxn-release.yml |
v* tag, manual |
Upload the release to PGXN |
These are the GitHub workflows, which run on the GitHub mirror
(gburd/pg_tre) and are the release gate. .forgejo/workflows/ci.yml
is meant for Codeberg, but no Forgejo runner is registered for the
repository, so Codeberg cancels every run without starting it. Check
the GitHub mirror’s runs for the pushed commit, not Codeberg’s.
Troubleshooting
Old SQL files in the Postgres share directory
If tests fail with old version messages, check for stale files:
ls $(pg_config --sharedir)/extension/pg_tre*
Remove old dev versions that shouldn’t be installed:
rm $(pg_config --sharedir)/extension/pg_tre--X.Y.Z-dev.sql
Extension won’t upgrade
If ALTER EXTENSION pg_tre UPDATE fails, verify:
- The upgrade SQL file exists in the share directory.
pg_tre.controlhas the correctdefault_version.- The upgrade path exists:
sql SELECT * FROM pg_extension_update_paths('pg_tre'); - The cluster has been restarted with the new
pg_tre.soloaded viashared_preload_libraries. The ALTER runs against the binary already in memory; replacing the file alone does not refresh the postmaster’s mapped copy.
Cluster won’t start after make install
shared_preload_libraries = 'pg_tre' requires the new .so to
be ABI-compatible with the postmaster’s existing state. Stop the
postmaster fully (pg_ctl stop), then start it (pg_ctl start).
A pg_ctl restart may not free the old shared-memory segment;
if pg_ctl start fails with “pre-existing shared memory block
… is still in use”, ipcrm -m <id> the stale segment from the
log message and retry.