mirror of
https://github.com/jackc/pgx.git
synced 2025-05-31 11:42:24 +00:00
Start replication now wraps the sql and returns errors properly
This commit is contained in:
parent
2b096a7d08
commit
dad2c383af
@ -12,6 +12,7 @@ const (
|
|||||||
walData = 'w'
|
walData = 'w'
|
||||||
senderKeepalive = 'k'
|
senderKeepalive = 'k'
|
||||||
standbyStatusUpdate = 'r'
|
standbyStatusUpdate = 'r'
|
||||||
|
initialReplicationResponseTimeout = 5 * time.Second
|
||||||
)
|
)
|
||||||
|
|
||||||
var epochNano int64
|
var epochNano int64
|
||||||
@ -215,6 +216,14 @@ func (c *Conn) readReplicationMessage() (r *ReplicationMessage, err error) {
|
|||||||
replyNow := reader.readByte()
|
replyNow := reader.readByte()
|
||||||
h := &ServerHeartbeat{ServerWalEnd: uint64(serverWalEnd), ServerTime: uint64(serverTime), ReplyRequested: replyNow}
|
h := &ServerHeartbeat{ServerWalEnd: uint64(serverWalEnd), ServerTime: uint64(serverTime), ReplyRequested: replyNow}
|
||||||
return &ReplicationMessage{ServerHeartbeat: h}, nil
|
return &ReplicationMessage{ServerHeartbeat: h}, nil
|
||||||
|
default:
|
||||||
|
if c.shouldLog(LogLevelError) {
|
||||||
|
c.log(LogLevelError,"Unexpected data playload message type %v", t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
if c.shouldLog(LogLevelError) {
|
||||||
|
c.log(LogLevelError,"Unexpected replication message type %v", t)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
@ -227,10 +236,6 @@ func (c *Conn) readReplicationMessage() (r *ReplicationMessage, err error) {
|
|||||||
// updates. The caller also must send standby status updates in order to keep the connection
|
// updates. The caller also must send standby status updates in order to keep the connection
|
||||||
// alive and working.
|
// alive and working.
|
||||||
//
|
//
|
||||||
// There is also a condition (during startup) which can cause both the replication message
|
|
||||||
// to return as nil as well as the error, which is a normal part of the replication protocol
|
|
||||||
// startup. It's important the client correctly handle (ignore) this scenario.
|
|
||||||
//
|
|
||||||
// This returns pgx.ErrNotificationTimeout when there is no replication message by the specified
|
// This returns pgx.ErrNotificationTimeout when there is no replication message by the specified
|
||||||
// duration.
|
// duration.
|
||||||
func (c *Conn) WaitForReplicationMessage(timeout time.Duration) (r *ReplicationMessage, err error) {
|
func (c *Conn) WaitForReplicationMessage(timeout time.Duration) (r *ReplicationMessage, err error) {
|
||||||
@ -270,19 +275,43 @@ func (c *Conn) WaitForReplicationMessage(timeout time.Duration) (r *ReplicationM
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Start a replication connection, sending WAL data to the given replication
|
// Start a replication connection, sending WAL data to the given replication
|
||||||
// receiver. The sql string here should be a "START_REPLICATION" command, as
|
// receiver. This function wraps a START_REPLICATION command as documented
|
||||||
// per the postgresql docs here:
|
// here:
|
||||||
// https://www.postgresql.org/docs/9.5/static/protocol-replication.html
|
// https://www.postgresql.org/docs/9.5/static/protocol-replication.html
|
||||||
//
|
//
|
||||||
// A typical query would look like:
|
|
||||||
// START_REPLICATION SLOT t LOGICAL test_decoder 0/0
|
|
||||||
//
|
|
||||||
// Once started, the client needs to invoke WaitForReplicationMessage() in order
|
// Once started, the client needs to invoke WaitForReplicationMessage() in order
|
||||||
// to fetch the WAL and standby status. Also, it is the responsibility of the caller
|
// to fetch the WAL and standby status. Also, it is the responsibility of the caller
|
||||||
// to periodically send StandbyStatus messages to update the replication slot position.
|
// to periodically send StandbyStatus messages to update the replication slot position.
|
||||||
func (c *Conn) StartReplication(sql string, arguments ...interface{}) (err error) {
|
//
|
||||||
if err = c.sendQuery(sql, arguments...); err != nil {
|
// This function assumes that slotName has already been created. In order to omit the timeline argument
|
||||||
|
// pass a -1 for the timeline to get the server default behavior.
|
||||||
|
func (c *Conn) StartReplication(slotName string, startLsn uint64, timeline int64, pluginArguments ...string) (err error) {
|
||||||
|
var queryString string
|
||||||
|
if timeline >= 0 {
|
||||||
|
queryString = fmt.Sprintf("START_REPLICATION SLOT %s LOGICAL %s TIMELINE %d", slotName, FormatLSN(startLsn), timeline)
|
||||||
|
} else {
|
||||||
|
queryString = fmt.Sprintf("START_REPLICATION SLOT %s LOGICAL %s", slotName, FormatLSN(startLsn))
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, arg := range pluginArguments {
|
||||||
|
queryString += fmt.Sprintf(" %s", arg)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = c.sendQuery(queryString); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The first replication message that comes back here will be (in a success case)
|
||||||
|
// a empty CopyBoth that is (apparently) sent as the confirmation that the replication has
|
||||||
|
// started. This call will either return nil, nil or if it returns an error
|
||||||
|
// that indicates the start replication command failed
|
||||||
|
var r *ReplicationMessage
|
||||||
|
r, err = c.WaitForReplicationMessage(initialReplicationResponseTimeout)
|
||||||
|
if err != nil && r != nil {
|
||||||
|
if c.shouldLog(LogLevelError) {
|
||||||
|
c.log(LogLevelError, "Unxpected replication message %v", r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -7,12 +7,13 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"fmt"
|
||||||
)
|
)
|
||||||
|
|
||||||
// This function uses a postgresql 9.6 specific column
|
// This function uses a postgresql 9.6 specific column
|
||||||
func getConfirmedFlushLsnFor(t *testing.T, conn *pgx.Conn, slot string) string {
|
func getConfirmedFlushLsnFor(t *testing.T, conn *pgx.Conn, slot string) string {
|
||||||
// Fetch the restart LSN of the slot, to establish a starting point
|
// Fetch the restart LSN of the slot, to establish a starting point
|
||||||
rows, err := conn.Query("select confirmed_flush_lsn from pg_replication_slots where slot_name='pgx_test'")
|
rows, err := conn.Query(fmt.Sprintf("select confirmed_flush_lsn from pg_replication_slots where slot_name='%s'", slot))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("conn.Query failed: %v", err)
|
t.Fatalf("conn.Query failed: %v", err)
|
||||||
}
|
}
|
||||||
@ -66,7 +67,7 @@ func TestSimpleReplicationConnection(t *testing.T) {
|
|||||||
t.Fatalf("Failed to create table: %v", err)
|
t.Fatalf("Failed to create table: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = replicationConn.StartReplication("START_REPLICATION SLOT pgx_test LOGICAL 0/0")
|
err = replicationConn.StartReplication("pgx_test", 0, -1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to start replication: %v", err)
|
t.Fatalf("Failed to start replication: %v", err)
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user