UPDATE/DELETE FOR PORTION OF on Updatable Views: Rewriter Assertion Failure and Server Crash
Core Problem
This thread reports a bug in PostgreSQL 19's temporal table feature — specifically the FOR PORTION OF clause of UPDATE/DELETE — when combined with the automatically-updatable views subsystem. The issue exposes a gap in the view rewriter's validation logic: it fails to reject FOR PORTION OF operations targeting view columns that are not simple references to a base table column (e.g., they involve expressions like ('[' || abs(b) || ',20]')::int4range).
Reproducer
CREATE TABLE base_tbl (a int, b int, c int);
CREATE VIEW rw_view15 AS
SELECT a, ('[' || abs(b) || ',20]')::int4range AS b
FROM base_tbl;
UPDATE rw_view15 FOR PORTION OF b FROM 1 TO 10 SET a = 2;
-- ERROR: attribute number 2 not found in view targetlist
-- (from rewriteTargetView, rewriteHandler.c:3779)
DELETE FROM rw_view15 FOR PORTION OF b FROM 1 TO 10;
-- Server crash
Architectural Significance
The failure location — rewriteTargetView() in rewriteHandler.c — is the heart of the automatically-updatable views machinery. When a DML statement targets a view, the rewriter translates references to view columns into references on the underlying base relation. This translation requires each affected view column to be a plain Var pointing at a base-table column; expressions cannot be "reverse-engineered" into assignments on the base table.
The existing updatability logic correctly rejects UPDATE rw_view15 SET b = ... with:
cannot update column "b" of view "rw_view15"/View columns that are not columns of their base relation are not updatable.
However, FOR PORTION OF is a new way in which an UPDATE/DELETE statement can implicitly modify a column: the temporal range column is split by the server into pre- and post-portion rows, which requires writing back adjusted range values into that column. The updatability analysis added for FOR PORTION OF did not account for this case, so:
- For
UPDATE, the rewriter proceeds past the initial check and later trips an internal sanity assertion (attribute number N not found in view targetlist), which is anXX000internal error — user-visible evidence of a missed check. - For
DELETE, the path is even less defended and dereferences something invalid, crashing the backend.
Both should be caught early in rewriteTargetView and surfaced as clean user errors consistent with the existing "not updatable" family.
Proposed Fix
Paul Jungwirth — the original author of the SQL:2011 temporal features in PostgreSQL — attached a patch about four weeks after the report. Based on his description, the fix:
-
Extends the view-updatability check in
rewriteTargetView(or the helperview_cols_are_auto_updatable/view_col_is_auto_updatable) so that when the query carries aFOR PORTION OFclause, the column named in that clause is verified to be a simple base-table column reference, not an expression. -
Emits a dedicated error message for the DELETE case. Jian Guo's initial suggestion was:
DELETE ... FOR PORTION OF is not supported for column "b" on view "rw_view15"Jungwirth rephrased this to align with the stylistic conventions of the surrounding error messages in
rewriteHandler.c(likely retaining thecannot ... column "..." of view "..."form and putting the FOR PORTION OF specifics in the DETAIL/HINT).
Implications
- No catalog or planner change is required. This is strictly a rewriter validation gap: the correct behavior is to reject the statement, not to make it work. Making a FOR PORTION OF on an expression column actually succeed would require inverting arbitrary expressions, which is not generally possible — the same fundamental reason
UPDATE ... SET expr_col = ...is not supported on non-trivial view columns. - Back-patch scope: FOR PORTION OF is new in v19, so the fix is a v19 open item only; there is no back-patch exposure to stable branches.
- Test coverage: a regression test using a view with an expression-derived range column is the natural addition, exercising both the UPDATE and DELETE paths to ensure neither crashes nor emits an
XX000.
Key Technical Insights
-
XX000in a user-reachable path is always a bug. Theattribute number 2 not found in view targetlistmessage originates from an internal consistency check; encountering it from pure SQL means an earlier semantic gate was missing. This is the classic signature of a feature (FOR PORTION OF) that was not cross-wired with an orthogonal subsystem (auto-updatable views). -
FOR PORTION OF is an implicit write to the range column. Unlike a regular UPDATE, the user does not name the range column in SET, yet the executor must still write to it. Any code that enumerates "columns being modified" for authorization or updatability purposes must treat the FOR PORTION OF target column as written, even for DELETE — which is unusual, since DELETE normally writes no columns.
-
The DELETE crash vs. UPDATE error asymmetry suggests the UPDATE path at least reaches a defensive check in the rewriter, while the DELETE path skips it entirely. This is a hint that the FOR PORTION OF integration added validation on the UPDATE rewrite branch but forgot the symmetric DELETE branch — a common oversight given that DELETE historically has no targetlist manipulation.
-
Trust in the fix: Paul Jungwirth is the implementer of the temporal/FOR PORTION OF feature itself, so he is the domain authority here. A fix from him on his own feature, during the v19 open-items window, is essentially the expected and authoritative resolution.
Participant Roles
- Jian Guo (reporter): Frequent bug-hunter on pgsql-hackers, particularly skilled at finding edge cases in rewriter/planner interactions. Provided a minimal reproducer, identified the exact source location of the assertion, and proposed appropriate user-facing error wording for both the UPDATE and DELETE cases. Committed to adding the item to the PG19 Open Items wiki.
- Paul Jungwirth (fix author): Author of the SQL:2011 application-time / FOR PORTION OF feature in PostgreSQL. Owns the fix by virtue of authoring the buggy feature; produced a patch and committed to opening a CF entry.
Timeline
The issue was reported in mid-April 2026, during the v19 development / open-items phase. About four weeks later, in early May 2026, the feature's original author posted a patch. No disagreement emerged on the mailing list; the proposed direction — reject these statements in rewriteTargetView with a message consistent with existing view-updatability errors — is the only architecturally sound option.