On 01/10/2026 19:25, Tomas Vondra wrote: > Multiple tables scenario, I think. I suggested asking for a "real" use > case on Discord, because (a) I still don't know if anyone actually needs > this feature, and (b) if there's such use case, it'd probably give us > insights what to do about duplicate comments.
Oh, I totally missed this discussion on discord. > Yes, I can construct a made-up example, but I struggle to create an > example where copying table comments would be meaningful. > > I think this lack of use case is partially due to the origin of the > patch. If it was written because of genuine need for the feature, we'd > have the use case by definition. But it was written "because it's > missing" and so no use case. Yeah, this is the root cause of this issue. I had to deal with this multiple tables scenario, which I didn't anticipated when proposing the patch, and couldn't find any good argument just to ignore it. But the lack of practical use cases is indeed argument enough to leave it alone for now. The support for multiple LIKE clauses is now removed from the patch. I added a variable in CreateStmtContext to count the numbers of LIKE clauses in transformTableLikeClause(), so that we can skip the comments in transformCreateStmt() if it's different than 1. Docs and tests were updated accordingly. Thanks! Best, Jim
From 62f3f4cb2121abd91c9d91d99e2bd9b973bd72b1 Mon Sep 17 00:00:00 2001 From: Jim Jones <[email protected]> Date: Thu, 1 Oct 2026 23:04:24 +0200 Subject: [PATCH v5] Add table comments in CREATE TABLE LIKE INCLUDING COMMENTS MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CREATE TABLE ... (LIKE ... INCLUDING COMMENTS), and therefore INCLUDING ALL, copies the comments on the source relation's columns, constraints, indexes, and extended statistics, but not the comment on the source relation itself. Copy that one too, so that INCLUDING COMMENTS covers every comment attached to the objects that LIKE copies. The table comment is copied only when the command contains a single LIKE clause. A table built from several source relations is not described by any one of their comments, and there is no obvious way to choose between or combine them, so in that case no table comment is copied. Comments on the copied columns, constraints, indexes, and statistics are unaffected by this. Author: Jim Jones <[email protected]> Reviewed-by: Alex Liapychev <[email protected]> Reviewed-by: Eddie Cho <[email protected]> Reviewed-by: Tomas Vondra <[email protected]> Reviewed-by: ZizhuanLiu X-MAN <[email protected]> Reviewed-by: Carlos Alves <[email protected]> Reviewed-by: Hüseyin Demir <[email protected]> Reviewed-by: Tom Lane <[email protected]> Reviewed-by: David. G. Johnston <[email protected]> Reviewed-by: Chao Li <[email protected]> Reviewed-by: Matheus Alcantara <[email protected]> Reviewed-by: Fujii Masao <[email protected]> Discussion: https://www.postgresql.org/message-id/flat/e08cb97f-0364-4002-9cda-3c16b42e4136%40uni-muenster.de --- doc/src/sgml/ref/create_table.sgml | 17 ++- src/backend/parser/parse_utilcmd.c | 48 ++++++++ .../regress/expected/create_table_like.out | 109 ++++++++++++++++++ src/test/regress/sql/create_table_like.sql | 70 +++++++++++ 4 files changed, 239 insertions(+), 5 deletions(-) diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml index fef24d8f3a2..4e36b121150 100644 --- a/doc/src/sgml/ref/create_table.sgml +++ b/doc/src/sgml/ref/create_table.sgml @@ -692,11 +692,18 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM <term><literal>INCLUDING COMMENTS</literal></term> <listitem> <para> - Comments for the copied columns, check constraints, - not-null constraints, indexes, and extended statistics will be - copied. The default behavior is to exclude comments, resulting in - the corresponding objects in the new table having no - comments. + Comments for the copied columns, check constraints, not-null + constraints, indexes, and extended statistics will be copied, as + will the comment on the source relation itself. The default + behavior is to exclude comments, resulting in the corresponding + objects in the new table having no comments. + </para> + <para> + The comment on the source relation is copied only if the command + contains a single <literal>LIKE</literal> clause. If there are + several, no source relation's comment is copied to the new table, + though the comments on their columns, constraints, indexes, and + extended statistics still are. </para> </listitem> </varlistentry> diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index f0c7755b8fb..92d05956a92 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -86,6 +86,8 @@ typedef struct List *fkconstraints; /* FOREIGN KEY constraints */ List *ixconstraints; /* index-creating constraints */ List *likeclauses; /* LIKE clauses that need post-processing */ + char *tablecomment; /* comment on the LIKE source relation */ + int nlikeclauses; /* # of LIKE clauses seen so far */ List *blist; /* "before list" of things to do before * creating the table */ List *alist; /* "after list" of things to do after creating @@ -251,6 +253,8 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString) cxt.fkconstraints = NIL; cxt.ixconstraints = NIL; cxt.likeclauses = NIL; + cxt.tablecomment = NULL; + cxt.nlikeclauses = 0; cxt.blist = NIL; cxt.alist = NIL; cxt.pkey = NULL; @@ -300,6 +304,27 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString) } } + /* + * Copy the comment on the LIKE source relation to the new table, but only + * if there is exactly one LIKE clause. A table built from several source + * relations is not described by any one of their comments, and there is + * no obvious way to combine them, so we copy none. + */ + if (cxt.nlikeclauses == 1 && cxt.tablecomment != NULL) + { + CommentStmt *cstmt = makeNode(CommentStmt); + + cstmt->objtype = cxt.isforeign ? OBJECT_FOREIGN_TABLE : OBJECT_TABLE; + if (cxt.relation->schemaname) + cstmt->object = (Node *) list_make2(makeString(cxt.relation->schemaname), + makeString(cxt.relation->relname)); + else + cstmt->object = (Node *) list_make1(makeString(cxt.relation->relname)); + cstmt->comment = cxt.tablecomment; + + cxt.alist = lappend(cxt.alist, cstmt); + } + /* * Transfer anything we already have in cxt.alist into save_alist, to keep * it separate from the output of transformIndexConstraints. (This may @@ -1311,6 +1336,27 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla } } + /* + * Remember the comment on the source relation itself, if requested. + * transformCreateStmt decides whether to copy it, since that depends on + * how many LIKE clauses there are. There's no need to look up the + * comment for any clause after the first. + */ + cxt->nlikeclauses++; + if (cxt->nlikeclauses == 1 && + (table_like_clause->options & CREATE_TABLE_LIKE_COMMENTS)) + { + /* A composite type's comment is attached to its pg_type entry */ + if (relation->rd_rel->relkind == RELKIND_COMPOSITE_TYPE) + cxt->tablecomment = GetComment(relation->rd_rel->reltype, + TypeRelationId, + 0); + else + cxt->tablecomment = GetComment(RelationGetRelid(relation), + RelationRelationId, + 0); + } + /* * We cannot yet deal with defaults, CHECK constraints, indexes, or * statistics, since we don't yet know what column numbers the copied @@ -3624,6 +3670,8 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, cxt.fkconstraints = NIL; cxt.ixconstraints = NIL; cxt.likeclauses = NIL; + cxt.tablecomment = NULL; + cxt.nlikeclauses = 0; cxt.blist = NIL; cxt.alist = NIL; cxt.pkey = NULL; diff --git a/src/test/regress/expected/create_table_like.out b/src/test/regress/expected/create_table_like.out index a23735b5fb4..8a491f338bc 100644 --- a/src/test/regress/expected/create_table_like.out +++ b/src/test/regress/expected/create_table_like.out @@ -698,6 +698,115 @@ SELECT attname, attcompression FROM pg_attribute e | (5 rows) +-- LIKE ... INCLUDING COMMENTS +-- Test copying the comment on the source relation itself +CREATE TABLE ctlt_comment1 (a int); +COMMENT ON TABLE ctlt_comment1 IS 'comment1'; +COMMENT ON COLUMN ctlt_comment1.a IS 'column a'; +CREATE FOREIGN TABLE ctlft_comment2 (b int) SERVER ctl_s0; +COMMENT ON FOREIGN TABLE ctlft_comment2 IS 'comment2'; +CREATE VIEW ctlv_comment3 AS SELECT 42 AS c; +COMMENT ON VIEW ctlv_comment3 IS 'comment3'; +CREATE TEMPORARY TABLE ctltt_comment4 (d int); +COMMENT ON TABLE ctltt_comment4 IS 'comment4'; +-- Single LIKE clause should copy table comment when INCLUDING COMMENTS is specified. +CREATE TABLE ctlt_single_comment (LIKE ctlt_comment1 INCLUDING COMMENTS); +SELECT obj_description('ctlt_single_comment'::regclass, 'pg_class') AS table_comment; + table_comment +--------------- + comment1 +(1 row) + +-- Single LIKE clause should copy table comment when INCLUDING ALL is specified. +CREATE TABLE ctlt_single_comment_all (LIKE ctlt_comment1 INCLUDING ALL); +SELECT obj_description('ctlt_single_comment_all'::regclass, 'pg_class') AS table_comment; + table_comment +--------------- + comment1 +(1 row) + +-- With several LIKE clauses no table comment is copied, even if only one of +-- them specifies INCLUDING COMMENTS +CREATE TABLE ctlt_one_of_many ( + LIKE ctlft_comment2, + LIKE ctlv_comment3 INCLUDING COMMENTS, + LIKE ctltt_comment4 INCLUDING ALL EXCLUDING COMMENTS +); +SELECT obj_description('ctlt_one_of_many'::regclass, 'pg_class') IS NULL AS no_comment; + no_comment +------------ + t +(1 row) + +-- Likewise if several of them specify INCLUDING COMMENTS (INCLUDING ALL implies +-- it); column comments are still copied +CREATE TABLE ctlt_multi_comments ( + LIKE ctlt_comment1 INCLUDING ALL, + LIKE ctlv_comment3 INCLUDING COMMENTS +); +SELECT obj_description('ctlt_multi_comments'::regclass, 'pg_class') IS NULL AS no_comment; + no_comment +------------ + t +(1 row) + +SELECT col_description('ctlt_multi_comments'::regclass, 1) AS column_comment; + column_comment +---------------- + column a +(1 row) + +-- Likewise if only one of the sources actually has a comment +CREATE TABLE ctlt_nocomment (e int); +CREATE TABLE ctlt_gap_comments ( + LIKE ctlt_nocomment INCLUDING COMMENTS, + LIKE ctlt_comment1 INCLUDING COMMENTS +); +SELECT obj_description('ctlt_gap_comments'::regclass, 'pg_class') IS NULL AS no_comment; + no_comment +------------ + t +(1 row) + +-- Test that INCLUDING COMMENTS works for target foreign tables +CREATE FOREIGN TABLE ctlft_comment4 (LIKE ctlt_comment1 INCLUDING COMMENTS) SERVER ctl_s0; +SELECT obj_description('ctlft_comment4'::regclass, 'pg_class') AS table_comment; + table_comment +--------------- + comment1 +(1 row) + +-- Test that INCLUDING COMMENTS works for target temporary tables +CREATE TEMPORARY TABLE ctltt_comment5 (LIKE ctlt_comment1 INCLUDING COMMENTS); +SELECT obj_description('ctltt_comment5'::regclass, 'pg_class') AS table_comment; + table_comment +--------------- + comment1 +(1 row) + +-- INCLUDING ALL EXCLUDING COMMENTS must not copy the comment +CREATE TABLE ctlt_no_comment (LIKE ctlt_comment1 INCLUDING ALL EXCLUDING COMMENTS); +SELECT obj_description('ctlt_no_comment'::regclass, 'pg_class') IS NULL AS no_comment; + no_comment +------------ + t +(1 row) + +-- A composite type's comment is stored on its pg_type entry, not on pg_class, +-- but it should be copied just the same +CREATE TYPE ctlty_comment6 AS (f int); +COMMENT ON TYPE ctlty_comment6 IS 'comment6'; +CREATE TABLE ctlt_type_comment (LIKE ctlty_comment6 INCLUDING COMMENTS); +SELECT obj_description('ctlt_type_comment'::regclass, 'pg_class') AS table_comment; + table_comment +--------------- + comment6 +(1 row) + +DROP TABLE ctlt_comment1, ctlt_one_of_many, ctlt_multi_comments, ctltt_comment4, ctltt_comment5, ctlt_single_comment, ctlt_nocomment, ctlt_gap_comments, ctlt_no_comment, ctlt_single_comment_all, ctlt_type_comment; +DROP FOREIGN TABLE ctlft_comment2, ctlft_comment4; +DROP VIEW ctlv_comment3; +DROP TYPE ctlty_comment6; -- LIKE ... INCLUDING STATISTICS with dropped columns in the parent, -- so stxkeys attnums are not contiguous. CREATE TABLE ctl_stats3_parent (a int, b int, c int); diff --git a/src/test/regress/sql/create_table_like.sql b/src/test/regress/sql/create_table_like.sql index d52a93ef131..1023e8c5a5c 100644 --- a/src/test/regress/sql/create_table_like.sql +++ b/src/test/regress/sql/create_table_like.sql @@ -276,6 +276,76 @@ CREATE FOREIGN TABLE ctl_foreign_table2(LIKE ctl_table INCLUDING ALL) SERVER ctl SELECT attname, attcompression FROM pg_attribute WHERE attrelid = 'ctl_foreign_table2'::regclass and attnum > 0 ORDER BY attnum; +-- LIKE ... INCLUDING COMMENTS +-- Test copying the comment on the source relation itself +CREATE TABLE ctlt_comment1 (a int); +COMMENT ON TABLE ctlt_comment1 IS 'comment1'; +COMMENT ON COLUMN ctlt_comment1.a IS 'column a'; +CREATE FOREIGN TABLE ctlft_comment2 (b int) SERVER ctl_s0; +COMMENT ON FOREIGN TABLE ctlft_comment2 IS 'comment2'; +CREATE VIEW ctlv_comment3 AS SELECT 42 AS c; +COMMENT ON VIEW ctlv_comment3 IS 'comment3'; +CREATE TEMPORARY TABLE ctltt_comment4 (d int); +COMMENT ON TABLE ctltt_comment4 IS 'comment4'; + +-- Single LIKE clause should copy table comment when INCLUDING COMMENTS is specified. +CREATE TABLE ctlt_single_comment (LIKE ctlt_comment1 INCLUDING COMMENTS); +SELECT obj_description('ctlt_single_comment'::regclass, 'pg_class') AS table_comment; + +-- Single LIKE clause should copy table comment when INCLUDING ALL is specified. +CREATE TABLE ctlt_single_comment_all (LIKE ctlt_comment1 INCLUDING ALL); +SELECT obj_description('ctlt_single_comment_all'::regclass, 'pg_class') AS table_comment; + +-- With several LIKE clauses no table comment is copied, even if only one of +-- them specifies INCLUDING COMMENTS +CREATE TABLE ctlt_one_of_many ( + LIKE ctlft_comment2, + LIKE ctlv_comment3 INCLUDING COMMENTS, + LIKE ctltt_comment4 INCLUDING ALL EXCLUDING COMMENTS +); +SELECT obj_description('ctlt_one_of_many'::regclass, 'pg_class') IS NULL AS no_comment; + +-- Likewise if several of them specify INCLUDING COMMENTS (INCLUDING ALL implies +-- it); column comments are still copied +CREATE TABLE ctlt_multi_comments ( + LIKE ctlt_comment1 INCLUDING ALL, + LIKE ctlv_comment3 INCLUDING COMMENTS +); +SELECT obj_description('ctlt_multi_comments'::regclass, 'pg_class') IS NULL AS no_comment; +SELECT col_description('ctlt_multi_comments'::regclass, 1) AS column_comment; + +-- Likewise if only one of the sources actually has a comment +CREATE TABLE ctlt_nocomment (e int); +CREATE TABLE ctlt_gap_comments ( + LIKE ctlt_nocomment INCLUDING COMMENTS, + LIKE ctlt_comment1 INCLUDING COMMENTS +); +SELECT obj_description('ctlt_gap_comments'::regclass, 'pg_class') IS NULL AS no_comment; + +-- Test that INCLUDING COMMENTS works for target foreign tables +CREATE FOREIGN TABLE ctlft_comment4 (LIKE ctlt_comment1 INCLUDING COMMENTS) SERVER ctl_s0; +SELECT obj_description('ctlft_comment4'::regclass, 'pg_class') AS table_comment; + +-- Test that INCLUDING COMMENTS works for target temporary tables +CREATE TEMPORARY TABLE ctltt_comment5 (LIKE ctlt_comment1 INCLUDING COMMENTS); +SELECT obj_description('ctltt_comment5'::regclass, 'pg_class') AS table_comment; + +-- INCLUDING ALL EXCLUDING COMMENTS must not copy the comment +CREATE TABLE ctlt_no_comment (LIKE ctlt_comment1 INCLUDING ALL EXCLUDING COMMENTS); +SELECT obj_description('ctlt_no_comment'::regclass, 'pg_class') IS NULL AS no_comment; + +-- A composite type's comment is stored on its pg_type entry, not on pg_class, +-- but it should be copied just the same +CREATE TYPE ctlty_comment6 AS (f int); +COMMENT ON TYPE ctlty_comment6 IS 'comment6'; +CREATE TABLE ctlt_type_comment (LIKE ctlty_comment6 INCLUDING COMMENTS); +SELECT obj_description('ctlt_type_comment'::regclass, 'pg_class') AS table_comment; + +DROP TABLE ctlt_comment1, ctlt_one_of_many, ctlt_multi_comments, ctltt_comment4, ctltt_comment5, ctlt_single_comment, ctlt_nocomment, ctlt_gap_comments, ctlt_no_comment, ctlt_single_comment_all, ctlt_type_comment; +DROP FOREIGN TABLE ctlft_comment2, ctlft_comment4; +DROP VIEW ctlv_comment3; +DROP TYPE ctlty_comment6; + -- LIKE ... INCLUDING STATISTICS with dropped columns in the parent, -- so stxkeys attnums are not contiguous. CREATE TABLE ctl_stats3_parent (a int, b int, c int); -- 2.55.0
