LCOV - code coverage report
Current view: top level - src/backend/catalog - pg_subscription.c (source / functions) Hit Total Coverage
Test: PostgreSQL 18devel Lines: 183 192 95.3 %
Date: 2024-11-21 10:14:43 Functions: 12 12 100.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /*-------------------------------------------------------------------------
       2             :  *
       3             :  * pg_subscription.c
       4             :  *      replication subscriptions
       5             :  *
       6             :  * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
       7             :  * Portions Copyright (c) 1994, Regents of the University of California
       8             :  *
       9             :  * IDENTIFICATION
      10             :  *      src/backend/catalog/pg_subscription.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/indexing.h"
      22             : #include "catalog/pg_subscription.h"
      23             : #include "catalog/pg_subscription_rel.h"
      24             : #include "catalog/pg_type.h"
      25             : #include "miscadmin.h"
      26             : #include "storage/lmgr.h"
      27             : #include "utils/array.h"
      28             : #include "utils/builtins.h"
      29             : #include "utils/fmgroids.h"
      30             : #include "utils/lsyscache.h"
      31             : #include "utils/pg_lsn.h"
      32             : #include "utils/rel.h"
      33             : #include "utils/syscache.h"
      34             : 
      35             : static List *textarray_to_stringlist(ArrayType *textarray);
      36             : 
      37             : /*
      38             :  * Add a comma-separated list of publication names to the 'dest' string.
      39             :  */
      40             : void
      41         852 : GetPublicationsStr(List *publications, StringInfo dest, bool quote_literal)
      42             : {
      43             :     ListCell   *lc;
      44         852 :     bool        first = true;
      45             : 
      46             :     Assert(publications != NIL);
      47             : 
      48        2232 :     foreach(lc, publications)
      49             :     {
      50        1380 :         char       *pubname = strVal(lfirst(lc));
      51             : 
      52        1380 :         if (first)
      53         852 :             first = false;
      54             :         else
      55         528 :             appendStringInfoString(dest, ", ");
      56             : 
      57        1380 :         if (quote_literal)
      58        1368 :             appendStringInfoString(dest, quote_literal_cstr(pubname));
      59             :         else
      60             :         {
      61          12 :             appendStringInfoChar(dest, '"');
      62          12 :             appendStringInfoString(dest, pubname);
      63          12 :             appendStringInfoChar(dest, '"');
      64             :         }
      65             :     }
      66         852 : }
      67             : 
      68             : /*
      69             :  * Fetch the subscription from the syscache.
      70             :  */
      71             : Subscription *
      72        1344 : GetSubscription(Oid subid, bool missing_ok)
      73             : {
      74             :     HeapTuple   tup;
      75             :     Subscription *sub;
      76             :     Form_pg_subscription subform;
      77             :     Datum       datum;
      78             :     bool        isnull;
      79             : 
      80        1344 :     tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
      81             : 
      82        1342 :     if (!HeapTupleIsValid(tup))
      83             :     {
      84           0 :         if (missing_ok)
      85           0 :             return NULL;
      86             : 
      87           0 :         elog(ERROR, "cache lookup failed for subscription %u", subid);
      88             :     }
      89             : 
      90        1342 :     subform = (Form_pg_subscription) GETSTRUCT(tup);
      91             : 
      92        1342 :     sub = (Subscription *) palloc(sizeof(Subscription));
      93        1342 :     sub->oid = subid;
      94        1342 :     sub->dbid = subform->subdbid;
      95        1342 :     sub->skiplsn = subform->subskiplsn;
      96        1342 :     sub->name = pstrdup(NameStr(subform->subname));
      97        1342 :     sub->owner = subform->subowner;
      98        1342 :     sub->enabled = subform->subenabled;
      99        1342 :     sub->binary = subform->subbinary;
     100        1342 :     sub->stream = subform->substream;
     101        1342 :     sub->twophasestate = subform->subtwophasestate;
     102        1342 :     sub->disableonerr = subform->subdisableonerr;
     103        1342 :     sub->passwordrequired = subform->subpasswordrequired;
     104        1342 :     sub->runasowner = subform->subrunasowner;
     105        1342 :     sub->failover = subform->subfailover;
     106             : 
     107             :     /* Get conninfo */
     108        1342 :     datum = SysCacheGetAttrNotNull(SUBSCRIPTIONOID,
     109             :                                    tup,
     110             :                                    Anum_pg_subscription_subconninfo);
     111        1342 :     sub->conninfo = TextDatumGetCString(datum);
     112             : 
     113             :     /* Get slotname */
     114        1342 :     datum = SysCacheGetAttr(SUBSCRIPTIONOID,
     115             :                             tup,
     116             :                             Anum_pg_subscription_subslotname,
     117             :                             &isnull);
     118        1342 :     if (!isnull)
     119        1276 :         sub->slotname = pstrdup(NameStr(*DatumGetName(datum)));
     120             :     else
     121          66 :         sub->slotname = NULL;
     122             : 
     123             :     /* Get synccommit */
     124        1342 :     datum = SysCacheGetAttrNotNull(SUBSCRIPTIONOID,
     125             :                                    tup,
     126             :                                    Anum_pg_subscription_subsynccommit);
     127        1342 :     sub->synccommit = TextDatumGetCString(datum);
     128             : 
     129             :     /* Get publications */
     130        1342 :     datum = SysCacheGetAttrNotNull(SUBSCRIPTIONOID,
     131             :                                    tup,
     132             :                                    Anum_pg_subscription_subpublications);
     133        1342 :     sub->publications = textarray_to_stringlist(DatumGetArrayTypeP(datum));
     134             : 
     135             :     /* Get origin */
     136        1342 :     datum = SysCacheGetAttrNotNull(SUBSCRIPTIONOID,
     137             :                                    tup,
     138             :                                    Anum_pg_subscription_suborigin);
     139        1342 :     sub->origin = TextDatumGetCString(datum);
     140             : 
     141             :     /* Is the subscription owner a superuser? */
     142        1342 :     sub->ownersuperuser = superuser_arg(sub->owner);
     143             : 
     144        1342 :     ReleaseSysCache(tup);
     145             : 
     146        1342 :     return sub;
     147             : }
     148             : 
     149             : /*
     150             :  * Return number of subscriptions defined in given database.
     151             :  * Used by dropdb() to check if database can indeed be dropped.
     152             :  */
     153             : int
     154          68 : CountDBSubscriptions(Oid dbid)
     155             : {
     156          68 :     int         nsubs = 0;
     157             :     Relation    rel;
     158             :     ScanKeyData scankey;
     159             :     SysScanDesc scan;
     160             :     HeapTuple   tup;
     161             : 
     162          68 :     rel = table_open(SubscriptionRelationId, RowExclusiveLock);
     163             : 
     164          68 :     ScanKeyInit(&scankey,
     165             :                 Anum_pg_subscription_subdbid,
     166             :                 BTEqualStrategyNumber, F_OIDEQ,
     167             :                 ObjectIdGetDatum(dbid));
     168             : 
     169          68 :     scan = systable_beginscan(rel, InvalidOid, false,
     170             :                               NULL, 1, &scankey);
     171             : 
     172          68 :     while (HeapTupleIsValid(tup = systable_getnext(scan)))
     173           0 :         nsubs++;
     174             : 
     175          68 :     systable_endscan(scan);
     176             : 
     177          68 :     table_close(rel, NoLock);
     178             : 
     179          68 :     return nsubs;
     180             : }
     181             : 
     182             : /*
     183             :  * Free memory allocated by subscription struct.
     184             :  */
     185             : void
     186          68 : FreeSubscription(Subscription *sub)
     187             : {
     188          68 :     pfree(sub->name);
     189          68 :     pfree(sub->conninfo);
     190          68 :     if (sub->slotname)
     191          68 :         pfree(sub->slotname);
     192          68 :     list_free_deep(sub->publications);
     193          68 :     pfree(sub);
     194          68 : }
     195             : 
     196             : /*
     197             :  * Disable the given subscription.
     198             :  */
     199             : void
     200           8 : DisableSubscription(Oid subid)
     201             : {
     202             :     Relation    rel;
     203             :     bool        nulls[Natts_pg_subscription];
     204             :     bool        replaces[Natts_pg_subscription];
     205             :     Datum       values[Natts_pg_subscription];
     206             :     HeapTuple   tup;
     207             : 
     208             :     /* Look up the subscription in the catalog */
     209           8 :     rel = table_open(SubscriptionRelationId, RowExclusiveLock);
     210           8 :     tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
     211             : 
     212           8 :     if (!HeapTupleIsValid(tup))
     213           0 :         elog(ERROR, "cache lookup failed for subscription %u", subid);
     214             : 
     215           8 :     LockSharedObject(SubscriptionRelationId, subid, 0, AccessShareLock);
     216             : 
     217             :     /* Form a new tuple. */
     218           8 :     memset(values, 0, sizeof(values));
     219           8 :     memset(nulls, false, sizeof(nulls));
     220           8 :     memset(replaces, false, sizeof(replaces));
     221             : 
     222             :     /* Set the subscription to disabled. */
     223           8 :     values[Anum_pg_subscription_subenabled - 1] = BoolGetDatum(false);
     224           8 :     replaces[Anum_pg_subscription_subenabled - 1] = true;
     225             : 
     226             :     /* Update the catalog */
     227           8 :     tup = heap_modify_tuple(tup, RelationGetDescr(rel), values, nulls,
     228             :                             replaces);
     229           8 :     CatalogTupleUpdate(rel, &tup->t_self, tup);
     230           8 :     heap_freetuple(tup);
     231             : 
     232           8 :     table_close(rel, NoLock);
     233           8 : }
     234             : 
     235             : /*
     236             :  * Convert text array to list of strings.
     237             :  *
     238             :  * Note: the resulting list of strings is pallocated here.
     239             :  */
     240             : static List *
     241        1342 : textarray_to_stringlist(ArrayType *textarray)
     242             : {
     243             :     Datum      *elems;
     244             :     int         nelems,
     245             :                 i;
     246        1342 :     List       *res = NIL;
     247             : 
     248        1342 :     deconstruct_array_builtin(textarray, TEXTOID, &elems, NULL, &nelems);
     249             : 
     250        1342 :     if (nelems == 0)
     251           0 :         return NIL;
     252             : 
     253        3344 :     for (i = 0; i < nelems; i++)
     254        2002 :         res = lappend(res, makeString(TextDatumGetCString(elems[i])));
     255             : 
     256        1342 :     return res;
     257             : }
     258             : 
     259             : /*
     260             :  * Add new state record for a subscription table.
     261             :  *
     262             :  * If retain_lock is true, then don't release the locks taken in this function.
     263             :  * We normally release the locks at the end of transaction but in binary-upgrade
     264             :  * mode, we expect to release those immediately.
     265             :  */
     266             : void
     267         358 : AddSubscriptionRelState(Oid subid, Oid relid, char state,
     268             :                         XLogRecPtr sublsn, bool retain_lock)
     269             : {
     270             :     Relation    rel;
     271             :     HeapTuple   tup;
     272             :     bool        nulls[Natts_pg_subscription_rel];
     273             :     Datum       values[Natts_pg_subscription_rel];
     274             : 
     275         358 :     LockSharedObject(SubscriptionRelationId, subid, 0, AccessShareLock);
     276             : 
     277         358 :     rel = table_open(SubscriptionRelRelationId, RowExclusiveLock);
     278             : 
     279             :     /* Try finding existing mapping. */
     280         358 :     tup = SearchSysCacheCopy2(SUBSCRIPTIONRELMAP,
     281             :                               ObjectIdGetDatum(relid),
     282             :                               ObjectIdGetDatum(subid));
     283         358 :     if (HeapTupleIsValid(tup))
     284           0 :         elog(ERROR, "subscription table %u in subscription %u already exists",
     285             :              relid, subid);
     286             : 
     287             :     /* Form the tuple. */
     288         358 :     memset(values, 0, sizeof(values));
     289         358 :     memset(nulls, false, sizeof(nulls));
     290         358 :     values[Anum_pg_subscription_rel_srsubid - 1] = ObjectIdGetDatum(subid);
     291         358 :     values[Anum_pg_subscription_rel_srrelid - 1] = ObjectIdGetDatum(relid);
     292         358 :     values[Anum_pg_subscription_rel_srsubstate - 1] = CharGetDatum(state);
     293         358 :     if (sublsn != InvalidXLogRecPtr)
     294           2 :         values[Anum_pg_subscription_rel_srsublsn - 1] = LSNGetDatum(sublsn);
     295             :     else
     296         356 :         nulls[Anum_pg_subscription_rel_srsublsn - 1] = true;
     297             : 
     298         358 :     tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
     299             : 
     300             :     /* Insert tuple into catalog. */
     301         358 :     CatalogTupleInsert(rel, tup);
     302             : 
     303         358 :     heap_freetuple(tup);
     304             : 
     305             :     /* Cleanup. */
     306         358 :     if (retain_lock)
     307             :     {
     308         354 :         table_close(rel, NoLock);
     309             :     }
     310             :     else
     311             :     {
     312           4 :         table_close(rel, RowExclusiveLock);
     313           4 :         UnlockSharedObject(SubscriptionRelationId, subid, 0, AccessShareLock);
     314             :     }
     315         358 : }
     316             : 
     317             : /*
     318             :  * Update the state of a subscription table.
     319             :  */
     320             : void
     321        1324 : UpdateSubscriptionRelState(Oid subid, Oid relid, char state,
     322             :                            XLogRecPtr sublsn)
     323             : {
     324             :     Relation    rel;
     325             :     HeapTuple   tup;
     326             :     bool        nulls[Natts_pg_subscription_rel];
     327             :     Datum       values[Natts_pg_subscription_rel];
     328             :     bool        replaces[Natts_pg_subscription_rel];
     329             : 
     330        1324 :     LockSharedObject(SubscriptionRelationId, subid, 0, AccessShareLock);
     331             : 
     332        1324 :     rel = table_open(SubscriptionRelRelationId, RowExclusiveLock);
     333             : 
     334             :     /* Try finding existing mapping. */
     335        1324 :     tup = SearchSysCacheCopy2(SUBSCRIPTIONRELMAP,
     336             :                               ObjectIdGetDatum(relid),
     337             :                               ObjectIdGetDatum(subid));
     338        1324 :     if (!HeapTupleIsValid(tup))
     339           0 :         elog(ERROR, "subscription table %u in subscription %u does not exist",
     340             :              relid, subid);
     341             : 
     342             :     /* Update the tuple. */
     343        1324 :     memset(values, 0, sizeof(values));
     344        1324 :     memset(nulls, false, sizeof(nulls));
     345        1324 :     memset(replaces, false, sizeof(replaces));
     346             : 
     347        1324 :     replaces[Anum_pg_subscription_rel_srsubstate - 1] = true;
     348        1324 :     values[Anum_pg_subscription_rel_srsubstate - 1] = CharGetDatum(state);
     349             : 
     350        1324 :     replaces[Anum_pg_subscription_rel_srsublsn - 1] = true;
     351        1324 :     if (sublsn != InvalidXLogRecPtr)
     352         652 :         values[Anum_pg_subscription_rel_srsublsn - 1] = LSNGetDatum(sublsn);
     353             :     else
     354         672 :         nulls[Anum_pg_subscription_rel_srsublsn - 1] = true;
     355             : 
     356        1324 :     tup = heap_modify_tuple(tup, RelationGetDescr(rel), values, nulls,
     357             :                             replaces);
     358             : 
     359             :     /* Update the catalog. */
     360        1324 :     CatalogTupleUpdate(rel, &tup->t_self, tup);
     361             : 
     362             :     /* Cleanup. */
     363        1324 :     table_close(rel, NoLock);
     364        1324 : }
     365             : 
     366             : /*
     367             :  * Get state of subscription table.
     368             :  *
     369             :  * Returns SUBREL_STATE_UNKNOWN when the table is not in the subscription.
     370             :  */
     371             : char
     372        2100 : GetSubscriptionRelState(Oid subid, Oid relid, XLogRecPtr *sublsn)
     373             : {
     374             :     HeapTuple   tup;
     375             :     char        substate;
     376             :     bool        isnull;
     377             :     Datum       d;
     378             :     Relation    rel;
     379             : 
     380             :     /*
     381             :      * This is to avoid the race condition with AlterSubscription which tries
     382             :      * to remove this relstate.
     383             :      */
     384        2100 :     rel = table_open(SubscriptionRelRelationId, AccessShareLock);
     385             : 
     386             :     /* Try finding the mapping. */
     387        2100 :     tup = SearchSysCache2(SUBSCRIPTIONRELMAP,
     388             :                           ObjectIdGetDatum(relid),
     389             :                           ObjectIdGetDatum(subid));
     390             : 
     391        2100 :     if (!HeapTupleIsValid(tup))
     392             :     {
     393          52 :         table_close(rel, AccessShareLock);
     394          52 :         *sublsn = InvalidXLogRecPtr;
     395          52 :         return SUBREL_STATE_UNKNOWN;
     396             :     }
     397             : 
     398             :     /* Get the state. */
     399        2048 :     substate = ((Form_pg_subscription_rel) GETSTRUCT(tup))->srsubstate;
     400             : 
     401             :     /* Get the LSN */
     402        2048 :     d = SysCacheGetAttr(SUBSCRIPTIONRELMAP, tup,
     403             :                         Anum_pg_subscription_rel_srsublsn, &isnull);
     404        2048 :     if (isnull)
     405        1114 :         *sublsn = InvalidXLogRecPtr;
     406             :     else
     407         934 :         *sublsn = DatumGetLSN(d);
     408             : 
     409             :     /* Cleanup */
     410        2048 :     ReleaseSysCache(tup);
     411             : 
     412        2048 :     table_close(rel, AccessShareLock);
     413             : 
     414        2048 :     return substate;
     415             : }
     416             : 
     417             : /*
     418             :  * Drop subscription relation mapping. These can be for a particular
     419             :  * subscription, or for a particular relation, or both.
     420             :  */
     421             : void
     422       45292 : RemoveSubscriptionRel(Oid subid, Oid relid)
     423             : {
     424             :     Relation    rel;
     425             :     TableScanDesc scan;
     426             :     ScanKeyData skey[2];
     427             :     HeapTuple   tup;
     428       45292 :     int         nkeys = 0;
     429             : 
     430       45292 :     rel = table_open(SubscriptionRelRelationId, RowExclusiveLock);
     431             : 
     432       45292 :     if (OidIsValid(subid))
     433             :     {
     434         222 :         ScanKeyInit(&skey[nkeys++],
     435             :                     Anum_pg_subscription_rel_srsubid,
     436             :                     BTEqualStrategyNumber,
     437             :                     F_OIDEQ,
     438             :                     ObjectIdGetDatum(subid));
     439             :     }
     440             : 
     441       45292 :     if (OidIsValid(relid))
     442             :     {
     443       45106 :         ScanKeyInit(&skey[nkeys++],
     444             :                     Anum_pg_subscription_rel_srrelid,
     445             :                     BTEqualStrategyNumber,
     446             :                     F_OIDEQ,
     447             :                     ObjectIdGetDatum(relid));
     448             :     }
     449             : 
     450             :     /* Do the search and delete what we found. */
     451       45292 :     scan = table_beginscan_catalog(rel, nkeys, skey);
     452       45474 :     while (HeapTupleIsValid(tup = heap_getnext(scan, ForwardScanDirection)))
     453             :     {
     454             :         Form_pg_subscription_rel subrel;
     455             : 
     456         182 :         subrel = (Form_pg_subscription_rel) GETSTRUCT(tup);
     457             : 
     458             :         /*
     459             :          * We don't allow to drop the relation mapping when the table
     460             :          * synchronization is in progress unless the caller updates the
     461             :          * corresponding subscription as well. This is to ensure that we don't
     462             :          * leave tablesync slots or origins in the system when the
     463             :          * corresponding table is dropped.
     464             :          */
     465         182 :         if (!OidIsValid(subid) && subrel->srsubstate != SUBREL_STATE_READY)
     466             :         {
     467           0 :             ereport(ERROR,
     468             :                     (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     469             :                      errmsg("could not drop relation mapping for subscription \"%s\"",
     470             :                             get_subscription_name(subrel->srsubid, false)),
     471             :                      errdetail("Table synchronization for relation \"%s\" is in progress and is in state \"%c\".",
     472             :                                get_rel_name(relid), subrel->srsubstate),
     473             : 
     474             :             /*
     475             :              * translator: first %s is a SQL ALTER command and second %s is a
     476             :              * SQL DROP command
     477             :              */
     478             :                      errhint("Use %s to enable subscription if not already enabled or use %s to drop the subscription.",
     479             :                              "ALTER SUBSCRIPTION ... ENABLE",
     480             :                              "DROP SUBSCRIPTION ...")));
     481             :         }
     482             : 
     483         182 :         CatalogTupleDelete(rel, &tup->t_self);
     484             :     }
     485       45292 :     table_endscan(scan);
     486             : 
     487       45292 :     table_close(rel, RowExclusiveLock);
     488       45292 : }
     489             : 
     490             : /*
     491             :  * Does the subscription have any relations?
     492             :  *
     493             :  * Use this function only to know true/false, and when you have no need for the
     494             :  * List returned by GetSubscriptionRelations.
     495             :  */
     496             : bool
     497         406 : HasSubscriptionRelations(Oid subid)
     498             : {
     499             :     Relation    rel;
     500             :     ScanKeyData skey[1];
     501             :     SysScanDesc scan;
     502             :     bool        has_subrels;
     503             : 
     504         406 :     rel = table_open(SubscriptionRelRelationId, AccessShareLock);
     505             : 
     506         406 :     ScanKeyInit(&skey[0],
     507             :                 Anum_pg_subscription_rel_srsubid,
     508             :                 BTEqualStrategyNumber, F_OIDEQ,
     509             :                 ObjectIdGetDatum(subid));
     510             : 
     511         406 :     scan = systable_beginscan(rel, InvalidOid, false,
     512             :                               NULL, 1, skey);
     513             : 
     514             :     /* If even a single tuple exists then the subscription has tables. */
     515         406 :     has_subrels = HeapTupleIsValid(systable_getnext(scan));
     516             : 
     517             :     /* Cleanup */
     518         406 :     systable_endscan(scan);
     519         406 :     table_close(rel, AccessShareLock);
     520             : 
     521         406 :     return has_subrels;
     522             : }
     523             : 
     524             : /*
     525             :  * Get the relations for the subscription.
     526             :  *
     527             :  * If not_ready is true, return only the relations that are not in a ready
     528             :  * state, otherwise return all the relations of the subscription.  The
     529             :  * returned list is palloc'ed in the current memory context.
     530             :  */
     531             : List *
     532        1760 : GetSubscriptionRelations(Oid subid, bool not_ready)
     533             : {
     534        1760 :     List       *res = NIL;
     535             :     Relation    rel;
     536             :     HeapTuple   tup;
     537        1760 :     int         nkeys = 0;
     538             :     ScanKeyData skey[2];
     539             :     SysScanDesc scan;
     540             : 
     541        1760 :     rel = table_open(SubscriptionRelRelationId, AccessShareLock);
     542             : 
     543        1760 :     ScanKeyInit(&skey[nkeys++],
     544             :                 Anum_pg_subscription_rel_srsubid,
     545             :                 BTEqualStrategyNumber, F_OIDEQ,
     546             :                 ObjectIdGetDatum(subid));
     547             : 
     548        1760 :     if (not_ready)
     549        1704 :         ScanKeyInit(&skey[nkeys++],
     550             :                     Anum_pg_subscription_rel_srsubstate,
     551             :                     BTEqualStrategyNumber, F_CHARNE,
     552             :                     CharGetDatum(SUBREL_STATE_READY));
     553             : 
     554        1760 :     scan = systable_beginscan(rel, InvalidOid, false,
     555             :                               NULL, nkeys, skey);
     556             : 
     557        4330 :     while (HeapTupleIsValid(tup = systable_getnext(scan)))
     558             :     {
     559             :         Form_pg_subscription_rel subrel;
     560             :         SubscriptionRelState *relstate;
     561             :         Datum       d;
     562             :         bool        isnull;
     563             : 
     564        2570 :         subrel = (Form_pg_subscription_rel) GETSTRUCT(tup);
     565             : 
     566        2570 :         relstate = (SubscriptionRelState *) palloc(sizeof(SubscriptionRelState));
     567        2570 :         relstate->relid = subrel->srrelid;
     568        2570 :         relstate->state = subrel->srsubstate;
     569        2570 :         d = SysCacheGetAttr(SUBSCRIPTIONRELMAP, tup,
     570             :                             Anum_pg_subscription_rel_srsublsn, &isnull);
     571        2570 :         if (isnull)
     572        2080 :             relstate->lsn = InvalidXLogRecPtr;
     573             :         else
     574         490 :             relstate->lsn = DatumGetLSN(d);
     575             : 
     576        2570 :         res = lappend(res, relstate);
     577             :     }
     578             : 
     579             :     /* Cleanup */
     580        1760 :     systable_endscan(scan);
     581        1760 :     table_close(rel, AccessShareLock);
     582             : 
     583        1760 :     return res;
     584             : }

Generated by: LCOV version 1.14