Replicate generated columns when 'publish_generated_columns' is set.
authorAmit Kapila <[email protected]>
Thu, 7 Nov 2024 03:28:49 +0000 (08:58 +0530)
committerAmit Kapila <[email protected]>
Thu, 7 Nov 2024 03:28:49 +0000 (08:58 +0530)
This  builds on the work done in commit 745217a051 by enabling the
replication of generated columns alongside regular column changes through
a new publication parameter: publish_generated_columns.

Example usage:
CREATE PUBLICATION pub1 FOR TABLE tab_gencol WITH (publish_generated_columns = true);

The column list takes precedence. If the generated columns are specified
in the column list, they will be replicated even if
'publish_generated_columns' is set to false. Conversely, if generated
columns are not included in the column list (assuming the user specifies a
column list), they will not be replicated even if
'publish_generated_columns' is true.

Author: Vignesh C, Shubham Khanna
Reviewed-by: Peter Smith, Amit Kapila, Hayato Kuroda, Shlok Kyal, Ajin Cherian, Hou Zhijie, Masahiko Sawada
Discussion: https://postgr.es/m/B80D17B2-2C8E-4C7D-87F2-E5B4BE3C069E@gmail.com

20 files changed:
doc/src/sgml/ddl.sgml
doc/src/sgml/protocol.sgml
doc/src/sgml/ref/create_publication.sgml
src/backend/catalog/pg_publication.c
src/backend/commands/publicationcmds.c
src/backend/replication/logical/proto.c
src/backend/replication/pgoutput/pgoutput.c
src/bin/pg_dump/pg_dump.c
src/bin/pg_dump/pg_dump.h
src/bin/pg_dump/t/002_pg_dump.pl
src/bin/psql/describe.c
src/bin/psql/tab-complete.in.c
src/include/catalog/catversion.h
src/include/catalog/pg_publication.h
src/include/replication/logicalproto.h
src/test/regress/expected/psql.out
src/test/regress/expected/publication.out
src/test/regress/sql/publication.sql
src/test/subscription/t/011_generated.pl
src/test/subscription/t/031_column_list.pl

index f02f67d7b868ca8c6988af9040afb69412f4633d..898b6ddc8dfafd5c92a9e5ceb911d3766c9c4050 100644 (file)
@@ -514,9 +514,11 @@ CREATE TABLE people (
     </listitem>
     <listitem>
      <para>
-      Generated columns can be replicated during logical replication by
-      including them in the column list of the
-      <command>CREATE PUBLICATION</command> command.
+      Generated columns are allowed to be replicated during logical replication
+      according to the <command>CREATE PUBLICATION</command> parameter
+      <link linkend="sql-createpublication-params-with-publish-generated-columns">
+      <literal>publish_generated_columns</literal></link> or by including them
+      in the column list of the <command>CREATE PUBLICATION</command> command.
      </para>
     </listitem>
    </itemizedlist>
index 71b6b2a535f55d96c9a1cf8a84585ffdf23532b7..4c0a1a006885f733bcb5e19ad0a2310d76a946ce 100644 (file)
@@ -7477,7 +7477,7 @@ psql "dbname=postgres replication=database" -c "IDENTIFY_SYSTEM;"
      </variablelist>
 
      <para>
-      Next, one of the following submessages appears for each column:
+      Next, one of the following submessages appears for each published column:
 
       <variablelist>
        <varlistentry>
index d2cac06fd76c827ed37a50cc48ae1a23567a8740..f8e217d661044688fdb68790fe896dc9352517ae 100644 (file)
@@ -189,6 +189,26 @@ CREATE PUBLICATION <replaceable class="parameter">name</replaceable>
         </listitem>
        </varlistentry>
 
+       <varlistentry id="sql-createpublication-params-with-publish-generated-columns">
+        <term><literal>publish_generated_columns</literal> (<type>boolean</type>)</term>
+        <listitem>
+         <para>
+          Specifies whether the generated columns present in the tables
+          associated with the publication should be replicated.
+          The default is <literal>false</literal>.
+         </para>
+
+         <note>
+          <para>
+           If the subscriber is from a release prior to 18, then initial table
+           synchronization won't copy generated columns even if parameter
+           <literal>publish_generated_columns</literal> is true in the
+           publisher.
+          </para>
+         </note>
+        </listitem>
+       </varlistentry>
+
        <varlistentry id="sql-createpublication-params-with-publish-via-partition-root">
         <term><literal>publish_via_partition_root</literal> (<type>boolean</type>)</term>
         <listitem>
index 17a6093d0695cfdd6bdd2a8ac9661c3161afc3e1..09e2dbdd10a951ef53822258ab0ae2847669bf0b 100644 (file)
@@ -256,6 +256,52 @@ is_schema_publication(Oid pubid)
    return result;
 }
 
+/*
+ * Returns true if the relation has column list associated with the
+ * publication, false otherwise.
+ *
+ * If a column list is found, the corresponding bitmap is returned through the
+ * cols parameter, if provided. The bitmap is constructed within the given
+ * memory context (mcxt).
+ */
+bool
+check_and_fetch_column_list(Publication *pub, Oid relid, MemoryContext mcxt,
+                           Bitmapset **cols)
+{
+   HeapTuple   cftuple;
+   bool        found = false;
+
+   if (pub->alltables)
+       return false;
+
+   cftuple = SearchSysCache2(PUBLICATIONRELMAP,
+                             ObjectIdGetDatum(relid),
+                             ObjectIdGetDatum(pub->oid));
+   if (HeapTupleIsValid(cftuple))
+   {
+       Datum       cfdatum;
+       bool        isnull;
+
+       /* Lookup the column list attribute. */
+       cfdatum = SysCacheGetAttr(PUBLICATIONRELMAP, cftuple,
+                                 Anum_pg_publication_rel_prattrs, &isnull);
+
+       /* Was a column list found? */
+       if (!isnull)
+       {
+           /* Build the column list bitmap in the given memory context. */
+           if (cols)
+               *cols = pub_collist_to_bitmapset(*cols, cfdatum, mcxt);
+
+           found = true;
+       }
+
+       ReleaseSysCache(cftuple);
+   }
+
+   return found;
+}
+
 /*
  * Gets the relations based on the publication partition option for a specified
  * relation.
@@ -573,6 +619,30 @@ pub_collist_to_bitmapset(Bitmapset *columns, Datum pubcols, MemoryContext mcxt)
    return result;
 }
 
+/*
+ * Returns a bitmap representing the columns of the specified table.
+ *
+ * Generated columns are included if include_gencols is true.
+ */
+Bitmapset *
+pub_form_cols_map(Relation relation, bool include_gencols)
+{
+   Bitmapset  *result = NULL;
+   TupleDesc   desc = RelationGetDescr(relation);
+
+   for (int i = 0; i < desc->natts; i++)
+   {
+       Form_pg_attribute att = TupleDescAttr(desc, i);
+
+       if (att->attisdropped || (att->attgenerated && !include_gencols))
+           continue;
+
+       result = bms_add_member(result, att->attnum);
+   }
+
+   return result;
+}
+
 /*
  * Insert new publication / schema mapping.
  */
@@ -998,6 +1068,7 @@ GetPublication(Oid pubid)
    pub->pubactions.pubdelete = pubform->pubdelete;
    pub->pubactions.pubtruncate = pubform->pubtruncate;
    pub->pubviaroot = pubform->pubviaroot;
+   pub->pubgencols = pubform->pubgencols;
 
    ReleaseSysCache(tup);
 
@@ -1205,7 +1276,7 @@ pg_get_publication_tables(PG_FUNCTION_ARGS)
            {
                Form_pg_attribute att = TupleDescAttr(desc, i);
 
-               if (att->attisdropped || att->attgenerated)
+               if (att->attisdropped || (att->attgenerated && !pub->pubgencols))
                    continue;
 
                attnums[nattnums++] = att->attnum;
index d6ffef374ea35399571d5a0b12d5f678026c6b88..0129db18c6ec974a1929841952f758f0a55b395f 100644 (file)
@@ -78,12 +78,15 @@ parse_publication_options(ParseState *pstate,
                          bool *publish_given,
                          PublicationActions *pubactions,
                          bool *publish_via_partition_root_given,
-                         bool *publish_via_partition_root)
+                         bool *publish_via_partition_root,
+                         bool *publish_generated_columns_given,
+                         bool *publish_generated_columns)
 {
    ListCell   *lc;
 
    *publish_given = false;
    *publish_via_partition_root_given = false;
+   *publish_generated_columns_given = false;
 
    /* defaults */
    pubactions->pubinsert = true;
@@ -91,6 +94,7 @@ parse_publication_options(ParseState *pstate,
    pubactions->pubdelete = true;
    pubactions->pubtruncate = true;
    *publish_via_partition_root = false;
+   *publish_generated_columns = false;
 
    /* Parse options */
    foreach(lc, options)
@@ -151,6 +155,13 @@ parse_publication_options(ParseState *pstate,
            *publish_via_partition_root_given = true;
            *publish_via_partition_root = defGetBoolean(defel);
        }
+       else if (strcmp(defel->defname, "publish_generated_columns") == 0)
+       {
+           if (*publish_generated_columns_given)
+               errorConflictingDefElem(defel, pstate);
+           *publish_generated_columns_given = true;
+           *publish_generated_columns = defGetBoolean(defel);
+       }
        else
            ereport(ERROR,
                    (errcode(ERRCODE_SYNTAX_ERROR),
@@ -737,6 +748,8 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
    PublicationActions pubactions;
    bool        publish_via_partition_root_given;
    bool        publish_via_partition_root;
+   bool        publish_generated_columns_given;
+   bool        publish_generated_columns;
    AclResult   aclresult;
    List       *relations = NIL;
    List       *schemaidlist = NIL;
@@ -776,7 +789,9 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
                              stmt->options,
                              &publish_given, &pubactions,
                              &publish_via_partition_root_given,
-                             &publish_via_partition_root);
+                             &publish_via_partition_root,
+                             &publish_generated_columns_given,
+                             &publish_generated_columns);
 
    puboid = GetNewOidWithIndex(rel, PublicationObjectIndexId,
                                Anum_pg_publication_oid);
@@ -793,6 +808,8 @@ CreatePublication(ParseState *pstate, CreatePublicationStmt *stmt)
        BoolGetDatum(pubactions.pubtruncate);
    values[Anum_pg_publication_pubviaroot - 1] =
        BoolGetDatum(publish_via_partition_root);
+   values[Anum_pg_publication_pubgencols - 1] =
+       BoolGetDatum(publish_generated_columns);
 
    tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
 
@@ -878,6 +895,8 @@ AlterPublicationOptions(ParseState *pstate, AlterPublicationStmt *stmt,
    PublicationActions pubactions;
    bool        publish_via_partition_root_given;
    bool        publish_via_partition_root;
+   bool        publish_generated_columns_given;
+   bool        publish_generated_columns;
    ObjectAddress obj;
    Form_pg_publication pubform;
    List       *root_relids = NIL;
@@ -887,7 +906,9 @@ AlterPublicationOptions(ParseState *pstate, AlterPublicationStmt *stmt,
                              stmt->options,
                              &publish_given, &pubactions,
                              &publish_via_partition_root_given,
-                             &publish_via_partition_root);
+                             &publish_via_partition_root,
+                             &publish_generated_columns_given,
+                             &publish_generated_columns);
 
    pubform = (Form_pg_publication) GETSTRUCT(tup);
 
@@ -997,6 +1018,12 @@ AlterPublicationOptions(ParseState *pstate, AlterPublicationStmt *stmt,
        replaces[Anum_pg_publication_pubviaroot - 1] = true;
    }
 
+   if (publish_generated_columns_given)
+   {
+       values[Anum_pg_publication_pubgencols - 1] = BoolGetDatum(publish_generated_columns);
+       replaces[Anum_pg_publication_pubgencols - 1] = true;
+   }
+
    tup = heap_modify_tuple(tup, RelationGetDescr(rel), values, nulls,
                            replaces);
 
index ac4af53feba32810b7cb7af1479b7365f2c8405c..2c2085b2f98854064031440320f0694dc5b48da7 100644 (file)
 #define TRUNCATE_RESTART_SEQS  (1<<1)
 
 static void logicalrep_write_attrs(StringInfo out, Relation rel,
-                                  Bitmapset *columns);
+                                  Bitmapset *columns, bool include_gencols);
 static void logicalrep_write_tuple(StringInfo out, Relation rel,
                                   TupleTableSlot *slot,
-                                  bool binary, Bitmapset *columns);
+                                  bool binary, Bitmapset *columns,
+                                  bool include_gencols);
 static void logicalrep_read_attrs(StringInfo in, LogicalRepRelation *rel);
 static void logicalrep_read_tuple(StringInfo in, LogicalRepTupleData *tuple);
 
@@ -399,7 +400,8 @@ logicalrep_read_origin(StringInfo in, XLogRecPtr *origin_lsn)
  */
 void
 logicalrep_write_insert(StringInfo out, TransactionId xid, Relation rel,
-                       TupleTableSlot *newslot, bool binary, Bitmapset *columns)
+                       TupleTableSlot *newslot, bool binary,
+                       Bitmapset *columns, bool include_gencols)
 {
    pq_sendbyte(out, LOGICAL_REP_MSG_INSERT);
 
@@ -411,7 +413,7 @@ logicalrep_write_insert(StringInfo out, TransactionId xid, Relation rel,
    pq_sendint32(out, RelationGetRelid(rel));
 
    pq_sendbyte(out, 'N');      /* new tuple follows */
-   logicalrep_write_tuple(out, rel, newslot, binary, columns);
+   logicalrep_write_tuple(out, rel, newslot, binary, columns, include_gencols);
 }
 
 /*
@@ -444,7 +446,7 @@ logicalrep_read_insert(StringInfo in, LogicalRepTupleData *newtup)
 void
 logicalrep_write_update(StringInfo out, TransactionId xid, Relation rel,
                        TupleTableSlot *oldslot, TupleTableSlot *newslot,
-                       bool binary, Bitmapset *columns)
+                       bool binary, Bitmapset *columns, bool include_gencols)
 {
    pq_sendbyte(out, LOGICAL_REP_MSG_UPDATE);
 
@@ -465,11 +467,12 @@ logicalrep_write_update(StringInfo out, TransactionId xid, Relation rel,
            pq_sendbyte(out, 'O');  /* old tuple follows */
        else
            pq_sendbyte(out, 'K');  /* old key follows */
-       logicalrep_write_tuple(out, rel, oldslot, binary, columns);
+       logicalrep_write_tuple(out, rel, oldslot, binary, columns,
+                              include_gencols);
    }
 
    pq_sendbyte(out, 'N');      /* new tuple follows */
-   logicalrep_write_tuple(out, rel, newslot, binary, columns);
+   logicalrep_write_tuple(out, rel, newslot, binary, columns, include_gencols);
 }
 
 /*
@@ -519,7 +522,7 @@ logicalrep_read_update(StringInfo in, bool *has_oldtuple,
 void
 logicalrep_write_delete(StringInfo out, TransactionId xid, Relation rel,
                        TupleTableSlot *oldslot, bool binary,
-                       Bitmapset *columns)
+                       Bitmapset *columns, bool include_gencols)
 {
    Assert(rel->rd_rel->relreplident == REPLICA_IDENTITY_DEFAULT ||
           rel->rd_rel->relreplident == REPLICA_IDENTITY_FULL ||
@@ -539,7 +542,7 @@ logicalrep_write_delete(StringInfo out, TransactionId xid, Relation rel,
    else
        pq_sendbyte(out, 'K');  /* old key follows */
 
-   logicalrep_write_tuple(out, rel, oldslot, binary, columns);
+   logicalrep_write_tuple(out, rel, oldslot, binary, columns, include_gencols);
 }
 
 /*
@@ -655,7 +658,7 @@ logicalrep_write_message(StringInfo out, TransactionId xid, XLogRecPtr lsn,
  */
 void
 logicalrep_write_rel(StringInfo out, TransactionId xid, Relation rel,
-                    Bitmapset *columns)
+                    Bitmapset *columns, bool include_gencols)
 {
    char       *relname;
 
@@ -677,7 +680,7 @@ logicalrep_write_rel(StringInfo out, TransactionId xid, Relation rel,
    pq_sendbyte(out, rel->rd_rel->relreplident);
 
    /* send the attribute info */
-   logicalrep_write_attrs(out, rel, columns);
+   logicalrep_write_attrs(out, rel, columns, include_gencols);
 }
 
 /*
@@ -754,7 +757,7 @@ logicalrep_read_typ(StringInfo in, LogicalRepTyp *ltyp)
  */
 static void
 logicalrep_write_tuple(StringInfo out, Relation rel, TupleTableSlot *slot,
-                      bool binary, Bitmapset *columns)
+                      bool binary, Bitmapset *columns, bool include_gencols)
 {
    TupleDesc   desc;
    Datum      *values;
@@ -768,7 +771,7 @@ logicalrep_write_tuple(StringInfo out, Relation rel, TupleTableSlot *slot,
    {
        Form_pg_attribute att = TupleDescAttr(desc, i);
 
-       if (!logicalrep_should_publish_column(att, columns))
+       if (!logicalrep_should_publish_column(att, columns, include_gencols))
            continue;
 
        nliveatts++;
@@ -786,7 +789,7 @@ logicalrep_write_tuple(StringInfo out, Relation rel, TupleTableSlot *slot,
        Form_pg_type typclass;
        Form_pg_attribute att = TupleDescAttr(desc, i);
 
-       if (!logicalrep_should_publish_column(att, columns))
+       if (!logicalrep_should_publish_column(att, columns, include_gencols))
            continue;
 
        if (isnull[i])
@@ -904,7 +907,8 @@ logicalrep_read_tuple(StringInfo in, LogicalRepTupleData *tuple)
  * Write relation attribute metadata to the stream.
  */
 static void
-logicalrep_write_attrs(StringInfo out, Relation rel, Bitmapset *columns)
+logicalrep_write_attrs(StringInfo out, Relation rel, Bitmapset *columns,
+                      bool include_gencols)
 {
    TupleDesc   desc;
    int         i;
@@ -919,7 +923,7 @@ logicalrep_write_attrs(StringInfo out, Relation rel, Bitmapset *columns)
    {
        Form_pg_attribute att = TupleDescAttr(desc, i);
 
-       if (!logicalrep_should_publish_column(att, columns))
+       if (!logicalrep_should_publish_column(att, columns, include_gencols))
            continue;
 
        nliveatts++;
@@ -937,7 +941,7 @@ logicalrep_write_attrs(StringInfo out, Relation rel, Bitmapset *columns)
        Form_pg_attribute att = TupleDescAttr(desc, i);
        uint8       flags = 0;
 
-       if (!logicalrep_should_publish_column(att, columns))
+       if (!logicalrep_should_publish_column(att, columns, include_gencols))
            continue;
 
        /* REPLICA IDENTITY FULL means all columns are sent as part of key. */
@@ -1248,29 +1252,26 @@ logicalrep_message_type(LogicalRepMsgType action)
 /*
  * Check if the column 'att' of a table should be published.
  *
- * 'columns' represents the column list specified for that table in the
- * publication.
+ * 'columns' represents the publication column list (if any) for that table.
  *
- * Note that generated columns can be present only in 'columns' list.
+ * 'include_gencols' flag indicates whether generated columns should be
+ * published when there is no column list. Typically, this will have the same
+ * value as the 'publish_generated_columns' publication parameter.
+ *
+ * Note that generated columns can be published only when present in a
+ * publication column list, or when include_gencols is true.
  */
 bool
-logicalrep_should_publish_column(Form_pg_attribute att, Bitmapset *columns)
+logicalrep_should_publish_column(Form_pg_attribute att, Bitmapset *columns,
+                                bool include_gencols)
 {
    if (att->attisdropped)
        return false;
 
-   /*
-    * Skip publishing generated columns if they are not included in the
-    * column list.
-    */
-   if (!columns && att->attgenerated)
-       return false;
-
-   /*
-    * Check if a column is covered by a column list.
-    */
-   if (columns && !bms_is_member(att->attnum, columns))
-       return false;
+   /* If a column list is provided, publish only the cols in that list. */
+   if (columns)
+       return bms_is_member(att->attnum, columns);
 
-   return true;
+   /* All non-generated columns are always published. */
+   return att->attgenerated ? include_gencols : true;
 }
index 12c17359063750c31aa0728640630a44e64f60b8..a6002b223df784a98815c818dc9406f607bdffd8 100644 (file)
@@ -84,9 +84,6 @@ static bool publications_valid;
 static List *LoadPublications(List *pubnames);
 static void publication_invalidation_cb(Datum arg, int cacheid,
                                        uint32 hashvalue);
-static void send_relation_and_attrs(Relation relation, TransactionId xid,
-                                   LogicalDecodingContext *ctx,
-                                   Bitmapset *columns);
 static void send_repl_origin(LogicalDecodingContext *ctx,
                             RepOriginId origin_id, XLogRecPtr origin_lsn,
                             bool send_origin);
@@ -129,6 +126,12 @@ typedef struct RelationSyncEntry
    bool        replicate_valid;    /* overall validity flag for entry */
 
    bool        schema_sent;
+
+   /*
+    * This is set if the 'publish_generated_columns' parameter is true, and
+    * the relation contains generated columns.
+    */
+   bool        include_gencols;
    List       *streamed_txns;  /* streamed toplevel transactions with this
                                 * schema */
 
@@ -213,6 +216,9 @@ static void init_rel_sync_cache(MemoryContext cachectx);
 static void cleanup_rel_sync_cache(TransactionId xid, bool is_commit);
 static RelationSyncEntry *get_rel_sync_entry(PGOutputData *data,
                                             Relation relation);
+static void send_relation_and_attrs(Relation relation, TransactionId xid,
+                                   LogicalDecodingContext *ctx,
+                                   RelationSyncEntry *relentry);
 static void rel_sync_cache_relation_cb(Datum arg, Oid relid);
 static void rel_sync_cache_publication_cb(Datum arg, int cacheid,
                                          uint32 hashvalue);
@@ -731,11 +737,11 @@ maybe_send_schema(LogicalDecodingContext *ctx,
    {
        Relation    ancestor = RelationIdGetRelation(relentry->publish_as_relid);
 
-       send_relation_and_attrs(ancestor, xid, ctx, relentry->columns);
+       send_relation_and_attrs(ancestor, xid, ctx, relentry);
        RelationClose(ancestor);
    }
 
-   send_relation_and_attrs(relation, xid, ctx, relentry->columns);
+   send_relation_and_attrs(relation, xid, ctx, relentry);
 
    if (data->in_)
        set_schema_sent_in_streamed_txn(relentry, topxid);
@@ -749,9 +755,11 @@ maybe_send_schema(LogicalDecodingContext *ctx,
 static void
 send_relation_and_attrs(Relation relation, TransactionId xid,
                        LogicalDecodingContext *ctx,
-                       Bitmapset *columns)
+                       RelationSyncEntry *relentry)
 {
    TupleDesc   desc = RelationGetDescr(relation);
+   Bitmapset  *columns = relentry->columns;
+   bool        include_gencols = relentry->include_gencols;
    int         i;
 
    /*
@@ -766,7 +774,7 @@ send_relation_and_attrs(Relation relation, TransactionId xid,
    {
        Form_pg_attribute att = TupleDescAttr(desc, i);
 
-       if (!logicalrep_should_publish_column(att, columns))
+       if (!logicalrep_should_publish_column(att, columns, include_gencols))
            continue;
 
        if (att->atttypid < FirstGenbkiObjectId)
@@ -778,7 +786,7 @@ send_relation_and_attrs(Relation relation, TransactionId xid,
    }
 
    OutputPluginPrepareWrite(ctx, false);
-   logicalrep_write_rel(ctx->out, xid, relation, columns);
+   logicalrep_write_rel(ctx->out, xid, relation, columns, include_gencols);
    OutputPluginWrite(ctx, false);
 }
 
@@ -1004,6 +1012,66 @@ pgoutput_row_filter_init(PGOutputData *data, List *publications,
    }
 }
 
+/*
+ * If the table contains a generated column, check for any conflicting
+ * values of 'publish_generated_columns' parameter in the publications.
+ */
+static void
+check_and_init_gencol(PGOutputData *data, List *publications,
+                     RelationSyncEntry *entry)
+{
+   Relation    relation = RelationIdGetRelation(entry->publish_as_relid);
+   TupleDesc   desc = RelationGetDescr(relation);
+   bool        gencolpresent = false;
+   bool        first = true;
+
+   /* Check if there is any generated column present. */
+   for (int i = 0; i < desc->natts; i++)
+   {
+       Form_pg_attribute att = TupleDescAttr(desc, i);
+
+       if (att->attgenerated)
+       {
+           gencolpresent = true;
+           break;
+       }
+   }
+
+   /* There are no generated columns to be published. */
+   if (!gencolpresent)
+   {
+       entry->include_gencols = false;
+       return;
+   }
+
+   /*
+    * There may be a conflicting value for 'publish_generated_columns'
+    * parameter in the publications.
+    */
+   foreach_ptr(Publication, pub, publications)
+   {
+       /*
+        * The column list takes precedence over the
+        * 'publish_generated_columns' parameter. Those will be checked later,
+        * see pgoutput_column_list_init.
+        */
+       if (check_and_fetch_column_list(pub, entry->publish_as_relid, NULL, NULL))
+           continue;
+
+       if (first)
+       {
+           entry->include_gencols = pub->pubgencols;
+           first = false;
+       }
+       else if (entry->include_gencols != pub->pubgencols)
+           ereport(ERROR,
+                   errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                   errmsg("cannot use different values of publish_generated_columns for table \"%s.%s\" in different publications",
+                          get_namespace_name(RelationGetNamespace(relation)),
+                          RelationGetRelationName(relation)));
+   }
+}
+
 /*
  * Initialize the column list.
  */
@@ -1014,6 +1082,10 @@ pgoutput_column_list_init(PGOutputData *data, List *publications,
    ListCell   *lc;
    bool        first = true;
    Relation    relation = RelationIdGetRelation(entry->publish_as_relid);
+   bool        found_pub_collist = false;
+   Bitmapset  *relcols = NULL;
+
+   pgoutput_ensure_entry_cxt(data, entry);
 
    /*
     * Find if there are any column lists for this relation. If there are,
@@ -1027,93 +1099,39 @@ pgoutput_column_list_init(PGOutputData *data, List *publications,
     * fetch_table_list. But one can later change the publication so we still
     * need to check all the given publication-table mappings and report an
     * error if any publications have a different column list.
-    *
-    * FOR ALL TABLES and FOR TABLES IN SCHEMA imply "don't use column list".
     */
    foreach(lc, publications)
    {
        Publication *pub = lfirst(lc);
-       HeapTuple   cftuple = NULL;
-       Datum       cfdatum = 0;
        Bitmapset  *cols = NULL;
 
+       /* Retrieve the bitmap of columns for a column list publication. */
+       found_pub_collist |= check_and_fetch_column_list(pub,
+                                                        entry->publish_as_relid,
+                                                        entry->entry_cxt, &cols);
+
        /*
-        * If the publication is FOR ALL TABLES then it is treated the same as
-        * if there are no column lists (even if other publications have a
-        * list).
+        * For non-column list publications â€” e.g. TABLE (without a column
+        * list), ALL TABLES, or ALL TABLES IN SCHEMA, we consider all columns
+        * of the table (including generated columns when
+        * 'publish_generated_columns' parameter is true).
         */
-       if (!pub->alltables)
+       if (!cols)
        {
-           bool        pub_no_list = true;
-
            /*
-            * Check for the presence of a column list in this publication.
-            *
-            * Note: If we find no pg_publication_rel row, it's a publication
-            * defined for a whole schema, so it can't have a column list,
-            * just like a FOR ALL TABLES publication.
+            * Cache the table columns for the first publication with no
+            * specified column list to detect publication with a different
+            * column list.
             */
-           cftuple = SearchSysCache2(PUBLICATIONRELMAP,
-                                     ObjectIdGetDatum(entry->publish_as_relid),
-                                     ObjectIdGetDatum(pub->oid));
-
-           if (HeapTupleIsValid(cftuple))
+           if (!relcols && (list_length(publications) > 1))
            {
-               /* Lookup the column list attribute. */
-               cfdatum = SysCacheGetAttr(PUBLICATIONRELMAP, cftuple,
-                                         Anum_pg_publication_rel_prattrs,
-                                         &pub_no_list);
-
-               /* Build the column list bitmap in the per-entry context. */
-               if (!pub_no_list)   /* when not null */
-               {
-                   int         i;
-                   int         nliveatts = 0;
-                   TupleDesc   desc = RelationGetDescr(relation);
-                   bool        att_gen_present = false;
-
-                   pgoutput_ensure_entry_cxt(data, entry);
-
-                   cols = pub_collist_to_bitmapset(cols, cfdatum,
-                                                   entry->entry_cxt);
+               MemoryContext oldcxt = MemoryContextSwitchTo(entry->entry_cxt);
 
-                   /* Get the number of live attributes. */
-                   for (i = 0; i < desc->natts; i++)
-                   {
-                       Form_pg_attribute att = TupleDescAttr(desc, i);
-
-                       if (att->attisdropped)
-                           continue;
-
-                       if (att->attgenerated)
-                       {
-                           /*
-                            * Generated cols are skipped unless they are
-                            * present in a column list.
-                            */
-                           if (!bms_is_member(att->attnum, cols))
-                               continue;
-
-                           att_gen_present = true;
-                       }
-
-                       nliveatts++;
-                   }
-
-                   /*
-                    * Generated attributes are published only when they are
-                    * present in the column list. Otherwise, a NULL column
-                    * list means publish all columns.
-                    */
-                   if (!att_gen_present && bms_num_members(cols) == nliveatts)
-                   {
-                       bms_free(cols);
-                       cols = NULL;
-                   }
-               }
-
-               ReleaseSysCache(cftuple);
+               relcols = pub_form_cols_map(relation, entry->include_gencols);
+               MemoryContextSwitchTo(oldcxt);
            }
+
+           cols = relcols;
        }
 
        if (first)
@@ -1129,6 +1147,13 @@ pgoutput_column_list_init(PGOutputData *data, List *publications,
                           RelationGetRelationName(relation)));
    }                           /* loop all subscribed publications */
 
+   /*
+    * If no column list publications exist, columns to be published will be
+    * computed later according to the 'publish_generated_columns' parameter.
+    */
+   if (!found_pub_collist)
+       entry->columns = NULL;
+
    RelationClose(relation);
 }
 
@@ -1541,15 +1566,18 @@ pgoutput_change(LogicalDecodingContext *ctx, ReorderBufferTXN *txn,
    {
        case REORDER_BUFFER_CHANGE_INSERT:
            logicalrep_write_insert(ctx->out, xid, targetrel, new_slot,
-                                   data->binary, relentry->columns);
+                                   data->binary, relentry->columns,
+                                   relentry->include_gencols);
            break;
        case REORDER_BUFFER_CHANGE_UPDATE:
            logicalrep_write_update(ctx->out, xid, targetrel, old_slot,
-                                   new_slot, data->binary, relentry->columns);
+                                   new_slot, data->binary, relentry->columns,
+                                   relentry->include_gencols);
            break;
        case REORDER_BUFFER_CHANGE_DELETE:
            logicalrep_write_delete(ctx->out, xid, targetrel, old_slot,
-                                   data->binary, relentry->columns);
+                                   data->binary, relentry->columns,
+                                   relentry->include_gencols);
            break;
        default:
            Assert(false);
@@ -2000,6 +2028,7 @@ get_rel_sync_entry(PGOutputData *data, Relation relation)
    {
        entry->replicate_valid = false;
        entry->schema_sent = false;
+       entry->include_gencols = false;
        entry->streamed_txns = NIL;
        entry->pubactions.pubinsert = entry->pubactions.pubupdate =
            entry->pubactions.pubdelete = entry->pubactions.pubtruncate = false;
@@ -2052,6 +2081,7 @@ get_rel_sync_entry(PGOutputData *data, Relation relation)
         * earlier definition.
         */
        entry->schema_sent = false;
+       entry->include_gencols = false;
        list_free(entry->streamed_txns);
        entry->streamed_txns = NIL;
        bms_free(entry->columns);
@@ -2223,6 +2253,9 @@ get_rel_sync_entry(PGOutputData *data, Relation relation)
            /* Initialize the row filter */
            pgoutput_row_filter_init(data, rel_publications, entry);
 
+           /* Check whether to publish generated columns. */
+           check_and_init_gencol(data, rel_publications, entry);
+
            /* Initialize the column list */
            pgoutput_column_list_init(data, rel_publications, entry);
        }
index b2f4eb2c6de7a7e482a3f8c59ab8a4e09cc8073f..b7b822da6242fa91c56244dbb6f8401bd6e54d59 100644 (file)
@@ -4280,6 +4280,7 @@ getPublications(Archive *fout)
    int         i_pubdelete;
    int         i_pubtruncate;
    int         i_pubviaroot;
+   int         i_pubgencols;
    int         i,
                ntups;
 
@@ -4289,24 +4290,26 @@ getPublications(Archive *fout)
    query = createPQExpBuffer();
 
    /* Get the publications. */
+   appendPQExpBufferStr(query, "SELECT p.tableoid, p.oid, p.pubname, "
+                        "p.pubowner, p.puballtables, p.pubinsert, "
+                        "p.pubupdate, p.pubdelete, ");
+
+   if (fout->remoteVersion >= 110000)
+       appendPQExpBufferStr(query, "p.pubtruncate, ");
+   else
+       appendPQExpBufferStr(query, "false AS pubtruncate, ");
+
    if (fout->remoteVersion >= 130000)
-       appendPQExpBufferStr(query,
-                            "SELECT p.tableoid, p.oid, p.pubname, "
-                            "p.pubowner, "
-                            "p.puballtables, p.pubinsert, p.pubupdate, p.pubdelete, p.pubtruncate, p.pubviaroot "
-                            "FROM pg_publication p");
-   else if (fout->remoteVersion >= 110000)
-       appendPQExpBufferStr(query,
-                            "SELECT p.tableoid, p.oid, p.pubname, "
-                            "p.pubowner, "
-                            "p.puballtables, p.pubinsert, p.pubupdate, p.pubdelete, p.pubtruncate, false AS pubviaroot "
-                            "FROM pg_publication p");
+       appendPQExpBufferStr(query, "p.pubviaroot, ");
    else
-       appendPQExpBufferStr(query,
-                            "SELECT p.tableoid, p.oid, p.pubname, "
-                            "p.pubowner, "
-                            "p.puballtables, p.pubinsert, p.pubupdate, p.pubdelete, false AS pubtruncate, false AS pubviaroot "
-                            "FROM pg_publication p");
+       appendPQExpBufferStr(query, "false AS pubviaroot, ");
+
+   if (fout->remoteVersion >= 180000)
+       appendPQExpBufferStr(query, "p.pubgencols ");
+   else
+       appendPQExpBufferStr(query, "false AS pubgencols ");
+
+   appendPQExpBufferStr(query, "FROM pg_publication p");
 
    res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
 
@@ -4325,6 +4328,7 @@ getPublications(Archive *fout)
    i_pubdelete = PQfnumber(res, "pubdelete");
    i_pubtruncate = PQfnumber(res, "pubtruncate");
    i_pubviaroot = PQfnumber(res, "pubviaroot");
+   i_pubgencols = PQfnumber(res, "pubgencols");
 
    pubinfo = pg_malloc(ntups * sizeof(PublicationInfo));
 
@@ -4349,6 +4353,8 @@ getPublications(Archive *fout)
            (strcmp(PQgetvalue(res, i, i_pubtruncate), "t") == 0);
        pubinfo[i].pubviaroot =
            (strcmp(PQgetvalue(res, i, i_pubviaroot), "t") == 0);
+       pubinfo[i].pubgencols =
+           (strcmp(PQgetvalue(res, i, i_pubgencols), "t") == 0);
 
        /* Decide whether we want to dump it */
        selectDumpableObject(&(pubinfo[i].dobj), fout);
@@ -4430,6 +4436,9 @@ dumpPublication(Archive *fout, const PublicationInfo *pubinfo)
    if (pubinfo->pubviaroot)
        appendPQExpBufferStr(query, ", publish_via_partition_root = true");
 
+   if (pubinfo->pubgencols)
+       appendPQExpBufferStr(query, ", publish_generated_columns = true");
+
    appendPQExpBufferStr(query, ");\n");
 
    if (pubinfo->dobj.dump & DUMP_COMPONENT_DEFINITION)
index 9f907ed5ad4b568ed0ea714f76cac432a5a00ba0..c1552ead452abbeabec9a10bcc2d38c6c030bcb4 100644 (file)
@@ -626,6 +626,7 @@ typedef struct _PublicationInfo
    bool        pubdelete;
    bool        pubtruncate;
    bool        pubviaroot;
+   bool        pubgencols;
 } PublicationInfo;
 
 /*
index ac60829d686eab9dde48412d8ccc5284a13a6a72..213904440f65651209b690862f6c040ef0e17850 100644 (file)
@@ -2986,6 +2986,16 @@ my %tests = (
        like => { %full_runs, section_post_data => 1, },
    },
 
+   'CREATE PUBLICATION pub5' => {
+       create_order => 50,
+       create_sql =>
+         'CREATE PUBLICATION pub5 WITH (publish_generated_columns = true);',
+       regexp => qr/^
+           \QCREATE PUBLICATION pub5 WITH (publish = 'insert, update, delete, truncate', publish_generated_columns = true);\E
+           /xm,
+       like => { %full_runs, section_post_data => 1, },
+   },
+
    'CREATE SUBSCRIPTION sub1' => {
        create_order => 50,
        create_sql => 'CREATE SUBSCRIPTION sub1
index 37b43fb1221de8554a734b4906001265a7472138..bbe632cc792286ef325d707c0460e890ac8ae58b 100644 (file)
@@ -6232,7 +6232,7 @@ listPublications(const char *pattern)
    PQExpBufferData buf;
    PGresult   *res;
    printQueryOpt myopt = pset.popt;
-   static const bool translate_columns[] = {false, false, false, false, false, false, false, false};
+   static const bool translate_columns[] = {false, false, false, false, false, false, false, false, false};
 
    if (pset.sversion < 100000)
    {
@@ -6263,6 +6263,10 @@ listPublications(const char *pattern)
        appendPQExpBuffer(&buf,
                          ",\n  pubtruncate AS \"%s\"",
                          gettext_noop("Truncates"));
+   if (pset.sversion >= 180000)
+       appendPQExpBuffer(&buf,
+                         ",\n  pubgencols AS \"%s\"",
+                         gettext_noop("Generated columns"));
    if (pset.sversion >= 130000)
        appendPQExpBuffer(&buf,
                          ",\n  pubviaroot AS \"%s\"",
@@ -6355,6 +6359,7 @@ describePublications(const char *pattern)
    int         i;
    PGresult   *res;
    bool        has_pubtruncate;
+   bool        has_pubgencols;
    bool        has_pubviaroot;
 
    PQExpBufferData title;
@@ -6371,6 +6376,7 @@ describePublications(const char *pattern)
    }
 
    has_pubtruncate = (pset.sversion >= 110000);
+   has_pubgencols = (pset.sversion >= 180000);
    has_pubviaroot = (pset.sversion >= 130000);
 
    initPQExpBuffer(&buf);
@@ -6382,9 +6388,13 @@ describePublications(const char *pattern)
    if (has_pubtruncate)
        appendPQExpBufferStr(&buf,
                             ", pubtruncate");
+   if (has_pubgencols)
+       appendPQExpBufferStr(&buf,
+                            ", pubgencols");
    if (has_pubviaroot)
        appendPQExpBufferStr(&buf,
                             ", pubviaroot");
+
    appendPQExpBufferStr(&buf,
                         "\nFROM pg_catalog.pg_publication\n");
 
@@ -6434,6 +6444,8 @@ describePublications(const char *pattern)
 
        if (has_pubtruncate)
            ncols++;
+       if (has_pubgencols)
+           ncols++;
        if (has_pubviaroot)
            ncols++;
 
@@ -6448,6 +6460,8 @@ describePublications(const char *pattern)
        printTableAddHeader(&cont, gettext_noop("Deletes"), true, align);
        if (has_pubtruncate)
            printTableAddHeader(&cont, gettext_noop("Truncates"), true, align);
+       if (has_pubgencols)
+           printTableAddHeader(&cont, gettext_noop("Generated columns"), true, align);
        if (has_pubviaroot)
            printTableAddHeader(&cont, gettext_noop("Via root"), true, align);
 
@@ -6458,8 +6472,10 @@ describePublications(const char *pattern)
        printTableAddCell(&cont, PQgetvalue(res, i, 6), false, false);
        if (has_pubtruncate)
            printTableAddCell(&cont, PQgetvalue(res, i, 7), false, false);
-       if (has_pubviaroot)
+       if (has_pubgencols)
            printTableAddCell(&cont, PQgetvalue(res, i, 8), false, false);
+       if (has_pubviaroot)
+           printTableAddCell(&cont, PQgetvalue(res, i, 9), false, false);
 
        if (!puballtables)
        {
index 1be0056af7382fa3abbf44db5b8124470b37e14d..fad2277991ddce84c160e4b026ae360afff07b28 100644 (file)
@@ -2261,7 +2261,7 @@ match_previous_words(int pattern_id,
                                 "CURRENT_SCHEMA");
    /* ALTER PUBLICATION <name> SET ( */
    else if (Matches("ALTER", "PUBLICATION", MatchAny, MatchAnyN, "SET", "("))
-       COMPLETE_WITH("publish", "publish_via_partition_root");
+       COMPLETE_WITH("publish", "publish_generated_columns", "publish_via_partition_root");
    /* ALTER SUBSCRIPTION <name> */
    else if (Matches("ALTER", "SUBSCRIPTION", MatchAny))
        COMPLETE_WITH("CONNECTION", "ENABLE", "DISABLE", "OWNER TO",
@@ -3513,7 +3513,7 @@ match_previous_words(int pattern_id,
        COMPLETE_WITH("WITH (");
    /* Complete "CREATE PUBLICATION <name> [...] WITH" */
    else if (Matches("CREATE", "PUBLICATION", MatchAnyN, "WITH", "("))
-       COMPLETE_WITH("publish", "publish_via_partition_root");
+       COMPLETE_WITH("publish", "publish_generated_columns", "publish_via_partition_root");
 
 /* CREATE RULE */
    /* Complete "CREATE [ OR REPLACE ] RULE <sth>" with "AS ON" */
index 993e699127487e9e45df8e821db351f12b2b74b6..2abc523f5c2f54e0ef0fe743dcc6b934cc6a48e5 100644 (file)
@@ -57,6 +57,6 @@
  */
 
 /*                         yyyymmddN */
-#define CATALOG_VERSION_NO 202411042
+#define CATALOG_VERSION_NO 202411071
 
 #endif
index d9518a58b008c285ca4c39dc7770b87c0c669d53..9a83a72d6b2e739cf2799975da4dec0719dd35d7 100644 (file)
@@ -54,6 +54,9 @@ CATALOG(pg_publication,6104,PublicationRelationId)
 
    /* true if partition changes are published using root schema */
    bool        pubviaroot;
+
+   /* true if generated columns data should be published */
+   bool        pubgencols;
 } FormData_pg_publication;
 
 /* ----------------
@@ -103,6 +106,7 @@ typedef struct Publication
    char       *name;
    bool        alltables;
    bool        pubviaroot;
+   bool        pubgencols;
    PublicationActions pubactions;
 } Publication;
 
@@ -150,6 +154,8 @@ extern Oid  GetTopMostAncestorInPublication(Oid puboid, List *ancestors,
 
 extern bool is_publishable_relation(Relation rel);
 extern bool is_schema_publication(Oid pubid);
+extern bool check_and_fetch_column_list(Publication *pub, Oid relid,
+                                       MemoryContext mcxt, Bitmapset **cols);
 extern ObjectAddress publication_add_relation(Oid pubid, PublicationRelInfo *pri,
                                              bool if_not_exists);
 extern Bitmapset *pub_collist_validate(Relation targetrel, List *columns);
@@ -158,5 +164,6 @@ extern ObjectAddress publication_add_schema(Oid pubid, Oid schemaid,
 
 extern Bitmapset *pub_collist_to_bitmapset(Bitmapset *columns, Datum pubcols,
                                           MemoryContext mcxt);
+extern Bitmapset *pub_form_cols_map(Relation relation, bool include_gencols);
 
 #endif                         /* PG_PUBLICATION_H */
index b219f2265574c56e8bcf05d0a01689686edfd369..fe8583d1b6e56028e06e6ca3474ac3af775c5d0b 100644 (file)
@@ -223,20 +223,21 @@ extern void logicalrep_write_origin(StringInfo out, const char *origin,
                                    XLogRecPtr origin_lsn);
 extern char *logicalrep_read_origin(StringInfo in, XLogRecPtr *origin_lsn);
 extern void logicalrep_write_insert(StringInfo out, TransactionId xid,
-                                   Relation rel,
-                                   TupleTableSlot *newslot,
-                                   bool binary, Bitmapset *columns);
+                                   Relation rel, TupleTableSlot *newslot,
+                                   bool binary, Bitmapset *columns,
+                                   bool include_gencols);
 extern LogicalRepRelId logicalrep_read_insert(StringInfo in, LogicalRepTupleData *newtup);
 extern void logicalrep_write_update(StringInfo out, TransactionId xid,
-                                   Relation rel,
-                                   TupleTableSlot *oldslot,
-                                   TupleTableSlot *newslot, bool binary, Bitmapset *columns);
+                                   Relation rel, TupleTableSlot *oldslot,
+                                   TupleTableSlot *newslot, bool binary,
+                                   Bitmapset *columns, bool include_gencols);
 extern LogicalRepRelId logicalrep_read_update(StringInfo in,
                                              bool *has_oldtuple, LogicalRepTupleData *oldtup,
                                              LogicalRepTupleData *newtup);
 extern void logicalrep_write_delete(StringInfo out, TransactionId xid,
                                    Relation rel, TupleTableSlot *oldslot,
-                                   bool binary, Bitmapset *columns);
+                                   bool binary, Bitmapset *columns,
+                                   bool include_gencols);
 extern LogicalRepRelId logicalrep_read_delete(StringInfo in,
                                              LogicalRepTupleData *oldtup);
 extern void logicalrep_write_truncate(StringInfo out, TransactionId xid,
@@ -247,7 +248,8 @@ extern List *logicalrep_read_truncate(StringInfo in,
 extern void logicalrep_write_message(StringInfo out, TransactionId xid, XLogRecPtr lsn,
                                     bool transactional, const char *prefix, Size sz, const char *message);
 extern void logicalrep_write_rel(StringInfo out, TransactionId xid,
-                                Relation rel, Bitmapset *columns);
+                                Relation rel, Bitmapset *columns,
+                                bool include_gencols);
 extern LogicalRepRelation *logicalrep_read_rel(StringInfo in);
 extern void logicalrep_write_typ(StringInfo out, TransactionId xid,
                                 Oid typoid);
@@ -271,6 +273,7 @@ extern void logicalrep_read_stream_abort(StringInfo in,
                                         bool read_abort_info);
 extern const char *logicalrep_message_type(LogicalRepMsgType action);
 extern bool logicalrep_should_publish_column(Form_pg_attribute att,
-                                            Bitmapset *columns);
+                                            Bitmapset *columns,
+                                            bool include_gencols);
 
 #endif                         /* LOGICAL_PROTO_H */
index 3819bf5e25441bd828562ce6d0616a0bc98004e1..36dc31c16c4c077440d70187832ff74f8d9f28b3 100644 (file)
@@ -6350,9 +6350,9 @@ List of schemas
 (0 rows)
 
 \dRp "no.such.publication"
-                              List of publications
- Name | Owner | All tables | Inserts | Updates | Deletes | Truncates | Via root 
-------+-------+------------+---------+---------+---------+-----------+----------
+                                        List of publications
+ Name | Owner | All tables | Inserts | Updates | Deletes | Truncates | Generated columns | Via root 
+------+-------+------------+---------+---------+---------+-----------+-------------------+----------
 (0 rows)
 
 \dRs "no.such.subscription"
index d2ed1efc3bf681840bfb738c43120e8b69879442..a8949ffc2c666e4316469868e91ca65cdb8945e7 100644 (file)
@@ -29,21 +29,27 @@ CREATE PUBLICATION testpub_xxx WITH (publish_via_partition_root = 'true', publis
 ERROR:  conflicting or redundant options
 LINE 1: ...ub_xxx WITH (publish_via_partition_root = 'true', publish_vi...
                                                              ^
+CREATE PUBLICATION testpub_xxx WITH (publish_generated_columns = 'true', publish_generated_columns = '0');
+ERROR:  conflicting or redundant options
+LINE 1: ...pub_xxx WITH (publish_generated_columns = 'true', publish_ge...
+                                                             ^
+CREATE PUBLICATION testpub_xxx WITH (publish_generated_columns = 'foo');
+ERROR:  publish_generated_columns requires a Boolean value
 \dRp
-                                              List of publications
-        Name        |          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Via root 
---------------------+--------------------------+------------+---------+---------+---------+-----------+----------
- testpib_ins_trunct | regress_publication_user | f          | t       | f       | f       | f         | f
- testpub_default    | regress_publication_user | f          | f       | t       | f       | f         | f
+                                                        List of publications
+        Name        |          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Generated columns | Via root 
+--------------------+--------------------------+------------+---------+---------+---------+-----------+-------------------+----------
+ testpib_ins_trunct | regress_publication_user | f          | t       | f       | f       | f         | f                 | f
+ testpub_default    | regress_publication_user | f          | f       | t       | f       | f         | f                 | f
 (2 rows)
 
 ALTER PUBLICATION testpub_default SET (publish = 'insert, update, delete');
 \dRp
-                                              List of publications
-        Name        |          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Via root 
---------------------+--------------------------+------------+---------+---------+---------+-----------+----------
- testpib_ins_trunct | regress_publication_user | f          | t       | f       | f       | f         | f
- testpub_default    | regress_publication_user | f          | t       | t       | t       | f         | f
+                                                        List of publications
+        Name        |          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Generated columns | Via root 
+--------------------+--------------------------+------------+---------+---------+---------+-----------+-------------------+----------
+ testpib_ins_trunct | regress_publication_user | f          | t       | f       | f       | f         | f                 | f
+ testpub_default    | regress_publication_user | f          | t       | t       | t       | f         | f                 | f
 (2 rows)
 
 --- adding tables
@@ -87,10 +93,10 @@ RESET client_min_messages;
 -- should be able to add schema to 'FOR TABLE' publication
 ALTER PUBLICATION testpub_fortable ADD TABLES IN SCHEMA pub_test;
 \dRp+ testpub_fortable
-                                Publication testpub_fortable
-          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Via root 
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f          | t       | t       | t       | t         | f
+                                          Publication testpub_fortable
+          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Generated columns | Via root 
+--------------------------+------------+---------+---------+---------+-----------+-------------------+----------
+ regress_publication_user | f          | t       | t       | t       | t         | f                 | f
 Tables:
     "public.testpub_tbl1"
 Tables from schemas:
@@ -99,20 +105,20 @@ Tables from schemas:
 -- should be able to drop schema from 'FOR TABLE' publication
 ALTER PUBLICATION testpub_fortable DROP TABLES IN SCHEMA pub_test;
 \dRp+ testpub_fortable
-                                Publication testpub_fortable
-          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Via root 
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f          | t       | t       | t       | t         | f
+                                          Publication testpub_fortable
+          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Generated columns | Via root 
+--------------------------+------------+---------+---------+---------+-----------+-------------------+----------
+ regress_publication_user | f          | t       | t       | t       | t         | f                 | f
 Tables:
     "public.testpub_tbl1"
 
 -- should be able to set schema to 'FOR TABLE' publication
 ALTER PUBLICATION testpub_fortable SET TABLES IN SCHEMA pub_test;
 \dRp+ testpub_fortable
-                                Publication testpub_fortable
-          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Via root 
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f          | t       | t       | t       | t         | f
+                                          Publication testpub_fortable
+          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Generated columns | Via root 
+--------------------------+------------+---------+---------+---------+-----------+-------------------+----------
+ regress_publication_user | f          | t       | t       | t       | t         | f                 | f
 Tables from schemas:
     "pub_test"
 
@@ -123,10 +129,10 @@ CREATE PUBLICATION testpub_forschema FOR TABLES IN SCHEMA pub_test;
 CREATE PUBLICATION testpub_for_tbl_schema FOR TABLES IN SCHEMA pub_test, TABLE pub_test.testpub_nopk;
 RESET client_min_messages;
 \dRp+ testpub_for_tbl_schema
-                             Publication testpub_for_tbl_schema
-          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Via root 
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f          | t       | t       | t       | t         | f
+                                       Publication testpub_for_tbl_schema
+          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Generated columns | Via root 
+--------------------------+------------+---------+---------+---------+-----------+-------------------+----------
+ regress_publication_user | f          | t       | t       | t       | t         | f                 | f
 Tables:
     "pub_test.testpub_nopk"
 Tables from schemas:
@@ -144,10 +150,10 @@ LINE 1: ...CATION testpub_parsertst FOR TABLES IN SCHEMA foo, test.foo;
 -- should be able to add a table of the same schema to the schema publication
 ALTER PUBLICATION testpub_forschema ADD TABLE pub_test.testpub_nopk;
 \dRp+ testpub_forschema
-                               Publication testpub_forschema
-          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Via root 
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f          | t       | t       | t       | t         | f
+                                         Publication testpub_forschema
+          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Generated columns | Via root 
+--------------------------+------------+---------+---------+---------+-----------+-------------------+----------
+ regress_publication_user | f          | t       | t       | t       | t         | f                 | f
 Tables:
     "pub_test.testpub_nopk"
 Tables from schemas:
@@ -156,10 +162,10 @@ Tables from schemas:
 -- should be able to drop the table
 ALTER PUBLICATION testpub_forschema DROP TABLE pub_test.testpub_nopk;
 \dRp+ testpub_forschema
-                               Publication testpub_forschema
-          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Via root 
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f          | t       | t       | t       | t         | f
+                                         Publication testpub_forschema
+          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Generated columns | Via root 
+--------------------------+------------+---------+---------+---------+-----------+-------------------+----------
+ regress_publication_user | f          | t       | t       | t       | t         | f                 | f
 Tables from schemas:
     "pub_test"
 
@@ -170,10 +176,10 @@ ERROR:  relation "testpub_nopk" is not part of the publication
 -- should be able to set table to schema publication
 ALTER PUBLICATION testpub_forschema SET TABLE pub_test.testpub_nopk;
 \dRp+ testpub_forschema
-                               Publication testpub_forschema
-          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Via root 
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f          | t       | t       | t       | t         | f
+                                         Publication testpub_forschema
+          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Generated columns | Via root 
+--------------------------+------------+---------+---------+---------+-----------+-------------------+----------
+ regress_publication_user | f          | t       | t       | t       | t         | f                 | f
 Tables:
     "pub_test.testpub_nopk"
 
@@ -195,10 +201,10 @@ Publications:
     "testpub_foralltables"
 
 \dRp+ testpub_foralltables
-                              Publication testpub_foralltables
-          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Via root 
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | t          | t       | t       | f       | f         | f
+                                        Publication testpub_foralltables
+          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Generated columns | Via root 
+--------------------------+------------+---------+---------+---------+-----------+-------------------+----------
+ regress_publication_user | t          | t       | t       | f       | f         | f                 | f
 (1 row)
 
 DROP TABLE testpub_tbl2;
@@ -210,19 +216,19 @@ CREATE PUBLICATION testpub3 FOR TABLE testpub_tbl3;
 CREATE PUBLICATION testpub4 FOR TABLE ONLY testpub_tbl3;
 RESET client_min_messages;
 \dRp+ testpub3
-                                    Publication testpub3
-          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Via root 
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f          | t       | t       | t       | t         | f
+                                              Publication testpub3
+          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Generated columns | Via root 
+--------------------------+------------+---------+---------+---------+-----------+-------------------+----------
+ regress_publication_user | f          | t       | t       | t       | t         | f                 | f
 Tables:
     "public.testpub_tbl3"
     "public.testpub_tbl3a"
 
 \dRp+ testpub4
-                                    Publication testpub4
-          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Via root 
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f          | t       | t       | t       | t         | f
+                                              Publication testpub4
+          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Generated columns | Via root 
+--------------------------+------------+---------+---------+---------+-----------+-------------------+----------
+ regress_publication_user | f          | t       | t       | t       | t         | f                 | f
 Tables:
     "public.testpub_tbl3"
 
@@ -243,10 +249,10 @@ UPDATE testpub_parted1 SET a = 1;
 -- only parent is listed as being in publication, not the partition
 ALTER PUBLICATION testpub_forparted ADD TABLE testpub_parted;
 \dRp+ testpub_forparted
-                               Publication testpub_forparted
-          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Via root 
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f          | t       | t       | t       | t         | f
+                                         Publication testpub_forparted
+          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Generated columns | Via root 
+--------------------------+------------+---------+---------+---------+-----------+-------------------+----------
+ regress_publication_user | f          | t       | t       | t       | t         | f                 | f
 Tables:
     "public.testpub_parted"
 
@@ -261,10 +267,10 @@ ALTER TABLE testpub_parted DETACH PARTITION testpub_parted1;
 UPDATE testpub_parted1 SET a = 1;
 ALTER PUBLICATION testpub_forparted SET (publish_via_partition_root = true);
 \dRp+ testpub_forparted
-                               Publication testpub_forparted
-          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Via root 
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f          | t       | t       | t       | t         | t
+                                         Publication testpub_forparted
+          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Generated columns | Via root 
+--------------------------+------------+---------+---------+---------+-----------+-------------------+----------
+ regress_publication_user | f          | t       | t       | t       | t         | f                 | t
 Tables:
     "public.testpub_parted"
 
@@ -293,10 +299,10 @@ SET client_min_messages = 'ERROR';
 CREATE PUBLICATION testpub5 FOR TABLE testpub_rf_tbl1, testpub_rf_tbl2 WHERE (c <> 'test' AND d < 5) WITH (publish = 'insert');
 RESET client_min_messages;
 \dRp+ testpub5
-                                    Publication testpub5
-          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Via root 
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f          | t       | f       | f       | f         | f
+                                              Publication testpub5
+          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Generated columns | Via root 
+--------------------------+------------+---------+---------+---------+-----------+-------------------+----------
+ regress_publication_user | f          | t       | f       | f       | f         | f                 | f
 Tables:
     "public.testpub_rf_tbl1"
     "public.testpub_rf_tbl2" WHERE ((c <> 'test'::text) AND (d < 5))
@@ -309,10 +315,10 @@ Tables:
 
 ALTER PUBLICATION testpub5 ADD TABLE testpub_rf_tbl3 WHERE (e > 1000 AND e < 2000);
 \dRp+ testpub5
-                                    Publication testpub5
-          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Via root 
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f          | t       | f       | f       | f         | f
+                                              Publication testpub5
+          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Generated columns | Via root 
+--------------------------+------------+---------+---------+---------+-----------+-------------------+----------
+ regress_publication_user | f          | t       | f       | f       | f         | f                 | f
 Tables:
     "public.testpub_rf_tbl1"
     "public.testpub_rf_tbl2" WHERE ((c <> 'test'::text) AND (d < 5))
@@ -328,10 +334,10 @@ Publications:
 
 ALTER PUBLICATION testpub5 DROP TABLE testpub_rf_tbl2;
 \dRp+ testpub5
-                                    Publication testpub5
-          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Via root 
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f          | t       | f       | f       | f         | f
+                                              Publication testpub5
+          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Generated columns | Via root 
+--------------------------+------------+---------+---------+---------+-----------+-------------------+----------
+ regress_publication_user | f          | t       | f       | f       | f         | f                 | f
 Tables:
     "public.testpub_rf_tbl1"
     "public.testpub_rf_tbl3" WHERE ((e > 1000) AND (e < 2000))
@@ -339,10 +345,10 @@ Tables:
 -- remove testpub_rf_tbl1 and add testpub_rf_tbl3 again (another WHERE expression)
 ALTER PUBLICATION testpub5 SET TABLE testpub_rf_tbl3 WHERE (e > 300 AND e < 500);
 \dRp+ testpub5
-                                    Publication testpub5
-          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Via root 
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f          | t       | f       | f       | f         | f
+                                              Publication testpub5
+          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Generated columns | Via root 
+--------------------------+------------+---------+---------+---------+-----------+-------------------+----------
+ regress_publication_user | f          | t       | f       | f       | f         | f                 | f
 Tables:
     "public.testpub_rf_tbl3" WHERE ((e > 300) AND (e < 500))
 
@@ -375,10 +381,10 @@ SET client_min_messages = 'ERROR';
 CREATE PUBLICATION testpub_syntax1 FOR TABLE testpub_rf_tbl1, ONLY testpub_rf_tbl3 WHERE (e < 999) WITH (publish = 'insert');
 RESET client_min_messages;
 \dRp+ testpub_syntax1
-                                Publication testpub_syntax1
-          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Via root 
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f          | t       | f       | f       | f         | f
+                                          Publication testpub_syntax1
+          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Generated columns | Via root 
+--------------------------+------------+---------+---------+---------+-----------+-------------------+----------
+ regress_publication_user | f          | t       | f       | f       | f         | f                 | f
 Tables:
     "public.testpub_rf_tbl1"
     "public.testpub_rf_tbl3" WHERE (e < 999)
@@ -388,10 +394,10 @@ SET client_min_messages = 'ERROR';
 CREATE PUBLICATION testpub_syntax2 FOR TABLE testpub_rf_tbl1, testpub_rf_schema1.testpub_rf_tbl5 WHERE (h < 999) WITH (publish = 'insert');
 RESET client_min_messages;
 \dRp+ testpub_syntax2
-                                Publication testpub_syntax2
-          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Via root 
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f          | t       | f       | f       | f         | f
+                                          Publication testpub_syntax2
+          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Generated columns | Via root 
+--------------------------+------------+---------+---------+---------+-----------+-------------------+----------
+ regress_publication_user | f          | t       | f       | f       | f         | f                 | f
 Tables:
     "public.testpub_rf_tbl1"
     "testpub_rf_schema1.testpub_rf_tbl5" WHERE (h < 999)
@@ -506,10 +512,10 @@ CREATE PUBLICATION testpub6 FOR TABLES IN SCHEMA testpub_rf_schema2;
 ALTER PUBLICATION testpub6 SET TABLES IN SCHEMA testpub_rf_schema2, TABLE testpub_rf_schema2.testpub_rf_tbl6 WHERE (i < 99);
 RESET client_min_messages;
 \dRp+ testpub6
-                                    Publication testpub6
-          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Via root 
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f          | t       | t       | t       | t         | f
+                                              Publication testpub6
+          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Generated columns | Via root 
+--------------------------+------------+---------+---------+---------+-----------+-------------------+----------
+ regress_publication_user | f          | t       | t       | t       | t         | f                 | f
 Tables:
     "testpub_rf_schema2.testpub_rf_tbl6" WHERE (i < 99)
 Tables from schemas:
@@ -730,10 +736,10 @@ CREATE PUBLICATION testpub_table_ins WITH (publish = 'insert, truncate');
 RESET client_min_messages;
 ALTER PUBLICATION testpub_table_ins ADD TABLE testpub_tbl5 (a);        -- ok
 \dRp+ testpub_table_ins
-                               Publication testpub_table_ins
-          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Via root 
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f          | t       | f       | f       | t         | f
+                                         Publication testpub_table_ins
+          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Generated columns | Via root 
+--------------------------+------------+---------+---------+---------+-----------+-------------------+----------
+ regress_publication_user | f          | t       | f       | f       | t         | f                 | f
 Tables:
     "public.testpub_tbl5" (a)
 
@@ -917,10 +923,10 @@ CREATE TABLE testpub_tbl_both_filters (a int, b int, c int, PRIMARY KEY (a,c));
 ALTER TABLE testpub_tbl_both_filters REPLICA IDENTITY USING INDEX testpub_tbl_both_filters_pkey;
 ALTER PUBLICATION testpub_both_filters ADD TABLE testpub_tbl_both_filters (a,c) WHERE (c != 1);
 \dRp+ testpub_both_filters
-                              Publication testpub_both_filters
-          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Via root 
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f          | t       | t       | t       | t         | f
+                                        Publication testpub_both_filters
+          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Generated columns | Via root 
+--------------------------+------------+---------+---------+---------+-----------+-------------------+----------
+ regress_publication_user | f          | t       | t       | t       | t         | f                 | f
 Tables:
     "public.testpub_tbl_both_filters" (a, c) WHERE (c <> 1)
 
@@ -1125,10 +1131,10 @@ ERROR:  relation "testpub_tbl1" is already member of publication "testpub_fortbl
 CREATE PUBLICATION testpub_fortbl FOR TABLE testpub_tbl1;
 ERROR:  publication "testpub_fortbl" already exists
 \dRp+ testpub_fortbl
-                                 Publication testpub_fortbl
-          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Via root 
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f          | t       | t       | t       | t         | f
+                                           Publication testpub_fortbl
+          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Generated columns | Via root 
+--------------------------+------------+---------+---------+---------+-----------+-------------------+----------
+ regress_publication_user | f          | t       | t       | t       | t         | f                 | f
 Tables:
     "pub_test.testpub_nopk"
     "public.testpub_tbl1"
@@ -1166,10 +1172,10 @@ Publications:
     "testpub_fortbl"
 
 \dRp+ testpub_default
-                                Publication testpub_default
-          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Via root 
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f          | t       | t       | t       | f         | f
+                                          Publication testpub_default
+          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Generated columns | Via root 
+--------------------------+------------+---------+---------+---------+-----------+-------------------+----------
+ regress_publication_user | f          | t       | t       | t       | f         | f                 | f
 Tables:
     "pub_test.testpub_nopk"
     "public.testpub_tbl1"
@@ -1247,10 +1253,10 @@ REVOKE CREATE ON DATABASE regression FROM regress_publication_user2;
 DROP TABLE testpub_parted;
 DROP TABLE testpub_tbl1;
 \dRp+ testpub_default
-                                Publication testpub_default
-          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Via root 
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f          | t       | t       | t       | f         | f
+                                          Publication testpub_default
+          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Generated columns | Via root 
+--------------------------+------------+---------+---------+---------+-----------+-------------------+----------
+ regress_publication_user | f          | t       | t       | t       | f         | f                 | f
 (1 row)
 
 -- fail - must be owner of publication
@@ -1260,20 +1266,20 @@ ERROR:  must be owner of publication testpub_default
 RESET ROLE;
 ALTER PUBLICATION testpub_default RENAME TO testpub_foo;
 \dRp testpub_foo
-                                           List of publications
-    Name     |          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Via root 
--------------+--------------------------+------------+---------+---------+---------+-----------+----------
- testpub_foo | regress_publication_user | f          | t       | t       | t       | f         | f
+                                                     List of publications
+    Name     |          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Generated columns | Via root 
+-------------+--------------------------+------------+---------+---------+---------+-----------+-------------------+----------
+ testpub_foo | regress_publication_user | f          | t       | t       | t       | f         | f                 | f
 (1 row)
 
 -- rename back to keep the rest simple
 ALTER PUBLICATION testpub_foo RENAME TO testpub_default;
 ALTER PUBLICATION testpub_default OWNER TO regress_publication_user2;
 \dRp testpub_default
-                                             List of publications
-      Name       |           Owner           | All tables | Inserts | Updates | Deletes | Truncates | Via root 
------------------+---------------------------+------------+---------+---------+---------+-----------+----------
- testpub_default | regress_publication_user2 | f          | t       | t       | t       | f         | f
+                                                       List of publications
+      Name       |           Owner           | All tables | Inserts | Updates | Deletes | Truncates | Generated columns | Via root 
+-----------------+---------------------------+------------+---------+---------+---------+-----------+-------------------+----------
+ testpub_default | regress_publication_user2 | f          | t       | t       | t       | f         | f                 | f
 (1 row)
 
 -- adding schemas and tables
@@ -1289,19 +1295,19 @@ CREATE TABLE "CURRENT_SCHEMA"."CURRENT_SCHEMA"(id int);
 SET client_min_messages = 'ERROR';
 CREATE PUBLICATION testpub1_forschema FOR TABLES IN SCHEMA pub_test1;
 \dRp+ testpub1_forschema
-                               Publication testpub1_forschema
-          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Via root 
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f          | t       | t       | t       | t         | f
+                                         Publication testpub1_forschema
+          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Generated columns | Via root 
+--------------------------+------------+---------+---------+---------+-----------+-------------------+----------
+ regress_publication_user | f          | t       | t       | t       | t         | f                 | f
 Tables from schemas:
     "pub_test1"
 
 CREATE PUBLICATION testpub2_forschema FOR TABLES IN SCHEMA pub_test1, pub_test2, pub_test3;
 \dRp+ testpub2_forschema
-                               Publication testpub2_forschema
-          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Via root 
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f          | t       | t       | t       | t         | f
+                                         Publication testpub2_forschema
+          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Generated columns | Via root 
+--------------------------+------------+---------+---------+---------+-----------+-------------------+----------
+ regress_publication_user | f          | t       | t       | t       | t         | f                 | f
 Tables from schemas:
     "pub_test1"
     "pub_test2"
@@ -1315,44 +1321,44 @@ CREATE PUBLICATION testpub6_forschema FOR TABLES IN SCHEMA "CURRENT_SCHEMA", CUR
 CREATE PUBLICATION testpub_fortable FOR TABLE "CURRENT_SCHEMA"."CURRENT_SCHEMA";
 RESET client_min_messages;
 \dRp+ testpub3_forschema
-                               Publication testpub3_forschema
-          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Via root 
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f          | t       | t       | t       | t         | f
+                                         Publication testpub3_forschema
+          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Generated columns | Via root 
+--------------------------+------------+---------+---------+---------+-----------+-------------------+----------
+ regress_publication_user | f          | t       | t       | t       | t         | f                 | f
 Tables from schemas:
     "public"
 
 \dRp+ testpub4_forschema
-                               Publication testpub4_forschema
-          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Via root 
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f          | t       | t       | t       | t         | f
+                                         Publication testpub4_forschema
+          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Generated columns | Via root 
+--------------------------+------------+---------+---------+---------+-----------+-------------------+----------
+ regress_publication_user | f          | t       | t       | t       | t         | f                 | f
 Tables from schemas:
     "CURRENT_SCHEMA"
 
 \dRp+ testpub5_forschema
-                               Publication testpub5_forschema
-          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Via root 
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f          | t       | t       | t       | t         | f
+                                         Publication testpub5_forschema
+          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Generated columns | Via root 
+--------------------------+------------+---------+---------+---------+-----------+-------------------+----------
+ regress_publication_user | f          | t       | t       | t       | t         | f                 | f
 Tables from schemas:
     "CURRENT_SCHEMA"
     "public"
 
 \dRp+ testpub6_forschema
-                               Publication testpub6_forschema
-          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Via root 
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f          | t       | t       | t       | t         | f
+                                         Publication testpub6_forschema
+          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Generated columns | Via root 
+--------------------------+------------+---------+---------+---------+-----------+-------------------+----------
+ regress_publication_user | f          | t       | t       | t       | t         | f                 | f
 Tables from schemas:
     "CURRENT_SCHEMA"
     "public"
 
 \dRp+ testpub_fortable
-                                Publication testpub_fortable
-          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Via root 
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f          | t       | t       | t       | t         | f
+                                          Publication testpub_fortable
+          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Generated columns | Via root 
+--------------------------+------------+---------+---------+---------+-----------+-------------------+----------
+ regress_publication_user | f          | t       | t       | t       | t         | f                 | f
 Tables:
     "CURRENT_SCHEMA.CURRENT_SCHEMA"
 
@@ -1386,10 +1392,10 @@ ERROR:  schema "testpub_view" does not exist
 -- dropping the schema should reflect the change in publication
 DROP SCHEMA pub_test3;
 \dRp+ testpub2_forschema
-                               Publication testpub2_forschema
-          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Via root 
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f          | t       | t       | t       | t         | f
+                                         Publication testpub2_forschema
+          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Generated columns | Via root 
+--------------------------+------------+---------+---------+---------+-----------+-------------------+----------
+ regress_publication_user | f          | t       | t       | t       | t         | f                 | f
 Tables from schemas:
     "pub_test1"
     "pub_test2"
@@ -1397,20 +1403,20 @@ Tables from schemas:
 -- renaming the schema should reflect the change in publication
 ALTER SCHEMA pub_test1 RENAME to pub_test1_renamed;
 \dRp+ testpub2_forschema
-                               Publication testpub2_forschema
-          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Via root 
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f          | t       | t       | t       | t         | f
+                                         Publication testpub2_forschema
+          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Generated columns | Via root 
+--------------------------+------------+---------+---------+---------+-----------+-------------------+----------
+ regress_publication_user | f          | t       | t       | t       | t         | f                 | f
 Tables from schemas:
     "pub_test1_renamed"
     "pub_test2"
 
 ALTER SCHEMA pub_test1_renamed RENAME to pub_test1;
 \dRp+ testpub2_forschema
-                               Publication testpub2_forschema
-          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Via root 
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f          | t       | t       | t       | t         | f
+                                         Publication testpub2_forschema
+          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Generated columns | Via root 
+--------------------------+------------+---------+---------+---------+-----------+-------------------+----------
+ regress_publication_user | f          | t       | t       | t       | t         | f                 | f
 Tables from schemas:
     "pub_test1"
     "pub_test2"
@@ -1418,10 +1424,10 @@ Tables from schemas:
 -- alter publication add schema
 ALTER PUBLICATION testpub1_forschema ADD TABLES IN SCHEMA pub_test2;
 \dRp+ testpub1_forschema
-                               Publication testpub1_forschema
-          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Via root 
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f          | t       | t       | t       | t         | f
+                                         Publication testpub1_forschema
+          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Generated columns | Via root 
+--------------------------+------------+---------+---------+---------+-----------+-------------------+----------
+ regress_publication_user | f          | t       | t       | t       | t         | f                 | f
 Tables from schemas:
     "pub_test1"
     "pub_test2"
@@ -1430,10 +1436,10 @@ Tables from schemas:
 ALTER PUBLICATION testpub1_forschema ADD TABLES IN SCHEMA non_existent_schema;
 ERROR:  schema "non_existent_schema" does not exist
 \dRp+ testpub1_forschema
-                               Publication testpub1_forschema
-          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Via root 
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f          | t       | t       | t       | t         | f
+                                         Publication testpub1_forschema
+          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Generated columns | Via root 
+--------------------------+------------+---------+---------+---------+-----------+-------------------+----------
+ regress_publication_user | f          | t       | t       | t       | t         | f                 | f
 Tables from schemas:
     "pub_test1"
     "pub_test2"
@@ -1442,10 +1448,10 @@ Tables from schemas:
 ALTER PUBLICATION testpub1_forschema ADD TABLES IN SCHEMA pub_test1;
 ERROR:  schema "pub_test1" is already member of publication "testpub1_forschema"
 \dRp+ testpub1_forschema
-                               Publication testpub1_forschema
-          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Via root 
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f          | t       | t       | t       | t         | f
+                                         Publication testpub1_forschema
+          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Generated columns | Via root 
+--------------------------+------------+---------+---------+---------+-----------+-------------------+----------
+ regress_publication_user | f          | t       | t       | t       | t         | f                 | f
 Tables from schemas:
     "pub_test1"
     "pub_test2"
@@ -1453,10 +1459,10 @@ Tables from schemas:
 -- alter publication drop schema
 ALTER PUBLICATION testpub1_forschema DROP TABLES IN SCHEMA pub_test2;
 \dRp+ testpub1_forschema
-                               Publication testpub1_forschema
-          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Via root 
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f          | t       | t       | t       | t         | f
+                                         Publication testpub1_forschema
+          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Generated columns | Via root 
+--------------------------+------------+---------+---------+---------+-----------+-------------------+----------
+ regress_publication_user | f          | t       | t       | t       | t         | f                 | f
 Tables from schemas:
     "pub_test1"
 
@@ -1464,10 +1470,10 @@ Tables from schemas:
 ALTER PUBLICATION testpub1_forschema DROP TABLES IN SCHEMA pub_test2;
 ERROR:  tables from schema "pub_test2" are not part of the publication
 \dRp+ testpub1_forschema
-                               Publication testpub1_forschema
-          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Via root 
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f          | t       | t       | t       | t         | f
+                                         Publication testpub1_forschema
+          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Generated columns | Via root 
+--------------------------+------------+---------+---------+---------+-----------+-------------------+----------
+ regress_publication_user | f          | t       | t       | t       | t         | f                 | f
 Tables from schemas:
     "pub_test1"
 
@@ -1475,29 +1481,29 @@ Tables from schemas:
 ALTER PUBLICATION testpub1_forschema DROP TABLES IN SCHEMA non_existent_schema;
 ERROR:  schema "non_existent_schema" does not exist
 \dRp+ testpub1_forschema
-                               Publication testpub1_forschema
-          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Via root 
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f          | t       | t       | t       | t         | f
+                                         Publication testpub1_forschema
+          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Generated columns | Via root 
+--------------------------+------------+---------+---------+---------+-----------+-------------------+----------
+ regress_publication_user | f          | t       | t       | t       | t         | f                 | f
 Tables from schemas:
     "pub_test1"
 
 -- drop all schemas
 ALTER PUBLICATION testpub1_forschema DROP TABLES IN SCHEMA pub_test1;
 \dRp+ testpub1_forschema
-                               Publication testpub1_forschema
-          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Via root 
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f          | t       | t       | t       | t         | f
+                                         Publication testpub1_forschema
+          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Generated columns | Via root 
+--------------------------+------------+---------+---------+---------+-----------+-------------------+----------
+ regress_publication_user | f          | t       | t       | t       | t         | f                 | f
 (1 row)
 
 -- alter publication set multiple schema
 ALTER PUBLICATION testpub1_forschema SET TABLES IN SCHEMA pub_test1, pub_test2;
 \dRp+ testpub1_forschema
-                               Publication testpub1_forschema
-          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Via root 
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f          | t       | t       | t       | t         | f
+                                         Publication testpub1_forschema
+          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Generated columns | Via root 
+--------------------------+------------+---------+---------+---------+-----------+-------------------+----------
+ regress_publication_user | f          | t       | t       | t       | t         | f                 | f
 Tables from schemas:
     "pub_test1"
     "pub_test2"
@@ -1506,10 +1512,10 @@ Tables from schemas:
 ALTER PUBLICATION testpub1_forschema SET TABLES IN SCHEMA non_existent_schema;
 ERROR:  schema "non_existent_schema" does not exist
 \dRp+ testpub1_forschema
-                               Publication testpub1_forschema
-          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Via root 
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f          | t       | t       | t       | t         | f
+                                         Publication testpub1_forschema
+          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Generated columns | Via root 
+--------------------------+------------+---------+---------+---------+-----------+-------------------+----------
+ regress_publication_user | f          | t       | t       | t       | t         | f                 | f
 Tables from schemas:
     "pub_test1"
     "pub_test2"
@@ -1518,10 +1524,10 @@ Tables from schemas:
 -- removing the duplicate schemas
 ALTER PUBLICATION testpub1_forschema SET TABLES IN SCHEMA pub_test1, pub_test1;
 \dRp+ testpub1_forschema
-                               Publication testpub1_forschema
-          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Via root 
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f          | t       | t       | t       | t         | f
+                                         Publication testpub1_forschema
+          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Generated columns | Via root 
+--------------------------+------------+---------+---------+---------+-----------+-------------------+----------
+ regress_publication_user | f          | t       | t       | t       | t         | f                 | f
 Tables from schemas:
     "pub_test1"
 
@@ -1600,18 +1606,18 @@ SET client_min_messages = 'ERROR';
 CREATE PUBLICATION testpub3_forschema;
 RESET client_min_messages;
 \dRp+ testpub3_forschema
-                               Publication testpub3_forschema
-          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Via root 
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f          | t       | t       | t       | t         | f
+                                         Publication testpub3_forschema
+          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Generated columns | Via root 
+--------------------------+------------+---------+---------+---------+-----------+-------------------+----------
+ regress_publication_user | f          | t       | t       | t       | t         | f                 | f
 (1 row)
 
 ALTER PUBLICATION testpub3_forschema SET TABLES IN SCHEMA pub_test1;
 \dRp+ testpub3_forschema
-                               Publication testpub3_forschema
-          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Via root 
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f          | t       | t       | t       | t         | f
+                                         Publication testpub3_forschema
+          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Generated columns | Via root 
+--------------------------+------------+---------+---------+---------+-----------+-------------------+----------
+ regress_publication_user | f          | t       | t       | t       | t         | f                 | f
 Tables from schemas:
     "pub_test1"
 
@@ -1621,20 +1627,20 @@ CREATE PUBLICATION testpub_forschema_fortable FOR TABLES IN SCHEMA pub_test1, TA
 CREATE PUBLICATION testpub_fortable_forschema FOR TABLE pub_test2.tbl1, TABLES IN SCHEMA pub_test1;
 RESET client_min_messages;
 \dRp+ testpub_forschema_fortable
-                           Publication testpub_forschema_fortable
-          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Via root 
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f          | t       | t       | t       | t         | f
+                                     Publication testpub_forschema_fortable
+          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Generated columns | Via root 
+--------------------------+------------+---------+---------+---------+-----------+-------------------+----------
+ regress_publication_user | f          | t       | t       | t       | t         | f                 | f
 Tables:
     "pub_test2.tbl1"
 Tables from schemas:
     "pub_test1"
 
 \dRp+ testpub_fortable_forschema
-                           Publication testpub_fortable_forschema
-          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Via root 
---------------------------+------------+---------+---------+---------+-----------+----------
- regress_publication_user | f          | t       | t       | t       | t         | f
+                                     Publication testpub_fortable_forschema
+          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Generated columns | Via root 
+--------------------------+------------+---------+---------+---------+-----------+-------------------+----------
+ regress_publication_user | f          | t       | t       | t       | t         | f                 | f
 Tables:
     "pub_test2.tbl1"
 Tables from schemas:
@@ -1749,6 +1755,84 @@ DROP PUBLICATION pub;
 DROP TABLE sch1.tbl1;
 DROP SCHEMA sch1 cascade;
 DROP SCHEMA sch2 cascade;
+-- ======================================================
+-- Test the publication 'publish_generated_columns' parameter enabled or disabled
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION pub1 FOR ALL TABLES WITH (publish_generated_columns=1);
+\dRp+ pub1
+                                                Publication pub1
+          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Generated columns | Via root 
+--------------------------+------------+---------+---------+---------+-----------+-------------------+----------
+ regress_publication_user | t          | t       | t       | t       | t         | t                 | f
+(1 row)
+
+CREATE PUBLICATION pub2 FOR ALL TABLES WITH (publish_generated_columns=0);
+\dRp+ pub2
+                                                Publication pub2
+          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Generated columns | Via root 
+--------------------------+------------+---------+---------+---------+-----------+-------------------+----------
+ regress_publication_user | t          | t       | t       | t       | t         | f                 | f
+(1 row)
+
+DROP PUBLICATION pub1;
+DROP PUBLICATION pub2;
+-- Test the 'publish_generated_columns' parameter enabled or disabled for
+-- different scenarios with/without generated columns in column lists.
+CREATE TABLE gencols (a int, gen1 int GENERATED ALWAYS AS (a * 2) STORED);
+-- Generated columns in column list, when 'publish_generated_columns'=false
+CREATE PUBLICATION pub1 FOR table gencols(a, gen1) WITH (publish_generated_columns=false);
+\dRp+ pub1
+                                                Publication pub1
+          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Generated columns | Via root 
+--------------------------+------------+---------+---------+---------+-----------+-------------------+----------
+ regress_publication_user | f          | t       | t       | t       | t         | f                 | f
+Tables:
+    "public.gencols" (a, gen1)
+
+-- Generated columns in column list, when 'publish_generated_columns'=true
+CREATE PUBLICATION pub2 FOR table gencols(a, gen1) WITH (publish_generated_columns=true);
+\dRp+ pub2
+                                                Publication pub2
+          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Generated columns | Via root 
+--------------------------+------------+---------+---------+---------+-----------+-------------------+----------
+ regress_publication_user | f          | t       | t       | t       | t         | t                 | f
+Tables:
+    "public.gencols" (a, gen1)
+
+-- Generated columns in column list, then set 'publication_generate_columns'=false
+ALTER PUBLICATION pub2 SET (publish_generated_columns = false);
+\dRp+ pub2
+                                                Publication pub2
+          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Generated columns | Via root 
+--------------------------+------------+---------+---------+---------+-----------+-------------------+----------
+ regress_publication_user | f          | t       | t       | t       | t         | f                 | f
+Tables:
+    "public.gencols" (a, gen1)
+
+-- Remove generated columns from column list, when 'publish_generated_columns'=false
+ALTER PUBLICATION pub2 SET TABLE gencols(a);
+\dRp+ pub2
+                                                Publication pub2
+          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Generated columns | Via root 
+--------------------------+------------+---------+---------+---------+-----------+-------------------+----------
+ regress_publication_user | f          | t       | t       | t       | t         | f                 | f
+Tables:
+    "public.gencols" (a)
+
+-- Add generated columns in column list, when 'publish_generated_columns'=false
+ALTER PUBLICATION pub2 SET TABLE gencols(a, gen1);
+\dRp+ pub2
+                                                Publication pub2
+          Owner           | All tables | Inserts | Updates | Deletes | Truncates | Generated columns | Via root 
+--------------------------+------------+---------+---------+---------+-----------+-------------------+----------
+ regress_publication_user | f          | t       | t       | t       | t         | f                 | f
+Tables:
+    "public.gencols" (a, gen1)
+
+DROP PUBLICATION pub1;
+DROP PUBLICATION pub2;
+DROP TABLE gencols;
+RESET client_min_messages;
 RESET SESSION AUTHORIZATION;
 DROP ROLE regress_publication_user, regress_publication_user2;
 DROP ROLE regress_publication_user_dummy;
index 12aea71c0f67fbafa9595dd03a0bdbdec8262c58..48e68bcca2df0d8db4e17ec6525faa1631c44bd9 100644 (file)
@@ -24,6 +24,8 @@ ALTER PUBLICATION testpub_default SET (publish = update);
 CREATE PUBLICATION testpub_xxx WITH (foo);
 CREATE PUBLICATION testpub_xxx WITH (publish = 'cluster, vacuum');
 CREATE PUBLICATION testpub_xxx WITH (publish_via_partition_root = 'true', publish_via_partition_root = '0');
+CREATE PUBLICATION testpub_xxx WITH (publish_generated_columns = 'true', publish_generated_columns = '0');
+CREATE PUBLICATION testpub_xxx WITH (publish_generated_columns = 'foo');
 
 \dRp
 
@@ -1111,7 +1113,47 @@ DROP PUBLICATION pub;
 DROP TABLE sch1.tbl1;
 DROP SCHEMA sch1 cascade;
 DROP SCHEMA sch2 cascade;
+-- ======================================================
+
+-- Test the publication 'publish_generated_columns' parameter enabled or disabled
+SET client_min_messages = 'ERROR';
+CREATE PUBLICATION pub1 FOR ALL TABLES WITH (publish_generated_columns=1);
+\dRp+ pub1
+CREATE PUBLICATION pub2 FOR ALL TABLES WITH (publish_generated_columns=0);
+\dRp+ pub2
+
+DROP PUBLICATION pub1;
+DROP PUBLICATION pub2;
+
+-- Test the 'publish_generated_columns' parameter enabled or disabled for
+-- different scenarios with/without generated columns in column lists.
+CREATE TABLE gencols (a int, gen1 int GENERATED ALWAYS AS (a * 2) STORED);
+
+-- Generated columns in column list, when 'publish_generated_columns'=false
+CREATE PUBLICATION pub1 FOR table gencols(a, gen1) WITH (publish_generated_columns=false);
+\dRp+ pub1
 
+-- Generated columns in column list, when 'publish_generated_columns'=true
+CREATE PUBLICATION pub2 FOR table gencols(a, gen1) WITH (publish_generated_columns=true);
+\dRp+ pub2
+
+-- Generated columns in column list, then set 'publication_generate_columns'=false
+ALTER PUBLICATION pub2 SET (publish_generated_columns = false);
+\dRp+ pub2
+
+-- Remove generated columns from column list, when 'publish_generated_columns'=false
+ALTER PUBLICATION pub2 SET TABLE gencols(a);
+\dRp+ pub2
+
+-- Add generated columns in column list, when 'publish_generated_columns'=false
+ALTER PUBLICATION pub2 SET TABLE gencols(a, gen1);
+\dRp+ pub2
+
+DROP PUBLICATION pub1;
+DROP PUBLICATION pub2;
+DROP TABLE gencols;
+
+RESET client_min_messages;
 RESET SESSION AUTHORIZATION;
 DROP ROLE regress_publication_user, regress_publication_user2;
 DROP ROLE regress_publication_user_dummy;
index 8b2e5f4708eb2c42104af903a4ff23a19a037cad..211b54c316214508a87ee9aa707000ad328cceab 100644 (file)
@@ -96,4 +96,234 @@ is( $result, qq(1|22|
 8|176|18
 9|198|19), 'generated columns replicated with trigger');
 
+# cleanup
+$node_subscriber->safe_psql('postgres', "DROP SUBSCRIPTION sub1");
+$node_publisher->safe_psql('postgres', "DROP PUBLICATION pub1");
+
+# =============================================================================
+# Exercise logical replication of a generated column to a subscriber side
+# regular column. This is done both when the publication parameter
+# 'publish_generated_columns' is set to false (to confirm existing default
+# behavior), and is set to true (to confirm replication occurs).
+#
+# The test environment is set up as follows:
+#
+# - Publication pub1 on the 'postgres' database.
+#   pub1 has publish_generated_columns=false.
+#
+# - Publication pub2 on the 'postgres' database.
+#   pub2 has publish_generated_columns=true.
+#
+# - Subscription sub1 on the 'postgres' database for publication pub1.
+#
+# - Subscription sub2 on the 'test_pgc_true' database for publication pub2.
+# =============================================================================
+
+$node_subscriber->safe_psql('postgres', "CREATE DATABASE test_pgc_true");
+
+# --------------------------------------------------
+# Test Case: Generated to regular column replication
+# Publisher table has generated column 'b'.
+# Subscriber table has regular column 'b'.
+# --------------------------------------------------
+
+# Create table and publications. Insert data to verify initial sync.
+$node_publisher->safe_psql(
+   'postgres', qq(
+   CREATE TABLE tab_gen_to_nogen (a int, b int GENERATED ALWAYS AS (a * 2) STORED);
+   INSERT INTO tab_gen_to_nogen (a) VALUES (1), (2), (3);
+   CREATE PUBLICATION regress_pub1_gen_to_nogen FOR TABLE tab_gen_to_nogen WITH (publish_generated_columns = false);
+   CREATE PUBLICATION regress_pub2_gen_to_nogen FOR TABLE tab_gen_to_nogen WITH (publish_generated_columns = true);
+));
+
+# Create the table and subscription in the 'postgres' database.
+$node_subscriber->safe_psql(
+   'postgres', qq(
+   CREATE TABLE tab_gen_to_nogen (a int, b int);
+   CREATE SUBSCRIPTION regress_sub1_gen_to_nogen CONNECTION '$publisher_connstr' PUBLICATION regress_pub1_gen_to_nogen WITH (copy_data = true);
+));
+
+# Create the table and subscription in the 'test_pgc_true' database.
+$node_subscriber->safe_psql(
+   'test_pgc_true', qq(
+   CREATE TABLE tab_gen_to_nogen (a int, b int);
+   CREATE SUBSCRIPTION regress_sub2_gen_to_nogen CONNECTION '$publisher_connstr' PUBLICATION regress_pub2_gen_to_nogen WITH (copy_data = true);
+));
+
+# Wait for the initial synchronization of both subscriptions.
+$node_subscriber->wait_for_subscription_sync($node_publisher,
+   'regress_sub1_gen_to_nogen', 'postgres');
+$node_subscriber->wait_for_subscription_sync($node_publisher,
+   'regress_sub2_gen_to_nogen', 'test_pgc_true');
+
+# Verify that generated column data is not copied during the initial
+# synchronization when publish_generated_columns is set to false.
+$result = $node_subscriber->safe_psql('postgres',
+   "SELECT a, b FROM tab_gen_to_nogen ORDER BY a");
+is( $result, qq(1|
+2|
+3|), 'tab_gen_to_nogen initial sync, when publish_generated_columns=false');
+
+# Verify that generated column data is copied during the initial synchronization
+# when publish_generated_columns is set to true.
+$result = $node_subscriber->safe_psql('test_pgc_true',
+   "SELECT a, b FROM tab_gen_to_nogen ORDER BY a");
+is( $result, qq(1|2
+2|4
+3|6),
+   'tab_gen_to_nogen initial sync, when publish_generated_columns=true');
+
+# Insert data to verify incremental replication.
+$node_publisher->safe_psql('postgres',
+   "INSERT INTO tab_gen_to_nogen VALUES (4), (5)");
+
+# Verify that the generated column data is not replicated during incremental
+# replication when publish_generated_columns is set to false.
+$node_publisher->wait_for_catchup('regress_sub1_gen_to_nogen');
+$result = $node_subscriber->safe_psql('postgres',
+   "SELECT a, b FROM tab_gen_to_nogen ORDER BY a");
+is( $result, qq(1|
+2|
+3|
+4|
+5|),
+   'tab_gen_to_nogen incremental replication, when publish_generated_columns=false'
+);
+
+# Verify that generated column data is replicated during incremental
+# synchronization when publish_generated_columns is set to true.
+$node_publisher->wait_for_catchup('regress_sub2_gen_to_nogen');
+$result = $node_subscriber->safe_psql('test_pgc_true',
+   "SELECT a, b FROM tab_gen_to_nogen ORDER BY a");
+is( $result, qq(1|2
+2|4
+3|6
+4|8
+5|10),
+   'tab_gen_to_nogen incremental replication, when publish_generated_columns=true'
+);
+
+# cleanup
+$node_subscriber->safe_psql('postgres',
+   "DROP SUBSCRIPTION regress_sub1_gen_to_nogen");
+$node_subscriber->safe_psql('test_pgc_true',
+   "DROP SUBSCRIPTION regress_sub2_gen_to_nogen");
+$node_publisher->safe_psql(
+   'postgres', qq(
+   DROP PUBLICATION regress_pub1_gen_to_nogen;
+   DROP PUBLICATION regress_pub2_gen_to_nogen;
+));
+$node_subscriber->safe_psql('test_pgc_true', "DROP table tab_gen_to_nogen");
+$node_subscriber->safe_psql('postgres', "DROP DATABASE test_pgc_true");
+
+# =============================================================================
+# The following test cases demonstrate how publication column lists interact
+# with the publication parameter 'publish_generated_columns'.
+#
+# Test: Column lists take precedence, so generated columns in a column list
+# will be replicated even when publish_generated_columns=false.
+#
+# Test: When there is a column list, only those generated columns named in the
+# column list will be replicated even when publish_generated_columns=true.
+# =============================================================================
+
+# --------------------------------------------------
+# Test Case: Publisher replicates the column list, including generated columns,
+# even when the publish_generated_columns option is set to false.
+# --------------------------------------------------
+
+# Create table and publication. Insert data to verify initial sync.
+$node_publisher->safe_psql(
+   'postgres', qq(
+   CREATE TABLE tab2 (a int, gen1 int GENERATED ALWAYS AS (a * 2) STORED);
+   INSERT INTO tab2 (a) VALUES (1), (2);
+   CREATE PUBLICATION pub1 FOR table tab2(gen1) WITH (publish_generated_columns=false);
+));
+
+# Create table and subscription.
+$node_subscriber->safe_psql(
+   'postgres', qq(
+   CREATE TABLE tab2 (a int, gen1 int);
+   CREATE SUBSCRIPTION sub1 CONNECTION '$publisher_connstr' PUBLICATION pub1 WITH (copy_data = true);
+));
+
+# Wait for initial sync.
+$node_subscriber->wait_for_subscription_sync($node_publisher, 'sub1');
+
+# Initial sync test when publish_generated_columns=false.
+# Verify 'gen1' is replicated regardless of the false parameter value.
+$result =
+  $node_subscriber->safe_psql('postgres', "SELECT * FROM tab2 ORDER BY gen1");
+is( $result, qq(|2
+|4),
+   'tab2 initial sync, when publish_generated_columns=false');
+
+# Insert data to verify incremental replication.
+$node_publisher->safe_psql('postgres', "INSERT INTO tab2 VALUES (3), (4)");
+
+# Incremental replication test when publish_generated_columns=false.
+# Verify 'gen1' is replicated regardless of the false parameter value.
+$node_publisher->wait_for_catchup('sub1');
+$result =
+  $node_subscriber->safe_psql('postgres', "SELECT * FROM tab2 ORDER BY gen1");
+is( $result, qq(|2
+|4
+|6
+|8),
+   'tab2 incremental replication, when publish_generated_columns=false');
+
+# cleanup
+$node_subscriber->safe_psql('postgres', "DROP SUBSCRIPTION sub1");
+$node_publisher->safe_psql('postgres', "DROP PUBLICATION pub1");
+
+# --------------------------------------------------
+# Test Case: Even when publish_generated_columns is set to true, the publisher
+# only publishes the data of columns specified in the column list,
+# skipping other generated and non-generated columns.
+# --------------------------------------------------
+
+# Create table and publication. Insert data to verify initial sync.
+$node_publisher->safe_psql(
+   'postgres', qq(
+   CREATE TABLE tab3 (a int, gen1 int GENERATED ALWAYS AS (a * 2) STORED, gen2 int GENERATED ALWAYS AS (a * 2) STORED);
+   INSERT INTO tab3 (a) VALUES (1), (2);
+   CREATE PUBLICATION pub1 FOR table tab3(gen1) WITH (publish_generated_columns=true);
+));
+
+# Create table and subscription.
+$node_subscriber->safe_psql(
+   'postgres', qq(
+   CREATE TABLE tab3 (a int, gen1 int, gen2 int);
+   CREATE SUBSCRIPTION sub1 CONNECTION '$publisher_connstr' PUBLICATION pub1 WITH (copy_data = true);
+));
+
+# Wait for initial sync.
+$node_subscriber->wait_for_subscription_sync($node_publisher, 'sub1');
+
+# Initial sync test when publish_generated_columns=true.
+# Verify only 'gen1' is replicated regardless of the true parameter value.
+$result =
+  $node_subscriber->safe_psql('postgres', "SELECT * FROM tab3 ORDER BY gen1");
+is( $result, qq(|2|
+|4|),
+   'tab3 initial sync, when publish_generated_columns=true');
+
+# Insert data to verify incremental replication.
+$node_publisher->safe_psql('postgres', "INSERT INTO tab3 VALUES (3), (4)");
+
+# Incremental replication test when publish_generated_columns=true.
+# Verify only 'gen1' is replicated regardless of the true parameter value.
+$node_publisher->wait_for_catchup('sub1');
+$result =
+  $node_subscriber->safe_psql('postgres', "SELECT * FROM tab3 ORDER BY gen1");
+is( $result, qq(|2|
+|4|
+|6|
+|8|),
+   'tab3 incremental replication, when publish_generated_columns=true');
+
+# cleanup
+$node_subscriber->safe_psql('postgres', "DROP SUBSCRIPTION sub1");
+$node_publisher->safe_psql('postgres', "DROP PUBLICATION pub1");
+
 done_testing();
index e54861b599d67323697fc4e0ca0c4e1773e38fd2..3e9b4521e823321ae422133d1e4beba7f924f91e 100644 (file)
@@ -1276,40 +1276,6 @@ ok( $stderr =~
      qr/cannot use different column lists for table "public.test_mix_1" in different publications/,
    'different column lists detected');
 
-# TEST: Generated columns are considered for the column list.
-$node_publisher->safe_psql(
-   'postgres', qq(
-   CREATE TABLE test_gen (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a + 1) STORED);
-   INSERT INTO test_gen VALUES (0);
-   CREATE PUBLICATION pub_gen FOR TABLE test_gen (a, b);
-));
-
-$node_subscriber->safe_psql(
-   'postgres', qq(
-   CREATE TABLE test_gen (a int PRIMARY KEY, b int);
-   CREATE SUBSCRIPTION sub_gen CONNECTION '$publisher_connstr' PUBLICATION pub_gen;
-));
-
-$node_subscriber->wait_for_subscription_sync;
-
-is( $node_subscriber->safe_psql(
-       'postgres', "SELECT * FROM test_gen ORDER BY a"),
-   qq(0|1),
-   'initial replication with generated columns in column list');
-
-$node_publisher->safe_psql(
-   'postgres', qq(
-   INSERT INTO test_gen VALUES (1);
-));
-
-$node_publisher->wait_for_catchup('sub_gen');
-
-is( $node_subscriber->safe_psql(
-       'postgres', "SELECT * FROM test_gen ORDER BY a"),
-   qq(0|1
-1|2),
-   'replication with generated columns in column list');
-
 # TEST: If the column list is changed after creating the subscription, we
 # should catch the error reported by walsender.