Supporting a New PostgreSQL Version¶
Support policy¶
Greenmask supports only PostgreSQL major versions that are still supported upstream (non-EOL).
We deliberately do not claim support for EOL versions. Every version we list is exercised by the
integration suite on every release, so we can actually guarantee it works — dump, transform, restore,
and native pg_restore interoperability. Versions past their upstream EOL date are dropped from the
matrix at the first release after that date. Greenmask will very likely keep working against them,
but it is untested and unsupported.
| Version | Upstream EOL | Status in greenmask |
|---|---|---|
| 19 | ~Nov 2031 | beta — support in progress |
| 18 | Nov 14, 2030 | supported |
| 17 | Nov 8, 2029 | supported |
| 16 | Nov 9, 2028 | supported |
| 15 | Nov 11, 2027 | supported |
| 14 | Nov 12, 2026 | supported |
| 13 | Nov 13, 2025 | EOL — to be dropped |
Authoritative source: PostgreSQL Versioning Policy.
Keep this table, docker-compose-integration.yml, docker/integration/tests/Dockerfile, and the
PG_VERSIONS_CHECK environment variables in sync. They are the single definition of "supported".
Where greenmask is coupled to PostgreSQL¶
Three independent coupling points. A new major version can break any one of them, so all three are checked separately.
- The ported TOC archive library —
internal/db/postgres/tocis a Go re-implementation of pg_dump'stoc.datreader/writer. It must stay byte-compatible with the C implementation. - The
pg_dump/pg_restoreCLI contract — greenmask shells out to the real binaries for the pre-data and post-data sections (internal/db/postgres/pgdump,internal/db/postgres/pgrestore). - Catalog introspection — greenmask runs its own catalog queries instead of reusing pg_dump's
(
internal/db/postgres/context).
Pre-release checklist¶
Run this before every release, not only when adding a major version. Upstream changes the archive format and pg_dump behaviour in minor releases far less often than in majors, but catalog and client-tool behaviour does move.
1. Refresh the local PostgreSQL checkout¶
cd /path/to/postgres
git fetch origin --tags
git tag -l 'REL_1*' # find the newest tag of each supported branch
2. Check the archive format version¶
This is the single most important check. If K_VERS_MINOR was bumped, the ported library must be
updated before the release ships.
git diff REL_18_0 REL_19_BETA3 -- src/bin/pg_dump/pg_backup_archiver.h
Compare against MaxVersion in internal/db/postgres/toc/utils.go.
As of PostgreSQL 19 beta 3 the format is still 1.16, unchanged since PostgreSQL 18.
3. Diff the TOC read/write functions¶
Walk the watchlist in TOC archive format watchlist below, function by
function. Any change in field order, field count, or version gating must be mirrored in
reader.go / writer.go.
git diff REL_18_0 REL_19_BETA3 -- src/bin/pg_dump/pg_backup_archiver.c \
src/bin/pg_dump/pg_backup_directory.c
4. Diff the pg_dump / pg_restore option surface¶
Any option greenmask passes that was removed upstream is an immediate runtime failure. Any new option is a candidate feature.
git diff REL_18_0 REL_19_BETA3 -- src/bin/pg_dump/pg_dump.c \
src/bin/pg_dump/pg_restore.c \
src/bin/pg_dump/pg_backup.h
Cross-check every field of Options in
internal/db/postgres/pgdump/pgdump.go and
internal/db/postgres/pgrestore/pgrestore.go.
5. Diff the TOC entry desc strings¶
New object types add new desc values (STATISTICS DATA, EXTENDED STATISTICS DATA,
SUBSCRIPTION TABLE, …). Greenmask passes unknown descs through, but the ones it handles explicitly
in internal/db/postgres/cmd/restore.go and internal/db/postgres/toc/common.go must stay correct.
git show REL_19_BETA3:src/bin/pg_dump/pg_dump.c \
| grep -oE '\.description = "[A-Z][A-Z /]*"' | sort -u
6. Diff the catalogs greenmask queries¶
Greenmask reads pg_class, pg_attribute, pg_type, pg_constraint, pg_sequence, pg_namespace,
pg_inherits, pg_largeobject_metadata, pg_foreign_table, pg_foreign_server, and pg_roles.
git diff REL_18_0 REL_19_BETA3 -- src/include/catalog/
Pay attention to:
- New
relkindvalues (pg_class.h). PostgreSQL 19 adds'g'(property graph). Every introspection query filtersrelkind, so new kinds are excluded by default — verify that is still the behaviour you want, and that theunknown relkindbranch ininternal/db/postgres/context/pg_catalog.gocannot be reached. - New
contypevalues — theswitchininternal/db/postgres/context/tables_introspection.gomust cover them or log the unknown-constraint warning. - New built-in types in
pg_type.dat. These are not "custom types", so they are invisible toCustomTypesWithTypeChainQueryand depend onpgxknowing their OID. Ifpgxdoes not, columns of that type are marked unsupported inpkg/toolkit/driver.goand no transformer can be attached. PostgreSQL 19 addsoid8(6437) andregdatabase(6490). - Behaviour changes in functions greenmask calls —
pg_sequence_last_value,has_sequence_privilege,pg_get_constraintdef,aclexplode,acldefault,pg_partition_ancestors.
7. Read the release notes migration section¶
sed -n '/<sect2 .*id="release-19-migration"/,/^ <\/sect2>/p' doc/src/sgml/release-19.sgml
Anything under "Migration to Version N" that mentions pg_dump, pg_restore, dump/restore
compatibility, or a GUC default change is a candidate for a release-note caveat on our side.
8. Drop versions that went EOL¶
If a supported version passed its EOL date since the last release, remove it now — see Dropping an EOL version.
9. Run the integration suite against every supported version¶
docker compose -f docker-compose-integration.yml --profile all up \
--renew-anon-volumes --force-recreate \
--exit-code-from greenmask --abort-on-container-exit greenmask
The suite must be green for every version in the support table. Exit code 2 means at least one version failed.
10. Update the support table¶
Update the table at the top of this file, and note the tested version range in the release notes.
TOC archive format watchlist¶
These are the upstream functions that define the toc.dat binary layout. Diff every one of them on
every revision. Links are pinned to REL_19_BETA3; swap the tag when a new major branches.
Format version constants¶
| Upstream | Greenmask counterpart | What to check |
|---|---|---|
K_VERS_1_16 / K_VERS_MAJOR / K_VERS_MINOR / K_VERS_REV |
MaxVersion, BackupVersions |
A bumped K_VERS_MINOR means new fields were added to the entry or header layout. Add the new 1.N key and gate the new fields identically. |
K_VERS_MAX |
version guard in readHeader |
Upper bound accepted by pg_restore; ours must not exceed it. |
archUnknown / archCustom / archTar / archNull / archDirectory |
ArchTar and friends |
Format byte values. |
pg_compress_algorithm |
PgCompressionNone…PgCompressionZSTD |
Ordinal values of the compression algorithm byte written since format 1.15. |
Header read/write¶
| Upstream | Greenmask counterpart | What to check |
|---|---|---|
WriteHead() |
Writer.writeHeader |
Exact field order: magic PGDMP, major, minor, rev, intSize, offSize, format, compression algorithm, seven tm ints, db name, remote version, dump version. |
ReadHead() |
Reader.readHeader |
Same order, plus the version gates (>= 1.7 offset size, >= 1.15 compression algorithm, >= 1.4 timestamp and db name, >= 1.10 version strings). |
TOC entry read/write¶
| Upstream | Greenmask counterpart | What to check |
|---|---|---|
WriteToc() |
Writer.writeEntries |
Per-entry field order: dumpId, hadDumper, tableoid, oid, tag, desc, section, defn, dropStmt, copyStmt, namespace, tablespace, tableam, relkind, owner, literal "false", dependency list, NULL terminator, then the format's extra TOC. |
ReadToc() |
Reader.readEntries |
Same order and the same version gates (>= 1.8 tableoid, >= 1.11 section, >= 1.3 copyStmt, >= 1.6 namespace, >= 1.10 tablespace, >= 1.14 tableam, >= 1.16 relkind, >= 1.5 dependencies). |
ArchiveEntry() |
Entry struct |
New members on _tocEntry are the early warning that a format bump is coming. |
Primitive encoders¶
| Upstream | Greenmask counterpart | What to check |
|---|---|---|
WriteInt() / ReadInt() |
writeInt / readInt |
Sign byte then intSize little-endian bytes. Only intSize == 4 is supported by our port. |
WriteStr() / ReadStr() |
writeStr / readStr |
Length-prefixed, -1 encodes NULL. |
WriteOffset() / ReadOffset() |
not ported | Only used by the custom format. If we ever support -Fc, this is required. |
Directory format specifics¶
| Upstream | Greenmask counterpart | What to check |
|---|---|---|
_CloseArchive() |
format check in Reader.readHeader |
The directory format deliberately writes archTar (3) into the format byte of toc.dat. Our reader rejects anything else. |
_ArchiveEntry() |
entries/table.go, entries/large_object.go |
Data file naming: %d.dat for table data, blobs_%d.toc for large objects. |
_WriteExtraToc() / _ReadExtraToc() |
trailing FileName in writeEntries / readEntries |
For the directory format the "extra TOC" is a single string — the data file name. If upstream ever writes more than one field here, our entry loop desynchronises. |
Regression coverage¶
tests/integration/greenmask/toc_readwriter_test.go round-trips a real toc.dat produced by the
version under test. It is the practical guard for everything in this section — if the layout changed
and we did not follow, this test fails against the new version.
Adding a new major version¶
Step 1: Run the pre-release checklist against the new branch¶
Steps 1–7 above, diffing the new tag against the newest currently supported one. Fix whatever it surfaces before touching the test matrix.
Step 2: Add the database service¶
In docker-compose-integration.yml:
db-19:
profiles: ["pg19", "all"]
volumes:
- "/var/lib/postgresql/19/data"
image: postgres:19beta3
ports:
- "54319:5432"
restart: always
environment:
POSTGRES_PASSWORD: example
healthcheck:
test: ["CMD", "psql", "-U", "postgres"]
interval: 5s
timeout: 1s
retries: 3
Port convention is 543<major>. Beta releases are published as postgres:19beta3; switch to
postgres:19 at GA.
Step 3: Wire the new profile into the shared services¶
In the same file, add "pg19" to the profiles list of storage, test-dbs-filler, and greenmask,
add the db-19 dependency to test-dbs-filler, and extend PG_VERSIONS_CHECK on both
test-dbs-filler and greenmask:
test-dbs-filler:
profiles: ["pg14", "pg15", "pg16", "pg17", "pg18", "pg19", "all"]
environment:
PG_VERSIONS_CHECK: "14,15,16,17,18,19"
depends_on:
db-19:
condition: service_healthy
required: false
greenmask:
profiles: ["pg14", "pg15", "pg16", "pg17", "pg18", "pg19", "all"]
environment:
PG_VERSIONS_CHECK: "14,15,16,17,18,19"
filldb.sh needs no change — it derives the host name db-$pgver from PG_VERSIONS_CHECK.
Step 4: Install the client binaries in the test image¶
In docker/integration/tests/Dockerfile, add postgresql-19 to the apt install list. Greenmask
resolves pg_dump/pg_restore from /usr/lib/postgresql/${pg_version}/bin/, so the package must be
present for the version to be testable.
Beta packages are not in the default PGDG repository — they live in -pgdg-testing. Until GA, add:
RUN echo "deb https://apt.postgresql.org/pub/repos/apt $(lsb_release -sc)-pgdg-testing main 19" \
> /etc/apt/sources.list.d/pgdg-testing.list
Step 5: Run the tests for the new version alone¶
docker compose -f docker-compose-integration.yml --profile pg19 up
Step 6: Update this document and the release notes¶
Add the version to the support table with its upstream EOL date, and record any behavioural caveat found in step 7 of the checklist.
Dropping an EOL version¶
Do this at the first release after the upstream EOL date.
- Remove the
db-<major>service fromdocker-compose-integration.yml. - Remove
pg<major>from everyprofileslist and from bothPG_VERSIONS_CHECKvariables. - Remove
postgresql-<major>fromdocker/integration/tests/Dockerfile. - Remove the row from the support table in this document.
- Remove now-dead version gates. Introspection queries are templated on
server_version_num({{ if ge .Version NNNNNN }}ininternal/db/postgres/context/queries.goand theversion >=checks intables_introspection.go); gates below the new floor can go. - Note the drop in the release notes.
Historical reference¶
Commits that show the shape of a typical version bump:
- PostgreSQL 17 support
- PostgreSQL 18 support
- PostgreSQL 18 sequence privileges — an example of a behaviour change found by step 6 of the checklist rather than by a format diff
The main integration test is
tests/integration/greenmask/backward_compatibility_test.go.