From a0bcb6be2380820108fbba739d0e13e02fb6a66a Mon Sep 17 00:00:00 2001
From: Amit Langote <amitlan@postgresql.org>
Date: Wed, 14 Jan 2026 22:30:05 +0900
Subject: [PATCH v4] Fix row-identity handling for dummy partitioned resultrels

Stop adding tableoid for child relations that do not have any
row-identity columns.  In cases where all partitions are excluded
by pruning or constraint exclusion, this allows
distribute_row_identity_vars() to detect the empty state
(root->row_identity_vars == NIL) and add the appropriate ctid
column for the dummy partitioned result relation, satisfying the
executor's requirement that a resultrel always have a row identity.

As part of this, make add_row_identity_columns() return a boolean
to report whether any row-identity columns were added, returning
false for foreign tables whose FDW lacks AddForeignUpdateTargets.
Adjust expected EXPLAIN output accordingly and extend file_fdw
tests to cover dummy-root plans with and without pruning.

This changes the order of ctid and tableoid columns in EXPLAIN
VERBOSE output for UPDATE/DELETE/MERGE on inheritance trees;
ctid now appears before tableoid.

Back-patch to v14 where the bug was introduced.

Bug: #19099
Reported-by: Alexander Lakhin <exclusion@gmail.com>
Author: Amit Langote <amitlangote09@gmail.com>
Reviewed-by: Tender Wang <tndrwang@gmail.com>
Reviewed-by: Kirill Reshke <reshkekirill@gmail.com>
Discussion: https://postgr.es/m/19099-e05dcfa022fe553d%40postgresql.org
Backpatch-through: 14
---
 contrib/file_fdw/expected/file_fdw.out        | 26 ++++++
 contrib/file_fdw/sql/file_fdw.sql             | 10 +++
 .../postgres_fdw/expected/postgres_fdw.out    | 86 +++++++++----------
 src/backend/optimizer/prep/preptlist.c        |  4 +-
 src/backend/optimizer/util/appendinfo.c       | 23 ++++-
 src/backend/optimizer/util/inherit.c          | 11 ++-
 src/include/optimizer/appendinfo.h            |  2 +-
 src/test/regress/expected/inherit.out         | 14 +--
 src/test/regress/expected/merge.out           |  6 +-
 src/test/regress/expected/partition_prune.out |  4 +-
 src/test/regress/expected/returning.out       | 24 +++---
 src/test/regress/expected/updatable_views.out | 20 ++---
 src/test/regress/expected/with.out            | 14 +--
 13 files changed, 150 insertions(+), 94 deletions(-)

diff --git a/contrib/file_fdw/expected/file_fdw.out b/contrib/file_fdw/expected/file_fdw.out
index 5121e27dce5..e26f786403c 100644
--- a/contrib/file_fdw/expected/file_fdw.out
+++ b/contrib/file_fdw/expected/file_fdw.out
@@ -457,6 +457,32 @@ SELECT tableoid::regclass, * FROM p2;
  p2       | 2 | xyzzy
 (3 rows)
 
+-- Verify that DELETE/UPDATE on a partitioned table with a foreign partition
+-- that doesn't support the operation works when all partitions are excluded
+-- (by pruning or constraint exclusion). The dummy root should get ctid added.
+DROP TABLE p2;
+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)
+
 DROP TABLE pt;
 -- generated column tests
 \set filename :abs_srcdir '/data/list1.csv'
diff --git a/contrib/file_fdw/sql/file_fdw.sql b/contrib/file_fdw/sql/file_fdw.sql
index 1a397ad4bd1..e8a8c8430af 100644
--- a/contrib/file_fdw/sql/file_fdw.sql
+++ b/contrib/file_fdw/sql/file_fdw.sql
@@ -242,6 +242,16 @@ UPDATE pt set a = 1 where a = 2; -- ERROR
 SELECT tableoid::regclass, * FROM pt;
 SELECT tableoid::regclass, * FROM p1;
 SELECT tableoid::regclass, * FROM p2;
+
+-- Verify that DELETE/UPDATE on a partitioned table with a foreign partition
+-- that doesn't support the operation works when all partitions are excluded
+-- (by pruning or constraint exclusion). The dummy root should get ctid added.
+DROP TABLE p2;
+SET enable_partition_pruning TO off;
+EXPLAIN (COSTS OFF, VERBOSE) DELETE FROM pt WHERE false;
+SET enable_partition_pruning TO on;
+EXPLAIN (COSTS OFF, VERBOSE) DELETE FROM pt WHERE false;
+
 DROP TABLE pt;
 
 -- generated column tests
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index 6066510c7c0..04da4c171eb 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -7399,7 +7399,7 @@ UPDATE rw_view SET b = b + 5;
    Foreign Update on public.foreign_tbl parent_tbl_1
      Remote SQL: UPDATE public.child_tbl SET b = $2 WHERE ctid = $1 RETURNING a, b
    ->  Foreign Scan on public.foreign_tbl parent_tbl_1
-         Output: (parent_tbl_1.b + 5), parent_tbl_1.tableoid, parent_tbl_1.ctid, parent_tbl_1.*
+         Output: (parent_tbl_1.b + 5), parent_tbl_1.ctid, parent_tbl_1.*, parent_tbl_1.tableoid
          Remote SQL: SELECT a, b, ctid FROM public.child_tbl WHERE ((a < b)) FOR UPDATE
 (6 rows)
 
@@ -7414,7 +7414,7 @@ UPDATE rw_view SET b = b + 15;
    Foreign Update on public.foreign_tbl parent_tbl_1
      Remote SQL: UPDATE public.child_tbl SET b = $2 WHERE ctid = $1 RETURNING a, b
    ->  Foreign Scan on public.foreign_tbl parent_tbl_1
-         Output: (parent_tbl_1.b + 15), parent_tbl_1.tableoid, parent_tbl_1.ctid, parent_tbl_1.*
+         Output: (parent_tbl_1.b + 15), parent_tbl_1.ctid, parent_tbl_1.*, parent_tbl_1.tableoid
          Remote SQL: SELECT a, b, ctid FROM public.child_tbl WHERE ((a < b)) FOR UPDATE
 (6 rows)
 
@@ -7470,7 +7470,7 @@ UPDATE rw_view SET b = 'text', c = 123.456;
    Foreign Update on public.child_foreign parent_tbl_1
      Remote SQL: UPDATE public.child_local SET b = $2, c = $3 WHERE ctid = $1 RETURNING a
    ->  Foreign Scan on public.child_foreign parent_tbl_1
-         Output: 'text'::text, 123.456, parent_tbl_1.tableoid, parent_tbl_1.ctid, parent_tbl_1.*
+         Output: 'text'::text, 123.456, parent_tbl_1.ctid, parent_tbl_1.*, parent_tbl_1.tableoid
          Remote SQL: SELECT b, c, a, ctid FROM public.child_local WHERE ((a < 5)) FOR UPDATE
 (6 rows)
 
@@ -8268,7 +8268,7 @@ UPDATE parent_tbl SET b = b + 1;
    Foreign Update on public.foreign_tbl parent_tbl_1
      Remote SQL: UPDATE public.local_tbl SET b = $2 WHERE ctid = $1
    ->  Foreign Scan on public.foreign_tbl parent_tbl_1
-         Output: (parent_tbl_1.b + 1), parent_tbl_1.tableoid, parent_tbl_1.ctid, parent_tbl_1.*
+         Output: (parent_tbl_1.b + 1), parent_tbl_1.ctid, parent_tbl_1.*, parent_tbl_1.tableoid
          Remote SQL: SELECT a, b, ctid FROM public.local_tbl FOR UPDATE
 (6 rows)
 
@@ -8282,7 +8282,7 @@ DELETE FROM parent_tbl;
    Foreign Delete on public.foreign_tbl parent_tbl_1
      Remote SQL: DELETE FROM public.local_tbl WHERE ctid = $1
    ->  Foreign Scan on public.foreign_tbl parent_tbl_1
-         Output: parent_tbl_1.tableoid, parent_tbl_1.ctid
+         Output: parent_tbl_1.ctid, parent_tbl_1.tableoid
          Remote SQL: SELECT ctid FROM public.local_tbl FOR UPDATE
 (6 rows)
 
@@ -8308,12 +8308,12 @@ UPDATE parent_tbl SET b = b + 1;
    Foreign Update on public.foreign_tbl parent_tbl_2
      Remote SQL: UPDATE public.local_tbl SET b = $2 WHERE ctid = $1
    ->  Result
-         Output: (parent_tbl.b + 1), parent_tbl.tableoid, parent_tbl.ctid, (NULL::record)
+         Output: (parent_tbl.b + 1), parent_tbl.ctid, parent_tbl.tableoid, (NULL::record)
          ->  Append
                ->  Seq Scan on public.parent_tbl parent_tbl_1
-                     Output: parent_tbl_1.b, parent_tbl_1.tableoid, parent_tbl_1.ctid, NULL::record
+                     Output: parent_tbl_1.b, parent_tbl_1.ctid, parent_tbl_1.tableoid, NULL::record
                ->  Foreign Scan on public.foreign_tbl parent_tbl_2
-                     Output: parent_tbl_2.b, parent_tbl_2.tableoid, parent_tbl_2.ctid, parent_tbl_2.*
+                     Output: parent_tbl_2.b, parent_tbl_2.ctid, parent_tbl_2.tableoid, parent_tbl_2.*
                      Remote SQL: SELECT a, b, ctid FROM public.local_tbl FOR UPDATE
 (12 rows)
 
@@ -8329,9 +8329,9 @@ DELETE FROM parent_tbl;
      Remote SQL: DELETE FROM public.local_tbl WHERE ctid = $1
    ->  Append
          ->  Seq Scan on public.parent_tbl parent_tbl_1
-               Output: parent_tbl_1.tableoid, parent_tbl_1.ctid
+               Output: parent_tbl_1.ctid, parent_tbl_1.tableoid
          ->  Foreign Scan on public.foreign_tbl parent_tbl_2
-               Output: parent_tbl_2.tableoid, parent_tbl_2.ctid
+               Output: parent_tbl_2.ctid, parent_tbl_2.tableoid
                Remote SQL: SELECT ctid FROM public.local_tbl FOR UPDATE
 (10 rows)
 
@@ -8682,14 +8682,14 @@ update bar set f2 = f2 + 100 where f1 in (select f1 from foo);
    Foreign Update on public.bar2 bar_2
      Remote SQL: UPDATE public.loct2 SET f2 = $2 WHERE ctid = $1
    ->  Hash Join
-         Output: (bar.f2 + 100), foo.ctid, bar.tableoid, bar.ctid, (NULL::record), foo.*, foo.tableoid
+         Output: (bar.f2 + 100), foo.ctid, bar.ctid, bar.tableoid, (NULL::record), foo.*, foo.tableoid
          Inner Unique: true
          Hash Cond: (bar.f1 = foo.f1)
          ->  Append
                ->  Seq Scan on public.bar bar_1
-                     Output: bar_1.f2, bar_1.f1, bar_1.tableoid, bar_1.ctid, NULL::record
+                     Output: bar_1.f2, bar_1.f1, bar_1.ctid, bar_1.tableoid, NULL::record
                ->  Foreign Scan on public.bar2 bar_2
-                     Output: bar_2.f2, bar_2.f1, bar_2.tableoid, bar_2.ctid, bar_2.*
+                     Output: bar_2.f2, bar_2.f1, bar_2.ctid, bar_2.tableoid, bar_2.*
                      Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE
          ->  Hash
                Output: foo.ctid, foo.f1, foo.*, foo.tableoid
@@ -8729,16 +8729,16 @@ where bar.f1 = ss.f1;
    Foreign Update on public.bar2 bar_2
      Remote SQL: UPDATE public.loct2 SET f2 = $2 WHERE ctid = $1
    ->  Merge Join
-         Output: (bar.f2 + 100), (ROW(foo.f1)), bar.tableoid, bar.ctid, (NULL::record)
+         Output: (bar.f2 + 100), (ROW(foo.f1)), bar.ctid, bar.tableoid, (NULL::record)
          Merge Cond: (bar.f1 = foo.f1)
          ->  Sort
-               Output: bar.f2, bar.f1, bar.tableoid, bar.ctid, (NULL::record)
+               Output: bar.f2, bar.f1, bar.ctid, bar.tableoid, (NULL::record)
                Sort Key: bar.f1
                ->  Append
                      ->  Seq Scan on public.bar bar_1
-                           Output: bar_1.f2, bar_1.f1, bar_1.tableoid, bar_1.ctid, NULL::record
+                           Output: bar_1.f2, bar_1.f1, bar_1.ctid, bar_1.tableoid, NULL::record
                      ->  Foreign Scan on public.bar2 bar_2
-                           Output: bar_2.f2, bar_2.f1, bar_2.tableoid, bar_2.ctid, bar_2.*
+                           Output: bar_2.f2, bar_2.f1, bar_2.ctid, bar_2.tableoid, bar_2.*
                            Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE
          ->  Sort
                Output: (ROW(foo.f1)), foo.f1
@@ -8888,7 +8888,7 @@ delete from foo where f1 < 5 returning *;
    Foreign Delete on public.foo2 foo_2
    ->  Append
          ->  Index Scan using i_foo_f1 on public.foo foo_1
-               Output: foo_1.tableoid, foo_1.ctid
+               Output: foo_1.ctid, foo_1.tableoid
                Index Cond: (foo_1.f1 < 5)
          ->  Foreign Delete on public.foo2 foo_2
                Remote SQL: DELETE FROM public.loct1 WHERE ((f1 < 5)) RETURNING f1, f2
@@ -8913,10 +8913,10 @@ update bar set f2 = f2 + 100 returning *;
    Update on public.bar bar_1
    Foreign Update on public.bar2 bar_2
    ->  Result
-         Output: (bar.f2 + 100), bar.tableoid, bar.ctid, (NULL::record)
+         Output: (bar.f2 + 100), bar.ctid, bar.tableoid, (NULL::record)
          ->  Append
                ->  Seq Scan on public.bar bar_1
-                     Output: bar_1.f2, bar_1.tableoid, bar_1.ctid, NULL::record
+                     Output: bar_1.f2, bar_1.ctid, bar_1.tableoid, NULL::record
                ->  Foreign Update on public.bar2 bar_2
                      Remote SQL: UPDATE public.loct2 SET f2 = (f2 + 100) RETURNING f1, f2
 (11 rows)
@@ -8948,12 +8948,12 @@ update bar set f2 = f2 + 100;
    Foreign Update on public.bar2 bar_2
      Remote SQL: UPDATE public.loct2 SET f1 = $2, f2 = $3, f3 = $4 WHERE ctid = $1 RETURNING f1, f2, f3
    ->  Result
-         Output: (bar.f2 + 100), bar.tableoid, bar.ctid, (NULL::record)
+         Output: (bar.f2 + 100), bar.ctid, bar.tableoid, (NULL::record)
          ->  Append
                ->  Seq Scan on public.bar bar_1
-                     Output: bar_1.f2, bar_1.tableoid, bar_1.ctid, NULL::record
+                     Output: bar_1.f2, bar_1.ctid, bar_1.tableoid, NULL::record
                ->  Foreign Scan on public.bar2 bar_2
-                     Output: bar_2.f2, bar_2.tableoid, bar_2.ctid, bar_2.*
+                     Output: bar_2.f2, bar_2.ctid, bar_2.tableoid, bar_2.*
                      Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE
 (12 rows)
 
@@ -8980,10 +8980,10 @@ delete from bar where f2 < 400;
      Remote SQL: DELETE FROM public.loct2 WHERE ctid = $1 RETURNING f1, f2, f3
    ->  Append
          ->  Seq Scan on public.bar bar_1
-               Output: bar_1.tableoid, bar_1.ctid, NULL::record
+               Output: bar_1.ctid, bar_1.tableoid, NULL::record
                Filter: (bar_1.f2 < 400)
          ->  Foreign Scan on public.bar2 bar_2
-               Output: bar_2.tableoid, bar_2.ctid, bar_2.*
+               Output: bar_2.ctid, bar_2.tableoid, bar_2.*
                Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 WHERE ((f2 < 400)) FOR UPDATE
 (11 rows)
 
@@ -9024,13 +9024,13 @@ update parent set b = parent.b || remt2.b from remt2 where parent.a = remt2.a re
    Foreign Update on public.remt1 parent_2
      Remote SQL: UPDATE public.loct1 SET b = $2 WHERE ctid = $1 RETURNING a, b
    ->  Nested Loop
-         Output: (parent.b || remt2.b), remt2.*, remt2.a, remt2.b, parent.tableoid, parent.ctid, (NULL::record)
+         Output: (parent.b || remt2.b), remt2.*, remt2.a, remt2.b, parent.ctid, parent.tableoid, (NULL::record)
          Join Filter: (parent.a = remt2.a)
          ->  Append
                ->  Seq Scan on public.parent parent_1
-                     Output: parent_1.b, parent_1.a, parent_1.tableoid, parent_1.ctid, NULL::record
+                     Output: parent_1.b, parent_1.a, parent_1.ctid, parent_1.tableoid, NULL::record
                ->  Foreign Scan on public.remt1 parent_2
-                     Output: parent_2.b, parent_2.a, parent_2.tableoid, parent_2.ctid, parent_2.*
+                     Output: parent_2.b, parent_2.a, parent_2.ctid, parent_2.tableoid, parent_2.*
                      Remote SQL: SELECT a, b, ctid FROM public.loct1 FOR UPDATE
          ->  Materialize
                Output: remt2.b, remt2.*, remt2.a
@@ -9056,13 +9056,13 @@ delete from parent using remt2 where parent.a = remt2.a returning parent;
    Foreign Delete on public.remt1 parent_2
      Remote SQL: DELETE FROM public.loct1 WHERE ctid = $1 RETURNING a, b
    ->  Nested Loop
-         Output: remt2.*, parent.tableoid, parent.ctid
+         Output: remt2.*, parent.ctid, parent.tableoid
          Join Filter: (parent.a = remt2.a)
          ->  Append
                ->  Seq Scan on public.parent parent_1
-                     Output: parent_1.a, parent_1.tableoid, parent_1.ctid
+                     Output: parent_1.a, parent_1.ctid, parent_1.tableoid
                ->  Foreign Scan on public.remt1 parent_2
-                     Output: parent_2.a, parent_2.tableoid, parent_2.ctid
+                     Output: parent_2.a, parent_2.ctid, parent_2.tableoid
                      Remote SQL: SELECT a, ctid FROM public.loct1 FOR UPDATE
          ->  Materialize
                Output: remt2.*, remt2.a
@@ -9293,7 +9293,7 @@ update utrtest set a = 1 where a = 1 or a = 2 returning *;
          ->  Foreign Update on public.remp utrtest_1
                Remote SQL: UPDATE public.loct SET a = 1 WHERE (((a = 1) OR (a = 2))) RETURNING a, b
          ->  Seq Scan on public.locp utrtest_2
-               Output: 1, utrtest_2.tableoid, utrtest_2.ctid, NULL::record
+               Output: 1, utrtest_2.ctid, NULL::record, utrtest_2.tableoid
                Filter: ((utrtest_2.a = 1) OR (utrtest_2.a = 2))
 (10 rows)
 
@@ -9311,7 +9311,7 @@ update utrtest set a = 1 where a = 2 returning *;
    Output: utrtest_1.a, utrtest_1.b
    Update on public.locp utrtest_1
    ->  Seq Scan on public.locp utrtest_1
-         Output: 1, utrtest_1.tableoid, utrtest_1.ctid
+         Output: 1, utrtest_1.ctid, utrtest_1.tableoid
          Filter: (utrtest_1.a = 2)
 (6 rows)
 
@@ -9342,7 +9342,7 @@ update utrtest set a = 1 returning *;
          ->  Foreign Update on public.remp utrtest_1
                Remote SQL: UPDATE public.loct SET a = 1 RETURNING a, b
          ->  Seq Scan on public.locp utrtest_2
-               Output: 1, utrtest_2.tableoid, utrtest_2.ctid, NULL::record
+               Output: 1, utrtest_2.ctid, NULL::record, utrtest_2.tableoid
 (9 rows)
 
 update utrtest set a = 1 returning *;
@@ -9361,14 +9361,14 @@ update utrtest set a = 1 from (values (1), (2)) s(x) where a = s.x returning *;
      Remote SQL: UPDATE public.loct SET a = $2 WHERE ctid = $1 RETURNING a, b
    Update on public.locp utrtest_2
    ->  Hash Join
-         Output: 1, "*VALUES*".*, "*VALUES*".column1, utrtest.tableoid, utrtest.ctid, utrtest.*
+         Output: 1, "*VALUES*".*, "*VALUES*".column1, utrtest.ctid, utrtest.*, utrtest.tableoid
          Hash Cond: (utrtest.a = "*VALUES*".column1)
          ->  Append
                ->  Foreign Scan on public.remp utrtest_1
-                     Output: utrtest_1.a, utrtest_1.tableoid, utrtest_1.ctid, utrtest_1.*
+                     Output: utrtest_1.a, utrtest_1.ctid, utrtest_1.*, utrtest_1.tableoid
                      Remote SQL: SELECT a, b, ctid FROM public.loct FOR UPDATE
                ->  Seq Scan on public.locp utrtest_2
-                     Output: utrtest_2.a, utrtest_2.tableoid, utrtest_2.ctid, NULL::record
+                     Output: utrtest_2.a, utrtest_2.ctid, NULL::record, utrtest_2.tableoid
          ->  Hash
                Output: "*VALUES*".*, "*VALUES*".column1
                ->  Values Scan on "*VALUES*"
@@ -9400,7 +9400,7 @@ update utrtest set a = 3 returning *;
    Foreign Update on public.remp utrtest_2
    ->  Append
          ->  Seq Scan on public.locp utrtest_1
-               Output: 3, utrtest_1.tableoid, utrtest_1.ctid, NULL::record
+               Output: 3, utrtest_1.ctid, utrtest_1.tableoid, NULL::record
          ->  Foreign Update on public.remp utrtest_2
                Remote SQL: UPDATE public.loct SET a = 3 RETURNING a, b
 (9 rows)
@@ -9418,13 +9418,13 @@ update utrtest set a = 3 from (values (2), (3)) s(x) where a = s.x returning *;
    Foreign Update on public.remp utrtest_2
      Remote SQL: UPDATE public.loct SET a = $2 WHERE ctid = $1 RETURNING a, b
    ->  Hash Join
-         Output: 3, "*VALUES*".*, "*VALUES*".column1, utrtest.tableoid, utrtest.ctid, (NULL::record)
+         Output: 3, "*VALUES*".*, "*VALUES*".column1, utrtest.ctid, utrtest.tableoid, (NULL::record)
          Hash Cond: (utrtest.a = "*VALUES*".column1)
          ->  Append
                ->  Seq Scan on public.locp utrtest_1
-                     Output: utrtest_1.a, utrtest_1.tableoid, utrtest_1.ctid, NULL::record
+                     Output: utrtest_1.a, utrtest_1.ctid, utrtest_1.tableoid, NULL::record
                ->  Foreign Scan on public.remp utrtest_2
-                     Output: utrtest_2.a, utrtest_2.tableoid, utrtest_2.ctid, utrtest_2.*
+                     Output: utrtest_2.a, utrtest_2.ctid, utrtest_2.tableoid, utrtest_2.*
                      Remote SQL: SELECT a, b, ctid FROM public.loct FOR UPDATE
          ->  Hash
                Output: "*VALUES*".*, "*VALUES*".column1
@@ -12325,7 +12325,7 @@ UPDATE async_pt SET c = c || c WHERE b = 0 RETURNING *;
          ->  Foreign Update on public.async_p2 async_pt_2
                Remote SQL: UPDATE public.base_tbl2 SET c = (c || c) WHERE ((b = 0)) RETURNING a, b, c
          ->  Seq Scan on public.async_p3 async_pt_3
-               Output: (async_pt_3.c || async_pt_3.c), async_pt_3.tableoid, async_pt_3.ctid, NULL::record
+               Output: (async_pt_3.c || async_pt_3.c), async_pt_3.ctid, NULL::record, async_pt_3.tableoid
                Filter: (async_pt_3.b = 0)
 (13 rows)
 
@@ -12352,7 +12352,7 @@ DELETE FROM async_pt WHERE b = 0 RETURNING *;
          ->  Foreign Delete on public.async_p2 async_pt_2
                Remote SQL: DELETE FROM public.base_tbl2 WHERE ((b = 0)) RETURNING a, b, c
          ->  Seq Scan on public.async_p3 async_pt_3
-               Output: async_pt_3.tableoid, async_pt_3.ctid
+               Output: async_pt_3.ctid, async_pt_3.tableoid
                Filter: (async_pt_3.b = 0)
 (13 rows)
 
diff --git a/src/backend/optimizer/prep/preptlist.c b/src/backend/optimizer/prep/preptlist.c
index ff9c7c4fb96..b32ae387560 100644
--- a/src/backend/optimizer/prep/preptlist.c
+++ b/src/backend/optimizer/prep/preptlist.c
@@ -122,8 +122,8 @@ preprocess_targetlist(PlannerInfo *root)
 	{
 		/* row-identity logic expects to add stuff to processed_tlist */
 		root->processed_tlist = tlist;
-		add_row_identity_columns(root, result_relation,
-								 target_rte, target_relation);
+		(void) add_row_identity_columns(root, result_relation,
+										target_rte, target_relation);
 		tlist = root->processed_tlist;
 	}
 
diff --git a/src/backend/optimizer/util/appendinfo.c b/src/backend/optimizer/util/appendinfo.c
index 689840d6564..e13841f34e4 100644
--- a/src/backend/optimizer/util/appendinfo.c
+++ b/src/backend/optimizer/util/appendinfo.c
@@ -949,8 +949,12 @@ add_row_identity_var(PlannerInfo *root, Var *orig_var,
  * This function adds the row identity columns needed by the core code.
  * FDWs might call add_row_identity_var() for themselves to add nonstandard
  * columns.  (Duplicate requests are fine.)
+ *
+ * Returns true if any row-identity columns were added, false if not.
+ * For foreign tables whose FDW lacks AddForeignUpdateTargets, does
+ * nothing and returns false.
  */
-void
+bool
 add_row_identity_columns(PlannerInfo *root, Index rtindex,
 						 RangeTblEntry *target_rte,
 						 Relation target_relation)
@@ -976,6 +980,7 @@ add_row_identity_columns(PlannerInfo *root, Index rtindex,
 					  InvalidOid,
 					  0);
 		add_row_identity_var(root, var, rtindex, "ctid");
+		return true;
 	}
 	else if (relkind == RELKIND_FOREIGN_TABLE)
 	{
@@ -986,9 +991,17 @@ add_row_identity_columns(PlannerInfo *root, Index rtindex,
 
 		fdwroutine = GetFdwRoutineForRelation(target_relation, false);
 
+		/*
+		 * If the FDW provides AddForeignUpdateTargets, let it add whatever
+		 * row-identity columns it needs.  Otherwise, this child cannot
+		 * provide row identity, so return false to indicate no columns
+		 * were added.
+		 */
 		if (fdwroutine->AddForeignUpdateTargets != NULL)
 			fdwroutine->AddForeignUpdateTargets(root, rtindex,
 												target_rte, target_relation);
+		else
+			return false;
 
 		/*
 		 * For UPDATE, we need to make the FDW fetch unchanged columns by
@@ -1016,7 +1029,11 @@ add_row_identity_columns(PlannerInfo *root, Index rtindex,
 						  0);
 			add_row_identity_var(root, var, rtindex, "wholerow");
 		}
+
+		return true;
 	}
+
+	return false;
 }
 
 /*
@@ -1074,8 +1091,8 @@ distribute_row_identity_vars(PlannerInfo *root)
 		Relation	target_relation;
 
 		target_relation = table_open(target_rte->relid, NoLock);
-		add_row_identity_columns(root, result_relation,
-								 target_rte, target_relation);
+		(void) 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. */
diff --git a/src/backend/optimizer/util/inherit.c b/src/backend/optimizer/util/inherit.c
index 48b5d0aac4c..56cecc2d5c0 100644
--- a/src/backend/optimizer/util/inherit.c
+++ b/src/backend/optimizer/util/inherit.c
@@ -634,11 +634,14 @@ expand_single_inheritance_child(PlannerInfo *root, RangeTblEntry *parentrte,
 							-1,
 							InvalidOid,
 							0);
-			add_row_identity_var(root, rrvar, childRTindex, "tableoid");
 
-			/* Register any row-identity columns needed by this child. */
-			add_row_identity_columns(root, childRTindex,
-									 childrte, childrel);
+			/*
+			 * Register any row-identity columns needed by this child.
+			 * Add tableoid only if row-identity columns were added.
+			 */
+			if (add_row_identity_columns(root, childRTindex,
+										 childrte, childrel))
+				add_row_identity_var(root, rrvar, childRTindex, "tableoid");
 		}
 	}
 }
diff --git a/src/include/optimizer/appendinfo.h b/src/include/optimizer/appendinfo.h
index b59a6218853..5fa8aad6df8 100644
--- a/src/include/optimizer/appendinfo.h
+++ b/src/include/optimizer/appendinfo.h
@@ -42,7 +42,7 @@ extern AppendRelInfo **find_appinfos_by_relids(PlannerInfo *root,
 											   Relids relids, int *nappinfos);
 extern void add_row_identity_var(PlannerInfo *root, Var *orig_var,
 								 Index rtindex, const char *rowid_name);
-extern void add_row_identity_columns(PlannerInfo *root, Index rtindex,
+extern bool add_row_identity_columns(PlannerInfo *root, Index rtindex,
 									 RangeTblEntry *target_rte,
 									 Relation target_relation);
 extern void distribute_row_identity_vars(PlannerInfo *root);
diff --git a/src/test/regress/expected/inherit.out b/src/test/regress/expected/inherit.out
index 0490a746555..e8fcae6514f 100644
--- a/src/test/regress/expected/inherit.out
+++ b/src/test/regress/expected/inherit.out
@@ -579,7 +579,7 @@ update some_tab set a = a + 1 where false;
 --------------------------------------------------------
  Update on public.some_tab
    ->  Result
-         Output: (some_tab.a + 1), NULL::oid, NULL::tid
+         Output: (some_tab.a + 1), NULL::tid, NULL::oid
          Replaces: Scan on some_tab
          One-Time Filter: false
 (5 rows)
@@ -592,7 +592,7 @@ update some_tab set a = a + 1 where false returning b, a;
  Update on public.some_tab
    Output: some_tab.b, some_tab.a
    ->  Result
-         Output: (some_tab.a + 1), NULL::oid, NULL::tid
+         Output: (some_tab.a + 1), NULL::tid, NULL::oid
          Replaces: Scan on some_tab
          One-Time Filter: false
 (6 rows)
@@ -2054,12 +2054,12 @@ update inhpar i set (f1, f2) = (select i.f1, i.f2 || '-' from int4_tbl limit 1);
    Update on public.inhpar i_1
    Update on public.inhcld i_2
    ->  Result
-         Output: (SubPlan multiexpr_1).col1, (SubPlan multiexpr_1).col2, (rescan SubPlan multiexpr_1), i.tableoid, i.ctid
+         Output: (SubPlan multiexpr_1).col1, (SubPlan multiexpr_1).col2, (rescan SubPlan multiexpr_1), i.ctid, i.tableoid
          ->  Append
                ->  Seq Scan on public.inhpar i_1
-                     Output: i_1.f1, i_1.f2, i_1.tableoid, i_1.ctid
+                     Output: i_1.f1, i_1.f2, i_1.ctid, i_1.tableoid
                ->  Seq Scan on public.inhcld i_2
-                     Output: i_2.f1, i_2.f2, i_2.tableoid, i_2.ctid
+                     Output: i_2.f1, i_2.f2, i_2.ctid, i_2.tableoid
          SubPlan multiexpr_1
            ->  Limit
                  Output: (i.f1), (((i.f2)::text || '-'::text))
@@ -2103,14 +2103,14 @@ update inhpar i set (f1, f2) = (select i.f1, i.f2 || '-' from int4_tbl limit 1);
    Update on public.inhcld2 i_2
    ->  Append
          ->  Seq Scan on public.inhcld1 i_1
-               Output: (SubPlan multiexpr_1).col1, (SubPlan multiexpr_1).col2, (rescan SubPlan multiexpr_1), i_1.tableoid, i_1.ctid
+               Output: (SubPlan multiexpr_1).col1, (SubPlan multiexpr_1).col2, (rescan SubPlan multiexpr_1), i_1.ctid, i_1.tableoid
                SubPlan multiexpr_1
                  ->  Limit
                        Output: (i_1.f1), (((i_1.f2)::text || '-'::text))
                        ->  Seq Scan on public.int4_tbl
                              Output: i_1.f1, ((i_1.f2)::text || '-'::text)
          ->  Seq Scan on public.inhcld2 i_2
-               Output: (SubPlan multiexpr_1).col1, (SubPlan multiexpr_1).col2, (rescan SubPlan multiexpr_1), i_2.tableoid, i_2.ctid
+               Output: (SubPlan multiexpr_1).col1, (SubPlan multiexpr_1).col2, (rescan SubPlan multiexpr_1), i_2.ctid, i_2.tableoid
 (13 rows)
 
 update inhpar i set (f1, f2) = (select i.f1, i.f2 || '-' from int4_tbl limit 1);
diff --git a/src/test/regress/expected/merge.out b/src/test/regress/expected/merge.out
index 9cb1d87066a..10b27b01532 100644
--- a/src/test/regress/expected/merge.out
+++ b/src/test/regress/expected/merge.out
@@ -2387,15 +2387,15 @@ MERGE INTO pa_target t USING pa_source s ON t.tid = s.sid
  Merge on public.pa_target t
    Merge on public.pa_targetp t_1
    ->  Hash Left Join
-         Output: s.sid, s.ctid, t_1.tableoid, t_1.ctid
+         Output: s.sid, s.ctid, t_1.ctid, t_1.tableoid
          Inner Unique: true
          Hash Cond: (s.sid = t_1.tid)
          ->  Seq Scan on public.pa_source s
                Output: s.sid, s.ctid
          ->  Hash
-               Output: t_1.tid, t_1.tableoid, t_1.ctid
+               Output: t_1.tid, t_1.ctid, t_1.tableoid
                ->  Seq Scan on public.pa_targetp t_1
-                     Output: t_1.tid, t_1.tableoid, t_1.ctid
+                     Output: t_1.tid, t_1.ctid, t_1.tableoid
 (12 rows)
 
 MERGE INTO pa_target t USING pa_source s ON t.tid = s.sid
diff --git a/src/test/regress/expected/partition_prune.out b/src/test/regress/expected/partition_prune.out
index deacdd75807..24c6ac408f3 100644
--- a/src/test/regress/expected/partition_prune.out
+++ b/src/test/regress/expected/partition_prune.out
@@ -4585,7 +4585,7 @@ explain (verbose, costs off) execute update_part_abc_view (1, 'd');
    ->  Append
          Subplans Removed: 1
          ->  Seq Scan on public.part_abc_1
-               Output: $2, part_abc_1.tableoid, part_abc_1.ctid
+               Output: $2, part_abc_1.ctid, part_abc_1.tableoid
                Filter: ((part_abc_1.b <> 'a'::text) AND (part_abc_1.a = $1))
 (8 rows)
 
@@ -4604,7 +4604,7 @@ explain (verbose, costs off) execute update_part_abc_view (2, 'a');
    ->  Append
          Subplans Removed: 1
          ->  Seq Scan on public.part_abc_2
-               Output: $2, part_abc_2.tableoid, part_abc_2.ctid
+               Output: $2, part_abc_2.ctid, part_abc_2.tableoid
                Filter: ((part_abc_2.b <> 'a'::text) AND (part_abc_2.a = $1))
 (8 rows)
 
diff --git a/src/test/regress/expected/returning.out b/src/test/regress/expected/returning.out
index cfaaf015bb3..2b2161f245c 100644
--- a/src/test/regress/expected/returning.out
+++ b/src/test/regress/expected/returning.out
@@ -504,9 +504,9 @@ UPDATE foo SET f4 = 100 WHERE f1 = 5
    Output: (old.tableoid)::regclass, old.ctid, old.f1, old.f2, old.f3, old.f4, old.*, (new.tableoid)::regclass, new.ctid, new.f1, new.f2, new.f3, new.f4, new.*, (((old.f4)::text || '->'::text) || (new.f4)::text)
    Update on pg_temp.foo foo_1
    ->  Result
-         Output: '100'::bigint, foo_1.tableoid, foo_1.ctid
+         Output: '100'::bigint, foo_1.ctid, foo_1.tableoid
          ->  Seq Scan on pg_temp.foo foo_1
-               Output: foo_1.tableoid, foo_1.ctid
+               Output: foo_1.ctid, foo_1.tableoid
                Filter: (foo_1.f1 = 5)
 (8 rows)
 
@@ -530,7 +530,7 @@ DELETE FROM foo WHERE f1 = 5
    Output: (old.tableoid)::regclass, old.ctid, old.f1, old.f2, old.f3, old.f4, (new.tableoid)::regclass, new.ctid, new.f1, new.f2, new.f3, new.f4, foo_1.f1, foo_1.f2, foo_1.f3, foo_1.f4
    Delete on pg_temp.foo foo_1
    ->  Seq Scan on pg_temp.foo foo_1
-         Output: foo_1.tableoid, foo_1.ctid
+         Output: foo_1.ctid, foo_1.tableoid
          Filter: (foo_1.f1 = 5)
 (6 rows)
 
@@ -586,9 +586,9 @@ UPDATE foo SET f4 = 100 WHERE f1 = 5
    Output: (SubPlan expr_1), (SubPlan expr_2), (SubPlan expr_3)
    Update on pg_temp.foo foo_1
    ->  Result
-         Output: '100'::bigint, foo_1.tableoid, foo_1.ctid
+         Output: '100'::bigint, foo_1.ctid, foo_1.tableoid
          ->  Seq Scan on pg_temp.foo foo_1
-               Output: foo_1.tableoid, foo_1.ctid
+               Output: foo_1.ctid, foo_1.tableoid
                Filter: (foo_1.f1 = 5)
    SubPlan expr_1
      ->  Result
@@ -626,7 +626,7 @@ DELETE FROM foo WHERE f1 = 5
    Output: (SubPlan expr_1), (SubPlan expr_2)
    Delete on pg_temp.foo foo_1
    ->  Seq Scan on pg_temp.foo foo_1
-         Output: foo_1.tableoid, foo_1.ctid
+         Output: foo_1.ctid, foo_1.tableoid
          Filter: (foo_1.f1 = 5)
    SubPlan expr_1
      ->  Aggregate
@@ -662,9 +662,9 @@ DELETE FROM foo WHERE f1 = 4 RETURNING old.*,new.*, *;
    Output: old.f1, old.f2, old.f3, old.f4, new.f1, new.f2, new.f3, new.f4, foo_2.f1, foo_2.f2, foo_2.f3, foo_2.f4
    Update on pg_temp.foo foo_2
    ->  Nested Loop
-         Output: (foo_2.f2 || ' (deleted)'::text), '-1'::integer, '-1'::bigint, foo_1.ctid, foo_1.tableoid, foo_2.tableoid, foo_2.ctid
+         Output: (foo_2.f2 || ' (deleted)'::text), '-1'::integer, '-1'::bigint, foo_1.ctid, foo_1.tableoid, foo_2.ctid, foo_2.tableoid
          ->  Seq Scan on pg_temp.foo foo_2
-               Output: foo_2.f2, foo_2.f1, foo_2.tableoid, foo_2.ctid
+               Output: foo_2.f2, foo_2.f1, foo_2.ctid, foo_2.tableoid
                Filter: (foo_2.f1 = 4)
          ->  Seq Scan on pg_temp.foo foo_1
                Output: foo_1.ctid, foo_1.f1, foo_1.tableoid
@@ -687,17 +687,17 @@ UPDATE joinview SET f3 = f3 + 1 WHERE f3 = 57
    Output: old.f1, old.f2, old.f3, old.f4, joinme.other, new.f1, new.f2, new.f3, new.f4, joinme.other, foo_1.f1, foo_1.f2, foo_1.f3, foo_1.f4, joinme.other, (new.f3 - old.f3)
    Update on pg_temp.foo foo_1
    ->  Hash Join
-         Output: foo_2.f1, (foo_2.f3 + 1), joinme.ctid, foo_2.ctid, joinme_1.ctid, joinme.other, foo_1.tableoid, foo_1.ctid, foo_2.tableoid
+         Output: foo_2.f1, (foo_2.f3 + 1), joinme.ctid, foo_2.ctid, joinme_1.ctid, joinme.other, foo_1.ctid, foo_1.tableoid, foo_2.tableoid
          Hash Cond: (foo_1.f2 = joinme.f2j)
          ->  Hash Join
-               Output: foo_1.f2, foo_1.tableoid, foo_1.ctid, joinme_1.ctid, joinme_1.f2j
+               Output: foo_1.f2, foo_1.ctid, foo_1.tableoid, joinme_1.ctid, joinme_1.f2j
                Hash Cond: (joinme_1.f2j = foo_1.f2)
                ->  Seq Scan on pg_temp.joinme joinme_1
                      Output: joinme_1.ctid, joinme_1.f2j
                ->  Hash
-                     Output: foo_1.f2, foo_1.tableoid, foo_1.ctid
+                     Output: foo_1.f2, foo_1.ctid, foo_1.tableoid
                      ->  Seq Scan on pg_temp.foo foo_1
-                           Output: foo_1.f2, foo_1.tableoid, foo_1.ctid
+                           Output: foo_1.f2, foo_1.ctid, foo_1.tableoid
          ->  Hash
                Output: joinme.ctid, joinme.other, joinme.f2j, foo_2.f1, foo_2.f3, foo_2.ctid, foo_2.f2, foo_2.tableoid
                ->  Hash Join
diff --git a/src/test/regress/expected/updatable_views.out b/src/test/regress/expected/updatable_views.out
index 03df7e75b7b..d059e70e0c5 100644
--- a/src/test/regress/expected/updatable_views.out
+++ b/src/test/regress/expected/updatable_views.out
@@ -3248,10 +3248,10 @@ UPDATE v1 SET a=100 WHERE snoop(a) AND leakproof(a) AND a < 7 AND a != 6;
    Update on public.t12 t1_3
    Update on public.t111 t1_4
    ->  Result
-         Output: 100, t1.tableoid, t1.ctid
+         Output: 100, t1.ctid, t1.tableoid
          ->  Append
                ->  Index Scan using t1_a_idx on public.t1 t1_1
-                     Output: t1_1.tableoid, t1_1.ctid
+                     Output: t1_1.ctid, t1_1.tableoid
                      Index Cond: ((t1_1.a > 5) AND (t1_1.a < 7))
                      Filter: ((t1_1.a <> 6) AND EXISTS(SubPlan exists_1) AND snoop(t1_1.a) AND leakproof(t1_1.a))
                      SubPlan exists_1
@@ -3261,15 +3261,15 @@ UPDATE v1 SET a=100 WHERE snoop(a) AND leakproof(a) AND a < 7 AND a != 6;
                              ->  Seq Scan on public.t111 t12_2
                                    Filter: (t12_2.a = t1_1.a)
                ->  Index Scan using t11_a_idx on public.t11 t1_2
-                     Output: t1_2.tableoid, t1_2.ctid
+                     Output: t1_2.ctid, t1_2.tableoid
                      Index Cond: ((t1_2.a > 5) AND (t1_2.a < 7))
                      Filter: ((t1_2.a <> 6) AND EXISTS(SubPlan exists_1) AND snoop(t1_2.a) AND leakproof(t1_2.a))
                ->  Index Scan using t12_a_idx on public.t12 t1_3
-                     Output: t1_3.tableoid, t1_3.ctid
+                     Output: t1_3.ctid, t1_3.tableoid
                      Index Cond: ((t1_3.a > 5) AND (t1_3.a < 7))
                      Filter: ((t1_3.a <> 6) AND EXISTS(SubPlan exists_1) AND snoop(t1_3.a) AND leakproof(t1_3.a))
                ->  Index Scan using t111_a_idx on public.t111 t1_4
-                     Output: t1_4.tableoid, t1_4.ctid
+                     Output: t1_4.ctid, t1_4.tableoid
                      Index Cond: ((t1_4.a > 5) AND (t1_4.a < 7))
                      Filter: ((t1_4.a <> 6) AND EXISTS(SubPlan exists_1) AND snoop(t1_4.a) AND leakproof(t1_4.a))
 (30 rows)
@@ -3295,10 +3295,10 @@ UPDATE v1 SET a=a+1 WHERE snoop(a) AND leakproof(a) AND a = 8;
    Update on public.t12 t1_3
    Update on public.t111 t1_4
    ->  Result
-         Output: (t1.a + 1), t1.tableoid, t1.ctid
+         Output: (t1.a + 1), t1.ctid, t1.tableoid
          ->  Append
                ->  Index Scan using t1_a_idx on public.t1 t1_1
-                     Output: t1_1.a, t1_1.tableoid, t1_1.ctid
+                     Output: t1_1.a, t1_1.ctid, t1_1.tableoid
                      Index Cond: ((t1_1.a > 5) AND (t1_1.a = 8))
                      Filter: (EXISTS(SubPlan exists_1) AND snoop(t1_1.a) AND leakproof(t1_1.a))
                      SubPlan exists_1
@@ -3308,15 +3308,15 @@ UPDATE v1 SET a=a+1 WHERE snoop(a) AND leakproof(a) AND a = 8;
                              ->  Seq Scan on public.t111 t12_2
                                    Filter: (t12_2.a = t1_1.a)
                ->  Index Scan using t11_a_idx on public.t11 t1_2
-                     Output: t1_2.a, t1_2.tableoid, t1_2.ctid
+                     Output: t1_2.a, t1_2.ctid, t1_2.tableoid
                      Index Cond: ((t1_2.a > 5) AND (t1_2.a = 8))
                      Filter: (EXISTS(SubPlan exists_1) AND snoop(t1_2.a) AND leakproof(t1_2.a))
                ->  Index Scan using t12_a_idx on public.t12 t1_3
-                     Output: t1_3.a, t1_3.tableoid, t1_3.ctid
+                     Output: t1_3.a, t1_3.ctid, t1_3.tableoid
                      Index Cond: ((t1_3.a > 5) AND (t1_3.a = 8))
                      Filter: (EXISTS(SubPlan exists_1) AND snoop(t1_3.a) AND leakproof(t1_3.a))
                ->  Index Scan using t111_a_idx on public.t111 t1_4
-                     Output: t1_4.a, t1_4.tableoid, t1_4.ctid
+                     Output: t1_4.a, t1_4.ctid, t1_4.tableoid
                      Index Cond: ((t1_4.a > 5) AND (t1_4.a = 8))
                      Filter: (EXISTS(SubPlan exists_1) AND snoop(t1_4.a) AND leakproof(t1_4.a))
 (30 rows)
diff --git a/src/test/regress/expected/with.out b/src/test/regress/expected/with.out
index 77ded01b046..417514ffc31 100644
--- a/src/test/regress/expected/with.out
+++ b/src/test/regress/expected/with.out
@@ -3638,21 +3638,21 @@ DELETE FROM a_star USING wcte WHERE aa = q2;
            ->  Result
                  Output: '42'::bigint, '47'::bigint
    ->  Hash Join
-         Output: wcte.*, a_star.tableoid, a_star.ctid
+         Output: wcte.*, a_star.ctid, a_star.tableoid
          Hash Cond: (a_star.aa = wcte.q2)
          ->  Append
                ->  Seq Scan on public.a_star a_star_1
-                     Output: a_star_1.aa, a_star_1.tableoid, a_star_1.ctid
+                     Output: a_star_1.aa, a_star_1.ctid, a_star_1.tableoid
                ->  Seq Scan on public.b_star a_star_2
-                     Output: a_star_2.aa, a_star_2.tableoid, a_star_2.ctid
+                     Output: a_star_2.aa, a_star_2.ctid, a_star_2.tableoid
                ->  Seq Scan on public.c_star a_star_3
-                     Output: a_star_3.aa, a_star_3.tableoid, a_star_3.ctid
+                     Output: a_star_3.aa, a_star_3.ctid, a_star_3.tableoid
                ->  Seq Scan on public.d_star a_star_4
-                     Output: a_star_4.aa, a_star_4.tableoid, a_star_4.ctid
+                     Output: a_star_4.aa, a_star_4.ctid, a_star_4.tableoid
                ->  Seq Scan on public.e_star a_star_5
-                     Output: a_star_5.aa, a_star_5.tableoid, a_star_5.ctid
+                     Output: a_star_5.aa, a_star_5.ctid, a_star_5.tableoid
                ->  Seq Scan on public.f_star a_star_6
-                     Output: a_star_6.aa, a_star_6.tableoid, a_star_6.ctid
+                     Output: a_star_6.aa, a_star_6.ctid, a_star_6.tableoid
          ->  Hash
                Output: wcte.*, wcte.q2
                ->  CTE Scan on wcte
-- 
2.47.3

