Coverage Report

Created: 2026-11-09 07:20

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/postgres/src/backend/replication/logical/relation.c
Line
Count
Source
1
/*-------------------------------------------------------------------------
2
 * relation.c
3
 *     PostgreSQL logical replication relation mapping cache
4
 *
5
 * Copyright (c) 2016-2026, PostgreSQL Global Development Group
6
 *
7
 * IDENTIFICATION
8
 *    src/backend/replication/logical/relation.c
9
 *
10
 * NOTES
11
 *    Routines in this file mainly have to do with mapping the properties
12
 *    of local replication target relations to the properties of their
13
 *    remote counterpart.
14
 *
15
 *-------------------------------------------------------------------------
16
 */
17
18
#include "postgres.h"
19
20
#include "access/amapi.h"
21
#include "access/genam.h"
22
#include "access/table.h"
23
#include "catalog/namespace.h"
24
#include "catalog/pg_subscription_rel.h"
25
#include "executor/executor.h"
26
#include "nodes/makefuncs.h"
27
#include "replication/logicalrelation.h"
28
#include "replication/worker_internal.h"
29
#include "utils/inval.h"
30
#include "utils/lsyscache.h"
31
#include "utils/syscache.h"
32
#include "utils/typcache.h"
33
34
35
static MemoryContext LogicalRepRelMapContext = NULL;
36
37
static HTAB *LogicalRepRelMap = NULL;
38
39
/*
40
 * Partition map (LogicalRepPartMap)
41
 *
42
 * When a partitioned table is used as replication target, replicated
43
 * operations are actually performed on its leaf partitions, which requires
44
 * the partitions to also be mapped to the remote relation.  Parent's entry
45
 * (LogicalRepRelMapEntry) cannot be used as-is for all partitions, because
46
 * individual partitions may have different attribute numbers, which means
47
 * attribute mappings to remote relation's attributes must be maintained
48
 * separately for each partition.
49
 */
50
static MemoryContext LogicalRepPartMapContext = NULL;
51
static HTAB *LogicalRepPartMap = NULL;
52
typedef struct LogicalRepPartMapEntry
53
{
54
  Oid     partoid;    /* LogicalRepPartMap's key */
55
  LogicalRepRelMapEntry relmapentry;
56
} LogicalRepPartMapEntry;
57
58
static Oid  FindLogicalRepLocalIndex(Relation localrel, LogicalRepRelation *remoterel,
59
                   AttrMap *attrMap, bool *idxisreplident);
60
61
/*
62
 * Relcache invalidation callback for our relation map cache.
63
 */
64
static void
65
logicalrep_relmap_invalidate_cb(Datum arg, Oid reloid)
66
0
{
67
0
  LogicalRepRelMapEntry *entry;
68
69
  /* Just to be sure. */
70
0
  if (LogicalRepRelMap == NULL)
71
0
    return;
72
73
0
  if (reloid != InvalidOid)
74
0
  {
75
0
    HASH_SEQ_STATUS status;
76
77
0
    hash_seq_init(&status, LogicalRepRelMap);
78
79
    /* TODO, use inverse lookup hashtable? */
80
0
    while ((entry = (LogicalRepRelMapEntry *) hash_seq_search(&status)) != NULL)
81
0
    {
82
0
      if (entry->localreloid == reloid)
83
0
      {
84
0
        entry->localrelvalid = false;
85
0
        hash_seq_term(&status);
86
0
        break;
87
0
      }
88
0
    }
89
0
  }
90
0
  else
91
0
  {
92
    /* invalidate all cache entries */
93
0
    HASH_SEQ_STATUS status;
94
95
0
    hash_seq_init(&status, LogicalRepRelMap);
96
97
0
    while ((entry = (LogicalRepRelMapEntry *) hash_seq_search(&status)) != NULL)
98
0
      entry->localrelvalid = false;
99
0
  }
100
0
}
101
102
/*
103
 * Initialize the relation map cache.
104
 */
105
static void
106
logicalrep_relmap_init(void)
107
0
{
108
0
  HASHCTL   ctl;
109
110
0
  if (!LogicalRepRelMapContext)
111
0
    LogicalRepRelMapContext =
112
0
      AllocSetContextCreate(CacheMemoryContext,
113
0
                  "LogicalRepRelMapContext",
114
0
                  ALLOCSET_DEFAULT_SIZES);
115
116
  /* Initialize the relation hash table. */
117
0
  ctl.keysize = sizeof(LogicalRepRelId);
118
0
  ctl.entrysize = sizeof(LogicalRepRelMapEntry);
119
0
  ctl.hcxt = LogicalRepRelMapContext;
120
121
0
  LogicalRepRelMap = hash_create("logicalrep relation map cache", 128, &ctl,
122
0
                   HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
123
124
  /* Watch for invalidation events. */
125
0
  CacheRegisterRelcacheCallback(logicalrep_relmap_invalidate_cb,
126
0
                  (Datum) 0);
127
0
}
128
129
/*
130
 * Free the entry of a relation map cache.
131
 */
132
static void
133
logicalrep_relmap_free_entry(LogicalRepRelMapEntry *entry)
134
0
{
135
0
  LogicalRepRelation *remoterel;
136
137
0
  remoterel = &entry->remoterel;
138
139
0
  pfree(remoterel->nspname);
140
0
  pfree(remoterel->relname);
141
142
0
  if (remoterel->natts > 0)
143
0
  {
144
0
    int     i;
145
146
0
    for (i = 0; i < remoterel->natts; i++)
147
0
      pfree(remoterel->attnames[i]);
148
149
0
    pfree(remoterel->attnames);
150
0
    pfree(remoterel->atttyps);
151
0
  }
152
0
  bms_free(remoterel->attkeys);
153
154
0
  if (entry->attrmap)
155
0
    free_attrmap(entry->attrmap);
156
0
}
157
158
/*
159
 * Add new entry or update existing entry in the relation map cache.
160
 *
161
 * Called when new relation mapping is sent by the publisher to update
162
 * our expected view of incoming data from said publisher.
163
 */
164
void
165
logicalrep_relmap_update(LogicalRepRelation *remoterel)
166
0
{
167
0
  MemoryContext oldctx;
168
0
  LogicalRepRelMapEntry *entry;
169
0
  bool    found;
170
0
  int     i;
171
172
0
  if (LogicalRepRelMap == NULL)
173
0
    logicalrep_relmap_init();
174
175
  /*
176
   * HASH_ENTER returns the existing entry if present or creates a new one.
177
   */
178
0
  entry = hash_search(LogicalRepRelMap, &remoterel->remoteid,
179
0
            HASH_ENTER, &found);
180
181
0
  if (found)
182
0
    logicalrep_relmap_free_entry(entry);
183
184
0
  memset(entry, 0, sizeof(LogicalRepRelMapEntry));
185
186
  /* Make cached copy of the data */
187
0
  oldctx = MemoryContextSwitchTo(LogicalRepRelMapContext);
188
0
  entry->remoterel.remoteid = remoterel->remoteid;
189
0
  entry->remoterel.nspname = pstrdup(remoterel->nspname);
190
0
  entry->remoterel.relname = pstrdup(remoterel->relname);
191
0
  entry->remoterel.natts = remoterel->natts;
192
0
  entry->remoterel.attnames = palloc_array(char *, remoterel->natts);
193
0
  entry->remoterel.atttyps = palloc_array(Oid, remoterel->natts);
194
0
  for (i = 0; i < remoterel->natts; i++)
195
0
  {
196
0
    entry->remoterel.attnames[i] = pstrdup(remoterel->attnames[i]);
197
0
    entry->remoterel.atttyps[i] = remoterel->atttyps[i];
198
0
  }
199
0
  entry->remoterel.replident = remoterel->replident;
200
201
  /*
202
   * XXX The walsender currently does not transmit the relkind of the remote
203
   * relation when replicating changes. Since we support replicating only
204
   * table changes at present, we default to initializing relkind as
205
   * RELKIND_RELATION. This is needed in CheckSubscriptionRelkind() to check
206
   * if the publisher and subscriber relation kinds are compatible.
207
   */
208
0
  entry->remoterel.relkind =
209
0
    (remoterel->relkind == 0) ? RELKIND_RELATION : remoterel->relkind;
210
211
0
  entry->remoterel.attkeys = bms_copy(remoterel->attkeys);
212
0
  MemoryContextSwitchTo(oldctx);
213
0
}
214
215
/*
216
 * Find attribute index in TupleDesc struct by attribute name.
217
 *
218
 * Returns -1 if not found.
219
 */
220
static int
221
logicalrep_rel_att_by_name(LogicalRepRelation *remoterel, const char *attname)
222
0
{
223
0
  int     i;
224
225
0
  for (i = 0; i < remoterel->natts; i++)
226
0
  {
227
0
    if (strcmp(remoterel->attnames[i], attname) == 0)
228
0
      return i;
229
0
  }
230
231
0
  return -1;
232
0
}
233
234
/*
235
 * Returns a comma-separated string of attribute names based on the provided
236
 * relation and bitmap indicating which attributes to include.
237
 */
238
static char *
239
logicalrep_get_attrs_str(LogicalRepRelation *remoterel, Bitmapset *atts)
240
0
{
241
0
  StringInfoData attsbuf;
242
0
  bool    first = true;
243
0
  int     i = -1;
244
245
0
  Assert(!bms_is_empty(atts));
246
247
0
  initStringInfo(&attsbuf);
248
249
0
  while ((i = bms_next_member(atts, i)) >= 0)
250
0
  {
251
0
    if (first)
252
0
      appendStringInfo(&attsbuf, _("\"%s\""), remoterel->attnames[i]);
253
0
    else
254
0
      appendStringInfo(&attsbuf, _(", \"%s\""), remoterel->attnames[i]);
255
0
    first = false;
256
0
  }
257
258
0
  return attsbuf.data;
259
0
}
260
261
/*
262
 * If attempting to replicate missing or generated columns, report an error.
263
 * Prioritize 'missing' errors if both occur though the prioritization is
264
 * arbitrary.
265
 */
266
static void
267
logicalrep_report_missing_or_gen_attrs(LogicalRepRelation *remoterel,
268
                     Bitmapset *missingatts,
269
                     Bitmapset *generatedatts)
270
0
{
271
0
  if (!bms_is_empty(missingatts))
272
0
    ereport(ERROR,
273
0
        errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
274
0
        errmsg_plural("logical replication target relation \"%s.%s\" is missing replicated column: %s",
275
0
                "logical replication target relation \"%s.%s\" is missing replicated columns: %s",
276
0
                bms_num_members(missingatts),
277
0
                remoterel->nspname,
278
0
                remoterel->relname,
279
0
                logicalrep_get_attrs_str(remoterel,
280
0
                             missingatts)));
281
282
0
  if (!bms_is_empty(generatedatts))
283
0
    ereport(ERROR,
284
0
        errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
285
0
        errmsg_plural("logical replication target relation \"%s.%s\" has incompatible generated column: %s",
286
0
                "logical replication target relation \"%s.%s\" has incompatible generated columns: %s",
287
0
                bms_num_members(generatedatts),
288
0
                remoterel->nspname,
289
0
                remoterel->relname,
290
0
                logicalrep_get_attrs_str(remoterel,
291
0
                             generatedatts)));
292
0
}
293
294
/*
295
 * Check if replica identity matches and mark the updatable flag.
296
 *
297
 * We allow for stricter replica identity (fewer columns) on subscriber as
298
 * that will not stop us from finding unique tuple. IE, if publisher has
299
 * identity (id,timestamp) and subscriber just (id) this will not be a
300
 * problem, but in the opposite scenario it will.
301
 *
302
 * We just mark the relation entry as not updatable here if the local
303
 * replica identity is found to be insufficient for applying
304
 * updates/deletes (inserts don't care!) and leave it to
305
 * check_relation_updatable() to throw the actual error if needed.
306
 */
307
static void
308
logicalrep_rel_mark_updatable(LogicalRepRelMapEntry *entry)
309
0
{
310
0
  Bitmapset  *idkey;
311
0
  LogicalRepRelation *remoterel = &entry->remoterel;
312
0
  int     i;
313
314
0
  entry->updatable = true;
315
316
0
  idkey = RelationGetIndexAttrBitmap(entry->localrel,
317
0
                     INDEX_ATTR_BITMAP_IDENTITY_KEY);
318
  /* fallback to PK if no replica identity */
319
0
  if (idkey == NULL)
320
0
  {
321
0
    idkey = RelationGetIndexAttrBitmap(entry->localrel,
322
0
                       INDEX_ATTR_BITMAP_PRIMARY_KEY);
323
324
    /*
325
     * If no replica identity index and no PK, the published table must
326
     * have replica identity FULL.
327
     */
328
0
    if (idkey == NULL && remoterel->replident != REPLICA_IDENTITY_FULL)
329
0
      entry->updatable = false;
330
0
  }
331
332
0
  i = -1;
333
0
  while ((i = bms_next_member(idkey, i)) >= 0)
334
0
  {
335
0
    int     attnum = i + FirstLowInvalidHeapAttributeNumber;
336
337
0
    if (!AttrNumberIsForUserDefinedAttr(attnum))
338
0
      ereport(ERROR,
339
0
          (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
340
0
           errmsg("logical replication target relation \"%s.%s\" uses "
341
0
              "system columns in REPLICA IDENTITY index",
342
0
              remoterel->nspname, remoterel->relname)));
343
344
0
    attnum = AttrNumberGetAttrOffset(attnum);
345
346
0
    if (entry->attrmap->attnums[attnum] < 0 ||
347
0
      !bms_is_member(entry->attrmap->attnums[attnum], remoterel->attkeys))
348
0
    {
349
0
      entry->updatable = false;
350
0
      break;
351
0
    }
352
0
  }
353
0
}
354
355
/*
356
 * Open the local relation associated with the remote one.
357
 *
358
 * Rebuilds the Relcache mapping if it was invalidated by local DDL.
359
 */
360
LogicalRepRelMapEntry *
361
logicalrep_rel_open(LogicalRepRelId remoteid, LOCKMODE lockmode)
362
0
{
363
0
  LogicalRepRelMapEntry *entry;
364
0
  bool    found;
365
0
  LogicalRepRelation *remoterel;
366
367
0
  if (LogicalRepRelMap == NULL)
368
0
    logicalrep_relmap_init();
369
370
  /* Search for existing entry. */
371
0
  entry = hash_search(LogicalRepRelMap, &remoteid,
372
0
            HASH_FIND, &found);
373
374
0
  if (!found)
375
0
    elog(ERROR, "no relation map entry for remote relation ID %u",
376
0
       remoteid);
377
378
0
  remoterel = &entry->remoterel;
379
380
  /* Ensure we don't leak a relcache refcount. */
381
0
  if (entry->localrel)
382
0
    elog(ERROR, "remote relation ID %u is already open", remoteid);
383
384
  /*
385
   * When opening and locking a relation, pending invalidation messages are
386
   * processed which can invalidate the relation.  Hence, if the entry is
387
   * currently considered valid, try to open the local relation by OID and
388
   * see if invalidation ensues.
389
   */
390
0
  if (entry->localrelvalid)
391
0
  {
392
0
    entry->localrel = try_table_open(entry->localreloid, lockmode);
393
0
    if (!entry->localrel)
394
0
    {
395
      /* Table was renamed or dropped. */
396
0
      entry->localrelvalid = false;
397
0
    }
398
0
    else if (!entry->localrelvalid)
399
0
    {
400
      /* Note we release the no-longer-useful lock here. */
401
0
      table_close(entry->localrel, lockmode);
402
0
      entry->localrel = NULL;
403
0
    }
404
0
  }
405
406
  /*
407
   * If the entry has been marked invalid since we last had lock on it,
408
   * re-open the local relation by name and rebuild all derived data.
409
   */
410
0
  if (!entry->localrelvalid)
411
0
  {
412
0
    Oid     relid;
413
0
    TupleDesc desc;
414
0
    MemoryContext oldctx;
415
0
    int     i;
416
0
    Bitmapset  *missingatts;
417
0
    Bitmapset  *generatedattrs = NULL;
418
419
    /* Release the no-longer-useful attrmap, if any. */
420
0
    if (entry->attrmap)
421
0
    {
422
0
      free_attrmap(entry->attrmap);
423
0
      entry->attrmap = NULL;
424
0
    }
425
426
    /* Try to find and lock the relation by name. */
427
0
    relid = RangeVarGetRelid(makeRangeVar(remoterel->nspname,
428
0
                        remoterel->relname, -1),
429
0
                 lockmode, true);
430
0
    if (!OidIsValid(relid))
431
0
      ereport(ERROR,
432
0
          (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
433
0
           errmsg("logical replication target relation \"%s.%s\" does not exist",
434
0
              remoterel->nspname, remoterel->relname)));
435
0
    entry->localrel = table_open(relid, NoLock);
436
0
    entry->localreloid = relid;
437
438
    /* Check for supported relkind. */
439
0
    CheckSubscriptionRelkind(entry->localrel->rd_rel->relkind,
440
0
                 remoterel->relkind,
441
0
                 remoterel->nspname, remoterel->relname);
442
443
    /*
444
     * Build the mapping of local attribute numbers to remote attribute
445
     * numbers and validate that we don't miss any replicated columns as
446
     * that would result in potentially unwanted data loss.
447
     */
448
0
    desc = RelationGetDescr(entry->localrel);
449
0
    oldctx = MemoryContextSwitchTo(LogicalRepRelMapContext);
450
0
    entry->attrmap = make_attrmap(desc->natts);
451
0
    MemoryContextSwitchTo(oldctx);
452
453
    /* check and report missing attrs, if any */
454
0
    missingatts = bms_add_range(NULL, 0, remoterel->natts - 1);
455
0
    for (i = 0; i < desc->natts; i++)
456
0
    {
457
0
      int     attnum;
458
0
      Form_pg_attribute attr = TupleDescAttr(desc, i);
459
460
0
      if (attr->attisdropped)
461
0
      {
462
0
        entry->attrmap->attnums[i] = -1;
463
0
        continue;
464
0
      }
465
466
0
      attnum = logicalrep_rel_att_by_name(remoterel,
467
0
                        NameStr(attr->attname));
468
469
0
      entry->attrmap->attnums[i] = attnum;
470
0
      if (attnum >= 0)
471
0
      {
472
        /* Remember which subscriber columns are generated. */
473
0
        if (attr->attgenerated)
474
0
          generatedattrs = bms_add_member(generatedattrs, attnum);
475
476
0
        missingatts = bms_del_member(missingatts, attnum);
477
0
      }
478
0
    }
479
480
0
    logicalrep_report_missing_or_gen_attrs(remoterel, missingatts,
481
0
                         generatedattrs);
482
483
    /* be tidy */
484
0
    bms_free(generatedattrs);
485
0
    bms_free(missingatts);
486
487
    /*
488
     * Set if the table's replica identity is enough to apply
489
     * update/delete.
490
     */
491
0
    logicalrep_rel_mark_updatable(entry);
492
493
    /*
494
     * Finding a usable index is an infrequent task. It occurs when an
495
     * operation is first performed on the relation, or after invalidation
496
     * of the relation cache entry (such as ANALYZE or CREATE/DROP index
497
     * on the relation).
498
     */
499
0
    entry->localindexoid = FindLogicalRepLocalIndex(entry->localrel, remoterel,
500
0
                            entry->attrmap,
501
0
                            &entry->idxisreplident);
502
503
0
    entry->localrelvalid = true;
504
0
  }
505
506
0
  if (entry->state != SUBREL_STATE_READY)
507
0
    entry->state = GetSubscriptionRelState(MySubscription->oid,
508
0
                         entry->localreloid,
509
0
                         &entry->statelsn);
510
511
0
  return entry;
512
0
}
513
514
/*
515
 * Close the previously opened logical relation.
516
 */
517
void
518
logicalrep_rel_close(LogicalRepRelMapEntry *rel, LOCKMODE lockmode)
519
0
{
520
0
  table_close(rel->localrel, lockmode);
521
0
  rel->localrel = NULL;
522
0
}
523
524
/*
525
 * Partition cache: look up partition LogicalRepRelMapEntry's
526
 *
527
 * Unlike relation map cache, this is keyed by partition OID, not remote
528
 * relation OID, because we only have to use this cache in the case where
529
 * partitions are not directly mapped to any remote relation, such as when
530
 * replication is occurring with one of their ancestors as target.
531
 */
532
533
/*
534
 * Relcache invalidation callback
535
 */
536
static void
537
logicalrep_partmap_invalidate_cb(Datum arg, Oid reloid)
538
0
{
539
0
  LogicalRepPartMapEntry *entry;
540
541
  /* Just to be sure. */
542
0
  if (LogicalRepPartMap == NULL)
543
0
    return;
544
545
0
  if (reloid != InvalidOid)
546
0
  {
547
    /*
548
     * LogicalRepPartMap is keyed by partition OID, matching with
549
     * entry->relmapentry.localreloid (see logicalrep_partition_open), so
550
     * we can invalidate via a direct hash lookup.
551
     */
552
0
    entry = hash_search(LogicalRepPartMap, &reloid, HASH_FIND, NULL);
553
0
    if (entry != NULL)
554
0
      entry->relmapentry.localrelvalid = false;
555
0
  }
556
0
  else
557
0
  {
558
    /* invalidate all cache entries */
559
0
    HASH_SEQ_STATUS status;
560
561
0
    hash_seq_init(&status, LogicalRepPartMap);
562
563
0
    while ((entry = (LogicalRepPartMapEntry *) hash_seq_search(&status)) != NULL)
564
0
      entry->relmapentry.localrelvalid = false;
565
0
  }
566
0
}
567
568
/*
569
 * Reset the entries in the partition map that refer to remoterel.
570
 *
571
 * Called when new relation mapping is sent by the publisher to update our
572
 * expected view of incoming data from said publisher.
573
 *
574
 * Note that we don't update the remoterel information in the entry here,
575
 * we will update the information in logicalrep_partition_open to avoid
576
 * unnecessary work.
577
 */
578
void
579
logicalrep_partmap_reset_relmap(LogicalRepRelation *remoterel)
580
0
{
581
0
  HASH_SEQ_STATUS status;
582
0
  LogicalRepPartMapEntry *part_entry;
583
0
  LogicalRepRelMapEntry *entry;
584
585
0
  if (LogicalRepPartMap == NULL)
586
0
    return;
587
588
0
  hash_seq_init(&status, LogicalRepPartMap);
589
0
  while ((part_entry = (LogicalRepPartMapEntry *) hash_seq_search(&status)) != NULL)
590
0
  {
591
0
    entry = &part_entry->relmapentry;
592
593
0
    if (entry->remoterel.remoteid != remoterel->remoteid)
594
0
      continue;
595
596
0
    logicalrep_relmap_free_entry(entry);
597
598
0
    memset(entry, 0, sizeof(LogicalRepRelMapEntry));
599
0
  }
600
0
}
601
602
/*
603
 * Initialize the partition map cache.
604
 */
605
static void
606
logicalrep_partmap_init(void)
607
0
{
608
0
  HASHCTL   ctl;
609
610
0
  if (!LogicalRepPartMapContext)
611
0
    LogicalRepPartMapContext =
612
0
      AllocSetContextCreate(CacheMemoryContext,
613
0
                  "LogicalRepPartMapContext",
614
0
                  ALLOCSET_DEFAULT_SIZES);
615
616
  /* Initialize the relation hash table. */
617
0
  ctl.keysize = sizeof(Oid);  /* partition OID */
618
0
  ctl.entrysize = sizeof(LogicalRepPartMapEntry);
619
0
  ctl.hcxt = LogicalRepPartMapContext;
620
621
0
  LogicalRepPartMap = hash_create("logicalrep partition map cache", 64, &ctl,
622
0
                  HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
623
624
  /* Watch for invalidation events. */
625
0
  CacheRegisterRelcacheCallback(logicalrep_partmap_invalidate_cb,
626
0
                  (Datum) 0);
627
0
}
628
629
/*
630
 * logicalrep_partition_open
631
 *
632
 * Returned entry reuses most of the values of the root table's entry, save
633
 * the attribute map, which can be different for the partition.  However,
634
 * we must physically copy all the data, in case the root table's entry
635
 * gets freed/rebuilt.
636
 *
637
 * Note there's no logicalrep_partition_close, because the caller closes the
638
 * component relation.
639
 */
640
LogicalRepRelMapEntry *
641
logicalrep_partition_open(LogicalRepRelMapEntry *root,
642
              Relation partrel, AttrMap *map)
643
0
{
644
0
  LogicalRepRelMapEntry *entry;
645
0
  LogicalRepPartMapEntry *part_entry;
646
0
  LogicalRepRelation *remoterel = &root->remoterel;
647
0
  Oid     partOid = RelationGetRelid(partrel);
648
0
  AttrMap    *attrmap = root->attrmap;
649
0
  bool    found;
650
0
  MemoryContext oldctx;
651
652
0
  if (LogicalRepPartMap == NULL)
653
0
    logicalrep_partmap_init();
654
655
  /* Search for existing entry. */
656
0
  part_entry = (LogicalRepPartMapEntry *) hash_search(LogicalRepPartMap,
657
0
                            &partOid,
658
0
                            HASH_ENTER, &found);
659
660
0
  entry = &part_entry->relmapentry;
661
662
  /*
663
   * We must always overwrite entry->localrel with the latest partition
664
   * Relation pointer, because the Relation pointed to by the old value may
665
   * have been cleared after the caller would have closed the partition
666
   * relation after the last use of this entry.  Note that localrelvalid is
667
   * only updated by the relcache invalidation callback, so it may still be
668
   * true irrespective of whether the Relation pointed to by localrel has
669
   * been cleared or not.
670
   */
671
0
  if (found && entry->localrelvalid)
672
0
  {
673
0
    Assert(entry->localreloid == partOid);
674
0
    entry->localrel = partrel;
675
0
    return entry;
676
0
  }
677
678
  /* Switch to longer-lived context. */
679
0
  oldctx = MemoryContextSwitchTo(LogicalRepPartMapContext);
680
681
0
  if (!found)
682
0
  {
683
0
    memset(part_entry, 0, sizeof(LogicalRepPartMapEntry));
684
0
    part_entry->partoid = partOid;
685
0
  }
686
687
  /* Release the no-longer-useful attrmap, if any. */
688
0
  if (entry->attrmap)
689
0
  {
690
0
    free_attrmap(entry->attrmap);
691
0
    entry->attrmap = NULL;
692
0
  }
693
694
0
  if (!entry->remoterel.remoteid)
695
0
  {
696
0
    int     i;
697
698
    /* Remote relation is copied as-is from the root entry. */
699
0
    entry->remoterel.remoteid = remoterel->remoteid;
700
0
    entry->remoterel.nspname = pstrdup(remoterel->nspname);
701
0
    entry->remoterel.relname = pstrdup(remoterel->relname);
702
0
    entry->remoterel.natts = remoterel->natts;
703
0
    entry->remoterel.attnames = palloc_array(char *, remoterel->natts);
704
0
    entry->remoterel.atttyps = palloc_array(Oid, remoterel->natts);
705
0
    for (i = 0; i < remoterel->natts; i++)
706
0
    {
707
0
      entry->remoterel.attnames[i] = pstrdup(remoterel->attnames[i]);
708
0
      entry->remoterel.atttyps[i] = remoterel->atttyps[i];
709
0
    }
710
0
    entry->remoterel.replident = remoterel->replident;
711
0
    entry->remoterel.attkeys = bms_copy(remoterel->attkeys);
712
0
  }
713
714
0
  entry->localrel = partrel;
715
0
  entry->localreloid = partOid;
716
717
  /*
718
   * If the partition's attributes don't match the root relation's, we'll
719
   * need to make a new attrmap which maps partition attribute numbers to
720
   * remoterel's, instead of the original which maps root relation's
721
   * attribute numbers to remoterel's.
722
   *
723
   * Note that 'map' which comes from the tuple routing data structure
724
   * contains 1-based attribute numbers (of the parent relation).  However,
725
   * the map in 'entry', a logical replication data structure, contains
726
   * 0-based attribute numbers (of the remote relation).
727
   */
728
0
  if (map)
729
0
  {
730
0
    AttrNumber  attno;
731
732
0
    entry->attrmap = make_attrmap(map->maplen);
733
0
    for (attno = 0; attno < entry->attrmap->maplen; attno++)
734
0
    {
735
0
      AttrNumber  root_attno = map->attnums[attno];
736
737
      /* 0 means it's a dropped attribute.  See comments atop AttrMap. */
738
0
      if (root_attno == 0)
739
0
        entry->attrmap->attnums[attno] = -1;
740
0
      else
741
0
        entry->attrmap->attnums[attno] = attrmap->attnums[root_attno - 1];
742
0
    }
743
0
  }
744
0
  else
745
0
  {
746
    /* Lacking copy_attmap, do this the hard way. */
747
0
    entry->attrmap = make_attrmap(attrmap->maplen);
748
0
    memcpy(entry->attrmap->attnums, attrmap->attnums,
749
0
         attrmap->maplen * sizeof(AttrNumber));
750
0
  }
751
752
  /* Set if the table's replica identity is enough to apply update/delete. */
753
0
  logicalrep_rel_mark_updatable(entry);
754
755
  /* state and statelsn are left set to 0. */
756
0
  MemoryContextSwitchTo(oldctx);
757
758
  /*
759
   * Finding a usable index is an infrequent task. It occurs when an
760
   * operation is first performed on the relation, or after invalidation of
761
   * the relation cache entry (such as ANALYZE or CREATE/DROP index on the
762
   * relation).
763
   *
764
   * We also prefer to run this code on the oldctx so that we do not leak
765
   * anything in the LogicalRepPartMapContext (hence CacheMemoryContext).
766
   */
767
0
  entry->localindexoid = FindLogicalRepLocalIndex(partrel, remoterel,
768
0
                          entry->attrmap,
769
0
                          &entry->idxisreplident);
770
771
0
  entry->localrelvalid = true;
772
773
0
  return entry;
774
0
}
775
776
/*
777
 * Returns the oid of an index that can be used by the apply worker to scan
778
 * the relation.
779
 *
780
 * We expect to call this function when REPLICA IDENTITY FULL is defined for
781
 * the remote relation.
782
 *
783
 * If no suitable index is found, returns InvalidOid.
784
 */
785
static Oid
786
FindUsableIndexForReplicaIdentityFull(Relation localrel, AttrMap *attrmap)
787
0
{
788
0
  List     *idxlist = RelationGetIndexList(localrel);
789
790
0
  foreach_oid(idxoid, idxlist)
791
0
  {
792
0
    bool    isUsableIdx;
793
0
    Relation  idxRel;
794
795
0
    idxRel = index_open(idxoid, AccessShareLock);
796
797
    /*
798
     * indisvalid is checked here, not in
799
     * IsIndexUsableForReplicaIdentityFull(), since that function's other
800
     * caller (an assertion) must tolerate an index made transiently
801
     * invalid by a concurrent DROP INDEX CONCURRENTLY, whereas a
802
     * permanently invalid leftover of a failed CREATE INDEX CONCURRENTLY
803
     * must never be chosen here.
804
     */
805
0
    isUsableIdx = idxRel->rd_index->indisvalid &&
806
0
      IsIndexUsableForReplicaIdentityFull(idxRel, attrmap);
807
0
    index_close(idxRel, AccessShareLock);
808
809
    /* Return the first eligible index found */
810
0
    if (isUsableIdx)
811
0
      return idxoid;
812
0
  }
813
814
0
  return InvalidOid;
815
0
}
816
817
/*
818
 * Returns true if the index is usable for replica identity full.
819
 *
820
 * The index must have an equal strategy for each key column, be non-partial,
821
 * and the leftmost field must be a column (not an expression) that references
822
 * the remote relation column. These limitations help to keep the index scan
823
 * similar to PK/RI index scans.
824
 *
825
 * attrmap is a map of local attributes to remote ones. We can consult this
826
 * map to check whether the local index attribute has a corresponding remote
827
 * attribute.
828
 *
829
 * Note that this function does not check indisvalid. Callers that are
830
 * selecting an index to use for future lookups must check indisvalid
831
 * themselves and reject invalid indexes.
832
 *
833
 * Note that the limitations of index scans for replica identity full only
834
 * adheres to a subset of the limitations of PK/RI. For example, we support
835
 * columns that are marked as [NULL] or we are not interested in the [NOT
836
 * DEFERRABLE] aspect of constraints here. It works for us because we always
837
 * compare the tuples for non-PK/RI index scans. See
838
 * RelationFindReplTupleByIndex().
839
 *
840
 * XXX: To support partial indexes, the required changes are likely to be larger.
841
 * If none of the tuples satisfy the expression for the index scan, we fall-back
842
 * to sequential execution, which might not be a good idea in some cases.
843
 */
844
bool
845
IsIndexUsableForReplicaIdentityFull(Relation idxrel, AttrMap *attrmap)
846
0
{
847
0
  AttrNumber  keycol;
848
0
  oidvector  *indclass;
849
850
  /* The index must not be a partial index */
851
0
  if (!heap_attisnull(idxrel->rd_indextuple, Anum_pg_index_indpred, NULL))
852
0
    return false;
853
854
0
  Assert(idxrel->rd_index->indnatts >= 1);
855
856
0
  indclass = (oidvector *) DatumGetPointer(SysCacheGetAttrNotNull(INDEXRELID,
857
0
                                  idxrel->rd_indextuple,
858
0
                                  Anum_pg_index_indclass));
859
860
  /* Ensure that the index has a valid equal strategy for each key column */
861
0
  for (int i = 0; i < idxrel->rd_index->indnkeyatts; i++)
862
0
  {
863
0
    Oid     opfamily;
864
865
0
    opfamily = get_opclass_family(indclass->values[i]);
866
0
    if (IndexAmTranslateCompareType(COMPARE_EQ, idxrel->rd_rel->relam, opfamily, true) == InvalidStrategy)
867
0
      return false;
868
0
  }
869
870
  /*
871
   * For indexes other than PK and REPLICA IDENTITY, we need to match the
872
   * local and remote tuples.  The equality routine tuples_equal() cannot
873
   * accept a data type where the type cache cannot provide an equality
874
   * operator.
875
   */
876
0
  for (int i = 0; i < idxrel->rd_att->natts; i++)
877
0
  {
878
0
    TypeCacheEntry *typentry;
879
880
0
    typentry = lookup_type_cache(TupleDescAttr(idxrel->rd_att, i)->atttypid, TYPECACHE_EQ_OPR_FINFO);
881
0
    if (!OidIsValid(typentry->eq_opr_finfo.fn_oid))
882
0
      return false;
883
0
  }
884
885
  /* The leftmost index field must not be an expression */
886
0
  keycol = idxrel->rd_index->indkey.values[0];
887
0
  if (!AttributeNumberIsValid(keycol))
888
0
    return false;
889
890
  /*
891
   * And the leftmost index field must reference the remote relation column.
892
   * This is because if it doesn't, the sequential scan is favorable over
893
   * index scan in most cases.
894
   */
895
0
  if (attrmap->maplen <= AttrNumberGetAttrOffset(keycol) ||
896
0
    attrmap->attnums[AttrNumberGetAttrOffset(keycol)] < 0)
897
0
    return false;
898
899
  /*
900
   * The given index access method must implement "amgettuple", which will
901
   * be used later to fetch the tuples.  See RelationFindReplTupleByIndex().
902
   */
903
0
  if (GetIndexAmRoutineByAmId(idxrel->rd_rel->relam, false)->amgettuple == NULL)
904
0
    return false;
905
906
0
  return true;
907
0
}
908
909
/*
910
 * Return the OID of the replica identity index if one is defined;
911
 * the OID of the PK if one exists and is not deferrable;
912
 * otherwise, InvalidOid.
913
 */
914
Oid
915
GetRelationIdentityOrPK(Relation rel)
916
0
{
917
0
  Oid     idxoid;
918
919
0
  idxoid = RelationGetReplicaIndex(rel);
920
921
0
  if (!OidIsValid(idxoid))
922
0
    idxoid = RelationGetPrimaryKeyIndex(rel, false);
923
924
0
  return idxoid;
925
0
}
926
927
/*
928
 * Returns the index oid if we can use an index for subscriber. Otherwise,
929
 * returns InvalidOid.
930
 *
931
 * '*idxisreplident' is true if the returned index is the relation's replica
932
 * identity or primary key, and false otherwise.
933
 */
934
static Oid
935
FindLogicalRepLocalIndex(Relation localrel, LogicalRepRelation *remoterel,
936
             AttrMap *attrMap, bool *idxisreplident)
937
0
{
938
0
  Oid     idxoid;
939
940
0
  *idxisreplident = false;
941
942
  /*
943
   * We never need index oid for partitioned tables, always rely on leaf
944
   * partition's index.
945
   */
946
0
  if (localrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
947
0
    return InvalidOid;
948
949
  /*
950
   * Simple case, we already have a primary key or a replica identity index.
951
   */
952
0
  idxoid = GetRelationIdentityOrPK(localrel);
953
0
  if (OidIsValid(idxoid))
954
0
  {
955
0
    *idxisreplident = true;
956
0
    return idxoid;
957
0
  }
958
959
0
  if (remoterel->replident == REPLICA_IDENTITY_FULL)
960
0
  {
961
    /*
962
     * We are looking for one more opportunity for using an index. If
963
     * there are any indexes defined on the local relation, try to pick a
964
     * suitable index.
965
     *
966
     * The index selection safely assumes that all the columns are going
967
     * to be available for the index scan given that remote relation has
968
     * replica identity full.
969
     *
970
     * Note that we are not using the planner to find the cheapest method
971
     * to scan the relation as that would require us to either use lower
972
     * level planner functions which would be a maintenance burden in the
973
     * long run or use the full-fledged planner which could cause
974
     * overhead.
975
     */
976
0
    return FindUsableIndexForReplicaIdentityFull(localrel, attrMap);
977
0
  }
978
979
0
  return InvalidOid;
980
0
}