LCOV - code coverage report
Current view: top level - src/backend/replication/logical - conflict.c (source / functions) Coverage Total Hit
Test: Code coverage Lines: 0.0 % 215 0
Test Date: 2026-01-26 10:56:24 Functions: 0.0 % 8 0
Legend: Lines:     hit not hit
Branches: + taken - not taken # not executed
Branches: 0.0 % 131 0

             Branch data     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                 :             :                                                                          RepOriginId 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
      63                 :           0 : GetTupleTransactionInfo(TupleTableSlot *localslot, TransactionId *xmin,
      64                 :             :                                                 RepOriginId *localorigin, TimestampTz *localts)
      65                 :             : {
      66                 :           0 :         Datum           xminDatum;
      67                 :           0 :         bool            isnull;
      68                 :             : 
      69                 :           0 :         xminDatum = slot_getsysattr(localslot, MinTransactionIdAttributeNumber,
      70                 :             :                                                                 &isnull);
      71                 :           0 :         *xmin = DatumGetTransactionId(xminDatum);
      72         [ #  # ]:           0 :         Assert(!isnull);
      73                 :             : 
      74                 :             :         /*
      75                 :             :          * The commit timestamp data is not available if track_commit_timestamp is
      76                 :             :          * disabled.
      77                 :             :          */
      78         [ #  # ]:           0 :         if (!track_commit_timestamp)
      79                 :             :         {
      80                 :           0 :                 *localorigin = InvalidRepOriginId;
      81                 :           0 :                 *localts = 0;
      82                 :           0 :                 return false;
      83                 :             :         }
      84                 :             : 
      85                 :           0 :         return TransactionIdGetCommitTsData(*xmin, localts, localorigin);
      86                 :           0 : }
      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                 :           0 : ReportApplyConflict(EState *estate, ResultRelInfo *relinfo, int elevel,
     105                 :             :                                         ConflictType type, TupleTableSlot *searchslot,
     106                 :             :                                         TupleTableSlot *remoteslot, List *conflicttuples)
     107                 :             : {
     108                 :           0 :         Relation        localrel = relinfo->ri_RelationDesc;
     109                 :           0 :         StringInfoData err_detail;
     110                 :             : 
     111                 :           0 :         initStringInfo(&err_detail);
     112                 :             : 
     113                 :             :         /* Form errdetail message by combining conflicting tuples information. */
     114   [ #  #  #  #  :           0 :         foreach_ptr(ConflictTupleInfo, conflicttuple, conflicttuples)
             #  #  #  # ]
     115                 :           0 :                 errdetail_apply_conflict(estate, relinfo, type, searchslot,
     116                 :           0 :                                                                  conflicttuple->slot, remoteslot,
     117                 :           0 :                                                                  conflicttuple->indexoid,
     118                 :           0 :                                                                  conflicttuple->xmin,
     119                 :           0 :                                                                  conflicttuple->origin,
     120                 :           0 :                                                                  conflicttuple->ts,
     121                 :           0 :                                                                  &err_detail);
     122                 :             : 
     123                 :           0 :         pgstat_report_subscription_conflict(MySubscription->oid, type);
     124                 :             : 
     125   [ #  #  #  #  :           0 :         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                 :           0 : }
     133                 :             : 
     134                 :             : /*
     135                 :             :  * Find all unique indexes to check for a conflict and store them into
     136                 :             :  * ResultRelInfo.
     137                 :             :  */
     138                 :             : void
     139                 :           0 : InitConflictIndexes(ResultRelInfo *relInfo)
     140                 :             : {
     141                 :           0 :         List       *uniqueIndexes = NIL;
     142                 :             : 
     143         [ #  # ]:           0 :         for (int i = 0; i < relInfo->ri_NumIndices; i++)
     144                 :             :         {
     145                 :           0 :                 Relation        indexRelation = relInfo->ri_IndexRelationDescs[i];
     146                 :             : 
     147         [ #  # ]:           0 :                 if (indexRelation == NULL)
     148                 :           0 :                         continue;
     149                 :             : 
     150                 :             :                 /* Detect conflict only for unique indexes */
     151         [ #  # ]:           0 :                 if (!relInfo->ri_IndexRelationInfo[i]->ii_Unique)
     152                 :           0 :                         continue;
     153                 :             : 
     154                 :             :                 /* Don't support conflict detection for deferrable index */
     155         [ #  # ]:           0 :                 if (!indexRelation->rd_index->indimmediate)
     156                 :           0 :                         continue;
     157                 :             : 
     158                 :           0 :                 uniqueIndexes = lappend_oid(uniqueIndexes,
     159                 :           0 :                                                                         RelationGetRelid(indexRelation));
     160      [ #  #  # ]:           0 :         }
     161                 :             : 
     162                 :           0 :         relInfo->ri_onConflictArbiterIndexes = uniqueIndexes;
     163                 :           0 : }
     164                 :             : 
     165                 :             : /*
     166                 :             :  * Add SQLSTATE error code to the current conflict report.
     167                 :             :  */
     168                 :             : static int
     169                 :           0 : errcode_apply_conflict(ConflictType type)
     170                 :             : {
     171      [ #  #  # ]:           0 :         switch (type)
     172                 :             :         {
     173                 :             :                 case CT_INSERT_EXISTS:
     174                 :             :                 case CT_UPDATE_EXISTS:
     175                 :             :                 case CT_MULTIPLE_UNIQUE_CONFLICTS:
     176                 :           0 :                         return errcode(ERRCODE_UNIQUE_VIOLATION);
     177                 :             :                 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:
     182                 :           0 :                         return errcode(ERRCODE_T_R_SERIALIZATION_FAILURE);
     183                 :             :         }
     184                 :             : 
     185                 :           0 :         Assert(false);
     186                 :           0 :         return 0;                                       /* silence compiler warning */
     187                 :           0 : }
     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
     194                 :           0 : append_tuple_value_detail(StringInfo buf, List *tuple_values,
     195                 :             :                                                   bool need_newline)
     196                 :             : {
     197                 :           0 :         bool            first = true;
     198                 :             : 
     199         [ #  # ]:           0 :         Assert(buf != NULL && tuple_values != NIL);
     200                 :             : 
     201   [ #  #  #  #  :           0 :         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         [ #  # ]:           0 :                 if (!tuple_value)
     209                 :           0 :                         continue;
     210                 :             : 
     211         [ #  # ]:           0 :                 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                 :           0 :                         appendStringInfoString(buf, _(": "));
     220                 :           0 :                 }
     221                 :             :                 else
     222                 :             :                 {
     223                 :             :                         /*
     224                 :             :                          * translator: This is a separator in a list of conflicting keys
     225                 :             :                          * and tuple data.
     226                 :             :                          */
     227                 :           0 :                         appendStringInfoString(buf, _(", "));
     228                 :             :                 }
     229                 :             : 
     230                 :           0 :                 appendStringInfoString(buf, tuple_value);
     231                 :           0 :                 first = false;
     232                 :           0 :         }
     233                 :             : 
     234                 :             :         /* translator: This is the terminator of a conflict message */
     235                 :           0 :         appendStringInfoString(buf, _("."));
     236                 :             : 
     237         [ #  # ]:           0 :         if (need_newline)
     238                 :           0 :                 appendStringInfoChar(buf, '\n');
     239                 :           0 : }
     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
     252                 :           0 : errdetail_apply_conflict(EState *estate, ResultRelInfo *relinfo,
     253                 :             :                                                  ConflictType type, TupleTableSlot *searchslot,
     254                 :             :                                                  TupleTableSlot *localslot, TupleTableSlot *remoteslot,
     255                 :             :                                                  Oid indexoid, TransactionId localxmin,
     256                 :             :                                                  RepOriginId localorigin, TimestampTz localts,
     257                 :             :                                                  StringInfo err_msg)
     258                 :             : {
     259                 :           0 :         StringInfoData err_detail;
     260                 :           0 :         char       *origin_name;
     261                 :           0 :         char       *key_desc = NULL;
     262                 :           0 :         char       *local_desc = NULL;
     263                 :           0 :         char       *remote_desc = NULL;
     264                 :           0 :         char       *search_desc = NULL;
     265                 :             : 
     266                 :             :         /* Get key, replica identity, remote, and local value data */
     267                 :           0 :         get_tuple_desc(estate, relinfo, type, &key_desc,
     268                 :           0 :                                    localslot, &local_desc,
     269                 :           0 :                                    remoteslot, &remote_desc,
     270                 :           0 :                                    searchslot, &search_desc,
     271                 :           0 :                                    indexoid);
     272                 :             : 
     273                 :           0 :         initStringInfo(&err_detail);
     274                 :             : 
     275                 :             :         /* Construct a detailed message describing the type of conflict */
     276   [ #  #  #  #  :           0 :         switch (type)
                #  #  # ]
     277                 :             :         {
     278                 :             :                 case CT_INSERT_EXISTS:
     279                 :             :                 case CT_UPDATE_EXISTS:
     280                 :             :                 case CT_MULTIPLE_UNIQUE_CONFLICTS:
     281         [ #  # ]:           0 :                         Assert(OidIsValid(indexoid) &&
     282                 :             :                                    CheckRelationOidLockedByMe(indexoid, RowExclusiveLock, true));
     283                 :             : 
     284         [ #  # ]:           0 :                         if (err_msg->len == 0)
     285                 :             :                         {
     286                 :           0 :                                 appendStringInfoString(&err_detail, _("Could not apply remote change"));
     287                 :             : 
     288                 :           0 :                                 append_tuple_value_detail(&err_detail,
     289                 :           0 :                                                                                   list_make2(remote_desc, search_desc),
     290                 :             :                                                                                   true);
     291                 :           0 :                         }
     292                 :             : 
     293         [ #  # ]:           0 :                         if (localts)
     294                 :             :                         {
     295         [ #  # ]:           0 :                                 if (localorigin == InvalidRepOriginId)
     296                 :           0 :                                         appendStringInfo(&err_detail, _("Key already exists in unique index \"%s\", modified locally in transaction %u at %s"),
     297                 :           0 :                                                                          get_rel_name(indexoid),
     298                 :           0 :                                                                          localxmin, timestamptz_to_str(localts));
     299         [ #  # ]:           0 :                                 else if (replorigin_by_oid(localorigin, true, &origin_name))
     300                 :           0 :                                         appendStringInfo(&err_detail, _("Key already exists in unique index \"%s\", modified by origin \"%s\" in transaction %u at %s"),
     301                 :           0 :                                                                          get_rel_name(indexoid), origin_name,
     302                 :           0 :                                                                          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                 :           0 :                                         appendStringInfo(&err_detail, _("Key already exists in unique index \"%s\", modified by a non-existent origin in transaction %u at %s"),
     313                 :           0 :                                                                          get_rel_name(indexoid),
     314                 :           0 :                                                                          localxmin, timestamptz_to_str(localts));
     315                 :           0 :                         }
     316                 :             :                         else
     317                 :           0 :                                 appendStringInfo(&err_detail, _("Key already exists in unique index \"%s\", modified in transaction %u"),
     318                 :           0 :                                                                  get_rel_name(indexoid), localxmin);
     319                 :             : 
     320                 :           0 :                         append_tuple_value_detail(&err_detail,
     321                 :           0 :                                                                           list_make2(key_desc, local_desc), false);
     322                 :             : 
     323                 :           0 :                         break;
     324                 :             : 
     325                 :             :                 case CT_UPDATE_ORIGIN_DIFFERS:
     326         [ #  # ]:           0 :                         if (localorigin == InvalidRepOriginId)
     327                 :           0 :                                 appendStringInfo(&err_detail, _("Updating the row that was modified locally in transaction %u at %s"),
     328                 :           0 :                                                                  localxmin, timestamptz_to_str(localts));
     329         [ #  # ]:           0 :                         else if (replorigin_by_oid(localorigin, true, &origin_name))
     330                 :           0 :                                 appendStringInfo(&err_detail, _("Updating the row that was modified by a different origin \"%s\" in transaction %u at %s"),
     331                 :           0 :                                                                  origin_name, localxmin, timestamptz_to_str(localts));
     332                 :             : 
     333                 :             :                         /* The origin that modified this row has been removed. */
     334                 :             :                         else
     335                 :           0 :                                 appendStringInfo(&err_detail, _("Updating the row that was modified by a non-existent origin in transaction %u at %s"),
     336                 :           0 :                                                                  localxmin, timestamptz_to_str(localts));
     337                 :             : 
     338                 :           0 :                         append_tuple_value_detail(&err_detail,
     339                 :           0 :                                                                           list_make3(local_desc, remote_desc,
     340                 :             :                                                                                                  search_desc), false);
     341                 :             : 
     342                 :           0 :                         break;
     343                 :             : 
     344                 :             :                 case CT_UPDATE_DELETED:
     345                 :           0 :                         appendStringInfoString(&err_detail, _("Could not find the row to be updated"));
     346                 :             : 
     347                 :           0 :                         append_tuple_value_detail(&err_detail,
     348                 :           0 :                                                                           list_make2(remote_desc, search_desc),
     349                 :             :                                                                           true);
     350                 :             : 
     351         [ #  # ]:           0 :                         if (localts)
     352                 :             :                         {
     353         [ #  # ]:           0 :                                 if (localorigin == InvalidRepOriginId)
     354                 :           0 :                                         appendStringInfo(&err_detail, _("The row to be updated was deleted locally in transaction %u at %s"),
     355                 :           0 :                                                                          localxmin, timestamptz_to_str(localts));
     356         [ #  # ]:           0 :                                 else if (replorigin_by_oid(localorigin, true, &origin_name))
     357                 :           0 :                                         appendStringInfo(&err_detail, _("The row to be updated was deleted by a different origin \"%s\" in transaction %u at %s"),
     358                 :           0 :                                                                          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                 :           0 :                                                                          localxmin, timestamptz_to_str(localts));
     364                 :           0 :                         }
     365                 :             :                         else
     366                 :           0 :                                 appendStringInfo(&err_detail, _("The row to be updated was deleted"));
     367                 :             : 
     368                 :           0 :                         break;
     369                 :             : 
     370                 :             :                 case CT_UPDATE_MISSING:
     371                 :           0 :                         appendStringInfoString(&err_detail, _("Could not find the row to be updated"));
     372                 :             : 
     373                 :           0 :                         append_tuple_value_detail(&err_detail,
     374                 :           0 :                                                                           list_make2(remote_desc, search_desc),
     375                 :             :                                                                           false);
     376                 :             : 
     377                 :           0 :                         break;
     378                 :             : 
     379                 :             :                 case CT_DELETE_ORIGIN_DIFFERS:
     380         [ #  # ]:           0 :                         if (localorigin == InvalidRepOriginId)
     381                 :           0 :                                 appendStringInfo(&err_detail, _("Deleting the row that was modified locally in transaction %u at %s"),
     382                 :           0 :                                                                  localxmin, timestamptz_to_str(localts));
     383         [ #  # ]:           0 :                         else if (replorigin_by_oid(localorigin, true, &origin_name))
     384                 :           0 :                                 appendStringInfo(&err_detail, _("Deleting the row that was modified by a different origin \"%s\" in transaction %u at %s"),
     385                 :           0 :                                                                  origin_name, localxmin, timestamptz_to_str(localts));
     386                 :             : 
     387                 :             :                         /* The origin that modified this row has been removed. */
     388                 :             :                         else
     389                 :           0 :                                 appendStringInfo(&err_detail, _("Deleting the row that was modified by a non-existent origin in transaction %u at %s"),
     390                 :           0 :                                                                  localxmin, timestamptz_to_str(localts));
     391                 :             : 
     392                 :           0 :                         append_tuple_value_detail(&err_detail,
     393                 :           0 :                                                                           list_make3(local_desc, remote_desc,
     394                 :             :                                                                                                  search_desc), false);
     395                 :             : 
     396                 :           0 :                         break;
     397                 :             : 
     398                 :             :                 case CT_DELETE_MISSING:
     399                 :           0 :                         appendStringInfoString(&err_detail, _("Could not find the row to be deleted"));
     400                 :             : 
     401                 :           0 :                         append_tuple_value_detail(&err_detail,
     402                 :           0 :                                                                           list_make1(search_desc), false);
     403                 :             : 
     404                 :           0 :                         break;
     405                 :             :         }
     406                 :             : 
     407         [ #  # ]:           0 :         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                 :             :          */
     413         [ #  # ]:           0 :         if (err_msg->len > 0)
     414                 :           0 :                 appendStringInfoChar(err_msg, '\n');
     415                 :             : 
     416                 :           0 :         appendStringInfoString(err_msg, err_detail.data);
     417                 :           0 : }
     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
     427                 :           0 : 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                 :             : {
     434                 :           0 :         Relation        localrel = relinfo->ri_RelationDesc;
     435                 :           0 :         Oid                     relid = RelationGetRelid(localrel);
     436                 :           0 :         TupleDesc       tupdesc = RelationGetDescr(localrel);
     437                 :           0 :         char       *desc = NULL;
     438                 :             : 
     439   [ #  #  #  #  :           0 :         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                 :             :          */
     446   [ #  #  #  #  :           0 :         if (type == CT_INSERT_EXISTS || type == CT_UPDATE_EXISTS ||
                   #  # ]
     447                 :           0 :                 type == CT_MULTIPLE_UNIQUE_CONFLICTS)
     448                 :             :         {
     449         [ #  # ]:           0 :                 Assert(OidIsValid(indexoid) && localslot);
     450                 :             : 
     451                 :           0 :                 desc = build_index_value_desc(estate, localrel, localslot,
     452                 :           0 :                                                                           indexoid);
     453                 :             : 
     454         [ #  # ]:           0 :                 if (desc)
     455                 :           0 :                         *key_desc = psprintf(_("key %s"), desc);
     456                 :           0 :         }
     457                 :             : 
     458         [ #  # ]:           0 :         if (localslot)
     459                 :             :         {
     460                 :             :                 /*
     461                 :             :                  * The 'modifiedCols' only applies to the new tuple, hence we pass
     462                 :             :                  * NULL for the local row.
     463                 :             :                  */
     464                 :           0 :                 desc = ExecBuildSlotValueDescription(relid, localslot, tupdesc,
     465                 :             :                                                                                          NULL, 64);
     466                 :             : 
     467         [ #  # ]:           0 :                 if (desc)
     468                 :           0 :                         *local_desc = psprintf(_("local row %s"), desc);
     469                 :           0 :         }
     470                 :             : 
     471         [ #  # ]:           0 :         if (remoteslot)
     472                 :             :         {
     473                 :           0 :                 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                 :           0 :                 modifiedCols = bms_union(ExecGetInsertedCols(relinfo, estate),
     483                 :           0 :                                                                  ExecGetUpdatedCols(relinfo, estate));
     484                 :           0 :                 desc = ExecBuildSlotValueDescription(relid, remoteslot,
     485                 :           0 :                                                                                          tupdesc, modifiedCols,
     486                 :             :                                                                                          64);
     487                 :             : 
     488         [ #  # ]:           0 :                 if (desc)
     489                 :           0 :                         *remote_desc = psprintf(_("remote row %s"), desc);
     490                 :           0 :         }
     491                 :             : 
     492         [ #  # ]:           0 :         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                 :           0 :                 Oid                     replica_index = GetRelationIdentityOrPK(localrel);
     502                 :             : 
     503         [ #  # ]:           0 :                 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         [ #  # ]:           0 :                 if (OidIsValid(replica_index))
     511                 :           0 :                         desc = build_index_value_desc(estate, localrel, searchslot, replica_index);
     512                 :             :                 else
     513                 :           0 :                         desc = ExecBuildSlotValueDescription(relid, searchslot, tupdesc, NULL, 64);
     514                 :             : 
     515         [ #  # ]:           0 :                 if (desc)
     516                 :             :                 {
     517         [ #  # ]:           0 :                         if (OidIsValid(replica_index))
     518                 :           0 :                                 *search_desc = psprintf(_("replica identity %s"), desc);
     519                 :             :                         else
     520                 :           0 :                                 *search_desc = psprintf(_("replica identity full %s"), desc);
     521                 :           0 :                 }
     522                 :           0 :         }
     523                 :           0 : }
     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 *
     533                 :           0 : build_index_value_desc(EState *estate, Relation localrel, TupleTableSlot *slot,
     534                 :             :                                            Oid indexoid)
     535                 :             : {
     536                 :           0 :         char       *index_value;
     537                 :           0 :         Relation        indexDesc;
     538                 :           0 :         Datum           values[INDEX_MAX_KEYS];
     539                 :           0 :         bool            isnull[INDEX_MAX_KEYS];
     540                 :           0 :         TupleTableSlot *tableslot = slot;
     541                 :             : 
     542         [ #  # ]:           0 :         if (!tableslot)
     543                 :           0 :                 return NULL;
     544                 :             : 
     545         [ #  # ]:           0 :         Assert(CheckRelationOidLockedByMe(indexoid, RowExclusiveLock, true));
     546                 :             : 
     547                 :           0 :         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         [ #  # ]:           0 :         if (TTS_IS_VIRTUAL(slot))
     554                 :             :         {
     555                 :           0 :                 tableslot = table_slot_create(localrel, &estate->es_tupleTable);
     556                 :           0 :                 tableslot = ExecCopySlot(tableslot, slot);
     557                 :           0 :         }
     558                 :             : 
     559                 :             :         /*
     560                 :             :          * Initialize ecxt_scantuple for potential use in FormIndexDatum when
     561                 :             :          * index expressions are present.
     562                 :             :          */
     563         [ #  # ]:           0 :         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                 :           0 :         FormIndexDatum(BuildIndexInfo(indexDesc), tableslot, estate, values, isnull);
     571                 :             : 
     572                 :           0 :         index_value = BuildIndexValueDescription(indexDesc, values, isnull);
     573                 :             : 
     574                 :           0 :         index_close(indexDesc, NoLock);
     575                 :             : 
     576                 :           0 :         return index_value;
     577                 :           0 : }
        

Generated by: LCOV version 2.3.2-1