[PATCH] Fix null pointer dereference in PG19

First seen: 2026-04-21 13:40:49+00:00 · Messages: 9 · Participants: 4

Latest Update

2026-05-18 · claude-opus-4-6

Technical Analysis: Null Pointer Dereference in FOR PORTION OF with Views and INSTEAD OF Triggers

Core Problem

A null pointer dereference crash occurs in PostgreSQL 19 when combining three features: views, the FOR PORTION OF syntax (a temporal table feature), and INSTEAD OF triggers. The crash manifests in ExecModifyTable() around line 4827, where the code path for FOR PORTION OF processing does not account for the case where the target relation is a view (RELKIND_VIEW).

Root Cause Mechanics

When a DML operation targets a view with an INSTEAD OF trigger, the executor does not have a physical tuple identifier (tupleid) because views don't store tuples — the trigger handles the actual modification. However, the FOR PORTION OF machinery (specifically ExecForPortionOfLeftovers(), called from ExecDeleteEpilogue() or ExecUpdateEpilogue()) unconditionally dereferences tupleid, which is NULL in the view case. This is a straightforward oversight in the interaction between the relatively new FOR PORTION OF feature and the existing INSTEAD OF trigger code path.

Architectural Issues Identified

Tom Lane's review exposed much deeper architectural concerns beyond the immediate crash fix. The FOR PORTION OF implementation (originally committed as part of a larger temporal tables feature) has several parse-time checks that violate PostgreSQL's phase separation principles:

1. Premature View/FDW Checks at Parse Time

The original code in transformForPortionOfClause checks relkind at parse time. This is architecturally wrong because:

2. Premature Volatility Check

contain_volatile_functions_after_planning is called at parse time, but this function is designed for post-planning use (as its name implies). It should be called in the planner where constant folding has already occurred, making it both more correct and cheaper.

3. FDW Partition Coverage Gap

The anti-FDW check at parse time cannot detect the case where a partitioned table has foreign data wrapper partitions as children. This requires runtime checking to properly handle partition routing.

4. Opclass Lookup Timing (Partially Retracted)

Tom initially flagged the opclass lookups as premature, but later partially retracted this concern. The analogy to IN-clause equality operator resolution makes the parse-time opclass resolution acceptable — the dependency is on specific operators/functions, not on the opclass structure itself. However, he noted that dependencies must be properly recorded, and that dependency tracking only guarantees existence, not property stability.

Proposed Solutions

Immediate Fix (v4/v5 by Paul Jungwirth)

Structural Fix (jian he's approach)

Volatility Check Placement

jian he analyzed the ForPortionOfExpr structure and determined:

Final Combined Approach

Paul Jungwirth created a new thread with a commitfest entry combining:

  1. jian he's CheckValidResultRel approach for runtime validation (preferred over Paul's own patch)
  2. Paul's test additions including a case for pruned FDW partitions (ensuring no false-positive errors when a partition child FDW is pruned away)
  3. Moving the volatility check to plan-time

Key Design Tradeoffs

  1. Parse-time vs. execution-time checks: PostgreSQL's stored parse trees (in rules, SQL functions) mean parse-time assumptions about relkind, triggers, and catalog state are fragile. The principle is: anything that could change must be checked at execution time.

  2. Error granularity vs. performance: Moving checks to execution time means some errors that could be caught early are deferred. However, correctness trumps early error reporting.

  3. Future-proofing CheckValidResultRel: Passing the full ModifyTable node rather than individual fields anticipates that other execution-time checks may need access to more plan information.