LCOV - code coverage report
Current view: top level - src/backend/replication/logical - conflict.c (source / functions) Coverage Total Hit
Test: PostgreSQL 19devel Lines: 85.5 % 179 153
Test Date: 2026-05-17 10:16:07 Functions: 100.0 % 8 8
Legend: Lines:     hit not hit

            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
      64        72318 : GetTupleTransactionInfo(TupleTableSlot *localslot, TransactionId *xmin,
      65              :                         ReplOriginId *localorigin, TimestampTz *localts)
      66              : {
      67              :     Datum       xminDatum;
      68              :     bool        isnull;
      69              : 
      70        72318 :     xminDatum = slot_getsysattr(localslot, MinTransactionIdAttributeNumber,
      71              :                                 &isnull);
      72        72318 :     *xmin = DatumGetTransactionId(xminDatum);
      73              :     Assert(!isnull);
      74              : 
      75              :     /*
      76              :      * The commit timestamp data is not available if track_commit_timestamp is
      77              :      * disabled.
      78              :      */
      79        72318 :     if (!track_commit_timestamp)
      80              :     {
      81        72254 :         *localorigin = InvalidReplOriginId;
      82        72254 :         *localts = 0;
      83        72254 :         return false;
      84              :     }
      85              : 
      86           64 :     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           69 : ReportApplyConflict(EState *estate, ResultRelInfo *relinfo, int elevel,
     106              :                     ConflictType type, TupleTableSlot *searchslot,
     107              :                     TupleTableSlot *remoteslot, List *conflicttuples)
     108              : {
     109           69 :     Relation    localrel = relinfo->ri_RelationDesc;
     110              :     StringInfoData err_detail;
     111              : 
     112           69 :     initStringInfo(&err_detail);
     113              : 
     114              :     /* Form errdetail message by combining conflicting tuples information. */
     115          240 :     foreach_ptr(ConflictTupleInfo, conflicttuple, conflicttuples)
     116          102 :         errdetail_apply_conflict(estate, relinfo, type, searchslot,
     117              :                                  conflicttuple->slot, remoteslot,
     118              :                                  conflicttuple->indexoid,
     119              :                                  conflicttuple->xmin,
     120          102 :                                  conflicttuple->origin,
     121              :                                  conflicttuple->ts,
     122              :                                  &err_detail);
     123              : 
     124           69 :     pgstat_report_subscription_conflict(MySubscription->oid, type);
     125              : 
     126           69 :     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           32 : }
     134              : 
     135              : /*
     136              :  * Find all unique indexes to check for a conflict and store them into
     137              :  * ResultRelInfo.
     138              :  */
     139              : void
     140       131417 : InitConflictIndexes(ResultRelInfo *relInfo)
     141              : {
     142       131417 :     List       *uniqueIndexes = NIL;
     143              : 
     144       239404 :     for (int i = 0; i < relInfo->ri_NumIndices; i++)
     145              :     {
     146       107987 :         Relation    indexRelation = relInfo->ri_IndexRelationDescs[i];
     147              : 
     148       107987 :         if (indexRelation == NULL)
     149            0 :             continue;
     150              : 
     151              :         /* Detect conflict only for unique indexes */
     152       107987 :         if (!relInfo->ri_IndexRelationInfo[i]->ii_Unique)
     153           29 :             continue;
     154              : 
     155              :         /* Don't support conflict detection for deferrable index */
     156       107958 :         if (!indexRelation->rd_index->indimmediate)
     157            0 :             continue;
     158              : 
     159       107958 :         uniqueIndexes = lappend_oid(uniqueIndexes,
     160              :                                     RelationGetRelid(indexRelation));
     161              :     }
     162              : 
     163       131417 :     relInfo->ri_onConflictArbiterIndexes = uniqueIndexes;
     164       131417 : }
     165              : 
     166              : /*
     167              :  * Add SQLSTATE error code to the current conflict report.
     168              :  */
     169              : static int
     170           69 : errcode_apply_conflict(ConflictType type)
     171              : {
     172           69 :     switch (type)
     173              :     {
     174           37 :         case CT_INSERT_EXISTS:
     175              :         case CT_UPDATE_EXISTS:
     176              :         case CT_MULTIPLE_UNIQUE_CONFLICTS:
     177           37 :             return errcode(ERRCODE_UNIQUE_VIOLATION);
     178           32 :         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:
     183           32 :             return errcode(ERRCODE_T_R_SERIALIZATION_FAILURE);
     184              :     }
     185              : 
     186              :     Assert(false);
     187            0 :     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
     195          139 : append_tuple_value_detail(StringInfo buf, List *tuple_values)
     196              : {
     197          139 :     bool        first = true;
     198              : 
     199              :     Assert(buf != NULL && tuple_values != NIL);
     200              : 
     201          555 :     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          277 :         if (!tuple_value)
     209           40 :             continue;
     210              : 
     211              :         /* standard SQL punctuation, not translated */
     212          237 :         if (!first)
     213           98 :             appendStringInfoString(buf, ", ");
     214              : 
     215          237 :         appendStringInfoString(buf, tuple_value);
     216          237 :         first = false;
     217              :     }
     218          139 : }
     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
     231          102 : 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;
     241          102 :     char       *key_desc = NULL;
     242          102 :     char       *local_desc = NULL;
     243          102 :     char       *remote_desc = NULL;
     244          102 :     char       *search_desc = NULL;
     245              : 
     246              :     /* Get key, replica identity, remote, and local value data */
     247          102 :     get_tuple_desc(estate, relinfo, type, &key_desc,
     248              :                    localslot, &local_desc,
     249              :                    remoteslot, &remote_desc,
     250              :                    searchslot, &search_desc,
     251              :                    indexoid);
     252              : 
     253          102 :     initStringInfo(&err_detail);
     254          102 :     initStringInfo(&tuple_buf);
     255              : 
     256              :     /* Construct a detailed message describing the type of conflict */
     257          102 :     switch (type)
     258              :     {
     259           70 :         case CT_INSERT_EXISTS:
     260              :         case CT_UPDATE_EXISTS:
     261              :         case CT_MULTIPLE_UNIQUE_CONFLICTS:
     262              :             Assert(OidIsValid(indexoid) &&
     263              :                    CheckRelationOidLockedByMe(indexoid, RowExclusiveLock, true));
     264              : 
     265           70 :             if (err_msg->len == 0)
     266              :             {
     267           37 :                 append_tuple_value_detail(&tuple_buf,
     268              :                                           list_make2(remote_desc, search_desc));
     269              : 
     270           37 :                 if (tuple_buf.len)
     271           37 :                     appendStringInfo(&err_detail, _("Could not apply remote change: %s.\n"),
     272              :                                      tuple_buf.data);
     273              :                 else
     274            0 :                     appendStringInfo(&err_detail, _("Could not apply remote change.\n"));
     275              : 
     276              : 
     277           37 :                 resetStringInfo(&tuple_buf);
     278              :             }
     279              : 
     280           70 :             append_tuple_value_detail(&tuple_buf,
     281              :                                       list_make2(key_desc, local_desc));
     282              : 
     283           70 :             if (localts)
     284              :             {
     285            3 :                 if (localorigin == InvalidReplOriginId)
     286              :                 {
     287            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              :                 }
     297            3 :                 else if (replorigin_by_oid(localorigin, true, &origin_name))
     298              :                 {
     299            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
     305            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              :                 {
     319            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
     325            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              :             {
     332           67 :                 if (tuple_buf.len)
     333           67 :                     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
     337            0 :                     appendStringInfo(&err_detail, _("Key already exists in unique index \"%s\", modified in transaction %u."),
     338              :                                      get_rel_name(indexoid), localxmin);
     339              :             }
     340              : 
     341           70 :             break;
     342              : 
     343            3 :         case CT_UPDATE_ORIGIN_DIFFERS:
     344            3 :             append_tuple_value_detail(&tuple_buf,
     345              :                                       list_make3(local_desc, remote_desc,
     346              :                                                  search_desc));
     347              : 
     348            3 :             if (localorigin == InvalidReplOriginId)
     349              :             {
     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
     355            0 :                     appendStringInfo(&err_detail, _("Updating the row that was modified locally in transaction %u at %s."),
     356              :                                      localxmin, timestamptz_to_str(localts));
     357              :             }
     358            2 :             else if (replorigin_by_oid(localorigin, true, &origin_name))
     359              :             {
     360            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
     366            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              :             {
     374            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
     379            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              : 
     383            3 :             break;
     384              : 
     385            3 :         case CT_UPDATE_DELETED:
     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
     393            0 :                 appendStringInfo(&err_detail, _("Could not find the row to be updated.\n"));
     394              : 
     395            3 :             if (localts)
     396              :             {
     397            3 :                 if (localorigin == InvalidReplOriginId)
     398            3 :                     appendStringInfo(&err_detail, _("The row to be updated was deleted locally in transaction %u at %s"),
     399              :                                      localxmin, timestamptz_to_str(localts));
     400            0 :                 else if (replorigin_by_oid(localorigin, true, &origin_name))
     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
     410            0 :                 appendStringInfoString(&err_detail, _("The row to be updated was deleted"));
     411              : 
     412            3 :             break;
     413              : 
     414           12 :         case CT_UPDATE_MISSING:
     415           12 :             append_tuple_value_detail(&tuple_buf,
     416              :                                       list_make2(remote_desc, search_desc));
     417              : 
     418           12 :             if (tuple_buf.len)
     419           12 :                 appendStringInfo(&err_detail, _("Could not find the row to be updated: %s."),
     420              :                                  tuple_buf.data);
     421              :             else
     422            0 :                 appendStringInfo(&err_detail, _("Could not find the row to be updated."));
     423              : 
     424           12 :             break;
     425              : 
     426            5 :         case CT_DELETE_ORIGIN_DIFFERS:
     427            5 :             append_tuple_value_detail(&tuple_buf,
     428              :                                       list_make3(local_desc, remote_desc,
     429              :                                                  search_desc));
     430              : 
     431            5 :             if (localorigin == InvalidReplOriginId)
     432              :             {
     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
     438            0 :                     appendStringInfo(&err_detail, _("Deleting the row that was modified locally in transaction %u at %s."),
     439              :                                      localxmin, timestamptz_to_str(localts));
     440              :             }
     441            1 :             else if (replorigin_by_oid(localorigin, true, &origin_name))
     442              :             {
     443            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
     449            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              : 
     466            5 :             break;
     467              : 
     468            9 :         case CT_DELETE_MISSING:
     469            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
     476            0 :                 appendStringInfo(&err_detail, _("Could not find the row to be deleted."));
     477              : 
     478            9 :             break;
     479              :     }
     480              : 
     481              :     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              :      */
     487          102 :     if (err_msg->len > 0)
     488           33 :         appendStringInfoChar(err_msg, '\n');
     489              : 
     490          102 :     appendStringInfoString(err_msg, err_detail.data);
     491          102 : }
     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
     501          102 : 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              : {
     508          102 :     Relation    localrel = relinfo->ri_RelationDesc;
     509          102 :     Oid         relid = RelationGetRelid(localrel);
     510          102 :     TupleDesc   tupdesc = RelationGetDescr(localrel);
     511          102 :     char       *desc = NULL;
     512              : 
     513              :     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              :      */
     520          102 :     if (type == CT_INSERT_EXISTS || type == CT_UPDATE_EXISTS ||
     521              :         type == CT_MULTIPLE_UNIQUE_CONFLICTS)
     522              :     {
     523              :         Assert(OidIsValid(indexoid) && localslot);
     524              : 
     525           70 :         desc = build_index_value_desc(estate, localrel, localslot,
     526              :                                       indexoid);
     527              : 
     528           70 :         if (desc)
     529           70 :             *key_desc = psprintf(_("key %s"), desc);
     530              :     }
     531              : 
     532          102 :     if (localslot)
     533              :     {
     534              :         /*
     535              :          * The 'modifiedCols' only applies to the new tuple, hence we pass
     536              :          * NULL for the local row.
     537              :          */
     538           78 :         desc = ExecBuildSlotValueDescription(relid, localslot, tupdesc,
     539              :                                              NULL, 64);
     540              : 
     541           78 :         if (desc)
     542           78 :             *local_desc = psprintf(_("local row %s"), desc);
     543              :     }
     544              : 
     545          102 :     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           88 :         modifiedCols = bms_union(ExecGetInsertedCols(relinfo, estate),
     557           88 :                                  ExecGetUpdatedCols(relinfo, estate));
     558           88 :         desc = ExecBuildSlotValueDescription(relid, remoteslot,
     559              :                                              tupdesc, modifiedCols,
     560              :                                              64);
     561              : 
     562           88 :         if (desc)
     563           88 :             *remote_desc = psprintf(_("remote row %s"), desc);
     564              :     }
     565              : 
     566          102 :     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           36 :         Oid         replica_index = GetRelationIdentityOrPK(localrel);
     576              : 
     577              :         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           36 :         if (OidIsValid(replica_index))
     585           32 :             desc = build_index_value_desc(estate, localrel, searchslot, replica_index);
     586              :         else
     587            4 :             desc = ExecBuildSlotValueDescription(relid, searchslot, tupdesc, NULL, 64);
     588              : 
     589           36 :         if (desc)
     590              :         {
     591           36 :             if (OidIsValid(replica_index))
     592           32 :                 *search_desc = psprintf(_("replica identity %s"), desc);
     593              :             else
     594            4 :                 *search_desc = psprintf(_("replica identity full %s"), desc);
     595              :         }
     596              :     }
     597          102 : }
     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 *
     607          102 : 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          102 :     TupleTableSlot *tableslot = slot;
     615              : 
     616          102 :     if (!tableslot)
     617            0 :         return NULL;
     618              : 
     619              :     Assert(CheckRelationOidLockedByMe(indexoid, RowExclusiveLock, true));
     620              : 
     621          102 :     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          102 :     if (TTS_IS_VIRTUAL(slot))
     628              :     {
     629           23 :         tableslot = table_slot_create(localrel, &estate->es_tupleTable);
     630           23 :         tableslot = ExecCopySlot(tableslot, slot);
     631              :     }
     632              : 
     633              :     /*
     634              :      * Initialize ecxt_scantuple for potential use in FormIndexDatum when
     635              :      * index expressions are present.
     636              :      */
     637          102 :     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          102 :     FormIndexDatum(BuildIndexInfo(indexDesc), tableslot, estate, values, isnull);
     645              : 
     646          102 :     index_value = BuildIndexValueDescription(indexDesc, values, isnull);
     647              : 
     648          102 :     index_close(indexDesc, NoLock);
     649              : 
     650          102 :     return index_value;
     651              : }
        

Generated by: LCOV version 2.0-1