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 : }
|