From 2a7688763f187d29db887db456695bf1da1d564d Mon Sep 17 00:00:00 2001
From: shihao zhong <zhong950419@gmail.com>
Date: Mon, 19 Oct 2026 23:57:54 -0400
Subject: [PATCH v1] Remove HEAP_HASOID_OLD

Tuples with an oid field can no longer be created, and core code no
longer reads this bit.  Remove it, and remove the t_oid column from
pageinspect's heap_page_items() and heap_page_item_attrs() in a new
version 1.14.

Discussion: https://postgr.es/m/CAApHDvqB=kapsgwvLiPZRhfyFcJB6nw-Xs2D-jHm6ddRcxP7zA@mail.gmail.com
---
 contrib/pageinspect/Makefile                  |  2 +-
 .../pageinspect/expected/oldextversions.out   | 31 +++++++
 contrib/pageinspect/expected/page.out         |  3 +-
 contrib/pageinspect/heapfuncs.c               | 43 ++++-----
 contrib/pageinspect/meson.build               |  1 +
 .../pageinspect/pageinspect--1.13--1.14.sql   | 93 +++++++++++++++++++
 contrib/pageinspect/pageinspect.control       |  2 +-
 contrib/pageinspect/sql/oldextversions.sql    | 12 +++
 doc/src/sgml/storage.sgml                     |  8 +-
 src/include/access/htup_details.h             |  4 +-
 10 files changed, 159 insertions(+), 40 deletions(-)
 create mode 100644 contrib/pageinspect/pageinspect--1.13--1.14.sql

diff --git a/contrib/pageinspect/Makefile b/contrib/pageinspect/Makefile
index eae989569d0..09774fd340c 100644
--- a/contrib/pageinspect/Makefile
+++ b/contrib/pageinspect/Makefile
@@ -13,7 +13,7 @@ OBJS = \
 	rawpage.o
 
 EXTENSION = pageinspect
-DATA =  pageinspect--1.12--1.13.sql \
+DATA =  pageinspect--1.13--1.14.sql pageinspect--1.12--1.13.sql \
 	pageinspect--1.11--1.12.sql pageinspect--1.10--1.11.sql \
 	pageinspect--1.9--1.10.sql pageinspect--1.8--1.9.sql \
 	pageinspect--1.7--1.8.sql pageinspect--1.6--1.7.sql \
diff --git a/contrib/pageinspect/expected/oldextversions.out b/contrib/pageinspect/expected/oldextversions.out
index 2910891ece7..5cd8fbc955b 100644
--- a/contrib/pageinspect/expected/oldextversions.out
+++ b/contrib/pageinspect/expected/oldextversions.out
@@ -66,5 +66,36 @@ SELECT * FROM brin_page_items(get_raw_page('test_1_a_brin_idx', 2), 'test_1_a_br
           1 |      0 |      1 | f        | f        | f           | f     | {72057594037927937 .. 72057594037927937}
 (1 row)
 
+-- heap_page_items() lost its t_oid column in 1.14, make sure we detect
+-- an old function definition
+ALTER EXTENSION pageinspect UPDATE TO '1.13';
+INSERT INTO test1 VALUES (1, 'x'), (2, 'y');
+DELETE FROM test1 WHERE a = 1;
+VACUUM test1;
+SELECT lp, lp_flags, t_oid, t_data FROM heap_page_items(get_raw_page('test1', 0));
+ lp | lp_flags | t_oid |            t_data            
+----+----------+-------+------------------------------
+  1 |        1 |       | \x01000000000000010b74657874
+  2 |        0 |       | 
+  3 |        1 |       | \x02000000000000000579
+(3 rows)
+
+SELECT lp, t_oid, t_attrs FROM heap_page_item_attrs(get_raw_page('test1', 0), 'test1');
+ lp | t_oid |                 t_attrs                 
+----+-------+-----------------------------------------
+  1 |       | {"\\x0100000000000001","\\x0b74657874"}
+  2 |       | 
+  3 |       | {"\\x0200000000000000","\\x0579"}
+(3 rows)
+
+ALTER EXTENSION pageinspect UPDATE TO '1.14';
+SELECT lp, t_data FROM heap_page_items(get_raw_page('test1', 0));
+ lp |            t_data            
+----+------------------------------
+  1 | \x01000000000000010b74657874
+  2 | 
+  3 | \x02000000000000000579
+(3 rows)
+
 DROP TABLE test1;
 DROP EXTENSION pageinspect;
diff --git a/contrib/pageinspect/expected/page.out b/contrib/pageinspect/expected/page.out
index fcf19c5ca5a..167d3175d31 100644
--- a/contrib/pageinspect/expected/page.out
+++ b/contrib/pageinspect/expected/page.out
@@ -134,7 +134,6 @@ SELECT unnest(raw_flags)
  HEAP_COMBOCID
  HEAP_HASEXTERNAL
  HEAP_HASNULL
- HEAP_HASOID_OLD
  HEAP_HASVARWIDTH
  HEAP_HOT_UPDATED
  HEAP_KEYS_UPDATED
@@ -150,7 +149,7 @@ SELECT unnest(raw_flags)
  HEAP_XMAX_LOCK_ONLY
  HEAP_XMIN_COMMITTED
  HEAP_XMIN_INVALID
-(19 rows)
+(18 rows)
 
 SELECT unnest(combined_flags)
   FROM heap_tuple_infomask_flags(x'FFFF'::int, x'FFFF'::int) ORDER BY 1;
diff --git a/contrib/pageinspect/heapfuncs.c b/contrib/pageinspect/heapfuncs.c
index 7be4770dc84..eb3f16c0ad1 100644
--- a/contrib/pageinspect/heapfuncs.c
+++ b/contrib/pageinspect/heapfuncs.c
@@ -38,21 +38,6 @@
 #include "utils/builtins.h"
 #include "utils/rel.h"
 
-/*
- * It's not supported to create tuples with oids anymore, but when pg_upgrade
- * was used to upgrade from an older version, tuples might still have an
- * oid. Seems worthwhile to display that.
- */
-static inline Oid
-HeapTupleHeaderGetOidOld(const HeapTupleHeaderData *tup)
-{
-	if (tup->t_infomask & HEAP_HASOID_OLD)
-		return *((const Oid *) ((const char *) (tup) + (tup)->t_hoff - sizeof(Oid)));
-	else
-		return InvalidOid;
-}
-
-
 /*
  * bits_to_text
  *
@@ -231,7 +216,7 @@ heap_page_items(PG_FUNCTION_ARGS)
 				int			tuple_data_len;
 				bytea	   *tuple_data_bytea;
 
-				/* Copy null bitmask and OID, if present */
+				/* Copy null bitmask, if present */
 				if (tuphdr->t_infomask & HEAP_HASNULL)
 				{
 					int			bitmaplen;
@@ -248,11 +233,6 @@ heap_page_items(PG_FUNCTION_ARGS)
 				else
 					nulls[11] = true;
 
-				if (tuphdr->t_infomask & HEAP_HASOID_OLD)
-					values[12] = ObjectIdGetDatum(HeapTupleHeaderGetOidOld(tuphdr));
-				else
-					nulls[12] = true;
-
 				/* Copy raw tuple data into bytea attribute */
 				tuple_data_len = lp_len - tuphdr->t_hoff;
 				tuple_data_bytea = (bytea *) palloc(tuple_data_len + VARHDRSZ);
@@ -261,13 +241,12 @@ heap_page_items(PG_FUNCTION_ARGS)
 					memcpy(VARDATA(tuple_data_bytea),
 						   (char *) tuphdr + tuphdr->t_hoff,
 						   tuple_data_len);
-				values[13] = PointerGetDatum(tuple_data_bytea);
+				values[12] = PointerGetDatum(tuple_data_bytea);
 			}
 			else
 			{
 				nulls[11] = true;
 				nulls[12] = true;
-				nulls[13] = true;
 			}
 		}
 		else
@@ -278,10 +257,24 @@ heap_page_items(PG_FUNCTION_ARGS)
 			 */
 			int			i;
 
-			for (i = 4; i <= 13; i++)
+			for (i = 4; i <= 12; i++)
 				nulls[i] = true;
 		}
 
+		/*
+		 * Before 1.14 column 13 was t_oid.  It is always NULL now.  1.4 to
+		 * 1.13 also have t_data after it.
+		 */
+		if (TupleDescAttr(inter_call_data->tupd, 12)->atttypid == OIDOID)
+		{
+			if (inter_call_data->tupd->natts == 14)
+			{
+				values[13] = values[12];
+				nulls[13] = nulls[12];
+			}
+			nulls[12] = true;
+		}
+
 		/* Build and return the result tuple. */
 		resultTuple = heap_form_tuple(inter_call_data->tupd, values, nulls);
 		result = HeapTupleGetDatum(resultTuple);
@@ -555,8 +548,6 @@ heap_tuple_infomask_flags(PG_FUNCTION_ARGS)
 		flags[cnt++] = CStringGetTextDatum("HEAP_HASVARWIDTH");
 	if ((t_infomask & HEAP_HASEXTERNAL) != 0)
 		flags[cnt++] = CStringGetTextDatum("HEAP_HASEXTERNAL");
-	if ((t_infomask & HEAP_HASOID_OLD) != 0)
-		flags[cnt++] = CStringGetTextDatum("HEAP_HASOID_OLD");
 	if ((t_infomask & HEAP_XMAX_KEYSHR_LOCK) != 0)
 		flags[cnt++] = CStringGetTextDatum("HEAP_XMAX_KEYSHR_LOCK");
 	if ((t_infomask & HEAP_COMBOCID) != 0)
diff --git a/contrib/pageinspect/meson.build b/contrib/pageinspect/meson.build
index c43ea400a4d..2f333635838 100644
--- a/contrib/pageinspect/meson.build
+++ b/contrib/pageinspect/meson.build
@@ -38,6 +38,7 @@ install_data(
   'pageinspect--1.10--1.11.sql',
   'pageinspect--1.11--1.12.sql',
   'pageinspect--1.12--1.13.sql',
+  'pageinspect--1.13--1.14.sql',
   'pageinspect.control',
   kwargs: contrib_data_args,
 )
diff --git a/contrib/pageinspect/pageinspect--1.13--1.14.sql b/contrib/pageinspect/pageinspect--1.13--1.14.sql
new file mode 100644
index 00000000000..01b1519f5a2
--- /dev/null
+++ b/contrib/pageinspect/pageinspect--1.13--1.14.sql
@@ -0,0 +1,93 @@
+/* contrib/pageinspect/pageinspect--1.13--1.14.sql */
+
+-- complain if script is sourced in psql, rather than via ALTER EXTENSION
+\echo Use "ALTER EXTENSION pageinspect UPDATE TO '1.14'" to load this file. \quit
+
+-- Remove the t_oid column, tuples no longer have an oid field
+
+DROP FUNCTION heap_page_item_attrs(bytea, regclass);
+DROP FUNCTION heap_page_item_attrs(bytea, regclass, bool);
+DROP FUNCTION heap_page_items(bytea);
+
+CREATE FUNCTION heap_page_items(IN page bytea,
+    OUT lp smallint,
+    OUT lp_off smallint,
+    OUT lp_flags smallint,
+    OUT lp_len smallint,
+    OUT t_xmin xid,
+    OUT t_xmax xid,
+    OUT t_field3 int4,
+    OUT t_ctid tid,
+    OUT t_infomask2 integer,
+    OUT t_infomask integer,
+    OUT t_hoff smallint,
+    OUT t_bits text,
+    OUT t_data bytea)
+RETURNS SETOF record
+AS 'MODULE_PATHNAME', 'heap_page_items'
+LANGUAGE C STRICT PARALLEL SAFE;
+
+CREATE FUNCTION heap_page_item_attrs(
+    IN page bytea,
+    IN rel_oid regclass,
+    IN do_detoast bool,
+    OUT lp smallint,
+    OUT lp_off smallint,
+    OUT lp_flags smallint,
+    OUT lp_len smallint,
+    OUT t_xmin xid,
+    OUT t_xmax xid,
+    OUT t_field3 int4,
+    OUT t_ctid tid,
+    OUT t_infomask2 integer,
+    OUT t_infomask integer,
+    OUT t_hoff smallint,
+    OUT t_bits text,
+    OUT t_attrs bytea[]
+    )
+RETURNS SETOF record
+LANGUAGE SQL PARALLEL RESTRICTED
+BEGIN ATOMIC
+SELECT lp,
+       lp_off,
+       lp_flags,
+       lp_len,
+       t_xmin,
+       t_xmax,
+       t_field3,
+       t_ctid,
+       t_infomask2,
+       t_infomask,
+       t_hoff,
+       t_bits,
+       tuple_data_split(
+         rel_oid::oid,
+         t_data,
+         t_infomask,
+         t_infomask2,
+         t_bits,
+         do_detoast)
+         AS t_attrs
+  FROM heap_page_items(page);
+END;
+
+CREATE FUNCTION heap_page_item_attrs(IN page bytea, IN rel_oid regclass,
+    OUT lp smallint,
+    OUT lp_off smallint,
+    OUT lp_flags smallint,
+    OUT lp_len smallint,
+    OUT t_xmin xid,
+    OUT t_xmax xid,
+    OUT t_field3 int4,
+    OUT t_ctid tid,
+    OUT t_infomask2 integer,
+    OUT t_infomask integer,
+    OUT t_hoff smallint,
+    OUT t_bits text,
+    OUT t_attrs bytea[]
+    )
+RETURNS SETOF record
+LANGUAGE SQL PARALLEL RESTRICTED
+BEGIN ATOMIC
+SELECT * FROM heap_page_item_attrs(page, rel_oid, false);
+END;
diff --git a/contrib/pageinspect/pageinspect.control b/contrib/pageinspect/pageinspect.control
index cfc87feac03..aee3f598a9e 100644
--- a/contrib/pageinspect/pageinspect.control
+++ b/contrib/pageinspect/pageinspect.control
@@ -1,5 +1,5 @@
 # pageinspect extension
 comment = 'inspect the contents of database pages at a low level'
-default_version = '1.13'
+default_version = '1.14'
 module_pathname = '$libdir/pageinspect'
 relocatable = true
diff --git a/contrib/pageinspect/sql/oldextversions.sql b/contrib/pageinspect/sql/oldextversions.sql
index 44f564faec9..a250faf2f56 100644
--- a/contrib/pageinspect/sql/oldextversions.sql
+++ b/contrib/pageinspect/sql/oldextversions.sql
@@ -31,5 +31,17 @@ SELECT * FROM brin_page_items(get_raw_page('test_1_a_brin_idx', 2), 'test_1_a_br
 ALTER EXTENSION pageinspect UPDATE TO '1.12';
 SELECT * FROM brin_page_items(get_raw_page('test_1_a_brin_idx', 2), 'test_1_a_brin_idx');
 
+-- heap_page_items() lost its t_oid column in 1.14, make sure we detect
+-- an old function definition
+ALTER EXTENSION pageinspect UPDATE TO '1.13';
+INSERT INTO test1 VALUES (1, 'x'), (2, 'y');
+DELETE FROM test1 WHERE a = 1;
+VACUUM test1;
+SELECT lp, lp_flags, t_oid, t_data FROM heap_page_items(get_raw_page('test1', 0));
+SELECT lp, t_oid, t_attrs FROM heap_page_item_attrs(get_raw_page('test1', 0), 'test1');
+
+ALTER EXTENSION pageinspect UPDATE TO '1.14';
+SELECT lp, t_data FROM heap_page_items(get_raw_page('test1', 0));
+
 DROP TABLE test1;
 DROP EXTENSION pageinspect;
diff --git a/doc/src/sgml/storage.sgml b/doc/src/sgml/storage.sgml
index 83de016eaa5..397c0a39ea1 100644
--- a/doc/src/sgml/storage.sgml
+++ b/doc/src/sgml/storage.sgml
@@ -959,7 +959,7 @@ data. Empty in ordinary tables.</entry>
 
   All table rows are structured in the same way. There is a fixed-size
   header (occupying 23 bytes on most machines), followed by an optional null
-  bitmap, an optional object ID field, and the user data. The header is
+  bitmap and the user data. The header is
   detailed
   in <xref linkend="heaptupleheaderdata-table"/>.  The actual user data
   (columns of the row) begins at the offset indicated by
@@ -973,12 +973,6 @@ data. Empty in ordinary tables.</entry>
   <structfield>t_infomask2</structfield>). In this list of bits, a
   1 bit indicates not-null, a 0 bit is a null.  When the bitmap is not
   present, all columns are assumed not-null.
-  The object ID is only present if the <firstterm>HEAP_HASOID_OLD</firstterm> bit
-  is set in <structfield>t_infomask</structfield>.  If present, it appears just
-  before the <structfield>t_hoff</structfield> boundary.  Any padding needed to make
-  <structfield>t_hoff</structfield> a MAXALIGN multiple will appear between the null
-  bitmap and the object ID.  (This in turn ensures that the object ID is
-  suitably aligned.)
 
  </para>
 
diff --git a/src/include/access/htup_details.h b/src/include/access/htup_details.h
index 77a6c48fd71..7d9c84a45ad 100644
--- a/src/include/access/htup_details.h
+++ b/src/include/access/htup_details.h
@@ -66,8 +66,6 @@
  *			fixed fields (HeapTupleHeaderData struct)
  *			nulls bitmap (if HEAP_HASNULL is set in t_infomask)
  *			alignment padding (as needed to make user data MAXALIGN'd)
- *			object ID (if HEAP_HASOID_OLD is set in t_infomask, not created
- *          anymore)
  *			user data fields
  *
  * We store five "virtual" fields Xmin, Cmin, Xmax, Cmax, and Xvac in three
@@ -190,7 +188,7 @@ struct HeapTupleHeaderData
 #define HEAP_HASNULL			0x0001	/* has null attribute(s) */
 #define HEAP_HASVARWIDTH		0x0002	/* has variable-width attribute(s) */
 #define HEAP_HASEXTERNAL		0x0004	/* has external stored attribute(s) */
-#define HEAP_HASOID_OLD			0x0008	/* has an object-id field */
+/* 0x0008 was HEAP_HASOID_OLD, don't reuse, pre-8.4 rows may have it */
 #define HEAP_XMAX_KEYSHR_LOCK	0x0010	/* xmax is a key-shared locker */
 #define HEAP_COMBOCID			0x0020	/* t_cid is a combo CID */
 #define HEAP_XMAX_EXCL_LOCK		0x0040	/* xmax is exclusive locker */
-- 
2.37.1 (Apple Git-137.1)

