Age Owner Branch data TLA Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : *
3 : : * execReplication.c
4 : : * miscellaneous executor routines for logical replication
5 : : *
6 : : * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
7 : : * Portions Copyright (c) 1994, Regents of the University of California
8 : : *
9 : : * IDENTIFICATION
10 : : * src/backend/executor/execReplication.c
11 : : *
12 : : *-------------------------------------------------------------------------
13 : : */
14 : :
15 : : #include "postgres.h"
16 : :
17 : : #include "access/genam.h"
18 : : #include "access/relscan.h"
19 : : #include "access/tableam.h"
20 : : #include "access/transam.h"
21 : : #include "access/xact.h"
22 : : #include "catalog/pg_am_d.h"
23 : : #include "commands/trigger.h"
24 : : #include "executor/executor.h"
25 : : #include "executor/nodeModifyTable.h"
26 : : #include "replication/logicalrelation.h"
27 : : #include "storage/lmgr.h"
28 : : #include "utils/builtins.h"
29 : : #include "utils/lsyscache.h"
30 : : #include "utils/rel.h"
31 : : #include "utils/snapmgr.h"
32 : : #include "utils/syscache.h"
33 : : #include "utils/typcache.h"
34 : :
35 : :
36 : : static bool tuples_equal(TupleTableSlot *slot1, TupleTableSlot *slot2,
37 : : TypeCacheEntry **eq);
38 : :
39 : : /*
40 : : * Returns the fixed strategy number, if any, of the equality operator for the
41 : : * given index access method, otherwise, InvalidStrategy.
42 : : *
43 : : * Currently, only Btree and Hash indexes are supported. The other index access
44 : : * methods don't have a fixed strategy for equality operation - instead, the
45 : : * support routines of each operator class interpret the strategy numbers
46 : : * according to the operator class's definition.
47 : : */
48 : : StrategyNumber
275 akapila@postgresql.o 49 :GNC 144227 : get_equal_strategy_number_for_am(Oid am)
50 : : {
51 : : int ret;
52 : :
53 [ + + - ]: 144227 : switch (am)
54 : : {
55 : 144217 : case BTREE_AM_OID:
56 : 144217 : ret = BTEqualStrategyNumber;
57 : 144217 : break;
58 : 10 : case HASH_AM_OID:
59 : 10 : ret = HTEqualStrategyNumber;
60 : 10 : break;
275 akapila@postgresql.o 61 :UNC 0 : default:
62 : : /* XXX: Only Btree and Hash indexes are supported */
63 : 0 : ret = InvalidStrategy;
64 : 0 : break;
65 : : }
66 : :
275 akapila@postgresql.o 67 :GNC 144227 : return ret;
68 : : }
69 : :
70 : : /*
71 : : * Return the appropriate strategy number which corresponds to the equality
72 : : * operator.
73 : : */
74 : : static StrategyNumber
75 : 72110 : get_equal_strategy_number(Oid opclass)
76 : : {
77 : 72110 : Oid am = get_opclass_method(opclass);
78 : :
79 : 72110 : return get_equal_strategy_number_for_am(am);
80 : : }
81 : :
82 : : /*
83 : : * Setup a ScanKey for a search in the relation 'rel' for a tuple 'key' that
84 : : * is setup to match 'rel' (*NOT* idxrel!).
85 : : *
86 : : * Returns how many columns to use for the index scan.
87 : : *
88 : : * This is not generic routine, idxrel must be PK, RI, or an index that can be
89 : : * used for REPLICA IDENTITY FULL table. See FindUsableIndexForReplicaIdentityFull()
90 : : * for details.
91 : : *
92 : : * By definition, replication identity of a rel meets all limitations associated
93 : : * with that. Note that any other index could also meet these limitations.
94 : : */
95 : : static int
2642 peter_e@gmx.net 96 :CBC 72099 : build_replindex_scan_key(ScanKey skey, Relation rel, Relation idxrel,
97 : : TupleTableSlot *searchslot)
98 : : {
99 : : int index_attoff;
396 akapila@postgresql.o 100 : 72099 : int skey_attoff = 0;
101 : : Datum indclassDatum;
102 : : oidvector *opclass;
2642 peter_e@gmx.net 103 : 72099 : int2vector *indkey = &idxrel->rd_index->indkey;
104 : :
386 dgustafsson@postgres 105 : 72099 : indclassDatum = SysCacheGetAttrNotNull(INDEXRELID, idxrel->rd_indextuple,
106 : : Anum_pg_index_indclass);
2642 peter_e@gmx.net 107 : 72099 : opclass = (oidvector *) DatumGetPointer(indclassDatum);
108 : :
109 : : /* Build scankey for every non-expression attribute in the index. */
396 akapila@postgresql.o 110 [ + + ]: 144211 : for (index_attoff = 0; index_attoff < IndexRelationGetNumberOfKeyAttributes(idxrel);
111 : 72112 : index_attoff++)
112 : : {
113 : : Oid operator;
114 : : Oid optype;
115 : : Oid opfamily;
116 : : RegProcedure regop;
117 : 72112 : int table_attno = indkey->values[index_attoff];
118 : : StrategyNumber eq_strategy;
119 : :
120 [ + + ]: 72112 : if (!AttributeNumberIsValid(table_attno))
121 : : {
122 : : /*
123 : : * XXX: Currently, we don't support expressions in the scan key,
124 : : * see code below.
125 : : */
126 : 2 : continue;
127 : : }
128 : :
129 : : /*
130 : : * Load the operator info. We need this to get the equality operator
131 : : * function for the scan key.
132 : : */
133 : 72110 : optype = get_opclass_input_type(opclass->values[index_attoff]);
134 : 72110 : opfamily = get_opclass_family(opclass->values[index_attoff]);
275 akapila@postgresql.o 135 :GNC 72110 : eq_strategy = get_equal_strategy_number(opclass->values[index_attoff]);
136 : :
2642 peter_e@gmx.net 137 :CBC 72110 : operator = get_opfamily_member(opfamily, optype,
138 : : optype,
139 : : eq_strategy);
140 : :
141 [ - + ]: 72110 : if (!OidIsValid(operator))
2456 tgl@sss.pgh.pa.us 142 [ # # ]:UBC 0 : elog(ERROR, "missing operator %d(%u,%u) in opfamily %u",
143 : : eq_strategy, optype, optype, opfamily);
144 : :
2642 peter_e@gmx.net 145 :CBC 72110 : regop = get_opcode(operator);
146 : :
147 : : /* Initialize the scankey. */
396 akapila@postgresql.o 148 : 72110 : ScanKeyInit(&skey[skey_attoff],
149 : 72110 : index_attoff + 1,
150 : : eq_strategy,
151 : : regop,
152 : 72110 : searchslot->tts_values[table_attno - 1]);
153 : :
154 : 72110 : skey[skey_attoff].sk_collation = idxrel->rd_indcollation[index_attoff];
155 : :
156 : : /* Check for null value. */
157 [ + + ]: 72110 : if (searchslot->tts_isnull[table_attno - 1])
158 : 1 : skey[skey_attoff].sk_flags |= (SK_ISNULL | SK_SEARCHNULL);
159 : :
160 : 72110 : skey_attoff++;
161 : : }
162 : :
163 : : /* There must always be at least one attribute for the index scan. */
164 [ - + ]: 72099 : Assert(skey_attoff > 0);
165 : :
166 : 72099 : return skey_attoff;
167 : : }
168 : :
169 : : /*
170 : : * Search the relation 'rel' for tuple using the index.
171 : : *
172 : : * If a matching tuple is found, lock it with lockmode, fill the slot with its
173 : : * contents, and return true. Return false otherwise.
174 : : */
175 : : bool
2642 peter_e@gmx.net 176 : 72099 : RelationFindReplTupleByIndex(Relation rel, Oid idxoid,
177 : : LockTupleMode lockmode,
178 : : TupleTableSlot *searchslot,
179 : : TupleTableSlot *outslot)
180 : : {
181 : : ScanKeyData skey[INDEX_MAX_KEYS];
182 : : int skey_attoff;
183 : : IndexScanDesc scan;
184 : : SnapshotData snap;
185 : : TransactionId xwait;
186 : : Relation idxrel;
187 : : bool found;
396 akapila@postgresql.o 188 : 72099 : TypeCacheEntry **eq = NULL;
189 : : bool isIdxSafeToSkipDuplicates;
190 : :
191 : : /* Open the index. */
2642 peter_e@gmx.net 192 : 72099 : idxrel = index_open(idxoid, RowExclusiveLock);
193 : :
396 akapila@postgresql.o 194 : 72099 : isIdxSafeToSkipDuplicates = (GetRelationIdentityOrPK(rel) == idxoid);
195 : :
2642 peter_e@gmx.net 196 : 72099 : InitDirtySnapshot(snap);
197 : :
198 : : /* Build scan key. */
396 akapila@postgresql.o 199 : 72099 : skey_attoff = build_replindex_scan_key(skey, rel, idxrel, searchslot);
200 : :
201 : : /* Start an index scan. */
202 : 72099 : scan = index_beginscan(rel, idxrel, &snap, skey_attoff, 0);
203 : :
2642 peter_e@gmx.net 204 :UBC 0 : retry:
2642 peter_e@gmx.net 205 :CBC 72099 : found = false;
206 : :
396 akapila@postgresql.o 207 : 72099 : index_rescan(scan, skey, skey_attoff, NULL, 0);
208 : :
209 : : /* Try to find the tuple */
210 [ + + ]: 72099 : while (index_getnext_slot(scan, ForwardScanDirection, outslot))
211 : : {
212 : : /*
213 : : * Avoid expensive equality check if the index is primary key or
214 : : * replica identity index.
215 : : */
216 [ + + ]: 72091 : if (!isIdxSafeToSkipDuplicates)
217 : : {
218 [ + - ]: 15 : if (eq == NULL)
219 : 15 : eq = palloc0(sizeof(*eq) * outslot->tts_tupleDescriptor->natts);
220 : :
221 [ - + ]: 15 : if (!tuples_equal(outslot, searchslot, eq))
396 akapila@postgresql.o 222 :UBC 0 : continue;
223 : : }
224 : :
2642 peter_e@gmx.net 225 :CBC 72091 : ExecMaterializeSlot(outslot);
226 : :
227 : 144182 : xwait = TransactionIdIsValid(snap.xmin) ?
228 [ - + ]: 72091 : snap.xmin : snap.xmax;
229 : :
230 : : /*
231 : : * If the tuple is locked, wait for locking transaction to finish and
232 : : * retry.
233 : : */
234 [ - + ]: 72091 : if (TransactionIdIsValid(xwait))
235 : : {
2642 peter_e@gmx.net 236 :UBC 0 : XactLockTableWait(xwait, NULL, NULL, XLTW_None);
237 : 0 : goto retry;
238 : : }
239 : :
240 : : /* Found our tuple and it's not locked */
396 akapila@postgresql.o 241 :CBC 72091 : found = true;
242 : 72091 : break;
243 : : }
244 : :
245 : : /* Found tuple, try to lock it in the lockmode. */
2642 peter_e@gmx.net 246 [ + + ]: 72099 : if (found)
247 : : {
248 : : TM_FailureData tmfd;
249 : : TM_Result res;
250 : :
251 : 72091 : PushActiveSnapshot(GetLatestSnapshot());
252 : :
1788 andres@anarazel.de 253 : 72091 : res = table_tuple_lock(rel, &(outslot->tts_tid), GetLatestSnapshot(),
254 : : outslot,
255 : : GetCurrentCommandId(false),
256 : : lockmode,
257 : : LockWaitBlock,
258 : : 0 /* don't follow updates */ ,
259 : : &tmfd);
260 : :
2642 peter_e@gmx.net 261 : 72091 : PopActiveSnapshot();
262 : :
263 [ + - - - : 72091 : switch (res)
- ]
264 : : {
1849 andres@anarazel.de 265 : 72091 : case TM_Ok:
2642 peter_e@gmx.net 266 : 72091 : break;
1849 andres@anarazel.de 267 :UBC 0 : case TM_Updated:
268 : : /* XXX: Improve handling here */
269 [ # # ]: 0 : if (ItemPointerIndicatesMovedPartitions(&tmfd.ctid))
2199 270 [ # # ]: 0 : ereport(LOG,
271 : : (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
272 : : errmsg("tuple to be locked was already moved to another partition due to concurrent update, retrying")));
273 : : else
274 [ # # ]: 0 : ereport(LOG,
275 : : (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
276 : : errmsg("concurrent update, retrying")));
2642 peter_e@gmx.net 277 : 0 : goto retry;
1849 andres@anarazel.de 278 : 0 : case TM_Deleted:
279 : : /* XXX: Improve handling here */
280 [ # # ]: 0 : ereport(LOG,
281 : : (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
282 : : errmsg("concurrent delete, retrying")));
283 : 0 : goto retry;
284 : 0 : case TM_Invisible:
2642 peter_e@gmx.net 285 [ # # ]: 0 : elog(ERROR, "attempted to lock invisible tuple");
286 : : break;
287 : 0 : default:
1788 andres@anarazel.de 288 [ # # ]: 0 : elog(ERROR, "unexpected table_tuple_lock status: %u", res);
289 : : break;
290 : : }
291 : : }
292 : :
2642 peter_e@gmx.net 293 :CBC 72099 : index_endscan(scan);
294 : :
295 : : /* Don't release lock until commit. */
296 : 72099 : index_close(idxrel, NoLock);
297 : :
298 : 72099 : return found;
299 : : }
300 : :
301 : : /*
302 : : * Compare the tuples in the slots by checking if they have equal values.
303 : : */
304 : : static bool
1464 noah@leadboat.com 305 : 105282 : tuples_equal(TupleTableSlot *slot1, TupleTableSlot *slot2,
306 : : TypeCacheEntry **eq)
307 : : {
308 : : int attrnum;
309 : :
1861 andres@anarazel.de 310 [ - + ]: 105282 : Assert(slot1->tts_tupleDescriptor->natts ==
311 : : slot2->tts_tupleDescriptor->natts);
312 : :
313 : 105282 : slot_getallattrs(slot1);
314 : 105282 : slot_getallattrs(slot2);
315 : :
316 : : /* Check equality of the attributes. */
317 [ + + ]: 105473 : for (attrnum = 0; attrnum < slot1->tts_tupleDescriptor->natts; attrnum++)
318 : : {
319 : : Form_pg_attribute att;
320 : : TypeCacheEntry *typentry;
321 : :
390 akapila@postgresql.o 322 : 105312 : att = TupleDescAttr(slot1->tts_tupleDescriptor, attrnum);
323 : :
324 : : /*
325 : : * Ignore dropped and generated columns as the publisher doesn't send
326 : : * those
327 : : */
388 328 [ + + + + ]: 105312 : if (att->attisdropped || att->attgenerated)
390 329 : 2 : continue;
330 : :
331 : : /*
332 : : * If one value is NULL and other is not, then they are certainly not
333 : : * equal
334 : : */
1861 andres@anarazel.de 335 [ - + ]: 105310 : if (slot1->tts_isnull[attrnum] != slot2->tts_isnull[attrnum])
2642 peter_e@gmx.net 336 :UBC 0 : return false;
337 : :
338 : : /*
339 : : * If both are NULL, they can be considered equal.
340 : : */
1861 andres@anarazel.de 341 [ + + - + ]:CBC 105310 : if (slot1->tts_isnull[attrnum] || slot2->tts_isnull[attrnum])
2642 peter_e@gmx.net 342 : 1 : continue;
343 : :
1464 noah@leadboat.com 344 : 105309 : typentry = eq[attrnum];
345 [ + + ]: 105309 : if (typentry == NULL)
346 : : {
347 : 188 : typentry = lookup_type_cache(att->atttypid,
348 : : TYPECACHE_EQ_OPR_FINFO);
349 [ - + ]: 188 : if (!OidIsValid(typentry->eq_opr_finfo.fn_oid))
1464 noah@leadboat.com 350 [ # # ]:UBC 0 : ereport(ERROR,
351 : : (errcode(ERRCODE_UNDEFINED_FUNCTION),
352 : : errmsg("could not identify an equality operator for type %s",
353 : : format_type_be(att->atttypid))));
1464 noah@leadboat.com 354 :CBC 188 : eq[attrnum] = typentry;
355 : : }
356 : :
1850 peter@eisentraut.org 357 [ + + ]: 105309 : if (!DatumGetBool(FunctionCall2Coll(&typentry->eq_opr_finfo,
358 : : att->attcollation,
1789 tgl@sss.pgh.pa.us 359 : 105309 : slot1->tts_values[attrnum],
360 : 105309 : slot2->tts_values[attrnum])))
2642 peter_e@gmx.net 361 : 105121 : return false;
362 : : }
363 : :
364 : 161 : return true;
365 : : }
366 : :
367 : : /*
368 : : * Search the relation 'rel' for tuple using the sequential scan.
369 : : *
370 : : * If a matching tuple is found, lock it with lockmode, fill the slot with its
371 : : * contents, and return true. Return false otherwise.
372 : : *
373 : : * Note that this stops on the first matching tuple.
374 : : *
375 : : * This can obviously be quite slow on tables that have more than few rows.
376 : : */
377 : : bool
378 : 146 : RelationFindReplTupleSeq(Relation rel, LockTupleMode lockmode,
379 : : TupleTableSlot *searchslot, TupleTableSlot *outslot)
380 : : {
381 : : TupleTableSlot *scanslot;
382 : : TableScanDesc scan;
383 : : SnapshotData snap;
384 : : TypeCacheEntry **eq;
385 : : TransactionId xwait;
386 : : bool found;
1861 andres@anarazel.de 387 : 146 : TupleDesc desc PG_USED_FOR_ASSERTS_ONLY = RelationGetDescr(rel);
388 : :
2642 peter_e@gmx.net 389 [ - + ]: 146 : Assert(equalTupleDescs(desc, outslot->tts_tupleDescriptor));
390 : :
1464 noah@leadboat.com 391 : 146 : eq = palloc0(sizeof(*eq) * outslot->tts_tupleDescriptor->natts);
392 : :
393 : : /* Start a heap scan. */
2642 peter_e@gmx.net 394 : 146 : InitDirtySnapshot(snap);
1861 andres@anarazel.de 395 : 146 : scan = table_beginscan(rel, &snap, 0, NULL);
396 : 146 : scanslot = table_slot_create(rel, NULL);
397 : :
2642 peter_e@gmx.net 398 :UBC 0 : retry:
2642 peter_e@gmx.net 399 :CBC 146 : found = false;
400 : :
1861 andres@anarazel.de 401 : 146 : table_rescan(scan, NULL);
402 : :
403 : : /* Try to find the tuple */
404 [ + - ]: 105267 : while (table_scan_getnextslot(scan, ForwardScanDirection, scanslot))
405 : : {
1464 noah@leadboat.com 406 [ + + ]: 105267 : if (!tuples_equal(scanslot, searchslot, eq))
2642 peter_e@gmx.net 407 : 105121 : continue;
408 : :
409 : 146 : found = true;
1861 andres@anarazel.de 410 : 146 : ExecCopySlot(outslot, scanslot);
411 : :
2642 peter_e@gmx.net 412 : 292 : xwait = TransactionIdIsValid(snap.xmin) ?
413 [ - + ]: 146 : snap.xmin : snap.xmax;
414 : :
415 : : /*
416 : : * If the tuple is locked, wait for locking transaction to finish and
417 : : * retry.
418 : : */
419 [ - + ]: 146 : if (TransactionIdIsValid(xwait))
420 : : {
2642 peter_e@gmx.net 421 :UBC 0 : XactLockTableWait(xwait, NULL, NULL, XLTW_None);
422 : 0 : goto retry;
423 : : }
424 : :
425 : : /* Found our tuple and it's not locked */
1532 alvherre@alvh.no-ip. 426 :CBC 146 : break;
427 : : }
428 : :
429 : : /* Found tuple, try to lock it in the lockmode. */
2642 peter_e@gmx.net 430 [ + - ]: 146 : if (found)
431 : : {
432 : : TM_FailureData tmfd;
433 : : TM_Result res;
434 : :
435 : 146 : PushActiveSnapshot(GetLatestSnapshot());
436 : :
1788 andres@anarazel.de 437 : 146 : res = table_tuple_lock(rel, &(outslot->tts_tid), GetLatestSnapshot(),
438 : : outslot,
439 : : GetCurrentCommandId(false),
440 : : lockmode,
441 : : LockWaitBlock,
442 : : 0 /* don't follow updates */ ,
443 : : &tmfd);
444 : :
2642 peter_e@gmx.net 445 : 146 : PopActiveSnapshot();
446 : :
447 [ + - - - : 146 : switch (res)
- ]
448 : : {
1849 andres@anarazel.de 449 : 146 : case TM_Ok:
2642 peter_e@gmx.net 450 : 146 : break;
1849 andres@anarazel.de 451 :UBC 0 : case TM_Updated:
452 : : /* XXX: Improve handling here */
453 [ # # ]: 0 : if (ItemPointerIndicatesMovedPartitions(&tmfd.ctid))
2199 454 [ # # ]: 0 : ereport(LOG,
455 : : (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
456 : : errmsg("tuple to be locked was already moved to another partition due to concurrent update, retrying")));
457 : : else
458 [ # # ]: 0 : ereport(LOG,
459 : : (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
460 : : errmsg("concurrent update, retrying")));
2642 peter_e@gmx.net 461 : 0 : goto retry;
1849 andres@anarazel.de 462 : 0 : case TM_Deleted:
463 : : /* XXX: Improve handling here */
464 [ # # ]: 0 : ereport(LOG,
465 : : (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
466 : : errmsg("concurrent delete, retrying")));
467 : 0 : goto retry;
468 : 0 : case TM_Invisible:
2642 peter_e@gmx.net 469 [ # # ]: 0 : elog(ERROR, "attempted to lock invisible tuple");
470 : : break;
471 : 0 : default:
1788 andres@anarazel.de 472 [ # # ]: 0 : elog(ERROR, "unexpected table_tuple_lock status: %u", res);
473 : : break;
474 : : }
475 : : }
476 : :
1861 andres@anarazel.de 477 :CBC 146 : table_endscan(scan);
478 : 146 : ExecDropSingleTupleTableSlot(scanslot);
479 : :
2642 peter_e@gmx.net 480 : 146 : return found;
481 : : }
482 : :
483 : : /*
484 : : * Insert tuple represented in the slot to the relation, update the indexes,
485 : : * and execute any constraints and per-row triggers.
486 : : *
487 : : * Caller is responsible for opening the indexes.
488 : : */
489 : : void
1278 heikki.linnakangas@i 490 : 75743 : ExecSimpleRelationInsert(ResultRelInfo *resultRelInfo,
491 : : EState *estate, TupleTableSlot *slot)
492 : : {
2524 bruce@momjian.us 493 : 75743 : bool skip_tuple = false;
494 : 75743 : Relation rel = resultRelInfo->ri_RelationDesc;
495 : :
496 : : /* For now we support only tables. */
2642 peter_e@gmx.net 497 [ - + ]: 75743 : Assert(rel->rd_rel->relkind == RELKIND_RELATION);
498 : :
499 : 75743 : CheckCmdReplicaIdentity(rel, CMD_INSERT);
500 : :
501 : : /* BEFORE ROW INSERT Triggers */
502 [ + + ]: 75743 : if (resultRelInfo->ri_TrigDesc &&
503 [ + + ]: 19 : resultRelInfo->ri_TrigDesc->trig_insert_before_row)
504 : : {
1874 andres@anarazel.de 505 [ + + ]: 3 : if (!ExecBRInsertTriggers(estate, resultRelInfo, slot))
1789 tgl@sss.pgh.pa.us 506 : 1 : skip_tuple = true; /* "do nothing" */
507 : : }
508 : :
2642 peter_e@gmx.net 509 [ + + ]: 75743 : if (!skip_tuple)
510 : : {
511 : 75742 : List *recheckIndexes = NIL;
512 : :
513 : : /* Compute stored generated columns */
1842 peter@eisentraut.org 514 [ + + ]: 75742 : if (rel->rd_att->constr &&
515 [ + + ]: 45478 : rel->rd_att->constr->has_generated_stored)
1278 heikki.linnakangas@i 516 : 4 : ExecComputeStoredGenerated(resultRelInfo, estate, slot,
517 : : CMD_INSERT);
518 : :
519 : : /* Check the constraints of the tuple */
2642 peter_e@gmx.net 520 [ + + ]: 75742 : if (rel->rd_att->constr)
2134 alvherre@alvh.no-ip. 521 : 45478 : ExecConstraints(resultRelInfo, slot, estate);
1306 tgl@sss.pgh.pa.us 522 [ + + ]: 75742 : if (rel->rd_rel->relispartition)
2134 alvherre@alvh.no-ip. 523 : 60 : ExecPartitionCheck(resultRelInfo, slot, estate, true);
524 : :
525 : : /* OK, store the tuple and create index entries for it */
3 akorotkov@postgresql 526 : 75742 : simple_table_tuple_insert(resultRelInfo->ri_RelationDesc, slot);
527 : :
528 [ + + ]: 75742 : if (resultRelInfo->ri_NumIndices > 0)
1278 heikki.linnakangas@i 529 : 55423 : recheckIndexes = ExecInsertIndexTuples(resultRelInfo,
530 : : slot, estate, false, false,
531 : : NULL, NIL, false);
532 : :
533 : : /* AFTER ROW INSERT Triggers */
1874 andres@anarazel.de 534 : 75734 : ExecARInsertTriggers(estate, resultRelInfo, slot,
535 : : recheckIndexes, NULL);
536 : :
537 : : /*
538 : : * XXX we should in theory pass a TransitionCaptureState object to the
539 : : * above to capture transition tuples, but after statement triggers
540 : : * don't actually get fired by replication yet anyway
541 : : */
542 : :
2642 peter_e@gmx.net 543 : 75734 : list_free(recheckIndexes);
544 : : }
545 : 75735 : }
546 : :
547 : : /*
548 : : * Find the searchslot tuple and update it with data in the slot,
549 : : * update the indexes, and execute any constraints and per-row triggers.
550 : : *
551 : : * Caller is responsible for opening the indexes.
552 : : */
553 : : void
1278 heikki.linnakangas@i 554 : 31925 : ExecSimpleRelationUpdate(ResultRelInfo *resultRelInfo,
555 : : EState *estate, EPQState *epqstate,
556 : : TupleTableSlot *searchslot, TupleTableSlot *slot)
557 : : {
2524 bruce@momjian.us 558 : 31925 : bool skip_tuple = false;
559 : 31925 : Relation rel = resultRelInfo->ri_RelationDesc;
1849 andres@anarazel.de 560 : 31925 : ItemPointer tid = &(searchslot->tts_tid);
561 : :
562 : : /* For now we support only tables. */
2642 peter_e@gmx.net 563 [ - + ]: 31925 : Assert(rel->rd_rel->relkind == RELKIND_RELATION);
564 : :
565 : 31925 : CheckCmdReplicaIdentity(rel, CMD_UPDATE);
566 : :
567 : : /* BEFORE ROW UPDATE Triggers */
568 [ + + ]: 31925 : if (resultRelInfo->ri_TrigDesc &&
569 [ + + ]: 10 : resultRelInfo->ri_TrigDesc->trig_update_before_row)
570 : : {
1874 andres@anarazel.de 571 [ + + ]: 3 : if (!ExecBRUpdateTriggers(estate, epqstate, resultRelInfo,
572 : : tid, NULL, slot, NULL, NULL))
1789 tgl@sss.pgh.pa.us 573 : 2 : skip_tuple = true; /* "do nothing" */
574 : : }
575 : :
2642 peter_e@gmx.net 576 [ + + ]: 31925 : if (!skip_tuple)
577 : : {
578 : 31923 : List *recheckIndexes = NIL;
579 : : TU_UpdateIndexes update_indexes;
580 : :
581 : : /* Compute stored generated columns */
1842 peter@eisentraut.org 582 [ + + ]: 31923 : if (rel->rd_att->constr &&
583 [ + + ]: 31880 : rel->rd_att->constr->has_generated_stored)
1278 heikki.linnakangas@i 584 : 3 : ExecComputeStoredGenerated(resultRelInfo, estate, slot,
585 : : CMD_UPDATE);
586 : :
587 : : /* Check the constraints of the tuple */
2642 peter_e@gmx.net 588 [ + + ]: 31923 : if (rel->rd_att->constr)
2134 alvherre@alvh.no-ip. 589 : 31880 : ExecConstraints(resultRelInfo, slot, estate);
1306 tgl@sss.pgh.pa.us 590 [ + + ]: 31923 : if (rel->rd_rel->relispartition)
2134 alvherre@alvh.no-ip. 591 : 11 : ExecPartitionCheck(resultRelInfo, slot, estate, true);
592 : :
1788 andres@anarazel.de 593 : 31923 : simple_table_tuple_update(rel, tid, slot, estate->es_snapshot,
594 : : &update_indexes);
595 : :
391 tomas.vondra@postgre 596 [ + + + + ]: 31923 : if (resultRelInfo->ri_NumIndices > 0 && (update_indexes != TU_None))
1278 heikki.linnakangas@i 597 : 20297 : recheckIndexes = ExecInsertIndexTuples(resultRelInfo,
598 : : slot, estate, true, false,
599 : : NULL, NIL,
600 : : (update_indexes == TU_Summarizing));
601 : :
602 : : /* AFTER ROW UPDATE Triggers */
2642 peter_e@gmx.net 603 : 31923 : ExecARUpdateTriggers(estate, resultRelInfo,
604 : : NULL, NULL,
605 : : tid, NULL, slot,
606 : : recheckIndexes, NULL, false);
607 : :
608 : 31923 : list_free(recheckIndexes);
609 : : }
610 : 31925 : }
611 : :
612 : : /*
613 : : * Find the searchslot tuple and delete it, and execute any constraints
614 : : * and per-row triggers.
615 : : *
616 : : * Caller is responsible for opening the indexes.
617 : : */
618 : : void
1278 heikki.linnakangas@i 619 : 40311 : ExecSimpleRelationDelete(ResultRelInfo *resultRelInfo,
620 : : EState *estate, EPQState *epqstate,
621 : : TupleTableSlot *searchslot)
622 : : {
2524 bruce@momjian.us 623 : 40311 : bool skip_tuple = false;
624 : 40311 : Relation rel = resultRelInfo->ri_RelationDesc;
1849 andres@anarazel.de 625 : 40311 : ItemPointer tid = &searchslot->tts_tid;
626 : :
2642 peter_e@gmx.net 627 : 40311 : CheckCmdReplicaIdentity(rel, CMD_DELETE);
628 : :
629 : : /* BEFORE ROW DELETE Triggers */
630 [ + + ]: 40311 : if (resultRelInfo->ri_TrigDesc &&
2376 rhaas@postgresql.org 631 [ - + ]: 10 : resultRelInfo->ri_TrigDesc->trig_delete_before_row)
632 : : {
2642 peter_e@gmx.net 633 :UBC 0 : skip_tuple = !ExecBRDeleteTriggers(estate, epqstate, resultRelInfo,
398 dean.a.rasheed@gmail 634 : 0 : tid, NULL, NULL, NULL, NULL);
635 : : }
636 : :
2642 peter_e@gmx.net 637 [ + - ]:CBC 40311 : if (!skip_tuple)
638 : : {
639 : : /* OK, delete the tuple */
3 akorotkov@postgresql 640 : 40311 : simple_table_tuple_delete(rel, tid, estate->es_snapshot);
641 : :
642 : : /* AFTER ROW DELETE Triggers */
2642 peter_e@gmx.net 643 : 40311 : ExecARDeleteTriggers(estate, resultRelInfo,
644 : : tid, NULL, NULL, false);
645 : : }
646 : 40311 : }
647 : :
648 : : /*
649 : : * Check if command can be executed with current replica identity.
650 : : */
651 : : void
652 : 209955 : CheckCmdReplicaIdentity(Relation rel, CmdType cmd)
653 : : {
654 : : PublicationDesc pubdesc;
655 : :
656 : : /*
657 : : * Skip checking the replica identity for partitioned tables, because the
658 : : * operations are actually performed on the leaf partitions.
659 : : */
607 akapila@postgresql.o 660 [ + + ]: 209955 : if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
661 : 199138 : return;
662 : :
663 : : /* We only need to do checks for UPDATE and DELETE. */
2642 peter_e@gmx.net 664 [ + + + + ]: 207315 : if (cmd != CMD_UPDATE && cmd != CMD_DELETE)
665 : 121422 : return;
666 : :
667 : : /*
668 : : * It is only safe to execute UPDATE/DELETE when all columns, referenced
669 : : * in the row filters from publications which the relation is in, are
670 : : * valid - i.e. when all referenced columns are part of REPLICA IDENTITY
671 : : * or the table does not publish UPDATEs or DELETEs.
672 : : *
673 : : * XXX We could optimize it by first checking whether any of the
674 : : * publications have a row filter for this relation. If not and relation
675 : : * has replica identity then we can avoid building the descriptor but as
676 : : * this happens only one time it doesn't seem worth the additional
677 : : * complexity.
678 : : */
782 akapila@postgresql.o 679 : 85893 : RelationBuildPublicationDesc(rel, &pubdesc);
680 [ + + + + ]: 85893 : if (cmd == CMD_UPDATE && !pubdesc.rf_valid_for_update)
681 [ + - ]: 30 : ereport(ERROR,
682 : : (errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
683 : : errmsg("cannot update table \"%s\"",
684 : : RelationGetRelationName(rel)),
685 : : errdetail("Column used in the publication WHERE expression is not part of the replica identity.")));
750 tomas.vondra@postgre 686 [ + + + + ]: 85863 : else if (cmd == CMD_UPDATE && !pubdesc.cols_valid_for_update)
687 [ + - ]: 54 : ereport(ERROR,
688 : : (errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
689 : : errmsg("cannot update table \"%s\"",
690 : : RelationGetRelationName(rel)),
691 : : errdetail("Column list used by the publication does not cover the replica identity.")));
782 akapila@postgresql.o 692 [ + + - + ]: 85809 : else if (cmd == CMD_DELETE && !pubdesc.rf_valid_for_delete)
782 akapila@postgresql.o 693 [ # # ]:UBC 0 : ereport(ERROR,
694 : : (errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
695 : : errmsg("cannot delete from table \"%s\"",
696 : : RelationGetRelationName(rel)),
697 : : errdetail("Column used in the publication WHERE expression is not part of the replica identity.")));
750 tomas.vondra@postgre 698 [ + + - + ]:CBC 85809 : else if (cmd == CMD_DELETE && !pubdesc.cols_valid_for_delete)
750 tomas.vondra@postgre 699 [ # # ]:UBC 0 : ereport(ERROR,
700 : : (errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
701 : : errmsg("cannot delete from table \"%s\"",
702 : : RelationGetRelationName(rel)),
703 : : errdetail("Column list used by the publication does not cover the replica identity.")));
704 : :
705 : : /* If relation has replica identity we are always good. */
782 akapila@postgresql.o 706 [ + + ]:CBC 85809 : if (OidIsValid(RelationGetReplicaIndex(rel)))
2642 peter_e@gmx.net 707 : 74870 : return;
708 : :
709 : : /* REPLICA IDENTITY FULL is also good for UPDATE/DELETE. */
750 tomas.vondra@postgre 710 [ + + ]: 10939 : if (rel->rd_rel->relreplident == REPLICA_IDENTITY_FULL)
711 : 206 : return;
712 : :
713 : : /*
714 : : * This is UPDATE/DELETE and there is no replica identity.
715 : : *
716 : : * Check if the table publishes UPDATES or DELETES.
717 : : */
782 akapila@postgresql.o 718 [ + + + + ]: 10733 : if (cmd == CMD_UPDATE && pubdesc.pubactions.pubupdate)
2642 peter_e@gmx.net 719 [ + - ]: 48 : ereport(ERROR,
720 : : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
721 : : errmsg("cannot update table \"%s\" because it does not have a replica identity and publishes updates",
722 : : RelationGetRelationName(rel)),
723 : : errhint("To enable updating the table, set REPLICA IDENTITY using ALTER TABLE.")));
782 akapila@postgresql.o 724 [ + + - + ]: 10685 : else if (cmd == CMD_DELETE && pubdesc.pubactions.pubdelete)
2642 peter_e@gmx.net 725 [ # # ]:UBC 0 : ereport(ERROR,
726 : : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
727 : : errmsg("cannot delete from table \"%s\" because it does not have a replica identity and publishes deletes",
728 : : RelationGetRelationName(rel)),
729 : : errhint("To enable deleting from the table, set REPLICA IDENTITY using ALTER TABLE.")));
730 : : }
731 : :
732 : :
733 : : /*
734 : : * Check if we support writing into specific relkind.
735 : : *
736 : : * The nspname and relname are only needed for error reporting.
737 : : */
738 : : void
2525 peter_e@gmx.net 739 :CBC 810 : CheckSubscriptionRelkind(char relkind, const char *nspname,
740 : : const char *relname)
741 : : {
738 tomas.vondra@postgre 742 [ + + - + ]: 810 : if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE)
2525 peter_e@gmx.net 743 [ # # ]:UBC 0 : ereport(ERROR,
744 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
745 : : errmsg("cannot use relation \"%s.%s\" as logical replication target",
746 : : nspname, relname),
747 : : errdetail_relkind_not_supported(relkind)));
2525 peter_e@gmx.net 748 :CBC 810 : }
|