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