/// <summary> /// Instructs the server to start the Logical Streaming Replication Protocol (pgoutput logical decoding plugin), /// starting at WAL location <paramref name="walLocation"/> or at the slot's consistent point if <paramref name="walLocation"/> /// isn't specified. /// The server can reply with an error, for example if the requested section of the WAL has already been recycled. /// </summary> /// <param name="connection">The <see cref="LogicalReplicationConnection"/> to use for starting replication</param> /// <param name="slot">The replication slot that will be updated as replication progresses so that the server /// knows which WAL segments are still needed by the standby. /// </param> /// <param name="options">The collection of options passed to the slot's logical decoding plugin.</param> /// <param name="cancellationToken">The token to monitor for stopping the replication.</param> /// <param name="walLocation">The WAL location to begin streaming at.</param> /// <returns>A <see cref="Task{T}"/> representing an <see cref="IAsyncEnumerable{T}"/> that /// can be used to stream WAL entries in form of <see cref="PgOutputReplicationMessage"/> instances.</returns> public static IAsyncEnumerable <PgOutputReplicationMessage> StartReplication( this LogicalReplicationConnection connection, PgOutputReplicationSlot slot, PgOutputReplicationOptions options, CancellationToken cancellationToken, NpgsqlLogSequenceNumber?walLocation = null) => new PgOutputAsyncEnumerable(connection, slot, options, cancellationToken, walLocation);
/// <summary> /// Instructs the server to start streaming the WAL for logical replication using the test_decoding logical decoding plugin, /// starting at WAL location <paramref name="walLocation"/> or at the slot's consistent point if <paramref name="walLocation"/> /// isn't specified. /// The server can reply with an error, for example if the requested section of the WAL has already been recycled. /// </summary> /// <param name="connection">The <see cref="LogicalReplicationConnection"/> to use for starting replication</param> /// <param name="slot">The replication slot that will be updated as replication progresses so that the server /// knows which WAL segments are still needed by the standby. /// </param> /// <param name="cancellationToken">The token to monitor for stopping the replication.</param> /// <param name="options">The collection of options passed to the slot's logical decoding plugin.</param> /// <param name="walLocation">The WAL location to begin streaming at.</param> /// <returns>A <see cref="Task{T}"/> representing an <see cref="IAsyncEnumerable{T}"/> that /// can be used to stream WAL entries in form of <see cref="TestDecodingData"/> instances.</returns> public static IAsyncEnumerable <TestDecodingData> StartReplication( this LogicalReplicationConnection connection, TestDecodingReplicationSlot slot, CancellationToken cancellationToken, TestDecodingOptions?options = default, NpgsqlLogSequenceNumber?walLocation = null) => new TestDecodingAsyncEnumerable(connection, slot, options ?? new TestDecodingOptions(), cancellationToken, walLocation);
internal TestDecodingAsyncEnumerable( LogicalReplicationConnection connection, TestDecodingReplicationSlot slot, TestDecodingOptions options, CancellationToken cancellationToken, NpgsqlLogSequenceNumber?walLocation = null) { _connection = connection; _slot = slot; _options = options; _baseCancellationToken = cancellationToken; _walLocation = walLocation; }
internal PgOutputAsyncEnumerable( LogicalReplicationConnection connection, PgOutputReplicationSlot slot, PgOutputReplicationOptions options, CancellationToken cancellationToken, NpgsqlLogSequenceNumber?walLocation = null) { _connection = connection; _slot = slot; _options = options; _baseCancellationToken = cancellationToken; _walLocation = walLocation; }
/// <summary> /// Creates a <see cref="LogicalReplicationSlot"/> class that wraps a replication slot using the /// "pgoutput" logical decoding plugin and can be used to start streaming replication via the logical /// streaming replication protocol. /// </summary> /// <remarks> /// See <a href="https://www.postgresql.org/docs/current/protocol-logical-replication.html">https://www.postgresql.org/docs/current/protocol-logical-replication.html</a> /// and <a href="https://www.postgresql.org/docs/current/protocol-logicalrep-message-formats.html">https://www.postgresql.org/docs/current/protocol-logicalrep-message-formats.html</a> /// for more information. /// </remarks> /// <param name="connection">The <see cref="LogicalReplicationConnection"/> to use for creating the replication slot</param> /// <param name="slotName">The name of the slot to create. Must be a valid replication slot name (see /// <a href="https://www.postgresql.org/docs/current/warm-standby.html#STREAMING-REPLICATION-SLOTS-MANIPULATION">https://www.postgresql.org/docs/current/warm-standby.html#STREAMING-REPLICATION-SLOTS-MANIPULATION</a>). /// </param> /// <param name="temporarySlot"> /// <see langword="true"/> if this replication slot shall be temporary one; otherwise <see langword="false"/>. /// Temporary slots are not saved to disk and are automatically dropped on error or when the session has finished. /// </param> /// <param name="slotSnapshotInitMode"> /// A <see cref="LogicalSlotSnapshotInitMode"/> to specify what to do with the snapshot created during logical slot /// initialization. <see cref="LogicalSlotSnapshotInitMode.Export"/>, which is also the default, will export the /// snapshot for use in other sessions. This option can't be used inside a transaction. /// <see cref="LogicalSlotSnapshotInitMode.Use"/> will use the snapshot for the current transaction executing the /// command. This option must be used in a transaction, and <see cref="LogicalSlotSnapshotInitMode.Use"/> must be the /// first command run in that transaction. Finally, <see cref="LogicalSlotSnapshotInitMode.NoExport"/> will just use /// the snapshot for logical decoding as normal but won't do anything else with it. /// </param> /// <param name="twoPhase"> /// If <see langword="true"/>, this logical replication slot supports decoding of two-phase transactions. With this option, /// two-phase commands like PREPARE TRANSACTION, COMMIT PREPARED and ROLLBACK PREPARED are decoded and transmitted. /// The transaction will be decoded and transmitted at PREPARE TRANSACTION time. The default is <see langword="false"/>. /// </param> /// <param name="cancellationToken"> /// An optional token to cancel the asynchronous operation. The default value is <see cref="CancellationToken.None"/>. /// </param> /// <returns> /// A <see cref="LogicalReplicationSlot"/> that wraps the newly-created replication slot. /// </returns> public static async Task <PgOutputReplicationSlot> CreatePgOutputReplicationSlot( this LogicalReplicationConnection connection, string slotName, bool temporarySlot = false, LogicalSlotSnapshotInitMode?slotSnapshotInitMode = null, bool twoPhase = false, CancellationToken cancellationToken = default) { // We don't enter NoSynchronizationContextScope here since we (have to) do it in CreateLogicalReplicationSlot, because // otherwise it wouldn't be set for external plugins. var options = await connection.CreateLogicalReplicationSlot( slotName, "pgoutput", temporarySlot, slotSnapshotInitMode, twoPhase, cancellationToken).ConfigureAwait(false); return(new PgOutputReplicationSlot(options)); }
/// <summary> /// This API is for internal use and for implementing logical replication plugins. /// It is not meant to be consumed in common Npgsql usage scenarios. /// </summary> /// <remarks> /// Creates a new replication slot and returns information about the newly-created slot. /// </remarks> /// <param name="connection">The <see cref="LogicalReplicationConnection"/> to use for creating the /// replication slot</param> /// <param name="slotName">The name of the slot to create. Must be a valid replication slot name (see /// <a href="https://www.postgresql.org/docs/current/warm-standby.html#STREAMING-REPLICATION-SLOTS-MANIPULATION"> /// https://www.postgresql.org/docs/current/warm-standby.html#STREAMING-REPLICATION-SLOTS-MANIPULATION</a>). /// </param> /// <param name="outputPlugin">The name of the output plugin used for logical decoding (see /// <a href="https://www.postgresql.org/docs/current/logicaldecoding-output-plugin.html"> /// https://www.postgresql.org/docs/current/logicaldecoding-output-plugin.html</a>). /// </param> /// <param name="isTemporary"><see langword="true"/> if this replication slot shall be temporary one; otherwise /// <see langword="false"/>. Temporary slots are not saved to disk and are automatically dropped on error or /// when the session has finished.</param> /// <param name="slotSnapshotInitMode">A <see cref="LogicalSlotSnapshotInitMode"/> to specify what to do with the /// snapshot created during logical slot initialization. <see cref="LogicalSlotSnapshotInitMode.Export"/>, which is /// also the default, will export the snapshot for use in other sessions. This option can't be used inside a /// transaction. <see cref="LogicalSlotSnapshotInitMode.Use"/> will use the snapshot for the current transaction /// executing the command. This option must be used in a transaction, and <see cref="LogicalSlotSnapshotInitMode.Use"/> /// must be the first command run in that transaction. Finally, <see cref="LogicalSlotSnapshotInitMode.NoExport"/> will /// just use the snapshot for logical decoding as normal but won't do anything else with it.</param> /// <param name="twoPhase"> /// If <see langword="true"/>, this logical replication slot supports decoding of two-phase transactions. With this option, /// two-phase commands like PREPARE TRANSACTION, COMMIT PREPARED and ROLLBACK PREPARED are decoded and transmitted. /// The transaction will be decoded and transmitted at PREPARE TRANSACTION time. The default is <see langword="false"/>. /// </param> /// <param name="cancellationToken"> /// An optional token to cancel the asynchronous operation. The default value is <see cref="CancellationToken.None"/>. /// </param> /// <returns>A <see cref="Task{T}"/> representing a <see cref="ReplicationSlotOptions"/> class that /// can be used to initialize instances of <see cref="ReplicationSlot"/> subclasses.</returns> public static Task <ReplicationSlotOptions> CreateLogicalReplicationSlot( this LogicalReplicationConnection connection, string slotName, string outputPlugin, bool isTemporary = false, LogicalSlotSnapshotInitMode?slotSnapshotInitMode = null, bool twoPhase = false, CancellationToken cancellationToken = default) { connection.CheckDisposed(); using var _ = NoSynchronizationContextScope.Enter(); return(CreateLogicalReplicationSlotCore()); Task <ReplicationSlotOptions> CreateLogicalReplicationSlotCore() { if (slotName is null) { throw new ArgumentNullException(nameof(slotName)); } if (outputPlugin is null) { throw new ArgumentNullException(nameof(outputPlugin)); } cancellationToken.ThrowIfCancellationRequested(); var builder = new StringBuilder("CREATE_REPLICATION_SLOT ").Append(slotName); if (isTemporary) { builder.Append(" TEMPORARY"); } builder.Append(" LOGICAL ").Append(outputPlugin); builder.Append(slotSnapshotInitMode switch { // EXPORT_SNAPSHOT is the default since it has been introduced. // We don't set it unless it is explicitly requested so that older backends can digest the query too. null => string.Empty, LogicalSlotSnapshotInitMode.Export => " EXPORT_SNAPSHOT", LogicalSlotSnapshotInitMode.Use => " USE_SNAPSHOT", LogicalSlotSnapshotInitMode.NoExport => " NOEXPORT_SNAPSHOT", _ => throw new ArgumentOutOfRangeException(nameof(slotSnapshotInitMode), slotSnapshotInitMode, $"Unexpected value {slotSnapshotInitMode} for argument {nameof(slotSnapshotInitMode)}.") });
public PostgresReplicationSubscriber(PostgresConfiguration postgresConfiguration) { _config = postgresConfiguration ?? throw new ArgumentNullException(nameof(postgresConfiguration)); if (string.IsNullOrWhiteSpace(_config.ConnectionString)) { throw new ArgumentNullException(nameof(_config.ConnectionString)); } if (string.IsNullOrWhiteSpace(_config.PublicationName)) { throw new ArgumentNullException(nameof(_config.PublicationName)); } if (string.IsNullOrWhiteSpace(_config.ReplicationSlotName)) { throw new ArgumentNullException(nameof(_config.ReplicationSlotName)); } _connection = new LogicalReplicationConnection(_config.ConnectionString); _ = StartAsync(); }