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: bed3ffbf9d952be6c7d739d068cdce44c046dfb7 vs 574581b50ac9c63dd9e4abebb731a3b67e5b50f6 Lines: 86.1 % 187 161 22 4 1 69 91 1 4 41
Current Date: 2026-05-05 10:23:31 +0900 Functions: 100.0 % 8 8 5 3 1
Baseline: lcov-20260505-025707-baseline Branches: 67.8 % 152 103 28 1 20 1 42 60 7 19
Baseline Date: 2026-05-05 10:27:06 +0900 Line coverage date bins:
Legend: Lines:     hit not hit
Branches: + taken - not taken # not executed
[..1] days: 66.7 % 54 36 18 36
(7,30] days: 0.0 % 1 0 1
(30,360] days: 91.7 % 36 33 3 32 1
(360..) days: 95.8 % 96 92 4 1 1 90 1
Function coverage date bins:
[..1] days: 100.0 % 1 1 1
(30,360] days: 100.0 % 1 1 1
(360..) days: 100.0 % 6 6 3 3
Branch coverage date bins:
[..1] days: 46.7 % 30 14 16 14
(30,360] days: 70.0 % 40 28 12 28
(360..) days: 74.4 % 82 61 1 20 1 60

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

Generated by: LCOV version 2.5.0-beta