pg-replication-protocol 0.0.2 → 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b47169a6a877e1d68022faad3c47e2b49fda70e9e4925f931e4ed211055cc717
4
- data.tar.gz: 68a845688a3a7ad74b5b858319d72a7f85c5d484923a890c34d9069cc05a7d02
3
+ metadata.gz: 7eb14ee3cd0484e8160ed1bc27a841178bb5bb19a35304d46992c8d7accad5be
4
+ data.tar.gz: 51e43b8bcba5ee2db08b08962418ae0ff6ab6657076ca586984c279fe6603f6e
5
5
  SHA512:
6
- metadata.gz: 7ba1e8821d066d3f82a5ba37a8b8d73066baea146e34ee022996a391568e17c7d08e298c3061f7ebbf22d37f7099c54f01521845ff5f4b727249e7c4fb84456b
7
- data.tar.gz: 3df84fdf551747d54ef62160ee3423068378d0fafd3c7015f5f263072e2139825f074b2f4f95614d434920c842af453a0bff65b837f320f0fdc45c6547a706c7
6
+ metadata.gz: 0e86a9fcaa6a3497d6b211004551219fd9080adbca09977de836c53766780088fcb6c42966e461b8517026ef310ab67134c52d20fef396adf1807c06bf004ffe
7
+ data.tar.gz: 7e21681ae16444dc1a43450074a574ea81edfd554ce6362c13befad60f651c637c887416fd58befdf1bb8e5f604efd946a6e253486c584b562bb790a1e644864
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- pg-replication-protocol (0.0.2)
4
+ pg-replication-protocol (0.0.3)
5
5
  pg (~> 1.0)
6
6
 
7
7
  GEM
@@ -34,6 +34,7 @@ GEM
34
34
  testcontainers-core (~> 0.1)
35
35
 
36
36
  PLATFORMS
37
+ arm64-darwin-23
37
38
  x86_64-linux
38
39
 
39
40
  DEPENDENCIES
data/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # PG::Replication
2
2
 
3
+ Adds support to `pg` for listening to replication slots
4
+
3
5
  ## Usage
4
6
 
5
7
  Add to your Gemfile:
@@ -14,7 +16,7 @@ gem "pg-replication-protocol", require: "pg/replication"
14
16
  require "pg"
15
17
  require "pg/replication"
16
18
 
17
- # Important to create a connection with the `replication: "database"` flag
19
+ # It is important to create a connection with the `replication: "database"` option
18
20
  connection = PG.connect(..., replication: "database")
19
21
 
20
22
  # Create a publication and a slot (in a real use case the slot will not be temporary)
@@ -25,25 +27,26 @@ connection.query('CREATE_REPLICATION_SLOT some_slot TEMPORARY LOGICAL "pgoutput"
25
27
  tables = {}
26
28
 
27
29
  # Start a pgoutput plugin replication slot message stream
28
- connection.start_pgoutput_replication_slot(slot, publications).each do |msg|
30
+ # The `messages: true` option is required to be able to decode `PG::Replication::PGOutput::Message`
31
+ connection.start_pgoutput_replication_slot(slot, publications, messages: true).each do |msg|
29
32
  case msg
30
- in PG::Replication::PGOutput::Relation(oid:, name:, columns:)
31
- # We receive this message on the first row of each table
33
+ in PG::Replication::Protocol::XLogData(data: PG::Replication::PGOutput::Relation(oid:, name:, columns:))
34
+ # This message is received on the first row of each table, or when there are schema changes
32
35
  tables[oid] = { name:, columns: }
33
36
 
34
- in PG::Replication::PGOutput::Begin
37
+ in PG::Replication::Protocol::XLogData(data: PG::Replication::PGOutput::Begin)
35
38
  puts "Transaction start"
36
39
 
37
- in PG::Replication::PGOutput::Commit
40
+ in PG::Replication::Protocol::XLogData(data: PG::Replication::PGOutput::Commit)
38
41
  puts "Transaction end"
39
42
 
40
- in PG::Replication::PGOutput::Insert(oid:, new:)
43
+ in PG::Replication::Protocol::XLogData(data: PG::Replication::PGOutput::Insert(oid:, new:))
41
44
  puts "Insert #{tables[oid][:name]}"
42
45
  new.zip(tables[oid][:columns]).each do |tuple, col|
43
46
  puts "#{col.name}: #{tuple.data || "NULL"}"
44
47
  end
45
48
 
46
- in PG::Replication::PGOutput::Update(oid:, new:, old:)
49
+ in PG::Replication::Protocol::XLogData(data: PG::Replication::PGOutput::Update(oid:, new:, old:))
47
50
  puts "Update #{tables[oid][:name]}"
48
51
  if !old.empty? && new != old
49
52
  new.zip(old, tables[oid][:columns]).each do |new, old, col|
@@ -53,9 +56,12 @@ connection.start_pgoutput_replication_slot(slot, publications).each do |msg|
53
56
  end
54
57
  end
55
58
 
56
- in PG::Replication::PGOutput::Delete(oid:)
59
+ in PG::Replication::Protocol::XLogData(data: PG::Replication::PGOutput::Delete(oid:))
57
60
  puts "Delete #{tables[oid][:name]}"
58
61
 
62
+ in PG::Replication::Protocol::XLogData(data: PG::Replication::PGOutput::Message(prefix:, content:))
63
+ puts "Message #{prefix}: #{content}"
64
+
59
65
  else
60
66
  nil
61
67
  end
@@ -11,7 +11,7 @@ module PG
11
11
  Type = Data.define(:oid, :namespace, :name)
12
12
  Insert = Data.define(:oid, :new)
13
13
  Update = Data.define(:oid, :key, :old, :new)
14
- Delete = Data.define(:oid, :old)
14
+ Delete = Data.define(:oid, :key, :old)
15
15
  Truncate = Data.define(:oid)
16
16
  Tuple = Data.define(:type, :data)
17
17
  Column = Data.define(:flags, :name, :oid, :modifier) do
@@ -115,14 +115,23 @@ module PG
115
115
  )
116
116
 
117
117
  in "D"
118
+ oid = buffer.read_int32
119
+ key = []
120
+ old = []
121
+
122
+ until buffer.eof?
123
+ case buffer.read_char
124
+ when "K"
125
+ key = PGOutput.read_tuples(buffer)
126
+ when "O"
127
+ old = PGOutput.read_tuples(buffer)
128
+ end
129
+ end
130
+
118
131
  PGOutput::Delete.new(
119
- oid: buffer.read_int32,
120
- old: case buffer.read_char
121
- when "N", "K"
122
- PGOutput.read_tuples(buffer)
123
- else
124
- []
125
- end,
132
+ oid:,
133
+ key:,
134
+ old:,
126
135
  )
127
136
 
128
137
  in "T"
@@ -2,6 +2,6 @@
2
2
 
3
3
  module PG
4
4
  module Replication
5
- VERSION = "0.0.2"
5
+ VERSION = "0.0.4"
6
6
  end
7
7
  end
@@ -8,10 +8,8 @@ require_relative "replication/protocol"
8
8
 
9
9
  module PG
10
10
  module Replication
11
- def start_replication_slot(slot, logical: true, location: "0/0", **params)
12
- keep_alive_secs = query(<<~SQL).getvalue(0, 0)&.to_i || 10
13
- SELECT setting FROM pg_catalog.pg_settings WHERE name = 'wal_receiver_status_interval'
14
- SQL
11
+ def start_replication_slot(slot, logical: true, auto_keep_alive: true, location: "0/0", **params)
12
+ keep_alive_secs = wal_receiver_status_interval
15
13
 
16
14
  start_query = "START_REPLICATION SLOT #{slot} #{logical ? "LOGICAL" : "PHYSICAL"} #{location}"
17
15
  unless params.empty?
@@ -23,14 +21,14 @@ module PG
23
21
  end
24
22
  query(start_query)
25
23
 
26
- last_processed_lsn = 0
24
+ @last_confirmed_lsn = 0
27
25
  last_keep_alive = Time.now
28
26
 
29
27
  Enumerator
30
28
  .new do |y|
31
29
  loop do
32
- if Time.now - last_keep_alive > keep_alive_secs
33
- standby_status_update(write_lsn: last_processed_lsn)
30
+ if auto_keep_alive && Time.now - last_keep_alive > keep_alive_secs
31
+ standby_status_update(write_lsn: @last_confirmed_lsn)
34
32
  last_keep_alive = Time.now
35
33
  end
36
34
 
@@ -47,37 +45,40 @@ module PG
47
45
  next
48
46
 
49
47
  in data
50
- buffer = Buffer.new(StringIO.new(data))
51
- y << Protocol.read_message(buffer)
48
+ case (msg = Protocol.read_message(Buffer.new(StringIO.new(data))))
49
+ in Protocol::XLogData(lsn:, data:) if auto_keep_alive
50
+ standby_status_update(write_lsn: @last_confirmed_lsn)
51
+ last_keep_alive = Time.now
52
+ y << msg
53
+ @last_confirmed_lsn = lsn
54
+
55
+ in Protocol::PrimaryKeepalive(server_time:, asap: true) if auto_keep_alive
56
+ standby_status_update(write_lsn: @last_confirmed_lsn)
57
+ last_keep_alive = Time.now
58
+ y << msg
59
+
60
+ else
61
+ y << msg
62
+ end
52
63
  end
53
64
  end
54
65
  end
55
66
  .lazy
56
- .filter_map do |msg|
57
- case msg
58
- in Protocol::XLogData(lsn:, data:)
59
- last_processed_lsn = lsn
60
- standby_status_update(write_lsn: last_processed_lsn)
61
- last_keep_alive = Time.now
62
- data
63
-
64
- in Protocol::PrimaryKeepalive(server_time:, asap: true)
65
- standby_status_update(write_lsn: last_processed_lsn)
66
- last_keep_alive = Time.now
67
- next
68
-
69
- else
70
- next
71
- end
72
- end
73
67
  end
74
68
 
75
69
  def start_pgoutput_replication_slot(slot, publication_names, **kwargs)
76
70
  publication_names = publication_names.join(",")
77
71
 
78
72
  start_replication_slot(slot, **kwargs.merge(proto_version: "1", publication_names:))
79
- .map { |data| data.force_encoding(internal_encoding) }
80
- .map { |data| PGOutput.read_message(Buffer.from_string(data)) }
73
+ .map do |msg|
74
+ case msg
75
+ in Protocol::XLogData(data:, lsn:)
76
+ data = data.force_encoding(internal_encoding)
77
+ msg.with(data: PGOutput.read_message(Buffer.from_string(data)))
78
+ else
79
+ msg
80
+ end
81
+ end
81
82
  end
82
83
 
83
84
  def standby_status_update(
@@ -98,6 +99,17 @@ module PG
98
99
 
99
100
  put_copy_data(msg)
100
101
  flush
102
+ @last_confirmed_lsn = [@last_confirmed_lsn, write_lsn].compact.max
103
+ end
104
+
105
+ def last_confirmed_lsn
106
+ @last_confirmed_lsn
107
+ end
108
+
109
+ def wal_receiver_status_interval
110
+ query(<<~SQL).getvalue(0, 0)&.to_i || 10
111
+ SELECT setting FROM pg_catalog.pg_settings WHERE name = 'wal_receiver_status_interval'
112
+ SQL
101
113
  end
102
114
  end
103
115
 
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pg-replication-protocol
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rodrigo Navarro
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2024-11-17 00:00:00.000000000 Z
10
+ date: 2025-06-04 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: pg
@@ -24,7 +23,6 @@ dependencies:
24
23
  - - "~>"
25
24
  - !ruby/object:Gem::Version
26
25
  version: '1.0'
27
- description:
28
26
  email:
29
27
  - rnavarro@rnavarro.com.br
30
28
  executables: []
@@ -43,13 +41,11 @@ files:
43
41
  - lib/pg/replication/protocol.rb
44
42
  - lib/pg/replication/version.rb
45
43
  - pg-replication-protocol.gemspec
46
- homepage:
47
44
  licenses:
48
45
  - MIT
49
46
  metadata:
50
47
  homepage_uri: https://github.com/reu/pg-replication-protocol-rb
51
48
  source_code_uri: https://github.com/reu/pg-replication-protocol-rb
52
- post_install_message:
53
49
  rdoc_options: []
54
50
  require_paths:
55
51
  - lib
@@ -64,8 +60,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
64
60
  - !ruby/object:Gem::Version
65
61
  version: '0'
66
62
  requirements: []
67
- rubygems_version: 3.4.1
68
- signing_key:
63
+ rubygems_version: 3.6.2
69
64
  specification_version: 4
70
65
  summary: Postgres replication protocol
71
66
  test_files: []