一个示例输出插件可以在 PostgreSQL 源代码树的 contrib/test_decoding
子目录中找到。
输出插件通过动态加载共享库来加载,库的基名称为输出插件的名称。使用正常的库搜索路径来定位库。为了提供所需的输出插件回调并指示库实际上是一个输出插件,它需要提供一个名为 _PG_output_plugin_init
的函数。此函数传递一个结构体,该结构体需要填充各个操作的回调函数指针。
typedef struct OutputPluginCallbacks { LogicalDecodeStartupCB startup_cb; LogicalDecodeBeginCB begin_cb; LogicalDecodeChangeCB change_cb; LogicalDecodeTruncateCB truncate_cb; LogicalDecodeCommitCB commit_cb; LogicalDecodeMessageCB message_cb; LogicalDecodeFilterByOriginCB filter_by_origin_cb; LogicalDecodeShutdownCB shutdown_cb; LogicalDecodeFilterPrepareCB filter_prepare_cb; LogicalDecodeBeginPrepareCB begin_prepare_cb; LogicalDecodePrepareCB prepare_cb; LogicalDecodeCommitPreparedCB commit_prepared_cb; LogicalDecodeRollbackPreparedCB rollback_prepared_cb; LogicalDecodeStreamStartCB stream_start_cb; LogicalDecodeStreamStopCB stream_stop_cb; LogicalDecodeStreamAbortCB stream_abort_cb; LogicalDecodeStreamPrepareCB stream_prepare_cb; LogicalDecodeStreamCommitCB stream_commit_cb; LogicalDecodeStreamChangeCB stream_change_cb; LogicalDecodeStreamMessageCB stream_message_cb; LogicalDecodeStreamTruncateCB stream_truncate_cb; } OutputPluginCallbacks; typedef void (*LogicalOutputPluginInit) (struct OutputPluginCallbacks *cb);
begin_cb
、change_cb
和 commit_cb
回调是必需的,而 startup_cb
、truncate_cb
、message_cb
、filter_by_origin_cb
和 shutdown_cb
是可选的。如果 truncate_cb
未设置但要解码 TRUNCATE
,则该操作将被忽略。
输出插件还可以定义函数来支持大型正在进行的事务的流式传输。stream_start_cb
、stream_stop_cb
、stream_abort_cb
、stream_commit_cb
和 stream_change_cb
是必需的,而 stream_message_cb
和 stream_truncate_cb
是可选的。stream_prepare_cb
也是必需的,如果输出插件还支持两阶段提交。
输出插件还可以定义函数来支持两阶段提交,这允许在 PREPARE TRANSACTION
上解码操作。begin_prepare_cb
、prepare_cb
、commit_prepared_cb
和 rollback_prepared_cb
回调是必需的,而 filter_prepare_cb
是可选的。stream_prepare_cb
也是必需的,如果输出插件还支持大型正在进行的事务的流式传输。
为了解码、格式化和输出更改,输出插件可以使用大部分后端的正常基础设施,包括调用输出函数。只要只访问在 pg_catalog
模式下由 initdb
创建的关系,或者使用以下方法标记为用户提供的目录表的关系,就可以允许只读访问关系。
ALTER TABLE user_catalog_table SET (user_catalog_table = true); CREATE TABLE another_catalog_table(data text) WITH (user_catalog_table = true);
请注意,输出插件中对用户目录表或常规系统目录表的访问必须仅通过 systable_*
扫描 API 进行。通过 heap_*
扫描 API 进行访问将导致错误。此外,任何导致事务 ID 分配的操作都禁止。这包括但不限于写入表、执行 DDL 更改以及调用 pg_current_xact_id()
。
输出插件回调可以以几乎任意格式将数据传递给使用者。对于某些用例,例如通过 SQL 查看更改,以可以包含任意数据的类型(例如 bytea
)返回数据很麻烦。如果输出插件仅以服务器的编码输出文本数据,则可以通过将 OutputPluginOptions.output_type
设置为 OUTPUT_PLUGIN_TEXTUAL_OUTPUT
而不是 OUTPUT_PLUGIN_BINARY_OUTPUT
来声明,在 启动回调 中。在这种情况下,所有数据都必须使用服务器的编码,以便 text
数据可以包含它。在启用断言的构建中会检查这一点。
输出插件通过它需要提供的各种回调来获取有关正在发生的更改的通知。
并发事务按提交顺序解码,并且只有属于特定事务的更改在 begin
和 commit
回调之间解码。显式或隐式回滚的事务及其内容永远不会被解码。成功的事务点折叠到包含它们的事务中,并按照它们在该事务中执行的顺序进行折叠。使用 PREPARE TRANSACTION
为两阶段提交准备的事务也将被解码,前提是提供了解码它们所需的输出插件回调。当前正在解码的准备的事务可能会通过 ROLLBACK PREPARED
命令并发中止。在这种情况下,此事务的逻辑解码也将中止。一旦检测到中止并调用 prepare_cb
回调,此类事务的所有更改都将被跳过。因此,即使在并发中止的情况下,也向输出插件提供了足够的信息,以便在解码后正确处理 ROLLBACK PREPARED
。
仅解码已安全刷新到磁盘的事务。当 synchronous_commit
设置为 off
时,这可能导致在直接后续的 pg_logical_slot_get_changes()
中 COMMIT
未立即解码。
无论准备输出多少更改,只要创建复制槽或要求流式传输更改,就会调用可选的 startup_cb
回调。
typedef void (*LogicalDecodeStartupCB) (struct LogicalDecodingContext *ctx, OutputPluginOptions *options, bool is_init);
当创建复制槽时,is_init
参数将为 true,否则为 false。options
指向输出插件可以设置选项的结构体
typedef struct OutputPluginOptions { OutputPluginOutputType output_type; bool receive_rewrites; } OutputPluginOptions;
output_type
必须设置为 OUTPUT_PLUGIN_TEXTUAL_OUTPUT
或 OUTPUT_PLUGIN_BINARY_OUTPUT
。另请参见 第 47.6.3 节。如果 receive_rewrites
为 true,则输出插件也将被调用以对某些 DDL 操作期间堆重写所做的更改进行调用。这些对处理 DDL 复制的插件很有用,但需要特殊处理。
启动回调应验证 ctx->output_plugin_options
中存在的选项。如果输出插件需要具有状态,则可以使用 ctx->output_plugin_private
来存储它。
只要以前处于活动状态的复制槽不再使用,就会调用可选的 shutdown_cb
回调,并且可以使用它来释放输出插件专用的资源。该槽不一定被删除,只是停止了流式传输。
typedef void (*LogicalDecodeShutdownCB) (struct LogicalDecodingContext *ctx);
只要解码已提交事务的开始,就会调用必需的 begin_cb
回调。中止的事务及其内容永远不会被解码。
typedef void (*LogicalDecodeBeginCB) (struct LogicalDecodingContext *ctx, ReorderBufferTXN *txn);
txn
参数包含有关事务的元信息,例如其提交时间戳及其 XID。
只要解码事务提交,就会调用必需的 commit_cb
回调。如果存在任何修改的行,则所有修改行的 change_cb
回调都将在其之前被调用。
typedef void (*LogicalDecodeCommitCB) (struct LogicalDecodingContext *ctx, ReorderBufferTXN *txn, XLogRecPtr commit_lsn);
对于事务内每个单独的行修改,都会调用必需的 change_cb
回调,无论是 INSERT
、UPDATE
还是 DELETE
。即使原始命令一次修改多行,也会针对每一行单独调用回调。change_cb
回调可以访问系统或用户目录表以帮助输出行修改详细信息。在解码准备(但尚未提交)的事务或解码未提交的事务的情况下,此更改回调也可能由于同时回滚此完全相同的事务而导致错误。在这种情况下,此中止事务的逻辑解码将被优雅地停止。
typedef void (*LogicalDecodeChangeCB) (struct LogicalDecodingContext *ctx, ReorderBufferTXN *txn, Relation relation, ReorderBufferChange *change);
ctx
和 txn
参数与 begin_cb
和 commit_cb
回调的参数具有相同的内容,但此外,关系描述符 relation
指向该行所属的关系,并且传递了描述行修改的结构体 change
。
仅可以使用逻辑解码提取未记录(请参见 UNLOGGED
)且不是临时(请参见 TEMPORARY
或 TEMP
)的用户定义表中的更改。
对于 TRUNCATE
命令,将调用可选的 truncate_cb
回调。
typedef void (*LogicalDecodeTruncateCB) (struct LogicalDecodingContext *ctx, ReorderBufferTXN *txn, int nrelations, Relation relations[], ReorderBufferChange *change);
参数类似于 change_cb
回调。但是,由于需要一起执行由外键连接的表上的 TRUNCATE
操作,因此此回调接收关系数组而不是单个关系。有关详细信息,请参阅 TRUNCATE 语句的说明。
可选的 filter_by_origin_cb
回调函数用于确定是否需要将从 origin_id
回放的数据输出到输出插件。
typedef bool (*LogicalDecodeFilterByOriginCB) (struct LogicalDecodingContext *ctx, RepOriginId origin_id);
ctx
参数与其他回调函数中的内容相同。除了来源节点之外,没有其他信息可用。如果要表明来自传递的节点的变化无关紧要,则返回 true,导致它们被过滤掉;否则返回 false。对于已被过滤掉的事务和更改,其他回调函数将不会被调用。
这在实现级联或多向复制解决方案时非常有用。通过来源节点进行过滤可以防止在这些设置中来回复制相同的更改。虽然事务和更改也包含关于来源节点的信息,但通过此回调函数进行过滤效率明显更高。
可选的 message_cb
回调函数在解码逻辑解码消息时被调用。
typedef void (*LogicalDecodeMessageCB) (struct LogicalDecodingContext *ctx, ReorderBufferTXN *txn, XLogRecPtr message_lsn, bool transactional, const char *prefix, Size message_size, const char *message);
txn
参数包含有关事务的元信息,例如事务提交的时间戳及其 XID。但是请注意,当消息是非事务性的并且尚未在记录消息的事务中分配 XID 时,它可能为 NULL。 lsn
包含消息的 WAL 位置。 transactional
表示消息是否作为事务发送。类似于更改回调,在解码已准备(但尚未提交)的事务或未提交的事务时,此消息回调也可能因同时回滚同一事务而出错。在这种情况下,此已中止事务的逻辑解码将被优雅地停止。 prefix
是任意以 null 结尾的前缀,可用于识别当前插件感兴趣的消息。最后, message
参数保存实际大小为 message_size
的消息。
应格外小心,以确保输出插件认为感兴趣的前缀是唯一的。使用扩展名或输出插件本身的名称通常是一个不错的选择。
可选的 filter_prepare_cb
回调函数用于确定当前两阶段提交事务的一部分数据是否应在此准备阶段或稍后作为常规的一阶段事务在 COMMIT PREPARED
时进行解码。如果要表明应跳过解码,则返回 true
;否则返回 false
。当未定义回调函数时,假定为 false
(即不进行过滤,所有使用两阶段提交的事务也都在两个阶段进行解码)。
typedef bool (*LogicalDecodeFilterPrepareCB) (struct LogicalDecodingContext *ctx, TransactionId xid, const char *gid);
ctx
参数与其他回调函数中的内容相同。 xid
和 gid
参数提供了两种不同的方法来识别事务。后来的 COMMIT PREPARED
或 ROLLBACK PREPARED
包含这两个标识符,从而为输出插件提供了选择使用哪个标识符的选项。
每个事务可以多次调用回调函数以进行解码,并且必须为给定的 xid
和 gid
对在每次调用时提供相同的静态答案。
必需的 begin_prepare_cb
回调函数在解码已准备事务的开始时被调用。 gid
字段(它是 txn
参数的一部分)可在此回调函数中用于检查插件是否已收到此 PREPARE
,在这种情况下,它可以出错或跳过事务的剩余更改。
typedef void (*LogicalDecodeBeginPrepareCB) (struct LogicalDecodingContext *ctx, ReorderBufferTXN *txn);
必需的 prepare_cb
回调函数在解码为两阶段提交而准备的事务时被调用。如果存在任何已修改的行,则在此之前将已为所有已修改的行调用 change_cb
回调函数。 gid
字段(它是 txn
参数的一部分)可在此回调函数中使用。
typedef void (*LogicalDecodePrepareCB) (struct LogicalDecodingContext *ctx, ReorderBufferTXN *txn, XLogRecPtr prepare_lsn);
必需的 commit_prepared_cb
回调函数在解码事务 COMMIT PREPARED
时被调用。 gid
字段(它是 txn
参数的一部分)可在此回调函数中使用。
typedef void (*LogicalDecodeCommitPreparedCB) (struct LogicalDecodingContext *ctx, ReorderBufferTXN *txn, XLogRecPtr commit_lsn);
必需的 rollback_prepared_cb
回调函数在解码事务 ROLLBACK PREPARED
时被调用。 gid
字段(它是 txn
参数的一部分)可在此回调函数中使用。 prepare_end_lsn
和 prepare_time
参数可用于检查插件是否已收到此 PREPARE TRANSACTION
,在这种情况下,它可以应用回滚,否则,它可以跳过回滚操作。仅使用 gid
不够,因为下游节点可以具有相同标识符的已准备事务。
typedef void (*LogicalDecodeRollbackPreparedCB) (struct LogicalDecodingContext *ctx, ReorderBufferTXN *txn, XLogRecPtr prepare_end_lsn, TimestampTz prepare_time);
必需的 stream_start_cb
回调函数在打开来自正在进行的事务的流更改块时被调用。
typedef void (*LogicalDecodeStreamStartCB) (struct LogicalDecodingContext *ctx, ReorderBufferTXN *txn);
必需的 stream_stop_cb
回调函数在关闭来自正在进行的事务的流更改块时被调用。
typedef void (*LogicalDecodeStreamStopCB) (struct LogicalDecodingContext *ctx, ReorderBufferTXN *txn);
必需的 stream_abort_cb
回调函数用于中止先前流式传输的事务。
typedef void (*LogicalDecodeStreamAbortCB) (struct LogicalDecodingContext *ctx, ReorderBufferTXN *txn, XLogRecPtr abort_lsn);
stream_prepare_cb
回调函数用于将先前流式传输的事务作为两阶段提交的一部分进行准备。当输出插件同时支持大型正在进行的事务的流式传输和两阶段提交时,此回调函数是必需的。
typedef void (*LogicalDecodeStreamPrepareCB) (struct LogicalDecodingContext *ctx, ReorderBufferTXN *txn, XLogRecPtr prepare_lsn);
必需的 stream_commit_cb
回调函数用于提交先前流式传输的事务。
typedef void (*LogicalDecodeStreamCommitCB) (struct LogicalDecodingContext *ctx, ReorderBufferTXN *txn, XLogRecPtr commit_lsn);
必需的 stream_change_cb
回调函数在发送流更改块中的更改时被调用(由 stream_start_cb
和 stream_stop_cb
调用分隔)。不会显示实际更改,因为事务可能在以后的某个时间点中止,并且我们不会解码已中止事务的更改。
typedef void (*LogicalDecodeStreamChangeCB) (struct LogicalDecodingContext *ctx, ReorderBufferTXN *txn, Relation relation, ReorderBufferChange *change);
可选的 stream_message_cb
回调函数在发送流更改块中的通用消息时被调用(由 stream_start_cb
和 stream_stop_cb
调用分隔)。不会显示事务消息的内容,因为事务可能在以后的某个时间点中止,并且我们不会解码已中止事务的更改。
typedef void (*LogicalDecodeStreamMessageCB) (struct LogicalDecodingContext *ctx, ReorderBufferTXN *txn, XLogRecPtr message_lsn, bool transactional, const char *prefix, Size message_size, const char *message);
可选的 stream_truncate_cb
回调函数在流更改块中的 TRUNCATE
命令时被调用(由 stream_start_cb
和 stream_stop_cb
调用分隔)。
typedef void (*LogicalDecodeStreamTruncateCB) (struct LogicalDecodingContext *ctx, ReorderBufferTXN *txn, int nrelations, Relation relations[], ReorderBufferChange *change);
参数类似于 stream_change_cb
回调函数。但是,由于需要一起执行外键连接的表上的 TRUNCATE
操作,因此此回调函数接收关系数组而不是单个关系。有关详细信息,请参阅 TRUNCATE 语句的说明。
为了实际生成输出,输出插件可以在 begin_cb
、commit_cb
或 change_cb
回调函数内部将数据写入 ctx->out
中的 StringInfo
输出缓冲区。在写入输出缓冲区之前,必须调用 OutputPluginPrepareWrite(ctx, last_write)
,并在完成写入缓冲区后,必须调用 OutputPluginWrite(ctx, last_write)
以执行写入操作。 last_write
指示特定写入是否是回调函数的最后一次写入。
以下示例演示如何将数据输出到输出插件的使用者
OutputPluginPrepareWrite(ctx, true); appendStringInfo(ctx->out, "BEGIN %u", txn->xid); OutputPluginWrite(ctx, true);
如果您在文档中看到任何不正确的内容,与您对特定功能的体验不符或需要进一步澄清的内容,请使用 此表单 报告文档问题。