LCOV - differential code coverage report
Current view: top level - src/backend/replication/logical - conflict.c (source / functions) Coverage Total Hit UNC UIC UBC GBC GIC GNC CBC ECB DUB DCB
Current: 0e5ff9b9b45a657aea12440478dc002e9b01f138 vs 0123ce131fca454009439dfa3b2266d1d40737d7 Lines: 94.0 % 168 158 6 4 1 66 91 1 4 41
Current Date: 2026-03-14 14:10:32 -0400 Functions: 100.0 % 8 8 5 3 1
Baseline: lcov-20260315-024220-baseline Branches: 73.8 % 126 93 12 1 20 1 32 60 7 19
Baseline Date: 2026-03-14 15:27:56 +0100 Line coverage date bins:
Legend: Lines:     hit not hit
Branches: + taken - not taken # not executed
(30,360] days: 92.6 % 81 75 6 65 10 1
(360..) days: 95.4 % 87 83 4 1 1 81
Function coverage date bins:
(30,360] days: 100.0 % 2 2 2
(360..) days: 100.0 % 6 6 3 3
Branch coverage date bins:
(30,360] days: 75.8 % 62 47 12 3 32 15
(360..) days: 71.9 % 64 46 1 17 1 45

 Age         Owner                    Branch data    TLA  Line data    Source code
                                  1                 :                : /*-------------------------------------------------------------------------
                                  2                 :                :  * conflict.c
                                  3                 :                :  *     Support routines for logging conflicts.
                                  4                 :                :  *
                                  5                 :                :  * Copyright (c) 2024-2026, PostgreSQL Global Development Group
                                  6                 :                :  *
                                  7                 :                :  * IDENTIFICATION
                                  8                 :                :  *    src/backend/replication/logical/conflict.c
                                  9                 :                :  *
                                 10                 :                :  * This file contains the code for logging conflicts on the subscriber during
                                 11                 :                :  * logical replication.
                                 12                 :                :  *-------------------------------------------------------------------------
                                 13                 :                :  */
                                 14                 :                : 
                                 15                 :                : #include "postgres.h"
                                 16                 :                : 
                                 17                 :                : #include "access/commit_ts.h"
                                 18                 :                : #include "access/tableam.h"
                                 19                 :                : #include "executor/executor.h"
                                 20                 :                : #include "pgstat.h"
                                 21                 :                : #include "replication/conflict.h"
                                 22                 :                : #include "replication/worker_internal.h"
                                 23                 :                : #include "storage/lmgr.h"
                                 24                 :                : #include "utils/lsyscache.h"
                                 25                 :                : 
                                 26                 :                : static const char *const ConflictTypeNames[] = {
                                 27                 :                :     [CT_INSERT_EXISTS] = "insert_exists",
                                 28                 :                :     [CT_UPDATE_ORIGIN_DIFFERS] = "update_origin_differs",
                                 29                 :                :     [CT_UPDATE_EXISTS] = "update_exists",
                                 30                 :                :     [CT_UPDATE_MISSING] = "update_missing",
                                 31                 :                :     [CT_DELETE_ORIGIN_DIFFERS] = "delete_origin_differs",
                                 32                 :                :     [CT_UPDATE_DELETED] = "update_deleted",
                                 33                 :                :     [CT_DELETE_MISSING] = "delete_missing",
                                 34                 :                :     [CT_MULTIPLE_UNIQUE_CONFLICTS] = "multiple_unique_conflicts"
                                 35                 :                : };
                                 36                 :                : 
                                 37                 :                : static int  errcode_apply_conflict(ConflictType type);
                                 38                 :                : static void errdetail_apply_conflict(EState *estate,
                                 39                 :                :                                      ResultRelInfo *relinfo,
                                 40                 :                :                                      ConflictType type,
                                 41                 :                :                                      TupleTableSlot *searchslot,
                                 42                 :                :                                      TupleTableSlot *localslot,
                                 43                 :                :                                      TupleTableSlot *remoteslot,
                                 44                 :                :                                      Oid indexoid, TransactionId localxmin,
                                 45                 :                :                                      ReplOriginId localorigin,
                                 46                 :                :                                      TimestampTz localts, StringInfo err_msg);
                                 47                 :                : static void get_tuple_desc(EState *estate, ResultRelInfo *relinfo,
                                 48                 :                :                            ConflictType type, char **key_desc,
                                 49                 :                :                            TupleTableSlot *searchslot, char **search_desc,
                                 50                 :                :                            TupleTableSlot *localslot, char **local_desc,
                                 51                 :                :                            TupleTableSlot *remoteslot, char **remote_desc,
                                 52                 :                :                            Oid indexoid);
                                 53                 :                : static char *build_index_value_desc(EState *estate, Relation localrel,
                                 54                 :                :                                     TupleTableSlot *slot, Oid indexoid);
                                 55                 :                : 
                                 56                 :                : /*
                                 57                 :                :  * Get the xmin and commit timestamp data (origin and timestamp) associated
                                 58                 :                :  * with the provided local row.
                                 59                 :                :  *
                                 60                 :                :  * Return true if the commit timestamp data was found, false otherwise.
                                 61                 :                :  */
                                 62                 :                : bool
  572 akapila@postgresql.o       63                 :CBC       72296 : GetTupleTransactionInfo(TupleTableSlot *localslot, TransactionId *xmin,
                                 64                 :                :                         ReplOriginId *localorigin, TimestampTz *localts)
                                 65                 :                : {
                                 66                 :                :     Datum       xminDatum;
                                 67                 :                :     bool        isnull;
                                 68                 :                : 
                                 69                 :          72296 :     xminDatum = slot_getsysattr(localslot, MinTransactionIdAttributeNumber,
                                 70                 :                :                                 &isnull);
                                 71                 :          72296 :     *xmin = DatumGetTransactionId(xminDatum);
                                 72         [ -  + ]:          72296 :     Assert(!isnull);
                                 73                 :                : 
                                 74                 :                :     /*
                                 75                 :                :      * The commit timestamp data is not available if track_commit_timestamp is
                                 76                 :                :      * disabled.
                                 77                 :                :      */
                                 78         [ +  + ]:          72296 :     if (!track_commit_timestamp)
                                 79                 :                :     {
   46 msawada@postgresql.o       80                 :GNC       72242 :         *localorigin = InvalidReplOriginId;
  572 akapila@postgresql.o       81                 :CBC       72242 :         *localts = 0;
                                 82                 :          72242 :         return false;
                                 83                 :                :     }
                                 84                 :                : 
                                 85                 :             54 :     return TransactionIdGetCommitTsData(*xmin, localts, localorigin);
                                 86                 :                : }
                                 87                 :                : 
                                 88                 :                : /*
                                 89                 :                :  * This function is used to report a conflict while applying replication
                                 90                 :                :  * changes.
                                 91                 :                :  *
                                 92                 :                :  * 'searchslot' should contain the tuple used to search the local row to be
                                 93                 :                :  * updated or deleted.
                                 94                 :                :  *
                                 95                 :                :  * 'remoteslot' should contain the remote new tuple, if any.
                                 96                 :                :  *
                                 97                 :                :  * conflicttuples is a list of local rows that caused the conflict and the
                                 98                 :                :  * conflict related information. See ConflictTupleInfo.
                                 99                 :                :  *
                                100                 :                :  * The caller must ensure that all the indexes passed in ConflictTupleInfo are
                                101                 :                :  * locked so that we can fetch and display the conflicting key values.
                                102                 :                :  */
                                103                 :                : void
                                104                 :             60 : ReportApplyConflict(EState *estate, ResultRelInfo *relinfo, int elevel,
                                105                 :                :                     ConflictType type, TupleTableSlot *searchslot,
                                106                 :                :                     TupleTableSlot *remoteslot, List *conflicttuples)
                                107                 :                : {
                                108                 :             60 :     Relation    localrel = relinfo->ri_RelationDesc;
                                109                 :                :     StringInfoData err_detail;
                                110                 :                : 
  356                           111                 :             60 :     initStringInfo(&err_detail);
                                112                 :                : 
                                113                 :                :     /* Form errdetail message by combining conflicting tuples information. */
                                114   [ +  -  +  +  :            208 :     foreach_ptr(ConflictTupleInfo, conflicttuple, conflicttuples)
                                              +  + ]
                                115                 :             88 :         errdetail_apply_conflict(estate, relinfo, type, searchslot,
  356 akapila@postgresql.o      116                 :ECB        (38) :                                  conflicttuple->slot, remoteslot,
                                117                 :                :                                  conflicttuple->indexoid,
                                118                 :                :                                  conflicttuple->xmin,
  356 akapila@postgresql.o      119                 :CBC          88 :                                  conflicttuple->origin,
                                120                 :                :                                  conflicttuple->ts,
                                121                 :                :                                  &err_detail);
                                122                 :                : 
  557                           123                 :             60 :     pgstat_report_subscription_conflict(MySubscription->oid, type);
                                124                 :                : 
  572                           125         [ +  - ]:             60 :     ereport(elevel,
                                126                 :                :             errcode_apply_conflict(type),
                                127                 :                :             errmsg("conflict detected on relation \"%s.%s\": conflict=%s",
                                128                 :                :                    get_namespace_name(RelationGetNamespace(localrel)),
                                129                 :                :                    RelationGetRelationName(localrel),
                                130                 :                :                    ConflictTypeNames[type]),
                                131                 :                :             errdetail_internal("%s", err_detail.data));
                                132                 :             28 : }
                                133                 :                : 
                                134                 :                : /*
                                135                 :                :  * Find all unique indexes to check for a conflict and store them into
                                136                 :                :  * ResultRelInfo.
                                137                 :                :  */
                                138                 :                : void
                                139                 :         107945 : InitConflictIndexes(ResultRelInfo *relInfo)
                                140                 :                : {
                                141                 :         107945 :     List       *uniqueIndexes = NIL;
                                142                 :                : 
                                143         [ +  + ]:         195495 :     for (int i = 0; i < relInfo->ri_NumIndices; i++)
                                144                 :                :     {
                                145                 :          87550 :         Relation    indexRelation = relInfo->ri_IndexRelationDescs[i];
                                146                 :                : 
                                147         [ -  + ]:          87550 :         if (indexRelation == NULL)
  572 akapila@postgresql.o      148                 :UBC           0 :             continue;
                                149                 :                : 
                                150                 :                :         /* Detect conflict only for unique indexes */
  572 akapila@postgresql.o      151         [ +  + ]:CBC       87550 :         if (!relInfo->ri_IndexRelationInfo[i]->ii_Unique)
                                152                 :             29 :             continue;
                                153                 :                : 
                                154                 :                :         /* Don't support conflict detection for deferrable index */
                                155         [ -  + ]:          87521 :         if (!indexRelation->rd_index->indimmediate)
  572 akapila@postgresql.o      156                 :UBC           0 :             continue;
                                157                 :                : 
  572 akapila@postgresql.o      158                 :CBC       87521 :         uniqueIndexes = lappend_oid(uniqueIndexes,
                                159                 :                :                                     RelationGetRelid(indexRelation));
                                160                 :                :     }
                                161                 :                : 
                                162                 :         107945 :     relInfo->ri_onConflictArbiterIndexes = uniqueIndexes;
                                163                 :         107945 : }
                                164                 :                : 
                                165                 :                : /*
                                166                 :                :  * Add SQLSTATE error code to the current conflict report.
                                167                 :                :  */
                                168                 :                : static int
                                169                 :             60 : errcode_apply_conflict(ConflictType type)
                                170                 :                : {
                                171      [ +  +  - ]:             60 :     switch (type)
                                172                 :                :     {
                                173                 :             32 :         case CT_INSERT_EXISTS:
                                174                 :                :         case CT_UPDATE_EXISTS:
                                175                 :                :         case CT_MULTIPLE_UNIQUE_CONFLICTS:
                                176                 :             32 :             return errcode(ERRCODE_UNIQUE_VIOLATION);
  563                           177                 :             28 :         case CT_UPDATE_ORIGIN_DIFFERS:
                                178                 :                :         case CT_UPDATE_MISSING:
                                179                 :                :         case CT_DELETE_ORIGIN_DIFFERS:
                                180                 :                :         case CT_UPDATE_DELETED:
                                181                 :                :         case CT_DELETE_MISSING:
  572                           182                 :             28 :             return errcode(ERRCODE_T_R_SERIALIZATION_FAILURE);
                                183                 :                :     }
                                184                 :                : 
  572 akapila@postgresql.o      185                 :UBC           0 :     Assert(false);
                                186                 :                :     return 0;                   /* silence compiler warning */
                                187                 :                : }
                                188                 :                : 
                                189                 :                : /*
                                190                 :                :  * Helper function to build the additional details for conflicting key,
                                191                 :                :  * local row, remote row, and replica identity columns.
                                192                 :                :  */
                                193                 :                : static void
   53 akapila@postgresql.o      194                 :GNC         120 : append_tuple_value_detail(StringInfo buf, List *tuple_values,
                                195                 :                :                           bool need_newline)
                                196                 :                : {
                                197                 :            120 :     bool        first = true;
                                198                 :                : 
                                199   [ +  -  -  + ]:            120 :     Assert(buf != NULL && tuple_values != NIL);
                                200                 :                : 
                                201   [ +  -  +  +  :            479 :     foreach_ptr(char, tuple_value, tuple_values)
                                              +  + ]
                                202                 :                :     {
                                203                 :                :         /*
                                204                 :                :          * Skip if the value is NULL. This means the current user does not
                                205                 :                :          * have enough permissions to see all columns in the table. See
                                206                 :                :          * get_tuple_desc().
                                207                 :                :          */
                                208         [ +  + ]:            239 :         if (!tuple_value)
                                209                 :             35 :             continue;
                                210                 :                : 
                                211         [ +  + ]:            204 :         if (first)
                                212                 :                :         {
                                213                 :                :             /*
                                214                 :                :              * translator: The colon is used as a separator in conflict
                                215                 :                :              * messages. The first part, built in the caller, describes what
                                216                 :                :              * happened locally; the second part lists the conflicting keys
                                217                 :                :              * and tuple data.
                                218                 :                :              */
                                219                 :            120 :             appendStringInfoString(buf, _(": "));
                                220                 :                :         }
                                221                 :                :         else
                                222                 :                :         {
                                223                 :                :             /*
                                224                 :                :              * translator: This is a separator in a list of conflicting keys
                                225                 :                :              * and tuple data.
                                226                 :                :              */
                                227                 :             84 :             appendStringInfoString(buf, _(", "));
                                228                 :                :         }
                                229                 :                : 
                                230                 :            204 :         appendStringInfoString(buf, tuple_value);
                                231                 :            204 :         first = false;
                                232                 :                :     }
                                233                 :                : 
                                234                 :                :     /* translator: This is the terminator of a conflict message */
                                235                 :            120 :     appendStringInfoString(buf, _("."));
                                236                 :                : 
                                237         [ +  + ]:            120 :     if (need_newline)
                                238                 :             35 :         appendStringInfoChar(buf, '\n');
                                239                 :            120 : }
                                240                 :                : 
                                241                 :                : /*
                                242                 :                :  * Add an errdetail() line showing conflict detail.
                                243                 :                :  *
                                244                 :                :  * The DETAIL line comprises of two parts:
                                245                 :                :  * 1. Explanation of the conflict type, including the origin and commit
                                246                 :                :  *    timestamp of the local row.
                                247                 :                :  * 2. Display of conflicting key, local row, remote new row, and replica
                                248                 :                :  *    identity columns, if any. The remote old row is excluded as its
                                249                 :                :  *    information is covered in the replica identity columns.
                                250                 :                :  */
                                251                 :                : static void
  572 akapila@postgresql.o      252                 :CBC          88 : errdetail_apply_conflict(EState *estate, ResultRelInfo *relinfo,
                                253                 :                :                          ConflictType type, TupleTableSlot *searchslot,
                                254                 :                :                          TupleTableSlot *localslot, TupleTableSlot *remoteslot,
                                255                 :                :                          Oid indexoid, TransactionId localxmin,
                                256                 :                :                          ReplOriginId localorigin, TimestampTz localts,
                                257                 :                :                          StringInfo err_msg)
                                258                 :                : {
                                259                 :                :     StringInfoData err_detail;
                                260                 :                :     char       *origin_name;
   53 akapila@postgresql.o      261                 :GNC          88 :     char       *key_desc = NULL;
                                262                 :             88 :     char       *local_desc = NULL;
                                263                 :             88 :     char       *remote_desc = NULL;
                                264                 :             88 :     char       *search_desc = NULL;
                                265                 :                : 
                                266                 :                :     /* Get key, replica identity, remote, and local value data */
                                267                 :             88 :     get_tuple_desc(estate, relinfo, type, &key_desc,
                                268                 :                :                    localslot, &local_desc,
                                269                 :                :                    remoteslot, &remote_desc,
                                270                 :                :                    searchslot, &search_desc,
                                271                 :                :                    indexoid);
                                272                 :                : 
  572 akapila@postgresql.o      273                 :CBC          88 :     initStringInfo(&err_detail);
                                274                 :                : 
                                275                 :                :     /* Construct a detailed message describing the type of conflict */
                                276   [ +  +  +  +  :             88 :     switch (type)
                                           +  +  - ]
                                277                 :                :     {
                                278                 :             60 :         case CT_INSERT_EXISTS:
                                279                 :                :         case CT_UPDATE_EXISTS:
                                280                 :                :         case CT_MULTIPLE_UNIQUE_CONFLICTS:
  356                           281   [ +  -  -  + ]:             60 :             Assert(OidIsValid(indexoid) &&
                                282                 :                :                    CheckRelationOidLockedByMe(indexoid, RowExclusiveLock, true));
                                283                 :                : 
   53 akapila@postgresql.o      284         [ +  + ]:GNC          60 :             if (err_msg->len == 0)
                                285                 :                :             {
                                286                 :             32 :                 appendStringInfoString(&err_detail, _("Could not apply remote change"));
                                287                 :                : 
                                288                 :             32 :                 append_tuple_value_detail(&err_detail,
                                289                 :             32 :                                           list_make2(remote_desc, search_desc),
                                290                 :                :                                           true);
                                291                 :                :             }
                                292                 :                : 
  572 akapila@postgresql.o      293         [ +  + ]:CBC          60 :             if (localts)
                                294                 :                :             {
   46 msawada@postgresql.o      295         [ -  + ]:GNC           3 :                 if (localorigin == InvalidReplOriginId)
   53 akapila@postgresql.o      296                 :UNC           0 :                     appendStringInfo(&err_detail, _("Key already exists in unique index \"%s\", modified locally in transaction %u at %s"),
                                297                 :                :                                      get_rel_name(indexoid),
                                298                 :                :                                      localxmin, timestamptz_to_str(localts));
  572 akapila@postgresql.o      299         [ +  + ]:CBC           3 :                 else if (replorigin_by_oid(localorigin, true, &origin_name))
   53 akapila@postgresql.o      300                 :GNC           1 :                     appendStringInfo(&err_detail, _("Key already exists in unique index \"%s\", modified by origin \"%s\" in transaction %u at %s"),
                                301                 :                :                                      get_rel_name(indexoid), origin_name,
                                302                 :                :                                      localxmin, timestamptz_to_str(localts));
                                303                 :                : 
                                304                 :                :                 /*
                                305                 :                :                  * The origin that modified this row has been removed. This
                                306                 :                :                  * can happen if the origin was created by a different apply
                                307                 :                :                  * worker and its associated subscription and origin were
                                308                 :                :                  * dropped after updating the row, or if the origin was
                                309                 :                :                  * manually dropped by the user.
                                310                 :                :                  */
                                311                 :                :                 else
                                312                 :              2 :                     appendStringInfo(&err_detail, _("Key already exists in unique index \"%s\", modified by a non-existent origin in transaction %u at %s"),
                                313                 :                :                                      get_rel_name(indexoid),
                                314                 :                :                                      localxmin, timestamptz_to_str(localts));
                                315                 :                :             }
                                316                 :                :             else
                                317                 :             57 :                 appendStringInfo(&err_detail, _("Key already exists in unique index \"%s\", modified in transaction %u"),
                                318                 :                :                                  get_rel_name(indexoid), localxmin);
                                319                 :                : 
                                320                 :             60 :             append_tuple_value_detail(&err_detail,
                                321                 :             60 :                                       list_make2(key_desc, local_desc), false);
                                322                 :                : 
  572 akapila@postgresql.o      323                 :CBC          60 :             break;
                                324                 :                : 
  563                           325                 :              3 :         case CT_UPDATE_ORIGIN_DIFFERS:
   46 msawada@postgresql.o      326         [ +  + ]:GNC           3 :             if (localorigin == InvalidReplOriginId)
   53 akapila@postgresql.o      327                 :              1 :                 appendStringInfo(&err_detail, _("Updating the row that was modified locally in transaction %u at %s"),
                                328                 :                :                                  localxmin, timestamptz_to_str(localts));
  572 akapila@postgresql.o      329         [ +  + ]:CBC           2 :             else if (replorigin_by_oid(localorigin, true, &origin_name))
   53 akapila@postgresql.o      330                 :GNC           1 :                 appendStringInfo(&err_detail, _("Updating the row that was modified by a different origin \"%s\" in transaction %u at %s"),
                                331                 :                :                                  origin_name, localxmin, timestamptz_to_str(localts));
                                332                 :                : 
                                333                 :                :             /* The origin that modified this row has been removed. */
                                334                 :                :             else
                                335                 :              1 :                 appendStringInfo(&err_detail, _("Updating the row that was modified by a non-existent origin in transaction %u at %s"),
                                336                 :                :                                  localxmin, timestamptz_to_str(localts));
                                337                 :                : 
                                338                 :              3 :             append_tuple_value_detail(&err_detail,
                                339                 :              3 :                                       list_make3(local_desc, remote_desc,
                                340                 :                :                                                  search_desc), false);
                                341                 :                : 
  572                           342                 :              3 :             break;
                                343                 :                : 
  223                           344                 :              3 :         case CT_UPDATE_DELETED:
   53                           345                 :              3 :             appendStringInfoString(&err_detail, _("Could not find the row to be updated"));
                                346                 :                : 
                                347                 :              3 :             append_tuple_value_detail(&err_detail,
                                348                 :              3 :                                       list_make2(remote_desc, search_desc),
                                349                 :                :                                       true);
                                350                 :                : 
  223                           351         [ +  - ]:              3 :             if (localts)
                                352                 :                :             {
   46 msawada@postgresql.o      353         [ +  - ]:              3 :                 if (localorigin == InvalidReplOriginId)
   53 akapila@postgresql.o      354                 :              3 :                     appendStringInfo(&err_detail, _("The row to be updated was deleted locally in transaction %u at %s"),
                                355                 :                :                                      localxmin, timestamptz_to_str(localts));
  223 akapila@postgresql.o      356         [ #  # ]:UNC           0 :                 else if (replorigin_by_oid(localorigin, true, &origin_name))
   53                           357                 :              0 :                     appendStringInfo(&err_detail, _("The row to be updated was deleted by a different origin \"%s\" in transaction %u at %s"),
                                358                 :                :                                      origin_name, localxmin, timestamptz_to_str(localts));
                                359                 :                : 
                                360                 :                :                 /* The origin that modified this row has been removed. */
                                361                 :                :                 else
                                362                 :              0 :                     appendStringInfo(&err_detail, _("The row to be updated was deleted by a non-existent origin in transaction %u at %s"),
                                363                 :                :                                      localxmin, timestamptz_to_str(localts));
                                364                 :                :             }
                                365                 :                :             else
                                366                 :              0 :                 appendStringInfo(&err_detail, _("The row to be updated was deleted"));
                                367                 :                : 
  223 akapila@postgresql.o      368                 :CBC           3 :             break;
                                369                 :                : 
  572                           370                 :              8 :         case CT_UPDATE_MISSING:
   53 akapila@postgresql.o      371                 :GNC           8 :             appendStringInfoString(&err_detail, _("Could not find the row to be updated"));
                                372                 :                : 
                                373                 :              8 :             append_tuple_value_detail(&err_detail,
                                374                 :              8 :                                       list_make2(remote_desc, search_desc),
                                375                 :                :                                       false);
                                376                 :                : 
  572 akapila@postgresql.o      377                 :CBC           8 :             break;
                                378                 :                : 
  563                           379                 :              5 :         case CT_DELETE_ORIGIN_DIFFERS:
   46 msawada@postgresql.o      380         [ +  + ]:GNC           5 :             if (localorigin == InvalidReplOriginId)
   53 akapila@postgresql.o      381                 :              4 :                 appendStringInfo(&err_detail, _("Deleting the row that was modified locally in transaction %u at %s"),
                                382                 :                :                                  localxmin, timestamptz_to_str(localts));
  572 akapila@postgresql.o      383         [ +  - ]:CBC           1 :             else if (replorigin_by_oid(localorigin, true, &origin_name))
   53 akapila@postgresql.o      384                 :GNC           1 :                 appendStringInfo(&err_detail, _("Deleting the row that was modified by a different origin \"%s\" in transaction %u at %s"),
                                385                 :                :                                  origin_name, localxmin, timestamptz_to_str(localts));
                                386                 :                : 
                                387                 :                :             /* The origin that modified this row has been removed. */
                                388                 :                :             else
   53 akapila@postgresql.o      389                 :UNC           0 :                 appendStringInfo(&err_detail, _("Deleting the row that was modified by a non-existent origin in transaction %u at %s"),
                                390                 :                :                                  localxmin, timestamptz_to_str(localts));
                                391                 :                : 
   53 akapila@postgresql.o      392                 :GNC           5 :             append_tuple_value_detail(&err_detail,
                                393                 :              5 :                                       list_make3(local_desc, remote_desc,
                                394                 :                :                                                  search_desc), false);
                                395                 :                : 
  572 akapila@postgresql.o      396                 :CBC           5 :             break;
                                397                 :                : 
                                398                 :              9 :         case CT_DELETE_MISSING:
   53 akapila@postgresql.o      399                 :GNC           9 :             appendStringInfoString(&err_detail, _("Could not find the row to be deleted"));
                                400                 :                : 
                                401                 :              9 :             append_tuple_value_detail(&err_detail,
                                402                 :              9 :                                       list_make1(search_desc), false);
                                403                 :                : 
  572 akapila@postgresql.o      404                 :CBC           9 :             break;
                                405                 :                :     }
                                406                 :                : 
                                407         [ -  + ]:             88 :     Assert(err_detail.len > 0);
                                408                 :                : 
                                409                 :                :     /*
                                410                 :                :      * Insert a blank line to visually separate the new detail line from the
                                411                 :                :      * existing ones.
                                412                 :                :      */
  356                           413         [ +  + ]:             88 :     if (err_msg->len > 0)
                                414                 :             28 :         appendStringInfoChar(err_msg, '\n');
                                415                 :                : 
  338 drowley@postgresql.o      416                 :             88 :     appendStringInfoString(err_msg, err_detail.data);
  572 akapila@postgresql.o      417                 :             88 : }
                                418                 :                : 
                                419                 :                : /*
                                420                 :                :  * Extract conflicting key, local row, remote row, and replica identity
                                421                 :                :  * columns. Results are set at xxx_desc.
                                422                 :                :  *
                                423                 :                :  * If the output is NULL, it indicates that the current user lacks permissions
                                424                 :                :  * to view the columns involved.
                                425                 :                :  */
                                426                 :                : static void
   53 akapila@postgresql.o      427                 :GNC          88 : get_tuple_desc(EState *estate, ResultRelInfo *relinfo, ConflictType type,
                                428                 :                :                char **key_desc,
                                429                 :                :                TupleTableSlot *localslot, char **local_desc,
                                430                 :                :                TupleTableSlot *remoteslot, char **remote_desc,
                                431                 :                :                TupleTableSlot *searchslot, char **search_desc,
                                432                 :                :                Oid indexoid)
                                433                 :                : {
  572 akapila@postgresql.o      434                 :CBC          88 :     Relation    localrel = relinfo->ri_RelationDesc;
                                435                 :             88 :     Oid         relid = RelationGetRelid(localrel);
                                436                 :             88 :     TupleDesc   tupdesc = RelationGetDescr(localrel);
                                437                 :             88 :     char       *desc = NULL;
                                438                 :                : 
   53 akapila@postgresql.o      439   [ +  +  -  +  :GNC          88 :     Assert((localslot && local_desc) || (remoteslot && remote_desc) ||
                                     +  +  -  +  +  
                                           -  -  + ]
                                440                 :                :            (searchslot && search_desc));
                                441                 :                : 
                                442                 :                :     /*
                                443                 :                :      * Report the conflicting key values in the case of a unique constraint
                                444                 :                :      * violation.
                                445                 :                :      */
  356 akapila@postgresql.o      446   [ +  +  +  +  :CBC          88 :     if (type == CT_INSERT_EXISTS || type == CT_UPDATE_EXISTS ||
                                              +  + ]
                                447                 :                :         type == CT_MULTIPLE_UNIQUE_CONFLICTS)
                                448                 :                :     {
  572                           449   [ +  -  -  + ]:             60 :         Assert(OidIsValid(indexoid) && localslot);
                                450                 :                : 
   53 akapila@postgresql.o      451                 :GNC          60 :         desc = build_index_value_desc(estate, localrel, localslot,
                                452                 :                :                                       indexoid);
                                453                 :                : 
  572 akapila@postgresql.o      454         [ +  - ]:CBC          60 :         if (desc)
   53 akapila@postgresql.o      455                 :GNC          60 :             *key_desc = psprintf(_("key %s"), desc);
                                456                 :                :     }
                                457                 :                : 
  572 akapila@postgresql.o      458         [ +  + ]:CBC          88 :     if (localslot)
                                459                 :                :     {
                                460                 :                :         /*
                                461                 :                :          * The 'modifiedCols' only applies to the new tuple, hence we pass
                                462                 :                :          * NULL for the local row.
                                463                 :                :          */
                                464                 :             68 :         desc = ExecBuildSlotValueDescription(relid, localslot, tupdesc,
                                465                 :                :                                              NULL, 64);
                                466                 :                : 
                                467         [ +  - ]:             68 :         if (desc)
   53 akapila@postgresql.o      468                 :GNC          68 :             *local_desc = psprintf(_("local row %s"), desc);
                                469                 :                :     }
                                470                 :                : 
  572 akapila@postgresql.o      471         [ +  + ]:CBC          88 :     if (remoteslot)
                                472                 :                :     {
                                473                 :                :         Bitmapset  *modifiedCols;
                                474                 :                : 
                                475                 :                :         /*
                                476                 :                :          * Although logical replication doesn't maintain the bitmap for the
                                477                 :                :          * columns being inserted, we still use it to create 'modifiedCols'
                                478                 :                :          * for consistency with other calls to ExecBuildSlotValueDescription.
                                479                 :                :          *
                                480                 :                :          * Note that generated columns are formed locally on the subscriber.
                                481                 :                :          */
                                482                 :             74 :         modifiedCols = bms_union(ExecGetInsertedCols(relinfo, estate),
                                483                 :             74 :                                  ExecGetUpdatedCols(relinfo, estate));
   53 akapila@postgresql.o      484                 :GNC          74 :         desc = ExecBuildSlotValueDescription(relid, remoteslot,
                                485                 :                :                                              tupdesc, modifiedCols,
                                486                 :                :                                              64);
                                487                 :                : 
  572 akapila@postgresql.o      488         [ +  - ]:CBC          74 :         if (desc)
   53 akapila@postgresql.o      489                 :GNC          74 :             *remote_desc = psprintf(_("remote row %s"), desc);
                                490                 :                :     }
                                491                 :                : 
  572 akapila@postgresql.o      492         [ +  + ]:CBC          88 :     if (searchslot)
                                493                 :                :     {
                                494                 :                :         /*
                                495                 :                :          * Note that while index other than replica identity may be used (see
                                496                 :                :          * IsIndexUsableForReplicaIdentityFull for details) to find the tuple
                                497                 :                :          * when applying update or delete, such an index scan may not result
                                498                 :                :          * in a unique tuple and we still compare the complete tuple in such
                                499                 :                :          * cases, thus such indexes are not used here.
                                500                 :                :          */
                                501                 :             32 :         Oid         replica_index = GetRelationIdentityOrPK(localrel);
                                502                 :                : 
                                503         [ -  + ]:             32 :         Assert(type != CT_INSERT_EXISTS);
                                504                 :                : 
                                505                 :                :         /*
                                506                 :                :          * If the table has a valid replica identity index, build the index
                                507                 :                :          * key value string. Otherwise, construct the full tuple value for
                                508                 :                :          * REPLICA IDENTITY FULL cases.
                                509                 :                :          */
                                510         [ +  + ]:             32 :         if (OidIsValid(replica_index))
                                511                 :             28 :             desc = build_index_value_desc(estate, localrel, searchslot, replica_index);
                                512                 :                :         else
                                513                 :              4 :             desc = ExecBuildSlotValueDescription(relid, searchslot, tupdesc, NULL, 64);
                                514                 :                : 
                                515         [ +  - ]:             32 :         if (desc)
                                516                 :                :         {
   53 akapila@postgresql.o      517         [ +  + ]:GNC          32 :             if (OidIsValid(replica_index))
                                518                 :             28 :                 *search_desc = psprintf(_("replica identity %s"), desc);
                                519                 :                :             else
                                520                 :              4 :                 *search_desc = psprintf(_("replica identity full %s"), desc);
                                521                 :                :         }
                                522                 :                :     }
  572 akapila@postgresql.o      523                 :GIC          88 : }
                                524                 :                : 
                                525                 :                : /*
                                526                 :                :  * Helper functions to construct a string describing the contents of an index
                                527                 :                :  * entry. See BuildIndexValueDescription for details.
                                528                 :                :  *
                                529                 :                :  * The caller must ensure that the index with the OID 'indexoid' is locked so
                                530                 :                :  * that we can fetch and display the conflicting key value.
                                531                 :                :  */
                                532                 :                : static char *
  572 akapila@postgresql.o      533                 :CBC          88 : build_index_value_desc(EState *estate, Relation localrel, TupleTableSlot *slot,
                                534                 :                :                        Oid indexoid)
                                535                 :                : {
                                536                 :                :     char       *index_value;
                                537                 :                :     Relation    indexDesc;
                                538                 :                :     Datum       values[INDEX_MAX_KEYS];
                                539                 :                :     bool        isnull[INDEX_MAX_KEYS];
                                540                 :             88 :     TupleTableSlot *tableslot = slot;
                                541                 :                : 
                                542         [ -  + ]:             88 :     if (!tableslot)
  572 akapila@postgresql.o      543                 :UBC           0 :         return NULL;
                                544                 :                : 
  572 akapila@postgresql.o      545         [ -  + ]:CBC          88 :     Assert(CheckRelationOidLockedByMe(indexoid, RowExclusiveLock, true));
                                546                 :                : 
                                547                 :             88 :     indexDesc = index_open(indexoid, NoLock);
                                548                 :                : 
                                549                 :                :     /*
                                550                 :                :      * If the slot is a virtual slot, copy it into a heap tuple slot as
                                551                 :                :      * FormIndexDatum only works with heap tuple slots.
                                552                 :                :      */
                                553         [ +  + ]:             88 :     if (TTS_IS_VIRTUAL(slot))
                                554                 :                :     {
                                555                 :             19 :         tableslot = table_slot_create(localrel, &estate->es_tupleTable);
                                556                 :             19 :         tableslot = ExecCopySlot(tableslot, slot);
                                557                 :                :     }
                                558                 :                : 
                                559                 :                :     /*
                                560                 :                :      * Initialize ecxt_scantuple for potential use in FormIndexDatum when
                                561                 :                :      * index expressions are present.
                                562                 :                :      */
                                563         [ +  - ]:             88 :     GetPerTupleExprContext(estate)->ecxt_scantuple = tableslot;
                                564                 :                : 
                                565                 :                :     /*
                                566                 :                :      * The values/nulls arrays passed to BuildIndexValueDescription should be
                                567                 :                :      * the results of FormIndexDatum, which are the "raw" input to the index
                                568                 :                :      * AM.
                                569                 :                :      */
                                570                 :             88 :     FormIndexDatum(BuildIndexInfo(indexDesc), tableslot, estate, values, isnull);
                                571                 :                : 
                                572                 :             88 :     index_value = BuildIndexValueDescription(indexDesc, values, isnull);
                                573                 :                : 
                                574                 :             88 :     index_close(indexDesc, NoLock);
                                575                 :                : 
                                576                 :             88 :     return index_value;
                                577                 :                : }
        

Generated by: LCOV version 2.4-beta