PostgreSQL versions
This page says which PostgreSQL majors this build accepts, what each verb does at each one, and what the engine version governs. Only the major is modeled. Each guide states its own verb’s row and links here.
Levels
| Major | Level | What it means |
|---|---|---|
| 17 | Default, supported | The value init writes to [project] engine_version, and the one a schema resolves to when nothing names another. |
| 15, 16 | Supported | Every verb works at the pin, and snapshot and migrate emit the same SQL as at 17. The known gaps apply. |
| 18 | Accepted, not supported | Taken only as an explicit pin. op add, validate, snapshot and migrate honor it; import and drift read PostgreSQL 17 syntax only, and several PostgreSQL 18 surfaces refuse or do not render. |
| Any other value | Refused | unknown-engine-version. The value is a bare major: 14, 19, 18.1, 018, v18 and 18 all refuse. |
An explicit pin is any of:
schema create --engine-version <major>orimport --engine-version <major>, which set a new schema’s pin and emit target together;schema engine <major>, which moves an existing schema’s pin and emit target (--config-onlymoves the target alone);engine_versionunder[project](the value a new schema takes when no flag names one) or under a[[schema]]row (that schema’s emit target);--using=postgres:<major>onsnapshotormigrate, which sets the emit target for that run.
What the engine version governs
Each schema carries two values.
- The pin is the oplog’s
# engine-version: <major>header: the major production runs.op addrefuses an op gated above the pin withengine-version-required,validatereports one already in the log withunsupported-on-engine-version, anddriftrefuses a candidate gated above it withunsupported-on-engine-version. - The emit target is the major
snapshotandmigratewrite SQL for. It resolves from--using=postgres:<major>, then the schema’s[[schema]] engine_version, then[project] engine_version, then 17. The snapshot banner names it (engine_version=<major>) and everymigratefile opens with-- engine: postgres <major>. A cut holding an op gated above the target refuses withunsupported-on-engine-version.
The target may not lag the pin: a schema whose [[schema]] engine_version
is below its oplog’s header fails at config load with
engine-version-target-below-oplog.
No command connects to a database, so neither value is read from a server. The header line states the major the script was written for; PostgreSQL at that major decides whether its DDL is valid.
Per verb
15, 16 and 17
No operator or parameter is gated inside this range. snapshot and
migrate emit the same SQL at each of the three majors; only the banner
and the header line name the major. The known gaps for 15
and 16 are the exceptions.
18
The PostgreSQL 18 surfaces are the SetConstraintEnforced and
SetConstraintInherit operators, and these parameter values:
generated_kind=virtual on AddColumn; enforced=false on
AddForeignKey and AddCheck; period=true on AddForeignKey; and
without_overlaps=true on AddPrimaryKey, AddUnique and
ChangePrimaryKey. Below 18, op add refuses each of them with
engine-version-required.
| Verb | At 18 |
|---|---|
op add | Appends SetConstraintEnforced (on a foreign key), SetConstraintInherit and a VIRTUAL generated column. The enforced=false, period=true and without_overlaps=true values still refuse, with constraint-facet-uncarried: the oplog has no place to carry them. A DropForeignKey whose reverse re-adds a partitioned table’s key NOT VALID appends; below 18 it refuses with reverse-policy-partitioned-fk. |
validate | Passes a log holding the PostgreSQL 18 surfaces. Under a lower pin it reports each with unsupported-on-engine-version. |
snapshot | Renders SetConstraintEnforced value=false as NOT ENFORCED on the foreign key. Emits nothing for SetConstraintInherit. Writes a VIRTUAL generated column as STORED. |
migrate | Renders a VIRTUAL generated column as VIRTUAL. Renders neither SetConstraintEnforced nor SetConstraintInherit: forward, a window holding one refuses with migrate-blocked-on-unsupported-smo, and so does a --down that would set a key’s enforceability back; a --down over a window holding SetConstraintInherit refuses with migrate-non-reversible. Renders a NOT VALID foreign key on a partitioned table, which 15 to 17 refuse with migrate-blocked-on-partitioned-fk-not-valid. |
import, drift | Accept an 18 pin, but parse with the PostgreSQL 17 grammar. PostgreSQL 18 syntax (NOT ENFORCED, VIRTUAL generated columns, WITHOUT OVERLAPS, PERIOD foreign keys, ALTER CONSTRAINT … [NO] INHERIT) does not parse: the run exits 1 with parse-error. |
SetConstraintInherit records lineage only: no verb emits DDL for it.
Testing
Continuous integration runs the live-server test suite once per major, against a PostgreSQL 15, 16, 17 and 18 server. The SQL a test emits comes from the oplog’s engine version, not from the server, and most tests use the default 17, so on 15, 16 and 18 the run shows that the server accepts the SQL 17 does. The tests that pin 18 to apply a PostgreSQL 18 surface run against the 18 server only.
Known gaps
15 and 16:
- The
AttachPartitioncolumn check applies PostgreSQL 17’s rules at every pin. A partition that brings its own identity column attaches on 15 and 16, and aSTOREDgenerated partition column under a plain parent column attaches on 15;importandop addrefuse both, namingattach-partition-columns-mismatch. - The same check does not compare generated expressions, so a 15 pin
accepts a partition whose generated expression differs from its parent’s.
PostgreSQL 15 refuses that
ATTACH, so the emitted script fails on a 15 server.
18:
enforced=false,period=trueandwithout_overlaps=truerefuse withconstraint-facet-uncarried.migraterenders neitherSetConstraintEnforcednorSetConstraintInherit, and no verb emits DDL forSetConstraintInherit.snapshotwrites aVIRTUALgenerated column asSTORED.importanddriftcannot read PostgreSQL 18 syntax.