ForgeSchema Now Manages Enums, Arrays and Views and Posts Plans on Pull Requests
ForgeSchema 0.4.0 is out. Until now, pointing it at a typical PostgreSQL database often ended at the first step: inspection refused any schema with an enum type, an array column or a view. Plenty of real schemas have all three. This release reads them, plans them and verifies them.
It also runs on PostgreSQL 18, and it can put its plan on a pull request, where the review already happens.
Enums and arrays
A schema file can now declare enum types with their values in order, and any column can use one. Any supported type, or an enum, can also be a one-dimensional array: text[], integer[], order_state[]. An enum column takes one of its values as a default. An array column takes '{}'.
PostgreSQL can add enum values but never take them away, and ForgeSchema plans exactly that. A new value goes in at the position the file gives it, before or after an existing one. Removing or reordering values is blocked, with the reason. There’s one catch worth knowing: PostgreSQL won’t let the transaction that adds a value use it. So a plan that adds a value and also uses it in a default or a view is blocked too. Add the value in one plan and use it in the next.
Views, in the right order
Views get a query and a depends_on list of the tables and views they read. The query is compared the way PostgreSQL stores it, so the file has to say what PostgreSQL would print. forgeschema inspect prints it that way. If you write it differently, apply notices that the result doesn’t match, rolls back, and tells you the stored form to copy.
The harder part is change. PostgreSQL refuses to rename, convert or drop a column while a view reads it. ForgeSchema drops those views first, readers before what they read, makes the change, then creates them again in dependency order. All of it happens in one transaction. A view holds no data, so nothing is lost, and the explanation for each drop says why it’s there.
We ran the release binary through exactly that: an orders table with two views on top, one reading the other. The plan renamed quantity under both views and added an enum value. It came out as six steps: two view drops, the new value, the rename, two view creates. The totals the second view reported were the same before and after.
PostgreSQL 18 and the pull request
CI now runs every test against the current minor of PostgreSQL 16, 17 and 18. Version 18 stores NOT NULL as a real constraint in the catalog, and inspection reads it as the column’s nullability. Its new NOT ENFORCED constraints are refused, since the schema contract has no such thing.
The GitHub Action inspects a database you point it at, plans to the schema file in the branch and posts plan --explain as a comment. Later runs update that same comment. A blocked plan fails the check. With mode: verify it checks a database against the file after a deploy instead. It never applies anything.
- uses: ForgeSchema/[email protected]
with:
database-url: ${{ secrets.STAGING_DATABASE_URL }}
schema: db/schema.yaml
Found in review
The independent review found no blocker and four things to fix. An enum named with an SQL keyword, like order, couldn’t round-trip a default, because PostgreSQL quotes only that half of the cast. A view using an enum value added in the same plan got a ready plan that always failed. The README still listed views as unsupported. And the Action’s macOS and Windows install path had never run. All fixed, each with a test. The second round caught one more: the new enum value check was matching 'a'::status inside 'a'::status_old. Now it reads each literal and its cast in full.
Documents without enums or views keep the same fingerprint as before, and plans saved by earlier versions still load and apply. Binaries are on the download page. The Action guide has the inputs and outputs.