Commit 0aa8a01d authored by Amit Kapila's avatar Amit Kapila

Extend the output plugin API to allow decoding of prepared xacts.

This adds six methods to the output plugin API, adding support for
streaming changes of two-phase transactions at prepare time.

* begin_prepare
* filter_prepare
* prepare
* commit_prepared
* rollback_prepared
* stream_prepare

Most of this is a simple extension of the existing methods, with the
semantic difference that the transaction is not yet committed and maybe
aborted later.

Until now two-phase transactions were translated into regular transactions
on the subscriber, and the GID was not forwarded to it. None of the
two-phase commands were communicated to the subscriber.

This patch provides the infrastructure for logical decoding plugins to be
informed of two-phase commands Like PREPARE TRANSACTION, COMMIT PREPARED
and ROLLBACK PREPARED commands with the corresponding GID.

This also extends the 'test_decoding' plugin, implementing these new
methods.

This commit simply adds these new APIs and the upcoming patch to "allow
the decoding at prepare time in ReorderBuffer" will use these APIs.

Author: Ajin Cherian and Amit Kapila based on previous work by Nikhil Sontakke and Stas Kelvich
Reviewed-by: Amit Kapila, Peter Smith, Sawada Masahiko, and Dilip Kumar
Discussion:
https://postgr.es/m/02DA5F5E-CECE-4D9C-8B4B-418077E2C010@postgrespro.ru
https://postgr.es/m/CAMGcDxeqEpWj3fTXwqhSwBdXd2RS9jzwWscO-XbeCfso6ts3+Q@mail.gmail.com
parent fa744697
...@@ -76,6 +76,20 @@ static void pg_decode_message(LogicalDecodingContext *ctx, ...@@ -76,6 +76,20 @@ static void pg_decode_message(LogicalDecodingContext *ctx,
ReorderBufferTXN *txn, XLogRecPtr message_lsn, ReorderBufferTXN *txn, XLogRecPtr message_lsn,
bool transactional, const char *prefix, bool transactional, const char *prefix,
Size sz, const char *message); Size sz, const char *message);
static bool pg_decode_filter_prepare(LogicalDecodingContext *ctx,
const char *gid);
static void pg_decode_begin_prepare_txn(LogicalDecodingContext *ctx,
ReorderBufferTXN *txn);
static void pg_decode_prepare_txn(LogicalDecodingContext *ctx,
ReorderBufferTXN *txn,
XLogRecPtr prepare_lsn);
static void pg_decode_commit_prepared_txn(LogicalDecodingContext *ctx,
ReorderBufferTXN *txn,
XLogRecPtr commit_lsn);
static void pg_decode_rollback_prepared_txn(LogicalDecodingContext *ctx,
ReorderBufferTXN *txn,
XLogRecPtr prepare_end_lsn,
TimestampTz prepare_time);
static void pg_decode_stream_start(LogicalDecodingContext *ctx, static void pg_decode_stream_start(LogicalDecodingContext *ctx,
ReorderBufferTXN *txn); ReorderBufferTXN *txn);
static void pg_output_stream_start(LogicalDecodingContext *ctx, static void pg_output_stream_start(LogicalDecodingContext *ctx,
...@@ -87,6 +101,9 @@ static void pg_decode_stream_stop(LogicalDecodingContext *ctx, ...@@ -87,6 +101,9 @@ static void pg_decode_stream_stop(LogicalDecodingContext *ctx,
static void pg_decode_stream_abort(LogicalDecodingContext *ctx, static void pg_decode_stream_abort(LogicalDecodingContext *ctx,
ReorderBufferTXN *txn, ReorderBufferTXN *txn,
XLogRecPtr abort_lsn); XLogRecPtr abort_lsn);
static void pg_decode_stream_prepare(LogicalDecodingContext *ctx,
ReorderBufferTXN *txn,
XLogRecPtr prepare_lsn);
static void pg_decode_stream_commit(LogicalDecodingContext *ctx, static void pg_decode_stream_commit(LogicalDecodingContext *ctx,
ReorderBufferTXN *txn, ReorderBufferTXN *txn,
XLogRecPtr commit_lsn); XLogRecPtr commit_lsn);
...@@ -123,9 +140,15 @@ _PG_output_plugin_init(OutputPluginCallbacks *cb) ...@@ -123,9 +140,15 @@ _PG_output_plugin_init(OutputPluginCallbacks *cb)
cb->filter_by_origin_cb = pg_decode_filter; cb->filter_by_origin_cb = pg_decode_filter;
cb->shutdown_cb = pg_decode_shutdown; cb->shutdown_cb = pg_decode_shutdown;
cb->message_cb = pg_decode_message; cb->message_cb = pg_decode_message;
cb->filter_prepare_cb = pg_decode_filter_prepare;
cb->begin_prepare_cb = pg_decode_begin_prepare_txn;
cb->prepare_cb = pg_decode_prepare_txn;
cb->commit_prepared_cb = pg_decode_commit_prepared_txn;
cb->rollback_prepared_cb = pg_decode_rollback_prepared_txn;
cb->stream_start_cb = pg_decode_stream_start; cb->stream_start_cb = pg_decode_stream_start;
cb->stream_stop_cb = pg_decode_stream_stop; cb->stream_stop_cb = pg_decode_stream_stop;
cb->stream_abort_cb = pg_decode_stream_abort; cb->stream_abort_cb = pg_decode_stream_abort;
cb->stream_prepare_cb = pg_decode_stream_prepare;
cb->stream_commit_cb = pg_decode_stream_commit; cb->stream_commit_cb = pg_decode_stream_commit;
cb->stream_change_cb = pg_decode_stream_change; cb->stream_change_cb = pg_decode_stream_change;
cb->stream_message_cb = pg_decode_stream_message; cb->stream_message_cb = pg_decode_stream_message;
...@@ -141,6 +164,7 @@ pg_decode_startup(LogicalDecodingContext *ctx, OutputPluginOptions *opt, ...@@ -141,6 +164,7 @@ pg_decode_startup(LogicalDecodingContext *ctx, OutputPluginOptions *opt,
ListCell *option; ListCell *option;
TestDecodingData *data; TestDecodingData *data;
bool enable_streaming = false; bool enable_streaming = false;
bool enable_twophase = false;
data = palloc0(sizeof(TestDecodingData)); data = palloc0(sizeof(TestDecodingData));
data->context = AllocSetContextCreate(ctx->context, data->context = AllocSetContextCreate(ctx->context,
...@@ -241,6 +265,16 @@ pg_decode_startup(LogicalDecodingContext *ctx, OutputPluginOptions *opt, ...@@ -241,6 +265,16 @@ pg_decode_startup(LogicalDecodingContext *ctx, OutputPluginOptions *opt,
errmsg("could not parse value \"%s\" for parameter \"%s\"", errmsg("could not parse value \"%s\" for parameter \"%s\"",
strVal(elem->arg), elem->defname))); strVal(elem->arg), elem->defname)));
} }
else if (strcmp(elem->defname, "two-phase-commit") == 0)
{
if (elem->arg == NULL)
continue;
else if (!parse_bool(strVal(elem->arg), &enable_twophase))
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("could not parse value \"%s\" for parameter \"%s\"",
strVal(elem->arg), elem->defname)));
}
else else
{ {
ereport(ERROR, ereport(ERROR,
...@@ -252,6 +286,7 @@ pg_decode_startup(LogicalDecodingContext *ctx, OutputPluginOptions *opt, ...@@ -252,6 +286,7 @@ pg_decode_startup(LogicalDecodingContext *ctx, OutputPluginOptions *opt,
} }
ctx->streaming &= enable_streaming; ctx->streaming &= enable_streaming;
ctx->twophase &= enable_twophase;
} }
/* cleanup this plugin's resources */ /* cleanup this plugin's resources */
...@@ -320,6 +355,111 @@ pg_decode_commit_txn(LogicalDecodingContext *ctx, ReorderBufferTXN *txn, ...@@ -320,6 +355,111 @@ pg_decode_commit_txn(LogicalDecodingContext *ctx, ReorderBufferTXN *txn,
OutputPluginWrite(ctx, true); OutputPluginWrite(ctx, true);
} }
/* BEGIN PREPARE callback */
static void
pg_decode_begin_prepare_txn(LogicalDecodingContext *ctx, ReorderBufferTXN *txn)
{
TestDecodingData *data = ctx->output_plugin_private;
TestDecodingTxnData *txndata =
MemoryContextAllocZero(ctx->context, sizeof(TestDecodingTxnData));
txndata->xact_wrote_changes = false;
txn->output_plugin_private = txndata;
if (data->skip_empty_xacts)
return;
pg_output_begin(ctx, data, txn, true);
}
/* PREPARE callback */
static void
pg_decode_prepare_txn(LogicalDecodingContext *ctx, ReorderBufferTXN *txn,
XLogRecPtr prepare_lsn)
{
TestDecodingData *data = ctx->output_plugin_private;
TestDecodingTxnData *txndata = txn->output_plugin_private;
if (data->skip_empty_xacts && !txndata->xact_wrote_changes)
return;
OutputPluginPrepareWrite(ctx, true);
appendStringInfo(ctx->out, "PREPARE TRANSACTION %s",
quote_literal_cstr(txn->gid));
if (data->include_xids)
appendStringInfo(ctx->out, ", txid %u", txn->xid);
if (data->include_timestamp)
appendStringInfo(ctx->out, " (at %s)",
timestamptz_to_str(txn->commit_time));
OutputPluginWrite(ctx, true);
}
/* COMMIT PREPARED callback */
static void
pg_decode_commit_prepared_txn(LogicalDecodingContext *ctx, ReorderBufferTXN *txn,
XLogRecPtr commit_lsn)
{
TestDecodingData *data = ctx->output_plugin_private;
OutputPluginPrepareWrite(ctx, true);
appendStringInfo(ctx->out, "COMMIT PREPARED %s",
quote_literal_cstr(txn->gid));
if (data->include_xids)
appendStringInfo(ctx->out, ", txid %u", txn->xid);
if (data->include_timestamp)
appendStringInfo(ctx->out, " (at %s)",
timestamptz_to_str(txn->commit_time));
OutputPluginWrite(ctx, true);
}
/* ROLLBACK PREPARED callback */
static void
pg_decode_rollback_prepared_txn(LogicalDecodingContext *ctx,
ReorderBufferTXN *txn,
XLogRecPtr prepare_end_lsn,
TimestampTz prepare_time)
{
TestDecodingData *data = ctx->output_plugin_private;
OutputPluginPrepareWrite(ctx, true);
appendStringInfo(ctx->out, "ROLLBACK PREPARED %s",
quote_literal_cstr(txn->gid));
if (data->include_xids)
appendStringInfo(ctx->out, ", txid %u", txn->xid);
if (data->include_timestamp)
appendStringInfo(ctx->out, " (at %s)",
timestamptz_to_str(txn->commit_time));
OutputPluginWrite(ctx, true);
}
/*
* Filter out two-phase transactions.
*
* Each plugin can implement its own filtering logic. Here we demonstrate a
* simple logic by checking the GID. If the GID contains the "_nodecode"
* substring, then we filter it out.
*/
static bool
pg_decode_filter_prepare(LogicalDecodingContext *ctx, const char *gid)
{
if (strstr(gid, "_nodecode") != NULL)
return true;
return false;
}
static bool static bool
pg_decode_filter(LogicalDecodingContext *ctx, pg_decode_filter(LogicalDecodingContext *ctx,
RepOriginId origin_id) RepOriginId origin_id)
...@@ -701,6 +841,33 @@ pg_decode_stream_abort(LogicalDecodingContext *ctx, ...@@ -701,6 +841,33 @@ pg_decode_stream_abort(LogicalDecodingContext *ctx,
OutputPluginWrite(ctx, true); OutputPluginWrite(ctx, true);
} }
static void
pg_decode_stream_prepare(LogicalDecodingContext *ctx,
ReorderBufferTXN *txn,
XLogRecPtr prepare_lsn)
{
TestDecodingData *data = ctx->output_plugin_private;
TestDecodingTxnData *txndata = txn->output_plugin_private;
if (data->skip_empty_xacts && !txndata->xact_wrote_changes)
return;
OutputPluginPrepareWrite(ctx, true);
if (data->include_xids)
appendStringInfo(ctx->out, "preparing streamed transaction TXN %s, txid %u",
quote_literal_cstr(txn->gid), txn->xid);
else
appendStringInfo(ctx->out, "preparing streamed transaction %s",
quote_literal_cstr(txn->gid));
if (data->include_timestamp)
appendStringInfo(ctx->out, " (at %s)",
timestamptz_to_str(txn->commit_time));
OutputPluginWrite(ctx, true);
}
static void static void
pg_decode_stream_commit(LogicalDecodingContext *ctx, pg_decode_stream_commit(LogicalDecodingContext *ctx,
ReorderBufferTXN *txn, ReorderBufferTXN *txn,
......
This diff is collapsed.
This diff is collapsed.
...@@ -84,6 +84,11 @@ typedef struct LogicalDecodingContext ...@@ -84,6 +84,11 @@ typedef struct LogicalDecodingContext
*/ */
bool streaming; bool streaming;
/*
* Does the output plugin support two-phase decoding, and is it enabled?
*/
bool twophase;
/* /*
* State for writing output. * State for writing output.
*/ */
...@@ -120,6 +125,7 @@ extern void LogicalIncreaseRestartDecodingForSlot(XLogRecPtr current_lsn, ...@@ -120,6 +125,7 @@ extern void LogicalIncreaseRestartDecodingForSlot(XLogRecPtr current_lsn,
XLogRecPtr restart_lsn); XLogRecPtr restart_lsn);
extern void LogicalConfirmReceivedLocation(XLogRecPtr lsn); extern void LogicalConfirmReceivedLocation(XLogRecPtr lsn);
extern bool filter_prepare_cb_wrapper(LogicalDecodingContext *ctx, const char *gid);
extern bool filter_by_origin_cb_wrapper(LogicalDecodingContext *ctx, RepOriginId origin_id); extern bool filter_by_origin_cb_wrapper(LogicalDecodingContext *ctx, RepOriginId origin_id);
extern void ResetLogicalStreamingState(void); extern void ResetLogicalStreamingState(void);
extern void UpdateDecodingStats(LogicalDecodingContext *ctx); extern void UpdateDecodingStats(LogicalDecodingContext *ctx);
......
...@@ -99,6 +99,45 @@ typedef bool (*LogicalDecodeFilterByOriginCB) (struct LogicalDecodingContext *ct ...@@ -99,6 +99,45 @@ typedef bool (*LogicalDecodeFilterByOriginCB) (struct LogicalDecodingContext *ct
*/ */
typedef void (*LogicalDecodeShutdownCB) (struct LogicalDecodingContext *ctx); typedef void (*LogicalDecodeShutdownCB) (struct LogicalDecodingContext *ctx);
/*
* Called before decoding of PREPARE record to decide whether this
* transaction should be decoded with separate calls to prepare and
* commit_prepared/rollback_prepared callbacks or wait till COMMIT PREPARED
* and sent as usual transaction.
*/
typedef bool (*LogicalDecodeFilterPrepareCB) (struct LogicalDecodingContext *ctx,
const char *gid);
/*
* Callback called for every BEGIN of a prepared trnsaction.
*/
typedef void (*LogicalDecodeBeginPrepareCB) (struct LogicalDecodingContext *ctx,
ReorderBufferTXN *txn);
/*
* Called for PREPARE record unless it was filtered by filter_prepare()
* callback.
*/
typedef void (*LogicalDecodePrepareCB) (struct LogicalDecodingContext *ctx,
ReorderBufferTXN *txn,
XLogRecPtr prepare_lsn);
/*
* Called for COMMIT PREPARED.
*/
typedef void (*LogicalDecodeCommitPreparedCB) (struct LogicalDecodingContext *ctx,
ReorderBufferTXN *txn,
XLogRecPtr commit_lsn);
/*
* Called for ROLLBACK PREPARED.
*/
typedef void (*LogicalDecodeRollbackPreparedCB) (struct LogicalDecodingContext *ctx,
ReorderBufferTXN *txn,
XLogRecPtr prepare_end_lsn,
TimestampTz prepare_time);
/* /*
* Called when starting to stream a block of changes from in-progress * Called when starting to stream a block of changes from in-progress
* transaction (may be called repeatedly, if it's streamed in multiple * transaction (may be called repeatedly, if it's streamed in multiple
...@@ -123,6 +162,14 @@ typedef void (*LogicalDecodeStreamAbortCB) (struct LogicalDecodingContext *ctx, ...@@ -123,6 +162,14 @@ typedef void (*LogicalDecodeStreamAbortCB) (struct LogicalDecodingContext *ctx,
ReorderBufferTXN *txn, ReorderBufferTXN *txn,
XLogRecPtr abort_lsn); XLogRecPtr abort_lsn);
/*
* Called to prepare changes streamed to remote node from in-progress
* transaction. This is called as part of a two-phase commit.
*/
typedef void (*LogicalDecodeStreamPrepareCB) (struct LogicalDecodingContext *ctx,
ReorderBufferTXN *txn,
XLogRecPtr prepare_lsn);
/* /*
* Called to apply changes streamed to remote node from in-progress * Called to apply changes streamed to remote node from in-progress
* transaction. * transaction.
...@@ -173,10 +220,19 @@ typedef struct OutputPluginCallbacks ...@@ -173,10 +220,19 @@ typedef struct OutputPluginCallbacks
LogicalDecodeMessageCB message_cb; LogicalDecodeMessageCB message_cb;
LogicalDecodeFilterByOriginCB filter_by_origin_cb; LogicalDecodeFilterByOriginCB filter_by_origin_cb;
LogicalDecodeShutdownCB shutdown_cb; LogicalDecodeShutdownCB shutdown_cb;
/* streaming of changes at prepare time */
LogicalDecodeFilterPrepareCB filter_prepare_cb;
LogicalDecodeBeginPrepareCB begin_prepare_cb;
LogicalDecodePrepareCB prepare_cb;
LogicalDecodeCommitPreparedCB commit_prepared_cb;
LogicalDecodeRollbackPreparedCB rollback_prepared_cb;
/* streaming of changes */ /* streaming of changes */
LogicalDecodeStreamStartCB stream_start_cb; LogicalDecodeStreamStartCB stream_start_cb;
LogicalDecodeStreamStopCB stream_stop_cb; LogicalDecodeStreamStopCB stream_stop_cb;
LogicalDecodeStreamAbortCB stream_abort_cb; LogicalDecodeStreamAbortCB stream_abort_cb;
LogicalDecodeStreamPrepareCB stream_prepare_cb;
LogicalDecodeStreamCommitCB stream_commit_cb; LogicalDecodeStreamCommitCB stream_commit_cb;
LogicalDecodeStreamChangeCB stream_change_cb; LogicalDecodeStreamChangeCB stream_change_cb;
LogicalDecodeStreamMessageCB stream_message_cb; LogicalDecodeStreamMessageCB stream_message_cb;
......
...@@ -244,6 +244,12 @@ typedef struct ReorderBufferTXN ...@@ -244,6 +244,12 @@ typedef struct ReorderBufferTXN
/* Xid of top-level transaction, if known */ /* Xid of top-level transaction, if known */
TransactionId toplevel_xid; TransactionId toplevel_xid;
/*
* Global transaction id required for identification of prepared
* transactions.
*/
char *gid;
/* /*
* LSN of the first data carrying, WAL record with knowledge about this * LSN of the first data carrying, WAL record with knowledge about this
* xid. This is allowed to *not* be first record adorned with this xid, if * xid. This is allowed to *not* be first record adorned with this xid, if
...@@ -418,6 +424,26 @@ typedef void (*ReorderBufferMessageCB) (ReorderBuffer *rb, ...@@ -418,6 +424,26 @@ typedef void (*ReorderBufferMessageCB) (ReorderBuffer *rb,
const char *prefix, Size sz, const char *prefix, Size sz,
const char *message); const char *message);
/* begin prepare callback signature */
typedef void (*ReorderBufferBeginPrepareCB) (ReorderBuffer *rb,
ReorderBufferTXN *txn);
/* prepare callback signature */
typedef void (*ReorderBufferPrepareCB) (ReorderBuffer *rb,
ReorderBufferTXN *txn,
XLogRecPtr prepare_lsn);
/* commit prepared callback signature */
typedef void (*ReorderBufferCommitPreparedCB) (ReorderBuffer *rb,
ReorderBufferTXN *txn,
XLogRecPtr commit_lsn);
/* rollback prepared callback signature */
typedef void (*ReorderBufferRollbackPreparedCB) (ReorderBuffer *rb,
ReorderBufferTXN *txn,
XLogRecPtr prepare_end_lsn,
TimestampTz prepare_time);
/* start streaming transaction callback signature */ /* start streaming transaction callback signature */
typedef void (*ReorderBufferStreamStartCB) ( typedef void (*ReorderBufferStreamStartCB) (
ReorderBuffer *rb, ReorderBuffer *rb,
...@@ -436,6 +462,12 @@ typedef void (*ReorderBufferStreamAbortCB) ( ...@@ -436,6 +462,12 @@ typedef void (*ReorderBufferStreamAbortCB) (
ReorderBufferTXN *txn, ReorderBufferTXN *txn,
XLogRecPtr abort_lsn); XLogRecPtr abort_lsn);
/* prepare streamed transaction callback signature */
typedef void (*ReorderBufferStreamPrepareCB) (
ReorderBuffer *rb,
ReorderBufferTXN *txn,
XLogRecPtr prepare_lsn);
/* commit streamed transaction callback signature */ /* commit streamed transaction callback signature */
typedef void (*ReorderBufferStreamCommitCB) ( typedef void (*ReorderBufferStreamCommitCB) (
ReorderBuffer *rb, ReorderBuffer *rb,
...@@ -504,12 +536,21 @@ struct ReorderBuffer ...@@ -504,12 +536,21 @@ struct ReorderBuffer
ReorderBufferCommitCB commit; ReorderBufferCommitCB commit;
ReorderBufferMessageCB message; ReorderBufferMessageCB message;
/*
* Callbacks to be called when streaming a transaction at prepare time.
*/
ReorderBufferBeginCB begin_prepare;
ReorderBufferPrepareCB prepare;
ReorderBufferCommitPreparedCB commit_prepared;
ReorderBufferRollbackPreparedCB rollback_prepared;
/* /*
* Callbacks to be called when streaming a transaction. * Callbacks to be called when streaming a transaction.
*/ */
ReorderBufferStreamStartCB stream_start; ReorderBufferStreamStartCB stream_start;
ReorderBufferStreamStopCB stream_stop; ReorderBufferStreamStopCB stream_stop;
ReorderBufferStreamAbortCB stream_abort; ReorderBufferStreamAbortCB stream_abort;
ReorderBufferStreamPrepareCB stream_prepare;
ReorderBufferStreamCommitCB stream_commit; ReorderBufferStreamCommitCB stream_commit;
ReorderBufferStreamChangeCB stream_change; ReorderBufferStreamChangeCB stream_change;
ReorderBufferStreamMessageCB stream_message; ReorderBufferStreamMessageCB stream_message;
......
...@@ -1315,9 +1315,21 @@ LogStmtLevel ...@@ -1315,9 +1315,21 @@ LogStmtLevel
LogicalDecodeBeginCB LogicalDecodeBeginCB
LogicalDecodeChangeCB LogicalDecodeChangeCB
LogicalDecodeCommitCB LogicalDecodeCommitCB
LogicalDecodeFilterPrepareCB
LogicalDecodeBeginPrepareCB
LogicalDecodePrepareCB
LogicalDecodeCommitPreparedCB
LogicalDecodeRollbackPreparedCB
LogicalDecodeFilterByOriginCB LogicalDecodeFilterByOriginCB
LogicalDecodeMessageCB LogicalDecodeMessageCB
LogicalDecodeShutdownCB LogicalDecodeShutdownCB
LogicalDecodeStreamStartCB
LogicalDecodeStreamStopCB
LogicalDecodeStreamAbortCB
LogicalDecodeStreamPrepareCB
LogicalDecodeStreamCommitCB
LogicalDecodeStreamChangeCB
LogicalDecodeStreamMessageCB
LogicalDecodeStartupCB LogicalDecodeStartupCB
LogicalDecodeTruncateCB LogicalDecodeTruncateCB
LogicalDecodingContext LogicalDecodingContext
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment