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