Branch data Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : *
3 : : * pgoutput.c
4 : : * Logical Replication output plugin
5 : : *
6 : : * Copyright (c) 2012-2026, PostgreSQL Global Development Group
7 : : *
8 : : * IDENTIFICATION
9 : : * src/backend/replication/pgoutput/pgoutput.c
10 : : *
11 : : *-------------------------------------------------------------------------
12 : : */
13 : : #include "postgres.h"
14 : :
15 : : #include "access/tupconvert.h"
16 : : #include "catalog/partition.h"
17 : : #include "catalog/pg_publication.h"
18 : : #include "catalog/pg_publication_rel.h"
19 : : #include "catalog/pg_subscription.h"
20 : : #include "commands/defrem.h"
21 : : #include "commands/subscriptioncmds.h"
22 : : #include "executor/executor.h"
23 : : #include "fmgr.h"
24 : : #include "nodes/makefuncs.h"
25 : : #include "parser/parse_relation.h"
26 : : #include "replication/logical.h"
27 : : #include "replication/logicalproto.h"
28 : : #include "replication/origin.h"
29 : : #include "replication/pgoutput.h"
30 : : #include "rewrite/rewriteHandler.h"
31 : : #include "utils/builtins.h"
32 : : #include "utils/inval.h"
33 : : #include "utils/lsyscache.h"
34 : : #include "utils/memutils.h"
35 : : #include "utils/rel.h"
36 : : #include "utils/syscache.h"
37 : : #include "utils/varlena.h"
38 : :
39 : 621 : PG_MODULE_MAGIC_EXT(
40 : : .name = "pgoutput",
41 : : .version = PG_VERSION
42 : : );
43 : :
44 : : static void pgoutput_startup(LogicalDecodingContext *ctx,
45 : : OutputPluginOptions *opt, bool is_init);
46 : : static void pgoutput_shutdown(LogicalDecodingContext *ctx);
47 : : static void pgoutput_begin_txn(LogicalDecodingContext *ctx,
48 : : ReorderBufferTXN *txn);
49 : : static void pgoutput_commit_txn(LogicalDecodingContext *ctx,
50 : : ReorderBufferTXN *txn, XLogRecPtr commit_lsn);
51 : : static void pgoutput_change(LogicalDecodingContext *ctx,
52 : : ReorderBufferTXN *txn, Relation relation,
53 : : ReorderBufferChange *change);
54 : : static void pgoutput_truncate(LogicalDecodingContext *ctx,
55 : : ReorderBufferTXN *txn, int nrelations, Relation relations[],
56 : : ReorderBufferChange *change);
57 : : static void pgoutput_message(LogicalDecodingContext *ctx,
58 : : ReorderBufferTXN *txn, XLogRecPtr message_lsn,
59 : : bool transactional, const char *prefix,
60 : : Size sz, const char *message);
61 : : static bool pgoutput_origin_filter(LogicalDecodingContext *ctx,
62 : : ReplOriginId origin_id);
63 : : static void pgoutput_begin_prepare_txn(LogicalDecodingContext *ctx,
64 : : ReorderBufferTXN *txn);
65 : : static void pgoutput_prepare_txn(LogicalDecodingContext *ctx,
66 : : ReorderBufferTXN *txn, XLogRecPtr prepare_lsn);
67 : : static void pgoutput_commit_prepared_txn(LogicalDecodingContext *ctx,
68 : : ReorderBufferTXN *txn, XLogRecPtr commit_lsn);
69 : : static void pgoutput_rollback_prepared_txn(LogicalDecodingContext *ctx,
70 : : ReorderBufferTXN *txn,
71 : : XLogRecPtr prepare_end_lsn,
72 : : TimestampTz prepare_time);
73 : : static void pgoutput_stream_start(struct LogicalDecodingContext *ctx,
74 : : ReorderBufferTXN *txn);
75 : : static void pgoutput_stream_stop(struct LogicalDecodingContext *ctx,
76 : : ReorderBufferTXN *txn);
77 : : static void pgoutput_stream_abort(struct LogicalDecodingContext *ctx,
78 : : ReorderBufferTXN *txn,
79 : : XLogRecPtr abort_lsn);
80 : : static void pgoutput_stream_commit(struct LogicalDecodingContext *ctx,
81 : : ReorderBufferTXN *txn,
82 : : XLogRecPtr commit_lsn);
83 : : static void pgoutput_stream_prepare_txn(LogicalDecodingContext *ctx,
84 : : ReorderBufferTXN *txn, XLogRecPtr prepare_lsn);
85 : :
86 : : static bool publications_valid;
87 : :
88 : : static List *LoadPublications(List *pubnames);
89 : : static void publication_invalidation_cb(Datum arg, SysCacheIdentifier cacheid,
90 : : uint32 hashvalue);
91 : : static void send_repl_origin(LogicalDecodingContext *ctx,
92 : : ReplOriginId origin_id, XLogRecPtr origin_lsn,
93 : : bool send_origin);
94 : :
95 : : /*
96 : : * Only 3 publication actions are used for row filtering ("insert", "update",
97 : : * "delete"). See RelationSyncEntry.exprstate[].
98 : : */
99 : : enum RowFilterPubAction
100 : : {
101 : : PUBACTION_INSERT,
102 : : PUBACTION_UPDATE,
103 : : PUBACTION_DELETE,
104 : : };
105 : :
106 : : #define NUM_ROWFILTER_PUBACTIONS (PUBACTION_DELETE+1)
107 : :
108 : : /*
109 : : * Entry in the map used to remember which relation schemas we sent.
110 : : *
111 : : * The schema_sent flag determines if the current schema record for the
112 : : * relation (and for its ancestor if publish_as_relid is set) was already
113 : : * sent to the subscriber (in which case we don't need to send it again).
114 : : *
115 : : * The schema cache on downstream is however updated only at commit time,
116 : : * and with streamed transactions the commit order may be different from
117 : : * the order the transactions are sent in. Also, the (sub) transactions
118 : : * might get aborted so we need to send the schema for each (sub) transaction
119 : : * so that we don't lose the schema information on abort. For handling this,
120 : : * we maintain the list of xids (streamed_txns) for those we have already sent
121 : : * the schema.
122 : : *
123 : : * For partitions, 'pubactions' considers not only the table's own
124 : : * publications, but also those of all of its ancestors.
125 : : */
126 : : typedef struct RelationSyncEntry
127 : : {
128 : : Oid relid; /* relation oid */
129 : :
130 : : bool replicate_valid; /* overall validity flag for entry */
131 : :
132 : : bool schema_sent;
133 : :
134 : : /*
135 : : * This will be PUBLISH_GENCOLS_STORED if the relation contains generated
136 : : * columns and the 'publish_generated_columns' parameter is set to
137 : : * PUBLISH_GENCOLS_STORED. Otherwise, it will be PUBLISH_GENCOLS_NONE,
138 : : * indicating that no generated columns should be published, unless
139 : : * explicitly specified in the column list.
140 : : */
141 : : PublishGencolsType include_gencols_type;
142 : : List *streamed_txns; /* streamed toplevel transactions with this
143 : : * schema */
144 : :
145 : : /* are we publishing this rel? */
146 : : PublicationActions pubactions;
147 : :
148 : : /*
149 : : * ExprState array for row filter. Different publication actions don't
150 : : * allow multiple expressions to always be combined into one, because
151 : : * updates or deletes restrict the column in expression to be part of the
152 : : * replica identity index whereas inserts do not have this restriction, so
153 : : * there is one ExprState per publication action.
154 : : */
155 : : ExprState *exprstate[NUM_ROWFILTER_PUBACTIONS];
156 : : EState *estate; /* executor state used for row filter */
157 : : TupleTableSlot *new_slot; /* slot for storing new tuple */
158 : : TupleTableSlot *old_slot; /* slot for storing old tuple */
159 : :
160 : : /*
161 : : * OID of the relation to publish changes as. For a partition, this may
162 : : * be set to one of its ancestors whose schema will be used when
163 : : * replicating changes, if publish_via_partition_root is set for the
164 : : * publication.
165 : : */
166 : : Oid publish_as_relid;
167 : :
168 : : /*
169 : : * Map used when replicating using an ancestor's schema to convert tuples
170 : : * from partition's type to the ancestor's; NULL if publish_as_relid is
171 : : * same as 'relid' or if unnecessary due to partition and the ancestor
172 : : * having identical TupleDesc.
173 : : */
174 : : AttrMap *attrmap;
175 : :
176 : : /*
177 : : * Columns included in the publication, or NULL if all columns are
178 : : * included implicitly. Note that the attnums in this bitmap are not
179 : : * shifted by FirstLowInvalidHeapAttributeNumber.
180 : : */
181 : : Bitmapset *columns;
182 : :
183 : : /*
184 : : * Private context to store additional data for this entry - state for the
185 : : * row filter expressions, column list, etc.
186 : : */
187 : : MemoryContext entry_cxt;
188 : : } RelationSyncEntry;
189 : :
190 : : /*
191 : : * Maintain a per-transaction level variable to track whether the transaction
192 : : * has sent BEGIN. BEGIN is only sent when the first change in a transaction
193 : : * is processed. This makes it possible to skip sending a pair of BEGIN/COMMIT
194 : : * messages for empty transactions which saves network bandwidth.
195 : : *
196 : : * This optimization is not used for prepared transactions because if the
197 : : * WALSender restarts after prepare of a transaction and before commit prepared
198 : : * of the same transaction then we won't be able to figure out if we have
199 : : * skipped sending BEGIN/PREPARE of a transaction as it was empty. This is
200 : : * because we would have lost the in-memory txndata information that was
201 : : * present prior to the restart. This will result in sending a spurious
202 : : * COMMIT PREPARED without a corresponding prepared transaction at the
203 : : * downstream which would lead to an error when it tries to process it.
204 : : *
205 : : * XXX We could achieve this optimization by changing protocol to send
206 : : * additional information so that downstream can detect that the corresponding
207 : : * prepare has not been sent. However, adding such a check for every
208 : : * transaction in the downstream could be costly so we might want to do it
209 : : * optionally.
210 : : *
211 : : * We also don't have this optimization for streamed transactions because
212 : : * they can contain prepared transactions.
213 : : */
214 : : typedef struct PGOutputTxnData
215 : : {
216 : : bool sent_begin_txn; /* flag indicating whether BEGIN has been sent */
217 : : } PGOutputTxnData;
218 : :
219 : : /* Map used to remember which relation schemas we sent. */
220 : : static HTAB *RelationSyncCache = NULL;
221 : :
222 : : static void init_rel_sync_cache(MemoryContext cachectx);
223 : : static void cleanup_rel_sync_cache(TransactionId xid, bool is_commit);
224 : : static RelationSyncEntry *get_rel_sync_entry(PGOutputData *data,
225 : : Relation relation);
226 : : static void send_relation_and_attrs(Relation relation, TransactionId xid,
227 : : LogicalDecodingContext *ctx,
228 : : RelationSyncEntry *relentry);
229 : : static void rel_sync_cache_relation_cb(Datum arg, Oid relid);
230 : : static void rel_sync_cache_publication_cb(Datum arg, SysCacheIdentifier cacheid,
231 : : uint32 hashvalue);
232 : : static void set_schema_sent_in_streamed_txn(RelationSyncEntry *entry,
233 : : TransactionId xid);
234 : : static bool get_schema_sent_in_streamed_txn(RelationSyncEntry *entry,
235 : : TransactionId xid);
236 : : static void init_tuple_slot(PGOutputData *data, Relation relation,
237 : : RelationSyncEntry *entry);
238 : : static void pgoutput_memory_context_reset(void *arg);
239 : :
240 : : /* row filter routines */
241 : : static EState *create_estate_for_relation(Relation rel);
242 : : static void pgoutput_row_filter_init(PGOutputData *data,
243 : : List *publications,
244 : : RelationSyncEntry *entry);
245 : : static bool pgoutput_row_filter_exec_expr(ExprState *state,
246 : : ExprContext *econtext);
247 : : static bool pgoutput_row_filter(Relation relation, TupleTableSlot *old_slot,
248 : : TupleTableSlot **new_slot_ptr,
249 : : RelationSyncEntry *entry,
250 : : ReorderBufferChangeType *action);
251 : :
252 : : /* column list routines */
253 : : static void pgoutput_column_list_init(PGOutputData *data,
254 : : List *publications,
255 : : RelationSyncEntry *entry);
256 : :
257 : : /*
258 : : * Specify output plugin callbacks
259 : : */
260 : : void
261 : 826 : _PG_output_plugin_init(OutputPluginCallbacks *cb)
262 : : {
263 : 826 : cb->startup_cb = pgoutput_startup;
264 : 826 : cb->begin_cb = pgoutput_begin_txn;
265 : 826 : cb->change_cb = pgoutput_change;
266 : 826 : cb->truncate_cb = pgoutput_truncate;
267 : 826 : cb->message_cb = pgoutput_message;
268 : 826 : cb->commit_cb = pgoutput_commit_txn;
269 : :
270 : 826 : cb->begin_prepare_cb = pgoutput_begin_prepare_txn;
271 : 826 : cb->prepare_cb = pgoutput_prepare_txn;
272 : 826 : cb->commit_prepared_cb = pgoutput_commit_prepared_txn;
273 : 826 : cb->rollback_prepared_cb = pgoutput_rollback_prepared_txn;
274 : 826 : cb->filter_by_origin_cb = pgoutput_origin_filter;
275 : 826 : cb->shutdown_cb = pgoutput_shutdown;
276 : :
277 : : /* transaction streaming */
278 : 826 : cb->stream_start_cb = pgoutput_stream_start;
279 : 826 : cb->stream_stop_cb = pgoutput_stream_stop;
280 : 826 : cb->stream_abort_cb = pgoutput_stream_abort;
281 : 826 : cb->stream_commit_cb = pgoutput_stream_commit;
282 : 826 : cb->stream_change_cb = pgoutput_change;
283 : 826 : cb->stream_message_cb = pgoutput_message;
284 : 826 : cb->stream_truncate_cb = pgoutput_truncate;
285 : : /* transaction streaming - two-phase commit */
286 : 826 : cb->stream_prepare_cb = pgoutput_stream_prepare_txn;
287 : 826 : }
288 : :
289 : : static void
290 : 467 : parse_output_parameters(List *options, PGOutputData *data)
291 : : {
292 : : ListCell *lc;
293 : 467 : bool protocol_version_given = false;
294 : 467 : bool publication_names_given = false;
295 : 467 : bool binary_option_given = false;
296 : 467 : bool messages_option_given = false;
297 : 467 : bool streaming_given = false;
298 : 467 : bool two_phase_option_given = false;
299 : 467 : bool origin_option_given = false;
300 : :
301 : : /* Initialize optional parameters to defaults */
302 : 467 : data->binary = false;
303 : 467 : data->streaming = LOGICALREP_STREAM_OFF;
304 : 467 : data->messages = false;
305 : 467 : data->two_phase = false;
306 : 467 : data->publish_no_origin = false;
307 : :
308 [ + - + + : 2319 : foreach(lc, options)
+ + ]
309 : : {
310 : 1853 : DefElem *defel = (DefElem *) lfirst(lc);
311 : :
312 : : Assert(defel->arg == NULL || IsA(defel->arg, String));
313 : :
314 : : /* Check each param, whether or not we recognize it */
315 [ + + ]: 1853 : if (strcmp(defel->defname, "proto_version") == 0)
316 : : {
317 : : unsigned long parsed;
318 : : char *endptr;
319 : 467 : const char *val = strVal(defel->arg);
320 : :
321 [ - + ]: 467 : if (protocol_version_given)
322 [ # # ]: 0 : ereport(ERROR,
323 : : (errcode(ERRCODE_SYNTAX_ERROR),
324 : : errmsg("conflicting or redundant options")));
325 : 467 : protocol_version_given = true;
326 : :
327 : 467 : errno = 0;
328 : 467 : parsed = strtoul(val, &endptr, 10);
329 [ + + + - : 467 : if (endptr == val || errno != 0 || *endptr != '\0')
- + ]
330 [ + - ]: 1 : ereport(ERROR,
331 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
332 : : errmsg("invalid proto_version")));
333 : :
334 [ - + ]: 466 : if (parsed > PG_UINT32_MAX)
335 [ # # ]: 0 : ereport(ERROR,
336 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
337 : : errmsg("proto_version \"%s\" out of range",
338 : : strVal(defel->arg))));
339 : :
340 : 466 : data->protocol_version = (uint32) parsed;
341 : : }
342 [ + + ]: 1386 : else if (strcmp(defel->defname, "publication_names") == 0)
343 : : {
344 [ - + ]: 466 : if (publication_names_given)
345 [ # # ]: 0 : ereport(ERROR,
346 : : (errcode(ERRCODE_SYNTAX_ERROR),
347 : : errmsg("conflicting or redundant options")));
348 : 466 : publication_names_given = true;
349 : :
350 : : /*
351 : : * Pass a copy of the DefElem->arg since SplitIdentifierString
352 : : * modifies its input.
353 : : */
354 [ - + ]: 466 : if (!SplitIdentifierString(pstrdup(strVal(defel->arg)), ',',
355 : : &data->publication_names))
356 [ # # ]: 0 : ereport(ERROR,
357 : : (errcode(ERRCODE_INVALID_NAME),
358 : : errmsg("invalid publication_names syntax")));
359 : : }
360 [ + + ]: 920 : else if (strcmp(defel->defname, "binary") == 0)
361 : : {
362 [ - + ]: 11 : if (binary_option_given)
363 [ # # ]: 0 : ereport(ERROR,
364 : : (errcode(ERRCODE_SYNTAX_ERROR),
365 : : errmsg("conflicting or redundant options")));
366 : 11 : binary_option_given = true;
367 : :
368 : 11 : data->binary = defGetBoolean(defel);
369 : : }
370 [ + + ]: 909 : else if (strcmp(defel->defname, "messages") == 0)
371 : : {
372 [ - + ]: 4 : if (messages_option_given)
373 [ # # ]: 0 : ereport(ERROR,
374 : : (errcode(ERRCODE_SYNTAX_ERROR),
375 : : errmsg("conflicting or redundant options")));
376 : 4 : messages_option_given = true;
377 : :
378 : 4 : data->messages = defGetBoolean(defel);
379 : : }
380 [ + + ]: 905 : else if (strcmp(defel->defname, "streaming") == 0)
381 : : {
382 [ - + ]: 444 : if (streaming_given)
383 [ # # ]: 0 : ereport(ERROR,
384 : : (errcode(ERRCODE_SYNTAX_ERROR),
385 : : errmsg("conflicting or redundant options")));
386 : 444 : streaming_given = true;
387 : :
388 : 444 : data->streaming = defGetStreamingMode(defel);
389 : : }
390 [ + + ]: 461 : else if (strcmp(defel->defname, "two_phase") == 0)
391 : : {
392 [ - + ]: 8 : if (two_phase_option_given)
393 [ # # ]: 0 : ereport(ERROR,
394 : : (errcode(ERRCODE_SYNTAX_ERROR),
395 : : errmsg("conflicting or redundant options")));
396 : 8 : two_phase_option_given = true;
397 : :
398 : 8 : data->two_phase = defGetBoolean(defel);
399 : : }
400 [ + - ]: 453 : else if (strcmp(defel->defname, "origin") == 0)
401 : : {
402 : : char *origin;
403 : :
404 [ - + ]: 453 : if (origin_option_given)
405 [ # # ]: 0 : ereport(ERROR,
406 : : errcode(ERRCODE_SYNTAX_ERROR),
407 : : errmsg("conflicting or redundant options"));
408 : 453 : origin_option_given = true;
409 : :
410 : 453 : origin = defGetString(defel);
411 [ + + ]: 453 : if (pg_strcasecmp(origin, LOGICALREP_ORIGIN_NONE) == 0)
412 : 29 : data->publish_no_origin = true;
413 [ + - ]: 424 : else if (pg_strcasecmp(origin, LOGICALREP_ORIGIN_ANY) == 0)
414 : 424 : data->publish_no_origin = false;
415 : : else
416 [ # # ]: 0 : ereport(ERROR,
417 : : errcode(ERRCODE_INVALID_PARAMETER_VALUE),
418 : : errmsg("unrecognized origin value: \"%s\"", origin));
419 : : }
420 : : else
421 [ # # ]: 0 : elog(ERROR, "unrecognized pgoutput option: %s", defel->defname);
422 : : }
423 : :
424 : : /* Check required options */
425 [ - + ]: 466 : if (!protocol_version_given)
426 [ # # ]: 0 : ereport(ERROR,
427 : : errcode(ERRCODE_INVALID_PARAMETER_VALUE),
428 : : errmsg("option \"%s\" missing", "proto_version"));
429 [ - + ]: 466 : if (!publication_names_given)
430 [ # # ]: 0 : ereport(ERROR,
431 : : errcode(ERRCODE_INVALID_PARAMETER_VALUE),
432 : : errmsg("option \"%s\" missing", "publication_names"));
433 : 466 : }
434 : :
435 : : /*
436 : : * Memory context reset callback of PGOutputData->context.
437 : : */
438 : : static void
439 : 1146 : pgoutput_memory_context_reset(void *arg)
440 : : {
441 [ + + ]: 1146 : if (RelationSyncCache)
442 : : {
443 : 214 : hash_destroy(RelationSyncCache);
444 : 214 : RelationSyncCache = NULL;
445 : : }
446 : 1146 : }
447 : :
448 : : /*
449 : : * Initialize this plugin
450 : : */
451 : : static void
452 : 826 : pgoutput_startup(LogicalDecodingContext *ctx, OutputPluginOptions *opt,
453 : : bool is_init)
454 : : {
455 : 826 : PGOutputData *data = palloc0_object(PGOutputData);
456 : : static bool publication_callback_registered = false;
457 : : MemoryContextCallback *mcallback;
458 : :
459 : : /* Create our memory context for private allocations. */
460 : 826 : data->context = AllocSetContextCreate(ctx->context,
461 : : "logical replication output context",
462 : : ALLOCSET_DEFAULT_SIZES);
463 : :
464 : 826 : data->cachectx = AllocSetContextCreate(ctx->context,
465 : : "logical replication cache context",
466 : : ALLOCSET_DEFAULT_SIZES);
467 : :
468 : 826 : data->pubctx = AllocSetContextCreate(ctx->context,
469 : : "logical replication publication list context",
470 : : ALLOCSET_SMALL_SIZES);
471 : :
472 : : /*
473 : : * Ensure to cleanup RelationSyncCache even when logical decoding invoked
474 : : * via SQL interface ends up with an error.
475 : : */
476 : 826 : mcallback = palloc0_object(MemoryContextCallback);
477 : 826 : mcallback->func = pgoutput_memory_context_reset;
478 : 826 : MemoryContextRegisterResetCallback(ctx->context, mcallback);
479 : :
480 : 826 : ctx->output_plugin_private = data;
481 : :
482 : : /* This plugin uses binary protocol. */
483 : 826 : opt->output_type = OUTPUT_PLUGIN_BINARY_OUTPUT;
484 : :
485 : : /*
486 : : * This is replication start and not slot initialization.
487 : : *
488 : : * Parse and validate options passed by the client.
489 : : */
490 [ + + ]: 826 : if (!is_init)
491 : : {
492 : : /* Parse the params and ERROR if we see any we don't recognize */
493 : 467 : parse_output_parameters(ctx->output_plugin_options, data);
494 : :
495 : : /* Check if we support requested protocol */
496 [ - + ]: 466 : if (data->protocol_version > LOGICALREP_PROTO_MAX_VERSION_NUM)
497 [ # # ]: 0 : ereport(ERROR,
498 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
499 : : errmsg("client sent proto_version=%d but server only supports protocol %d or lower",
500 : : data->protocol_version, LOGICALREP_PROTO_MAX_VERSION_NUM)));
501 : :
502 [ - + ]: 466 : if (data->protocol_version < LOGICALREP_PROTO_MIN_VERSION_NUM)
503 [ # # ]: 0 : ereport(ERROR,
504 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
505 : : errmsg("client sent proto_version=%d but server only supports protocol %d or higher",
506 : : data->protocol_version, LOGICALREP_PROTO_MIN_VERSION_NUM)));
507 : :
508 : : /*
509 : : * Decide whether to enable streaming. It is disabled by default, in
510 : : * which case we just update the flag in decoding context. Otherwise
511 : : * we only allow it with sufficient version of the protocol, and when
512 : : * the output plugin supports it.
513 : : */
514 [ + + ]: 466 : if (data->streaming == LOGICALREP_STREAM_OFF)
515 : 22 : ctx->streaming = false;
516 [ + + ]: 444 : else if (data->streaming == LOGICALREP_STREAM_ON &&
517 [ - + ]: 27 : data->protocol_version < LOGICALREP_PROTO_STREAM_VERSION_NUM)
518 [ # # ]: 0 : ereport(ERROR,
519 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
520 : : errmsg("requested proto_version=%d does not support streaming, need %d or higher",
521 : : data->protocol_version, LOGICALREP_PROTO_STREAM_VERSION_NUM)));
522 [ + + ]: 444 : else if (data->streaming == LOGICALREP_STREAM_PARALLEL &&
523 [ - + ]: 417 : data->protocol_version < LOGICALREP_PROTO_STREAM_PARALLEL_VERSION_NUM)
524 [ # # ]: 0 : ereport(ERROR,
525 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
526 : : errmsg("requested proto_version=%d does not support parallel streaming, need %d or higher",
527 : : data->protocol_version, LOGICALREP_PROTO_STREAM_PARALLEL_VERSION_NUM)));
528 [ - + ]: 444 : else if (!ctx->streaming)
529 [ # # ]: 0 : ereport(ERROR,
530 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
531 : : errmsg("streaming requested, but not supported by output plugin")));
532 : :
533 : : /*
534 : : * Here, we just check whether the two-phase option is passed by
535 : : * plugin and decide whether to enable it at later point of time. It
536 : : * remains enabled if the previous start-up has done so. But we only
537 : : * allow the option to be passed in with sufficient version of the
538 : : * protocol, and when the output plugin supports it.
539 : : */
540 [ + + ]: 466 : if (!data->two_phase)
541 : 458 : ctx->twophase_opt_given = false;
542 [ - + ]: 8 : else if (data->protocol_version < LOGICALREP_PROTO_TWOPHASE_VERSION_NUM)
543 [ # # ]: 0 : ereport(ERROR,
544 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
545 : : errmsg("requested proto_version=%d does not support two-phase commit, need %d or higher",
546 : : data->protocol_version, LOGICALREP_PROTO_TWOPHASE_VERSION_NUM)));
547 [ - + ]: 8 : else if (!ctx->twophase)
548 [ # # ]: 0 : ereport(ERROR,
549 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
550 : : errmsg("two-phase commit requested, but not supported by output plugin")));
551 : : else
552 : 8 : ctx->twophase_opt_given = true;
553 : :
554 : : /* Init publication state. */
555 : 466 : data->publications = NIL;
556 : 466 : publications_valid = false;
557 : :
558 : : /*
559 : : * Register callback for pg_publication if we didn't already do that
560 : : * during some previous call in this process.
561 : : */
562 [ + + ]: 466 : if (!publication_callback_registered)
563 : : {
564 : 464 : CacheRegisterSyscacheCallback(PUBLICATIONOID,
565 : : publication_invalidation_cb,
566 : : (Datum) 0);
567 : 464 : CacheRegisterRelSyncCallback(rel_sync_cache_relation_cb,
568 : : (Datum) 0);
569 : 464 : publication_callback_registered = true;
570 : : }
571 : :
572 : : /* Initialize relation schema cache. */
573 : 466 : init_rel_sync_cache(CacheMemoryContext);
574 : : }
575 : : else
576 : : {
577 : : /*
578 : : * Disable the streaming and prepared transactions during the slot
579 : : * initialization mode.
580 : : */
581 : 359 : ctx->streaming = false;
582 : 359 : ctx->twophase = false;
583 : : }
584 : 825 : }
585 : :
586 : : /*
587 : : * BEGIN callback.
588 : : *
589 : : * Don't send the BEGIN message here instead postpone it until the first
590 : : * change. In logical replication, a common scenario is to replicate a set of
591 : : * tables (instead of all tables) and transactions whose changes were on
592 : : * the table(s) that are not published will produce empty transactions. These
593 : : * empty transactions will send BEGIN and COMMIT messages to subscribers,
594 : : * using bandwidth on something with little/no use for logical replication.
595 : : */
596 : : static void
597 : 1400 : pgoutput_begin_txn(LogicalDecodingContext *ctx, ReorderBufferTXN *txn)
598 : : {
599 : 1400 : PGOutputTxnData *txndata = MemoryContextAllocZero(ctx->context,
600 : : sizeof(PGOutputTxnData));
601 : :
602 : 1400 : txn->output_plugin_private = txndata;
603 : 1400 : }
604 : :
605 : : /*
606 : : * Send BEGIN.
607 : : *
608 : : * This is called while processing the first change of the transaction.
609 : : */
610 : : static void
611 : 515 : pgoutput_send_begin(LogicalDecodingContext *ctx, ReorderBufferTXN *txn)
612 : : {
613 : 515 : bool send_replication_origin = txn->origin_id != InvalidReplOriginId;
614 : 515 : PGOutputTxnData *txndata = (PGOutputTxnData *) txn->output_plugin_private;
615 : :
616 : : Assert(txndata);
617 : : Assert(!txndata->sent_begin_txn);
618 : :
619 : 515 : OutputPluginPrepareWrite(ctx, !send_replication_origin);
620 : 515 : logicalrep_write_begin(ctx->out, txn);
621 : 515 : txndata->sent_begin_txn = true;
622 : :
623 : 515 : send_repl_origin(ctx, txn->origin_id, txn->origin_lsn,
624 : : send_replication_origin);
625 : :
626 : 515 : OutputPluginWrite(ctx, true);
627 : 515 : }
628 : :
629 : : /*
630 : : * COMMIT callback
631 : : */
632 : : static void
633 : 1397 : pgoutput_commit_txn(LogicalDecodingContext *ctx, ReorderBufferTXN *txn,
634 : : XLogRecPtr commit_lsn)
635 : : {
636 : 1397 : PGOutputTxnData *txndata = (PGOutputTxnData *) txn->output_plugin_private;
637 : : bool sent_begin_txn;
638 : :
639 : : Assert(txndata);
640 : :
641 : : /*
642 : : * We don't need to send the commit message unless some relevant change
643 : : * from this transaction has been sent to the downstream.
644 : : */
645 : 1397 : sent_begin_txn = txndata->sent_begin_txn;
646 : 1397 : OutputPluginUpdateProgress(ctx, !sent_begin_txn);
647 : 1397 : pfree(txndata);
648 : 1397 : txn->output_plugin_private = NULL;
649 : :
650 [ + + ]: 1397 : if (!sent_begin_txn)
651 : : {
652 [ + + ]: 883 : elog(DEBUG1, "skipped replication of an empty transaction with XID: %u", txn->xid);
653 : 883 : return;
654 : : }
655 : :
656 : 514 : OutputPluginPrepareWrite(ctx, true);
657 : 514 : logicalrep_write_commit(ctx->out, txn, commit_lsn);
658 : 514 : OutputPluginWrite(ctx, true);
659 : : }
660 : :
661 : : /*
662 : : * BEGIN PREPARE callback
663 : : */
664 : : static void
665 : 20 : pgoutput_begin_prepare_txn(LogicalDecodingContext *ctx, ReorderBufferTXN *txn)
666 : : {
667 : 20 : bool send_replication_origin = txn->origin_id != InvalidReplOriginId;
668 : :
669 : 20 : OutputPluginPrepareWrite(ctx, !send_replication_origin);
670 : 20 : logicalrep_write_begin_prepare(ctx->out, txn);
671 : :
672 : 20 : send_repl_origin(ctx, txn->origin_id, txn->origin_lsn,
673 : : send_replication_origin);
674 : :
675 : 20 : OutputPluginWrite(ctx, true);
676 : 20 : }
677 : :
678 : : /*
679 : : * PREPARE callback
680 : : */
681 : : static void
682 : 20 : pgoutput_prepare_txn(LogicalDecodingContext *ctx, ReorderBufferTXN *txn,
683 : : XLogRecPtr prepare_lsn)
684 : : {
685 : 20 : OutputPluginUpdateProgress(ctx, false);
686 : :
687 : 20 : OutputPluginPrepareWrite(ctx, true);
688 : 20 : logicalrep_write_prepare(ctx->out, txn, prepare_lsn);
689 : 20 : OutputPluginWrite(ctx, true);
690 : 20 : }
691 : :
692 : : /*
693 : : * COMMIT PREPARED callback
694 : : */
695 : : static void
696 : 25 : pgoutput_commit_prepared_txn(LogicalDecodingContext *ctx, ReorderBufferTXN *txn,
697 : : XLogRecPtr commit_lsn)
698 : : {
699 : 25 : OutputPluginUpdateProgress(ctx, false);
700 : :
701 : 25 : OutputPluginPrepareWrite(ctx, true);
702 : 25 : logicalrep_write_commit_prepared(ctx->out, txn, commit_lsn);
703 : 25 : OutputPluginWrite(ctx, true);
704 : 25 : }
705 : :
706 : : /*
707 : : * ROLLBACK PREPARED callback
708 : : */
709 : : static void
710 : 7 : pgoutput_rollback_prepared_txn(LogicalDecodingContext *ctx,
711 : : ReorderBufferTXN *txn,
712 : : XLogRecPtr prepare_end_lsn,
713 : : TimestampTz prepare_time)
714 : : {
715 : 7 : OutputPluginUpdateProgress(ctx, false);
716 : :
717 : 7 : OutputPluginPrepareWrite(ctx, true);
718 : 7 : logicalrep_write_rollback_prepared(ctx->out, txn, prepare_end_lsn,
719 : : prepare_time);
720 : 7 : OutputPluginWrite(ctx, true);
721 : 7 : }
722 : :
723 : : /*
724 : : * Write the current schema of the relation and its ancestor (if any) if not
725 : : * done yet.
726 : : */
727 : : static void
728 : 202724 : maybe_send_schema(LogicalDecodingContext *ctx,
729 : : ReorderBufferChange *change,
730 : : Relation relation, RelationSyncEntry *relentry)
731 : : {
732 : 202724 : PGOutputData *data = (PGOutputData *) ctx->output_plugin_private;
733 : : bool schema_sent;
734 : 202724 : TransactionId xid = InvalidTransactionId;
735 : 202724 : TransactionId topxid = InvalidTransactionId;
736 : :
737 : : /*
738 : : * Remember XID of the (sub)transaction for the change. We don't care if
739 : : * it's top-level transaction or not (we have already sent that XID in
740 : : * start of the current streaming block).
741 : : *
742 : : * If we're not in a streaming block, just use InvalidTransactionId and
743 : : * the write methods will not include it.
744 : : */
745 [ + + ]: 202724 : if (data->in_streaming)
746 : 175923 : xid = change->txn->xid;
747 : :
748 [ + + ]: 202724 : if (rbtxn_is_subtxn(change->txn))
749 [ + - ]: 10169 : topxid = rbtxn_get_toptxn(change->txn)->xid;
750 : : else
751 : 192555 : topxid = xid;
752 : :
753 : : /*
754 : : * Do we need to send the schema? We do track streamed transactions
755 : : * separately, because those may be applied later (and the regular
756 : : * transactions won't see their effects until then) and in an order that
757 : : * we don't know at this point.
758 : : *
759 : : * XXX There is a scope of optimization here. Currently, we always send
760 : : * the schema first time in a streaming transaction but we can probably
761 : : * avoid that by checking 'relentry->schema_sent' flag. However, before
762 : : * doing that we need to study its impact on the case where we have a mix
763 : : * of streaming and non-streaming transactions.
764 : : */
765 [ + + ]: 202724 : if (data->in_streaming)
766 : 175923 : schema_sent = get_schema_sent_in_streamed_txn(relentry, topxid);
767 : : else
768 : 26801 : schema_sent = relentry->schema_sent;
769 : :
770 : : /* Nothing to do if we already sent the schema. */
771 [ + + ]: 202724 : if (schema_sent)
772 : 202333 : return;
773 : :
774 : : /*
775 : : * Send the schema. If the changes will be published using an ancestor's
776 : : * schema, not the relation's own, send that ancestor's schema before
777 : : * sending relation's own (XXX - maybe sending only the former suffices?).
778 : : */
779 [ + + ]: 391 : if (relentry->publish_as_relid != RelationGetRelid(relation))
780 : : {
781 : 39 : Relation ancestor = RelationIdGetRelation(relentry->publish_as_relid);
782 : :
783 : 39 : send_relation_and_attrs(ancestor, xid, ctx, relentry);
784 : 39 : RelationClose(ancestor);
785 : : }
786 : :
787 : 391 : send_relation_and_attrs(relation, xid, ctx, relentry);
788 : :
789 [ + + ]: 391 : if (data->in_streaming)
790 : 70 : set_schema_sent_in_streamed_txn(relentry, topxid);
791 : : else
792 : 321 : relentry->schema_sent = true;
793 : : }
794 : :
795 : : /*
796 : : * Sends a relation
797 : : */
798 : : static void
799 : 430 : send_relation_and_attrs(Relation relation, TransactionId xid,
800 : : LogicalDecodingContext *ctx,
801 : : RelationSyncEntry *relentry)
802 : : {
803 : 430 : TupleDesc desc = RelationGetDescr(relation);
804 : 430 : Bitmapset *columns = relentry->columns;
805 : 430 : PublishGencolsType include_gencols_type = relentry->include_gencols_type;
806 : : int i;
807 : :
808 : : /*
809 : : * Write out type info if needed. We do that only for user-created types.
810 : : * We use FirstGenbkiObjectId as the cutoff, so that we only consider
811 : : * objects with hand-assigned OIDs to be "built in", not for instance any
812 : : * function or type defined in the information_schema. This is important
813 : : * because only hand-assigned OIDs can be expected to remain stable across
814 : : * major versions.
815 : : */
816 [ + + ]: 1321 : for (i = 0; i < desc->natts; i++)
817 : : {
818 : 891 : Form_pg_attribute att = TupleDescAttr(desc, i);
819 : :
820 [ + + ]: 891 : if (!logicalrep_should_publish_column(att, columns,
821 : : include_gencols_type))
822 : 71 : continue;
823 : :
824 [ + + ]: 820 : if (att->atttypid < FirstGenbkiObjectId)
825 : 802 : continue;
826 : :
827 : 18 : OutputPluginPrepareWrite(ctx, false);
828 : 18 : logicalrep_write_typ(ctx->out, xid, att->atttypid);
829 : 18 : OutputPluginWrite(ctx, false);
830 : : }
831 : :
832 : 430 : OutputPluginPrepareWrite(ctx, false);
833 : 430 : logicalrep_write_rel(ctx->out, xid, relation, columns,
834 : : include_gencols_type);
835 : 430 : OutputPluginWrite(ctx, false);
836 : 430 : }
837 : :
838 : : /*
839 : : * Executor state preparation for evaluation of row filter expressions for the
840 : : * specified relation.
841 : : */
842 : : static EState *
843 : 18 : create_estate_for_relation(Relation rel)
844 : : {
845 : : EState *estate;
846 : : RangeTblEntry *rte;
847 : 18 : List *perminfos = NIL;
848 : :
849 : 18 : estate = CreateExecutorState();
850 : :
851 : 18 : rte = makeNode(RangeTblEntry);
852 : 18 : rte->rtekind = RTE_RELATION;
853 : 18 : rte->relid = RelationGetRelid(rel);
854 : 18 : rte->relkind = rel->rd_rel->relkind;
855 : 18 : rte->rellockmode = AccessShareLock;
856 : :
857 : 18 : addRTEPermissionInfo(&perminfos, rte);
858 : :
859 : 18 : ExecInitRangeTable(estate, list_make1(rte), perminfos,
860 : : bms_make_singleton(1));
861 : :
862 : 18 : estate->es_output_cid = GetCurrentCommandId(false);
863 : :
864 : 18 : return estate;
865 : : }
866 : :
867 : : /*
868 : : * Evaluates row filter.
869 : : *
870 : : * If the row filter evaluates to NULL, it is taken as false i.e. the change
871 : : * isn't replicated.
872 : : */
873 : : static bool
874 : 39 : pgoutput_row_filter_exec_expr(ExprState *state, ExprContext *econtext)
875 : : {
876 : : Datum ret;
877 : : bool isnull;
878 : :
879 : : Assert(state != NULL);
880 : :
881 : 39 : ret = ExecEvalExprSwitchContext(state, econtext, &isnull);
882 : :
883 [ - + - - : 38 : elog(DEBUG3, "row filter evaluates to %s (isnull: %s)",
- - - - ]
884 : : isnull ? "false" : DatumGetBool(ret) ? "true" : "false",
885 : : isnull ? "true" : "false");
886 : :
887 [ + + ]: 38 : if (isnull)
888 : 1 : return false;
889 : :
890 : 37 : return DatumGetBool(ret);
891 : : }
892 : :
893 : : /*
894 : : * Make sure the per-entry memory context exists.
895 : : */
896 : : static void
897 : 375 : pgoutput_ensure_entry_cxt(PGOutputData *data, RelationSyncEntry *entry)
898 : : {
899 : : Relation relation;
900 : :
901 : : /* The context may already exist, in which case bail out. */
902 [ + + ]: 375 : if (entry->entry_cxt)
903 : 18 : return;
904 : :
905 : 357 : relation = RelationIdGetRelation(entry->publish_as_relid);
906 : :
907 : 357 : entry->entry_cxt = AllocSetContextCreate(data->cachectx,
908 : : "entry private context",
909 : : ALLOCSET_SMALL_SIZES);
910 : :
911 : 357 : MemoryContextCopyAndSetIdentifier(entry->entry_cxt,
912 : : RelationGetRelationName(relation));
913 : : }
914 : :
915 : : /*
916 : : * Initialize the row filter.
917 : : */
918 : : static void
919 : 357 : pgoutput_row_filter_init(PGOutputData *data, List *publications,
920 : : RelationSyncEntry *entry)
921 : : {
922 : : ListCell *lc;
923 : 357 : List *rfnodes[] = {NIL, NIL, NIL}; /* One per pubaction */
924 : 357 : bool no_filter[] = {false, false, false}; /* One per pubaction */
925 : : MemoryContext oldctx;
926 : : int idx;
927 : 357 : bool has_filter = true;
928 : 357 : Oid schemaid = get_rel_namespace(entry->publish_as_relid);
929 : :
930 : : /*
931 : : * Find if there are any row filters for this relation. If there are, then
932 : : * prepare the necessary ExprState and cache it in entry->exprstate. To
933 : : * build an expression state, we need to ensure the following:
934 : : *
935 : : * All the given publication-table mappings must be checked.
936 : : *
937 : : * Multiple publications might have multiple row filters for this
938 : : * relation. Since row filter usage depends on the DML operation, there
939 : : * are multiple lists (one for each operation) to which row filters will
940 : : * be appended.
941 : : *
942 : : * FOR ALL TABLES and FOR TABLES IN SCHEMA implies "don't use row filter
943 : : * expression" so it takes precedence.
944 : : */
945 [ + - + + : 379 : foreach(lc, publications)
+ + ]
946 : : {
947 : 361 : Publication *pub = lfirst(lc);
948 : 361 : HeapTuple rftuple = NULL;
949 : 361 : Datum rfdatum = 0;
950 : 361 : bool pub_no_filter = true;
951 : :
952 : : /*
953 : : * If the publication is FOR ALL TABLES, or the publication includes a
954 : : * FOR TABLES IN SCHEMA where the table belongs to the referred
955 : : * schema, then it is treated the same as if there are no row filters
956 : : * (even if other publications have a row filter).
957 : : */
958 [ + + ]: 361 : if (!pub->alltables &&
959 [ + + ]: 265 : !SearchSysCacheExists2(PUBLICATIONNAMESPACEMAP,
960 : : ObjectIdGetDatum(schemaid),
961 : : ObjectIdGetDatum(pub->oid)))
962 : : {
963 : : /*
964 : : * Check for the presence of a row filter in this publication.
965 : : */
966 : 258 : rftuple = SearchSysCache2(PUBLICATIONRELMAP,
967 : : ObjectIdGetDatum(entry->publish_as_relid),
968 : : ObjectIdGetDatum(pub->oid));
969 : :
970 [ + + ]: 258 : if (HeapTupleIsValid(rftuple))
971 : : {
972 : : /* Null indicates no filter. */
973 : 246 : rfdatum = SysCacheGetAttr(PUBLICATIONRELMAP, rftuple,
974 : : Anum_pg_publication_rel_prqual,
975 : : &pub_no_filter);
976 : : }
977 : : }
978 : :
979 [ + + ]: 361 : if (pub_no_filter)
980 : : {
981 [ + + ]: 346 : if (rftuple)
982 : 231 : ReleaseSysCache(rftuple);
983 : :
984 : 346 : no_filter[PUBACTION_INSERT] |= pub->pubactions.pubinsert;
985 : 346 : no_filter[PUBACTION_UPDATE] |= pub->pubactions.pubupdate;
986 : 346 : no_filter[PUBACTION_DELETE] |= pub->pubactions.pubdelete;
987 : :
988 : : /*
989 : : * Quick exit if all the DML actions are publicized via this
990 : : * publication.
991 : : */
992 [ + - ]: 346 : if (no_filter[PUBACTION_INSERT] &&
993 [ + + ]: 346 : no_filter[PUBACTION_UPDATE] &&
994 [ + - ]: 339 : no_filter[PUBACTION_DELETE])
995 : : {
996 : 339 : has_filter = false;
997 : 339 : break;
998 : : }
999 : :
1000 : : /* No additional work for this publication. Next one. */
1001 : 7 : continue;
1002 : : }
1003 : :
1004 : : /* Form the per pubaction row filter lists. */
1005 [ + - + - ]: 15 : if (pub->pubactions.pubinsert && !no_filter[PUBACTION_INSERT])
1006 : 15 : rfnodes[PUBACTION_INSERT] = lappend(rfnodes[PUBACTION_INSERT],
1007 : 15 : TextDatumGetCString(rfdatum));
1008 [ + - + - ]: 15 : if (pub->pubactions.pubupdate && !no_filter[PUBACTION_UPDATE])
1009 : 15 : rfnodes[PUBACTION_UPDATE] = lappend(rfnodes[PUBACTION_UPDATE],
1010 : 15 : TextDatumGetCString(rfdatum));
1011 [ + - + - ]: 15 : if (pub->pubactions.pubdelete && !no_filter[PUBACTION_DELETE])
1012 : 15 : rfnodes[PUBACTION_DELETE] = lappend(rfnodes[PUBACTION_DELETE],
1013 : 15 : TextDatumGetCString(rfdatum));
1014 : :
1015 : 15 : ReleaseSysCache(rftuple);
1016 : : } /* loop all subscribed publications */
1017 : :
1018 : : /* Clean the row filter */
1019 [ + + ]: 1428 : for (idx = 0; idx < NUM_ROWFILTER_PUBACTIONS; idx++)
1020 : : {
1021 [ + + ]: 1071 : if (no_filter[idx])
1022 : : {
1023 : 1026 : list_free_deep(rfnodes[idx]);
1024 : 1026 : rfnodes[idx] = NIL;
1025 : : }
1026 : : }
1027 : :
1028 [ + + ]: 357 : if (has_filter)
1029 : : {
1030 : 18 : Relation relation = RelationIdGetRelation(entry->publish_as_relid);
1031 : :
1032 : 18 : pgoutput_ensure_entry_cxt(data, entry);
1033 : :
1034 : : /*
1035 : : * Now all the filters for all pubactions are known. Combine them when
1036 : : * their pubactions are the same.
1037 : : */
1038 : 18 : oldctx = MemoryContextSwitchTo(entry->entry_cxt);
1039 : 18 : entry->estate = create_estate_for_relation(relation);
1040 [ + + ]: 72 : for (idx = 0; idx < NUM_ROWFILTER_PUBACTIONS; idx++)
1041 : : {
1042 : 54 : List *filters = NIL;
1043 : : Expr *rfnode;
1044 : :
1045 [ + + ]: 54 : if (rfnodes[idx] == NIL)
1046 : 21 : continue;
1047 : :
1048 [ + - + + : 69 : foreach(lc, rfnodes[idx])
+ + ]
1049 : 36 : filters = lappend(filters, expand_generated_columns_in_expr(stringToNode((char *) lfirst(lc)), relation, 1));
1050 : :
1051 : : /* combine the row filter and cache the ExprState */
1052 : 33 : rfnode = make_orclause(filters);
1053 : 33 : entry->exprstate[idx] = ExecPrepareExpr(rfnode, entry->estate);
1054 : : } /* for each pubaction */
1055 : 18 : MemoryContextSwitchTo(oldctx);
1056 : :
1057 : 18 : RelationClose(relation);
1058 : : }
1059 : 357 : }
1060 : :
1061 : : /*
1062 : : * If the table contains a generated column, check for any conflicting
1063 : : * values of 'publish_generated_columns' parameter in the publications.
1064 : : */
1065 : : static void
1066 : 357 : check_and_init_gencol(PGOutputData *data, List *publications,
1067 : : RelationSyncEntry *entry)
1068 : : {
1069 : 357 : Relation relation = RelationIdGetRelation(entry->publish_as_relid);
1070 : 357 : TupleDesc desc = RelationGetDescr(relation);
1071 : 357 : bool gencolpresent = false;
1072 : 357 : bool first = true;
1073 : :
1074 : : /* Check if there is any generated column present. */
1075 [ + + ]: 1090 : for (int i = 0; i < desc->natts; i++)
1076 : : {
1077 : 740 : CompactAttribute *att = TupleDescCompactAttr(desc, i);
1078 : :
1079 [ + + ]: 740 : if (att->attgenerated)
1080 : : {
1081 : 7 : gencolpresent = true;
1082 : 7 : break;
1083 : : }
1084 : : }
1085 : :
1086 : : /* There are no generated columns to be published. */
1087 [ + + ]: 357 : if (!gencolpresent)
1088 : : {
1089 : 350 : entry->include_gencols_type = PUBLISH_GENCOLS_NONE;
1090 : 350 : return;
1091 : : }
1092 : :
1093 : : /*
1094 : : * There may be a conflicting value for 'publish_generated_columns'
1095 : : * parameter in the publications.
1096 : : */
1097 [ + - + + : 22 : foreach_ptr(Publication, pub, publications)
+ + ]
1098 : : {
1099 : : /*
1100 : : * The column list takes precedence over the
1101 : : * 'publish_generated_columns' parameter. Those will be checked later,
1102 : : * see pgoutput_column_list_init.
1103 : : */
1104 [ + + ]: 8 : if (check_and_fetch_column_list(pub, entry->publish_as_relid, NULL, NULL))
1105 : 3 : continue;
1106 : :
1107 [ + - ]: 5 : if (first)
1108 : : {
1109 : 5 : entry->include_gencols_type = pub->pubgencols_type;
1110 : 5 : first = false;
1111 : : }
1112 [ # # ]: 0 : else if (entry->include_gencols_type != pub->pubgencols_type)
1113 [ # # ]: 0 : ereport(ERROR,
1114 : : errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
1115 : : errmsg("cannot use different values of publish_generated_columns for table \"%s.%s\" in different publications",
1116 : : get_namespace_name(RelationGetNamespace(relation)),
1117 : : RelationGetRelationName(relation)));
1118 : : }
1119 : : }
1120 : :
1121 : : /*
1122 : : * Initialize the column list.
1123 : : */
1124 : : static void
1125 : 357 : pgoutput_column_list_init(PGOutputData *data, List *publications,
1126 : : RelationSyncEntry *entry)
1127 : : {
1128 : : ListCell *lc;
1129 : 357 : bool first = true;
1130 : 357 : Relation relation = RelationIdGetRelation(entry->publish_as_relid);
1131 : 357 : bool found_pub_collist = false;
1132 : 357 : Bitmapset *relcols = NULL;
1133 : :
1134 : 357 : pgoutput_ensure_entry_cxt(data, entry);
1135 : :
1136 : : /*
1137 : : * Find if there are any column lists for this relation. If there are,
1138 : : * build a bitmap using the column lists.
1139 : : *
1140 : : * Multiple publications might have multiple column lists for this
1141 : : * relation.
1142 : : *
1143 : : * Note that we don't support the case where the column list is different
1144 : : * for the same table when combining publications. See comments atop
1145 : : * fetch_relation_list. But one can later change the publication so we
1146 : : * still need to check all the given publication-table mappings and report
1147 : : * an error if any publications have a different column list.
1148 : : */
1149 [ + - + + : 724 : foreach(lc, publications)
+ + ]
1150 : : {
1151 : 368 : Publication *pub = lfirst(lc);
1152 : 368 : Bitmapset *cols = NULL;
1153 : :
1154 : : /* Retrieve the bitmap of columns for a column list publication. */
1155 : 368 : found_pub_collist |= check_and_fetch_column_list(pub,
1156 : : entry->publish_as_relid,
1157 : : entry->entry_cxt, &cols);
1158 : :
1159 : : /*
1160 : : * For non-column list publications — e.g. TABLE (without a column
1161 : : * list), ALL TABLES, or ALL TABLES IN SCHEMA, we consider all columns
1162 : : * of the table (including generated columns when
1163 : : * 'publish_generated_columns' parameter is true).
1164 : : */
1165 [ + + ]: 368 : if (!cols)
1166 : : {
1167 : : /*
1168 : : * Cache the table columns for the first publication with no
1169 : : * specified column list to detect publication with a different
1170 : : * column list.
1171 : : */
1172 [ + + + + ]: 329 : if (!relcols && (list_length(publications) > 1))
1173 : : {
1174 : 9 : MemoryContext oldcxt = MemoryContextSwitchTo(entry->entry_cxt);
1175 : :
1176 : 9 : relcols = pub_form_cols_map(relation,
1177 : : entry->include_gencols_type);
1178 : 9 : MemoryContextSwitchTo(oldcxt);
1179 : : }
1180 : :
1181 : 329 : cols = relcols;
1182 : : }
1183 : :
1184 [ + + ]: 368 : if (first)
1185 : : {
1186 : 357 : entry->columns = cols;
1187 : 357 : first = false;
1188 : : }
1189 [ + + ]: 11 : else if (!bms_equal(entry->columns, cols))
1190 [ + - ]: 1 : ereport(ERROR,
1191 : : errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
1192 : : errmsg("cannot use different column lists for table \"%s.%s\" in different publications",
1193 : : get_namespace_name(RelationGetNamespace(relation)),
1194 : : RelationGetRelationName(relation)));
1195 : : } /* loop all subscribed publications */
1196 : :
1197 : : /*
1198 : : * If no column list publications exist, columns to be published will be
1199 : : * computed later according to the 'publish_generated_columns' parameter.
1200 : : */
1201 [ + + ]: 356 : if (!found_pub_collist)
1202 : 320 : entry->columns = NULL;
1203 : :
1204 : 356 : RelationClose(relation);
1205 : 356 : }
1206 : :
1207 : : /*
1208 : : * Initialize the slot for storing new and old tuples, and build the map that
1209 : : * will be used to convert the relation's tuples into the ancestor's format.
1210 : : */
1211 : : static void
1212 : 357 : init_tuple_slot(PGOutputData *data, Relation relation,
1213 : : RelationSyncEntry *entry)
1214 : : {
1215 : : MemoryContext oldctx;
1216 : : TupleDesc oldtupdesc;
1217 : : TupleDesc newtupdesc;
1218 : :
1219 : 357 : oldctx = MemoryContextSwitchTo(data->cachectx);
1220 : :
1221 : : /*
1222 : : * Create tuple table slots. Create a copy of the TupleDesc as it needs to
1223 : : * live as long as the cache remains.
1224 : : */
1225 : 357 : oldtupdesc = CreateTupleDescCopyConstr(RelationGetDescr(relation));
1226 : 357 : newtupdesc = CreateTupleDescCopyConstr(RelationGetDescr(relation));
1227 : :
1228 : 357 : entry->old_slot = MakeSingleTupleTableSlot(oldtupdesc, &TTSOpsHeapTuple);
1229 : 357 : entry->new_slot = MakeSingleTupleTableSlot(newtupdesc, &TTSOpsHeapTuple);
1230 : :
1231 : 357 : MemoryContextSwitchTo(oldctx);
1232 : :
1233 : : /*
1234 : : * Cache the map that will be used to convert the relation's tuples into
1235 : : * the ancestor's format, if needed.
1236 : : */
1237 [ + + ]: 357 : if (entry->publish_as_relid != RelationGetRelid(relation))
1238 : : {
1239 : 43 : Relation ancestor = RelationIdGetRelation(entry->publish_as_relid);
1240 : 43 : TupleDesc indesc = RelationGetDescr(relation);
1241 : 43 : TupleDesc outdesc = RelationGetDescr(ancestor);
1242 : :
1243 : : /* Map must live as long as the logical decoding context. */
1244 : 43 : oldctx = MemoryContextSwitchTo(data->cachectx);
1245 : :
1246 : 43 : entry->attrmap = build_attrmap_by_name_if_req(indesc, outdesc, false);
1247 : :
1248 : 43 : MemoryContextSwitchTo(oldctx);
1249 : 43 : RelationClose(ancestor);
1250 : : }
1251 : 357 : }
1252 : :
1253 : : /*
1254 : : * Change is checked against the row filter if any.
1255 : : *
1256 : : * Returns true if the change is to be replicated, else false.
1257 : : *
1258 : : * For inserts, evaluate the row filter for new tuple.
1259 : : * For deletes, evaluate the row filter for old tuple.
1260 : : * For updates, evaluate the row filter for old and new tuple.
1261 : : *
1262 : : * For updates, if both evaluations are true, we allow sending the UPDATE and
1263 : : * if both the evaluations are false, it doesn't replicate the UPDATE. Now, if
1264 : : * only one of the tuples matches the row filter expression, we transform
1265 : : * UPDATE to DELETE or INSERT to avoid any data inconsistency based on the
1266 : : * following rules:
1267 : : *
1268 : : * Case 1: old-row (no match) new-row (no match) -> (drop change)
1269 : : * Case 2: old-row (no match) new row (match) -> INSERT
1270 : : * Case 3: old-row (match) new-row (no match) -> DELETE
1271 : : * Case 4: old-row (match) new row (match) -> UPDATE
1272 : : *
1273 : : * The new action is updated in the action parameter.
1274 : : *
1275 : : * The new slot could be updated when transforming the UPDATE into INSERT,
1276 : : * because the original new tuple might not have column values from the replica
1277 : : * identity.
1278 : : *
1279 : : * Examples:
1280 : : * Let's say the old tuple satisfies the row filter but the new tuple doesn't.
1281 : : * Since the old tuple satisfies, the initial table synchronization copied this
1282 : : * row (or another method was used to guarantee that there is data
1283 : : * consistency). However, after the UPDATE the new tuple doesn't satisfy the
1284 : : * row filter, so from a data consistency perspective, that row should be
1285 : : * removed on the subscriber. The UPDATE should be transformed into a DELETE
1286 : : * statement and be sent to the subscriber. Keeping this row on the subscriber
1287 : : * is undesirable because it doesn't reflect what was defined in the row filter
1288 : : * expression on the publisher. This row on the subscriber would likely not be
1289 : : * modified by replication again. If someone inserted a new row with the same
1290 : : * old identifier, replication could stop due to a constraint violation.
1291 : : *
1292 : : * Let's say the old tuple doesn't match the row filter but the new tuple does.
1293 : : * Since the old tuple doesn't satisfy, the initial table synchronization
1294 : : * probably didn't copy this row. However, after the UPDATE the new tuple does
1295 : : * satisfy the row filter, so from a data consistency perspective, that row
1296 : : * should be inserted on the subscriber. Otherwise, subsequent UPDATE or DELETE
1297 : : * statements have no effect (it matches no row -- see
1298 : : * apply_handle_update_internal()). So, the UPDATE should be transformed into a
1299 : : * INSERT statement and be sent to the subscriber. However, this might surprise
1300 : : * someone who expects the data set to satisfy the row filter expression on the
1301 : : * provider.
1302 : : */
1303 : : static bool
1304 : 202719 : pgoutput_row_filter(Relation relation, TupleTableSlot *old_slot,
1305 : : TupleTableSlot **new_slot_ptr, RelationSyncEntry *entry,
1306 : : ReorderBufferChangeType *action)
1307 : : {
1308 : : TupleDesc desc;
1309 : : int i;
1310 : : bool old_matched,
1311 : : new_matched,
1312 : : result;
1313 : : TupleTableSlot *tmp_new_slot;
1314 : 202719 : TupleTableSlot *new_slot = *new_slot_ptr;
1315 : : ExprContext *ecxt;
1316 : : ExprState *filter_exprstate;
1317 : :
1318 : : /*
1319 : : * We need this map to avoid relying on ReorderBufferChangeType enums
1320 : : * having specific values.
1321 : : */
1322 : : static const int map_changetype_pubaction[] = {
1323 : : [REORDER_BUFFER_CHANGE_INSERT] = PUBACTION_INSERT,
1324 : : [REORDER_BUFFER_CHANGE_UPDATE] = PUBACTION_UPDATE,
1325 : : [REORDER_BUFFER_CHANGE_DELETE] = PUBACTION_DELETE
1326 : : };
1327 : :
1328 : : Assert(*action == REORDER_BUFFER_CHANGE_INSERT ||
1329 : : *action == REORDER_BUFFER_CHANGE_UPDATE ||
1330 : : *action == REORDER_BUFFER_CHANGE_DELETE);
1331 : :
1332 : : Assert(new_slot || old_slot);
1333 : :
1334 : : /* Get the corresponding row filter */
1335 : 202719 : filter_exprstate = entry->exprstate[map_changetype_pubaction[*action]];
1336 : :
1337 : : /* Bail out if there is no row filter */
1338 [ + + ]: 202719 : if (!filter_exprstate)
1339 : 202684 : return true;
1340 : :
1341 [ - + ]: 35 : elog(DEBUG3, "table \"%s.%s\" has row filter",
1342 : : get_namespace_name(RelationGetNamespace(relation)),
1343 : : RelationGetRelationName(relation));
1344 : :
1345 [ + + ]: 35 : ResetPerTupleExprContext(entry->estate);
1346 : :
1347 [ + + ]: 35 : ecxt = GetPerTupleExprContext(entry->estate);
1348 : :
1349 : : /*
1350 : : * For the following occasions where there is only one tuple, we can
1351 : : * evaluate the row filter for that tuple and return.
1352 : : *
1353 : : * For inserts, we only have the new tuple.
1354 : : *
1355 : : * For updates, we can have only a new tuple when none of the replica
1356 : : * identity columns changed and none of those columns have external data
1357 : : * but we still need to evaluate the row filter for the new tuple as the
1358 : : * existing values of those columns might not match the filter. Also,
1359 : : * users can use constant expressions in the row filter, so we anyway need
1360 : : * to evaluate it for the new tuple.
1361 : : *
1362 : : * For deletes, we only have the old tuple.
1363 : : */
1364 [ + + + + ]: 35 : if (!new_slot || !old_slot)
1365 : : {
1366 [ + + ]: 31 : ecxt->ecxt_scantuple = new_slot ? new_slot : old_slot;
1367 : 31 : result = pgoutput_row_filter_exec_expr(filter_exprstate, ecxt);
1368 : :
1369 : 30 : return result;
1370 : : }
1371 : :
1372 : : /*
1373 : : * Both the old and new tuples must be valid only for updates and need to
1374 : : * be checked against the row filter.
1375 : : */
1376 : : Assert(map_changetype_pubaction[*action] == PUBACTION_UPDATE);
1377 : :
1378 : 4 : slot_getallattrs(new_slot);
1379 : 4 : slot_getallattrs(old_slot);
1380 : :
1381 : 4 : tmp_new_slot = NULL;
1382 : 4 : desc = RelationGetDescr(relation);
1383 : :
1384 : : /*
1385 : : * The new tuple might not have all the replica identity columns, in which
1386 : : * case it needs to be copied over from the old tuple.
1387 : : */
1388 [ + + ]: 12 : for (i = 0; i < desc->natts; i++)
1389 : : {
1390 : 8 : CompactAttribute *att = TupleDescCompactAttr(desc, i);
1391 : :
1392 : : /*
1393 : : * if the column in the new tuple or old tuple is null, nothing to do
1394 : : */
1395 [ + + - + ]: 8 : if (new_slot->tts_isnull[i] || old_slot->tts_isnull[i])
1396 : 1 : continue;
1397 : :
1398 : : /*
1399 : : * Unchanged toasted replica identity columns are only logged in the
1400 : : * old tuple. Copy this over to the new tuple. The changed (or WAL
1401 : : * Logged) toast values are always assembled in memory and set as
1402 : : * VARTAG_INDIRECT. See ReorderBufferToastReplace.
1403 : : */
1404 [ + + + + ]: 11 : if (att->attlen == -1 &&
1405 : 4 : VARATT_IS_EXTERNAL_ONDISK(DatumGetPointer(new_slot->tts_values[i])) &&
1406 [ + - ]: 1 : !VARATT_IS_EXTERNAL_ONDISK(DatumGetPointer(old_slot->tts_values[i])))
1407 : : {
1408 [ + - ]: 1 : if (!tmp_new_slot)
1409 : : {
1410 : 1 : tmp_new_slot = MakeSingleTupleTableSlot(desc, &TTSOpsVirtual);
1411 : 1 : ExecClearTuple(tmp_new_slot);
1412 : :
1413 : 1 : memcpy(tmp_new_slot->tts_values, new_slot->tts_values,
1414 : 1 : desc->natts * sizeof(Datum));
1415 : 1 : memcpy(tmp_new_slot->tts_isnull, new_slot->tts_isnull,
1416 : 1 : desc->natts * sizeof(bool));
1417 : : }
1418 : :
1419 : 1 : tmp_new_slot->tts_values[i] = old_slot->tts_values[i];
1420 : 1 : tmp_new_slot->tts_isnull[i] = old_slot->tts_isnull[i];
1421 : : }
1422 : : }
1423 : :
1424 : 4 : ecxt->ecxt_scantuple = old_slot;
1425 : 4 : old_matched = pgoutput_row_filter_exec_expr(filter_exprstate, ecxt);
1426 : :
1427 [ + + ]: 4 : if (tmp_new_slot)
1428 : : {
1429 : 1 : ExecStoreVirtualTuple(tmp_new_slot);
1430 : 1 : ecxt->ecxt_scantuple = tmp_new_slot;
1431 : : }
1432 : : else
1433 : 3 : ecxt->ecxt_scantuple = new_slot;
1434 : :
1435 : 4 : new_matched = pgoutput_row_filter_exec_expr(filter_exprstate, ecxt);
1436 : :
1437 : : /*
1438 : : * Case 1: if both tuples don't match the row filter, bailout. Send
1439 : : * nothing.
1440 : : */
1441 [ + + - + ]: 4 : if (!old_matched && !new_matched)
1442 : 0 : return false;
1443 : :
1444 : : /*
1445 : : * Case 2: if the old tuple doesn't satisfy the row filter but the new
1446 : : * tuple does, transform the UPDATE into INSERT.
1447 : : *
1448 : : * Use the newly transformed tuple that must contain the column values for
1449 : : * all the replica identity columns. This is required to ensure that the
1450 : : * while inserting the tuple in the downstream node, we have all the
1451 : : * required column values.
1452 : : */
1453 [ + + + - ]: 4 : if (!old_matched && new_matched)
1454 : : {
1455 : 2 : *action = REORDER_BUFFER_CHANGE_INSERT;
1456 : :
1457 [ + + ]: 2 : if (tmp_new_slot)
1458 : 1 : *new_slot_ptr = tmp_new_slot;
1459 : : }
1460 : :
1461 : : /*
1462 : : * Case 3: if the old tuple satisfies the row filter but the new tuple
1463 : : * doesn't, transform the UPDATE into DELETE.
1464 : : *
1465 : : * This transformation does not require another tuple. The Old tuple will
1466 : : * be used for DELETE.
1467 : : */
1468 [ + - + + ]: 2 : else if (old_matched && !new_matched)
1469 : 1 : *action = REORDER_BUFFER_CHANGE_DELETE;
1470 : :
1471 : : /*
1472 : : * Case 4: if both tuples match the row filter, transformation isn't
1473 : : * required. (*action is default UPDATE).
1474 : : */
1475 : :
1476 : 4 : return true;
1477 : : }
1478 : :
1479 : : /*
1480 : : * Sends the decoded DML over wire.
1481 : : *
1482 : : * This is called both in streaming and non-streaming modes.
1483 : : */
1484 : : static void
1485 : 204527 : pgoutput_change(LogicalDecodingContext *ctx, ReorderBufferTXN *txn,
1486 : : Relation relation, ReorderBufferChange *change)
1487 : : {
1488 : 204527 : PGOutputData *data = (PGOutputData *) ctx->output_plugin_private;
1489 : 204527 : PGOutputTxnData *txndata = (PGOutputTxnData *) txn->output_plugin_private;
1490 : : MemoryContext old;
1491 : : RelationSyncEntry *relentry;
1492 : 204527 : TransactionId xid = InvalidTransactionId;
1493 : 204527 : Relation ancestor = NULL;
1494 : 204527 : Relation targetrel = relation;
1495 : 204527 : ReorderBufferChangeType action = change->action;
1496 : 204527 : TupleTableSlot *old_slot = NULL;
1497 : 204527 : TupleTableSlot *new_slot = NULL;
1498 : :
1499 [ - + ]: 204527 : if (!is_publishable_relation(relation))
1500 : 1807 : return;
1501 : :
1502 : : /*
1503 : : * Remember the xid for the change in streaming mode. We need to send xid
1504 : : * with each change in the streaming mode so that subscriber can make
1505 : : * their association and on aborts, it can discard the corresponding
1506 : : * changes.
1507 : : */
1508 [ + + ]: 204527 : if (data->in_streaming)
1509 : 175923 : xid = change->txn->xid;
1510 : :
1511 : 204527 : relentry = get_rel_sync_entry(data, relation);
1512 : :
1513 : : /* First check the table filter */
1514 [ + + + - ]: 204526 : switch (action)
1515 : : {
1516 : 127050 : case REORDER_BUFFER_CHANGE_INSERT:
1517 [ + + ]: 127050 : if (!relentry->pubactions.pubinsert)
1518 : 674 : return;
1519 : 126376 : break;
1520 : 34501 : case REORDER_BUFFER_CHANGE_UPDATE:
1521 [ + + ]: 34501 : if (!relentry->pubactions.pubupdate)
1522 : 44 : return;
1523 : 34457 : break;
1524 : 42975 : case REORDER_BUFFER_CHANGE_DELETE:
1525 [ + + ]: 42975 : if (!relentry->pubactions.pubdelete)
1526 : 1089 : return;
1527 : :
1528 : : /*
1529 : : * This is only possible if deletes are allowed even when replica
1530 : : * identity is not defined for a table. Since the DELETE action
1531 : : * can't be published, we simply return.
1532 : : */
1533 [ - + ]: 41886 : if (!change->data.tp.oldtuple)
1534 : : {
1535 [ # # ]: 0 : elog(DEBUG1, "didn't send DELETE change because of missing oldtuple");
1536 : 0 : return;
1537 : : }
1538 : 41886 : break;
1539 : 202719 : default:
1540 : : Assert(false);
1541 : : }
1542 : :
1543 : : /* Avoid leaking memory by using and resetting our own context */
1544 : 202719 : old = MemoryContextSwitchTo(data->context);
1545 : :
1546 : : /* Switch relation if publishing via root. */
1547 [ + + ]: 202719 : if (relentry->publish_as_relid != RelationGetRelid(relation))
1548 : : {
1549 : : Assert(relation->rd_rel->relispartition);
1550 : 74 : ancestor = RelationIdGetRelation(relentry->publish_as_relid);
1551 : 74 : targetrel = ancestor;
1552 : : }
1553 : :
1554 [ + + ]: 202719 : if (change->data.tp.oldtuple)
1555 : : {
1556 : 42036 : old_slot = relentry->old_slot;
1557 : 42036 : ExecStoreHeapTuple(change->data.tp.oldtuple, old_slot, false);
1558 : :
1559 : : /* Convert tuple if needed. */
1560 [ + + ]: 42036 : if (relentry->attrmap)
1561 : : {
1562 : 5 : TupleTableSlot *slot = MakeTupleTableSlot(RelationGetDescr(targetrel),
1563 : : &TTSOpsVirtual, 0);
1564 : :
1565 : 5 : old_slot = execute_attr_map_slot(relentry->attrmap, old_slot, slot);
1566 : : }
1567 : : }
1568 : :
1569 [ + + ]: 202719 : if (change->data.tp.newtuple)
1570 : : {
1571 : 160833 : new_slot = relentry->new_slot;
1572 : 160833 : ExecStoreHeapTuple(change->data.tp.newtuple, new_slot, false);
1573 : :
1574 : : /* Convert tuple if needed. */
1575 [ + + ]: 160833 : if (relentry->attrmap)
1576 : : {
1577 : 21 : TupleTableSlot *slot = MakeTupleTableSlot(RelationGetDescr(targetrel),
1578 : : &TTSOpsVirtual, 0);
1579 : :
1580 : 21 : new_slot = execute_attr_map_slot(relentry->attrmap, new_slot, slot);
1581 : : }
1582 : : }
1583 : :
1584 : : /*
1585 : : * Check row filter.
1586 : : *
1587 : : * Updates could be transformed to inserts or deletes based on the results
1588 : : * of the row filter for old and new tuple.
1589 : : */
1590 [ + + ]: 202719 : if (!pgoutput_row_filter(targetrel, old_slot, &new_slot, relentry, &action))
1591 : 12 : goto cleanup;
1592 : :
1593 : : /*
1594 : : * Send BEGIN if we haven't yet.
1595 : : *
1596 : : * We send the BEGIN message after ensuring that we will actually send the
1597 : : * change. This avoids sending a pair of BEGIN/COMMIT messages for empty
1598 : : * transactions.
1599 : : */
1600 [ + + + + ]: 202706 : if (txndata && !txndata->sent_begin_txn)
1601 : 501 : pgoutput_send_begin(ctx, txn);
1602 : :
1603 : : /*
1604 : : * Schema should be sent using the original relation because it also sends
1605 : : * the ancestor's relation.
1606 : : */
1607 : 202706 : maybe_send_schema(ctx, change, relation, relentry);
1608 : :
1609 : 202706 : OutputPluginPrepareWrite(ctx, true);
1610 : :
1611 : : /* Send the data */
1612 [ + + + - ]: 202706 : switch (action)
1613 : : {
1614 : 126365 : case REORDER_BUFFER_CHANGE_INSERT:
1615 : 126365 : logicalrep_write_insert(ctx->out, xid, targetrel, new_slot,
1616 : 126365 : data->binary, relentry->columns,
1617 : : relentry->include_gencols_type);
1618 : 126365 : break;
1619 : 34454 : case REORDER_BUFFER_CHANGE_UPDATE:
1620 : 34454 : logicalrep_write_update(ctx->out, xid, targetrel, old_slot,
1621 : 34454 : new_slot, data->binary, relentry->columns,
1622 : : relentry->include_gencols_type);
1623 : 34454 : break;
1624 : 41887 : case REORDER_BUFFER_CHANGE_DELETE:
1625 : 41887 : logicalrep_write_delete(ctx->out, xid, targetrel, old_slot,
1626 : 41887 : data->binary, relentry->columns,
1627 : : relentry->include_gencols_type);
1628 : 41887 : break;
1629 : 202706 : default:
1630 : : Assert(false);
1631 : : }
1632 : :
1633 : 202706 : OutputPluginWrite(ctx, true);
1634 : :
1635 : 202717 : cleanup:
1636 [ + + ]: 202717 : if (RelationIsValid(ancestor))
1637 : : {
1638 : 74 : RelationClose(ancestor);
1639 : 74 : ancestor = NULL;
1640 : : }
1641 : :
1642 : : /* Drop the new slots that were used to store the converted tuples. */
1643 [ + + ]: 202717 : if (relentry->attrmap)
1644 : : {
1645 [ + + ]: 26 : if (old_slot)
1646 : 5 : ExecDropSingleTupleTableSlot(old_slot);
1647 : :
1648 [ + + ]: 26 : if (new_slot)
1649 : 21 : ExecDropSingleTupleTableSlot(new_slot);
1650 : : }
1651 : :
1652 : 202717 : MemoryContextSwitchTo(old);
1653 : 202717 : MemoryContextReset(data->context);
1654 : : }
1655 : :
1656 : : static void
1657 : 28 : pgoutput_truncate(LogicalDecodingContext *ctx, ReorderBufferTXN *txn,
1658 : : int nrelations, Relation relations[], ReorderBufferChange *change)
1659 : : {
1660 : 28 : PGOutputData *data = (PGOutputData *) ctx->output_plugin_private;
1661 : 28 : PGOutputTxnData *txndata = (PGOutputTxnData *) txn->output_plugin_private;
1662 : : MemoryContext old;
1663 : : RelationSyncEntry *relentry;
1664 : : int i;
1665 : : int nrelids;
1666 : : Oid *relids;
1667 : 28 : TransactionId xid = InvalidTransactionId;
1668 : :
1669 : : /* Remember the xid for the change in streaming mode. See pgoutput_change. */
1670 [ - + ]: 28 : if (data->in_streaming)
1671 : 0 : xid = change->txn->xid;
1672 : :
1673 : 28 : old = MemoryContextSwitchTo(data->context);
1674 : :
1675 : 28 : relids = palloc0(nrelations * sizeof(Oid));
1676 : 28 : nrelids = 0;
1677 : :
1678 [ + + ]: 75 : for (i = 0; i < nrelations; i++)
1679 : : {
1680 : 47 : Relation relation = relations[i];
1681 : 47 : Oid relid = RelationGetRelid(relation);
1682 : :
1683 [ - + ]: 47 : if (!is_publishable_relation(relation))
1684 : 0 : continue;
1685 : :
1686 : 47 : relentry = get_rel_sync_entry(data, relation);
1687 : :
1688 [ + + ]: 47 : if (!relentry->pubactions.pubtruncate)
1689 : 26 : continue;
1690 : :
1691 : : /*
1692 : : * Don't send partitions if the publication wants to send only the
1693 : : * root tables through it.
1694 : : */
1695 [ + + ]: 21 : if (relation->rd_rel->relispartition &&
1696 [ + + ]: 15 : relentry->publish_as_relid != relid)
1697 : 3 : continue;
1698 : :
1699 : 18 : relids[nrelids++] = relid;
1700 : :
1701 : : /* Send BEGIN if we haven't yet */
1702 [ + - + + ]: 18 : if (txndata && !txndata->sent_begin_txn)
1703 : 12 : pgoutput_send_begin(ctx, txn);
1704 : :
1705 : 18 : maybe_send_schema(ctx, change, relation, relentry);
1706 : : }
1707 : :
1708 [ + + ]: 28 : if (nrelids > 0)
1709 : : {
1710 : 12 : OutputPluginPrepareWrite(ctx, true);
1711 : 12 : logicalrep_write_truncate(ctx->out,
1712 : : xid,
1713 : : nrelids,
1714 : : relids,
1715 : 12 : change->data.truncate.cascade,
1716 : 12 : change->data.truncate.restart_seqs);
1717 : 12 : OutputPluginWrite(ctx, true);
1718 : : }
1719 : :
1720 : 28 : MemoryContextSwitchTo(old);
1721 : 28 : MemoryContextReset(data->context);
1722 : 28 : }
1723 : :
1724 : : static void
1725 : 7 : pgoutput_message(LogicalDecodingContext *ctx, ReorderBufferTXN *txn,
1726 : : XLogRecPtr message_lsn, bool transactional, const char *prefix, Size sz,
1727 : : const char *message)
1728 : : {
1729 : 7 : PGOutputData *data = (PGOutputData *) ctx->output_plugin_private;
1730 : 7 : TransactionId xid = InvalidTransactionId;
1731 : :
1732 [ + + ]: 7 : if (!data->messages)
1733 : 2 : return;
1734 : :
1735 : : /*
1736 : : * Remember the xid for the message in streaming mode. See
1737 : : * pgoutput_change.
1738 : : */
1739 [ - + ]: 5 : if (data->in_streaming)
1740 : 0 : xid = txn->xid;
1741 : :
1742 : : /*
1743 : : * Output BEGIN if we haven't yet. Avoid for non-transactional messages.
1744 : : */
1745 [ + + ]: 5 : if (transactional)
1746 : : {
1747 : 2 : PGOutputTxnData *txndata = (PGOutputTxnData *) txn->output_plugin_private;
1748 : :
1749 : : /* Send BEGIN if we haven't yet */
1750 [ + - + - ]: 2 : if (txndata && !txndata->sent_begin_txn)
1751 : 2 : pgoutput_send_begin(ctx, txn);
1752 : : }
1753 : :
1754 : 5 : OutputPluginPrepareWrite(ctx, true);
1755 : 5 : logicalrep_write_message(ctx->out,
1756 : : xid,
1757 : : message_lsn,
1758 : : transactional,
1759 : : prefix,
1760 : : sz,
1761 : : message);
1762 : 5 : OutputPluginWrite(ctx, true);
1763 : : }
1764 : :
1765 : : /*
1766 : : * Return true if the data is associated with an origin and the user has
1767 : : * requested the changes that don't have an origin, false otherwise.
1768 : : */
1769 : : static bool
1770 : 533650 : pgoutput_origin_filter(LogicalDecodingContext *ctx,
1771 : : ReplOriginId origin_id)
1772 : : {
1773 : 533650 : PGOutputData *data = (PGOutputData *) ctx->output_plugin_private;
1774 : :
1775 [ + + + + ]: 533650 : if (data->publish_no_origin && origin_id != InvalidReplOriginId)
1776 : 215 : return true;
1777 : :
1778 : 533435 : return false;
1779 : : }
1780 : :
1781 : : /*
1782 : : * Shutdown the output plugin.
1783 : : *
1784 : : * Note, we don't need to clean the data->context, data->cachectx, and
1785 : : * data->pubctx as they are child contexts of the ctx->context so they
1786 : : * will be cleaned up by logical decoding machinery.
1787 : : */
1788 : : static void
1789 : 572 : pgoutput_shutdown(LogicalDecodingContext *ctx)
1790 : : {
1791 : 572 : pgoutput_memory_context_reset(NULL);
1792 : 572 : }
1793 : :
1794 : : /*
1795 : : * Load publications from the list of publication names.
1796 : : *
1797 : : * Here, we skip the publications that don't exist yet. This will allow us
1798 : : * to silently continue the replication in the absence of a missing publication.
1799 : : * This is required because we allow the users to create publications after they
1800 : : * have specified the required publications at the time of replication start.
1801 : : */
1802 : : static List *
1803 : 266 : LoadPublications(List *pubnames)
1804 : : {
1805 : 266 : List *result = NIL;
1806 : : ListCell *lc;
1807 : :
1808 [ + - + + : 587 : foreach(lc, pubnames)
+ + ]
1809 : : {
1810 : 321 : char *pubname = (char *) lfirst(lc);
1811 : 321 : Publication *pub = GetPublicationByName(pubname, true);
1812 : :
1813 [ + + ]: 321 : if (pub)
1814 : 318 : result = lappend(result, pub);
1815 : : else
1816 [ + - ]: 3 : ereport(WARNING,
1817 : : errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
1818 : : errmsg("skipped loading publication \"%s\"", pubname),
1819 : : errdetail("The publication does not exist at this point in the WAL."),
1820 : : errhint("Create the publication if it does not exist."));
1821 : : }
1822 : :
1823 : 266 : return result;
1824 : : }
1825 : :
1826 : : /*
1827 : : * Publication syscache invalidation callback.
1828 : : *
1829 : : * Called for invalidations on pg_publication.
1830 : : */
1831 : : static void
1832 : 393 : publication_invalidation_cb(Datum arg, SysCacheIdentifier cacheid,
1833 : : uint32 hashvalue)
1834 : : {
1835 : 393 : publications_valid = false;
1836 : 393 : }
1837 : :
1838 : : /*
1839 : : * START STREAM callback
1840 : : */
1841 : : static void
1842 : 620 : pgoutput_stream_start(struct LogicalDecodingContext *ctx,
1843 : : ReorderBufferTXN *txn)
1844 : : {
1845 : 620 : PGOutputData *data = (PGOutputData *) ctx->output_plugin_private;
1846 : 620 : bool send_replication_origin = txn->origin_id != InvalidReplOriginId;
1847 : :
1848 : : /* we can't nest streaming of transactions */
1849 : : Assert(!data->in_streaming);
1850 : :
1851 : : /*
1852 : : * If we already sent the first stream for this transaction then don't
1853 : : * send the origin id in the subsequent streams.
1854 : : */
1855 [ + + ]: 620 : if (rbtxn_is_streamed(txn))
1856 : 559 : send_replication_origin = false;
1857 : :
1858 : 620 : OutputPluginPrepareWrite(ctx, !send_replication_origin);
1859 : 620 : logicalrep_write_stream_start(ctx->out, txn->xid, !rbtxn_is_streamed(txn));
1860 : :
1861 : 620 : send_repl_origin(ctx, txn->origin_id, InvalidXLogRecPtr,
1862 : : send_replication_origin);
1863 : :
1864 : 620 : OutputPluginWrite(ctx, true);
1865 : :
1866 : : /* we're streaming a chunk of transaction now */
1867 : 620 : data->in_streaming = true;
1868 : 620 : }
1869 : :
1870 : : /*
1871 : : * STOP STREAM callback
1872 : : */
1873 : : static void
1874 : 620 : pgoutput_stream_stop(struct LogicalDecodingContext *ctx,
1875 : : ReorderBufferTXN *txn)
1876 : : {
1877 : 620 : PGOutputData *data = (PGOutputData *) ctx->output_plugin_private;
1878 : :
1879 : : /* we should be streaming a transaction */
1880 : : Assert(data->in_streaming);
1881 : :
1882 : 620 : OutputPluginPrepareWrite(ctx, true);
1883 : 620 : logicalrep_write_stream_stop(ctx->out);
1884 : 620 : OutputPluginWrite(ctx, true);
1885 : :
1886 : : /* we've stopped streaming a transaction */
1887 : 620 : data->in_streaming = false;
1888 : 620 : }
1889 : :
1890 : : /*
1891 : : * Notify downstream to discard the streamed transaction (along with all
1892 : : * its subtransactions, if it's a toplevel transaction).
1893 : : */
1894 : : static void
1895 : 26 : pgoutput_stream_abort(struct LogicalDecodingContext *ctx,
1896 : : ReorderBufferTXN *txn,
1897 : : XLogRecPtr abort_lsn)
1898 : : {
1899 : : ReorderBufferTXN *toptxn;
1900 : 26 : PGOutputData *data = (PGOutputData *) ctx->output_plugin_private;
1901 : 26 : bool write_abort_info = (data->streaming == LOGICALREP_STREAM_PARALLEL);
1902 : :
1903 : : /*
1904 : : * The abort should happen outside streaming block, even for streamed
1905 : : * transactions. The transaction has to be marked as streamed, though.
1906 : : */
1907 : : Assert(!data->in_streaming);
1908 : :
1909 : : /* determine the toplevel transaction */
1910 [ + + ]: 26 : toptxn = rbtxn_get_toptxn(txn);
1911 : :
1912 : : Assert(rbtxn_is_streamed(toptxn));
1913 : :
1914 : 26 : OutputPluginPrepareWrite(ctx, true);
1915 : 26 : logicalrep_write_stream_abort(ctx->out, toptxn->xid, txn->xid, abort_lsn,
1916 : : txn->abort_time, write_abort_info);
1917 : :
1918 : 26 : OutputPluginWrite(ctx, true);
1919 : :
1920 : 26 : cleanup_rel_sync_cache(toptxn->xid, false);
1921 : 26 : }
1922 : :
1923 : : /*
1924 : : * Notify downstream to apply the streamed transaction (along with all
1925 : : * its subtransactions).
1926 : : */
1927 : : static void
1928 : 45 : pgoutput_stream_commit(struct LogicalDecodingContext *ctx,
1929 : : ReorderBufferTXN *txn,
1930 : : XLogRecPtr commit_lsn)
1931 : : {
1932 : 45 : PGOutputData *data PG_USED_FOR_ASSERTS_ONLY = (PGOutputData *) ctx->output_plugin_private;
1933 : :
1934 : : /*
1935 : : * The commit should happen outside streaming block, even for streamed
1936 : : * transactions. The transaction has to be marked as streamed, though.
1937 : : */
1938 : : Assert(!data->in_streaming);
1939 : : Assert(rbtxn_is_streamed(txn));
1940 : :
1941 : 45 : OutputPluginUpdateProgress(ctx, false);
1942 : :
1943 : 45 : OutputPluginPrepareWrite(ctx, true);
1944 : 45 : logicalrep_write_stream_commit(ctx->out, txn, commit_lsn);
1945 : 45 : OutputPluginWrite(ctx, true);
1946 : :
1947 : 45 : cleanup_rel_sync_cache(txn->xid, true);
1948 : 45 : }
1949 : :
1950 : : /*
1951 : : * PREPARE callback (for streaming two-phase commit).
1952 : : *
1953 : : * Notify the downstream to prepare the transaction.
1954 : : */
1955 : : static void
1956 : 13 : pgoutput_stream_prepare_txn(LogicalDecodingContext *ctx,
1957 : : ReorderBufferTXN *txn,
1958 : : XLogRecPtr prepare_lsn)
1959 : : {
1960 : : Assert(rbtxn_is_streamed(txn));
1961 : :
1962 : 13 : OutputPluginUpdateProgress(ctx, false);
1963 : 13 : OutputPluginPrepareWrite(ctx, true);
1964 : 13 : logicalrep_write_stream_prepare(ctx->out, txn, prepare_lsn);
1965 : 13 : OutputPluginWrite(ctx, true);
1966 : 13 : }
1967 : :
1968 : : /*
1969 : : * Initialize the relation schema sync cache for a decoding session.
1970 : : *
1971 : : * The hash table is destroyed at the end of a decoding session. While
1972 : : * relcache invalidations still exist and will still be invoked, they
1973 : : * will just see the null hash table global and take no action.
1974 : : */
1975 : : static void
1976 : 466 : init_rel_sync_cache(MemoryContext cachectx)
1977 : : {
1978 : : HASHCTL ctl;
1979 : : static bool relation_callbacks_registered = false;
1980 : :
1981 : : /* Nothing to do if hash table already exists */
1982 [ - + ]: 466 : if (RelationSyncCache != NULL)
1983 : 2 : return;
1984 : :
1985 : : /* Make a new hash table for the cache */
1986 : 466 : ctl.keysize = sizeof(Oid);
1987 : 466 : ctl.entrysize = sizeof(RelationSyncEntry);
1988 : 466 : ctl.hcxt = cachectx;
1989 : :
1990 : 466 : RelationSyncCache = hash_create("logical replication output relation cache",
1991 : : 128, &ctl,
1992 : : HASH_ELEM | HASH_CONTEXT | HASH_BLOBS);
1993 : :
1994 : : Assert(RelationSyncCache != NULL);
1995 : :
1996 : : /* No more to do if we already registered callbacks */
1997 [ + + ]: 466 : if (relation_callbacks_registered)
1998 : 2 : return;
1999 : :
2000 : : /* We must update the cache entry for a relation after a relcache flush */
2001 : 464 : CacheRegisterRelcacheCallback(rel_sync_cache_relation_cb, (Datum) 0);
2002 : :
2003 : : /*
2004 : : * Flush all cache entries after a pg_namespace change, in case it was a
2005 : : * schema rename affecting a relation being replicated.
2006 : : *
2007 : : * XXX: It is not a good idea to invalidate all the relation entries in
2008 : : * RelationSyncCache on schema rename. We can optimize it to invalidate
2009 : : * only the required relations by either having a specific invalidation
2010 : : * message containing impacted relations or by having schema information
2011 : : * in each RelationSyncCache entry and using hashvalue of pg_namespace.oid
2012 : : * passed to the callback.
2013 : : */
2014 : 464 : CacheRegisterSyscacheCallback(NAMESPACEOID,
2015 : : rel_sync_cache_publication_cb,
2016 : : (Datum) 0);
2017 : :
2018 : 464 : relation_callbacks_registered = true;
2019 : : }
2020 : :
2021 : : /*
2022 : : * We expect relatively small number of streamed transactions.
2023 : : */
2024 : : static bool
2025 : 175923 : get_schema_sent_in_streamed_txn(RelationSyncEntry *entry, TransactionId xid)
2026 : : {
2027 : 175923 : return list_member_xid(entry->streamed_txns, xid);
2028 : : }
2029 : :
2030 : : /*
2031 : : * Add the xid in the rel sync entry for which we have already sent the schema
2032 : : * of the relation.
2033 : : */
2034 : : static void
2035 : 70 : set_schema_sent_in_streamed_txn(RelationSyncEntry *entry, TransactionId xid)
2036 : : {
2037 : : MemoryContext oldctx;
2038 : :
2039 : 70 : oldctx = MemoryContextSwitchTo(CacheMemoryContext);
2040 : :
2041 : 70 : entry->streamed_txns = lappend_xid(entry->streamed_txns, xid);
2042 : :
2043 : 70 : MemoryContextSwitchTo(oldctx);
2044 : 70 : }
2045 : :
2046 : : /*
2047 : : * Find or create entry in the relation schema cache.
2048 : : *
2049 : : * This looks up publications that the given relation is directly or
2050 : : * indirectly part of (the latter if it's really the relation's ancestor that
2051 : : * is part of a publication) and fills up the found entry with the information
2052 : : * about which operations to publish and whether to use an ancestor's schema
2053 : : * when publishing.
2054 : : */
2055 : : static RelationSyncEntry *
2056 : 204574 : get_rel_sync_entry(PGOutputData *data, Relation relation)
2057 : : {
2058 : : RelationSyncEntry *entry;
2059 : : bool found;
2060 : : MemoryContext oldctx;
2061 : 204574 : Oid relid = RelationGetRelid(relation);
2062 : :
2063 : : Assert(RelationSyncCache != NULL);
2064 : :
2065 : : /* Find cached relation info, creating if not found */
2066 : 204574 : entry = (RelationSyncEntry *) hash_search(RelationSyncCache,
2067 : : &relid,
2068 : : HASH_ENTER, &found);
2069 : : Assert(entry != NULL);
2070 : :
2071 : : /* initialize entry, if it's new */
2072 [ + + ]: 204574 : if (!found)
2073 : : {
2074 : 377 : entry->replicate_valid = false;
2075 : 377 : entry->schema_sent = false;
2076 : 377 : entry->include_gencols_type = PUBLISH_GENCOLS_NONE;
2077 : 377 : entry->streamed_txns = NIL;
2078 : 377 : entry->pubactions.pubinsert = entry->pubactions.pubupdate =
2079 : 377 : entry->pubactions.pubdelete = entry->pubactions.pubtruncate = false;
2080 : 377 : entry->new_slot = NULL;
2081 : 377 : entry->old_slot = NULL;
2082 : 377 : memset(entry->exprstate, 0, sizeof(entry->exprstate));
2083 : 377 : entry->entry_cxt = NULL;
2084 : 377 : entry->publish_as_relid = InvalidOid;
2085 : 377 : entry->columns = NULL;
2086 : 377 : entry->attrmap = NULL;
2087 : : }
2088 : :
2089 : : /* Validate the entry */
2090 [ + + ]: 204574 : if (!entry->replicate_valid)
2091 : : {
2092 : 495 : Oid schemaId = get_rel_namespace(relid);
2093 : 495 : List *pubids = GetRelationIncludedPublications(relid);
2094 : :
2095 : : /*
2096 : : * We don't acquire a lock on the namespace system table as we build
2097 : : * the cache entry using a historic snapshot and all the later changes
2098 : : * are absorbed while decoding WAL.
2099 : : */
2100 : 495 : List *schemaPubids = GetSchemaPublications(schemaId);
2101 : : ListCell *lc;
2102 : 495 : Oid publish_as_relid = relid;
2103 : 495 : int publish_ancestor_level = 0;
2104 : 495 : bool am_partition = get_rel_relispartition(relid);
2105 : 495 : char relkind = get_rel_relkind(relid);
2106 : 495 : List *rel_publications = NIL;
2107 : :
2108 : : /* Reload publications if needed before use. */
2109 [ + + ]: 495 : if (!publications_valid)
2110 : : {
2111 : 266 : MemoryContextReset(data->pubctx);
2112 : :
2113 : 266 : oldctx = MemoryContextSwitchTo(data->pubctx);
2114 : 266 : data->publications = LoadPublications(data->publication_names);
2115 : 266 : MemoryContextSwitchTo(oldctx);
2116 : 266 : publications_valid = true;
2117 : : }
2118 : :
2119 : : /*
2120 : : * Reset schema_sent status as the relation definition may have
2121 : : * changed. Also reset pubactions to empty in case rel was dropped
2122 : : * from a publication. Also free any objects that depended on the
2123 : : * earlier definition.
2124 : : */
2125 : 495 : entry->schema_sent = false;
2126 : 495 : entry->include_gencols_type = PUBLISH_GENCOLS_NONE;
2127 : 495 : list_free(entry->streamed_txns);
2128 : 495 : entry->streamed_txns = NIL;
2129 : 495 : bms_free(entry->columns);
2130 : 495 : entry->columns = NULL;
2131 : 495 : entry->pubactions.pubinsert = false;
2132 : 495 : entry->pubactions.pubupdate = false;
2133 : 495 : entry->pubactions.pubdelete = false;
2134 : 495 : entry->pubactions.pubtruncate = false;
2135 : :
2136 : : /*
2137 : : * Tuple slots cleanups. (Will be rebuilt later if needed).
2138 : : */
2139 [ + + ]: 495 : if (entry->old_slot)
2140 : : {
2141 : 63 : TupleDesc desc = entry->old_slot->tts_tupleDescriptor;
2142 : :
2143 : : Assert(desc->tdrefcount == -1);
2144 : :
2145 : 63 : ExecDropSingleTupleTableSlot(entry->old_slot);
2146 : :
2147 : : /*
2148 : : * ExecDropSingleTupleTableSlot() would not free the TupleDesc, so
2149 : : * do it now to avoid any leaks.
2150 : : */
2151 : 63 : FreeTupleDesc(desc);
2152 : : }
2153 [ + + ]: 495 : if (entry->new_slot)
2154 : : {
2155 : 63 : TupleDesc desc = entry->new_slot->tts_tupleDescriptor;
2156 : :
2157 : : Assert(desc->tdrefcount == -1);
2158 : :
2159 : 63 : ExecDropSingleTupleTableSlot(entry->new_slot);
2160 : :
2161 : : /*
2162 : : * ExecDropSingleTupleTableSlot() would not free the TupleDesc, so
2163 : : * do it now to avoid any leaks.
2164 : : */
2165 : 63 : FreeTupleDesc(desc);
2166 : : }
2167 : :
2168 : 495 : entry->old_slot = NULL;
2169 : 495 : entry->new_slot = NULL;
2170 : :
2171 [ + + ]: 495 : if (entry->attrmap)
2172 : 3 : free_attrmap(entry->attrmap);
2173 : 495 : entry->attrmap = NULL;
2174 : :
2175 : : /*
2176 : : * Row filter cache cleanups.
2177 : : */
2178 [ + + ]: 495 : if (entry->entry_cxt)
2179 : 63 : MemoryContextDelete(entry->entry_cxt);
2180 : :
2181 : 495 : entry->entry_cxt = NULL;
2182 : 495 : entry->estate = NULL;
2183 : 495 : memset(entry->exprstate, 0, sizeof(entry->exprstate));
2184 : :
2185 : : /*
2186 : : * Build publication cache. We can't use one provided by relcache as
2187 : : * relcache considers all publications that the given relation is in,
2188 : : * but here we only need to consider ones that the subscriber
2189 : : * requested.
2190 : : */
2191 [ + + + + : 1165 : foreach(lc, data->publications)
+ + ]
2192 : : {
2193 : 670 : Publication *pub = lfirst(lc);
2194 : 670 : bool publish = false;
2195 : :
2196 : : /*
2197 : : * Under what relid should we publish changes in this publication?
2198 : : * We'll use the top-most relid across all publications. Also
2199 : : * track the ancestor level for this publication.
2200 : : */
2201 : 670 : Oid pub_relid = relid;
2202 : 670 : int ancestor_level = 0;
2203 : :
2204 : : /*
2205 : : * If this is a FOR ALL TABLES publication, pick the partition
2206 : : * root and set the ancestor level accordingly.
2207 : : */
2208 [ + + ]: 670 : if (pub->alltables)
2209 : : {
2210 : 113 : List *exceptpubids = NIL;
2211 : :
2212 [ + + ]: 113 : if (am_partition)
2213 : : {
2214 : 37 : List *ancestors = get_partition_ancestors(relid);
2215 : 37 : Oid last_ancestor_relid = llast_oid(ancestors);
2216 : :
2217 : : /*
2218 : : * For a partition, changes are published via top-most
2219 : : * ancestor when pubviaroot is true, so populate pub_relid
2220 : : * accordingly.
2221 : : */
2222 [ + + ]: 37 : if (pub->pubviaroot)
2223 : : {
2224 : 23 : pub_relid = last_ancestor_relid;
2225 : 23 : ancestor_level = list_length(ancestors);
2226 : : }
2227 : :
2228 : : /*
2229 : : * Only the top-most ancestor can appear in the EXCEPT
2230 : : * clause. Therefore, for a partition, exclusion must be
2231 : : * evaluated at the top-most ancestor.
2232 : : */
2233 : 37 : exceptpubids = GetRelationExcludedPublications(last_ancestor_relid);
2234 : : }
2235 : : else
2236 : : {
2237 : : /*
2238 : : * For a regular table or a root partitioned table, check
2239 : : * exclusion on table itself.
2240 : : */
2241 : 76 : exceptpubids = GetRelationExcludedPublications(pub_relid);
2242 : : }
2243 : :
2244 [ + + ]: 113 : if (!list_member_oid(exceptpubids, pub->oid))
2245 : 98 : publish = true;
2246 : :
2247 : 113 : list_free(exceptpubids);
2248 : :
2249 [ + + ]: 113 : if (!publish)
2250 : 15 : continue;
2251 : : }
2252 : :
2253 [ + + ]: 655 : if (!publish)
2254 : : {
2255 : 557 : bool ancestor_published = false;
2256 : :
2257 : : /*
2258 : : * For a partition, check if any of the ancestors are
2259 : : * published. If so, note down the topmost ancestor that is
2260 : : * published via this publication, which will be used as the
2261 : : * relation via which to publish the partition's changes.
2262 : : */
2263 [ + + ]: 557 : if (am_partition)
2264 : : {
2265 : : Oid ancestor;
2266 : : int level;
2267 : 121 : List *ancestors = get_partition_ancestors(relid);
2268 : :
2269 : 121 : ancestor = GetTopMostAncestorInPublication(pub->oid,
2270 : : ancestors,
2271 : : &level);
2272 : :
2273 [ + + ]: 121 : if (ancestor != InvalidOid)
2274 : : {
2275 : 48 : ancestor_published = true;
2276 [ + + ]: 48 : if (pub->pubviaroot)
2277 : : {
2278 : 25 : pub_relid = ancestor;
2279 : 25 : ancestor_level = level;
2280 : : }
2281 : : }
2282 : : }
2283 : :
2284 [ + + + + ]: 873 : if (list_member_oid(pubids, pub->oid) ||
2285 [ + + ]: 625 : list_member_oid(schemaPubids, pub->oid) ||
2286 : : ancestor_published)
2287 : 276 : publish = true;
2288 : : }
2289 : :
2290 : : /*
2291 : : * If the relation is to be published, determine actions to
2292 : : * publish, and list of columns, if appropriate.
2293 : : *
2294 : : * Don't publish changes for partitioned tables, because
2295 : : * publishing those of its partitions suffices, unless partition
2296 : : * changes won't be published due to pubviaroot being set.
2297 : : */
2298 [ + + + + ]: 655 : if (publish &&
2299 [ + + ]: 4 : (relkind != RELKIND_PARTITIONED_TABLE || pub->pubviaroot))
2300 : : {
2301 : 371 : entry->pubactions.pubinsert |= pub->pubactions.pubinsert;
2302 : 371 : entry->pubactions.pubupdate |= pub->pubactions.pubupdate;
2303 : 371 : entry->pubactions.pubdelete |= pub->pubactions.pubdelete;
2304 : 371 : entry->pubactions.pubtruncate |= pub->pubactions.pubtruncate;
2305 : :
2306 : : /*
2307 : : * We want to publish the changes as the top-most ancestor
2308 : : * across all publications. So we need to check if the already
2309 : : * calculated level is higher than the new one. If yes, we can
2310 : : * ignore the new value (as it's a child). Otherwise the new
2311 : : * value is an ancestor, so we keep it.
2312 : : */
2313 [ + + ]: 371 : if (publish_ancestor_level > ancestor_level)
2314 : 1 : continue;
2315 : :
2316 : : /*
2317 : : * If we found an ancestor higher up in the tree, discard the
2318 : : * list of publications through which we replicate it, and use
2319 : : * the new ancestor.
2320 : : */
2321 [ + + ]: 370 : if (publish_ancestor_level < ancestor_level)
2322 : : {
2323 : 44 : publish_as_relid = pub_relid;
2324 : 44 : publish_ancestor_level = ancestor_level;
2325 : :
2326 : : /* reset the publication list for this relation */
2327 : 44 : rel_publications = NIL;
2328 : : }
2329 : : else
2330 : : {
2331 : : /* Same ancestor level, has to be the same OID. */
2332 : : Assert(publish_as_relid == pub_relid);
2333 : : }
2334 : :
2335 : : /* Track publications for this ancestor. */
2336 : 370 : rel_publications = lappend(rel_publications, pub);
2337 : : }
2338 : : }
2339 : :
2340 : 495 : entry->publish_as_relid = publish_as_relid;
2341 : :
2342 : : /*
2343 : : * Initialize the tuple slot, map, and row filter. These are only used
2344 : : * when publishing inserts, updates, or deletes.
2345 : : */
2346 [ + + + - ]: 495 : if (entry->pubactions.pubinsert || entry->pubactions.pubupdate ||
2347 [ - + ]: 138 : entry->pubactions.pubdelete)
2348 : : {
2349 : : /* Initialize the tuple slot and map */
2350 : 357 : init_tuple_slot(data, relation, entry);
2351 : :
2352 : : /* Initialize the row filter */
2353 : 357 : pgoutput_row_filter_init(data, rel_publications, entry);
2354 : :
2355 : : /* Check whether to publish generated columns. */
2356 : 357 : check_and_init_gencol(data, rel_publications, entry);
2357 : :
2358 : : /* Initialize the column list */
2359 : 357 : pgoutput_column_list_init(data, rel_publications, entry);
2360 : : }
2361 : :
2362 : 494 : list_free(pubids);
2363 : 494 : list_free(schemaPubids);
2364 : 494 : list_free(rel_publications);
2365 : :
2366 : 494 : entry->replicate_valid = true;
2367 : : }
2368 : :
2369 : 204573 : return entry;
2370 : : }
2371 : :
2372 : : /*
2373 : : * Cleanup list of streamed transactions and update the schema_sent flag.
2374 : : *
2375 : : * When a streamed transaction commits or aborts, we need to remove the
2376 : : * toplevel XID from the schema cache. If the transaction aborted, the
2377 : : * subscriber will simply throw away the schema records we streamed, so
2378 : : * we don't need to do anything else.
2379 : : *
2380 : : * If the transaction is committed, the subscriber will update the relation
2381 : : * cache - so tweak the schema_sent flag accordingly.
2382 : : */
2383 : : static void
2384 : 71 : cleanup_rel_sync_cache(TransactionId xid, bool is_commit)
2385 : : {
2386 : : HASH_SEQ_STATUS hash_seq;
2387 : : RelationSyncEntry *entry;
2388 : :
2389 : : Assert(RelationSyncCache != NULL);
2390 : :
2391 : 71 : hash_seq_init(&hash_seq, RelationSyncCache);
2392 [ + + ]: 145 : while ((entry = hash_seq_search(&hash_seq)) != NULL)
2393 : : {
2394 : : /*
2395 : : * We can set the schema_sent flag for an entry that has committed xid
2396 : : * in the list as that ensures that the subscriber would have the
2397 : : * corresponding schema and we don't need to send it unless there is
2398 : : * any invalidation for that relation.
2399 : : */
2400 [ + + + - : 167 : foreach_xid(streamed_txn, entry->streamed_txns)
+ + ]
2401 : : {
2402 [ + + ]: 72 : if (xid == streamed_txn)
2403 : : {
2404 [ + + ]: 53 : if (is_commit)
2405 : 42 : entry->schema_sent = true;
2406 : :
2407 : 53 : entry->streamed_txns =
2408 : 53 : foreach_delete_current(entry->streamed_txns, streamed_txn);
2409 : 53 : break;
2410 : : }
2411 : : }
2412 : : }
2413 : 71 : }
2414 : :
2415 : : /*
2416 : : * Relcache invalidation callback
2417 : : */
2418 : : static void
2419 : 4699 : rel_sync_cache_relation_cb(Datum arg, Oid relid)
2420 : : {
2421 : : RelationSyncEntry *entry;
2422 : :
2423 : : /*
2424 : : * We can get here if the plugin was used in SQL interface as the
2425 : : * RelationSyncCache is destroyed when the decoding finishes, but there is
2426 : : * no way to unregister the relcache invalidation callback.
2427 : : */
2428 [ + + ]: 4699 : if (RelationSyncCache == NULL)
2429 : 32 : return;
2430 : :
2431 : : /*
2432 : : * Nobody keeps pointers to entries in this hash table around outside
2433 : : * logical decoding callback calls - but invalidation events can come in
2434 : : * *during* a callback if we do any syscache access in the callback.
2435 : : * Because of that we must mark the cache entry as invalid but not damage
2436 : : * any of its substructure here. The next get_rel_sync_entry() call will
2437 : : * rebuild it all.
2438 : : */
2439 [ + + ]: 4667 : if (OidIsValid(relid))
2440 : : {
2441 : : /*
2442 : : * Getting invalidations for relations that aren't in the table is
2443 : : * entirely normal. So we don't care if it's found or not.
2444 : : */
2445 : 4589 : entry = (RelationSyncEntry *) hash_search(RelationSyncCache, &relid,
2446 : : HASH_FIND, NULL);
2447 [ + + ]: 4589 : if (entry != NULL)
2448 : 803 : entry->replicate_valid = false;
2449 : : }
2450 : : else
2451 : : {
2452 : : /* Whole cache must be flushed. */
2453 : : HASH_SEQ_STATUS status;
2454 : :
2455 : 78 : hash_seq_init(&status, RelationSyncCache);
2456 [ + + ]: 150 : while ((entry = (RelationSyncEntry *) hash_seq_search(&status)) != NULL)
2457 : : {
2458 : 72 : entry->replicate_valid = false;
2459 : : }
2460 : : }
2461 : : }
2462 : :
2463 : : /*
2464 : : * Publication relation/schema map syscache invalidation callback
2465 : : *
2466 : : * Called for invalidations on pg_namespace.
2467 : : */
2468 : : static void
2469 : 46 : rel_sync_cache_publication_cb(Datum arg, SysCacheIdentifier cacheid,
2470 : : uint32 hashvalue)
2471 : : {
2472 : : HASH_SEQ_STATUS status;
2473 : : RelationSyncEntry *entry;
2474 : :
2475 : : /*
2476 : : * We can get here if the plugin was used in SQL interface as the
2477 : : * RelationSyncCache is destroyed when the decoding finishes, but there is
2478 : : * no way to unregister the invalidation callbacks.
2479 : : */
2480 [ + + ]: 46 : if (RelationSyncCache == NULL)
2481 : 13 : return;
2482 : :
2483 : : /*
2484 : : * We have no easy way to identify which cache entries this invalidation
2485 : : * event might have affected, so just mark them all invalid.
2486 : : */
2487 : 33 : hash_seq_init(&status, RelationSyncCache);
2488 [ + + ]: 58 : while ((entry = (RelationSyncEntry *) hash_seq_search(&status)) != NULL)
2489 : : {
2490 : 25 : entry->replicate_valid = false;
2491 : : }
2492 : : }
2493 : :
2494 : : /* Send Replication origin */
2495 : : static void
2496 : 1155 : send_repl_origin(LogicalDecodingContext *ctx, ReplOriginId origin_id,
2497 : : XLogRecPtr origin_lsn, bool send_origin)
2498 : : {
2499 [ + + ]: 1155 : if (send_origin)
2500 : : {
2501 : : char *origin;
2502 : :
2503 : : /*----------
2504 : : * XXX: which behaviour do we want here?
2505 : : *
2506 : : * Alternatives:
2507 : : * - don't send origin message if origin name not found
2508 : : * (that's what we do now)
2509 : : * - throw error - that will break replication, not good
2510 : : * - send some special "unknown" origin
2511 : : *----------
2512 : : */
2513 [ + - ]: 8 : if (replorigin_by_oid(origin_id, true, &origin))
2514 : : {
2515 : : /* Message boundary */
2516 : 8 : OutputPluginWrite(ctx, false);
2517 : 8 : OutputPluginPrepareWrite(ctx, true);
2518 : :
2519 : 8 : logicalrep_write_origin(ctx->out, origin, origin_lsn);
2520 : : }
2521 : : }
2522 : 1155 : }
|