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