Conversation
A view's definition is compared as text, but the text pg_get_viewdef returns is not necessarily its own output. Deparsing names an output column the definition left unnamed — an untyped literal in a UNION branch inside a subquery — and creating a view from that text names the column after its type, so the re-created view deparses to a different text than the view it was created from. Two consequences, both reachable with a database whose schema is the desired one: a plan validation rebuilds the current schema in a temporary database and then reports the rebuild as a difference from the schema it was built from and fails; and a plan that re-creates such a view never converges, because each re-creation changes the text again. The fetcher now records, next to the definition a plan emits, the definition's canonical form: the text PostgreSQL returns when the view is created from that definition and read back. One round trip through the parser and the deparser is a fixed point. The diff compares the canonical form, so two databases that hold the same view under two texts are equal, while a change to the view is still a change. A materialized view goes through the same round trip with a temporary view, because a materialized view cannot be created in pg_temp. The definition a plan emits is unchanged.
dilame
force-pushed
the
fix/view-definition-canonical
branch
from
September 27, 2026 01:07
8bb8b85 to
8790d14
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What changed
A view and a materialized view now carry, next to the definition a plan emits, the canonical form of that definition: the text PostgreSQL returns when the relation is created from the definition and read back. The diff compares the canonical form.
ViewandMaterializedViewgainViewDefinitionCanonical. The schema fetcher fills it with one round trip: create the definition as a temporary view inpg_temp, readpg_get_viewdefback, drop it. A materialized view goes through a temporary view over the same query, because a materialized view cannot be created inpg_temp.GetSchemaalready disables concurrency for those.Why
pg_get_viewdefis not idempotent. Deparsing names an output column the definition left unnamed, so re-creating a view from that text can change the view it was re-created from:The second UNION branch is an untyped literal: the parser leaves its
resnameunset, so the branch is deparsed without an alias; parsing'incentive'::textinstead gives the item aresnameoftext, which the next deparse prints. The view's output column names are unaffected — they come from the union's first branch.A view is compared by its definition text, so two databases that hold the same view under two texts read as a difference. Two failures follow. Plan validation builds the current schema in a temporary database and then reports that rebuild as a difference from the schema it was built from, and does not build at all. And a plan that re-creates such a view never converges: each re-creation lands on a new text, so the next plan re-creates it again.
One round trip through the parser and the deparser is a fixed point, which is what makes the comparison stable: it is the same for both databases, and a change to the view's query is still a change.
Known limitation
The definition a plan emits is unchanged, so a database the tool has written holds the text
pg_get_viewdefproduced for the view the tool created, while a database built by running the target DDL holds the text that DDL deparses to. For a view of the shape above those two texts differ. That difference is not introduced here — creating the view from the target's text already has that outcome — and this change does not remove it. What it removes is the difference read from a view that already exists, and with it the plan that re-created such a view on every run.Validation
go test ./...New acceptance cases, each failing without the comparison change: a view, a recursive view, and a materialized view whose definition holds an untyped literal in a nested UNION, unchanged between the two schemas, must produce an empty plan and pass validation.