Line data Source code
1 : /*-------------------------------------------------------------------------
2 : *
3 : * test_decoding.c
4 : * example logical decoding output plugin
5 : *
6 : * Copyright (c) 2012-2020, PostgreSQL Global Development Group
7 : *
8 : * IDENTIFICATION
9 : * contrib/test_decoding/test_decoding.c
10 : *
11 : *-------------------------------------------------------------------------
12 : */
13 : #include "postgres.h"
14 : #include "miscadmin.h"
15 :
16 : #include "access/transam.h"
17 : #include "catalog/pg_type.h"
18 :
19 : #include "replication/logical.h"
20 : #include "replication/origin.h"
21 :
22 : #include "storage/procarray.h"
23 :
24 : #include "utils/builtins.h"
25 : #include "utils/lsyscache.h"
26 : #include "utils/memutils.h"
27 : #include "utils/rel.h"
28 :
29 78 : PG_MODULE_MAGIC;
30 :
31 : /* These must be available to dlsym() */
32 : extern void _PG_init(void);
33 : extern void _PG_output_plugin_init(OutputPluginCallbacks *cb);
34 :
35 : typedef struct
36 : {
37 : MemoryContext context;
38 : bool include_xids;
39 : bool include_timestamp;
40 : bool skip_empty_xacts;
41 : bool xact_wrote_changes;
42 : bool only_local;
43 : TransactionId check_xid_aborted; /* track abort of this txid */
44 : } TestDecodingData;
45 :
46 : static void pg_decode_startup(LogicalDecodingContext *ctx, OutputPluginOptions *opt,
47 : bool is_init);
48 : static void pg_decode_shutdown(LogicalDecodingContext *ctx);
49 : static void pg_decode_begin_txn(LogicalDecodingContext *ctx,
50 : ReorderBufferTXN *txn);
51 : static void pg_output_begin(LogicalDecodingContext *ctx,
52 : TestDecodingData *data,
53 : ReorderBufferTXN *txn,
54 : bool last_write);
55 : static void pg_decode_commit_txn(LogicalDecodingContext *ctx,
56 : ReorderBufferTXN *txn, XLogRecPtr commit_lsn);
57 : static void pg_decode_change(LogicalDecodingContext *ctx,
58 : ReorderBufferTXN *txn, Relation rel,
59 : ReorderBufferChange *change);
60 : static void pg_decode_truncate(LogicalDecodingContext *ctx,
61 : ReorderBufferTXN *txn,
62 : int nrelations, Relation relations[],
63 : ReorderBufferChange *change);
64 : static bool pg_decode_filter(LogicalDecodingContext *ctx,
65 : RepOriginId origin_id);
66 : static void pg_decode_message(LogicalDecodingContext *ctx,
67 : ReorderBufferTXN *txn, XLogRecPtr message_lsn,
68 : bool transactional, const char *prefix,
69 : Size sz, const char *message);
70 : static void pg_decode_stream_start(LogicalDecodingContext *ctx,
71 : ReorderBufferTXN *txn);
72 : static void pg_output_stream_start(LogicalDecodingContext *ctx,
73 : TestDecodingData *data,
74 : ReorderBufferTXN *txn,
75 : bool last_write);
76 : static void pg_decode_stream_stop(LogicalDecodingContext *ctx,
77 : ReorderBufferTXN *txn);
78 : static void pg_decode_stream_abort(LogicalDecodingContext *ctx,
79 : ReorderBufferTXN *txn,
80 : XLogRecPtr abort_lsn);
81 : static void pg_decode_stream_prepare(LogicalDecodingContext *ctx,
82 : ReorderBufferTXN *txn,
83 : XLogRecPtr prepare_lsn);
84 : static void pg_decode_stream_commit(LogicalDecodingContext *ctx,
85 : ReorderBufferTXN *txn,
86 : XLogRecPtr commit_lsn);
87 : static void pg_decode_stream_change(LogicalDecodingContext *ctx,
88 : ReorderBufferTXN *txn,
89 : Relation relation,
90 : ReorderBufferChange *change);
91 : static void pg_decode_stream_message(LogicalDecodingContext *ctx,
92 : ReorderBufferTXN *txn, XLogRecPtr message_lsn,
93 : bool transactional, const char *prefix,
94 : Size sz, const char *message);
95 : static void pg_decode_stream_truncate(LogicalDecodingContext *ctx,
96 : ReorderBufferTXN *txn,
97 : int nrelations, Relation relations[],
98 : ReorderBufferChange *change);
99 : static bool pg_decode_filter_prepare(LogicalDecodingContext *ctx,
100 : ReorderBufferTXN *txn,
101 : TransactionId xid, const char *gid);
102 : static void pg_decode_prepare_txn(LogicalDecodingContext *ctx,
103 : ReorderBufferTXN *txn,
104 : XLogRecPtr prepare_lsn);
105 : static void pg_decode_commit_prepared_txn(LogicalDecodingContext *ctx,
106 : ReorderBufferTXN *txn,
107 : XLogRecPtr commit_lsn);
108 : static void pg_decode_rollback_prepared_txn(LogicalDecodingContext *ctx,
109 : ReorderBufferTXN *txn,
110 : XLogRecPtr abort_lsn);
111 :
112 : void
113 78 : _PG_init(void)
114 : {
115 : /* other plugins can perform things here */
116 78 : }
117 :
118 : /* specify output plugin callbacks */
119 : void
120 446 : _PG_output_plugin_init(OutputPluginCallbacks *cb)
121 : {
122 : AssertVariableIsOfType(&_PG_output_plugin_init, LogicalOutputPluginInit);
123 :
124 446 : cb->startup_cb = pg_decode_startup;
125 446 : cb->begin_cb = pg_decode_begin_txn;
126 446 : cb->change_cb = pg_decode_change;
127 446 : cb->truncate_cb = pg_decode_truncate;
128 446 : cb->commit_cb = pg_decode_commit_txn;
129 446 : cb->filter_by_origin_cb = pg_decode_filter;
130 446 : cb->shutdown_cb = pg_decode_shutdown;
131 446 : cb->message_cb = pg_decode_message;
132 446 : cb->stream_start_cb = pg_decode_stream_start;
133 446 : cb->stream_stop_cb = pg_decode_stream_stop;
134 446 : cb->stream_abort_cb = pg_decode_stream_abort;
135 446 : cb->stream_prepare_cb = pg_decode_stream_prepare;
136 446 : cb->stream_commit_cb = pg_decode_stream_commit;
137 446 : cb->stream_change_cb = pg_decode_stream_change;
138 446 : cb->stream_message_cb = pg_decode_stream_message;
139 446 : cb->stream_truncate_cb = pg_decode_stream_truncate;
140 446 : cb->filter_prepare_cb = pg_decode_filter_prepare;
141 446 : cb->prepare_cb = pg_decode_prepare_txn;
142 446 : cb->commit_prepared_cb = pg_decode_commit_prepared_txn;
143 446 : cb->rollback_prepared_cb = pg_decode_rollback_prepared_txn;
144 446 : }
145 :
146 :
147 : /* initialize this plugin */
148 : static void
149 446 : pg_decode_startup(LogicalDecodingContext *ctx, OutputPluginOptions *opt,
150 : bool is_init)
151 : {
152 : ListCell *option;
153 : TestDecodingData *data;
154 446 : bool enable_streaming = false;
155 446 : bool enable_twophase = false;
156 :
157 446 : data = palloc0(sizeof(TestDecodingData));
158 446 : data->context = AllocSetContextCreate(ctx->context,
159 : "text conversion context",
160 : ALLOCSET_DEFAULT_SIZES);
161 446 : data->include_xids = true;
162 446 : data->include_timestamp = false;
163 446 : data->skip_empty_xacts = false;
164 446 : data->only_local = false;
165 446 : data->check_xid_aborted = InvalidTransactionId;
166 :
167 446 : ctx->output_plugin_private = data;
168 :
169 446 : opt->output_type = OUTPUT_PLUGIN_TEXTUAL_OUTPUT;
170 446 : opt->receive_rewrites = false;
171 :
172 1052 : foreach(option, ctx->output_plugin_options)
173 : {
174 612 : DefElem *elem = lfirst(option);
175 :
176 612 : Assert(elem->arg == NULL || IsA(elem->arg, String));
177 :
178 612 : if (strcmp(elem->defname, "include-xids") == 0)
179 : {
180 : /* if option does not provide a value, it means its value is true */
181 262 : if (elem->arg == NULL)
182 0 : data->include_xids = true;
183 262 : else if (!parse_bool(strVal(elem->arg), &data->include_xids))
184 4 : ereport(ERROR,
185 : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
186 : errmsg("could not parse value \"%s\" for parameter \"%s\"",
187 : strVal(elem->arg), elem->defname)));
188 : }
189 350 : else if (strcmp(elem->defname, "include-timestamp") == 0)
190 : {
191 2 : if (elem->arg == NULL)
192 0 : data->include_timestamp = true;
193 2 : else if (!parse_bool(strVal(elem->arg), &data->include_timestamp))
194 0 : ereport(ERROR,
195 : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
196 : errmsg("could not parse value \"%s\" for parameter \"%s\"",
197 : strVal(elem->arg), elem->defname)));
198 : }
199 348 : else if (strcmp(elem->defname, "force-binary") == 0)
200 : {
201 : bool force_binary;
202 :
203 12 : if (elem->arg == NULL)
204 0 : continue;
205 12 : else if (!parse_bool(strVal(elem->arg), &force_binary))
206 0 : ereport(ERROR,
207 : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
208 : errmsg("could not parse value \"%s\" for parameter \"%s\"",
209 : strVal(elem->arg), elem->defname)));
210 :
211 12 : if (force_binary)
212 4 : opt->output_type = OUTPUT_PLUGIN_BINARY_OUTPUT;
213 : }
214 336 : else if (strcmp(elem->defname, "skip-empty-xacts") == 0)
215 : {
216 :
217 254 : if (elem->arg == NULL)
218 0 : data->skip_empty_xacts = true;
219 254 : else if (!parse_bool(strVal(elem->arg), &data->skip_empty_xacts))
220 0 : ereport(ERROR,
221 : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
222 : errmsg("could not parse value \"%s\" for parameter \"%s\"",
223 : strVal(elem->arg), elem->defname)));
224 : }
225 82 : else if (strcmp(elem->defname, "only-local") == 0)
226 : {
227 :
228 6 : if (elem->arg == NULL)
229 0 : data->only_local = true;
230 6 : else if (!parse_bool(strVal(elem->arg), &data->only_local))
231 0 : ereport(ERROR,
232 : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
233 : errmsg("could not parse value \"%s\" for parameter \"%s\"",
234 : strVal(elem->arg), elem->defname)));
235 : }
236 76 : else if (strcmp(elem->defname, "include-rewrites") == 0)
237 : {
238 :
239 2 : if (elem->arg == NULL)
240 0 : continue;
241 2 : else if (!parse_bool(strVal(elem->arg), &opt->receive_rewrites))
242 0 : ereport(ERROR,
243 : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
244 : errmsg("could not parse value \"%s\" for parameter \"%s\"",
245 : strVal(elem->arg), elem->defname)));
246 : }
247 74 : else if (strcmp(elem->defname, "stream-changes") == 0)
248 : {
249 18 : if (elem->arg == NULL)
250 0 : continue;
251 18 : else if (!parse_bool(strVal(elem->arg), &enable_streaming))
252 0 : ereport(ERROR,
253 : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
254 : errmsg("could not parse value \"%s\" for parameter \"%s\"",
255 : strVal(elem->arg), elem->defname)));
256 : }
257 56 : else if (strcmp(elem->defname, "two-phase-commit") == 0)
258 : {
259 52 : if (elem->arg == NULL)
260 0 : continue;
261 52 : else if (!parse_bool(strVal(elem->arg), &enable_twophase))
262 0 : ereport(ERROR,
263 : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
264 : errmsg("could not parse value \"%s\" for parameter \"%s\"",
265 : strVal(elem->arg), elem->defname)));
266 : }
267 4 : else if (strcmp(elem->defname, "check-xid-aborted") == 0)
268 : {
269 2 : if (elem->arg == NULL)
270 0 : ereport(ERROR,
271 : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
272 : errmsg("check-xid-aborted needs an input value")));
273 : else
274 : {
275 :
276 2 : errno = 0;
277 2 : data->check_xid_aborted = (TransactionId) strtoul(strVal(elem->arg), NULL, 0);
278 :
279 2 : if (errno || !TransactionIdIsValid(data->check_xid_aborted))
280 0 : ereport(ERROR,
281 : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
282 : errmsg("check-xid-aborted is not a valid xid: \"%s\"",
283 : strVal(elem->arg))));
284 : }
285 : }
286 : else
287 : {
288 2 : ereport(ERROR,
289 : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
290 : errmsg("option \"%s\" = \"%s\" is unknown",
291 : elem->defname,
292 : elem->arg ? strVal(elem->arg) : "(null)")));
293 : }
294 : }
295 :
296 440 : ctx->streaming &= enable_streaming;
297 440 : ctx->twophase &= enable_twophase;
298 440 : }
299 :
300 : /* cleanup this plugin's resources */
301 : static void
302 438 : pg_decode_shutdown(LogicalDecodingContext *ctx)
303 : {
304 438 : TestDecodingData *data = ctx->output_plugin_private;
305 :
306 : /* cleanup our own resources via memory context reset */
307 438 : MemoryContextDelete(data->context);
308 438 : }
309 :
310 : /* BEGIN callback */
311 : static void
312 732 : pg_decode_begin_txn(LogicalDecodingContext *ctx, ReorderBufferTXN *txn)
313 : {
314 732 : TestDecodingData *data = ctx->output_plugin_private;
315 :
316 732 : data->xact_wrote_changes = false;
317 732 : if (data->skip_empty_xacts)
318 1418 : return;
319 :
320 46 : pg_output_begin(ctx, data, txn, true);
321 : }
322 :
323 : static void
324 414 : pg_output_begin(LogicalDecodingContext *ctx, TestDecodingData *data, ReorderBufferTXN *txn, bool last_write)
325 : {
326 414 : OutputPluginPrepareWrite(ctx, last_write);
327 414 : if (data->include_xids)
328 32 : appendStringInfo(ctx->out, "BEGIN %u", txn->xid);
329 : else
330 382 : appendStringInfoString(ctx->out, "BEGIN");
331 414 : OutputPluginWrite(ctx, last_write);
332 414 : }
333 :
334 : /* COMMIT callback */
335 : static void
336 714 : pg_decode_commit_txn(LogicalDecodingContext *ctx, ReorderBufferTXN *txn,
337 : XLogRecPtr commit_lsn)
338 : {
339 714 : TestDecodingData *data = ctx->output_plugin_private;
340 :
341 714 : if (data->skip_empty_xacts && !data->xact_wrote_changes)
342 1030 : return;
343 :
344 398 : OutputPluginPrepareWrite(ctx, true);
345 398 : if (data->include_xids)
346 32 : appendStringInfo(ctx->out, "COMMIT %u", txn->xid);
347 : else
348 366 : appendStringInfoString(ctx->out, "COMMIT");
349 :
350 398 : if (data->include_timestamp)
351 2 : appendStringInfo(ctx->out, " (at %s)",
352 : timestamptz_to_str(txn->commit_time));
353 :
354 398 : OutputPluginWrite(ctx, true);
355 : }
356 :
357 : /*
358 : * Filter out two-phase transactions.
359 : *
360 : * Each plugin can implement its own filtering logic. Here
361 : * we demonstrate a simple logic by checking the GID. If the
362 : * GID contains the "_nodecode" substring, then we filter
363 : * it out.
364 : */
365 : static bool
366 196 : pg_decode_filter_prepare(LogicalDecodingContext *ctx, ReorderBufferTXN *txn,
367 : TransactionId xid, const char *gid)
368 : {
369 196 : if (strstr(gid, "_nodecode") != NULL)
370 14 : return true;
371 :
372 182 : return false;
373 : }
374 :
375 : /* PREPARE callback */
376 : static void
377 14 : pg_decode_prepare_txn(LogicalDecodingContext *ctx, ReorderBufferTXN *txn,
378 : XLogRecPtr prepare_lsn)
379 : {
380 14 : TestDecodingData *data = ctx->output_plugin_private;
381 :
382 14 : if (data->skip_empty_xacts && !data->xact_wrote_changes)
383 14 : return;
384 :
385 14 : OutputPluginPrepareWrite(ctx, true);
386 :
387 14 : appendStringInfo(ctx->out, "PREPARE TRANSACTION %s",
388 14 : quote_literal_cstr(txn->gid));
389 :
390 14 : if (data->include_xids)
391 0 : appendStringInfo(ctx->out, " %u", txn->xid);
392 :
393 14 : if (data->include_timestamp)
394 0 : appendStringInfo(ctx->out, " (at %s)",
395 : timestamptz_to_str(txn->commit_time));
396 :
397 14 : OutputPluginWrite(ctx, true);
398 : }
399 :
400 : /* COMMIT PREPARED callback */
401 : static void
402 12 : pg_decode_commit_prepared_txn(LogicalDecodingContext *ctx, ReorderBufferTXN *txn,
403 : XLogRecPtr commit_lsn)
404 : {
405 12 : TestDecodingData *data = ctx->output_plugin_private;
406 :
407 12 : OutputPluginPrepareWrite(ctx, true);
408 :
409 12 : appendStringInfo(ctx->out, "COMMIT PREPARED %s",
410 12 : quote_literal_cstr(txn->gid));
411 :
412 12 : if (data->include_xids)
413 0 : appendStringInfo(ctx->out, " %u", txn->xid);
414 :
415 12 : if (data->include_timestamp)
416 0 : appendStringInfo(ctx->out, " (at %s)",
417 : timestamptz_to_str(txn->commit_time));
418 :
419 12 : OutputPluginWrite(ctx, true);
420 12 : }
421 :
422 : /* ROLLBACK PREPARED callback */
423 : static void
424 8 : pg_decode_rollback_prepared_txn(LogicalDecodingContext *ctx, ReorderBufferTXN *txn,
425 : XLogRecPtr abort_lsn)
426 : {
427 8 : TestDecodingData *data = ctx->output_plugin_private;
428 :
429 8 : OutputPluginPrepareWrite(ctx, true);
430 :
431 8 : appendStringInfo(ctx->out, "ROLLBACK PREPARED %s",
432 8 : quote_literal_cstr(txn->gid));
433 :
434 8 : if (data->include_xids)
435 0 : appendStringInfo(ctx->out, " %u", txn->xid);
436 :
437 8 : if (data->include_timestamp)
438 0 : appendStringInfo(ctx->out, " (at %s)",
439 : timestamptz_to_str(txn->commit_time));
440 :
441 8 : OutputPluginWrite(ctx, true);
442 8 : }
443 :
444 : static bool
445 2376808 : pg_decode_filter(LogicalDecodingContext *ctx,
446 : RepOriginId origin_id)
447 : {
448 2376808 : TestDecodingData *data = ctx->output_plugin_private;
449 :
450 2376808 : if (data->only_local && origin_id != InvalidRepOriginId)
451 18 : return true;
452 2376790 : return false;
453 : }
454 :
455 : /*
456 : * Print literal `outputstr' already represented as string of type `typid'
457 : * into stringbuf `s'.
458 : *
459 : * Some builtin types aren't quoted, the rest is quoted. Escaping is done as
460 : * if standard_conforming_strings were enabled.
461 : */
462 : static void
463 361316 : print_literal(StringInfo s, Oid typid, char *outputstr)
464 : {
465 : const char *valptr;
466 :
467 361316 : switch (typid)
468 : {
469 : case INT2OID:
470 : case INT4OID:
471 : case INT8OID:
472 : case OIDOID:
473 : case FLOAT4OID:
474 : case FLOAT8OID:
475 : case NUMERICOID:
476 : /* NB: We don't care about Inf, NaN et al. */
477 120096 : appendStringInfoString(s, outputstr);
478 120096 : break;
479 :
480 : case BITOID:
481 : case VARBITOID:
482 0 : appendStringInfo(s, "B'%s'", outputstr);
483 0 : break;
484 :
485 : case BOOLOID:
486 0 : if (strcmp(outputstr, "t") == 0)
487 0 : appendStringInfoString(s, "true");
488 : else
489 0 : appendStringInfoString(s, "false");
490 0 : break;
491 :
492 : default:
493 241220 : appendStringInfoChar(s, '\'');
494 11073622 : for (valptr = outputstr; *valptr; valptr++)
495 : {
496 10832402 : char ch = *valptr;
497 :
498 10832402 : if (SQL_STR_DOUBLE(ch, false))
499 128 : appendStringInfoChar(s, ch);
500 10832402 : appendStringInfoChar(s, ch);
501 : }
502 241220 : appendStringInfoChar(s, '\'');
503 241220 : break;
504 : }
505 361316 : }
506 :
507 : /* print the tuple 'tuple' into the StringInfo s */
508 : static void
509 300764 : tuple_to_stringinfo(StringInfo s, TupleDesc tupdesc, HeapTuple tuple, bool skip_nulls)
510 : {
511 : int natt;
512 :
513 : /* print all columns individually */
514 713400 : for (natt = 0; natt < tupdesc->natts; natt++)
515 : {
516 : Form_pg_attribute attr; /* the attribute itself */
517 : Oid typid; /* type of current attribute */
518 : Oid typoutput; /* output function */
519 : bool typisvarlena;
520 : Datum origval; /* possibly toasted Datum */
521 : bool isnull; /* column is null? */
522 :
523 412636 : attr = TupleDescAttr(tupdesc, natt);
524 :
525 : /*
526 : * don't print dropped columns, we can't be sure everything is
527 : * available for them
528 : */
529 412636 : if (attr->attisdropped)
530 10246 : continue;
531 :
532 : /*
533 : * Don't print system columns, oid will already have been printed if
534 : * present.
535 : */
536 412544 : if (attr->attnum < 0)
537 0 : continue;
538 :
539 412544 : typid = attr->atttypid;
540 :
541 : /* get Datum from tuple */
542 412544 : origval = heap_getattr(tuple, natt + 1, tupdesc, &isnull);
543 :
544 412544 : if (isnull && skip_nulls)
545 10062 : continue;
546 :
547 : /* print attribute name */
548 402482 : appendStringInfoChar(s, ' ');
549 402482 : appendStringInfoString(s, quote_identifier(NameStr(attr->attname)));
550 :
551 : /* print attribute type */
552 402482 : appendStringInfoChar(s, '[');
553 402482 : appendStringInfoString(s, format_type_be(typid));
554 402482 : appendStringInfoChar(s, ']');
555 :
556 : /* query output function */
557 402482 : getTypeOutputInfo(typid,
558 : &typoutput, &typisvarlena);
559 :
560 : /* print separator */
561 402482 : appendStringInfoChar(s, ':');
562 :
563 : /* print data */
564 402482 : if (isnull)
565 41142 : appendStringInfoString(s, "null");
566 361340 : else if (typisvarlena && VARATT_IS_EXTERNAL_ONDISK(origval))
567 24 : appendStringInfoString(s, "unchanged-toast-datum");
568 361316 : else if (!typisvarlena)
569 120104 : print_literal(s, typid,
570 : OidOutputFunctionCall(typoutput, origval));
571 : else
572 : {
573 : Datum val; /* definitely detoasted Datum */
574 :
575 241212 : val = PointerGetDatum(PG_DETOAST_DATUM(origval));
576 241212 : print_literal(s, typid, OidOutputFunctionCall(typoutput, val));
577 : }
578 : }
579 300764 : }
580 :
581 : /*
582 : * callback for individual changed tuples
583 : */
584 : static void
585 310746 : pg_decode_change(LogicalDecodingContext *ctx, ReorderBufferTXN *txn,
586 : Relation relation, ReorderBufferChange *change)
587 : {
588 : TestDecodingData *data;
589 : Form_pg_class class_form;
590 : TupleDesc tupdesc;
591 : MemoryContext old;
592 :
593 310746 : data = ctx->output_plugin_private;
594 :
595 : /* output BEGIN if we haven't yet */
596 310746 : if (data->skip_empty_xacts && !data->xact_wrote_changes)
597 : {
598 362 : pg_output_begin(ctx, data, txn, false);
599 : }
600 310746 : data->xact_wrote_changes = true;
601 :
602 : /*
603 : * if check_xid_aborted is a valid xid, then it was passed in as an option
604 : * to check if the transaction having this xid would be aborted. This is
605 : * to test concurrent aborts.
606 : */
607 310746 : if (TransactionIdIsValid(data->check_xid_aborted))
608 : {
609 2 : elog(LOG, "waiting for %u to abort", data->check_xid_aborted);
610 26 : while (TransactionIdIsInProgress(data->check_xid_aborted))
611 : {
612 22 : CHECK_FOR_INTERRUPTS();
613 22 : pg_usleep(10000L);
614 : }
615 4 : if (!TransactionIdIsInProgress(data->check_xid_aborted) &&
616 2 : !TransactionIdDidCommit(data->check_xid_aborted))
617 2 : elog(LOG, "%u aborted", data->check_xid_aborted);
618 :
619 2 : Assert(TransactionIdDidAbort(data->check_xid_aborted));
620 : }
621 :
622 310746 : class_form = RelationGetForm(relation);
623 310746 : tupdesc = RelationGetDescr(relation);
624 :
625 : /* Avoid leaking memory by using and resetting our own context */
626 310746 : old = MemoryContextSwitchTo(data->context);
627 :
628 310746 : OutputPluginPrepareWrite(ctx, true);
629 :
630 310746 : appendStringInfoString(ctx->out, "table ");
631 310744 : appendStringInfoString(ctx->out,
632 310748 : quote_qualified_identifier(get_namespace_name(get_rel_namespace(RelationGetRelid(relation))),
633 310746 : class_form->relrewrite ?
634 2 : get_rel_name(class_form->relrewrite) :
635 : NameStr(class_form->relname)));
636 310744 : appendStringInfoChar(ctx->out, ':');
637 :
638 310744 : switch (change->action)
639 : {
640 : case REORDER_BUFFER_CHANGE_INSERT:
641 275670 : appendStringInfoString(ctx->out, " INSERT:");
642 275670 : if (change->data.tp.newtuple == NULL)
643 0 : appendStringInfoString(ctx->out, " (no-tuple-data)");
644 : else
645 275670 : tuple_to_stringinfo(ctx->out, tupdesc,
646 275670 : &change->data.tp.newtuple->tuple,
647 : false);
648 275670 : break;
649 : case REORDER_BUFFER_CHANGE_UPDATE:
650 15042 : appendStringInfoString(ctx->out, " UPDATE:");
651 15042 : if (change->data.tp.oldtuple != NULL)
652 : {
653 34 : appendStringInfoString(ctx->out, " old-key:");
654 34 : tuple_to_stringinfo(ctx->out, tupdesc,
655 34 : &change->data.tp.oldtuple->tuple,
656 : true);
657 34 : appendStringInfoString(ctx->out, " new-tuple:");
658 : }
659 :
660 15042 : if (change->data.tp.newtuple == NULL)
661 0 : appendStringInfoString(ctx->out, " (no-tuple-data)");
662 : else
663 15042 : tuple_to_stringinfo(ctx->out, tupdesc,
664 15042 : &change->data.tp.newtuple->tuple,
665 : false);
666 15042 : break;
667 : case REORDER_BUFFER_CHANGE_DELETE:
668 20032 : appendStringInfoString(ctx->out, " DELETE:");
669 :
670 : /* if there was no PK, we only know that a delete happened */
671 20032 : if (change->data.tp.oldtuple == NULL)
672 10014 : appendStringInfoString(ctx->out, " (no-tuple-data)");
673 : /* In DELETE, only the replica identity is present; display that */
674 : else
675 10018 : tuple_to_stringinfo(ctx->out, tupdesc,
676 10018 : &change->data.tp.oldtuple->tuple,
677 : true);
678 20032 : break;
679 : default:
680 0 : Assert(false);
681 : }
682 :
683 310744 : MemoryContextSwitchTo(old);
684 310744 : MemoryContextReset(data->context);
685 :
686 310744 : OutputPluginWrite(ctx, true);
687 310744 : }
688 :
689 : static void
690 6 : pg_decode_truncate(LogicalDecodingContext *ctx, ReorderBufferTXN *txn,
691 : int nrelations, Relation relations[], ReorderBufferChange *change)
692 : {
693 : TestDecodingData *data;
694 : MemoryContext old;
695 : int i;
696 :
697 6 : data = ctx->output_plugin_private;
698 :
699 : /* output BEGIN if we haven't yet */
700 6 : if (data->skip_empty_xacts && !data->xact_wrote_changes)
701 : {
702 6 : pg_output_begin(ctx, data, txn, false);
703 : }
704 6 : data->xact_wrote_changes = true;
705 :
706 : /* Avoid leaking memory by using and resetting our own context */
707 6 : old = MemoryContextSwitchTo(data->context);
708 :
709 6 : OutputPluginPrepareWrite(ctx, true);
710 :
711 6 : appendStringInfoString(ctx->out, "table ");
712 :
713 14 : for (i = 0; i < nrelations; i++)
714 : {
715 8 : if (i > 0)
716 2 : appendStringInfoString(ctx->out, ", ");
717 :
718 8 : appendStringInfoString(ctx->out,
719 8 : quote_qualified_identifier(get_namespace_name(relations[i]->rd_rel->relnamespace),
720 8 : NameStr(relations[i]->rd_rel->relname)));
721 : }
722 :
723 6 : appendStringInfoString(ctx->out, ": TRUNCATE:");
724 :
725 6 : if (change->data.truncate.restart_seqs
726 4 : || change->data.truncate.cascade)
727 : {
728 2 : if (change->data.truncate.restart_seqs)
729 2 : appendStringInfoString(ctx->out, " restart_seqs");
730 4 : if (change->data.truncate.cascade)
731 2 : appendStringInfoString(ctx->out, " cascade");
732 : }
733 : else
734 4 : appendStringInfoString(ctx->out, " (no-flags)");
735 :
736 6 : MemoryContextSwitchTo(old);
737 6 : MemoryContextReset(data->context);
738 :
739 6 : OutputPluginWrite(ctx, true);
740 6 : }
741 :
742 : static void
743 16 : pg_decode_message(LogicalDecodingContext *ctx,
744 : ReorderBufferTXN *txn, XLogRecPtr lsn, bool transactional,
745 : const char *prefix, Size sz, const char *message)
746 : {
747 16 : OutputPluginPrepareWrite(ctx, true);
748 16 : appendStringInfo(ctx->out, "message: transactional: %d prefix: %s, sz: %zu content:",
749 : transactional, prefix, sz);
750 16 : appendBinaryStringInfo(ctx->out, message, sz);
751 16 : OutputPluginWrite(ctx, true);
752 16 : }
753 :
754 : static void
755 20 : pg_decode_stream_start(LogicalDecodingContext *ctx,
756 : ReorderBufferTXN *txn)
757 : {
758 20 : TestDecodingData *data = ctx->output_plugin_private;
759 :
760 20 : data->xact_wrote_changes = false;
761 20 : if (data->skip_empty_xacts)
762 40 : return;
763 0 : pg_output_stream_start(ctx, data, txn, true);
764 : }
765 :
766 : static void
767 10 : pg_output_stream_start(LogicalDecodingContext *ctx, TestDecodingData *data, ReorderBufferTXN *txn, bool last_write)
768 : {
769 10 : OutputPluginPrepareWrite(ctx, last_write);
770 10 : if (data->include_xids)
771 0 : appendStringInfo(ctx->out, "opening a streamed block for transaction TXN %u", txn->xid);
772 : else
773 10 : appendStringInfoString(ctx->out, "opening a streamed block for transaction");
774 10 : OutputPluginWrite(ctx, last_write);
775 10 : }
776 :
777 : static void
778 20 : pg_decode_stream_stop(LogicalDecodingContext *ctx,
779 : ReorderBufferTXN *txn)
780 : {
781 20 : TestDecodingData *data = ctx->output_plugin_private;
782 :
783 20 : if (data->skip_empty_xacts && !data->xact_wrote_changes)
784 30 : return;
785 :
786 10 : OutputPluginPrepareWrite(ctx, true);
787 10 : if (data->include_xids)
788 0 : appendStringInfo(ctx->out, "closing a streamed block for transaction TXN %u", txn->xid);
789 : else
790 10 : appendStringInfoString(ctx->out, "closing a streamed block for transaction");
791 10 : OutputPluginWrite(ctx, true);
792 : }
793 :
794 : static void
795 8 : pg_decode_stream_abort(LogicalDecodingContext *ctx,
796 : ReorderBufferTXN *txn,
797 : XLogRecPtr abort_lsn)
798 : {
799 8 : TestDecodingData *data = ctx->output_plugin_private;
800 :
801 8 : if (data->skip_empty_xacts && !data->xact_wrote_changes)
802 16 : return;
803 :
804 0 : OutputPluginPrepareWrite(ctx, true);
805 0 : if (data->include_xids)
806 0 : appendStringInfo(ctx->out, "aborting streamed (sub)transaction TXN %u", txn->xid);
807 : else
808 0 : appendStringInfoString(ctx->out, "aborting streamed (sub)transaction");
809 0 : OutputPluginWrite(ctx, true);
810 : }
811 :
812 : static void
813 4 : pg_decode_stream_prepare(LogicalDecodingContext *ctx,
814 : ReorderBufferTXN *txn,
815 : XLogRecPtr prepare_lsn)
816 : {
817 4 : TestDecodingData *data = ctx->output_plugin_private;
818 :
819 4 : if (data->skip_empty_xacts && !data->xact_wrote_changes)
820 4 : return;
821 :
822 4 : OutputPluginPrepareWrite(ctx, true);
823 :
824 4 : if (data->include_xids)
825 0 : appendStringInfo(ctx->out, "preparing streamed transaction TXN %u, %s", txn->xid,
826 0 : quote_literal_cstr(txn->gid));
827 : else
828 4 : appendStringInfo(ctx->out, "preparing streamed transaction %s",
829 4 : quote_literal_cstr(txn->gid));
830 :
831 4 : if (data->include_timestamp)
832 0 : appendStringInfo(ctx->out, " (at %s)",
833 : timestamptz_to_str(txn->commit_time));
834 :
835 4 : OutputPluginWrite(ctx, true);
836 : }
837 :
838 : static void
839 6 : pg_decode_stream_commit(LogicalDecodingContext *ctx,
840 : ReorderBufferTXN *txn,
841 : XLogRecPtr commit_lsn)
842 : {
843 6 : TestDecodingData *data = ctx->output_plugin_private;
844 :
845 6 : if (data->skip_empty_xacts && !data->xact_wrote_changes)
846 6 : return;
847 :
848 6 : OutputPluginPrepareWrite(ctx, true);
849 :
850 6 : if (data->include_xids)
851 0 : appendStringInfo(ctx->out, "committing streamed transaction TXN %u", txn->xid);
852 : else
853 6 : appendStringInfoString(ctx->out, "committing streamed transaction");
854 :
855 6 : if (data->include_timestamp)
856 0 : appendStringInfo(ctx->out, " (at %s)",
857 : timestamptz_to_str(txn->commit_time));
858 :
859 6 : OutputPluginWrite(ctx, true);
860 : }
861 :
862 : /*
863 : * In streaming mode, we don't display the changes as the transaction can abort
864 : * at a later point in time. We don't want users to see the changes until the
865 : * transaction is committed.
866 : */
867 : static void
868 142 : pg_decode_stream_change(LogicalDecodingContext *ctx,
869 : ReorderBufferTXN *txn,
870 : Relation relation,
871 : ReorderBufferChange *change)
872 : {
873 142 : TestDecodingData *data = ctx->output_plugin_private;
874 :
875 : /* output stream start if we haven't yet */
876 142 : if (data->skip_empty_xacts && !data->xact_wrote_changes)
877 : {
878 10 : pg_output_stream_start(ctx, data, txn, false);
879 : }
880 142 : data->xact_wrote_changes = true;
881 :
882 142 : OutputPluginPrepareWrite(ctx, true);
883 142 : if (data->include_xids)
884 0 : appendStringInfo(ctx->out, "streaming change for TXN %u", txn->xid);
885 : else
886 142 : appendStringInfoString(ctx->out, "streaming change for transaction");
887 142 : OutputPluginWrite(ctx, true);
888 142 : }
889 :
890 : /*
891 : * In streaming mode, we don't display the contents for transactional messages
892 : * as the transaction can abort at a later point in time. We don't want users to
893 : * see the message contents until the transaction is committed.
894 : */
895 : static void
896 8 : pg_decode_stream_message(LogicalDecodingContext *ctx,
897 : ReorderBufferTXN *txn, XLogRecPtr lsn, bool transactional,
898 : const char *prefix, Size sz, const char *message)
899 : {
900 8 : OutputPluginPrepareWrite(ctx, true);
901 :
902 8 : if (transactional)
903 : {
904 8 : appendStringInfo(ctx->out, "streaming message: transactional: %d prefix: %s, sz: %zu",
905 : transactional, prefix, sz);
906 : }
907 : else
908 : {
909 0 : appendStringInfo(ctx->out, "streaming message: transactional: %d prefix: %s, sz: %zu content:",
910 : transactional, prefix, sz);
911 0 : appendBinaryStringInfo(ctx->out, message, sz);
912 : }
913 :
914 8 : OutputPluginWrite(ctx, true);
915 8 : }
916 :
917 : /*
918 : * In streaming mode, we don't display the detailed information of Truncate.
919 : * See pg_decode_stream_change.
920 : */
921 : static void
922 0 : pg_decode_stream_truncate(LogicalDecodingContext *ctx, ReorderBufferTXN *txn,
923 : int nrelations, Relation relations[],
924 : ReorderBufferChange *change)
925 : {
926 0 : TestDecodingData *data = ctx->output_plugin_private;
927 :
928 0 : if (data->skip_empty_xacts && !data->xact_wrote_changes)
929 : {
930 0 : pg_output_stream_start(ctx, data, txn, false);
931 : }
932 0 : data->xact_wrote_changes = true;
933 :
934 0 : OutputPluginPrepareWrite(ctx, true);
935 0 : if (data->include_xids)
936 0 : appendStringInfo(ctx->out, "streaming truncate for TXN %u", txn->xid);
937 : else
938 0 : appendStringInfoString(ctx->out, "streaming truncate for transaction");
939 0 : OutputPluginWrite(ctx, true);
940 0 : }
|