Thread
Commits
GET /api/v1/messages/:b64id/commits
the thread's linked commits as JSON, with link sources.
API reference →
-
Fix bogus ctid requirement for dummy-root partitioned targets
- f9a468c664a4 19 (unreleased) landed
- 9f4b7bfc5eb6 18.2 landed
- 933f67fb6a79 17.8 landed
- fab386f74888 16.12 landed
- 687533b39ecf 15.16 landed
- 6d2fa44d2a61 14.21 landed
-
BUG #19099: Conditional DELETE from partitioned table with non-updatable partition raises internal error
PG Bug reporting form <noreply@postgresql.org> — 2025-10-29T20:00:02Z
The following bug has been logged on the website: Bug reference: 19099 Logged by: Alexander Lakhin Email address: exclusion@gmail.com PostgreSQL version: 18.0 Operating system: Ubuntu 24.04 Description: The following script: CREATE EXTENSION file_fdw; CREATE SERVER file_server FOREIGN DATA WRAPPER file_fdw; CREATE TABLE pt (a int, b text) partition by list (a); CREATE FOREIGN TABLE p1 partition of pt for values in (1) SERVER file_server OPTIONS (format 'csv', filename '/tmp/1.csv'); SET enable_partition_pruning = 'off'; EXPLAIN DELETE FROM pt WHERE false; raises: ERROR: XX000: could not find junk ctid column LOCATION: ExecInitModifyTable, nodeModifyTable.c:4867 (Discovered with SQLsmith.) Reproduced starting from 86dc9005. On 86dc9005~1 or with enable_partition_pruning = 'on', EXPLAIN outputs the query plan and "DELETE FROM pt WHERE false;" completes with no error.
-
Re: BUG #19099: Conditional DELETE from partitioned table with non-updatable partition raises internal error
jian he <jian.universality@gmail.com> — 2025-10-30T04:07:03Z
On Thu, Oct 30, 2025 at 9:02 AM PG Bug reporting form <noreply@postgresql.org> wrote: > > The following bug has been logged on the website: > > Bug reference: 19099 > Logged by: Alexander Lakhin > Email address: exclusion@gmail.com > PostgreSQL version: 18.0 > Operating system: Ubuntu 24.04 > Description: > > The following script: > CREATE EXTENSION file_fdw; > CREATE SERVER file_server FOREIGN DATA WRAPPER file_fdw; > CREATE TABLE pt (a int, b text) partition by list (a); > CREATE FOREIGN TABLE p1 partition of pt for values in (1) SERVER file_server > OPTIONS (format 'csv', filename '/tmp/1.csv'); > SET enable_partition_pruning = 'off'; > EXPLAIN DELETE FROM pt WHERE false; > > raises: > ERROR: XX000: could not find junk ctid column > LOCATION: ExecInitModifyTable, nodeModifyTable.c:4867 > (Discovered with SQLsmith.) > > Reproduced starting from 86dc9005. > > On 86dc9005~1 or with enable_partition_pruning = 'on', EXPLAIN outputs the > query plan and "DELETE FROM pt WHERE false;" completes with no error. > we can add a postgresAddForeignUpdateTargets(postgres_fdw) equivalent function for file_fdw even though we do not support UPDATE/DELETE in file_fdw.
-
Re: BUG #19099: Conditional DELETE from partitioned table with non-updatable partition raises internal error
Tender Wang <tndrwang@gmail.com> — 2025-10-30T04:41:01Z
jian he <jian.universality@gmail.com> 于2025年10月30日周四 12:07写道: > On Thu, Oct 30, 2025 at 9:02 AM PG Bug reporting form > <noreply@postgresql.org> wrote: > > > > The following bug has been logged on the website: > > > > Bug reference: 19099 > > Logged by: Alexander Lakhin > > Email address: exclusion@gmail.com > > PostgreSQL version: 18.0 > > Operating system: Ubuntu 24.04 > > Description: > > > > The following script: > > CREATE EXTENSION file_fdw; > > CREATE SERVER file_server FOREIGN DATA WRAPPER file_fdw; > > CREATE TABLE pt (a int, b text) partition by list (a); > > CREATE FOREIGN TABLE p1 partition of pt for values in (1) SERVER > file_server > > OPTIONS (format 'csv', filename '/tmp/1.csv'); > > SET enable_partition_pruning = 'off'; > > EXPLAIN DELETE FROM pt WHERE false; > > > > raises: > > ERROR: XX000: could not find junk ctid column > > LOCATION: ExecInitModifyTable, nodeModifyTable.c:4867 > > (Discovered with SQLsmith.) > > > > Reproduced starting from 86dc9005. > > > > On 86dc9005~1 or with enable_partition_pruning = 'on', EXPLAIN outputs > the > > query plan and "DELETE FROM pt WHERE false;" completes with no error. > > > > we can add a postgresAddForeignUpdateTargets(postgres_fdw) equivalent > function for file_fdw even though we do not support UPDATE/DELETE in > file_fdw. > After applying your patch, I got a different output if I enable verbose in EXPLAIN: postgres=# EXPLAIN verbose DELETE FROM pt WHERE false; QUERY PLAN ------------------------------------------------------- Delete on public.pt (cost=0.00..0.00 rows=0 width=0) -> Result (cost=0.00..0.00 rows=0 width=0) Output: ctid Replaces: Scan on pt One-Time Filter: false (5 rows) postgres=# set enable_partition_pruning = 'off'; SET postgres=# EXPLAIN verbose DELETE FROM pt WHERE false; QUERY PLAN ------------------------------------------------------- Delete on public.pt (cost=0.00..0.00 rows=0 width=0) -> Result (cost=0.00..0.00 rows=0 width=0) Output: NULL::oid, NULL::tid Replaces: Scan on pt One-Time Filter: false (5 rows) Output: ctid (enable_partition_pruning = on) vs Output: NULL::oid, NULL::tid(enable_partition_pruning = off) I try add childrte->relkind != RELKIND_PARTITIONED_TABLE && childrte->relkind != RELKIND_FOREIGN_TABLE) to avoid adding "tableoid" for foreign table in expand_single_inheritance_child(). It works, but the file_fdw regression test failed. I added Tom and Amit to the cc list. Any thoughts? -- Thanks, Tender Wang -
Re: BUG #19099: Conditional DELETE from partitioned table with non-updatable partition raises internal error
Kirill Reshke <reshkekirill@gmail.com> — 2025-10-30T04:52:33Z
On Thu, 30 Oct 2025 at 09:41, Tender Wang <tndrwang@gmail.com> wrote: > > > > jian he <jian.universality@gmail.com> 于2025年10月30日周四 12:07写道: >> >> On Thu, Oct 30, 2025 at 9:02 AM PG Bug reporting form >> <noreply@postgresql.org> wrote: >> > >> > The following bug has been logged on the website: >> > >> > Bug reference: 19099 >> > Logged by: Alexander Lakhin >> > Email address: exclusion@gmail.com >> > PostgreSQL version: 18.0 >> > Operating system: Ubuntu 24.04 >> > Description: >> > >> > The following script: >> > CREATE EXTENSION file_fdw; >> > CREATE SERVER file_server FOREIGN DATA WRAPPER file_fdw; >> > CREATE TABLE pt (a int, b text) partition by list (a); >> > CREATE FOREIGN TABLE p1 partition of pt for values in (1) SERVER file_server >> > OPTIONS (format 'csv', filename '/tmp/1.csv'); >> > SET enable_partition_pruning = 'off'; >> > EXPLAIN DELETE FROM pt WHERE false; >> > >> > raises: >> > ERROR: XX000: could not find junk ctid column >> > LOCATION: ExecInitModifyTable, nodeModifyTable.c:4867 >> > (Discovered with SQLsmith.) >> > >> > Reproduced starting from 86dc9005. >> > >> > On 86dc9005~1 or with enable_partition_pruning = 'on', EXPLAIN outputs the >> > query plan and "DELETE FROM pt WHERE false;" completes with no error. >> > >> >> we can add a postgresAddForeignUpdateTargets(postgres_fdw) equivalent >> function for file_fdw even though we do not support UPDATE/DELETE in file_fdw. > > > After applying your patch, I got a different output if I enable verbose in EXPLAIN: > postgres=# EXPLAIN verbose DELETE FROM pt WHERE false; > QUERY PLAN > ------------------------------------------------------- > Delete on public.pt (cost=0.00..0.00 rows=0 width=0) > -> Result (cost=0.00..0.00 rows=0 width=0) > Output: ctid > Replaces: Scan on pt > One-Time Filter: false > (5 rows) > > postgres=# set enable_partition_pruning = 'off'; > SET > postgres=# EXPLAIN verbose DELETE FROM pt WHERE false; > QUERY PLAN > ------------------------------------------------------- > Delete on public.pt (cost=0.00..0.00 rows=0 width=0) > -> Result (cost=0.00..0.00 rows=0 width=0) > Output: NULL::oid, NULL::tid > Replaces: Scan on pt > One-Time Filter: false > (5 rows) > > Output: ctid (enable_partition_pruning = on) > vs > Output: NULL::oid, NULL::tid(enable_partition_pruning = off) > > I try add childrte->relkind != RELKIND_PARTITIONED_TABLE && childrte->relkind != RELKIND_FOREIGN_TABLE) > to avoid adding "tableoid" for foreign table in expand_single_inheritance_child(). > It works, but the file_fdw regression test failed. > > I added Tom and Amit to the cc list. > Any thoughts? > -- > Thanks, > Tender Wang Hi! Jian's fix WFM, I confirm 'EXPLAIN DELETE FROM pt WHERE false' now works. Should we add this test case to the regression suite of file_fdw? But I also wonder if Jian's fix fixed the right thing. Should we instead fail in the planning phase with a more user-friendly error message? This will be a regression though, because 'DELETE FROM file_fdw_table WHERE false' will no longer work... As for EXPLAIN VERBOSE output, are they both confusing? Both for enable_partition_pruning=on and enable_partition_pruning=off? I mean, file_fdw does not have semantics of neither ctid nor tid? -- Best regards, Kirill Reshke
-
Re: BUG #19099: Conditional DELETE from partitioned table with non-updatable partition raises internal error
Tom Lane <tgl@sss.pgh.pa.us> — 2025-10-30T05:08:23Z
Tender Wang <tndrwang@gmail.com> writes: > I added Tom and Amit to the cc list. > Any thoughts? I'm having a hard time getting super excited about this. file_fdw does not support DELETE -- it provides no ExecForeignDelete method -- which is why you get this: regression=# DELETE FROM pt; ERROR: cannot delete from foreign table "p1" It's surely pretty accidental (and arguably not desirable) if "DELETE FROM pt WHERE false" doesn't fail the same way. Now, I agree that it's not great if you instead get an internal error like "could not find junk ctid column". But that smells to me like error checks being applied in the wrong order rather than something fundamentally wrong. I didn't look at the proposed patch yet. regards, tom lane
-
Re: BUG #19099: Conditional DELETE from partitioned table with non-updatable partition raises internal error
Kirill Reshke <reshkekirill@gmail.com> — 2025-10-30T05:31:38Z
On Thu, 30 Oct 2025 at 10:08, Tom Lane <tgl@sss.pgh.pa.us> wrote: > > It's surely pretty accidental (and arguably not desirable) > if "DELETE FROM pt WHERE false" doesn't fail the same way. > > Now, I agree that it's not great if you instead get an > internal error like "could not find junk ctid column". > But that smells to me like error checks being applied in > the wrong order rather than something fundamentally wrong. > > I didn't look at the proposed patch yet. > > regards, tom lane I wrote: > But I also wonder if Jian's fix fixed the right thing. Should we > instead fail in the planning phase with a more user-friendly error > message? This will be a regression though, because 'DELETE FROM > file_fdw_table WHERE false' will no longer work... On the second thought, I doubt anyone will get unhappy with 'DELETE FROM file_fdw_table WHERE false' stop working Alexander wrote: > On 86dc9005~1 or with enable_partition_pruning = 'on', EXPLAIN outputs the > query plan and "DELETE FROM pt WHERE false;" completes with no error. So, behaviour was wrong both before and after 86dc9005, just in different ways. On head, we get an error about junk columns, because without partition pruning, we derive the result relation as 'pt', not its partition 'p1', which is correct I believe. But with 'p1' as result relation, we (correctly) error out in ExecInitModifyTable while with 'pt' we don't. So, error checks are applied, order is not wrong, but rather checks are not full enough? I mean, we I believe we need to execute CheckValidResultRel against all partitions in ExecInitModifyTable, at least when no partition pruning has been performed -- Best regards, Kirill Reshke
-
Re: BUG #19099: Conditional DELETE from partitioned table with non-updatable partition raises internal error
Kirill Reshke <reshkekirill@gmail.com> — 2025-10-30T06:44:07Z
On Thu, 30 Oct 2025 at 10:31, I wrote: > > I mean, we I believe we need to execute > CheckValidResultRel against all partitions in ExecInitModifyTable, at > least when no partition pruning has been performed > So, the problem is that we managed to exclude all child relations, and only have a single (dummy) root relation as a result of the modifyTable plan. Maybe we should populate its target list with pseudo-junk columns in create_modifytable_plan ? For instance, they query does not error-out if we have at least one another non-file-fdw partition: create table p2 partition of pt for values in ( 2) ; this is because we have this in create_modifytable_plan ``` /* Transfer resname/resjunk labeling, too, to keep executor happy */ apply_tlist_labeling(subplan->targetlist, root->processed_tlist); ``` and we successfully found a junk column in the p2 partition. The problem is, it works iff root->processed_tlist has at least one relation which can give us junk columns. Should we add handling for corner case here? Another option is to remove this 'Transfer resname/resjunk labeling' completely and rework planner-executer contracts somehow. -- Best regards, Kirill Reshke
-
Re: BUG #19099: Conditional DELETE from partitioned table with non-updatable partition raises internal error
amit <amitlangote09@gmail.com> — 2025-10-30T08:29:37Z
Hi, On Thu, Oct 30, 2025 at 3:44 PM Kirill Reshke <reshkekirill@gmail.com> wrote: > On Thu, 30 Oct 2025 at 10:31, I wrote: > > > > I mean, we I believe we need to execute > > CheckValidResultRel against all partitions in ExecInitModifyTable, at > > least when no partition pruning has been performed > > > > So, the problem is that we managed to exclude all child relations, and > only have a single (dummy) root relation as a result of the > modifyTable plan. Maybe we should populate its target list with > pseudo-junk columns in create_modifytable_plan ? > > For instance, they query does not error-out if we have at least one > another non-file-fdw partition: > > create table p2 partition of pt for values in ( 2) ; > > this is because we have this in create_modifytable_plan > > ``` > /* Transfer resname/resjunk labeling, too, to keep executor happy */ > apply_tlist_labeling(subplan->targetlist, root->processed_tlist); > ``` > > and we successfully found a junk column in the p2 partition. > > The problem is, it works iff root->processed_tlist has at least one > relation which can give us junk columns. Should we add handling for > corner case here? > Another option is to remove this 'Transfer resname/resjunk labeling' > completely and rework planner-executer contracts somehow. I am not really sure if we should play with the planner code. I suspect the real issue is that we’re assuming partitioned tables always need a ctid, which wasn’t true before MERGE started using the root ResultRelInfo. In fact, the old code already looked wrong -- it’s been requiring a ctid even for partitioned tables where that was never necessary. We can fix this by only requiring the junk ctid when we actually operate through the root partitioned table, that is, for MERGE. Like the attached. -- Thanks, Amit Langote
-
Re: BUG #19099: Conditional DELETE from partitioned table with non-updatable partition raises internal error
Kirill Reshke <reshkekirill@gmail.com> — 2025-10-30T08:55:18Z
On Thu, 30 Oct 2025 at 13:29, Amit Langote <amitlangote09@gmail.com> wrote: > > Hi, > > On Thu, Oct 30, 2025 at 3:44 PM Kirill Reshke <reshkekirill@gmail.com> wrote: > > On Thu, 30 Oct 2025 at 10:31, I wrote: > > > > > > I mean, we I believe we need to execute > > > CheckValidResultRel against all partitions in ExecInitModifyTable, at > > > least when no partition pruning has been performed > > > > > > > So, the problem is that we managed to exclude all child relations, and > > only have a single (dummy) root relation as a result of the > > modifyTable plan. Maybe we should populate its target list with > > pseudo-junk columns in create_modifytable_plan ? > > > > For instance, they query does not error-out if we have at least one > > another non-file-fdw partition: > > > > create table p2 partition of pt for values in ( 2) ; > > > > this is because we have this in create_modifytable_plan > > > > ``` > > /* Transfer resname/resjunk labeling, too, to keep executor happy */ > > apply_tlist_labeling(subplan->targetlist, root->processed_tlist); > > ``` > > > > and we successfully found a junk column in the p2 partition. > > > > The problem is, it works iff root->processed_tlist has at least one > > relation which can give us junk columns. Should we add handling for > > corner case here? > > Another option is to remove this 'Transfer resname/resjunk labeling' > > completely and rework planner-executer contracts somehow. > > I am not really sure if we should play with the planner code. > > I suspect the real issue is that we’re assuming partitioned tables > always need a ctid, which wasn’t true before MERGE started using the > root ResultRelInfo. In fact, the old code already looked wrong -- it’s > been requiring a ctid even for partitioned tables where that was never > necessary. We can fix this by only requiring the junk ctid when we > actually operate through the root partitioned table, that is, for > MERGE. Like the attached. > > -- > Thanks, Amit Langote Hi! Thanks for the patch. I can see your points, however I am unsure if this is the most right thing to do. As per ab5fcf2b04f9 commit message and src/backend/optimizer/plan/planner.c comments, I am under impression that the postgres-way of fixing that would allow for ExecInitModifyTable to operate with a NULL result relation list. And, in any case, I am still unsure if we should allow the 'DELETE' statement from Alexander's repro to successfully execute, which yout patch still does -- Best regards, Kirill Reshke
-
Re: BUG #19099: Conditional DELETE from partitioned table with non-updatable partition raises internal error
amit <amitlangote09@gmail.com> — 2025-10-30T09:17:43Z
On Thu, Oct 30, 2025 at 5:55 PM Kirill Reshke <reshkekirill@gmail.com> wrote: > On Thu, 30 Oct 2025 at 13:29, Amit Langote <amitlangote09@gmail.com> wrote: > > > > Hi, > > > > On Thu, Oct 30, 2025 at 3:44 PM Kirill Reshke <reshkekirill@gmail.com> wrote: > > > On Thu, 30 Oct 2025 at 10:31, I wrote: > > > > > > > > I mean, we I believe we need to execute > > > > CheckValidResultRel against all partitions in ExecInitModifyTable, at > > > > least when no partition pruning has been performed > > > > > > > > > > So, the problem is that we managed to exclude all child relations, and > > > only have a single (dummy) root relation as a result of the > > > modifyTable plan. Maybe we should populate its target list with > > > pseudo-junk columns in create_modifytable_plan ? > > > > > > For instance, they query does not error-out if we have at least one > > > another non-file-fdw partition: > > > > > > create table p2 partition of pt for values in ( 2) ; > > > > > > this is because we have this in create_modifytable_plan > > > > > > ``` > > > /* Transfer resname/resjunk labeling, too, to keep executor happy */ > > > apply_tlist_labeling(subplan->targetlist, root->processed_tlist); > > > ``` > > > > > > and we successfully found a junk column in the p2 partition. > > > > > > The problem is, it works iff root->processed_tlist has at least one > > > relation which can give us junk columns. Should we add handling for > > > corner case here? > > > Another option is to remove this 'Transfer resname/resjunk labeling' > > > completely and rework planner-executer contracts somehow. > > > > I am not really sure if we should play with the planner code. > > > > I suspect the real issue is that we’re assuming partitioned tables > > always need a ctid, which wasn’t true before MERGE started using the > > root ResultRelInfo. In fact, the old code already looked wrong -- it’s > > been requiring a ctid even for partitioned tables where that was never > > necessary. We can fix this by only requiring the junk ctid when we > > actually operate through the root partitioned table, that is, for > > MERGE. Like the attached. > > > > -- > > Thanks, Amit Langote > > Hi! Thanks for the patch. I can see your points, however I am unsure > if this is the most right thing to do. > As per ab5fcf2b04f9 commit message and > src/backend/optimizer/plan/planner.c comments, I am under impression > that the postgres-way of fixing that would allow for > ExecInitModifyTable to operate with a NULL result relation list. Isn't that what happens with my patch? > And, in any case, I am still unsure if we should allow the 'DELETE' > statement from Alexander's repro to successfully execute, which yout > patch still does What behavior do you propose in that case? The WHERE false part makes the plan a dummy ModifyTable on the root partitioned table pt (per ab5fcf2b0 I guess), and there’s nothing left in the plan that can be flagged at execution time; the error Alexander reported is a bug we're trying to fix. Are you suggesting instead that the attempt to plan DELETE on the file_fdw partition -- or any foreign table that doesn’t support DELETE -- should be prevented? -- Thanks, Amit Langote
-
Re: BUG #19099: Conditional DELETE from partitioned table with non-updatable partition raises internal error
Kirill Reshke <reshkekirill@gmail.com> — 2025-10-30T09:53:23Z
On Thu, 30 Oct 2025, 14:18 Amit Langote, <amitlangote09@gmail.com> wrote: > On Thu, Oct 30, 2025 at 5:55 PM Kirill Reshke <reshkekirill@gmail.com> > wrote: > > On Thu, 30 Oct 2025 at 13:29, Amit Langote <amitlangote09@gmail.com> > wrote: > > > > > > Hi, > > > > > > On Thu, Oct 30, 2025 at 3:44 PM Kirill Reshke <reshkekirill@gmail.com> > wrote: > > > > On Thu, 30 Oct 2025 at 10:31, I wrote: > > > > > > > > > > I mean, we I believe we need to execute > > > > > CheckValidResultRel against all partitions in ExecInitModifyTable, > at > > > > > least when no partition pruning has been performed > > > > > > > > > > > > > So, the problem is that we managed to exclude all child relations, > and > > > > only have a single (dummy) root relation as a result of the > > > > modifyTable plan. Maybe we should populate its target list with > > > > pseudo-junk columns in create_modifytable_plan ? > > > > > > > > For instance, they query does not error-out if we have at least one > > > > another non-file-fdw partition: > > > > > > > > create table p2 partition of pt for values in ( 2) ; > > > > > > > > this is because we have this in create_modifytable_plan > > > > > > > > ``` > > > > /* Transfer resname/resjunk labeling, too, to keep executor happy */ > > > > apply_tlist_labeling(subplan->targetlist, root->processed_tlist); > > > > ``` > > > > > > > > and we successfully found a junk column in the p2 partition. > > > > > > > > The problem is, it works iff root->processed_tlist has at least one > > > > relation which can give us junk columns. Should we add handling for > > > > corner case here? > > > > Another option is to remove this 'Transfer resname/resjunk labeling' > > > > completely and rework planner-executer contracts somehow. > > > > > > I am not really sure if we should play with the planner code. > > > > > > I suspect the real issue is that we’re assuming partitioned tables > > > always need a ctid, which wasn’t true before MERGE started using the > > > root ResultRelInfo. In fact, the old code already looked wrong -- it’s > > > been requiring a ctid even for partitioned tables where that was never > > > necessary. We can fix this by only requiring the junk ctid when we > > > actually operate through the root partitioned table, that is, for > > > MERGE. Like the attached. > > > > > > -- > > > Thanks, Amit Langote > > > > Hi! Thanks for the patch. I can see your points, however I am unsure > > if this is the most right thing to do. > > As per ab5fcf2b04f9 commit message and > > src/backend/optimizer/plan/planner.c comments, I am under impression > > that the postgres-way of fixing that would allow for > > ExecInitModifyTable to operate with a NULL result relation list. > > Isn't that what happens with my patch? > > > And, in any case, I am still unsure if we should allow the 'DELETE' > > statement from Alexander's repro to successfully execute, which yout > > patch still does > > What behavior do you propose in that case? The WHERE false part makes > the plan a dummy ModifyTable on the root partitioned table pt (per > ab5fcf2b0 I guess), and there’s nothing left in the plan that can be > flagged at execution time; the error Alexander reported is a bug we're > trying to fix. Are you suggesting instead that the attempt to plan > DELETE on the file_fdw partition -- or any foreign table that doesn’t > support DELETE -- should be prevented? > > -- > Thanks, Amit Langote > Okay, after putting more thought on it, I think your fix is OK. WFM, LGTM >
-
Re: BUG #19099: Conditional DELETE from partitioned table with non-updatable partition raises internal error
Tender Wang <tndrwang@gmail.com> — 2025-10-30T11:08:26Z
Amit Langote <amitlangote09@gmail.com> 于2025年10月30日周四 16:29写道: > > > I am not really sure if we should play with the planner code. > > I suspect the real issue is that we’re assuming partitioned tables > always need a ctid, which wasn’t true before MERGE started using the > root ResultRelInfo. In fact, the old code already looked wrong -- it’s > been requiring a ctid even for partitioned tables where that was never > necessary. We can fix this by only requiring the junk ctid when we > actually operate through the root partitioned table, that is, for > MERGE. Like the attached. > With your patch, this issue didn't happen again. But I still get a different output when I enable verbose in EXPLAIN, Output: ctid (enable_partition_pruning = on) vs Output: NULL::oid(enable_partition_pruning = off) From the user's perspective, it's a bit confusing. I agree more with Tom’s opinion — we should throw an error like "cannot delete from foreign table p1" But the plan only had a dummy root relation; CheckValidResultRel() doesn't work. Some other code place may need to do something. -- Thanks, Tender Wang
-
Re: BUG #19099: Conditional DELETE from partitioned table with non-updatable partition raises internal error
amit <amitlangote09@gmail.com> — 2025-10-30T13:17:11Z
On Thu, Oct 30, 2025 at 8:08 PM Tender Wang <tndrwang@gmail.com> wrote: > Amit Langote <amitlangote09@gmail.com> 于2025年10月30日周四 16:29写道: >> I am not really sure if we should play with the planner code. >> >> I suspect the real issue is that we’re assuming partitioned tables >> always need a ctid, which wasn’t true before MERGE started using the >> root ResultRelInfo. In fact, the old code already looked wrong -- it’s >> been requiring a ctid even for partitioned tables where that was never >> necessary. We can fix this by only requiring the junk ctid when we >> actually operate through the root partitioned table, that is, for >> MERGE. Like the attached. > > > With your patch, this issue didn't happen again. > But I still get a different output when I enable verbose in EXPLAIN, > > Output: ctid (enable_partition_pruning = on) > vs > Output: NULL::oid(enable_partition_pruning = off) > > From the user's perspective, it's a bit confusing. Hmm, that's perhaps not ideal. That's the row identity var "tableoid" added by expand_single_inheritance_child(): /* * If we have any child target relations, assume they all need to * generate a junk "tableoid" column. (If only one child survives * pruning, we wouldn't really need this, but it's not worth * thrashing about to avoid it.) */ rrvar = makeVar(childRTindex, TableOidAttributeNumber, OIDOID, -1, InvalidOid, 0); add_row_identity_var(root, rrvar, childRTindex, "tableoid"); The WHERE false excludes the child that adds the above var, so you end up with a NULL in the targetlist because of this part of set_plan_refs(): /* * The tlist of a childless Result could contain * unresolved ROWID_VAR Vars, in case it's representing a * target relation which is completely empty because of * constraint exclusion. Replace any such Vars by null * constants, as though they'd been resolved for a leaf * scan node that doesn't support them. We could have * fix_scan_expr do this, but since the case is only * expected to occur here, it seems safer to special-case * it here and keep the assertions that ROWID_VARs * shouldn't be seen by fix_scan_expr. * * We also must handle the case where set operations have * been short-circuited resulting in a dummy Result node. * prepunion.c uses varno==0 for the set op targetlist. * See generate_setop_tlist() and generate_setop_tlist(). * Here we rewrite these to use varno==1, which is the * varno of the first set-op child. Without this, EXPLAIN * will have trouble displaying targetlists of dummy set * operations. */ foreach(l, splan->plan.targetlist) { TargetEntry *tle = (TargetEntry *) lfirst(l); Var *var = (Var *) tle->expr; if (var && IsA(var, Var)) { if (var->varno == ROWID_VAR) tle->expr = (Expr *) makeNullConst(var->vartype, var->vartypmod, var->varcollid); I’m not sure how hard we should try to avoid that kind of confusion in the EXPLAIN VERBOSE output. > I agree more with Tom’s opinion — we should throw an error like "cannot delete from foreign table p1" > But the plan only had a dummy root relation; CheckValidResultRel() doesn't work. > Some other code place may need to do something. Yeah, I’m also not sure there’s an obvious place where we could detect that earlier. Once pruning removes all real children, the planner ends up with just a dummy root, and by that point there’s no surviving foreign table ResultRelInfo to check or even any child relation data structure in the planner. -- Thanks, Amit Langote -
Re: BUG #19099: Conditional DELETE from partitioned table with non-updatable partition raises internal error
Kirill Reshke <reshkekirill@gmail.com> — 2025-10-30T13:31:09Z
Hi! On Thu, 30 Oct 2025 at 16:08, Tender Wang <tndrwang@gmail.com> wrote: > > From the user's perspective, it's a bit confusing. > I agree more with Tom’s opinion — we should throw an error like "cannot delete from foreign table p1" > But the plan only had a dummy root relation; CheckValidResultRel() doesn't work. > Some other code place may need to do something. > Tom wrote: > It's surely pretty accidental (and arguably not desirable) > if "DELETE FROM pt WHERE false" doesn't fail the same way. I cannot prove to myself why failing here is actually desirable. Can you elaborate? -- Best regards, Kirill Reshke
-
Re: BUG #19099: Conditional DELETE from partitioned table with non-updatable partition raises internal error
Tom Lane <tgl@sss.pgh.pa.us> — 2025-10-30T13:48:32Z
Kirill Reshke <reshkekirill@gmail.com> writes: > Tom wrote: >> It's surely pretty accidental (and arguably not desirable) >> if "DELETE FROM pt WHERE false" doesn't fail the same way. > I cannot prove to myself why failing here is actually desirable. Can > you elaborate? If we throw that failure in some cases but not others, we're exposing implementation details. The definition could have been "throw 'cannot delete from foreign table' only if the query actually attempts to delete some specific row from some foreign table", but it is not implemented that way. Instead the error is thrown during query startup if the query has a foreign table as a potential delete target. Thus, as things stand today, you might or might not get the error depending on whether the planner can prove that that partition won't be deleted from. This is not a great user experience, because we don't (and won't) make any hard promises about how smart the planner is. An analogy perhaps is that whether you get a "permission denied" error about some target table is not conditional on whether the query actually attempts to delete any rows from it. We go out of our way to make sure that that happens when required by spec, even if the planner is able to prove that no delete will happen. None of this is meant to justify throwing an internal error here; that's clearly bad. I'm just saying that there would be little wrong with fixing it by throwing "cannot delete" instead. The user has no right to expect that that won't happen in a case like this. regards, tom lane
-
Re: BUG #19099: Conditional DELETE from partitioned table with non-updatable partition raises internal error
amit <amitlangote09@gmail.com> — 2025-10-31T00:30:39Z
On Thu, Oct 30, 2025 at 10:48 PM Tom Lane <tgl@sss.pgh.pa.us> wrote: > Kirill Reshke <reshkekirill@gmail.com> writes: > > Tom wrote: > >> It's surely pretty accidental (and arguably not desirable) > >> if "DELETE FROM pt WHERE false" doesn't fail the same way. > > > I cannot prove to myself why failing here is actually desirable. Can > > you elaborate? > > If we throw that failure in some cases but not others, we're exposing > implementation details. > > The definition could have been "throw 'cannot delete from foreign > table' only if the query actually attempts to delete some specific > row from some foreign table", but it is not implemented that way. > Instead the error is thrown during query startup if the query has > a foreign table as a potential delete target. Thus, as things stand > today, you might or might not get the error depending on whether > the planner can prove that that partition won't be deleted from. > This is not a great user experience, because we don't (and won't) > make any hard promises about how smart the planner is. > > An analogy perhaps is that whether you get a "permission denied" > error about some target table is not conditional on whether the > query actually attempts to delete any rows from it. We go out > of our way to make sure that that happens when required by spec, > even if the planner is able to prove that no delete will happen. > > None of this is meant to justify throwing an internal error here; > that's clearly bad. I'm just saying that there would be little > wrong with fixing it by throwing "cannot delete" instead. The user > has no right to expect that that won't happen in a case like this. We might be able to throw the "cannot delete from foreign table" like this: @@ -987,6 +987,16 @@ add_row_identity_columns(PlannerInfo *root, Index rtindex, fdwroutine = GetFdwRoutineForRelation(target_relation, false); + if (fdwroutine->ExecForeignDelete == NULL) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot delete from foreign table \"%s\"", + RelationGetRelationName(target_relation)))); + if (fdwroutine->ExecForeignUpdate == NULL) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot update foreign table \"%s\"", + RelationGetRelationName(target_relation)))); if (fdwroutine->AddForeignUpdateTargets != NULL) fdwroutine->AddForeignUpdateTargets(root, rtindex, target_rte, target_relation); but I am not sure how consistent the following is after applying that: postgres=# set enable_partition_pruning to off; SET postgres=# EXPLAIN verbose DELETE FROM pt WHERE false; ERROR: cannot delete from foreign table "p1" postgres=# set enable_partition_pruning to on; SET -- we don't even hit the foreign table in the planner postgres=# EXPLAIN verbose DELETE FROM pt WHERE false; QUERY PLAN ------------------------------------------------------- Delete on public.pt (cost=0.00..0.00 rows=0 width=0) -> Result (cost=0.00..0.00 rows=0 width=0) Output: ctid Replaces: Scan on pt One-Time Filter: false (5 rows) -- Thanks, Amit Langote -
Re: BUG #19099: Conditional DELETE from partitioned table with non-updatable partition raises internal error
David Rowley <dgrowleyml@gmail.com> — 2025-10-31T01:50:15Z
On Fri, 31 Oct 2025 at 02:48, Tom Lane <tgl@sss.pgh.pa.us> wrote: > The definition could have been "throw 'cannot delete from foreign > table' only if the query actually attempts to delete some specific > row from some foreign table", but it is not implemented that way. > Instead the error is thrown during query startup if the query has > a foreign table as a potential delete target. Thus, as things stand > today, you might or might not get the error depending on whether > the planner can prove that that partition won't be deleted from. > This is not a great user experience, because we don't (and won't) > make any hard promises about how smart the planner is. It's a good point, but I doubt we could change this fact as I expect there are people relying on pruned partitions being excluded from this check. It seems reasonable that someone might want to do something like archive ancient time-based partitioned table partitions into file_fdw stored on a compressed filesystem so that they can still at least query old data should they need to. If we were to precheck that all partitions support an UPDATE/DELETE, then it could break workloads that do updates on recent data in heap-based partitions. Things would go bad for those people if they switched off partition pruning, but I doubt that would be the only reason as that would also add a huge amount of overhead to their SELECT statements. In any case, the planner is now very efficient at not loading any metadata for pruned partitions, so I don't see how we'd do this without adding possibly large overhead to the planner. I'd say we're well beyond the point of being able to change this now. David
-
Re: BUG #19099: Conditional DELETE from partitioned table with non-updatable partition raises internal error
amit <amitlangote09@gmail.com> — 2025-11-06T10:00:33Z
On Fri, Oct 31, 2025 at 10:50 AM David Rowley <dgrowleyml@gmail.com> wrote: > On Fri, 31 Oct 2025 at 02:48, Tom Lane <tgl@sss.pgh.pa.us> wrote: > > The definition could have been "throw 'cannot delete from foreign > > table' only if the query actually attempts to delete some specific > > row from some foreign table", but it is not implemented that way. > > Instead the error is thrown during query startup if the query has > > a foreign table as a potential delete target. Thus, as things stand > > today, you might or might not get the error depending on whether > > the planner can prove that that partition won't be deleted from. > > This is not a great user experience, because we don't (and won't) > > make any hard promises about how smart the planner is. > > It's a good point, but I doubt we could change this fact as I expect > there are people relying on pruned partitions being excluded from this > check. It seems reasonable that someone might want to do something > like archive ancient time-based partitioned table partitions into > file_fdw stored on a compressed filesystem so that they can still at > least query old data should they need to. If we were to precheck that > all partitions support an UPDATE/DELETE, then it could break workloads > that do updates on recent data in heap-based partitions. Things would > go bad for those people if they switched off partition pruning, but I > doubt that would be the only reason as that would also add a huge > amount of overhead to their SELECT statements. > > In any case, the planner is now very efficient at not loading any > metadata for pruned partitions, so I don't see how we'd do this > without adding possibly large overhead to the planner. I'd say we're > well beyond the point of being able to change this now. I agree that we definitely shouldn’t load metadata for partitions that are excluded from the plan, especially not just to slightly improve user experience in this corner case. I looked at a few options, but none seem non-invasive enough for back-patching, apart from the first patch I posted. That one makes ExecInitModifyTable() not require a ctid to be present to set the root partitioned table’s ri_RowIdAttNo, except in the case of MERGE, which seems to need it. The corner case that triggers the internal error for UPDATE/DELETE doesn’t occur for MERGE now and likely won’t when foreign tables eventually gain MERGE support; don't mark my words though ;-). Among those options, I considered the following block, which adds a ctid for the partitioned root table when it’s the only target in the query after partition pruning removes all child tables due to the WHERE false condition in the problematic case: /* * Ordinarily, we expect that leaf result relation(s) will have added some * ROWID_VAR Vars to the query. However, it's possible that constraint * exclusion suppressed every leaf relation. The executor will get upset * if the plan has no row identity columns at all, even though it will * certainly process no rows. Handle this edge case by re-opening the top * result relation and adding the row identity columns it would have used, * as preprocess_targetlist() would have done if it weren't marked "inh". * Then re-run build_base_rel_tlists() to ensure that the added columns * get propagated to the relation's reltarget. (This is a bit ugly, but * it seems better to confine the ugliness and extra cycles to this * unusual corner case.) */ if (root->row_identity_vars == NIL) { Relation target_relation; target_relation = table_open(target_rte->relid, NoLock); add_row_identity_columns(root, result_relation, target_rte, target_relation); table_close(target_relation, NoLock); build_base_rel_tlists(root, root->processed_tlist); /* There are no ROWID_VAR Vars in this case, so we're done. */ return; } If enable_partition_pruning is off, root->row_identity_vars already contains a RowIdentityVarInfo entry for the tableoid Var that was added while processing the foreign-table child partition. Because of that, the if (root->row_identity_vars == NIL) block doesn’t run in this case, so it won’t add any row identity columns such as ctid for the partitioned root table. In theory, we could prevent the planner from adding tableoid in the first place when the child table doesn’t support any row identity column -- or worse, doesn’t support the UPDATE/DELETE/MERGE command at all -- but doing so would require changing the order in which tableoid appears in root->processed_tlist. That would be too invasive for a back-patch. So for back branches, I’d propose sticking with the smaller executor-side fix and perhaps revisiting the planner behavior separately if we ever want to refine handling of pruned partitions or dummy roots. I understand, as was reported upthread, that the EXPLAIN VERBOSE output isn’t very consistent with that patch even though the internal error goes away. Making sense of the output differences requires knowing that the targetlist population behavior differs depending on whether enable_partition_pruning is on or off as I described above. -- Thanks, Amit Langote -
Re: BUG #19099: Conditional DELETE from partitioned table with non-updatable partition raises internal error
Tender Wang <tndrwang@gmail.com> — 2025-11-07T01:01:45Z
Amit Langote <amitlangote09@gmail.com> 于2025年11月6日周四 18:00写道: > On Fri, Oct 31, 2025 at 10:50 AM David Rowley <dgrowleyml@gmail.com> > wrote: > > On Fri, 31 Oct 2025 at 02:48, Tom Lane <tgl@sss.pgh.pa.us> wrote: > > > The definition could have been "throw 'cannot delete from foreign > > > table' only if the query actually attempts to delete some specific > > > row from some foreign table", but it is not implemented that way. > > > Instead the error is thrown during query startup if the query has > > > a foreign table as a potential delete target. Thus, as things stand > > > today, you might or might not get the error depending on whether > > > the planner can prove that that partition won't be deleted from. > > > This is not a great user experience, because we don't (and won't) > > > make any hard promises about how smart the planner is. > > > > It's a good point, but I doubt we could change this fact as I expect > > there are people relying on pruned partitions being excluded from this > > check. It seems reasonable that someone might want to do something > > like archive ancient time-based partitioned table partitions into > > file_fdw stored on a compressed filesystem so that they can still at > > least query old data should they need to. If we were to precheck that > > all partitions support an UPDATE/DELETE, then it could break workloads > > that do updates on recent data in heap-based partitions. Things would > > go bad for those people if they switched off partition pruning, but I > > doubt that would be the only reason as that would also add a huge > > amount of overhead to their SELECT statements. > > > > In any case, the planner is now very efficient at not loading any > > metadata for pruned partitions, so I don't see how we'd do this > > without adding possibly large overhead to the planner. I'd say we're > > well beyond the point of being able to change this now. > > I agree that we definitely shouldn’t load metadata for partitions that > are excluded from the plan, especially not just to slightly improve > user experience in this corner case. > > I looked at a few options, but none seem non-invasive enough for > back-patching, apart from the first patch I posted. That one makes > ExecInitModifyTable() not require a ctid to be present to set the root > partitioned table’s ri_RowIdAttNo, except in the case of MERGE, which > seems to need it. The corner case that triggers the internal error for > UPDATE/DELETE doesn’t occur for MERGE now and likely won’t when > foreign tables eventually gain MERGE support; don't mark my words > though ;-). > > Among those options, I considered the following block, which adds a > ctid for the partitioned root table when it’s the only target in the > query after partition pruning removes all child tables due to the > WHERE false condition in the problematic case: > > /* > * Ordinarily, we expect that leaf result relation(s) will have added > some > * ROWID_VAR Vars to the query. However, it's possible that constraint > * exclusion suppressed every leaf relation. The executor will get > upset > * if the plan has no row identity columns at all, even though it will > * certainly process no rows. Handle this edge case by re-opening the > top > * result relation and adding the row identity columns it would have > used, > * as preprocess_targetlist() would have done if it weren't marked > "inh". > * Then re-run build_base_rel_tlists() to ensure that the added columns > * get propagated to the relation's reltarget. (This is a bit ugly, > but > * it seems better to confine the ugliness and extra cycles to this > * unusual corner case.) > */ > if (root->row_identity_vars == NIL) > { > Relation target_relation; > > target_relation = table_open(target_rte->relid, NoLock); > add_row_identity_columns(root, result_relation, > target_rte, target_relation); > table_close(target_relation, NoLock); > build_base_rel_tlists(root, root->processed_tlist); > /* There are no ROWID_VAR Vars in this case, so we're done. */ > return; > } > > If enable_partition_pruning is off, root->row_identity_vars already > contains a RowIdentityVarInfo entry for the tableoid Var that was > added while processing the foreign-table child partition. Because of > that, the if (root->row_identity_vars == NIL) block doesn’t run in > this case, so it won’t add any row identity columns such as ctid for > the partitioned root table. > > In theory, we could prevent the planner from adding tableoid in the > first place when the child table doesn’t support any row identity > column -- or worse, doesn’t support the UPDATE/DELETE/MERGE command at > all -- but doing so would require changing the order in which tableoid > appears in root->processed_tlist. That would be too invasive for a > back-patch. Yeah, it seems to need more work if we prevent the planner from adding tableoid in the first place. > So for back branches, I’d propose sticking with the smaller > executor-side fix and perhaps revisiting the planner behavior > separately if we ever want to refine handling of pruned partitions or > dummy roots. I understand, as was reported upthread, that the EXPLAIN > VERBOSE output isn’t very consistent with that patch even though the > internal error goes away. Making sense of the output differences > requires knowing that the targetlist population behavior differs > depending on whether enable_partition_pruning is on or off as I > described above. > The executor-side fix works for me and the test case should be added to your patch. Should we add some comments to explain the output difference in EXPLAIN VERBOSE if enable_partition_pruning is set to a different value? -- Thanks, Tender Wang -
Re: BUG #19099: Conditional DELETE from partitioned table with non-updatable partition raises internal error
amit <amitlangote09@gmail.com> — 2025-11-07T06:02:04Z
On Thu, Nov 6, 2025 at 7:00 PM Amit Langote <amitlangote09@gmail.com> wrote: > I looked at a few options, but none seem non-invasive enough for > back-patching, apart from the first patch I posted. That one makes > ExecInitModifyTable() not require a ctid to be present to set the root > partitioned table’s ri_RowIdAttNo, except in the case of MERGE, which > seems to need it. The corner case that triggers the internal error for > UPDATE/DELETE doesn’t occur for MERGE now and likely won’t when > foreign tables eventually gain MERGE support; don't mark my words > though ;-). Well, OK, I just had not tried hard enough to see that the same error happens for MERGE too. With my patch applied: EXPLAIN VERBOSE MERGE INTO pt t USING (VALUES (1, 'x'::text)) AS s(a, b) ON false WHEN MATCHED THEN UPDATE SET b = s.b; ERROR: could not find junk ctid column I have another idea: we can simply recognize the corner condition that throws this error in ExecInitModifyTable() by checking if ModifyTable.resultRelations contains only the root partitioned table. That can only happen for UPDATE, DELETE, or MERGE when all child relations were excluded. Patch doing that attached. Added test cases to file_fdw's suite. -- Thanks, Amit Langote
-
Re: BUG #19099: Conditional DELETE from partitioned table with non-updatable partition raises internal error
amit <amitlangote09@gmail.com> — 2025-11-07T06:03:50Z
Hi, On Fri, Nov 7, 2025 at 10:01 AM Tender Wang <tndrwang@gmail.com> wrote: > Amit Langote <amitlangote09@gmail.com> 于2025年11月6日周四 18:00写道: >> So for back branches, I’d propose sticking with the smaller >> executor-side fix and perhaps revisiting the planner behavior >> separately if we ever want to refine handling of pruned partitions or >> dummy roots. I understand, as was reported upthread, that the EXPLAIN >> VERBOSE output isn’t very consistent with that patch even though the >> internal error goes away. Making sense of the output differences >> requires knowing that the targetlist population behavior differs >> depending on whether enable_partition_pruning is on or off as I >> described above. > > The executor-side fix works for me Thanks for checking. > and the test case should be added to your patch. > Should we add some comments to explain the output difference in EXPLAIN VERBOSE > if enable_partition_pruning is set to a different value? I added some in the v2 patch I just posted. -- Thanks, Amit Langote
-
Re: BUG #19099: Conditional DELETE from partitioned table with non-updatable partition raises internal error
Tender Wang <tndrwang@gmail.com> — 2025-11-07T06:35:47Z
Amit Langote <amitlangote09@gmail.com> 于2025年11月7日周五 14:04写道: > Hi, > > On Fri, Nov 7, 2025 at 10:01 AM Tender Wang <tndrwang@gmail.com> wrote: > > Amit Langote <amitlangote09@gmail.com> 于2025年11月6日周四 18:00写道: > >> So for back branches, I’d propose sticking with the smaller > >> executor-side fix and perhaps revisiting the planner behavior > >> separately if we ever want to refine handling of pruned partitions or > >> dummy roots. I understand, as was reported upthread, that the EXPLAIN > >> VERBOSE output isn’t very consistent with that patch even though the > >> internal error goes away. Making sense of the output differences > >> requires knowing that the targetlist population behavior differs > >> depending on whether enable_partition_pruning is on or off as I > >> described above. > > > > The executor-side fix works for me > > Thanks for checking. > > > and the test case should be added to your patch. > > Should we add some comments to explain the output difference in EXPLAIN > VERBOSE > > if enable_partition_pruning is set to a different value? > > I added some in the v2 patch I just posted. > I run tests in regress and file_fdw, no failed cases. No objections from me. -- Thanks, Tender Wang
-
Re: BUG #19099: Conditional DELETE from partitioned table with non-updatable partition raises internal error
Kirill Reshke <reshkekirill@gmail.com> — 2025-11-07T09:04:53Z
On Fri, 7 Nov 2025 at 11:02, Amit Langote <amitlangote09@gmail.com> wrote: > > On Thu, Nov 6, 2025 at 7:00 PM Amit Langote <amitlangote09@gmail.com> wrote: > > I looked at a few options, but none seem non-invasive enough for > > back-patching, apart from the first patch I posted. That one makes > > ExecInitModifyTable() not require a ctid to be present to set the root > > partitioned table’s ri_RowIdAttNo, except in the case of MERGE, which > > seems to need it. The corner case that triggers the internal error for > > UPDATE/DELETE doesn’t occur for MERGE now and likely won’t when > > foreign tables eventually gain MERGE support; don't mark my words > > though ;-). > > Well, OK, I just had not tried hard enough to see that the same error > happens for MERGE too. > > With my patch applied: > EXPLAIN VERBOSE MERGE INTO pt t USING (VALUES (1, 'x'::text)) AS s(a, > b) ON false WHEN MATCHED THEN UPDATE SET b = s.b; > ERROR: could not find junk ctid column > > I have another idea: we can simply recognize the corner condition that > throws this error in ExecInitModifyTable() by checking if > ModifyTable.resultRelations contains only the root partitioned table. > That can only happen for UPDATE, DELETE, or MERGE when all child > relations were excluded. > > Patch doing that attached. Added test cases to file_fdw's suite. > > -- > Thanks, Amit Langote HI! I think this is an OK option for backpatching. After v2 applied, I found the behavior of DELETE and EXPLAIN DELETE consistent. The only remaining issue is VERBOSE output difference with or without enable_partition_pruning (which is v19+ issue to worry about), correct? Also, should we add COSTS OFF to EXPLAIN in the regression test? I understand that costs should be always zero, but COSTS OFF is almost everywhere is tests -- Best regards, Kirill Reshke
-
Re: BUG #19099: Conditional DELETE from partitioned table with non-updatable partition raises internal error
amit <amitlangote09@gmail.com> — 2025-11-07T09:23:16Z
Hi, On Fri, Nov 7, 2025 at 6:05 PM Kirill Reshke <reshkekirill@gmail.com> wrote: > On Fri, 7 Nov 2025 at 11:02, Amit Langote <amitlangote09@gmail.com> wrote: > > > > On Thu, Nov 6, 2025 at 7:00 PM Amit Langote <amitlangote09@gmail.com> wrote: > > > I looked at a few options, but none seem non-invasive enough for > > > back-patching, apart from the first patch I posted. That one makes > > > ExecInitModifyTable() not require a ctid to be present to set the root > > > partitioned table’s ri_RowIdAttNo, except in the case of MERGE, which > > > seems to need it. The corner case that triggers the internal error for > > > UPDATE/DELETE doesn’t occur for MERGE now and likely won’t when > > > foreign tables eventually gain MERGE support; don't mark my words > > > though ;-). > > > > Well, OK, I just had not tried hard enough to see that the same error > > happens for MERGE too. > > > > With my patch applied: > > EXPLAIN VERBOSE MERGE INTO pt t USING (VALUES (1, 'x'::text)) AS s(a, > > b) ON false WHEN MATCHED THEN UPDATE SET b = s.b; > > ERROR: could not find junk ctid column > > > > I have another idea: we can simply recognize the corner condition that > > throws this error in ExecInitModifyTable() by checking if > > ModifyTable.resultRelations contains only the root partitioned table. > > That can only happen for UPDATE, DELETE, or MERGE when all child > > relations were excluded. > > > > Patch doing that attached. Added test cases to file_fdw's suite. > > HI! > > I think this is an OK option for backpatching. After v2 applied, I > found the behavior of DELETE and EXPLAIN DELETE consistent. Thanks for the comment. > The only > remaining issue is VERBOSE output difference with or without > enable_partition_pruning (which is v19+ issue to worry about), > correct? Yes, iff we are to do anything at all about the difference. > Also, should we add COSTS OFF to EXPLAIN in the regression test? I > understand that costs should be always zero, but COSTS OFF is almost > everywhere is tests Yeah, a good call. v3 attached. -- Thanks, Amit Langote
-
Re: BUG #19099: Conditional DELETE from partitioned table with non-updatable partition raises internal error
amit <amitlangote09@gmail.com> — 2025-11-08T05:19:36Z
On Fri, Nov 7, 2025 at 6:23 PM Amit Langote <amitlangote09@gmail.com> wrote: > On Fri, Nov 7, 2025 at 6:05 PM Kirill Reshke <reshkekirill@gmail.com> wrote: > > On Fri, 7 Nov 2025 at 11:02, Amit Langote <amitlangote09@gmail.com> wrote: > > > I have another idea: we can simply recognize the corner condition that > > > throws this error in ExecInitModifyTable() by checking if > > > ModifyTable.resultRelations contains only the root partitioned table. > > > That can only happen for UPDATE, DELETE, or MERGE when all child > > > relations were excluded. > > > > > > Patch doing that attached. Added test cases to file_fdw's suite. > > > > I think this is an OK option for backpatching. After v2 applied, I > > found the behavior of DELETE and EXPLAIN DELETE consistent. > > Thanks for the comment. > > > The only > > remaining issue is VERBOSE output difference with or without > > enable_partition_pruning (which is v19+ issue to worry about), > > correct? > > Yes, iff we are to do anything at all about the difference. > > > Also, should we add COSTS OFF to EXPLAIN in the regression test? I > > understand that costs should be always zero, but COSTS OFF is almost > > everywhere is tests > > Yeah, a good call. > > v3 attached. Attached v4 where I have updated the commit message to mention 86dc9005. The bug doesn’t seem critical enough to rush the fix, so I’ll hold off on committing it for next week’s release to leave room for further comments. -- Thanks, Amit Langote
-
Re: BUG #19099: Conditional DELETE from partitioned table with non-updatable partition raises internal error
amit <amitlangote09@gmail.com> — 2025-11-26T11:27:04Z
On Thu, Nov 6, 2025 at 7:00 PM Amit Langote <amitlangote09@gmail.com> wrote: > Among those options, I considered the following block, which adds a > ctid for the partitioned root table when it’s the only target in the > query after partition pruning removes all child tables due to the > WHERE false condition in the problematic case: > > /* > * Ordinarily, we expect that leaf result relation(s) will have added some > * ROWID_VAR Vars to the query. However, it's possible that constraint > * exclusion suppressed every leaf relation. The executor will get upset > * if the plan has no row identity columns at all, even though it will > * certainly process no rows. Handle this edge case by re-opening the top > * result relation and adding the row identity columns it would have used, > * as preprocess_targetlist() would have done if it weren't marked "inh". > * Then re-run build_base_rel_tlists() to ensure that the added columns > * get propagated to the relation's reltarget. (This is a bit ugly, but > * it seems better to confine the ugliness and extra cycles to this > * unusual corner case.) > */ > if (root->row_identity_vars == NIL) > { > Relation target_relation; > > target_relation = table_open(target_rte->relid, NoLock); > add_row_identity_columns(root, result_relation, > target_rte, target_relation); > table_close(target_relation, NoLock); > build_base_rel_tlists(root, root->processed_tlist); > /* There are no ROWID_VAR Vars in this case, so we're done. */ > return; > } > > If enable_partition_pruning is off, root->row_identity_vars already > contains a RowIdentityVarInfo entry for the tableoid Var that was > added while processing the foreign-table child partition. Because of > that, the if (root->row_identity_vars == NIL) block doesn’t run in > this case, so it won’t add any row identity columns such as ctid for > the partitioned root table. > > In theory, we could prevent the planner from adding tableoid in the > first place when the child table doesn’t support any row identity > column -- or worse, doesn’t support the UPDATE/DELETE/MERGE command at > all -- but doing so would require changing the order in which tableoid > appears in root->processed_tlist. That would be too invasive for a > back-patch. I’ve implemented this alternative as well -- the version that prevents adding tableoid when no other row-identity columns are added for the child. That allows to keep root->row_identity_vars empty so the dummy-root path can add ctid as intended by the above code block of distribute_row_identity_vars(). This provides an alternative approach to compare against the other patch. -- Thanks, Amit Langote -
Re: BUG #19099: Conditional DELETE from partitioned table with non-updatable partition raises internal error
amit <amitlangote09@gmail.com> — 2025-11-27T01:25:36Z
On Wed, Nov 26, 2025 at 8:27 PM Amit Langote <amitlangote09@gmail.com> wrote: > On Thu, Nov 6, 2025 at 7:00 PM Amit Langote <amitlangote09@gmail.com> wrote: > > Among those options, I considered the following block, which adds a > > ctid for the partitioned root table when it’s the only target in the > > query after partition pruning removes all child tables due to the > > WHERE false condition in the problematic case: > > > > /* > > * Ordinarily, we expect that leaf result relation(s) will have added some > > * ROWID_VAR Vars to the query. However, it's possible that constraint > > * exclusion suppressed every leaf relation. The executor will get upset > > * if the plan has no row identity columns at all, even though it will > > * certainly process no rows. Handle this edge case by re-opening the top > > * result relation and adding the row identity columns it would have used, > > * as preprocess_targetlist() would have done if it weren't marked "inh". > > * Then re-run build_base_rel_tlists() to ensure that the added columns > > * get propagated to the relation's reltarget. (This is a bit ugly, but > > * it seems better to confine the ugliness and extra cycles to this > > * unusual corner case.) > > */ > > if (root->row_identity_vars == NIL) > > { > > Relation target_relation; > > > > target_relation = table_open(target_rte->relid, NoLock); > > add_row_identity_columns(root, result_relation, > > target_rte, target_relation); > > table_close(target_relation, NoLock); > > build_base_rel_tlists(root, root->processed_tlist); > > /* There are no ROWID_VAR Vars in this case, so we're done. */ > > return; > > } > > > > If enable_partition_pruning is off, root->row_identity_vars already > > contains a RowIdentityVarInfo entry for the tableoid Var that was > > added while processing the foreign-table child partition. Because of > > that, the if (root->row_identity_vars == NIL) block doesn’t run in > > this case, so it won’t add any row identity columns such as ctid for > > the partitioned root table. > > > > In theory, we could prevent the planner from adding tableoid in the > > first place when the child table doesn’t support any row identity > > column -- or worse, doesn’t support the UPDATE/DELETE/MERGE command at > > all -- but doing so would require changing the order in which tableoid > > appears in root->processed_tlist. That would be too invasive for a > > back-patch. > > I’ve implemented this alternative as well -- the version that prevents > adding tableoid when no other row-identity columns are added for the > child. That allows to keep root->row_identity_vars empty so the > dummy-root path can add ctid as intended by the above code block of > distribute_row_identity_vars(). > > This provides an alternative approach to compare against the other patch. Forgot to mention that with this approach, unlike the other patch, the targetlists are identical whether or not enable_partition_pruning is on, since in both cases the if (root->row_identity_vars == NIL) block of distribute_row_identity_vars() executes: +SET enable_partition_pruning TO off; +EXPLAIN (COSTS OFF, VERBOSE) DELETE FROM pt WHERE false; + QUERY PLAN +-------------------------------- + Delete on public.pt + -> Result + Output: pt.ctid + Replaces: Scan on pt + One-Time Filter: false +(5 rows) +SET enable_partition_pruning TO on; +EXPLAIN (COSTS OFF, VERBOSE) DELETE FROM pt WHERE false; + QUERY PLAN +-------------------------------- + Delete on public.pt + -> Result + Output: ctid + Replaces: Scan on pt + One-Time Filter: false +(5 rows) pt.ctid in the off case vs only ctid in the on case has to do, I think, with there being more than one entry in the rtable in the pruning off case. -- Thanks, Amit Langote -
Re: BUG #19099: Conditional DELETE from partitioned table with non-updatable partition raises internal error
Tender Wang <tndrwang@gmail.com> — 2025-11-30T05:59:59Z
Amit Langote <amitlangote09@gmail.com> 于2025年11月26日周三 19:27写道: > On Thu, Nov 6, 2025 at 7:00 PM Amit Langote <amitlangote09@gmail.com> > wrote: > > Among those options, I considered the following block, which adds a > > ctid for the partitioned root table when it’s the only target in the > > query after partition pruning removes all child tables due to the > > WHERE false condition in the problematic case: > > > > /* > > * Ordinarily, we expect that leaf result relation(s) will have > added some > > * ROWID_VAR Vars to the query. However, it's possible that > constraint > > * exclusion suppressed every leaf relation. The executor will get > upset > > * if the plan has no row identity columns at all, even though it > will > > * certainly process no rows. Handle this edge case by re-opening > the top > > * result relation and adding the row identity columns it would have > used, > > * as preprocess_targetlist() would have done if it weren't marked > "inh". > > * Then re-run build_base_rel_tlists() to ensure that the added > columns > > * get propagated to the relation's reltarget. (This is a bit ugly, > but > > * it seems better to confine the ugliness and extra cycles to this > > * unusual corner case.) > > */ > > if (root->row_identity_vars == NIL) > > { > > Relation target_relation; > > > > target_relation = table_open(target_rte->relid, NoLock); > > add_row_identity_columns(root, result_relation, > > target_rte, target_relation); > > table_close(target_relation, NoLock); > > build_base_rel_tlists(root, root->processed_tlist); > > /* There are no ROWID_VAR Vars in this case, so we're done. */ > > return; > > } > > > > If enable_partition_pruning is off, root->row_identity_vars already > > contains a RowIdentityVarInfo entry for the tableoid Var that was > > added while processing the foreign-table child partition. Because of > > that, the if (root->row_identity_vars == NIL) block doesn’t run in > > this case, so it won’t add any row identity columns such as ctid for > > the partitioned root table. > > > > In theory, we could prevent the planner from adding tableoid in the > > first place when the child table doesn’t support any row identity > > column -- or worse, doesn’t support the UPDATE/DELETE/MERGE command at > > all -- but doing so would require changing the order in which tableoid > > appears in root->processed_tlist. That would be too invasive for a > > back-patch. > > I’ve implemented this alternative as well -- the version that prevents > adding tableoid when no other row-identity columns are added for the > child. That allows to keep root->row_identity_vars empty so the > dummy-root path can add ctid as intended by the above code block of > distribute_row_identity_vars(). > > This provides an alternative approach to compare against the other patch. > > -- > Thanks, Amit Langote > I apply the patch, and I find it forgets to update the diff for postgres_fdw. So I add it in the v2 patch. With this patch, the targetlists are identical whether or not enable_partition_pruning is on. In my first email on this thread, to avoid adding "tableoid", I tried to add the following codes: "(childrte->relkind != RELKIND_PARTITIONED_TABLE && childrte->relkind != RELKIND_FOREIGN_TABLE)" in expand_single_inheritance_child(). But this didn't work for all test cases. It would trigger an assert failure in fix_scan_expr_walker(): Assert(!(IsA(node, Var) && ((Var *) node)->varno == ROWID_VAR)); Your patch is much better than mine. -- Thanks, Tender Wang