Subject: create table like including storage parameter

From: jian he <[email-masked]>
Date: Mon, 29 Sep 2025 08:00:00 +0800

hi.

attached patch is to make CREATE TABLE LIKE copy source relation
storage parameter
demo:

create table t(a text) with (fillfactor = 100, toast.vacuum_truncate=true);
create table t2(like t including storage parameter) with (parallel_workers = 3);

                                          Table "public.t2"
 Column | Type | Collation | Nullable | Default | Storage  |
Compression | Stats target | Description
--------+------+-----------+----------+---------+----------+-------------+--------------+-------------
 a      | text |           |          |         | extended |
  |              |
Access method: heap
Options: parallel_workers=3, fillfactor=100, toast.vacuum_truncate=true

of course, duplicate storage mention would result error,
for example:
create table t3(like t including storage parameter) with (fillfactor = 100);

Since we already support INCLUDING STORAGE, I’m not sure that
INCLUDING STORAGE PARAMETER is the right syntax to go with.
From a497b0309772b7c202d54b65f9c96b655ca40179 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Sun, 28 Sep 2025 19:20:00 +0800
Subject: [PATCH v1 1/1] create table like incluing storage parameter

demo:
create table t(a text) with (fillfactor = 100, toast.vacuum_truncate=true);
create table t2(like t including storage parameter);

                                          Table "public.t2"
 Column | Type | Collation | Nullable | Default | Storage  | Compression | Stats target | Description
--------+------+-----------+----------+---------+----------+-------------+--------------+-------------
 a      | text |           |          |         | extended |             |              |
Access method: heap
Options: fillfactor=100, toast.vacuum_truncate=true

discussion: https://postgr.es/m/
---
 doc/src/sgml/ref/create_table.sgml            | 19 +++++-
 src/backend/access/common/reloptions.c        | 49 +++++++++++++++
 src/backend/parser/gram.y                     |  1 +
 src/backend/parser/parse_utilcmd.c            | 63 +++++++++++++++++++
 src/include/access/reloptions.h               |  1 +
 src/include/nodes/parsenodes.h                |  1 +
 .../regress/expected/create_table_like.out    | 39 ++++++++++++
 src/test/regress/sql/create_table_like.sql    | 28 +++++++++
 8 files changed, 200 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index dc000e913c1..a389ce597a9 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -88,7 +88,7 @@ class="parameter">referential_action</replaceable> ] [ ON UPDATE <replaceable cl
 
 <phrase>and <replaceable class="parameter">like_option</replaceable> is:</phrase>
 
-{ INCLUDING | EXCLUDING } { COMMENTS | COMPRESSION | CONSTRAINTS | DEFAULTS | GENERATED | IDENTITY | INDEXES | STATISTICS | STORAGE | ALL }
+{ INCLUDING | EXCLUDING } { COMMENTS | COMPRESSION | CONSTRAINTS | DEFAULTS | GENERATED | IDENTITY | INDEXES | STATISTICS | STORAGE | STORAGE PARAMETER | ALL }
 
 <phrase>and <replaceable class="parameter">partition_bound_spec</replaceable> is:</phrase>
 
@@ -776,6 +776,23 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
         </listitem>
        </varlistentry>
 
+       <varlistentry id="sql-createtable-parms-like-opt-storage-paramter">
+        <term><literal>INCLUDING STORAGE PARAMETER</literal></term>
+        <listitem>
+         <para>
+          <literal>STORAGE PARAMETER</literal> settings for the copied table will be copied.
+          For table storage parameters, see <xref linkend="sql-createtable-storage-parameters"/> below for more information.
+         </para>
+
+        <para>
+          Do not confuse this with <literal>INCLUDING STORAGE</literal>.
+          <literal>INCLUDING STORAGE</literal> is for copying table column storage property,
+          this is for copying table storage parameters.
+        </para>
+
+        </listitem>
+       </varlistentry>
+
        <varlistentry id="sql-createtable-parms-like-opt-all">
         <term><literal>INCLUDING ALL</literal></term>
         <listitem>
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 0af3fea68fa..745a71d5192 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -1383,6 +1383,55 @@ untransformRelOptions(Datum options)
 	return result;
 }
 
+/*
+ * Convert the reloptions from text-array format into a List of DefElem.  This
+ * is the reverse operation of transformRelOptions(). If any option includes a
+ * namespace qualification, create the DefElem in that namespace.
+ * If nameSpace is NULL, this behave the same as untransformRelOptions.
+ */
+List *
+untransformRelOptionsExtended(Datum options, char* nameSpace)
+{
+	List	   *result = NIL;
+	ArrayType  *array;
+	Datum	   *optiondatums;
+	int			noptions;
+	int			i;
+
+	/* Nothing to do if no options */
+	if (!PointerIsValid(DatumGetPointer(options)))
+		return result;
+
+	array = DatumGetArrayTypeP(options);
+
+	deconstruct_array_builtin(array, TEXTOID, &optiondatums, NULL, &noptions);
+
+	for (i = 0; i < noptions; i++)
+	{
+		char	   *s;
+		char	   *p;
+		Node	   *val = NULL;
+
+		s = TextDatumGetCString(optiondatums[i]);
+		p = strchr(s, '=');
+		if (p)
+		{
+			*p++ = '\0';
+			val = (Node *) makeString(p);
+		}
+
+		if (nameSpace == NULL)
+			result = lappend(result, makeDefElem(s, val, -1));
+		else
+			result = lappend(result, makeDefElemExtended(nameSpace, s, val,
+														 DEFELEM_UNSPEC,
+														 -1));
+	}
+
+	return result;
+}
+
+
 /*
  * Extract and parse reloptions from a pg_class tuple.
  *
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 9fd48acb1f8..baa9108ac43 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -4216,6 +4216,7 @@ TableLikeOption:
 				| INDEXES			{ $$ = CREATE_TABLE_LIKE_INDEXES; }
 				| STATISTICS		{ $$ = CREATE_TABLE_LIKE_STATISTICS; }
 				| STORAGE			{ $$ = CREATE_TABLE_LIKE_STORAGE; }
+				| STORAGE PARAMETER	{ $$ = CREATE_TABLE_LIKE_STORAGE_PARAMETER; }
 				| ALL				{ $$ = CREATE_TABLE_LIKE_ALL; }
 		;
 
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..bd401cba5e4 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -85,6 +85,7 @@ typedef struct
 	List	   *fkconstraints;	/* FOREIGN KEY constraints */
 	List	   *ixconstraints;	/* index-creating constraints */
 	List	   *likeclauses;	/* LIKE clauses that need post-processing */
+	List	   *options;		/* options from WITH clause, table AM specific parameters */
 	List	   *blist;			/* "before list" of things to do before
 								 * creating the table */
 	List	   *alist;			/* "after list" of things to do after creating
@@ -245,6 +246,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 	cxt.fkconstraints = NIL;
 	cxt.ixconstraints = NIL;
 	cxt.likeclauses = NIL;
+	cxt.options = stmt->options;
 	cxt.blist = NIL;
 	cxt.alist = NIL;
 	cxt.pkey = NULL;
@@ -369,6 +371,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 	stmt->tableElts = cxt.columns;
 	stmt->constraints = cxt.ckconstraints;
 	stmt->nnconstraints = cxt.nnconstraints;
+	stmt->options = cxt.options;
 
 	result = lappend(cxt.blist, stmt);
 	result = list_concat(result, cxt.alist);
@@ -1268,6 +1271,65 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
 		}
 	}
 
+	/* Likewise, copy storage paramter if requested */
+	if ((table_like_clause->options & CREATE_TABLE_LIKE_STORAGE_PARAMETER) &&
+		!cxt->isforeign)
+	{
+		HeapTuple	tuple;
+		Datum		reloptions;
+		bool		isnull;
+		Oid			relid;
+		List		*oldoptions = NIL;
+		List		*oldtoastoptions = NIL;
+
+		relid = RelationGetRelid(relation);
+		tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
+		if (!HeapTupleIsValid(tuple))
+			elog(ERROR, "cache lookup failed for relation %u", relid);
+
+		reloptions = SysCacheGetAttr(RELOID, tuple,
+									 Anum_pg_class_reloptions, &isnull);
+
+		if (!isnull)
+		{
+			oldoptions = untransformRelOptions(reloptions);
+
+			foreach_node(DefElem, option, oldoptions)
+				cxt->options = lappend(cxt->options, option);
+		}
+
+		ReleaseSysCache(tuple);
+
+		/* now get the source relation toast relation reloptions */
+		if (OidIsValid(relation->rd_rel->reltoastrelid))
+		{
+			Relation	toastrel;
+
+			Oid			toastid = relation->rd_rel->reltoastrelid;
+
+			toastrel = table_open(toastid, AccessShareLock);
+
+			/* Fetch heap tuple */
+			tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(toastid));
+			if (!HeapTupleIsValid(tuple))
+				elog(ERROR, "cache lookup failed for toast relation %u", toastid);
+
+			/* Get the toast reloptions */
+			reloptions = SysCacheGetAttr(RELOID, tuple, Anum_pg_class_reloptions,
+										 &isnull);
+			if (!isnull)
+			{
+				oldtoastoptions = untransformRelOptionsExtended(reloptions, "toast");
+				foreach_node(DefElem, option, oldtoastoptions)
+					cxt->options = lappend(cxt->options, option);
+			}
+
+			ReleaseSysCache(tuple);
+
+			table_close(toastrel, NoLock);
+		}
+	}
+
 	/*
 	 * Reproduce not-null constraints, if any, by copying them.  We do this
 	 * regardless of options given.
@@ -3575,6 +3637,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 	cxt.fkconstraints = NIL;
 	cxt.ixconstraints = NIL;
 	cxt.likeclauses = NIL;
+	cxt.options = NIL;
 	cxt.blist = NIL;
 	cxt.alist = NIL;
 	cxt.pkey = NULL;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..8589616d08b 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -236,6 +236,7 @@ extern Datum transformRelOptions(Datum oldOptions, List *defList,
 								 const char *nameSpace, const char *const validnsps[],
 								 bool acceptOidsOff, bool isReset);
 extern List *untransformRelOptions(Datum options);
+extern List *untransformRelOptionsExtended(Datum options, char* nameSpace);
 extern bytea *extractRelOptions(HeapTuple tuple, TupleDesc tupdesc,
 								amoptions_function amoptions);
 extern void *build_reloptions(Datum reloptions, bool validate,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 86a236bd58b..97dbb70dfc0 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -793,6 +793,7 @@ typedef enum TableLikeOption
 	CREATE_TABLE_LIKE_INDEXES = 1 << 6,
 	CREATE_TABLE_LIKE_STATISTICS = 1 << 7,
 	CREATE_TABLE_LIKE_STORAGE = 1 << 8,
+	CREATE_TABLE_LIKE_STORAGE_PARAMETER = 1 << 9,
 	CREATE_TABLE_LIKE_ALL = PG_INT32_MAX
 } TableLikeOption;
 
diff --git a/src/test/regress/expected/create_table_like.out b/src/test/regress/expected/create_table_like.out
index d3c35c14847..193771f3408 100644
--- a/src/test/regress/expected/create_table_like.out
+++ b/src/test/regress/expected/create_table_like.out
@@ -699,3 +699,42 @@ DROP FOREIGN TABLE ctl_foreign_table1;
 DROP FOREIGN TABLE ctl_foreign_table2;
 DROP FOREIGN DATA WRAPPER ctl_dummy CASCADE;
 NOTICE:  drop cascades to server ctl_s0
+--CREATE TABLE LIKE with STORAGE PARAMETER
+create table t(a text) with (
+    fillfactor = 100,
+    toast_tuple_target = 128,
+    vacuum_index_cleanup = auto,
+    toast.vacuum_index_cleanup = auto,
+    vacuum_truncate = true,
+    toast.vacuum_truncate = false,
+    log_autovacuum_min_duration = 100,
+    toast.log_autovacuum_min_duration = 100);
+create table t1(like t including storage parameter) with (fillfactor = 100); --error
+ERROR:  parameter "fillfactor" specified more than once
+create table t1(like t excluding storage parameter) with (fillfactor = 100); --ok
+\d+ t1
+                                   Table "public.t1"
+ Column | Type | Collation | Nullable | Default | Storage  | Stats target | Description 
+--------+------+-----------+----------+---------+----------+--------------+-------------
+ a      | text |           |          |         | extended |              | 
+Options: fillfactor=100
+
+create table t2(like t including storage parameter) with (
+  parallel_workers = 3,
+  toast.autovacuum_vacuum_threshold = 101,
+  toast.autovacuum_vacuum_scale_factor = 0.3);
+select    c.relname, c.reloptions, tc.reloptions as toast_options
+from      pg_catalog.pg_class c
+left join pg_catalog.pg_class tc ON (c.reltoastrelid = tc.oid)
+where     c.relname in ('t', 't2')
+order by  c.relname \gx
+-[ RECORD 1 ]-+-----------------------------------------------------------------------------------------------------------------------------------------------------
+relname       | t
+reloptions    | {fillfactor=100,toast_tuple_target=128,vacuum_index_cleanup=auto,vacuum_truncate=true,log_autovacuum_min_duration=100}
+toast_options | {vacuum_index_cleanup=auto,vacuum_truncate=false,log_autovacuum_min_duration=100}
+-[ RECORD 2 ]-+-----------------------------------------------------------------------------------------------------------------------------------------------------
+relname       | t2
+reloptions    | {parallel_workers=3,fillfactor=100,toast_tuple_target=128,vacuum_index_cleanup=auto,vacuum_truncate=true,log_autovacuum_min_duration=100}
+toast_options | {autovacuum_vacuum_threshold=101,autovacuum_vacuum_scale_factor=0.3,vacuum_index_cleanup=auto,vacuum_truncate=false,log_autovacuum_min_duration=100}
+
+drop table t, t1, t2;
diff --git a/src/test/regress/sql/create_table_like.sql b/src/test/regress/sql/create_table_like.sql
index 93389b57dbf..47f67d244e2 100644
--- a/src/test/regress/sql/create_table_like.sql
+++ b/src/test/regress/sql/create_table_like.sql
@@ -280,3 +280,31 @@ DROP TABLE ctl_table;
 DROP FOREIGN TABLE ctl_foreign_table1;
 DROP FOREIGN TABLE ctl_foreign_table2;
 DROP FOREIGN DATA WRAPPER ctl_dummy CASCADE;
+
+--CREATE TABLE LIKE with STORAGE PARAMETER
+create table t(a text) with (
+    fillfactor = 100,
+    toast_tuple_target = 128,
+    vacuum_index_cleanup = auto,
+    toast.vacuum_index_cleanup = auto,
+    vacuum_truncate = true,
+    toast.vacuum_truncate = false,
+    log_autovacuum_min_duration = 100,
+    toast.log_autovacuum_min_duration = 100);
+
+create table t1(like t including storage parameter) with (fillfactor = 100); --error
+create table t1(like t excluding storage parameter) with (fillfactor = 100); --ok
+\d+ t1
+
+create table t2(like t including storage parameter) with (
+  parallel_workers = 3,
+  toast.autovacuum_vacuum_threshold = 101,
+  toast.autovacuum_vacuum_scale_factor = 0.3);
+
+select    c.relname, c.reloptions, tc.reloptions as toast_options
+from      pg_catalog.pg_class c
+left join pg_catalog.pg_class tc ON (c.reltoastrelid = tc.oid)
+where     c.relname in ('t', 't2')
+order by  c.relname \gx
+
+drop table t, t1, t2;
-- 
2.34.1

---

From: "David G. Johnston" <[email-masked]>
Date: Sun, 28 Sep 2025 20:28:45 -0500

> INCLUDING STORAGE PARAMETER is the right syntax to go with.

I’d just call it “including parameters”; sure, all of the existing ones are
storage related, but if there were non-storage ones we’d include those as
well, so qualifying with a parameter type doesn’t seem correct.

David J.

---

From: jian he <[email-masked]>
Date: Wed, 1 Oct 2025 14:42:29 +0800

On Mon, Sep 29, 2025 at 9:28 AM David G. Johnston
<[email-masked]> wrote:
> I’d just call it “including parameters”; sure, all of the existing ones are storage related, but if there were non-storage ones we’d include those as well, so qualifying with a parameter type doesn’t seem correct.
>

“including parameters” would add another keyword "PARAMETERS''.
currently "INCLUDING STORAGE PARAMETER" no need to add a new keyword.
If other people favor “including parameters”, then we can do it that
way in the future.

attached is just a simple rebase of v1.

---

From: jian he <[email-masked]>
Date: Sat, 11 Oct 2025 13:08:08 +0800

> attached is just a simple rebase of v1.

hi.
https://api.cirrus-ci.com/v1/artifact/task/5202996067303424/testrun/build/testrun/regress/regress/regression.diffs

+ERROR:  relation "t" already exists
I made the same mistake, regress test using a simple name like "t" will conflict
with other regress tests.

please check the attached, mainly regress tests changes.
From 58e527e08e0673683295465e07e049d41b29d79e Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Sat, 11 Oct 2025 11:03:51 +0800
Subject: [PATCH v3 1/1] create table like incluing storage parameter

demo:

                                          Table "public.t2"

discussion: https://postgr.es/m/CACJufxHr=nKEsS66M7rTHgB+mXdQ948=[email-masked]
commitfest entry: https://commitfest.postgresql.org/patch/6088
---

 

 
index 35150bf237b..6032cd5813e 100644
 
+/*
+	if (DatumGetPointer(options) == NULL)
index 21caf2d43bf..ef8498ddb23 100644
@@ -4223,6 +4223,7 @@ TableLikeOption:

 
index dc09d1a3f03..4f9b8e3e381 100644
@@ -795,6 +795,7 @@ typedef enum TableLikeOption
 
diff --git a/src/test/regress/expected/create_table_like.out b/src/test/regress/expected/create_table_like.out
index d3c35c14847..b8336de3343 100644
--- a/src/test/regress/expected/create_table_like.out
+++ b/src/test/regress/expected/create_table_like.out
@@ -699,3 +699,42 @@ DROP FOREIGN TABLE ctl_foreign_table1;
 DROP FOREIGN TABLE ctl_foreign_table2;
 DROP FOREIGN DATA WRAPPER ctl_dummy CASCADE;
 NOTICE:  drop cascades to server ctl_s0
+--CREATE TABLE LIKE with STORAGE PARAMETER
+create table t_storage(a text) with (
+    fillfactor = 100,
+    toast_tuple_target = 128,
+    vacuum_index_cleanup = auto,
+    toast.vacuum_index_cleanup = auto,
+    vacuum_truncate = true,
+    toast.vacuum_truncate = false,
+    log_autovacuum_min_duration = 100,
+    toast.log_autovacuum_min_duration = 100);
+create table t1(like t_storage including storage parameter) with (fillfactor = 100); --error
+ERROR:  parameter "fillfactor" specified more than once
+create table t_storage1(like t_storage excluding storage parameter) with (fillfactor = 100); --ok
+\d+ t_storage1
+                               Table "public.t_storage1"
+ Column | Type | Collation | Nullable | Default | Storage  | Stats target | Description 
+--------+------+-----------+----------+---------+----------+--------------+-------------
+ a      | text |           |          |         | extended |              | 
+Options: fillfactor=100
+
+create table t_storage2(like t_storage including storage parameter) with (
+  parallel_workers = 3,
+  toast.autovacuum_vacuum_threshold = 101,
+  toast.autovacuum_vacuum_scale_factor = 0.3);
+select    c.relname, c.reloptions, tc.reloptions as toast_options
+from      pg_catalog.pg_class c
+left join pg_catalog.pg_class tc ON (c.reltoastrelid = tc.oid)
+where     c.relname in ('t_storage', 't_storage2')
+order by  c.relname \gx
+-[ RECORD 1 ]-+-----------------------------------------------------------------------------------------------------------------------------------------------------
+relname       | t_storage
+reloptions    | {fillfactor=100,toast_tuple_target=128,vacuum_index_cleanup=auto,vacuum_truncate=true,log_autovacuum_min_duration=100}
+toast_options | {vacuum_index_cleanup=auto,vacuum_truncate=false,log_autovacuum_min_duration=100}
+-[ RECORD 2 ]-+-----------------------------------------------------------------------------------------------------------------------------------------------------
+relname       | t_storage2
+reloptions    | {parallel_workers=3,fillfactor=100,toast_tuple_target=128,vacuum_index_cleanup=auto,vacuum_truncate=true,log_autovacuum_min_duration=100}
+toast_options | {autovacuum_vacuum_threshold=101,autovacuum_vacuum_scale_factor=0.3,vacuum_index_cleanup=auto,vacuum_truncate=false,log_autovacuum_min_duration=100}
+
+drop table t_storage, t_storage1, t_storage2;
diff --git a/src/test/regress/sql/create_table_like.sql b/src/test/regress/sql/create_table_like.sql
index 93389b57dbf..dddaa80b846 100644
--- a/src/test/regress/sql/create_table_like.sql
+++ b/src/test/regress/sql/create_table_like.sql
@@ -280,3 +280,31 @@ DROP TABLE ctl_table;
 DROP FOREIGN TABLE ctl_foreign_table1;
 DROP FOREIGN TABLE ctl_foreign_table2;
 DROP FOREIGN DATA WRAPPER ctl_dummy CASCADE;
+
+--CREATE TABLE LIKE with STORAGE PARAMETER
+create table t_storage(a text) with (
+    fillfactor = 100,
+    toast_tuple_target = 128,
+    vacuum_index_cleanup = auto,
+    toast.vacuum_index_cleanup = auto,
+    vacuum_truncate = true,
+    toast.vacuum_truncate = false,
+    log_autovacuum_min_duration = 100,
+    toast.log_autovacuum_min_duration = 100);
+
+create table t1(like t_storage including storage parameter) with (fillfactor = 100); --error
+create table t_storage1(like t_storage excluding storage parameter) with (fillfactor = 100); --ok
+\d+ t_storage1
+
+create table t_storage2(like t_storage including storage parameter) with (
+  parallel_workers = 3,
+  toast.autovacuum_vacuum_threshold = 101,
+  toast.autovacuum_vacuum_scale_factor = 0.3);
+
+select    c.relname, c.reloptions, tc.reloptions as toast_options
+from      pg_catalog.pg_class c
+left join pg_catalog.pg_class tc ON (c.reltoastrelid = tc.oid)
+where     c.relname in ('t_storage', 't_storage2')
+order by  c.relname \gx
+
+drop table t_storage, t_storage1, t_storage2;
-- 
2.34.1

---

From: Nathan Bossart <[email-masked]>
Date: Fri, 24 Oct 2025 12:48:31 -0500

> well, so qualifying with a parameter type doesn’t seem correct.

+1

-- 
nathan

---

From: jian he <[email-masked]>
Date: Thu, 30 Oct 2025 09:57:24 +0800

hi.

attached patch using syntax:
CREATE TABLE LIKE INCLUDING PARAMETERS.
From 16dd551ad89c999d5e4ec654e20df7ac62df6a2d Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Thu, 30 Oct 2025 09:53:22 +0800
Subject: [PATCH v4 1/1] create table like incluing parameters

demo:
create table t(a text) with (fillfactor = 100, toast.vacuum_truncate=true);
create table t2(like t including parameters);
\d+ t2

                                          Table "public.t2"

discussion: https://postgr.es/m/CACJufxHr=nKEsS66M7rTHgB+mXdQ948=[email-masked]
---
 doc/src/sgml/ref/create_table.sgml            | 12 +++-
 src/backend/access/common/reloptions.c        | 50 +++++++++++++++
 src/backend/parser/gram.y                     |  5 +-
 src/backend/parser/parse_utilcmd.c            | 64 +++++++++++++++++++
 src/include/access/reloptions.h               |  1 +
 src/include/nodes/parsenodes.h                |  1 +
 src/include/parser/kwlist.h                   |  1 +
 .../regress/expected/create_table_like.out    | 39 +++++++++++
 src/test/regress/sql/create_table_like.sql    | 28 ++++++++
 9 files changed, 199 insertions(+), 2 deletions(-)

index a157a244e4e..cad839b452d 100644

-{ INCLUDING | EXCLUDING } { COMMENTS | COMPRESSION | CONSTRAINTS | DEFAULTS | GENERATED | IDENTITY | INDEXES | STATISTICS | STORAGE | ALL }
+{ INCLUDING | EXCLUDING } { COMMENTS | COMPRESSION | CONSTRAINTS | DEFAULTS | GENERATED | IDENTITY | INDEXES | STATISTICS | STORAGE | PARAMETERS | ALL }

@@ -753,6 +753,16 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
         </listitem>
        </varlistentry>
 
+       <varlistentry id="sql-createtable-parms-like-opt-storage-paramter">
+        <term><literal>INCLUDING PARAMETERS</literal></term>
+        <listitem>
+         <para>
+          All parameters, such as the <literal>STORAGE PARAMETER</literal> settings of the source table, will be copied.
+          For table storage parameters, see <xref linkend="sql-createtable-storage-parameters"/> below for more information.
+         </para>
+        </listitem>
+       </varlistentry>
+
        <varlistentry id="sql-createtable-parms-like-opt-statistics">
         <term><literal>INCLUDING STATISTICS</literal></term>
         <listitem>
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 9e288dfecbf..365f30402f0 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -1392,6 +1392,56 @@ untransformRelOptions(Datum options)
 	return result;
 }
 
+/*
+ * Convert the reloptions from text-array format into a List of DefElem.  This
+ * is the reverse operation of transformRelOptions().
+ *
+ * If any option includes a namespace qualification, create the DefElem in that
+ * namespace, otherwise this behave the same as untransformRelOptions.
+ */
+List *
+untransformRelOptionsExtended(Datum options, char* nameSpace)
+{
+	List	   *result = NIL;
+	ArrayType  *array;
+	Datum	   *optiondatums;
+	int			noptions;
+	int			i;
+
+	/* Nothing to do if no options */
+	if (DatumGetPointer(options) == NULL)
+		return result;
+
+	array = DatumGetArrayTypeP(options);
+
+	deconstruct_array_builtin(array, TEXTOID, &optiondatums, NULL, &noptions);
+
+	for (i = 0; i < noptions; i++)
+	{
+		char	   *s;
+		char	   *p;
+		Node	   *val = NULL;
+
+		s = TextDatumGetCString(optiondatums[i]);
+		p = strchr(s, '=');
+		if (p)
+		{
+			*p++ = '\0';
+			val = (Node *) makeString(p);
+		}
+
+		if (nameSpace == NULL)
+			result = lappend(result, makeDefElem(s, val, -1));
+		else
+			result = lappend(result, makeDefElemExtended(nameSpace, s, val,
+														 DEFELEM_UNSPEC,
+														 -1));
+	}
+
+	return result;
+}
+
+
 /*
  * Extract and parse reloptions from a pg_class tuple.
  *
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index a4b29c822e8..fe4b035fe34 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -762,7 +762,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	ORDER ORDINALITY OTHERS OUT_P OUTER_P
 	OVER OVERLAPS OVERLAY OVERRIDING OWNED OWNER
 
-	PARALLEL PARAMETER PARSER PARTIAL PARTITION PASSING PASSWORD PATH
+	PARALLEL PARAMETER PARAMETERS PARSER PARTIAL PARTITION PASSING PASSWORD PATH
 	PERIOD PLACING PLAN PLANS POLICY
 	POSITION PRECEDING PRECISION PRESERVE PREPARE PREPARED PRIMARY
 	PRIOR PRIVILEGES PROCEDURAL PROCEDURE PROCEDURES PROGRAM PUBLICATION
@@ -4223,6 +4223,7 @@ TableLikeOption:
 				| INDEXES			{ $$ = CREATE_TABLE_LIKE_INDEXES; }
 				| STATISTICS		{ $$ = CREATE_TABLE_LIKE_STATISTICS; }
 				| STORAGE			{ $$ = CREATE_TABLE_LIKE_STORAGE; }
+				| PARAMETERS 		{ $$ = CREATE_TABLE_LIKE_PARAMETERS; }
 				| ALL				{ $$ = CREATE_TABLE_LIKE_ALL; }
 		;
 
@@ -17994,6 +17995,7 @@ unreserved_keyword:
 			| OWNER
 			| PARALLEL
 			| PARAMETER
+			| PARAMETERS
 			| PARSER
 			| PARTIAL
 			| PARTITION
@@ -18622,6 +18624,7 @@ bare_label_keyword:
 			| OWNER
 			| PARALLEL
 			| PARAMETER
+			| PARAMETERS
 			| PARSER
 			| PARTIAL
 			| PARTITION
index e96b38a59d5..791ffc5150c 100644
 
 	result = lappend(cxt.blist, stmt);
 	result = list_concat(result, cxt.alist);
@@ -1268,6 +1271,66 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
 		}
 	}
 
+	/* Likewise, copy paramters if requested */
+	if ((table_like_clause->options & CREATE_TABLE_LIKE_PARAMETERS) &&
+		!cxt->isforeign)
+	{
+		HeapTuple	tuple;
+		Datum		reloptions;
+		bool		isnull;
+		Oid			relid;
+		/* get the toast relation reloptions */
@@ -3575,6 +3638,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
index ecbddd12e1b..a8e5538119c 100644
+	CREATE_TABLE_LIKE_PARAMETERS = 1 << 9,
 
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 84182eaaae2..d132eca0068 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -335,6 +335,7 @@ PG_KEYWORD("owned", OWNED, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("owner", OWNER, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("parallel", PARALLEL, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("parameter", PARAMETER, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("parameters", PARAMETERS, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("parser", PARSER, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("partial", PARTIAL, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("partition", PARTITION, UNRESERVED_KEYWORD, BARE_LABEL)
diff --git a/src/test/regress/expected/create_table_like.out b/src/test/regress/expected/create_table_like.out
index d3c35c14847..2e8962590a0 100644
--- a/src/test/regress/expected/create_table_like.out
+++ b/src/test/regress/expected/create_table_like.out
@@ -699,3 +699,42 @@ DROP FOREIGN TABLE ctl_foreign_table1;
 DROP FOREIGN TABLE ctl_foreign_table2;
 DROP FOREIGN DATA WRAPPER ctl_dummy CASCADE;
 NOTICE:  drop cascades to server ctl_s0
+--CREATE TABLE LIKE with PARAMETERS
+create table t_storage(a text) with (
+    fillfactor = 100,
+    toast_tuple_target = 128,
+    vacuum_index_cleanup = auto,
+    toast.vacuum_index_cleanup = auto,
+    vacuum_truncate = true,
+    toast.vacuum_truncate = false,
+    log_autovacuum_min_duration = 100,
+    toast.log_autovacuum_min_duration = 100);
+create table t1(like t_storage including parameters) with (fillfactor = 100); --error
+ERROR:  parameter "fillfactor" specified more than once
+create table t_storage1(like t_storage excluding parameters) with (fillfactor = 100); --ok
+\d+ t_storage1
+                               Table "public.t_storage1"
+ Column | Type | Collation | Nullable | Default | Storage  | Stats target | Description 
+--------+------+-----------+----------+---------+----------+--------------+-------------
+ a      | text |           |          |         | extended |              | 
+Options: fillfactor=100
+
+create table t_storage2(like t_storage including parameters) with (
+  parallel_workers = 3,
+  toast.autovacuum_vacuum_threshold = 101,
+  toast.autovacuum_vacuum_scale_factor = 0.3);
+select    c.relname, c.reloptions, tc.reloptions as toast_options
+from      pg_catalog.pg_class c
+left join pg_catalog.pg_class tc ON (c.reltoastrelid = tc.oid)
+where     c.relname in ('t_storage', 't_storage2')
+order by  c.relname \gx
+-[ RECORD 1 ]-+-----------------------------------------------------------------------------------------------------------------------------------------------------
+relname       | t_storage
+reloptions    | {fillfactor=100,toast_tuple_target=128,vacuum_index_cleanup=auto,vacuum_truncate=true,log_autovacuum_min_duration=100}
+toast_options | {vacuum_index_cleanup=auto,vacuum_truncate=false,log_autovacuum_min_duration=100}
+-[ RECORD 2 ]-+-----------------------------------------------------------------------------------------------------------------------------------------------------
+relname       | t_storage2
+reloptions    | {parallel_workers=3,fillfactor=100,toast_tuple_target=128,vacuum_index_cleanup=auto,vacuum_truncate=true,log_autovacuum_min_duration=100}
+toast_options | {autovacuum_vacuum_threshold=101,autovacuum_vacuum_scale_factor=0.3,vacuum_index_cleanup=auto,vacuum_truncate=false,log_autovacuum_min_duration=100}
+
+drop table t_storage, t_storage1, t_storage2;
diff --git a/src/test/regress/sql/create_table_like.sql b/src/test/regress/sql/create_table_like.sql
index 93389b57dbf..e1abf5a4262 100644
--- a/src/test/regress/sql/create_table_like.sql
+++ b/src/test/regress/sql/create_table_like.sql
@@ -280,3 +280,31 @@ DROP TABLE ctl_table;
 DROP FOREIGN TABLE ctl_foreign_table1;
 DROP FOREIGN TABLE ctl_foreign_table2;
 DROP FOREIGN DATA WRAPPER ctl_dummy CASCADE;
+
+--CREATE TABLE LIKE with PARAMETERS
+create table t_storage(a text) with (
+    fillfactor = 100,
+    toast_tuple_target = 128,
+    vacuum_index_cleanup = auto,
+    toast.vacuum_index_cleanup = auto,
+    vacuum_truncate = true,
+    toast.vacuum_truncate = false,
+    log_autovacuum_min_duration = 100,
+    toast.log_autovacuum_min_duration = 100);
+
+create table t1(like t_storage including parameters) with (fillfactor = 100); --error
+create table t_storage1(like t_storage excluding parameters) with (fillfactor = 100); --ok
+\d+ t_storage1
+
+create table t_storage2(like t_storage including parameters) with (
+  parallel_workers = 3,
+  toast.autovacuum_vacuum_threshold = 101,
+  toast.autovacuum_vacuum_scale_factor = 0.3);
+
+select    c.relname, c.reloptions, tc.reloptions as toast_options
+from      pg_catalog.pg_class c
+left join pg_catalog.pg_class tc ON (c.reltoastrelid = tc.oid)
+where     c.relname in ('t_storage', 't_storage2')
+order by  c.relname \gx
+
+drop table t_storage, t_storage1, t_storage2;
-- 
2.34.1

---

From: jian he <[email-masked]>
Date: Mon, 15 Dec 2025 16:48:12 +0800

> CREATE TABLE LIKE INCLUDING PARAMETERS.

hi.
rebased, with minor comment polishing.

--
jian
https://www.enterprisedb.com/
From f0226ac62a162d0cf6d0963ac1aa4cfd54363664 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Mon, 15 Dec 2025 16:41:59 +0800
Subject: [PATCH v5 1/1] create table like incluing parameters

demo:

commitfest: https://commitfest.postgresql.org/patch/6088
discussion: https://postgr.es/m/CACJufxHr=nKEsS66M7rTHgB+mXdQ948=[email-masked]
---
 doc/src/sgml/ref/create_table.sgml            | 14 +++-
 src/backend/access/common/reloptions.c        | 50 ++++++++++++
 src/backend/parser/gram.y                     |  5 +-
 src/backend/parser/parse_utilcmd.c            | 76 +++++++++++++++++++
 src/include/access/reloptions.h               |  1 +
 src/include/nodes/parsenodes.h                |  1 +
 src/include/parser/kwlist.h                   |  1 +
 .../regress/expected/create_table_like.out    | 39 ++++++++++
 src/test/regress/sql/create_table_like.sql    | 28 +++++++
 9 files changed, 213 insertions(+), 2 deletions(-)

index 77c5a763d45..d645ab4ed31 100644

 
 
@@ -753,6 +753,18 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
         </listitem>
        </varlistentry>
 
+       <varlistentry id="sql-createtable-parms-like-opt-storage-paramter">
+        <term><literal>INCLUDING PARAMETERS</literal></term>
+        <listitem>
+         <para>
+          All table parameters, such as the <literal>storage parameter</literal>
+          settings of the source table, will be copied.  For table storage
+          parameters, see <xref linkend="sql-createtable-storage-parameters"/>
+          below for more information.
+         </para>
+        </listitem>
+       </varlistentry>
+
        <varlistentry id="sql-createtable-parms-like-opt-statistics">
         <term><literal>INCLUDING STATISTICS</literal></term>
         <listitem>
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 31926d8a368..9289caef7ba 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -1392,6 +1392,56 @@ untransformRelOptions(Datum options)
 	return result;
 }
 
+/*
index 28f4e11e30f..732cbe8dbab 100644
@@ -765,7 +765,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 
-	PARALLEL PARAMETER PARSER PARTIAL PARTITION PARTITIONS PASSING PASSWORD PATH
+	PARALLEL PARAMETER PARAMETERS PARSER PARTIAL PARTITION PARTITIONS PASSING PASSWORD PATH
 	PERIOD PLACING PLAN PLANS POLICY
 	POSITION PRECEDING PRECISION PRESERVE PREPARE PREPARED PRIMARY
 	PRIOR PRIVILEGES PROCEDURAL PROCEDURE PROCEDURES PROGRAM PUBLICATION
@@ -4296,6 +4296,7 @@ TableLikeOption:
 				| INDEXES			{ $$ = CREATE_TABLE_LIKE_INDEXES; }
 				| STATISTICS		{ $$ = CREATE_TABLE_LIKE_STATISTICS; }
 				| STORAGE			{ $$ = CREATE_TABLE_LIKE_STORAGE; }
+				| PARAMETERS 		{ $$ = CREATE_TABLE_LIKE_PARAMETERS; }
 				| ALL				{ $$ = CREATE_TABLE_LIKE_ALL; }
 		;
 
@@ -18087,6 +18088,7 @@ unreserved_keyword:
 			| OWNER
 			| PARALLEL
 			| PARAMETER
+			| PARAMETERS
 			| PARSER
 			| PARTIAL
 			| PARTITION
@@ -18719,6 +18721,7 @@ bare_label_keyword:
 			| OWNER
 			| PARALLEL
 			| PARAMETER
+			| PARAMETERS
 			| PARSER
 			| PARTIAL
 			| PARTITION
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 375b40b29af..2e77c2db19b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -88,6 +88,7 @@ typedef struct
 	List	   *fkconstraints;	/* FOREIGN KEY constraints */
 	List	   *ixconstraints;	/* index-creating constraints */
 	List	   *likeclauses;	/* LIKE clauses that need post-processing */
+	List	   *options;		/* options from WITH clause, table AM specific parameters */
 	List	   *blist;			/* "before list" of things to do before
 								 * creating the table */
 	List	   *alist;			/* "after list" of things to do after creating
@@ -248,6 +249,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 	cxt.fkconstraints = NIL;
 	cxt.ixconstraints = NIL;
 	cxt.likeclauses = NIL;
+	cxt.options = stmt->options;
 	cxt.blist = NIL;
 	cxt.alist = NIL;
 	cxt.pkey = NULL;
@@ -372,6 +374,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 	stmt->tableElts = cxt.columns;
 	stmt->constraints = cxt.ckconstraints;
 	stmt->nnconstraints = cxt.nnconstraints;
+	stmt->options = cxt.options;
 
 	result = lappend(cxt.blist, stmt);
 	result = list_concat(result, cxt.alist);
@@ -1271,6 +1274,78 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
 		}
 	}
 
+	/* Likewise, copy stoarge paramters if requested */
+	if ((table_like_clause->options & CREATE_TABLE_LIKE_PARAMETERS) &&
+		!cxt->isforeign)
+	{
+		HeapTuple	tuple;
+		HeapTuple	toast_tuple;
+		Datum		reloptions;
+		bool		isnull;
+		Oid			relid;
+		List		*oldoptions = NIL;
+		List		*oldtoastoptions = NIL;
+
+		relid = RelationGetRelid(relation);
+		tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
+
+		if (!HeapTupleIsValid(tuple))
+			elog(ERROR, "cache lookup failed for relation %u", relid);
+
+		reloptions = SysCacheGetAttr(RELOID, tuple,
+									 Anum_pg_class_reloptions, &isnull);
+		if (!isnull)
+		{
+			oldoptions = untransformRelOptions(reloptions);
+
+			foreach_node(DefElem, option, oldoptions)
+				cxt->options = lappend(cxt->options, option);
+		}
+
+		ReleaseSysCache(tuple);
+
+		/* get the toast relation's reloptions */
+		if (OidIsValid(relation->rd_rel->reltoastrelid))
+		{
+			Relation	toastrel;
+
+			Oid			toastid = relation->rd_rel->reltoastrelid;
+
+			/*
+			 * The referenced table is already locked; acquire the lock on its
+			 * associated TOAST relation now.
+			 */
+			toastrel = table_open(toastid, AccessShareLock);
+
+			/* Fetch heap tuple */
+			toast_tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(toastid));
+
+			if (!HeapTupleIsValid(toast_tuple))
+				elog(ERROR, "cache lookup failed for toast relation %u", toastid);
+
+			/* Get the toast reloptions */
+			reloptions = SysCacheGetAttr(RELOID, toast_tuple, Anum_pg_class_reloptions,
+										 &isnull);
+			if (!isnull)
+			{
+				oldtoastoptions = untransformRelOptionsExtended(reloptions, "toast");
+
+				foreach_node(DefElem, option, oldtoastoptions)
+					cxt->options = lappend(cxt->options, option);
+			}
+
+			ReleaseSysCache(toast_tuple);
+
+			/*
+			 * Close the parent TOAST rel, but keep our AccessShareLock on it
+			 * until xact commit.  That will prevent someone else from deleting
+			 * or ALTERing the parent TOAST before we can run
+			 * expandTableLikeClause.
+			 */
+			table_close(toastrel, NoLock);
+		}
+	}
+
 	/*
 	 * Reproduce not-null constraints, if any, by copying them.  We do this
 	 * regardless of options given.
@@ -3860,6 +3935,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 	cxt.fkconstraints = NIL;
 	cxt.ixconstraints = NIL;
 	cxt.likeclauses = NIL;
+	cxt.options = NIL;
 	cxt.blist = NIL;
 	cxt.alist = NIL;
 	cxt.pkey = NULL;
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index a604a4702c3..8589616d08b 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -236,6 +236,7 @@ extern Datum transformRelOptions(Datum oldOptions, List *defList,
 								 const char *nameSpace, const char *const validnsps[],
 								 bool acceptOidsOff, bool isReset);
 extern List *untransformRelOptions(Datum options);
+extern List *untransformRelOptionsExtended(Datum options, char* nameSpace);
 extern bytea *extractRelOptions(HeapTuple tuple, TupleDesc tupdesc,
 								amoptions_function amoptions);
 extern void *build_reloptions(Datum reloptions, bool validate,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index bc7adba4a0f..9e0296aee39 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -795,6 +795,7 @@ typedef enum TableLikeOption
 	CREATE_TABLE_LIKE_INDEXES = 1 << 6,
 	CREATE_TABLE_LIKE_STATISTICS = 1 << 7,
 	CREATE_TABLE_LIKE_STORAGE = 1 << 8,
+	CREATE_TABLE_LIKE_PARAMETERS = 1 << 9,
 	CREATE_TABLE_LIKE_ALL = PG_INT32_MAX
 } TableLikeOption;
 
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 9fde58f541c..aad64e7ccca 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -336,6 +336,7 @@ PG_KEYWORD("owned", OWNED, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("owner", OWNER, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("parallel", PARALLEL, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("parameter", PARAMETER, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("parameters", PARAMETERS, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("parser", PARSER, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("partial", PARTIAL, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("partition", PARTITION, UNRESERVED_KEYWORD, BARE_LABEL)
diff --git a/src/test/regress/expected/create_table_like.out b/src/test/regress/expected/create_table_like.out
index d3c35c14847..3eb079e24ed 100644
--- a/src/test/regress/expected/create_table_like.out
+++ b/src/test/regress/expected/create_table_like.out
@@ -699,3 +699,42 @@ DROP FOREIGN TABLE ctl_foreign_table1;
 DROP FOREIGN TABLE ctl_foreign_table2;
 DROP FOREIGN DATA WRAPPER ctl_dummy CASCADE;
 NOTICE:  drop cascades to server ctl_s0
+--CREATE TABLE LIKE with PARAMETERS
+create table t_sp(a text) with (
+    fillfactor = 100,
+    toast_tuple_target = 128,
+    vacuum_index_cleanup = auto,
+    toast.vacuum_index_cleanup = auto,
+    vacuum_truncate = true,
+    toast.vacuum_truncate = false,
+    log_autovacuum_min_duration = 100,
+    toast.log_autovacuum_min_duration = 100);
+create table t_sp1(like t_sp including parameters) with (fillfactor = 100); --error
+ERROR:  parameter "fillfactor" specified more than once
+create table t_sp1(like t_sp excluding parameters) with (fillfactor = 100); --ok
+\d+ t_sp1
+                                  Table "public.t_sp1"
+ Column | Type | Collation | Nullable | Default | Storage  | Stats target | Description 
+--------+------+-----------+----------+---------+----------+--------------+-------------
+ a      | text |           |          |         | extended |              | 
+Options: fillfactor=100
+
+create table t_sp2(like t_sp including parameters) with (
+  parallel_workers = 3,
+  toast.autovacuum_vacuum_threshold = 101,
+  toast.autovacuum_vacuum_scale_factor = 0.3);
+select    c.relname, c.reloptions, tc.reloptions as toast_options
+from      pg_catalog.pg_class c
+left join pg_catalog.pg_class tc ON (c.reltoastrelid = tc.oid)
+where     c.relname in ('t_sp1', 't_sp2')
+order by  c.relname \gx
+-[ RECORD 1 ]-+-----------------------------------------------------------------------------------------------------------------------------------------------------
+relname       | t_sp1
+reloptions    | {fillfactor=100}
+toast_options | 
+-[ RECORD 2 ]-+-----------------------------------------------------------------------------------------------------------------------------------------------------
+relname       | t_sp2
+reloptions    | {parallel_workers=3,fillfactor=100,toast_tuple_target=128,vacuum_index_cleanup=auto,vacuum_truncate=true,log_autovacuum_min_duration=100}
+toast_options | {autovacuum_vacuum_threshold=101,autovacuum_vacuum_scale_factor=0.3,vacuum_index_cleanup=auto,vacuum_truncate=false,log_autovacuum_min_duration=100}
+
+drop table t_sp, t_sp1, t_sp2;
diff --git a/src/test/regress/sql/create_table_like.sql b/src/test/regress/sql/create_table_like.sql
index 93389b57dbf..afc8ffe6ad5 100644
--- a/src/test/regress/sql/create_table_like.sql
+++ b/src/test/regress/sql/create_table_like.sql
@@ -280,3 +280,31 @@ DROP TABLE ctl_table;
 DROP FOREIGN TABLE ctl_foreign_table1;
 DROP FOREIGN TABLE ctl_foreign_table2;
 DROP FOREIGN DATA WRAPPER ctl_dummy CASCADE;
+
+--CREATE TABLE LIKE with PARAMETERS
+create table t_sp(a text) with (
+    fillfactor = 100,
+    toast_tuple_target = 128,
+    vacuum_index_cleanup = auto,
+    toast.vacuum_index_cleanup = auto,
+    vacuum_truncate = true,
+    toast.vacuum_truncate = false,
+    log_autovacuum_min_duration = 100,
+    toast.log_autovacuum_min_duration = 100);
+
+create table t_sp1(like t_sp including parameters) with (fillfactor = 100); --error
+create table t_sp1(like t_sp excluding parameters) with (fillfactor = 100); --ok
+\d+ t_sp1
+
+create table t_sp2(like t_sp including parameters) with (
+  parallel_workers = 3,
+  toast.autovacuum_vacuum_threshold = 101,
+  toast.autovacuum_vacuum_scale_factor = 0.3);
+
+select    c.relname, c.reloptions, tc.reloptions as toast_options
+from      pg_catalog.pg_class c
+left join pg_catalog.pg_class tc ON (c.reltoastrelid = tc.oid)
+where     c.relname in ('t_sp1', 't_sp2')
+order by  c.relname \gx
+
+drop table t_sp, t_sp1, t_sp2;
-- 
2.34.1

---

From: "Euler Taveira" <[email-masked]>
Date: Mon, 15 Dec 2025 14:51:49 -0300

> rebased, with minor comment polishing.

I started reviewing this patch. Why don't you reuse untransformRelOptions()?
Even if this function is used by various extensions [1][2], that's not an
excuse to duplicate code. I adjusted the code in the fixup. I did a bunch of
stylish changes and fixed a few typos. I also ran pgindent. I removed some
superfluous comments ("Fetch heap tuple", "Get the toast reloptions",). Don't
think they are saying something important. Coverage looks good. As a homework,
add a nice commit message. Showing how it works is not a good commit message.

This v6 is your v5 plus my suggestions.

[1] https://codesearch.debian.net/search?q=untransformRelOptions&literal=1
[2] https://github.com/search?q=untransformRelOptions&type=code

-- 
Euler Taveira
EDB   https://www.enterprisedb.com/

From 423eeaeef069ee3c1fa9725e20bbfcfa0464916d Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Mon, 15 Dec 2025 16:41:59 +0800
Subject: [PATCH v6 1/2] create table like incluing parameters

demo:

 

 

+/*

 

2.39.5

From a87da89dea951c78b62e796eb13e4f75f171a6c8 Mon Sep 17 00:00:00 2001
From: Euler Taveira <[email-masked]>
Date: Mon, 15 Dec 2025 11:11:43 -0300
Subject: [PATCH v6 2/2] fixup! create table like incluing parameters

---
 src/backend/access/common/reloptions.c        | 40 ++-------------
 src/backend/parser/parse_utilcmd.c            | 33 ++++++-------
 src/include/access/reloptions.h               |  2 +-
 .../regress/expected/create_table_like.out    | 49 +++++++++----------
 src/test/regress/sql/create_table_like.sql    | 36 +++++++-------
 5 files changed, 60 insertions(+), 100 deletions(-)

diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 9289caef7ba..93db86acad3 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -1359,48 +1359,14 @@ transformRelOptions(Datum oldOptions, List *defList, const char *nameSpace,
 List *
 untransformRelOptions(Datum options)
 {
-	List	   *result = NIL;
-	ArrayType  *array;
-	Datum	   *optiondatums;
-	int			noptions;
-	int			i;
-
-	/* Nothing to do if no options */
-	if (DatumGetPointer(options) == NULL)
-		return result;
-
-	array = DatumGetArrayTypeP(options);
-
-	deconstruct_array_builtin(array, TEXTOID, &optiondatums, NULL, &noptions);
-
-	for (i = 0; i < noptions; i++)
-	{
-		char	   *s;
-		char	   *p;
-		Node	   *val = NULL;
-
-		s = TextDatumGetCString(optiondatums[i]);
-		p = strchr(s, '=');
-		if (p)
-		{
-			*p++ = '\0';
-			val = (Node *) makeString(p);
-		}
-		result = lappend(result, makeDefElem(s, val, -1));
-	}
-
-	return result;
+	return untransformRelOptionsExtended(options, NULL);
 }
 
 /*
- * Convert the reloptions from text-array format into a List of DefElem.  This
- * is the reverse operation of transformRelOptions().
- *
- * If any option includes a namespace qualification, create the DefElem in that
- * namespace, otherwise this behave the same as untransformRelOptions.
+ * Same as untransformRelOptions() but includes a namespace into DefElem.
  */
 List *
-untransformRelOptionsExtended(Datum options, char* nameSpace)
+untransformRelOptionsExtended(Datum options, char *nameSpace)
 {
 	List	   *result = NIL;
 	ArrayType  *array;
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 2e77c2db19b..c9056c4f22e 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -88,7 +88,8 @@ typedef struct
 	List	   *fkconstraints;	/* FOREIGN KEY constraints */
 	List	   *ixconstraints;	/* index-creating constraints */
 	List	   *likeclauses;	/* LIKE clauses that need post-processing */
-	List	   *options;		/* options from WITH clause, table AM specific parameters */
+	List	   *options;		/* options from WITH clause, table AM specific
+								 * parameters */
 	List	   *blist;			/* "before list" of things to do before
 								 * creating the table */
 	List	   *alist;			/* "after list" of things to do after creating
@@ -1125,8 +1126,8 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
  * table has been created.
  *
  * Some options are ignored.  For example, as foreign tables have no storage,
- * these INCLUDING options have no effect: STORAGE, COMPRESSION, IDENTITY
- * and INDEXES.  Similarly, INCLUDING INDEXES is ignored from a view.
+ * these INCLUDING options have no effect: STORAGE, COMPRESSION, IDENTITY,
+ * INDEXES and PARAMETERS.  Similarly, INCLUDING INDEXES is ignored from a view.
  */
 static void
 transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_clause)
@@ -1274,17 +1275,16 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
 		}
 	}
 
-	/* Likewise, copy stoarge paramters if requested */
+	/* Likewise, copy storage parameters if requested */
 	if ((table_like_clause->options & CREATE_TABLE_LIKE_PARAMETERS) &&
 		!cxt->isforeign)
 	{
 		HeapTuple	tuple;
-		HeapTuple	toast_tuple;
 		Datum		reloptions;
 		bool		isnull;
 		Oid			relid;
-		List		*oldoptions = NIL;
-		List		*oldtoastoptions = NIL;
+		List	   *oldoptions = NIL;
+		List	   *oldtoastoptions = NIL;
 
 		relid = RelationGetRelid(relation);
 		tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
@@ -1308,7 +1308,6 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
 		if (OidIsValid(relation->rd_rel->reltoastrelid))
 		{
 			Relation	toastrel;
-
 			Oid			toastid = relation->rd_rel->reltoastrelid;
 
 			/*
@@ -1317,14 +1316,12 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
 			 */
 			toastrel = table_open(toastid, AccessShareLock);
 
-			/* Fetch heap tuple */
-			toast_tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(toastid));
+			tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(toastid));
 
-			if (!HeapTupleIsValid(toast_tuple))
-				elog(ERROR, "cache lookup failed for toast relation %u", toastid);
+			if (!HeapTupleIsValid(tuple))
+				elog(ERROR, "cache lookup failed for relation %u", toastid);
 
-			/* Get the toast reloptions */
-			reloptions = SysCacheGetAttr(RELOID, toast_tuple, Anum_pg_class_reloptions,
+			reloptions = SysCacheGetAttr(RELOID, tuple, Anum_pg_class_reloptions,
 										 &isnull);
 			if (!isnull)
 			{
@@ -1334,12 +1331,12 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
 					cxt->options = lappend(cxt->options, option);
 			}
 
-			ReleaseSysCache(toast_tuple);
+			ReleaseSysCache(tuple);
 
 			/*
-			 * Close the parent TOAST rel, but keep our AccessShareLock on it
-			 * until xact commit.  That will prevent someone else from deleting
-			 * or ALTERing the parent TOAST before we can run
+			 * Close the toast relation, but keep our AccessShareLock on it
+			 * until xact commit.  That will prevent someone else from
+			 * deleting or ALTERing the parent TOAST before we can run
 			 * expandTableLikeClause.
 			 */
 			table_close(toastrel, NoLock);
diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h
index 8589616d08b..0c29f5df229 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -236,7 +236,7 @@ extern Datum transformRelOptions(Datum oldOptions, List *defList,
 								 const char *nameSpace, const char *const validnsps[],
 								 bool acceptOidsOff, bool isReset);
 extern List *untransformRelOptions(Datum options);
-extern List *untransformRelOptionsExtended(Datum options, char* nameSpace);
+extern List *untransformRelOptionsExtended(Datum options, char *nameSpace);
 extern bytea *extractRelOptions(HeapTuple tuple, TupleDesc tupdesc,
 								amoptions_function amoptions);
 extern void *build_reloptions(Datum reloptions, bool validate,
diff --git a/src/test/regress/expected/create_table_like.out b/src/test/regress/expected/create_table_like.out
index 3eb079e24ed..89145882aef 100644
--- a/src/test/regress/expected/create_table_like.out
+++ b/src/test/regress/expected/create_table_like.out
@@ -700,18 +700,18 @@ DROP FOREIGN TABLE ctl_foreign_table2;
 DROP FOREIGN DATA WRAPPER ctl_dummy CASCADE;
 NOTICE:  drop cascades to server ctl_s0
 --CREATE TABLE LIKE with PARAMETERS
-create table t_sp(a text) with (
-    fillfactor = 100,
-    toast_tuple_target = 128,
-    vacuum_index_cleanup = auto,
-    toast.vacuum_index_cleanup = auto,
-    vacuum_truncate = true,
-    toast.vacuum_truncate = false,
-    log_autovacuum_min_duration = 100,
-    toast.log_autovacuum_min_duration = 100);
-create table t_sp1(like t_sp including parameters) with (fillfactor = 100); --error
+CREATE TABLE t_sp (a text) WITH (
+  fillfactor = 100,
+  toast_tuple_target = 128,
+  vacuum_index_cleanup = auto,
+  toast.vacuum_index_cleanup = auto,
+  vacuum_truncate = true,
+  toast.vacuum_truncate = false,
+  log_autovacuum_min_duration = 100,
+  toast.log_autovacuum_min_duration = 100);
+CREATE TABLE t_sp1 (LIKE t_sp INCLUDING PARAMETERS) WITH (fillfactor = 100); -- fail
 ERROR:  parameter "fillfactor" specified more than once
-create table t_sp1(like t_sp excluding parameters) with (fillfactor = 100); --ok
+CREATE TABLE T_sp1 (LIKE t_sp EXCLUDING PARAMETERS) WITH (fillfactor = 100); -- ok
 \d+ t_sp1
                                   Table "public.t_sp1"
  Column | Type | Collation | Nullable | Default | Storage  | Stats target | Description 
@@ -719,22 +719,19 @@ create table t_sp1(like t_sp excluding parameters) with (fillfactor = 100); --ok
  a      | text |           |          |         | extended |              | 
 Options: fillfactor=100
 
-create table t_sp2(like t_sp including parameters) with (
+CREATE TABLE t_sp2 (LIKE t_sp INCLUDING PARAMETERS) WITH (
   parallel_workers = 3,
   toast.autovacuum_vacuum_threshold = 101,
   toast.autovacuum_vacuum_scale_factor = 0.3);
-select    c.relname, c.reloptions, tc.reloptions as toast_options
-from      pg_catalog.pg_class c
-left join pg_catalog.pg_class tc ON (c.reltoastrelid = tc.oid)
-where     c.relname in ('t_sp1', 't_sp2')
-order by  c.relname \gx
--[ RECORD 1 ]-+-----------------------------------------------------------------------------------------------------------------------------------------------------
-relname       | t_sp1
-reloptions    | {fillfactor=100}
-toast_options | 
--[ RECORD 2 ]-+-----------------------------------------------------------------------------------------------------------------------------------------------------
-relname       | t_sp2
-reloptions    | {parallel_workers=3,fillfactor=100,toast_tuple_target=128,vacuum_index_cleanup=auto,vacuum_truncate=true,log_autovacuum_min_duration=100}
-toast_options | {autovacuum_vacuum_threshold=101,autovacuum_vacuum_scale_factor=0.3,vacuum_index_cleanup=auto,vacuum_truncate=false,log_autovacuum_min_duration=100}
+SELECT c.relname, c.reloptions, t.reloptions as toast_reloptions
+  FROM pg_catalog.pg_class c
+  LEFT JOIN pg_catalog.pg_class t ON (c.reltoastrelid = t.oid)
+  WHERE c.relname IN ('t_sp1', 't_sp2')
+  ORDER BY c.relname;
+ relname |                                                                reloptions                                                                 |                                                                   toast_reloptions                                                                   
+---------+-------------------------------------------------------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------
+ t_sp1   | {fillfactor=100}                                                                                                                          | 
+ t_sp2   | {parallel_workers=3,fillfactor=100,toast_tuple_target=128,vacuum_index_cleanup=auto,vacuum_truncate=true,log_autovacuum_min_duration=100} | {autovacuum_vacuum_threshold=101,autovacuum_vacuum_scale_factor=0.3,vacuum_index_cleanup=auto,vacuum_truncate=false,log_autovacuum_min_duration=100}
+(2 rows)
 
-drop table t_sp, t_sp1, t_sp2;
+DROP TABLE t_sp, t_sp1, t_sp2;
diff --git a/src/test/regress/sql/create_table_like.sql b/src/test/regress/sql/create_table_like.sql
index afc8ffe6ad5..c357c2dec0d 100644
--- a/src/test/regress/sql/create_table_like.sql
+++ b/src/test/regress/sql/create_table_like.sql
@@ -282,29 +282,29 @@ DROP FOREIGN TABLE ctl_foreign_table2;
 DROP FOREIGN DATA WRAPPER ctl_dummy CASCADE;
 
 --CREATE TABLE LIKE with PARAMETERS
-create table t_sp(a text) with (
-    fillfactor = 100,
-    toast_tuple_target = 128,
-    vacuum_index_cleanup = auto,
-    toast.vacuum_index_cleanup = auto,
-    vacuum_truncate = true,
-    toast.vacuum_truncate = false,
-    log_autovacuum_min_duration = 100,
-    toast.log_autovacuum_min_duration = 100);
+CREATE TABLE t_sp (a text) WITH (
+  fillfactor = 100,
+  toast_tuple_target = 128,
+  vacuum_index_cleanup = auto,
+  toast.vacuum_index_cleanup = auto,
+  vacuum_truncate = true,
+  toast.vacuum_truncate = false,
+  log_autovacuum_min_duration = 100,
+  toast.log_autovacuum_min_duration = 100);
 
-create table t_sp1(like t_sp including parameters) with (fillfactor = 100); --error
-create table t_sp1(like t_sp excluding parameters) with (fillfactor = 100); --ok
+CREATE TABLE t_sp1 (LIKE t_sp INCLUDING PARAMETERS) WITH (fillfactor = 100); -- fail
+CREATE TABLE T_sp1 (LIKE t_sp EXCLUDING PARAMETERS) WITH (fillfactor = 100); -- ok
 \d+ t_sp1
 
-create table t_sp2(like t_sp including parameters) with (
+CREATE TABLE t_sp2 (LIKE t_sp INCLUDING PARAMETERS) WITH (
   parallel_workers = 3,
   toast.autovacuum_vacuum_threshold = 101,
   toast.autovacuum_vacuum_scale_factor = 0.3);
 
-select    c.relname, c.reloptions, tc.reloptions as toast_options
-from      pg_catalog.pg_class c
-left join pg_catalog.pg_class tc ON (c.reltoastrelid = tc.oid)
-where     c.relname in ('t_sp1', 't_sp2')
-order by  c.relname \gx
+SELECT c.relname, c.reloptions, t.reloptions as toast_reloptions
+  FROM pg_catalog.pg_class c
+  LEFT JOIN pg_catalog.pg_class t ON (c.reltoastrelid = t.oid)
+  WHERE c.relname IN ('t_sp1', 't_sp2')
+  ORDER BY c.relname;
 
-drop table t_sp, t_sp1, t_sp2;
+DROP TABLE t_sp, t_sp1, t_sp2;
-- 
2.39.5

---

From: jian he <[email-masked]>
Date: Tue, 16 Dec 2025 10:45:04 +0800

On Tue, Dec 16, 2025 at 1:52 AM Euler Taveira <[email-masked]> wrote:
> excuse to duplicate code.

yech, duplicate code is not good. I encountered the same issue in the CAST
DEFAULT patch.  I guess I was posting it in the "post early post often" spirit
for the rebase.

> This v6 is your v5 plus my suggestions.

your suggestions are great.
I have incorporated all the changes you proposed.

in transformTableLikeClause, we have:
    if (relation->rd_rel->relkind != RELKIND_RELATION &&
        relation->rd_rel->relkind != RELKIND_VIEW &&
        relation->rd_rel->relkind != RELKIND_MATVIEW &&
        relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
        relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&
        relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
        ereport(ERROR,
                (errcode(ERRCODE_WRONG_OBJECT_TYPE),
                 errmsg("relation \"%s\" is invalid in LIKE clause",
                        RelationGetRelationName(relation)),
                 errdetail_relkind_not_supported(relation->rd_rel->relkind)));

except RELKIND_RELATION, RELKIND_MATVIEW can also specify storage parameters, I
added a test for it too.

to test CREATE FOREIGN LIKE will silently ignore INCLUDING PARAMETERS, i also
slightly modified create_table_like.sql.

The last SELECT query output is too wide, to make it more readable, I
have to use \gx.

Below is the commit message I have drafted:
----------------------------------
Add INCLUDING PARAMETERS to CREATE TABLE LIKE

Introduce the PARAMETERS option to copy parameters when using CREATE TABLE LIKE.
Currently, this primarily applies to the storage parameters, but in the future,
it may encompass other kind parameters in the table.

Since storage parameters are not in the standard, INCLUDING PARAMETERS does not
comply with the standard.  Also because foreign tables cannot specify storage
parameters, CREATE FOREIGN TABLE LIKE will silently ignore INCLUDING PARAMETERS.

This makes creating a new table via CREATE TABLE LIKE more convenient, as the
user would otherwise have to manually specify every storage parameter. It is a
further step toward making a new table created with CREATE TABLE LIKE more
identical to the original table.

Author: jian he <[email-masked]>
Reviewed-by: Euler Taveira <[email-masked]>
Reviewed-by: Nathan Bossart <[email-masked]>
Reviewed-by: David G. Johnston <[email-masked]>

(delete this line later) commitfest:
https://commitfest.postgresql.org/patch/6088
discussion: https://postgr.es/m/CACJufxHr=nKEsS66M7rTHgB+mXdQ948=[email-masked]
----------------------------------

--
jian
https://www.enterprisedb.com/
From 0a1bbe140213168dc8df0b64e29d7a814cdecd06 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Tue, 16 Dec 2025 10:36:50 +0800
Subject: [PATCH v7 1/1] Add INCLUDING PARAMETERS to CREATE TABLE LIKE

Introduce the PARAMETERS option to copy parameters when using CREATE TABLE LIKE.
Currently, this primarily applies to the storage parameters, but in the future,
it may encompass other kind parameters in the table.

Since storage parameters are not in the standard, INCLUDING PARAMETERS does not
comply with the standard.  Also because foreign tables cannot specify storage
parameters, CREATE FOREIGN TABLE LIKE will silently ignore INCLUDING PARAMETERS.

This makes creating a new table via CREATE TABLE LIKE more convenient, as the
user would otherwise have to manually specify every storage parameter. It is a
further step toward making a new table created with CREATE TABLE LIKE more
identical to the original table.

Author: jian he <[email-masked]>
Reviewed-by: Euler Taveira <[email-masked]>
Reviewed-by: Nathan Bossart <[email-masked]>
Reviewed-by: David G. Johnston <[email-masked]>

(delete this line later) commitfest: https://commitfest.postgresql.org/patch/6088
discussion: https://postgr.es/m/CACJufxHr=nKEsS66M7rTHgB+mXdQ948=[email-masked]
---
 doc/src/sgml/ref/create_table.sgml            | 14 +++-
 src/backend/access/common/reloptions.c        | 17 +++-
 src/backend/parser/gram.y                     |  5 +-
 src/backend/parser/parse_utilcmd.c            | 77 ++++++++++++++++++-
 src/include/access/reloptions.h               |  1 +
 src/include/nodes/parsenodes.h                |  1 +
 src/include/parser/kwlist.h                   |  1 +
 .../regress/expected/create_table_like.out    | 55 ++++++++++++-
 src/test/regress/sql/create_table_like.sql    | 39 +++++++++-
 9 files changed, 201 insertions(+), 9 deletions(-)

 

 
index 31926d8a368..57c337377f5 100644
@@ -1358,6 +1358,15 @@ transformRelOptions(Datum oldOptions, List *defList, const char *nameSpace,
@@ -1386,7 +1395,13 @@ untransformRelOptions(Datum options)
 			*p++ = '\0';
 			val = (Node *) makeString(p);
 
 	return result;

index 375b40b29af..c9056c4f22e 100644
@@ -88,6 +88,8 @@ typedef struct
@@ -248,6 +250,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
@@ -372,6 +375,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 
@@ -1122,8 +1126,8 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
@@ -1271,6 +1275,74 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
 		}
 	}
 
@@ -3860,6 +3932,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
index a604a4702c3..0c29f5df229 100644
 
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 9fde58f541c..aad64e7ccca 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -336,6 +336,7 @@ PG_KEYWORD("owned", OWNED, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("owner", OWNER, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("parallel", PARALLEL, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("parameter", PARAMETER, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("parameters", PARAMETERS, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("parser", PARSER, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("partial", PARTIAL, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("partition", PARTITION, UNRESERVED_KEYWORD, BARE_LABEL)
diff --git a/src/test/regress/expected/create_table_like.out b/src/test/regress/expected/create_table_like.out
index d3c35c14847..dd8928bb262 100644
--- a/src/test/regress/expected/create_table_like.out
+++ b/src/test/regress/expected/create_table_like.out
@@ -605,7 +605,8 @@ CREATE TABLE ctl_table(a int PRIMARY KEY,
   b varchar COMPRESSION pglz,
   c int GENERATED ALWAYS AS (a * 2) STORED,
   d bigint GENERATED ALWAYS AS IDENTITY,
-  e int DEFAULT 1);
+  e int DEFAULT 1)
+  WITH (fillfactor = 100);
 CREATE INDEX ctl_table_a_key ON ctl_table(a);
 COMMENT ON COLUMN ctl_table.b IS 'Column b';
 CREATE STATISTICS ctl_table_stat ON a,b FROM ctl_table;
@@ -630,6 +631,7 @@ Statistics objects:
 Not-null constraints:
     "ctl_table_a_not_null" NOT NULL "a"
     "ctl_table_d_not_null" NOT NULL "d"
+Options: fillfactor=100
 
 -- Test EXCLUDING ALL
 CREATE FOREIGN TABLE ctl_foreign_table1(LIKE ctl_table EXCLUDING ALL) SERVER ctl_s0;
@@ -661,7 +663,7 @@ SELECT attname, attcompression FROM pg_attribute
 (5 rows)
 
 -- Test INCLUDING ALL
--- INDEXES, IDENTITY, COMPRESSION, STORAGE are not copied.
+-- INDEXES, IDENTITY, COMPRESSION, STORAGE, PARAMETERS are not copied.
 CREATE FOREIGN TABLE ctl_foreign_table2(LIKE ctl_table INCLUDING ALL) SERVER ctl_s0;
 \d+ ctl_foreign_table2
                                                   Foreign table "public.ctl_foreign_table2"
@@ -699,3 +701,52 @@ DROP FOREIGN TABLE ctl_foreign_table1;
 DROP FOREIGN TABLE ctl_foreign_table2;
 DROP FOREIGN DATA WRAPPER ctl_dummy CASCADE;
 NOTICE:  drop cascades to server ctl_s0
+--CREATE TABLE LIKE with PARAMETERS
+CREATE TABLE t_sp (a text) WITH (
+  fillfactor = 100,
+  toast_tuple_target = 128,
+  vacuum_index_cleanup = auto,
+  toast.vacuum_index_cleanup = auto,
+  vacuum_truncate = true,
+  toast.vacuum_truncate = false,
+  log_autovacuum_min_duration = 100,
+  toast.log_autovacuum_min_duration = 100);
+CREATE TABLE t_sp1 (LIKE t_sp INCLUDING PARAMETERS) WITH (fillfactor = 100); -- fail
+ERROR:  parameter "fillfactor" specified more than once
+CREATE TABLE T_sp1 (LIKE t_sp EXCLUDING PARAMETERS) WITH (fillfactor = 100); -- ok
+\d+ t_sp1
+                                  Table "public.t_sp1"
+ Column | Type | Collation | Nullable | Default | Storage  | Stats target | Description 
+--------+------+-----------+----------+---------+----------+--------------+-------------
+ a      | text |           |          |         | extended |              | 
+Options: fillfactor=100
+
+CREATE TABLE t_sp2 (LIKE t_sp INCLUDING PARAMETERS) WITH (
+  parallel_workers = 3,
+  toast.autovacuum_vacuum_threshold = 101,
+  toast.autovacuum_vacuum_scale_factor = 0.3);
+SELECT c.relname, c.reloptions, t.reloptions as toast_reloptions
+  FROM pg_catalog.pg_class c
+  LEFT JOIN pg_catalog.pg_class t ON (c.reltoastrelid = t.oid)
+  WHERE c.relname IN ('t_sp1', 't_sp2')
+  ORDER BY c.relname \gx
+-[ RECORD 1 ]----+-----------------------------------------------------------------------------------------------------------------------------------------------------
+relname          | t_sp1
+reloptions       | {fillfactor=100}
+toast_reloptions | 
+-[ RECORD 2 ]----+-----------------------------------------------------------------------------------------------------------------------------------------------------
+relname          | t_sp2
+reloptions       | {parallel_workers=3,fillfactor=100,toast_tuple_target=128,vacuum_index_cleanup=auto,vacuum_truncate=true,log_autovacuum_min_duration=100}
+toast_reloptions | {autovacuum_vacuum_threshold=101,autovacuum_vacuum_scale_factor=0.3,vacuum_index_cleanup=auto,vacuum_truncate=false,log_autovacuum_min_duration=100}
+
+CREATE MATERIALIZED VIEW mv_dummy WITH (fillfactor = 10) AS SELECT 1 AS a;
+CREATE TABLE t_sp3 (LIKE mv_dummy INCLUDING PARAMETERS);
+\d+ t_sp3
+                                   Table "public.t_sp3"
+ Column |  Type   | Collation | Nullable | Default | Storage | Stats target | Description 
+--------+---------+-----------+----------+---------+---------+--------------+-------------
+ a      | integer |           |          |         | plain   |              | 
+Options: fillfactor=10
+
+DROP MATERIALIZED VIEW mv_dummy;
+DROP TABLE t_sp, t_sp1, t_sp2, t_sp3;
diff --git a/src/test/regress/sql/create_table_like.sql b/src/test/regress/sql/create_table_like.sql
index 93389b57dbf..28f2d23f84c 100644
--- a/src/test/regress/sql/create_table_like.sql
+++ b/src/test/regress/sql/create_table_like.sql
@@ -249,7 +249,8 @@ CREATE TABLE ctl_table(a int PRIMARY KEY,
   b varchar COMPRESSION pglz,
   c int GENERATED ALWAYS AS (a * 2) STORED,
   d bigint GENERATED ALWAYS AS IDENTITY,
-  e int DEFAULT 1);
+  e int DEFAULT 1)
+  WITH (fillfactor = 100);
 
 CREATE INDEX ctl_table_a_key ON ctl_table(a);
 COMMENT ON COLUMN ctl_table.b IS 'Column b';
@@ -268,7 +269,7 @@ SELECT attname, attcompression FROM pg_attribute
   WHERE attrelid = 'ctl_foreign_table1'::regclass and attnum > 0 ORDER BY attnum;
 
 -- Test INCLUDING ALL
--- INDEXES, IDENTITY, COMPRESSION, STORAGE are not copied.
+-- INDEXES, IDENTITY, COMPRESSION, STORAGE, PARAMETERS are not copied.
 CREATE FOREIGN TABLE ctl_foreign_table2(LIKE ctl_table INCLUDING ALL) SERVER ctl_s0;
 \d+ ctl_foreign_table2
 -- \d+ does not report the value of attcompression for a foreign table, so
@@ -280,3 +281,37 @@ DROP TABLE ctl_table;
 DROP FOREIGN TABLE ctl_foreign_table1;
 DROP FOREIGN TABLE ctl_foreign_table2;
 DROP FOREIGN DATA WRAPPER ctl_dummy CASCADE;
+
+--CREATE TABLE LIKE with PARAMETERS
+CREATE TABLE t_sp (a text) WITH (
+  fillfactor = 100,
+  toast_tuple_target = 128,
+  vacuum_index_cleanup = auto,
+  toast.vacuum_index_cleanup = auto,
+  vacuum_truncate = true,
+  toast.vacuum_truncate = false,
+  log_autovacuum_min_duration = 100,
+  toast.log_autovacuum_min_duration = 100);
+
+CREATE TABLE t_sp1 (LIKE t_sp INCLUDING PARAMETERS) WITH (fillfactor = 100); -- fail
+CREATE TABLE T_sp1 (LIKE t_sp EXCLUDING PARAMETERS) WITH (fillfactor = 100); -- ok
+\d+ t_sp1
+
+CREATE TABLE t_sp2 (LIKE t_sp INCLUDING PARAMETERS) WITH (
+  parallel_workers = 3,
+  toast.autovacuum_vacuum_threshold = 101,
+  toast.autovacuum_vacuum_scale_factor = 0.3);
+
+SELECT c.relname, c.reloptions, t.reloptions as toast_reloptions
+  FROM pg_catalog.pg_class c
+  LEFT JOIN pg_catalog.pg_class t ON (c.reltoastrelid = t.oid)
+  WHERE c.relname IN ('t_sp1', 't_sp2')
+  ORDER BY c.relname \gx
+
+CREATE MATERIALIZED VIEW mv_dummy WITH (fillfactor = 10) AS SELECT 1 AS a;
+
+CREATE TABLE t_sp3 (LIKE mv_dummy INCLUDING PARAMETERS);
+\d+ t_sp3
+
+DROP MATERIALIZED VIEW mv_dummy;
+DROP TABLE t_sp, t_sp1, t_sp2, t_sp3;
-- 
2.34.1

---

From: jian he <[email-masked]>
Date: Wed, 17 Dec 2025 14:41:06 +0800

Hi,

current, v7 the DOC changes:
+       <varlistentry id="sql-createtable-parms-like-opt-storage-paramter">
+        <term><literal>INCLUDING PARAMETERS</literal></term>
+        <listitem>
+         <para>
+          All table parameters, such as the <literal>storage
parameter</literal>
+          settings of the source table, will be copied.  For table storage
+          parameters, see <xref linkend="sql-createtable-storage-parameters"/>
+          below for more information.
+         </para>
+        </listitem>
+       </varlistentry>

https://www.postgresql.org/docs/current/sql-altertable.html#SQL-ALTERTABLE-DESC-SET-ATTRIBUTE-OPTION

Does the "parameter" (INCLUDING PARAMETERS) definition is close to "option"?
This raises the question of whether we should also copy column-level options to
the new table.

What do you think?

--
jian
https://www.enterprisedb.com

---

From: "Euler Taveira" <[email-masked]>
Date: Thu, 18 Dec 2025 11:59:39 -0300

> This raises the question of whether we should also copy column-level options to

I don't think so. The current attribute options (n_distinct and
n_distinct_inherited) are closely tied to the data. I'm afraid that this
decision would impose bad plans in the future.

---

From: jian he <[email-masked]>
Date: Mon, 23 Mar 2026 12:44:40 +0800

transformTableLikeClause, we have:
```
    if (relation->rd_rel->relkind != RELKIND_RELATION &&
        relation->rd_rel->relkind != RELKIND_VIEW &&
        relation->rd_rel->relkind != RELKIND_MATVIEW &&
        relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
        relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&
        relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
        ereport(ERROR,
                (errcode(ERRCODE_WRONG_OBJECT_TYPE),
                 errmsg("relation \"%s\" is invalid in LIKE clause",
                        RelationGetRelationName(relation)),
                 errdetail_relkind_not_supported(relation->rd_rel->relkind)));
```
RELKIND_COMPOSITE_TYPE, RELKIND_FOREIGN_TABLE,
RELKIND_PARTITIONED_TABLE cann't/don't have storage parameters.

https://www.postgresql.org/docs/current/sql-creatematerializedview.html says
"All parameters supported for CREATE TABLE are also supported for
CREATE MATERIALIZED VIEW"

However for RELKIND_VIEW:
CREATE TABLE t_sp (a text) WITH (toast_tuple_target = 128);
CREATE VIEW v1 with (check_option=local) as select a from t_sp;
CREATE TABLE t_sp1 (LIKE v1 INCLUDING PARAMETERS);
ERROR:  unrecognized parameter "check_option"

INCLUDING PARAMETERS: basically copy source pg_class.reloptions and
use it for the target table.
RELKIND_VIEW pg_class.reloptions is different from RELKIND_RELATION,
Therefore INCLUDING PARAMETERS should not be applied when the source
relation is RELKIND_VIEW.

--
jian
https://www.enterprisedb.com/
From c2103689f4c7c7d5e7c6560c8f22311bd6a08027 Mon Sep 17 00:00:00 2001
From: jian he <[email-masked]>
Date: Mon, 23 Mar 2026 12:39:04 +0800
Subject: [PATCH v8 1/1] Add INCLUDING PARAMETERS to CREATE TABLE LIKE

Discussion: https://postgr.es/m/CACJufxHr=nKEsS66M7rTHgB+mXdQ948=[email-masked]
Commitfest: https://commitfest.postgresql.org/patch/6088
---
 doc/src/sgml/ref/create_table.sgml            | 14 +++-
 src/backend/access/common/reloptions.c        | 17 +++-
 src/backend/parser/gram.y                     |  5 +-
 src/backend/parser/parse_utilcmd.c            | 78 ++++++++++++++++++-
 src/include/access/reloptions.h               |  1 +
 src/include/nodes/parsenodes.h                |  1 +
 src/include/parser/kwlist.h                   |  1 +
 .../regress/expected/create_table_like.out    | 66 +++++++++++++++-
 src/test/regress/sql/create_table_like.sql    | 44 ++++++++++-
 9 files changed, 218 insertions(+), 9 deletions(-)

index 982532fe725..2a278d194a5 100644

 
 
@@ -754,6 +754,18 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
         </listitem>
        </varlistentry>
 
+       <varlistentry id="sql-createtable-parms-like-opt-paramters">
+        <term><literal>INCLUDING PARAMETERS</literal></term>
+        <listitem>
+         <para>
+          All table parameters, such as the storage parameter
+          settings of the source table, will be copied.  For table storage
+          parameters, see <xref linkend="sql-createtable-storage-parameters"/>
+          below for more information.
+         </para>
+        </listitem>
+       </varlistentry>
+
        <varlistentry id="sql-createtable-parms-like-opt-statistics">
         <term><literal>INCLUDING STATISTICS</literal></term>
         <listitem>
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 237ab8d0ed9..b8f68e6620e 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -1438,6 +1438,15 @@ transformRelOptions(Datum oldOptions, List *defList, const char *nameSpace,
  */
 List *
 untransformRelOptions(Datum options)
+{
+	return untransformRelOptionsExtended(options, NULL);
+}
+
+/*
+ * Same as untransformRelOptions() but includes a namespace into DefElem.
+ */
+List *
+untransformRelOptionsExtended(Datum options, char *nameSpace)
 {
 	List	   *result = NIL;
 	ArrayType  *array;
@@ -1466,7 +1475,13 @@ untransformRelOptions(Datum options)
 			*p++ = '\0';
 			val = (Node *) makeString(p);
 		}
-		result = lappend(result, makeDefElem(s, val, -1));
+
+		if (nameSpace == NULL)
+			result = lappend(result, makeDefElem(s, val, -1));
+		else
+			result = lappend(result, makeDefElemExtended(nameSpace, s, val,
+														 DEFELEM_UNSPEC,
+														 -1));
 	}
 
 	return result;
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 0fea726cdd5..7122cff8a58 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -799,7 +799,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	ORDER ORDINALITY OTHERS OUT_P OUTER_P
 	OVER OVERLAPS OVERLAY OVERRIDING OWNED OWNER
 
 	PRIOR PRIVILEGES PROCEDURAL PROCEDURE PROCEDURES PROGRAM PROPERTIES PROPERTY PUBLICATION
@@ -4345,6 +4345,7 @@ TableLikeOption:
 
@@ -18923,6 +18924,7 @@ unreserved_keyword:
 			| OWNER
 			| PARALLEL
 			| PARAMETER
+			| PARAMETERS
 			| PARSER
 			| PARTIAL
 			| PARTITION
@@ -19566,6 +19568,7 @@ bare_label_keyword:
 			| OWNER
 			| PARALLEL
 			| PARAMETER
+			| PARAMETERS
 			| PARSER
 			| PARTIAL
 			| PARTITION
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 9a918e14aa7..82cfc5e29f7 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -89,6 +89,8 @@ typedef struct
 	List	   *fkconstraints;	/* FOREIGN KEY constraints */
 	List	   *ixconstraints;	/* index-creating constraints */
 	List	   *likeclauses;	/* LIKE clauses that need post-processing */
+	List	   *options;		/* options from WITH clause, table AM specific
+								 * parameters */
 	List	   *blist;			/* "before list" of things to do before
 								 * creating the table */
 	List	   *alist;			/* "after list" of things to do after creating
@@ -249,6 +251,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 	cxt.fkconstraints = NIL;
 	cxt.ixconstraints = NIL;
 	cxt.likeclauses = NIL;
+	cxt.options = stmt->options;
 	cxt.blist = NIL;
 	cxt.alist = NIL;
 	cxt.pkey = NULL;
@@ -373,6 +376,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 	stmt->tableElts = cxt.columns;
 	stmt->constraints = cxt.ckconstraints;
 	stmt->nnconstraints = cxt.nnconstraints;
+	stmt->options = cxt.options;
 
@@ -1123,8 +1127,8 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
@@ -1272,6 +1276,75 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
 		}
 	}
 
+		!cxt->isforeign &&
+		relation->rd_rel->relkind != RELKIND_VIEW)
@@ -3867,6 +3940,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
index 0bd17b30ca7..21ce58ce8a7 100644
index df431220ac5..cc1e14ef8b8 100644
@@ -808,6 +808,7 @@ typedef enum TableLikeOption
 
index b7ded6e6088..48d9b20bb37 100644
@@ -341,6 +341,7 @@ PG_KEYWORD("owned", OWNED, UNRESERVED_KEYWORD, BARE_LABEL)
index d3c35c14847..b176c39a4fc 100644
 
 -- Test EXCLUDING ALL
 
 -- Test INCLUDING ALL
@@ -699,3 +701,63 @@ DROP FOREIGN TABLE ctl_foreign_table1;
+-- CREATE TABLE LIKE with PARAMETERS
+CREATE TABLE t_sp1 (LIKE t_sp EXCLUDING PARAMETERS) WITH (fillfactor = 100); -- ok
+CREATE VIEW t_spv1 WITH (check_option=local) AS SELECT a FROM t_sp;
+CREATE TABLE t_sp4 (LIKE t_spv1 INCLUDING PARAMETERS) WITH (fillfactor = 10);
+-- view option (check_option=local) will not copied to new table
+\d+ t_sp4
+                                  Table "public.t_sp4"
+DROP VIEW t_spv1;
index 93389b57dbf..07a6b50f7e0 100644

 -- Test INCLUDING ALL
@@ -280,3 +281,42 @@ DROP TABLE ctl_table;
+-- CREATE TABLE LIKE with PARAMETERS
+CREATE TABLE t_sp1 (LIKE t_sp EXCLUDING PARAMETERS) WITH (fillfactor = 100); -- ok
+CREATE VIEW t_spv1 WITH (check_option=local) AS SELECT a FROM t_sp;
+CREATE TABLE t_sp4 (LIKE t_spv1 INCLUDING PARAMETERS) WITH (fillfactor = 10);
+-- view option (check_option=local) will not copied to new table
+\d+ t_sp4
+DROP VIEW t_spv1;

---

From: jian he <[email-masked]>
Date: Fri, 3 Apr 2026 19:53:22 +0800

Hi.

Because of https://cfbot.cputube.org/patch_6088.log
A trivial rebase is attached.

---

From: lakshmi <[email-masked]>
Date: Tue, 28 Apr 2026 12:58:53 +0530

Hi all,

I tested the patch on current HEAD.

INCLUDING PARAMETERS works as expected. It correctly copies storage
parameters. Duplicate parameters raise an error, and existing LIKE behavior
remains unchanged.

When used with a VIEW, parameters are ignored (since they are not valid for
tables), and the command completes without error.

Overall, the feature looks good from my testing. It might be worth
documenting the behavior for VIEWs.

Regards,
Lakshmi G