LCOV - code coverage report
Current view: top level - src/backend/catalog - pg_publication.c (source / functions) Coverage Total Hit
Test: PostgreSQL 20devel Lines: 99.5 % 553 550
Test Date: 2026-08-15 17:15:51 Functions: 100.0 % 38 38
Legend: Lines:     hit not hit
Branches: + taken - not taken # not executed
Branches: 90.2 % 338 305

             Branch data     Line data    Source code
       1                 :             : /*-------------------------------------------------------------------------
       2                 :             :  *
       3                 :             :  * pg_publication.c
       4                 :             :  *      publication C API manipulation
       5                 :             :  *
       6                 :             :  * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
       7                 :             :  * Portions Copyright (c) 1994, Regents of the University of California
       8                 :             :  *
       9                 :             :  * IDENTIFICATION
      10                 :             :  *      src/backend/catalog/pg_publication.c
      11                 :             :  *
      12                 :             :  *-------------------------------------------------------------------------
      13                 :             :  */
      14                 :             : 
      15                 :             : #include "postgres.h"
      16                 :             : 
      17                 :             : #include "access/genam.h"
      18                 :             : #include "access/heapam.h"
      19                 :             : #include "access/htup_details.h"
      20                 :             : #include "access/tableam.h"
      21                 :             : #include "catalog/catalog.h"
      22                 :             : #include "catalog/dependency.h"
      23                 :             : #include "catalog/indexing.h"
      24                 :             : #include "catalog/namespace.h"
      25                 :             : #include "catalog/objectaddress.h"
      26                 :             : #include "catalog/partition.h"
      27                 :             : #include "catalog/pg_inherits.h"
      28                 :             : #include "catalog/pg_namespace.h"
      29                 :             : #include "catalog/pg_publication.h"
      30                 :             : #include "catalog/pg_publication_namespace.h"
      31                 :             : #include "catalog/pg_publication_rel.h"
      32                 :             : #include "catalog/pg_type.h"
      33                 :             : #include "commands/publicationcmds.h"
      34                 :             : #include "funcapi.h"
      35                 :             : #include "miscadmin.h"
      36                 :             : #include "utils/array.h"
      37                 :             : #include "utils/builtins.h"
      38                 :             : #include "utils/catcache.h"
      39                 :             : #include "utils/fmgroids.h"
      40                 :             : #include "utils/lsyscache.h"
      41                 :             : #include "utils/rel.h"
      42                 :             : #include "utils/syscache.h"
      43                 :             : 
      44                 :             : /* Records association between publication and published table */
      45                 :             : typedef struct
      46                 :             : {
      47                 :             :     Oid         relid;          /* OID of published table */
      48                 :             :     Oid         pubid;          /* OID of publication that publishes this
      49                 :             :                                  * table. */
      50                 :             : } published_rel;
      51                 :             : 
      52                 :             : /*
      53                 :             :  * Check if relation can be in given publication and throws appropriate
      54                 :             :  * error if not.
      55                 :             :  */
      56                 :             : static void
      57                 :         812 : check_publication_add_relation(PublicationRelInfo *pri)
      58                 :             : {
      59                 :         812 :     Relation    targetrel = pri->relation;
      60                 :             :     const char *relname;
      61                 :             :     const char *errormsg;
      62                 :             : 
      63         [ +  + ]:         812 :     if (pri->except)
      64                 :             :     {
      65                 :          81 :         relname = RelationGetQualifiedRelationName(targetrel);
      66                 :          81 :         errormsg = gettext_noop("cannot specify relation \"%s\" in the publication EXCEPT clause");
      67                 :             :     }
      68                 :             :     else
      69                 :             :     {
      70                 :         731 :         relname = RelationGetRelationName(targetrel);
      71                 :         731 :         errormsg = gettext_noop("cannot add relation \"%s\" to publication");
      72                 :             :     }
      73                 :             : 
      74                 :             :     /* If in EXCEPT clause, must be root partitioned table */
      75   [ +  +  +  + ]:         812 :     if (pri->except && targetrel->rd_rel->relispartition)
      76         [ +  - ]:           4 :         ereport(ERROR,
      77                 :             :                 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
      78                 :             :                  errmsg(errormsg, relname),
      79                 :             :                  errdetail("This operation is not supported for individual partitions.")));
      80                 :             : 
      81                 :             :     /* Must be a regular or partitioned table */
      82         [ +  + ]:         808 :     if (RelationGetForm(targetrel)->relkind != RELKIND_RELATION &&
      83         [ +  + ]:         115 :         RelationGetForm(targetrel)->relkind != RELKIND_PARTITIONED_TABLE)
      84         [ +  - ]:           9 :         ereport(ERROR,
      85                 :             :                 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
      86                 :             :                  errmsg(errormsg, relname),
      87                 :             :                  errdetail_relkind_not_supported(RelationGetForm(targetrel)->relkind)));
      88                 :             : 
      89                 :             :     /* Can't be system table */
      90         [ +  + ]:         799 :     if (IsCatalogRelation(targetrel))
      91         [ +  - ]:           4 :         ereport(ERROR,
      92                 :             :                 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
      93                 :             :                  errmsg(errormsg, relname),
      94                 :             :                  errdetail("This operation is not supported for system tables.")));
      95                 :             : 
      96                 :             :     /* Can't be conflict log table */
      97         [ +  + ]:         795 :     if (IsConflictLogTableNamespace(RelationGetNamespace(targetrel)))
      98         [ +  - ]:           4 :         ereport(ERROR,
      99                 :             :                 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     100                 :             :                  errmsg(errormsg, relname),
     101                 :             :                  errdetail("This operation is not supported for conflict log tables.")));
     102                 :             : 
     103                 :             :     /* UNLOGGED and TEMP relations cannot be part of publication. */
     104         [ +  + ]:         791 :     if (targetrel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
     105         [ +  - ]:           4 :         ereport(ERROR,
     106                 :             :                 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     107                 :             :                  errmsg(errormsg, relname),
     108                 :             :                  errdetail("This operation is not supported for temporary tables.")));
     109         [ +  + ]:         787 :     else if (targetrel->rd_rel->relpersistence == RELPERSISTENCE_UNLOGGED)
     110         [ +  - ]:           4 :         ereport(ERROR,
     111                 :             :                 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     112                 :             :                  errmsg(errormsg, relname),
     113                 :             :                  errdetail("This operation is not supported for unlogged tables.")));
     114                 :         783 : }
     115                 :             : 
     116                 :             : /*
     117                 :             :  * Check if schema can be in given publication and throw appropriate error if
     118                 :             :  * not.
     119                 :             :  */
     120                 :             : static void
     121                 :         164 : check_publication_add_schema(Oid schemaid)
     122                 :             : {
     123                 :             :     /* Can't be system namespace */
     124   [ +  +  +  -  :         324 :     if (IsCatalogNamespace(schemaid) || IsToastNamespace(schemaid) ||
                   -  + ]
     125                 :         160 :         IsConflictLogTableNamespace(schemaid))
     126         [ +  - ]:           4 :         ereport(ERROR,
     127                 :             :                 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     128                 :             :                  errmsg("cannot add schema \"%s\" to publication",
     129                 :             :                         get_namespace_name(schemaid)),
     130                 :             :                  errdetail("This operation is not supported for system schemas.")));
     131                 :             : 
     132                 :             :     /* Can't be temporary namespace */
     133         [ -  + ]:         160 :     if (isAnyTempNamespace(schemaid))
     134         [ #  # ]:           0 :         ereport(ERROR,
     135                 :             :                 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     136                 :             :                  errmsg("cannot add schema \"%s\" to publication",
     137                 :             :                         get_namespace_name(schemaid)),
     138                 :             :                  errdetail("Temporary schemas cannot be replicated.")));
     139                 :         160 : }
     140                 :             : 
     141                 :             : /*
     142                 :             :  * Returns if relation represented by oid and Form_pg_class entry
     143                 :             :  * is publishable.
     144                 :             :  *
     145                 :             :  * Does same checks as check_publication_add_relation() above except for
     146                 :             :  * RELKIND_SEQUENCE, but does not need relation to be opened and also does
     147                 :             :  * not throw errors. Here, the additional check is to support ALL SEQUENCES
     148                 :             :  * publication.
     149                 :             :  *
     150                 :             :  * XXX  This also excludes all tables with relid < FirstNormalObjectId,
     151                 :             :  * ie all tables created during initdb.  This mainly affects the preinstalled
     152                 :             :  * information_schema.  IsCatalogRelationOid() only excludes tables with
     153                 :             :  * relid < FirstUnpinnedObjectId, making that test rather redundant,
     154                 :             :  * but really we should get rid of the FirstNormalObjectId test not
     155                 :             :  * IsCatalogRelationOid.  We can't do so today because we don't want
     156                 :             :  * information_schema tables to be considered publishable; but this test
     157                 :             :  * is really inadequate for that, since the information_schema could be
     158                 :             :  * dropped and reloaded and then it'll be considered publishable.  The best
     159                 :             :  * long-term solution may be to add a "relispublishable" bool to pg_class,
     160                 :             :  * and depend on that instead of OID checks.  IsConflictLogTableClass()
     161                 :             :  * excludes tables in conflict schema.
     162                 :             :  */
     163                 :             : static bool
     164                 :      328599 : is_publishable_class(Oid relid, Form_pg_class reltuple)
     165                 :             : {
     166                 :      336919 :     return (reltuple->relkind == RELKIND_RELATION ||
     167         [ +  + ]:        8320 :             reltuple->relkind == RELKIND_PARTITIONED_TABLE ||
     168         [ +  + ]:        7296 :             reltuple->relkind == RELKIND_SEQUENCE) &&
     169         [ +  + ]:      322674 :         !IsCatalogRelationOid(relid) &&
     170         [ +  + ]:      317323 :         !IsConflictLogTableClass(reltuple) &&
     171   [ +  +  +  +  :      657198 :         reltuple->relpersistence == RELPERSISTENCE_PERMANENT &&
                   +  + ]
     172                 :             :         relid >= FirstNormalObjectId;
     173                 :             : }
     174                 :             : 
     175                 :             : /*
     176                 :             :  * Another variant of is_publishable_class(), taking a Relation.
     177                 :             :  */
     178                 :             : bool
     179                 :      302982 : is_publishable_relation(Relation rel)
     180                 :             : {
     181                 :      302982 :     return is_publishable_class(RelationGetRelid(rel), rel->rd_rel);
     182                 :             : }
     183                 :             : 
     184                 :             : /*
     185                 :             :  * Similar to is_publishable_class() but checks whether the given OID
     186                 :             :  * is a publishable "table" or not.
     187                 :             :  */
     188                 :             : static bool
     189                 :         554 : is_publishable_table(Oid tableoid)
     190                 :             : {
     191                 :             :     HeapTuple   tuple;
     192                 :             :     Form_pg_class relform;
     193                 :             : 
     194                 :         554 :     tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(tableoid));
     195         [ +  + ]:         554 :     if (!HeapTupleIsValid(tuple))
     196                 :           4 :         return false;
     197                 :             : 
     198                 :         550 :     relform = (Form_pg_class) GETSTRUCT(tuple);
     199                 :             : 
     200                 :             :     /*
     201                 :             :      * is_publishable_class() includes sequences, so we need to explicitly
     202                 :             :      * check the relkind to filter them out here.
     203                 :             :      */
     204   [ +  -  +  + ]:        1100 :     if (relform->relkind != RELKIND_SEQUENCE &&
     205                 :         550 :         is_publishable_class(tableoid, relform))
     206                 :             :     {
     207                 :         546 :         ReleaseSysCache(tuple);
     208                 :         546 :         return true;
     209                 :             :     }
     210                 :             : 
     211                 :           4 :     ReleaseSysCache(tuple);
     212                 :           4 :     return false;
     213                 :             : }
     214                 :             : 
     215                 :             : /*
     216                 :             :  * SQL-callable variant of the above
     217                 :             :  *
     218                 :             :  * This returns null when the relation does not exist.  This is intended to be
     219                 :             :  * used for example in psql to avoid gratuitous errors when there are
     220                 :             :  * concurrent catalog changes.
     221                 :             :  */
     222                 :             : Datum
     223                 :        4356 : pg_relation_is_publishable(PG_FUNCTION_ARGS)
     224                 :             : {
     225                 :        4356 :     Oid         relid = PG_GETARG_OID(0);
     226                 :             :     HeapTuple   tuple;
     227                 :             :     bool        result;
     228                 :             : 
     229                 :        4356 :     tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
     230         [ -  + ]:        4356 :     if (!HeapTupleIsValid(tuple))
     231                 :           0 :         PG_RETURN_NULL();
     232                 :        4356 :     result = is_publishable_class(relid, (Form_pg_class) GETSTRUCT(tuple));
     233                 :        4356 :     ReleaseSysCache(tuple);
     234                 :        4356 :     PG_RETURN_BOOL(result);
     235                 :             : }
     236                 :             : 
     237                 :             : /*
     238                 :             :  * Returns true if the ancestor is in the list of published relations.
     239                 :             :  * Otherwise, returns false.
     240                 :             :  */
     241                 :             : static bool
     242                 :          61 : is_ancestor_member_tableinfos(Oid ancestor, List *table_infos)
     243                 :             : {
     244                 :             :     ListCell   *lc;
     245                 :             : 
     246   [ +  -  +  +  :         272 :     foreach(lc, table_infos)
                   +  + ]
     247                 :             :     {
     248                 :         241 :         Oid         relid = ((published_rel *) lfirst(lc))->relid;
     249                 :             : 
     250         [ +  + ]:         241 :         if (relid == ancestor)
     251                 :          30 :             return true;
     252                 :             :     }
     253                 :             : 
     254                 :          31 :     return false;
     255                 :             : }
     256                 :             : 
     257                 :             : /*
     258                 :             :  * Filter out the partitions whose parent tables are also present in the list.
     259                 :             :  */
     260                 :             : static void
     261                 :         169 : filter_partitions(List *table_infos)
     262                 :             : {
     263                 :             :     ListCell   *lc;
     264                 :             : 
     265   [ +  +  +  +  :         399 :     foreach(lc, table_infos)
                   +  + ]
     266                 :             :     {
     267                 :         230 :         bool        skip = false;
     268                 :         230 :         List       *ancestors = NIL;
     269                 :             :         ListCell   *lc2;
     270                 :         230 :         published_rel *table_info = (published_rel *) lfirst(lc);
     271                 :             : 
     272         [ +  + ]:         230 :         if (get_rel_relispartition(table_info->relid))
     273                 :          61 :             ancestors = get_partition_ancestors(table_info->relid);
     274                 :             : 
     275   [ +  +  +  +  :         261 :         foreach(lc2, ancestors)
                   +  + ]
     276                 :             :         {
     277                 :          61 :             Oid         ancestor = lfirst_oid(lc2);
     278                 :             : 
     279         [ +  + ]:          61 :             if (is_ancestor_member_tableinfos(ancestor, table_infos))
     280                 :             :             {
     281                 :          30 :                 skip = true;
     282                 :          30 :                 break;
     283                 :             :             }
     284                 :             :         }
     285                 :             : 
     286         [ +  + ]:         230 :         if (skip)
     287                 :          30 :             table_infos = foreach_delete_current(table_infos, lc);
     288                 :             :     }
     289                 :         169 : }
     290                 :             : 
     291                 :             : /*
     292                 :             :  * Returns true if any schema is associated with the publication, false if no
     293                 :             :  * schema is associated with the publication.
     294                 :             :  */
     295                 :             : bool
     296                 :         215 : is_schema_publication(Oid pubid)
     297                 :             : {
     298                 :             :     Relation    pubschsrel;
     299                 :             :     ScanKeyData scankey;
     300                 :             :     SysScanDesc scan;
     301                 :             :     HeapTuple   tup;
     302                 :         215 :     bool        result = false;
     303                 :             : 
     304                 :         215 :     pubschsrel = table_open(PublicationNamespaceRelationId, AccessShareLock);
     305                 :         215 :     ScanKeyInit(&scankey,
     306                 :             :                 Anum_pg_publication_namespace_pnpubid,
     307                 :             :                 BTEqualStrategyNumber, F_OIDEQ,
     308                 :             :                 ObjectIdGetDatum(pubid));
     309                 :             : 
     310                 :         215 :     scan = systable_beginscan(pubschsrel,
     311                 :             :                               PublicationNamespacePnnspidPnpubidIndexId,
     312                 :             :                               true, NULL, 1, &scankey);
     313                 :         215 :     tup = systable_getnext(scan);
     314                 :         215 :     result = HeapTupleIsValid(tup);
     315                 :             : 
     316                 :         215 :     systable_endscan(scan);
     317                 :         215 :     table_close(pubschsrel, AccessShareLock);
     318                 :             : 
     319                 :         215 :     return result;
     320                 :             : }
     321                 :             : 
     322                 :             : /*
     323                 :             :  * Returns true if the publication has explicitly included relation (i.e.,
     324                 :             :  * not marked as EXCEPT).
     325                 :             :  */
     326                 :             : bool
     327                 :          49 : is_table_publication(Oid pubid)
     328                 :             : {
     329                 :             :     Relation    pubrelsrel;
     330                 :             :     ScanKeyData scankey;
     331                 :             :     SysScanDesc scan;
     332                 :             :     HeapTuple   tup;
     333                 :          49 :     bool        result = false;
     334                 :             : 
     335                 :          49 :     pubrelsrel = table_open(PublicationRelRelationId, AccessShareLock);
     336                 :          49 :     ScanKeyInit(&scankey,
     337                 :             :                 Anum_pg_publication_rel_prpubid,
     338                 :             :                 BTEqualStrategyNumber, F_OIDEQ,
     339                 :             :                 ObjectIdGetDatum(pubid));
     340                 :             : 
     341                 :          49 :     scan = systable_beginscan(pubrelsrel,
     342                 :             :                               PublicationRelPrpubidIndexId,
     343                 :             :                               true, NULL, 1, &scankey);
     344                 :          49 :     tup = systable_getnext(scan);
     345         [ +  + ]:          49 :     if (HeapTupleIsValid(tup))
     346                 :             :     {
     347                 :             :         Form_pg_publication_rel pubrel;
     348                 :             : 
     349                 :          25 :         pubrel = (Form_pg_publication_rel) GETSTRUCT(tup);
     350                 :             : 
     351                 :             :         /*
     352                 :             :          * For any publication, pg_publication_rel contains either only EXCEPT
     353                 :             :          * entries or only explicitly included tables. Therefore, examining
     354                 :             :          * the first tuple is sufficient to determine table inclusion.
     355                 :             :          */
     356                 :          25 :         result = !pubrel->prexcept;
     357                 :             :     }
     358                 :             : 
     359                 :          49 :     systable_endscan(scan);
     360                 :          49 :     table_close(pubrelsrel, AccessShareLock);
     361                 :             : 
     362                 :          49 :     return result;
     363                 :             : }
     364                 :             : 
     365                 :             : /*
     366                 :             :  * Returns true if the relation has column list associated with the
     367                 :             :  * publication, false otherwise.
     368                 :             :  *
     369                 :             :  * If a column list is found, the corresponding bitmap is returned through the
     370                 :             :  * cols parameter, if provided. The bitmap is constructed within the given
     371                 :             :  * memory context (mcxt).
     372                 :             :  */
     373                 :             : bool
     374                 :         941 : check_and_fetch_column_list(Publication *pub, Oid relid, MemoryContext mcxt,
     375                 :             :                             Bitmapset **cols)
     376                 :             : {
     377                 :             :     HeapTuple   cftuple;
     378                 :         941 :     bool        found = false;
     379                 :             : 
     380         [ +  + ]:         941 :     if (pub->alltables)
     381                 :         224 :         return false;
     382                 :             : 
     383                 :         717 :     cftuple = SearchSysCache2(PUBLICATIONRELMAP,
     384                 :             :                               ObjectIdGetDatum(relid),
     385                 :             :                               ObjectIdGetDatum(pub->oid));
     386         [ +  + ]:         717 :     if (HeapTupleIsValid(cftuple))
     387                 :             :     {
     388                 :             :         Datum       cfdatum;
     389                 :             :         bool        isnull;
     390                 :             : 
     391                 :             :         /* Lookup the column list attribute. */
     392                 :         659 :         cfdatum = SysCacheGetAttr(PUBLICATIONRELMAP, cftuple,
     393                 :             :                                   Anum_pg_publication_rel_prattrs, &isnull);
     394                 :             : 
     395                 :             :         /* Was a column list found? */
     396         [ +  + ]:         659 :         if (!isnull)
     397                 :             :         {
     398                 :             :             /* Build the column list bitmap in the given memory context. */
     399         [ +  + ]:         191 :             if (cols)
     400                 :         188 :                 *cols = pub_collist_to_bitmapset(*cols, cfdatum, mcxt);
     401                 :             : 
     402                 :         191 :             found = true;
     403                 :             :         }
     404                 :             : 
     405                 :         659 :         ReleaseSysCache(cftuple);
     406                 :             :     }
     407                 :             : 
     408                 :         717 :     return found;
     409                 :             : }
     410                 :             : 
     411                 :             : /*
     412                 :             :  * Gets the relations based on the publication partition option for a specified
     413                 :             :  * relation.
     414                 :             :  */
     415                 :             : List *
     416                 :        2497 : GetPubPartitionOptionRelations(List *result, PublicationPartOpt pub_partopt,
     417                 :             :                                Oid relid)
     418                 :             : {
     419   [ +  +  +  + ]:        2497 :     if (get_rel_relkind(relid) == RELKIND_PARTITIONED_TABLE &&
     420                 :             :         pub_partopt != PUBLICATION_PART_ROOT)
     421                 :         701 :     {
     422                 :         701 :         List       *all_parts = find_all_inheritors(relid, NoLock,
     423                 :             :                                                     NULL);
     424                 :             : 
     425         [ +  + ]:         701 :         if (pub_partopt == PUBLICATION_PART_ALL)
     426                 :         682 :             result = list_concat(result, all_parts);
     427         [ +  - ]:          19 :         else if (pub_partopt == PUBLICATION_PART_LEAF)
     428                 :             :         {
     429                 :             :             ListCell   *lc;
     430                 :             : 
     431   [ +  -  +  +  :          69 :             foreach(lc, all_parts)
                   +  + ]
     432                 :             :             {
     433                 :          50 :                 Oid         partOid = lfirst_oid(lc);
     434                 :             : 
     435         [ +  + ]:          50 :                 if (get_rel_relkind(partOid) != RELKIND_PARTITIONED_TABLE)
     436                 :          30 :                     result = lappend_oid(result, partOid);
     437                 :             :             }
     438                 :             :         }
     439                 :             :         else
     440                 :             :             Assert(false);
     441                 :             :     }
     442                 :             :     else
     443                 :        1796 :         result = lappend_oid(result, relid);
     444                 :             : 
     445                 :        2497 :     return result;
     446                 :             : }
     447                 :             : 
     448                 :             : /*
     449                 :             :  * Returns the relid of the topmost ancestor that is published via this
     450                 :             :  * publication if any and set its ancestor level to ancestor_level,
     451                 :             :  * otherwise returns InvalidOid.
     452                 :             :  *
     453                 :             :  * The ancestor_level value allows us to compare the results for multiple
     454                 :             :  * publications, and decide which value is higher up.
     455                 :             :  *
     456                 :             :  * Note that the list of ancestors should be ordered such that the topmost
     457                 :             :  * ancestor is at the end of the list.
     458                 :             :  */
     459                 :             : Oid
     460                 :         428 : GetTopMostAncestorInPublication(Oid puboid, List *ancestors, int *ancestor_level)
     461                 :             : {
     462                 :             :     ListCell   *lc;
     463                 :         428 :     Oid         topmost_relid = InvalidOid;
     464                 :         428 :     int         level = 0;
     465                 :             : 
     466                 :             :     /*
     467                 :             :      * Find the "topmost" ancestor that is in this publication.
     468                 :             :      */
     469   [ +  -  +  +  :         868 :     foreach(lc, ancestors)
                   +  + ]
     470                 :             :     {
     471                 :         440 :         Oid         ancestor = lfirst_oid(lc);
     472                 :         440 :         List       *apubids = GetRelationIncludedPublications(ancestor);
     473                 :         440 :         List       *aschemaPubids = NIL;
     474                 :             : 
     475                 :         440 :         level++;
     476                 :             : 
     477         [ +  + ]:         440 :         if (list_member_oid(apubids, puboid))
     478                 :             :         {
     479                 :         223 :             topmost_relid = ancestor;
     480                 :             : 
     481         [ +  + ]:         223 :             if (ancestor_level)
     482                 :          43 :                 *ancestor_level = level;
     483                 :             :         }
     484                 :             :         else
     485                 :             :         {
     486                 :         217 :             aschemaPubids = GetSchemaPublications(get_rel_namespace(ancestor));
     487         [ +  + ]:         217 :             if (list_member_oid(aschemaPubids, puboid))
     488                 :             :             {
     489                 :          13 :                 topmost_relid = ancestor;
     490                 :             : 
     491         [ +  + ]:          13 :                 if (ancestor_level)
     492                 :           5 :                     *ancestor_level = level;
     493                 :             :             }
     494                 :             :         }
     495                 :             : 
     496                 :         440 :         list_free(apubids);
     497                 :         440 :         list_free(aschemaPubids);
     498                 :             :     }
     499                 :             : 
     500                 :         428 :     return topmost_relid;
     501                 :             : }
     502                 :             : 
     503                 :             : /*
     504                 :             :  * attnumstoint2vector
     505                 :             :  *      Convert a Bitmapset of AttrNumbers into an int2vector.
     506                 :             :  *
     507                 :             :  * AttrNumber numbers are 0-based, i.e., not offset by
     508                 :             :  * FirstLowInvalidHeapAttributeNumber.
     509                 :             :  */
     510                 :             : static int2vector *
     511                 :         212 : attnumstoint2vector(Bitmapset *attrs)
     512                 :             : {
     513                 :             :     int2vector *result;
     514                 :         212 :     int         n = bms_num_members(attrs);
     515                 :         212 :     int         i = -1;
     516                 :         212 :     int         j = 0;
     517                 :             : 
     518                 :         212 :     result = buildint2vector(NULL, n);
     519                 :             : 
     520         [ +  + ]:         577 :     while ((i = bms_next_member(attrs, i)) >= 0)
     521                 :             :     {
     522                 :             :         Assert(i <= PG_INT16_MAX);
     523                 :             : 
     524                 :         365 :         result->values[j++] = (int16) i;
     525                 :             :     }
     526                 :             : 
     527                 :         212 :     return result;
     528                 :             : }
     529                 :             : 
     530                 :             : /*
     531                 :             :  * Insert new publication / relation mapping.
     532                 :             :  */
     533                 :             : ObjectAddress
     534                 :         834 : publication_add_relation(Oid pubid, PublicationRelInfo *pri,
     535                 :             :                          bool if_not_exists, AlterPublicationStmt *alter_stmt)
     536                 :             : {
     537                 :             :     Relation    rel;
     538                 :             :     HeapTuple   tup;
     539                 :             :     Datum       values[Natts_pg_publication_rel];
     540                 :             :     bool        nulls[Natts_pg_publication_rel];
     541                 :         834 :     Relation    targetrel = pri->relation;
     542                 :         834 :     Oid         relid = RelationGetRelid(targetrel);
     543                 :             :     Oid         pubreloid;
     544                 :             :     Bitmapset  *attnums;
     545                 :         834 :     Publication *pub = GetPublication(pubid);
     546                 :             :     ObjectAddress myself,
     547                 :             :                 referenced;
     548                 :         834 :     List       *relids = NIL;
     549                 :             :     int         i;
     550                 :             :     bool        inval_except_table;
     551                 :             : 
     552                 :         834 :     rel = table_open(PublicationRelRelationId, RowExclusiveLock);
     553                 :             : 
     554                 :             :     /*
     555                 :             :      * Check for duplicates. Note that this does not really prevent
     556                 :             :      * duplicates, it's here just to provide nicer error message in common
     557                 :             :      * case. The real protection is the unique key on the catalog.
     558                 :             :      */
     559         [ +  + ]:         834 :     if (SearchSysCacheExists2(PUBLICATIONRELMAP, ObjectIdGetDatum(relid),
     560                 :             :                               ObjectIdGetDatum(pubid)))
     561                 :             :     {
     562                 :          22 :         table_close(rel, RowExclusiveLock);
     563                 :             : 
     564         [ +  + ]:          22 :         if (if_not_exists)
     565                 :          18 :             return InvalidObjectAddress;
     566                 :             : 
     567         [ +  - ]:           4 :         ereport(ERROR,
     568                 :             :                 (errcode(ERRCODE_DUPLICATE_OBJECT),
     569                 :             :                  errmsg("relation \"%s\" is already a member of publication \"%s\"",
     570                 :             :                         RelationGetRelationName(targetrel), pub->name)));
     571                 :             :     }
     572                 :             : 
     573                 :         812 :     check_publication_add_relation(pri);
     574                 :             : 
     575                 :             :     /* Validate and translate column names into a Bitmapset of attnums. */
     576                 :         783 :     attnums = pub_collist_validate(pri->relation, pri->columns);
     577                 :             : 
     578                 :             :     /* Form a tuple. */
     579                 :         767 :     memset(values, 0, sizeof(values));
     580                 :         767 :     memset(nulls, false, sizeof(nulls));
     581                 :             : 
     582                 :         767 :     pubreloid = GetNewOidWithIndex(rel, PublicationRelObjectIndexId,
     583                 :             :                                    Anum_pg_publication_rel_oid);
     584                 :         767 :     values[Anum_pg_publication_rel_oid - 1] = ObjectIdGetDatum(pubreloid);
     585                 :         767 :     values[Anum_pg_publication_rel_prpubid - 1] =
     586                 :         767 :         ObjectIdGetDatum(pubid);
     587                 :         767 :     values[Anum_pg_publication_rel_prrelid - 1] =
     588                 :         767 :         ObjectIdGetDatum(relid);
     589                 :         767 :     values[Anum_pg_publication_rel_prexcept - 1] =
     590                 :         767 :         BoolGetDatum(pri->except);
     591                 :             : 
     592                 :             :     /* Add qualifications, if available */
     593         [ +  + ]:         767 :     if (pri->whereClause != NULL)
     594                 :         220 :         values[Anum_pg_publication_rel_prqual - 1] = CStringGetTextDatum(nodeToString(pri->whereClause));
     595                 :             :     else
     596                 :         547 :         nulls[Anum_pg_publication_rel_prqual - 1] = true;
     597                 :             : 
     598                 :             :     /* Add column list, if available */
     599         [ +  + ]:         767 :     if (pri->columns)
     600                 :         212 :         values[Anum_pg_publication_rel_prattrs - 1] = PointerGetDatum(attnumstoint2vector(attnums));
     601                 :             :     else
     602                 :         555 :         nulls[Anum_pg_publication_rel_prattrs - 1] = true;
     603                 :             : 
     604                 :         767 :     tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
     605                 :             : 
     606                 :             :     /* Insert tuple into catalog. */
     607                 :         767 :     CatalogTupleInsert(rel, tup);
     608                 :         767 :     heap_freetuple(tup);
     609                 :             : 
     610                 :             :     /* Register dependencies as needed */
     611                 :         767 :     ObjectAddressSet(myself, PublicationRelRelationId, pubreloid);
     612                 :             : 
     613                 :             :     /* Add dependency on the publication */
     614                 :         767 :     ObjectAddressSet(referenced, PublicationRelationId, pubid);
     615                 :         767 :     recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
     616                 :             : 
     617                 :             :     /* Add dependency on the relation */
     618                 :         767 :     ObjectAddressSet(referenced, RelationRelationId, relid);
     619                 :         767 :     recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
     620                 :             : 
     621                 :             :     /* Add dependency on the objects mentioned in the qualifications */
     622         [ +  + ]:         767 :     if (pri->whereClause)
     623                 :             :     {
     624                 :         220 :         CheckUsageOnTypesInSingleRelExpr(pri->whereClause, relid, GetUserId());
     625                 :         220 :         recordDependencyOnSingleRelExpr(&myself, pri->whereClause, relid,
     626                 :             :                                         DEPENDENCY_NORMAL, DEPENDENCY_NORMAL,
     627                 :             :                                         false);
     628                 :             :     }
     629                 :             : 
     630                 :             :     /* Add dependency on the columns, if any are listed */
     631                 :         767 :     i = -1;
     632         [ +  + ]:        1132 :     while ((i = bms_next_member(attnums, i)) >= 0)
     633                 :             :     {
     634                 :         365 :         ObjectAddressSubSet(referenced, RelationRelationId, relid, i);
     635                 :         365 :         recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
     636                 :             :     }
     637                 :             : 
     638                 :             :     /* Close the table. */
     639                 :         767 :     table_close(rel, RowExclusiveLock);
     640                 :             : 
     641                 :             :     /*
     642                 :             :      * Determine whether EXCEPT tables require explicit relcache invalidation.
     643                 :             :      *
     644                 :             :      * For CREATE PUBLICATION with EXCEPT tables, invalidation is skipped
     645                 :             :      * here, as CreatePublication() function invalidates all relations as part
     646                 :             :      * of defining a FOR ALL TABLES publication.
     647                 :             :      *
     648                 :             :      * For ALTER PUBLICATION, invalidation is needed only when adding an
     649                 :             :      * EXCEPT table to a publication already marked as ALL TABLES. For
     650                 :             :      * publications that were originally empty or defined as ALL SEQUENCES and
     651                 :             :      * are being converted to ALL TABLES, invalidation is skipped here, as
     652                 :             :      * AlterPublicationAllFlags() function invalidates all relations while
     653                 :             :      * marking the publication as ALL TABLES publication.
     654                 :             :      */
     655   [ +  +  +  + ]:         776 :     inval_except_table = (alter_stmt != NULL) && pub->alltables &&
     656   [ +  -  +  - ]:           9 :         (alter_stmt->for_all_tables && pri->except);
     657                 :             : 
     658   [ +  +  +  + ]:         767 :     if (!pri->except || inval_except_table)
     659                 :             :     {
     660                 :             :         /*
     661                 :             :          * Invalidate relcache so that publication info is rebuilt.
     662                 :             :          *
     663                 :             :          * For the partitioned tables, we must invalidate all partitions
     664                 :             :          * contained in the respective partition hierarchies, not just the one
     665                 :             :          * explicitly mentioned in the publication. This is required because
     666                 :             :          * we implicitly publish the child tables when the parent table is
     667                 :             :          * published.
     668                 :             :          */
     669                 :         699 :         relids = GetPubPartitionOptionRelations(relids, PUBLICATION_PART_ALL,
     670                 :             :                                                 relid);
     671                 :             : 
     672                 :         699 :         InvalidatePublicationRels(relids);
     673                 :             :     }
     674                 :             : 
     675                 :         767 :     return myself;
     676                 :             : }
     677                 :             : 
     678                 :             : /*
     679                 :             :  * pub_collist_validate
     680                 :             :  *      Process and validate the 'columns' list and ensure the columns are all
     681                 :             :  *      valid to use for a publication.  Checks for and raises an ERROR for
     682                 :             :  *      any unknown columns, system columns, duplicate columns, or virtual
     683                 :             :  *      generated columns.
     684                 :             :  *
     685                 :             :  * Looks up each column's attnum and returns a 0-based Bitmapset of the
     686                 :             :  * corresponding attnums.
     687                 :             :  */
     688                 :             : Bitmapset *
     689                 :        1073 : pub_collist_validate(Relation targetrel, List *columns)
     690                 :             : {
     691                 :        1073 :     Bitmapset  *set = NULL;
     692                 :             :     ListCell   *lc;
     693                 :        1073 :     TupleDesc   tupdesc = RelationGetDescr(targetrel);
     694                 :             : 
     695   [ +  +  +  +  :        1618 :     foreach(lc, columns)
                   +  + ]
     696                 :             :     {
     697                 :         569 :         char       *colname = strVal(lfirst(lc));
     698                 :         569 :         AttrNumber  attnum = get_attnum(RelationGetRelid(targetrel), colname);
     699                 :             : 
     700         [ +  + ]:         569 :         if (attnum == InvalidAttrNumber)
     701         [ +  - ]:           4 :             ereport(ERROR,
     702                 :             :                     errcode(ERRCODE_UNDEFINED_COLUMN),
     703                 :             :                     errmsg("column \"%s\" of relation \"%s\" does not exist",
     704                 :             :                            colname, RelationGetRelationName(targetrel)));
     705                 :             : 
     706         [ +  + ]:         565 :         if (!AttrNumberIsForUserDefinedAttr(attnum))
     707         [ +  - ]:           8 :             ereport(ERROR,
     708                 :             :                     errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
     709                 :             :                     errmsg("cannot use system column \"%s\" in publication column list",
     710                 :             :                            colname));
     711                 :             : 
     712         [ +  + ]:         557 :         if (TupleDescAttr(tupdesc, attnum - 1)->attgenerated == ATTRIBUTE_GENERATED_VIRTUAL)
     713         [ +  - ]:           4 :             ereport(ERROR,
     714                 :             :                     errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
     715                 :             :                     errmsg("cannot use virtual generated column \"%s\" in publication column list",
     716                 :             :                            colname));
     717                 :             : 
     718         [ +  + ]:         553 :         if (bms_is_member(attnum, set))
     719         [ +  - ]:           8 :             ereport(ERROR,
     720                 :             :                     errcode(ERRCODE_DUPLICATE_OBJECT),
     721                 :             :                     errmsg("duplicate column \"%s\" in publication column list",
     722                 :             :                            colname));
     723                 :             : 
     724                 :         545 :         set = bms_add_member(set, attnum);
     725                 :             :     }
     726                 :             : 
     727                 :        1049 :     return set;
     728                 :             : }
     729                 :             : 
     730                 :             : /*
     731                 :             :  * Transform a column list (represented by an array Datum) to a bitmapset.
     732                 :             :  *
     733                 :             :  * If columns isn't NULL, add the column numbers to that set.
     734                 :             :  *
     735                 :             :  * If mcxt isn't NULL, build the bitmapset in that context.
     736                 :             :  */
     737                 :             : Bitmapset *
     738                 :         278 : pub_collist_to_bitmapset(Bitmapset *columns, Datum pubcols, MemoryContext mcxt)
     739                 :             : {
     740                 :         278 :     Bitmapset  *result = columns;
     741                 :             :     ArrayType  *arr;
     742                 :             :     int         nelems;
     743                 :             :     int16      *elems;
     744                 :         278 :     MemoryContext oldcxt = NULL;
     745                 :             : 
     746                 :         278 :     arr = DatumGetArrayTypeP(pubcols);
     747                 :         278 :     nelems = ARR_DIMS(arr)[0];
     748         [ -  + ]:         278 :     elems = (int16 *) ARR_DATA_PTR(arr);
     749                 :             : 
     750                 :             :     /* If a memory context was specified, switch to it. */
     751         [ +  + ]:         278 :     if (mcxt)
     752                 :          39 :         oldcxt = MemoryContextSwitchTo(mcxt);
     753                 :             : 
     754         [ +  + ]:         764 :     for (int i = 0; i < nelems; i++)
     755                 :         486 :         result = bms_add_member(result, elems[i]);
     756                 :             : 
     757         [ +  + ]:         278 :     if (mcxt)
     758                 :          39 :         MemoryContextSwitchTo(oldcxt);
     759                 :             : 
     760                 :         278 :     return result;
     761                 :             : }
     762                 :             : 
     763                 :             : /*
     764                 :             :  * Returns a bitmap representing the columns of the specified table.
     765                 :             :  *
     766                 :             :  * Generated columns are included if include_gencols_type is
     767                 :             :  * PUBLISH_GENCOLS_STORED.
     768                 :             :  */
     769                 :             : Bitmapset *
     770                 :           9 : pub_form_cols_map(Relation relation, PublishGencolsType include_gencols_type)
     771                 :             : {
     772                 :           9 :     Bitmapset  *result = NULL;
     773                 :           9 :     TupleDesc   desc = RelationGetDescr(relation);
     774                 :             : 
     775         [ +  + ]:          30 :     for (int i = 0; i < desc->natts; i++)
     776                 :             :     {
     777                 :          21 :         Form_pg_attribute att = TupleDescAttr(desc, i);
     778                 :             : 
     779         [ +  + ]:          21 :         if (att->attisdropped)
     780                 :           1 :             continue;
     781                 :             : 
     782         [ +  + ]:          20 :         if (att->attgenerated)
     783                 :             :         {
     784                 :             :             /* We only support replication of STORED generated cols. */
     785         [ +  + ]:           2 :             if (att->attgenerated != ATTRIBUTE_GENERATED_STORED)
     786                 :           1 :                 continue;
     787                 :             : 
     788                 :             :             /* User hasn't requested to replicate STORED generated cols. */
     789         [ +  - ]:           1 :             if (include_gencols_type != PUBLISH_GENCOLS_STORED)
     790                 :           1 :                 continue;
     791                 :             :         }
     792                 :             : 
     793                 :          18 :         result = bms_add_member(result, att->attnum);
     794                 :             :     }
     795                 :             : 
     796                 :           9 :     return result;
     797                 :             : }
     798                 :             : 
     799                 :             : /*
     800                 :             :  * Insert new publication / schema mapping.
     801                 :             :  */
     802                 :             : ObjectAddress
     803                 :         176 : publication_add_schema(Oid pubid, Oid schemaid, bool if_not_exists)
     804                 :             : {
     805                 :             :     Relation    rel;
     806                 :             :     HeapTuple   tup;
     807                 :             :     Datum       values[Natts_pg_publication_namespace];
     808                 :             :     bool        nulls[Natts_pg_publication_namespace];
     809                 :             :     Oid         psschid;
     810                 :         176 :     Publication *pub = GetPublication(pubid);
     811                 :         176 :     List       *schemaRels = NIL;
     812                 :             :     ObjectAddress myself,
     813                 :             :                 referenced;
     814                 :             : 
     815                 :         176 :     rel = table_open(PublicationNamespaceRelationId, RowExclusiveLock);
     816                 :             : 
     817                 :             :     /*
     818                 :             :      * Check for duplicates. Note that this does not really prevent
     819                 :             :      * duplicates, it's here just to provide nicer error message in common
     820                 :             :      * case. The real protection is the unique key on the catalog.
     821                 :             :      */
     822         [ +  + ]:         176 :     if (SearchSysCacheExists2(PUBLICATIONNAMESPACEMAP,
     823                 :             :                               ObjectIdGetDatum(schemaid),
     824                 :             :                               ObjectIdGetDatum(pubid)))
     825                 :             :     {
     826                 :          12 :         table_close(rel, RowExclusiveLock);
     827                 :             : 
     828         [ +  + ]:          12 :         if (if_not_exists)
     829                 :           8 :             return InvalidObjectAddress;
     830                 :             : 
     831         [ +  - ]:           4 :         ereport(ERROR,
     832                 :             :                 (errcode(ERRCODE_DUPLICATE_OBJECT),
     833                 :             :                  errmsg("schema \"%s\" is already a member of publication \"%s\"",
     834                 :             :                         get_namespace_name(schemaid), pub->name)));
     835                 :             :     }
     836                 :             : 
     837                 :         164 :     check_publication_add_schema(schemaid);
     838                 :             : 
     839                 :             :     /* Form a tuple */
     840                 :         160 :     memset(values, 0, sizeof(values));
     841                 :         160 :     memset(nulls, false, sizeof(nulls));
     842                 :             : 
     843                 :         160 :     psschid = GetNewOidWithIndex(rel, PublicationNamespaceObjectIndexId,
     844                 :             :                                  Anum_pg_publication_namespace_oid);
     845                 :         160 :     values[Anum_pg_publication_namespace_oid - 1] = ObjectIdGetDatum(psschid);
     846                 :         160 :     values[Anum_pg_publication_namespace_pnpubid - 1] =
     847                 :         160 :         ObjectIdGetDatum(pubid);
     848                 :         160 :     values[Anum_pg_publication_namespace_pnnspid - 1] =
     849                 :         160 :         ObjectIdGetDatum(schemaid);
     850                 :             : 
     851                 :         160 :     tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
     852                 :             : 
     853                 :             :     /* Insert tuple into catalog */
     854                 :         160 :     CatalogTupleInsert(rel, tup);
     855                 :         160 :     heap_freetuple(tup);
     856                 :             : 
     857                 :         160 :     ObjectAddressSet(myself, PublicationNamespaceRelationId, psschid);
     858                 :             : 
     859                 :             :     /* Add dependency on the publication */
     860                 :         160 :     ObjectAddressSet(referenced, PublicationRelationId, pubid);
     861                 :         160 :     recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
     862                 :             : 
     863                 :             :     /* Add dependency on the schema */
     864                 :         160 :     ObjectAddressSet(referenced, NamespaceRelationId, schemaid);
     865                 :         160 :     recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
     866                 :             : 
     867                 :             :     /* Close the table */
     868                 :         160 :     table_close(rel, RowExclusiveLock);
     869                 :             : 
     870                 :             :     /*
     871                 :             :      * Invalidate relcache so that publication info is rebuilt. See
     872                 :             :      * publication_add_relation for why we need to consider all the
     873                 :             :      * partitions.
     874                 :             :      */
     875                 :         160 :     schemaRels = GetSchemaPublicationRelations(schemaid,
     876                 :             :                                                PUBLICATION_PART_ALL);
     877                 :         160 :     InvalidatePublicationRels(schemaRels);
     878                 :             : 
     879                 :         160 :     return myself;
     880                 :             : }
     881                 :             : 
     882                 :             : /*
     883                 :             :  * Internal function to get the list of publication oids for a relation.
     884                 :             :  *
     885                 :             :  * If except_flag is true, returns the list of publication that specified the
     886                 :             :  * relation in the EXCEPT clause; otherwise, returns the list of publications
     887                 :             :  * in which relation is included.
     888                 :             :  */
     889                 :             : static List *
     890                 :       16976 : get_relation_publications(Oid relid, bool except_flag)
     891                 :             : {
     892                 :       16976 :     List       *result = NIL;
     893                 :             :     CatCList   *pubrellist;
     894                 :             : 
     895                 :             :     /* Find all publications associated with the relation. */
     896                 :       16976 :     pubrellist = SearchSysCacheList1(PUBLICATIONRELMAP,
     897                 :             :                                      ObjectIdGetDatum(relid));
     898         [ +  + ]:       18649 :     for (int i = 0; i < pubrellist->n_members; i++)
     899                 :             :     {
     900                 :        1673 :         HeapTuple   tup = &pubrellist->members[i]->tuple;
     901                 :        1673 :         Form_pg_publication_rel pubrel = (Form_pg_publication_rel) GETSTRUCT(tup);
     902                 :        1673 :         Oid         pubid = pubrel->prpubid;
     903                 :             : 
     904         [ +  + ]:        1673 :         if (pubrel->prexcept == except_flag)
     905                 :        1197 :             result = lappend_oid(result, pubid);
     906                 :             :     }
     907                 :             : 
     908                 :       16976 :     ReleaseSysCacheList(pubrellist);
     909                 :             : 
     910                 :       16976 :     return result;
     911                 :             : }
     912                 :             : 
     913                 :             : /*
     914                 :             :  * Gets list of publication oids for a relation.
     915                 :             :  */
     916                 :             : List *
     917                 :        9156 : GetRelationIncludedPublications(Oid relid)
     918                 :             : {
     919                 :        9156 :     return get_relation_publications(relid, false);
     920                 :             : }
     921                 :             : 
     922                 :             : /*
     923                 :             :  * Gets list of publication oids which has relation in the EXCEPT clause.
     924                 :             :  */
     925                 :             : List *
     926                 :        7820 : GetRelationExcludedPublications(Oid relid)
     927                 :             : {
     928                 :        7820 :     return get_relation_publications(relid, true);
     929                 :             : }
     930                 :             : 
     931                 :             : /*
     932                 :             :  * Internal function to get the list of relation oids for a publication.
     933                 :             :  *
     934                 :             :  * If except_flag is true, returns the list of relations specified in the
     935                 :             :  * EXCEPT clause of the publication; otherwise, returns the list of relations
     936                 :             :  * included in the publication.
     937                 :             :  */
     938                 :             : static List *
     939                 :         680 : get_publication_relations(Oid pubid, PublicationPartOpt pub_partopt,
     940                 :             :                           bool except_flag)
     941                 :             : {
     942                 :             :     List       *result;
     943                 :             :     Relation    pubrelsrel;
     944                 :             :     ScanKeyData scankey;
     945                 :             :     SysScanDesc scan;
     946                 :             :     HeapTuple   tup;
     947                 :             : 
     948                 :             :     /* Find all relations associated with the publication. */
     949                 :         680 :     pubrelsrel = table_open(PublicationRelRelationId, AccessShareLock);
     950                 :             : 
     951                 :         680 :     ScanKeyInit(&scankey,
     952                 :             :                 Anum_pg_publication_rel_prpubid,
     953                 :             :                 BTEqualStrategyNumber, F_OIDEQ,
     954                 :             :                 ObjectIdGetDatum(pubid));
     955                 :             : 
     956                 :         680 :     scan = systable_beginscan(pubrelsrel, PublicationRelPrpubidIndexId,
     957                 :             :                               true, NULL, 1, &scankey);
     958                 :             : 
     959                 :         680 :     result = NIL;
     960         [ +  + ]:        1998 :     while (HeapTupleIsValid(tup = systable_getnext(scan)))
     961                 :             :     {
     962                 :             :         Form_pg_publication_rel pubrel;
     963                 :             : 
     964                 :         638 :         pubrel = (Form_pg_publication_rel) GETSTRUCT(tup);
     965                 :             : 
     966         [ -  + ]:         638 :         if (except_flag == pubrel->prexcept)
     967                 :         638 :             result = GetPubPartitionOptionRelations(result, pub_partopt,
     968                 :             :                                                     pubrel->prrelid);
     969                 :             :     }
     970                 :             : 
     971                 :         680 :     systable_endscan(scan);
     972                 :         680 :     table_close(pubrelsrel, AccessShareLock);
     973                 :             : 
     974                 :             :     /* Now sort and de-duplicate the result list */
     975                 :         680 :     list_sort(result, list_oid_cmp);
     976                 :         680 :     list_deduplicate_oid(result);
     977                 :             : 
     978                 :         680 :     return result;
     979                 :             : }
     980                 :             : 
     981                 :             : /*
     982                 :             :  * Gets list of relation oids that are associated with a publication.
     983                 :             :  *
     984                 :             :  * This should only be used FOR TABLE publications, the FOR ALL TABLES/SEQUENCES
     985                 :             :  * should use GetAllPublicationRelations().
     986                 :             :  */
     987                 :             : List *
     988                 :         610 : GetIncludedPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
     989                 :             : {
     990                 :             :     Assert(!GetPublication(pubid)->alltables);
     991                 :             : 
     992                 :         610 :     return get_publication_relations(pubid, pub_partopt, false);
     993                 :             : }
     994                 :             : 
     995                 :             : /*
     996                 :             :  * Gets list of table oids that were specified in the EXCEPT clause for a
     997                 :             :  * publication.
     998                 :             :  *
     999                 :             :  * This should only be used FOR ALL TABLES publications.
    1000                 :             :  */
    1001                 :             : List *
    1002                 :          70 : GetExcludedPublicationTables(Oid pubid, PublicationPartOpt pub_partopt)
    1003                 :             : {
    1004                 :             :     Assert(GetPublication(pubid)->alltables);
    1005                 :             : 
    1006                 :          70 :     return get_publication_relations(pubid, pub_partopt, true);
    1007                 :             : }
    1008                 :             : 
    1009                 :             : /*
    1010                 :             :  * Gets list of publication oids for publications marked as FOR ALL TABLES.
    1011                 :             :  */
    1012                 :             : List *
    1013                 :        6114 : GetAllTablesPublications(void)
    1014                 :             : {
    1015                 :             :     List       *result;
    1016                 :             :     Relation    rel;
    1017                 :             :     ScanKeyData scankey;
    1018                 :             :     SysScanDesc scan;
    1019                 :             :     HeapTuple   tup;
    1020                 :             : 
    1021                 :             :     /* Find all publications that are marked as for all tables. */
    1022                 :        6114 :     rel = table_open(PublicationRelationId, AccessShareLock);
    1023                 :             : 
    1024                 :        6114 :     ScanKeyInit(&scankey,
    1025                 :             :                 Anum_pg_publication_puballtables,
    1026                 :             :                 BTEqualStrategyNumber, F_BOOLEQ,
    1027                 :             :                 BoolGetDatum(true));
    1028                 :             : 
    1029                 :        6114 :     scan = systable_beginscan(rel, InvalidOid, false,
    1030                 :             :                               NULL, 1, &scankey);
    1031                 :             : 
    1032                 :        6114 :     result = NIL;
    1033         [ +  + ]:        6244 :     while (HeapTupleIsValid(tup = systable_getnext(scan)))
    1034                 :             :     {
    1035                 :         130 :         Oid         oid = ((Form_pg_publication) GETSTRUCT(tup))->oid;
    1036                 :             : 
    1037                 :         130 :         result = lappend_oid(result, oid);
    1038                 :             :     }
    1039                 :             : 
    1040                 :        6114 :     systable_endscan(scan);
    1041                 :        6114 :     table_close(rel, AccessShareLock);
    1042                 :             : 
    1043                 :        6114 :     return result;
    1044                 :             : }
    1045                 :             : 
    1046                 :             : /*
    1047                 :             :  * Gets list of all relations published by FOR ALL TABLES/SEQUENCES
    1048                 :             :  * publication.
    1049                 :             :  *
    1050                 :             :  * If the publication publishes partition changes via their respective root
    1051                 :             :  * partitioned tables, we must exclude partitions in favor of including the
    1052                 :             :  * root partitioned tables. This is not applicable to FOR ALL SEQUENCES
    1053                 :             :  * publication.
    1054                 :             :  *
    1055                 :             :  * For a FOR ALL TABLES publication, the returned list excludes tables mentioned
    1056                 :             :  * in the EXCEPT clause.
    1057                 :             :  */
    1058                 :             : List *
    1059                 :          55 : GetAllPublicationRelations(Oid pubid, char relkind, bool pubviaroot)
    1060                 :             : {
    1061                 :             :     Relation    classRel;
    1062                 :             :     ScanKeyData key[1];
    1063                 :             :     TableScanDesc scan;
    1064                 :             :     HeapTuple   tuple;
    1065                 :          55 :     List       *result = NIL;
    1066                 :          55 :     List       *exceptlist = NIL;
    1067                 :             : 
    1068                 :             :     Assert(!(relkind == RELKIND_SEQUENCE && pubviaroot));
    1069                 :             : 
    1070                 :             :     /* EXCEPT filtering applies only to relations, not sequences */
    1071         [ +  + ]:          55 :     if (relkind == RELKIND_RELATION)
    1072                 :          49 :         exceptlist = GetExcludedPublicationTables(pubid, pubviaroot ?
    1073                 :          49 :                                                   PUBLICATION_PART_ROOT :
    1074                 :             :                                                   PUBLICATION_PART_LEAF);
    1075                 :             : 
    1076                 :          55 :     classRel = table_open(RelationRelationId, AccessShareLock);
    1077                 :             : 
    1078                 :          55 :     ScanKeyInit(&key[0],
    1079                 :             :                 Anum_pg_class_relkind,
    1080                 :             :                 BTEqualStrategyNumber, F_CHAREQ,
    1081                 :             :                 CharGetDatum(relkind));
    1082                 :             : 
    1083                 :          55 :     scan = table_beginscan_catalog(classRel, 1, key);
    1084                 :             : 
    1085         [ +  + ]:        3769 :     while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
    1086                 :             :     {
    1087                 :        3714 :         Form_pg_class relForm = (Form_pg_class) GETSTRUCT(tuple);
    1088                 :        3714 :         Oid         relid = relForm->oid;
    1089                 :             : 
    1090         [ +  + ]:        3714 :         if (is_publishable_class(relid, relForm) &&
    1091   [ +  +  +  + ]:         135 :             !(relForm->relispartition && pubviaroot) &&
    1092         [ +  + ]:         112 :             !list_member_oid(exceptlist, relid))
    1093                 :         104 :             result = lappend_oid(result, relid);
    1094                 :             :     }
    1095                 :             : 
    1096                 :          55 :     table_endscan(scan);
    1097                 :             : 
    1098         [ +  + ]:          55 :     if (pubviaroot)
    1099                 :             :     {
    1100                 :           4 :         ScanKeyInit(&key[0],
    1101                 :             :                     Anum_pg_class_relkind,
    1102                 :             :                     BTEqualStrategyNumber, F_CHAREQ,
    1103                 :             :                     CharGetDatum(RELKIND_PARTITIONED_TABLE));
    1104                 :             : 
    1105                 :           4 :         scan = table_beginscan_catalog(classRel, 1, key);
    1106                 :             : 
    1107         [ +  + ]:          21 :         while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
    1108                 :             :         {
    1109                 :          17 :             Form_pg_class relForm = (Form_pg_class) GETSTRUCT(tuple);
    1110                 :          17 :             Oid         relid = relForm->oid;
    1111                 :             : 
    1112         [ +  - ]:          17 :             if (is_publishable_class(relid, relForm) &&
    1113         [ +  + ]:          17 :                 !relForm->relispartition &&
    1114         [ +  + ]:          13 :                 !list_member_oid(exceptlist, relid))
    1115                 :          12 :                 result = lappend_oid(result, relid);
    1116                 :             :         }
    1117                 :             : 
    1118                 :           4 :         table_endscan(scan);
    1119                 :             :     }
    1120                 :             : 
    1121                 :          55 :     table_close(classRel, AccessShareLock);
    1122                 :          55 :     return result;
    1123                 :             : }
    1124                 :             : 
    1125                 :             : /*
    1126                 :             :  * Gets the list of schema oids for a publication.
    1127                 :             :  *
    1128                 :             :  * This should only be used FOR TABLES IN SCHEMA publications.
    1129                 :             :  */
    1130                 :             : List *
    1131                 :         576 : GetPublicationSchemas(Oid pubid)
    1132                 :             : {
    1133                 :         576 :     List       *result = NIL;
    1134                 :             :     Relation    pubschsrel;
    1135                 :             :     ScanKeyData scankey;
    1136                 :             :     SysScanDesc scan;
    1137                 :             :     HeapTuple   tup;
    1138                 :             : 
    1139                 :             :     /* Find all schemas associated with the publication */
    1140                 :         576 :     pubschsrel = table_open(PublicationNamespaceRelationId, AccessShareLock);
    1141                 :             : 
    1142                 :         576 :     ScanKeyInit(&scankey,
    1143                 :             :                 Anum_pg_publication_namespace_pnpubid,
    1144                 :             :                 BTEqualStrategyNumber, F_OIDEQ,
    1145                 :             :                 ObjectIdGetDatum(pubid));
    1146                 :             : 
    1147                 :         576 :     scan = systable_beginscan(pubschsrel,
    1148                 :             :                               PublicationNamespacePnnspidPnpubidIndexId,
    1149                 :             :                               true, NULL, 1, &scankey);
    1150         [ +  + ]:         617 :     while (HeapTupleIsValid(tup = systable_getnext(scan)))
    1151                 :             :     {
    1152                 :             :         Form_pg_publication_namespace pubsch;
    1153                 :             : 
    1154                 :          41 :         pubsch = (Form_pg_publication_namespace) GETSTRUCT(tup);
    1155                 :             : 
    1156                 :          41 :         result = lappend_oid(result, pubsch->pnnspid);
    1157                 :             :     }
    1158                 :             : 
    1159                 :         576 :     systable_endscan(scan);
    1160                 :         576 :     table_close(pubschsrel, AccessShareLock);
    1161                 :             : 
    1162                 :         576 :     return result;
    1163                 :             : }
    1164                 :             : 
    1165                 :             : /*
    1166                 :             :  * Gets the list of publication oids associated with a specified schema.
    1167                 :             :  */
    1168                 :             : List *
    1169                 :        8812 : GetSchemaPublications(Oid schemaid)
    1170                 :             : {
    1171                 :        8812 :     List       *result = NIL;
    1172                 :             :     CatCList   *pubschlist;
    1173                 :             :     int         i;
    1174                 :             : 
    1175                 :             :     /* Find all publications associated with the schema */
    1176                 :        8812 :     pubschlist = SearchSysCacheList1(PUBLICATIONNAMESPACEMAP,
    1177                 :             :                                      ObjectIdGetDatum(schemaid));
    1178         [ +  + ]:        8891 :     for (i = 0; i < pubschlist->n_members; i++)
    1179                 :             :     {
    1180                 :          79 :         HeapTuple   tup = &pubschlist->members[i]->tuple;
    1181                 :          79 :         Oid         pubid = ((Form_pg_publication_namespace) GETSTRUCT(tup))->pnpubid;
    1182                 :             : 
    1183                 :          79 :         result = lappend_oid(result, pubid);
    1184                 :             :     }
    1185                 :             : 
    1186                 :        8812 :     ReleaseSysCacheList(pubschlist);
    1187                 :             : 
    1188                 :        8812 :     return result;
    1189                 :             : }
    1190                 :             : 
    1191                 :             : /*
    1192                 :             :  * Get the list of publishable relation oids for a specified schema.
    1193                 :             :  */
    1194                 :             : List *
    1195                 :         313 : GetSchemaPublicationRelations(Oid schemaid, PublicationPartOpt pub_partopt)
    1196                 :             : {
    1197                 :             :     Relation    classRel;
    1198                 :             :     ScanKeyData key[1];
    1199                 :             :     TableScanDesc scan;
    1200                 :             :     HeapTuple   tuple;
    1201                 :         313 :     List       *result = NIL;
    1202                 :             : 
    1203                 :             :     Assert(OidIsValid(schemaid));
    1204                 :             : 
    1205                 :         313 :     classRel = table_open(RelationRelationId, AccessShareLock);
    1206                 :             : 
    1207                 :         313 :     ScanKeyInit(&key[0],
    1208                 :             :                 Anum_pg_class_relnamespace,
    1209                 :             :                 BTEqualStrategyNumber, F_OIDEQ,
    1210                 :             :                 ObjectIdGetDatum(schemaid));
    1211                 :             : 
    1212                 :             :     /* get all the relations present in the specified schema */
    1213                 :         313 :     scan = table_beginscan_catalog(classRel, 1, key);
    1214         [ +  + ]:       17293 :     while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
    1215                 :             :     {
    1216                 :       16980 :         Form_pg_class relForm = (Form_pg_class) GETSTRUCT(tuple);
    1217                 :       16980 :         Oid         relid = relForm->oid;
    1218                 :             :         char        relkind;
    1219                 :             : 
    1220         [ +  + ]:       16980 :         if (!is_publishable_class(relid, relForm))
    1221                 :        5871 :             continue;
    1222                 :             : 
    1223                 :       11109 :         relkind = get_rel_relkind(relid);
    1224         [ +  + ]:       11109 :         if (relkind == RELKIND_RELATION)
    1225                 :        9556 :             result = lappend_oid(result, relid);
    1226         [ +  + ]:        1553 :         else if (relkind == RELKIND_PARTITIONED_TABLE)
    1227                 :             :         {
    1228                 :         495 :             List       *partitionrels = NIL;
    1229                 :             : 
    1230                 :             :             /*
    1231                 :             :              * It is quite possible that some of the partitions are in a
    1232                 :             :              * different schema than the parent table, so we need to get such
    1233                 :             :              * partitions separately.
    1234                 :             :              */
    1235                 :         495 :             partitionrels = GetPubPartitionOptionRelations(partitionrels,
    1236                 :             :                                                            pub_partopt,
    1237                 :             :                                                            relForm->oid);
    1238                 :         495 :             result = list_concat_unique_oid(result, partitionrels);
    1239                 :             :         }
    1240                 :             :     }
    1241                 :             : 
    1242                 :         313 :     table_endscan(scan);
    1243                 :         313 :     table_close(classRel, AccessShareLock);
    1244                 :         313 :     return result;
    1245                 :             : }
    1246                 :             : 
    1247                 :             : /*
    1248                 :             :  * Gets the list of all relations published by FOR TABLES IN SCHEMA
    1249                 :             :  * publication.
    1250                 :             :  */
    1251                 :             : List *
    1252                 :         283 : GetAllSchemaPublicationRelations(Oid pubid, PublicationPartOpt pub_partopt)
    1253                 :             : {
    1254                 :         283 :     List       *result = NIL;
    1255                 :         283 :     List       *pubschemalist = GetPublicationSchemas(pubid);
    1256                 :             :     ListCell   *cell;
    1257                 :             : 
    1258   [ +  +  +  +  :         304 :     foreach(cell, pubschemalist)
                   +  + ]
    1259                 :             :     {
    1260                 :          21 :         Oid         schemaid = lfirst_oid(cell);
    1261                 :          21 :         List       *schemaRels = NIL;
    1262                 :             : 
    1263                 :          21 :         schemaRels = GetSchemaPublicationRelations(schemaid, pub_partopt);
    1264                 :          21 :         result = list_concat(result, schemaRels);
    1265                 :             :     }
    1266                 :             : 
    1267                 :         283 :     return result;
    1268                 :             : }
    1269                 :             : 
    1270                 :             : /*
    1271                 :             :  * Get publication using oid
    1272                 :             :  *
    1273                 :             :  * The Publication struct and its data are palloc'ed here.
    1274                 :             :  */
    1275                 :             : Publication *
    1276                 :        4206 : GetPublication(Oid pubid)
    1277                 :             : {
    1278                 :             :     HeapTuple   tup;
    1279                 :             :     Publication *pub;
    1280                 :             :     Form_pg_publication pubform;
    1281                 :             : 
    1282                 :        4206 :     tup = SearchSysCache1(PUBLICATIONOID, ObjectIdGetDatum(pubid));
    1283         [ -  + ]:        4206 :     if (!HeapTupleIsValid(tup))
    1284         [ #  # ]:           0 :         elog(ERROR, "cache lookup failed for publication %u", pubid);
    1285                 :             : 
    1286                 :        4206 :     pubform = (Form_pg_publication) GETSTRUCT(tup);
    1287                 :             : 
    1288                 :        4206 :     pub = palloc_object(Publication);
    1289                 :        4206 :     pub->oid = pubid;
    1290                 :        4206 :     pub->name = pstrdup(NameStr(pubform->pubname));
    1291                 :        4206 :     pub->alltables = pubform->puballtables;
    1292                 :        4206 :     pub->allsequences = pubform->puballsequences;
    1293                 :        4206 :     pub->pubactions.pubinsert = pubform->pubinsert;
    1294                 :        4206 :     pub->pubactions.pubupdate = pubform->pubupdate;
    1295                 :        4206 :     pub->pubactions.pubdelete = pubform->pubdelete;
    1296                 :        4206 :     pub->pubactions.pubtruncate = pubform->pubtruncate;
    1297                 :        4206 :     pub->pubviaroot = pubform->pubviaroot;
    1298                 :        4206 :     pub->pubgencols_type = pubform->pubgencols;
    1299                 :             : 
    1300                 :        4206 :     ReleaseSysCache(tup);
    1301                 :             : 
    1302                 :        4206 :     return pub;
    1303                 :             : }
    1304                 :             : 
    1305                 :             : /*
    1306                 :             :  * Get Publication using name.
    1307                 :             :  */
    1308                 :             : Publication *
    1309                 :        1791 : GetPublicationByName(const char *pubname, bool missing_ok)
    1310                 :             : {
    1311                 :             :     Oid         oid;
    1312                 :             : 
    1313                 :        1791 :     oid = get_publication_oid(pubname, missing_ok);
    1314                 :             : 
    1315         [ +  + ]:        1791 :     return OidIsValid(oid) ? GetPublication(oid) : NULL;
    1316                 :             : }
    1317                 :             : 
    1318                 :             : /*
    1319                 :             :  * A helper function for pg_get_publication_tables() to check whether the
    1320                 :             :  * table with the given relid is published in the specified publication.
    1321                 :             :  *
    1322                 :             :  * This function evaluates the effective published OID based on the
    1323                 :             :  * publish_via_partition_root setting, rather than just checking catalog entries
    1324                 :             :  * (e.g., pg_publication_rel). For instance, when publish_via_partition_root is
    1325                 :             :  * false, it returns false for a parent partitioned table and returns true
    1326                 :             :  * for its leaf partitions, even if the parent is the one explicitly added
    1327                 :             :  * to the publication.
    1328                 :             :  *
    1329                 :             :  * For performance reasons, this function avoids the overhead of constructing
    1330                 :             :  * the complete list of published tables during the evaluation. It can execute
    1331                 :             :  * quickly even when the publication contains a large number of relations.
    1332                 :             :  *
    1333                 :             :  * Note: this leaks memory for the ancestors list into the current memory
    1334                 :             :  * context.
    1335                 :             :  */
    1336                 :             : static bool
    1337                 :         990 : is_table_publishable_in_publication(Oid relid, Publication *pub)
    1338                 :             : {
    1339                 :             :     bool        relispartition;
    1340                 :         990 :     List       *ancestors = NIL;
    1341                 :             : 
    1342                 :             :     /*
    1343                 :             :      * For non-pubviaroot publications, a partitioned table is never the
    1344                 :             :      * effective published OID; only its leaf partitions can be.
    1345                 :             :      */
    1346   [ +  +  +  + ]:         990 :     if (!pub->pubviaroot && get_rel_relkind(relid) == RELKIND_PARTITIONED_TABLE)
    1347                 :          86 :         return false;
    1348                 :             : 
    1349                 :         904 :     relispartition = get_rel_relispartition(relid);
    1350                 :             : 
    1351         [ +  + ]:         904 :     if (relispartition)
    1352                 :         172 :         ancestors = get_partition_ancestors(relid);
    1353                 :             : 
    1354         [ +  + ]:         904 :     if (pub->alltables)
    1355                 :             :     {
    1356                 :             :         /*
    1357                 :             :          * ALL TABLES with pubviaroot includes only regular tables or top-most
    1358                 :             :          * partitioned tables -- never child partitions.
    1359                 :             :          */
    1360   [ +  +  +  + ]:         202 :         if (pub->pubviaroot && relispartition)
    1361                 :          12 :             return false;
    1362                 :             : 
    1363                 :             :         /*
    1364                 :             :          * For ALL TABLES publications, the table is published unless it
    1365                 :             :          * appears in the EXCEPT clause. Only the top-most can appear in the
    1366                 :             :          * EXCEPT clause, so exclusion must be evaluated at the top-most
    1367                 :             :          * ancestor if it has. These publications store only EXCEPT'ed tables
    1368                 :             :          * in pg_publication_rel, so checking existence is sufficient.
    1369                 :             :          *
    1370                 :             :          * Note that this existence check below would incorrectly return true
    1371                 :             :          * (published) for partitions when pubviaroot is enabled; however,
    1372                 :             :          * that case is already caught and returned false by the above check.
    1373                 :             :          */
    1374         [ +  + ]:         190 :         return !SearchSysCacheExists2(PUBLICATIONRELMAP,
    1375                 :             :                                       ObjectIdGetDatum(ancestors
    1376                 :             :                                                        ? llast_oid(ancestors) : relid),
    1377                 :             :                                       ObjectIdGetDatum(pub->oid));
    1378                 :             :     }
    1379                 :             : 
    1380                 :             :     /*
    1381                 :             :      * Non-ALL-TABLE publication cases.
    1382                 :             :      *
    1383                 :             :      * A table is published if it (or a containing schema) was explicitly
    1384                 :             :      * added, or if it is a partition whose ancestor was added.
    1385                 :             :      */
    1386                 :             : 
    1387                 :             :     /*
    1388                 :             :      * If an ancestor is published, the partition's status depends on
    1389                 :             :      * publish_via_partition_root value.
    1390                 :             :      *
    1391                 :             :      * If it's true, the ancestor's relation OID is the effective published
    1392                 :             :      * OID, so the partition itself should be excluded (return false).
    1393                 :             :      *
    1394                 :             :      * If it's false, the partition is covered by its ancestor's presence in
    1395                 :             :      * the publication, it should be included (return true).
    1396                 :             :      */
    1397   [ +  +  +  + ]:         844 :     if (relispartition &&
    1398                 :         142 :         OidIsValid(GetTopMostAncestorInPublication(pub->oid, ancestors, NULL)))
    1399                 :          44 :         return !pub->pubviaroot;
    1400                 :             : 
    1401                 :             :     /*
    1402                 :             :      * Check whether the table is explicitly published via pg_publication_rel
    1403                 :             :      * or pg_publication_namespace.
    1404                 :             :      */
    1405                 :         658 :     return (SearchSysCacheExists2(PUBLICATIONRELMAP,
    1406                 :             :                                   ObjectIdGetDatum(relid),
    1407   [ +  +  +  + ]:        1028 :                                   ObjectIdGetDatum(pub->oid)) ||
    1408                 :         370 :             SearchSysCacheExists2(PUBLICATIONNAMESPACEMAP,
    1409                 :             :                                   ObjectIdGetDatum(get_rel_namespace(relid)),
    1410                 :             :                                   ObjectIdGetDatum(pub->oid)));
    1411                 :             : }
    1412                 :             : 
    1413                 :             : /*
    1414                 :             :  * Helper function to get information of the tables in the given
    1415                 :             :  * publication(s).
    1416                 :             :  *
    1417                 :             :  * If filter_by_relid is true, only the row(s) for target_relid is returned;
    1418                 :             :  * if target_relid does not exist or is not part of the publications, zero
    1419                 :             :  * rows are returned.  If filter_by_relid is false, rows for all tables
    1420                 :             :  * within the specified publications are returned and target_relid is
    1421                 :             :  * ignored.
    1422                 :             :  *
    1423                 :             :  * Returns pubid, relid, column list, and row filter for each table.
    1424                 :             :  */
    1425                 :             : static Datum
    1426                 :        1607 : pg_get_publication_tables(FunctionCallInfo fcinfo, ArrayType *pubnames,
    1427                 :             :                           Oid target_relid, bool filter_by_relid,
    1428                 :             :                           bool pub_missing_ok)
    1429                 :             : {
    1430                 :             : #define NUM_PUBLICATION_TABLES_ELEM 4
    1431                 :             : 
    1432                 :             :     /*
    1433                 :             :      * State carried across SRF calls. We track the index ourselves instead of
    1434                 :             :      * using funcctx->call_cntr, so that concurrently dropped tables can be
    1435                 :             :      * skipped without emitting a row.
    1436                 :             :      */
    1437                 :             :     typedef struct
    1438                 :             :     {
    1439                 :             :         List       *table_infos;    /* list of published_rel */
    1440                 :             :         int         curr_idx;   /* current index into table_infos */
    1441                 :             :     } publication_tables_state;
    1442                 :             : 
    1443                 :             :     FuncCallContext *funcctx;
    1444                 :        1607 :     publication_tables_state *ptstate = NULL;
    1445                 :             : 
    1446                 :             :     /* stuff done only on the first call of the function */
    1447         [ +  + ]:        1607 :     if (SRF_IS_FIRSTCALL())
    1448                 :             :     {
    1449                 :             :         TupleDesc   tupdesc;
    1450                 :             :         MemoryContext oldcontext;
    1451                 :         758 :         List       *table_infos = NIL;
    1452                 :             :         Datum      *elems;
    1453                 :             :         int         nelems,
    1454                 :             :                     i;
    1455                 :         758 :         bool        viaroot = false;
    1456                 :             : 
    1457                 :             :         /* create a function context for cross-call persistence */
    1458                 :         758 :         funcctx = SRF_FIRSTCALL_INIT();
    1459                 :             : 
    1460                 :             :         /*
    1461                 :             :          * Preliminary check if the specified table can be published in the
    1462                 :             :          * first place. If not, we can return early without checking the given
    1463                 :             :          * publications and the table.
    1464                 :             :          */
    1465   [ +  +  +  + ]:         758 :         if (filter_by_relid && !is_publishable_table(target_relid))
    1466                 :           8 :             SRF_RETURN_DONE(funcctx);
    1467                 :             : 
    1468                 :             :         /* switch to memory context appropriate for multiple function calls */
    1469                 :         750 :         oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
    1470                 :             : 
    1471                 :             :         /*
    1472                 :             :          * Deconstruct the parameter into elements where each element is a
    1473                 :             :          * publication name.
    1474                 :             :          */
    1475                 :         750 :         deconstruct_array_builtin(pubnames, TEXTOID, &elems, NULL, &nelems);
    1476                 :             : 
    1477                 :             :         /* Get Oids of tables from each publication. */
    1478         [ +  + ]:        1998 :         for (i = 0; i < nelems; i++)
    1479                 :             :         {
    1480                 :             :             Publication *pub_elem;
    1481                 :        1248 :             List       *pub_elem_tables = NIL;
    1482                 :             :             ListCell   *lc;
    1483                 :             : 
    1484                 :        1248 :             pub_elem = GetPublicationByName(TextDatumGetCString(elems[i]),
    1485                 :             :                                             pub_missing_ok);
    1486                 :             : 
    1487         [ +  + ]:        1248 :             if (pub_elem == NULL)
    1488                 :           6 :                 continue;
    1489                 :             : 
    1490         [ +  + ]:        1242 :             if (filter_by_relid)
    1491                 :             :             {
    1492                 :             :                 /* Check if the given table is published for the publication */
    1493         [ +  + ]:         990 :                 if (is_table_publishable_in_publication(target_relid, pub_elem))
    1494                 :             :                 {
    1495                 :         504 :                     pub_elem_tables = list_make1_oid(target_relid);
    1496                 :             :                 }
    1497                 :             :             }
    1498                 :             :             else
    1499                 :             :             {
    1500                 :             :                 /*
    1501                 :             :                  * Publications support partitioned tables. If
    1502                 :             :                  * publish_via_partition_root is false, all changes are
    1503                 :             :                  * replicated using leaf partition identity and schema, so we
    1504                 :             :                  * only need those. Otherwise, get the partitioned table
    1505                 :             :                  * itself.
    1506                 :             :                  */
    1507         [ +  + ]:         252 :                 if (pub_elem->alltables)
    1508                 :          49 :                     pub_elem_tables = GetAllPublicationRelations(pub_elem->oid,
    1509                 :             :                                                                  RELKIND_RELATION,
    1510                 :          49 :                                                                  pub_elem->pubviaroot);
    1511                 :             :                 else
    1512                 :             :                 {
    1513                 :             :                     List       *relids,
    1514                 :             :                                *schemarelids;
    1515                 :             : 
    1516                 :         203 :                     relids = GetIncludedPublicationRelations(pub_elem->oid,
    1517                 :         203 :                                                              pub_elem->pubviaroot ?
    1518                 :         203 :                                                              PUBLICATION_PART_ROOT :
    1519                 :             :                                                              PUBLICATION_PART_LEAF);
    1520                 :         203 :                     schemarelids = GetAllSchemaPublicationRelations(pub_elem->oid,
    1521                 :         203 :                                                                     pub_elem->pubviaroot ?
    1522                 :         203 :                                                                     PUBLICATION_PART_ROOT :
    1523                 :             :                                                                     PUBLICATION_PART_LEAF);
    1524                 :         203 :                     pub_elem_tables = list_concat_unique_oid(relids, schemarelids);
    1525                 :             :                 }
    1526                 :             :             }
    1527                 :             : 
    1528                 :             :             /*
    1529                 :             :              * Record the published table and the corresponding publication so
    1530                 :             :              * that we can get row filters and column lists later.
    1531                 :             :              *
    1532                 :             :              * When a table is published by multiple publications, to obtain
    1533                 :             :              * all row filters and column lists, the structure related to this
    1534                 :             :              * table will be recorded multiple times.
    1535                 :             :              */
    1536   [ +  +  +  +  :        2122 :             foreach(lc, pub_elem_tables)
                   +  + ]
    1537                 :             :             {
    1538                 :         880 :                 published_rel *table_info = palloc_object(published_rel);
    1539                 :             : 
    1540                 :         880 :                 table_info->relid = lfirst_oid(lc);
    1541                 :         880 :                 table_info->pubid = pub_elem->oid;
    1542                 :         880 :                 table_infos = lappend(table_infos, table_info);
    1543                 :             :             }
    1544                 :             : 
    1545                 :             :             /* At least one publication is using publish_via_partition_root. */
    1546         [ +  + ]:        1242 :             if (pub_elem->pubviaroot)
    1547                 :         249 :                 viaroot = true;
    1548                 :             :         }
    1549                 :             : 
    1550                 :             :         /*
    1551                 :             :          * If the publication publishes partition changes via their respective
    1552                 :             :          * root partitioned tables, we must exclude partitions in favor of
    1553                 :             :          * including the root partitioned tables. Otherwise, the function
    1554                 :             :          * could return both the child and parent tables which could cause
    1555                 :             :          * data of the child table to be double-published on the subscriber
    1556                 :             :          * side.
    1557                 :             :          */
    1558         [ +  + ]:         750 :         if (viaroot)
    1559                 :         169 :             filter_partitions(table_infos);
    1560                 :             : 
    1561                 :             :         /* Construct a tuple descriptor for the result rows. */
    1562                 :         750 :         tupdesc = CreateTemplateTupleDesc(NUM_PUBLICATION_TABLES_ELEM);
    1563                 :         750 :         TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pubid",
    1564                 :             :                            OIDOID, -1, 0);
    1565                 :         750 :         TupleDescInitEntry(tupdesc, (AttrNumber) 2, "relid",
    1566                 :             :                            OIDOID, -1, 0);
    1567                 :         750 :         TupleDescInitEntry(tupdesc, (AttrNumber) 3, "attrs",
    1568                 :             :                            INT2VECTOROID, -1, 0);
    1569                 :         750 :         TupleDescInitEntry(tupdesc, (AttrNumber) 4, "qual",
    1570                 :             :                            PG_NODE_TREEOID, -1, 0);
    1571                 :             : 
    1572                 :         750 :         TupleDescFinalize(tupdesc);
    1573                 :         750 :         funcctx->tuple_desc = BlessTupleDesc(tupdesc);
    1574                 :             : 
    1575                 :             :         /* Store the state to be used across SRF calls. */
    1576                 :         750 :         ptstate = palloc_object(publication_tables_state);
    1577                 :         750 :         ptstate->table_infos = table_infos;
    1578                 :         750 :         ptstate->curr_idx = 0;
    1579                 :         750 :         funcctx->user_fctx = ptstate;
    1580                 :             : 
    1581                 :         750 :         MemoryContextSwitchTo(oldcontext);
    1582                 :             :     }
    1583                 :             : 
    1584                 :             :     /* stuff done on every call of the function */
    1585                 :        1599 :     funcctx = SRF_PERCALL_SETUP();
    1586                 :        1599 :     ptstate = (publication_tables_state *) funcctx->user_fctx;
    1587                 :             : 
    1588         [ +  + ]:        1600 :     while (ptstate->curr_idx < list_length(ptstate->table_infos))
    1589                 :             :     {
    1590                 :         850 :         HeapTuple   pubtuple = NULL;
    1591                 :             :         HeapTuple   rettuple;
    1592                 :             :         Publication *pub;
    1593                 :         850 :         published_rel *table_info = (published_rel *) list_nth(ptstate->table_infos,
    1594                 :             :                                                                ptstate->curr_idx);
    1595                 :         850 :         Oid         relid = table_info->relid;
    1596                 :             :         Relation    rel;
    1597                 :             :         Oid         schemaid;
    1598                 :         850 :         Datum       values[NUM_PUBLICATION_TABLES_ELEM] = {0};
    1599                 :         850 :         bool        nulls[NUM_PUBLICATION_TABLES_ELEM] = {0};
    1600                 :             : 
    1601                 :             :         /* Advance the index for the next call. */
    1602                 :         850 :         ptstate->curr_idx++;
    1603                 :             : 
    1604                 :             :         /*
    1605                 :             :          * The table OIDs were collected earlier, so a table may have been
    1606                 :             :          * dropped before we get here. try_table_open() returns NULL if it is
    1607                 :             :          * already gone, in which case we skip it; such tables are simply
    1608                 :             :          * absent from the result set, which is the expected point-in-time
    1609                 :             :          * behavior.
    1610                 :             :          */
    1611                 :         850 :         rel = try_table_open(relid, AccessShareLock);
    1612         [ +  + ]:         850 :         if (rel == NULL)
    1613                 :           1 :             continue;
    1614                 :             : 
    1615                 :             :         /*
    1616                 :             :          * Form tuple with appropriate data.
    1617                 :             :          */
    1618                 :             : 
    1619                 :         849 :         pub = GetPublication(table_info->pubid);
    1620                 :             : 
    1621                 :         849 :         values[0] = ObjectIdGetDatum(pub->oid);
    1622                 :         849 :         values[1] = ObjectIdGetDatum(relid);
    1623                 :             : 
    1624                 :             :         /*
    1625                 :             :          * We don't consider row filters or column lists for FOR ALL TABLES or
    1626                 :             :          * FOR TABLES IN SCHEMA publications.
    1627                 :             :          */
    1628                 :         849 :         schemaid = RelationGetNamespace(rel);
    1629         [ +  + ]:         849 :         if (!pub->alltables &&
    1630         [ +  + ]:         578 :             !SearchSysCacheExists2(PUBLICATIONNAMESPACEMAP,
    1631                 :             :                                    ObjectIdGetDatum(schemaid),
    1632                 :             :                                    ObjectIdGetDatum(pub->oid)))
    1633                 :         541 :             pubtuple = SearchSysCacheCopy2(PUBLICATIONRELMAP,
    1634                 :             :                                            ObjectIdGetDatum(relid),
    1635                 :             :                                            ObjectIdGetDatum(pub->oid));
    1636                 :             : 
    1637         [ +  + ]:         849 :         if (HeapTupleIsValid(pubtuple))
    1638                 :             :         {
    1639                 :             :             /* Lookup the column list attribute. */
    1640                 :         502 :             values[2] = SysCacheGetAttr(PUBLICATIONRELMAP, pubtuple,
    1641                 :             :                                         Anum_pg_publication_rel_prattrs,
    1642                 :             :                                         &(nulls[2]));
    1643                 :             : 
    1644                 :             :             /* Null indicates no filter. */
    1645                 :         502 :             values[3] = SysCacheGetAttr(PUBLICATIONRELMAP, pubtuple,
    1646                 :             :                                         Anum_pg_publication_rel_prqual,
    1647                 :             :                                         &(nulls[3]));
    1648                 :             :         }
    1649                 :             :         else
    1650                 :             :         {
    1651                 :         347 :             nulls[2] = true;
    1652                 :         347 :             nulls[3] = true;
    1653                 :             :         }
    1654                 :             : 
    1655                 :             :         /* Show all columns when the column list is not specified. */
    1656         [ +  + ]:         849 :         if (nulls[2])
    1657                 :             :         {
    1658                 :         753 :             int         nattnums = 0;
    1659                 :             :             int16      *attnums;
    1660                 :         753 :             TupleDesc   desc = RelationGetDescr(rel);
    1661                 :             :             int         i;
    1662                 :             : 
    1663                 :         753 :             attnums = palloc_array(int16, desc->natts);
    1664                 :             : 
    1665         [ +  + ]:        1988 :             for (i = 0; i < desc->natts; i++)
    1666                 :             :             {
    1667                 :        1235 :                 Form_pg_attribute att = TupleDescAttr(desc, i);
    1668                 :             : 
    1669         [ +  + ]:        1235 :                 if (att->attisdropped)
    1670                 :           4 :                     continue;
    1671                 :             : 
    1672         [ +  + ]:        1231 :                 if (att->attgenerated)
    1673                 :             :                 {
    1674                 :             :                     /* We only support replication of STORED generated cols. */
    1675         [ +  + ]:          22 :                     if (att->attgenerated != ATTRIBUTE_GENERATED_STORED)
    1676                 :          10 :                         continue;
    1677                 :             : 
    1678                 :             :                     /*
    1679                 :             :                      * User hasn't requested to replicate STORED generated
    1680                 :             :                      * cols.
    1681                 :             :                      */
    1682         [ +  + ]:          12 :                     if (pub->pubgencols_type != PUBLISH_GENCOLS_STORED)
    1683                 :           9 :                         continue;
    1684                 :             :                 }
    1685                 :             : 
    1686                 :        1212 :                 attnums[nattnums++] = att->attnum;
    1687                 :             :             }
    1688                 :             : 
    1689         [ +  + ]:         753 :             if (nattnums > 0)
    1690                 :             :             {
    1691                 :         749 :                 values[2] = PointerGetDatum(buildint2vector(attnums, nattnums));
    1692                 :         749 :                 nulls[2] = false;
    1693                 :             :             }
    1694                 :             :         }
    1695                 :             : 
    1696                 :         849 :         table_close(rel, AccessShareLock);
    1697                 :             : 
    1698                 :         849 :         rettuple = heap_form_tuple(funcctx->tuple_desc, values, nulls);
    1699                 :             : 
    1700                 :         849 :         SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(rettuple));
    1701                 :             :     }
    1702                 :             : 
    1703                 :         750 :     SRF_RETURN_DONE(funcctx);
    1704                 :             : }
    1705                 :             : 
    1706                 :             : Datum
    1707                 :         549 : pg_get_publication_tables_a(PG_FUNCTION_ARGS)
    1708                 :             : {
    1709                 :             :     /*
    1710                 :             :      * Get information for all tables in the given publications.
    1711                 :             :      * filter_by_relid is false so all tables are returned; pub_missing_ok is
    1712                 :             :      * false for backward compatibility.
    1713                 :             :      */
    1714                 :         549 :     return pg_get_publication_tables(fcinfo, PG_GETARG_ARRAYTYPE_P(0),
    1715                 :             :                                      InvalidOid, false, false);
    1716                 :             : }
    1717                 :             : 
    1718                 :             : Datum
    1719                 :        1058 : pg_get_publication_tables_b(PG_FUNCTION_ARGS)
    1720                 :             : {
    1721                 :             :     /*
    1722                 :             :      * Get information for the specified table in the given publications. The
    1723                 :             :      * SQL-level function is declared STRICT, so target_relid is guaranteed to
    1724                 :             :      * be non-NULL here.
    1725                 :             :      */
    1726                 :        1058 :     return pg_get_publication_tables(fcinfo, PG_GETARG_ARRAYTYPE_P(0),
    1727                 :             :                                      PG_GETARG_OID(1), true, true);
    1728                 :             : }
    1729                 :             : 
    1730                 :             : /*
    1731                 :             :  * Returns Oids of sequences in a publication.
    1732                 :             :  */
    1733                 :             : Datum
    1734                 :         234 : pg_get_publication_sequences(PG_FUNCTION_ARGS)
    1735                 :             : {
    1736                 :             :     FuncCallContext *funcctx;
    1737                 :         234 :     List       *sequences = NIL;
    1738                 :             : 
    1739                 :             :     /* stuff done only on the first call of the function */
    1740         [ +  + ]:         234 :     if (SRF_IS_FIRSTCALL())
    1741                 :             :     {
    1742                 :         217 :         char       *pubname = text_to_cstring(PG_GETARG_TEXT_PP(0));
    1743                 :             :         Publication *publication;
    1744                 :             :         MemoryContext oldcontext;
    1745                 :             : 
    1746                 :             :         /* create a function context for cross-call persistence */
    1747                 :         217 :         funcctx = SRF_FIRSTCALL_INIT();
    1748                 :             : 
    1749                 :             :         /* switch to memory context appropriate for multiple function calls */
    1750                 :         217 :         oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
    1751                 :             : 
    1752                 :         217 :         publication = GetPublicationByName(pubname, false);
    1753                 :             : 
    1754         [ +  + ]:         217 :         if (publication->allsequences)
    1755                 :           6 :             sequences = GetAllPublicationRelations(publication->oid,
    1756                 :             :                                                    RELKIND_SEQUENCE,
    1757                 :             :                                                    false);
    1758                 :             : 
    1759                 :         217 :         funcctx->user_fctx = sequences;
    1760                 :             : 
    1761                 :         217 :         MemoryContextSwitchTo(oldcontext);
    1762                 :             :     }
    1763                 :             : 
    1764                 :             :     /* stuff done on every call of the function */
    1765                 :         234 :     funcctx = SRF_PERCALL_SETUP();
    1766                 :         234 :     sequences = (List *) funcctx->user_fctx;
    1767                 :             : 
    1768         [ +  + ]:         234 :     if (funcctx->call_cntr < list_length(sequences))
    1769                 :             :     {
    1770                 :          17 :         Oid         relid = list_nth_oid(sequences, funcctx->call_cntr);
    1771                 :             : 
    1772                 :          17 :         SRF_RETURN_NEXT(funcctx, ObjectIdGetDatum(relid));
    1773                 :             :     }
    1774                 :             : 
    1775                 :         217 :     SRF_RETURN_DONE(funcctx);
    1776                 :             : }
        

Generated by: LCOV version 2.0-1