pg-replication-protocol 0.0.6 → 0.0.8

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: 72bf40ef6c6c482636553033a8747eec8239f4b10602486376806fd6bff59a91
4
- data.tar.gz: 4df39c13ee3b16f7e77114c7ef061e75b33b1f112e6341ce4aeb28898096d703
3
+ metadata.gz: f00c769aee5ae5e1d41063e827f2d5fcaa19991aed6d4676f414ce5f6d6326ef
4
+ data.tar.gz: a99c6f062f9c33109a409b58ef465833fb1d0b3fd8499a91461db9ea3e2586af
5
5
  SHA512:
6
- metadata.gz: 6b3a4cd7627a4d8229f88ba7d086a95a6fac1ae76fe7d856970ce1dd745272696046f18e55bb551345fa8b431691ac351d3cffcdc809f07ecba5fa78d6f4b67a
7
- data.tar.gz: da57dba3124e0c3e8c5832d570f471b2a15ee68c2afc0fd71e0f5c5410ec61585f99dcfb1499059106cf1cb06073552335897035388b45aa884dfc0d757155e0
6
+ metadata.gz: a97e4828c3c0d41607c021fe3b3b87b9c2c2dc01ae70131a984ce4513a46c2386e8d86ce5fc3029f395cc16a9f410c1fbe4c2dff8f77c833bc40f4d75ff8e509
7
+ data.tar.gz: def00e2668b59de09ed0eb8a2fa8efb5974c25c36ea82fcf64b83115afb7519b38010019310267dc4efddeca76df56e6861696abd285d2b2dfab8c00a309e21b
data/Gemfile CHANGED
@@ -5,6 +5,8 @@ source "https://rubygems.org"
5
5
  gemspec
6
6
 
7
7
  gem "rake", "~> 13.0"
8
+ gem "base64"
9
+ gem "rbs"
8
10
 
9
11
  group :test do
10
12
  gem "rspec", "~> 3.0"
data/Gemfile.lock CHANGED
@@ -1,20 +1,24 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- pg-replication-protocol (0.0.5)
4
+ pg-replication-protocol (0.0.7)
5
5
  pg (~> 1.0)
6
6
 
7
7
  GEM
8
8
  remote: https://rubygems.org/
9
9
  specs:
10
+ base64 (0.3.0)
10
11
  diff-lcs (1.5.1)
11
12
  docker-api (2.4.0)
12
13
  excon (>= 0.64.0)
13
14
  multi_json
14
15
  excon (1.2.1)
16
+ logger (1.7.0)
15
17
  multi_json (1.15.0)
16
18
  pg (1.5.9)
17
19
  rake (13.2.1)
20
+ rbs (3.9.4)
21
+ logger
18
22
  rspec (3.13.0)
19
23
  rspec-core (~> 3.13.0)
20
24
  rspec-expectations (~> 3.13.0)
@@ -38,8 +42,10 @@ PLATFORMS
38
42
  x86_64-linux
39
43
 
40
44
  DEPENDENCIES
45
+ base64
41
46
  pg-replication-protocol!
42
47
  rake (~> 13.0)
48
+ rbs
43
49
  rspec (~> 3.0)
44
50
  testcontainers-postgres
45
51
 
data/Rakefile CHANGED
@@ -2,3 +2,7 @@
2
2
 
3
3
  require "bundler/gem_tasks"
4
4
  task default: %i[]
5
+
6
+ task :test do
7
+ sh "bundle exec rbs test --target PG::Replication::* bundle exec rspec"
8
+ end
@@ -1,21 +1,49 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "delegate"
4
- require "stringio"
5
-
6
3
  module PG
7
4
  module Replication
8
- class Buffer < SimpleDelegator
5
+ POSTGRES_EPOCH = Time.utc(2000, 1, 1).freeze
6
+ POSTGRES_EPOCH_USECS = (POSTGRES_EPOCH.to_r * 1_000_000).to_i
7
+
8
+ class Buffer
9
+ def initialize(data)
10
+ @data = data
11
+ @pos = 0
12
+ end
13
+
9
14
  def self.from_string(str)
10
- new(StringIO.new(str))
15
+ new(str.b)
16
+ end
17
+
18
+ def eof?
19
+ @pos >= @data.bytesize
20
+ end
21
+
22
+ def read(n = nil)
23
+ return nil if @pos >= @data.bytesize
24
+ if n.nil?
25
+ result = @data.byteslice(@pos..-1)
26
+ @pos = @data.bytesize
27
+ else
28
+ result = @data.byteslice(@pos, n)
29
+ @pos += result.bytesize
30
+ end
31
+ result
32
+ end
33
+
34
+ def readbyte
35
+ raise EOFError if @pos >= @data.bytesize
36
+ byte = @data.getbyte(@pos)
37
+ @pos += 1
38
+ byte
11
39
  end
12
40
 
13
41
  def read_char
14
- read_int8.chr
42
+ readbyte.chr
15
43
  end
16
44
 
17
45
  def read_bool
18
- read_int8 == 1
46
+ readbyte == 1
19
47
  end
20
48
 
21
49
  def read_int8
@@ -23,40 +51,38 @@ module PG
23
51
  end
24
52
 
25
53
  def read_int16
26
- read_bytes(2).unpack("n").first
54
+ read_bytes(2).unpack1("n")
27
55
  end
28
56
 
29
57
  def read_int32
30
- read_bytes(4).unpack("N").first
58
+ read_bytes(4).unpack1("N")
31
59
  end
32
60
 
33
61
  def read_int64
34
- read_bytes(8).unpack("Q>").first
62
+ read_bytes(8).unpack1("Q>")
35
63
  end
36
64
 
37
65
  def read_timestamp
38
- usecs = Time.new(2_000, 1, 1, 0, 0, 0, 0).to_i * 10**6 + read_int64
39
- Time.at(usecs / 10**6, usecs % 10**6, :microsecond)
66
+ usecs = POSTGRES_EPOCH_USECS + read_int64
67
+ Time.at(usecs / 1_000_000, usecs % 1_000_000, :microsecond, in: "UTC")
40
68
  end
41
69
 
42
70
  def read_cstring
43
- str = String.new
44
- loop do
45
- case read_char
46
- in "\0"
47
- return str
48
- in chr
49
- str << chr
50
- end
51
- end
71
+ null_pos = @data.index("\0", @pos)
72
+ raise EOFError, "Unterminated C-string" if null_pos.nil?
73
+
74
+ str = @data.byteslice(@pos, null_pos - @pos)
75
+ @pos = null_pos + 1 # Skip past null terminator
76
+ str
52
77
  end
53
78
 
54
79
  private
55
80
 
56
81
  def read_bytes(n)
57
- bytes = read(n)
58
- raise EOFError if bytes.nil? || bytes.size < n
59
- bytes
82
+ raise EOFError if @pos + n > @data.bytesize
83
+ result = @data.byteslice(@pos, n)
84
+ @pos += n
85
+ result
60
86
  end
61
87
  end
62
88
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module PG
4
4
  module Replication
5
- VERSION = "0.0.6"
5
+ VERSION = "0.0.8"
6
6
  end
7
7
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "pg"
4
+ require "thread"
4
5
  require_relative "replication/version"
5
6
  require_relative "replication/buffer"
6
7
  require_relative "replication/pg_output"
@@ -8,7 +9,12 @@ require_relative "replication/protocol"
8
9
 
9
10
  module PG
10
11
  module Replication
11
- def start_replication_slot(slot, logical: true, auto_keep_alive: true, location: "0/0", **params)
12
+ DEFAULT_QUEUE_SIZE = 10_000
13
+
14
+ StreamEnd = Object.new.freeze
15
+ StreamError = Data.define(:exception)
16
+
17
+ def start_replication_slot(slot, logical: true, auto_keep_alive: true, location: "0/0", queue_size: DEFAULT_QUEUE_SIZE, **params)
12
18
  keep_alive_secs = wal_receiver_status_interval
13
19
  @last_confirmed_lsn = confirmed_slot_lsn(slot) || 0
14
20
 
@@ -22,51 +28,11 @@ module PG
22
28
  end
23
29
  query(start_query)
24
30
 
25
- last_keep_alive = Time.now
26
-
27
- Enumerator
28
- .new do |y|
29
- loop do
30
- if auto_keep_alive && Time.now - last_keep_alive > keep_alive_secs
31
- standby_status_update(write_lsn: @last_confirmed_lsn)
32
- last_keep_alive = Time.now
33
- end
34
-
35
- consume_input
36
- next if is_busy
37
-
38
- case get_copy_data(async: true)
39
- in nil
40
- get_last_result
41
- break
42
-
43
- in false
44
- IO.select([socket_io], nil, nil, keep_alive_secs)
45
- next
46
-
47
- in data
48
- case (msg = Protocol.read_message(Buffer.new(StringIO.new(data))))
49
- in Protocol::XLogData(lsn:, data:) if auto_keep_alive
50
- y << msg
51
- standby_status_update(write_lsn: lsn) if lsn > 0
52
- last_keep_alive = Time.now
53
-
54
- in Protocol::PrimaryKeepalive(current_lsn:, server_time:, asap: true) if auto_keep_alive
55
- standby_status_update(write_lsn: current_lsn)
56
- last_keep_alive = Time.now
57
- y << msg
58
-
59
- in Protocol::PrimaryKeepalive(current_lsn:)
60
- y << msg
61
- @last_confirmed_lsn = [@last_confirmed_lsn, current_lsn].compact.max
62
-
63
- else
64
- y << msg
65
- end
66
- end
67
- end
68
- end
69
- .lazy
31
+ if auto_keep_alive
32
+ start_threaded_replication(keep_alive_secs, queue_size)
33
+ else
34
+ start_sync_replication
35
+ end
70
36
  end
71
37
 
72
38
  def start_pgoutput_replication_slot(slot, publication_names, **kwargs)
@@ -96,17 +62,25 @@ module PG
96
62
  write_lsn,
97
63
  flush_lsn,
98
64
  apply_lsn,
99
- (timestamp - Time.new(2_000, 1, 1, 0, 0, 0, 0)) * 10**6,
65
+ ((timestamp.to_r - POSTGRES_EPOCH.to_r) * 1_000_000).to_i,
100
66
  reply ? 1 : 0,
101
67
  ].pack("CQ>Q>Q>Q>C")
102
68
 
103
- put_copy_data(msg)
104
- flush
105
- @last_confirmed_lsn = [@last_confirmed_lsn, write_lsn].compact.max
69
+ status_update_mutex.synchronize do
70
+ put_copy_data(msg)
71
+ flush
72
+ @last_confirmed_lsn = [@last_confirmed_lsn, write_lsn].compact.max
73
+ end
106
74
  end
107
75
 
108
76
  def last_confirmed_lsn
109
- @last_confirmed_lsn
77
+ status_update_mutex.synchronize { @last_confirmed_lsn }
78
+ end
79
+
80
+ def stop_replication
81
+ status_update_mutex.synchronize do
82
+ put_copy_end
83
+ end
110
84
  end
111
85
 
112
86
  def wal_receiver_status_interval
@@ -124,6 +98,120 @@ module PG
124
98
  rescue StandardError
125
99
  nil
126
100
  end
101
+
102
+ private
103
+
104
+ def status_update_mutex
105
+ @status_update_mutex ||= Mutex.new
106
+ end
107
+
108
+ def start_threaded_replication(keep_alive_secs, queue_size)
109
+ conn = self
110
+ queue = SizedQueue.new(queue_size)
111
+ last_keepalive = Time.now
112
+
113
+ thread = Thread.new do
114
+ loop do
115
+ break if queue.closed?
116
+
117
+ conn.consume_input
118
+ next if conn.is_busy
119
+
120
+ case (data = conn.get_copy_data(async: true))
121
+ when nil
122
+ queue.push(StreamEnd, true) rescue nil
123
+ conn.get_last_result
124
+ break
125
+
126
+ when false
127
+ timeout = [keep_alive_secs - (Time.now - last_keepalive), 0.1].max
128
+ IO.select([conn.socket_io], nil, nil, timeout)
129
+
130
+ if Time.now - last_keepalive >= keep_alive_secs
131
+ conn.standby_status_update(write_lsn: conn.last_confirmed_lsn)
132
+ last_keepalive = Time.now
133
+ end
134
+
135
+ else
136
+ msg = Protocol.read_message(Buffer.from_string(data))
137
+
138
+ if msg.is_a?(Protocol::PrimaryKeepalive) && msg.asap
139
+ conn.standby_status_update(write_lsn: msg.current_lsn)
140
+ last_keepalive = Time.now
141
+ end
142
+
143
+ # Non-blocking push with keepalive retry loop
144
+ loop do
145
+ break if queue.closed?
146
+ begin
147
+ queue.push(msg, true)
148
+ break
149
+ rescue ThreadError
150
+ if Time.now - last_keepalive >= keep_alive_secs
151
+ conn.standby_status_update(write_lsn: conn.last_confirmed_lsn)
152
+ last_keepalive = Time.now
153
+ end
154
+ sleep(0.05)
155
+ end
156
+ end
157
+ end
158
+ end
159
+ rescue ClosedQueueError
160
+ # Clean exit
161
+ rescue => e
162
+ queue.push(StreamError.new(e), true) rescue nil
163
+ end
164
+
165
+ Enumerator.new do |y|
166
+ loop do
167
+ msg = queue.pop
168
+
169
+ case msg
170
+ when StreamEnd
171
+ break
172
+ when StreamError
173
+ raise msg.exception
174
+ else
175
+ y << msg
176
+
177
+ lsn = case msg
178
+ when Protocol::XLogData
179
+ msg.lsn
180
+ when Protocol::PrimaryKeepalive
181
+ msg.current_lsn
182
+ end
183
+
184
+ if lsn && lsn > 0
185
+ status_update_mutex.synchronize { @last_confirmed_lsn = lsn }
186
+ end
187
+ end
188
+ end
189
+ ensure
190
+ queue.close
191
+ thread.join(5)
192
+ thread.kill if thread.alive?
193
+ end.lazy
194
+ end
195
+
196
+ def start_sync_replication
197
+ Enumerator.new do |y|
198
+ loop do
199
+ consume_input
200
+ next if is_busy
201
+
202
+ case get_copy_data(async: true)
203
+ in nil
204
+ get_last_result
205
+ break
206
+ in false
207
+ IO.select([socket_io], nil, nil, 10)
208
+ next
209
+ in data
210
+ y << Protocol.read_message(Buffer.new(data))
211
+ end
212
+ end
213
+ end.lazy
214
+ end
127
215
  end
128
216
 
129
217
  Connection.send(:include, Replication)
@@ -0,0 +1,283 @@
1
+ module PG
2
+ module Replication
3
+ module Protocol
4
+ class PrimaryKeepalive < ::Data
5
+ def self.new: (Integer current_lsn, Time server_time, Boolean asap) -> instance
6
+ | (current_lsn: Integer, server_time: Time, asap: Boolean) -> instance
7
+
8
+ def self.[]: (Integer current_lsn, Time server_time, Boolean asap) -> instance
9
+ | (current_lsn: Integer, server_time: Time, asap: Boolean) -> instance
10
+
11
+ def self.members: () -> [ :current_lsn, :server_time, :asap ]
12
+
13
+ def members: () -> [ :current_lsn, :server_time, :asap ]
14
+
15
+ attr_reader current_lsn: Integer
16
+
17
+ attr_reader server_time: Time
18
+
19
+ attr_reader asap: Boolean
20
+ end
21
+
22
+ class XLogData[T] < ::Data
23
+ def self.new: (Integer lsn, Integer current_lsn, Time server_time, T data) -> instance
24
+ | (lsn: Integer, current_lsn: Integer, server_time: Time, data: T) -> instance
25
+
26
+ def self.[]: (Integer lsn, Integer current_lsn, Time server_time, T data) -> instance
27
+ | (lsn: Integer, current_lsn: Integer, server_time: Time, data: T) -> instance
28
+
29
+ def self.members: () -> [ :lsn, :current_lsn, :server_time, :data ]
30
+
31
+ def members: () -> [ :lsn, :current_lsn, :server_time, :data ]
32
+
33
+ attr_reader lsn: Integer
34
+
35
+ attr_reader current_lsn: Integer
36
+
37
+ attr_reader server_time: Time
38
+
39
+ attr_reader data: T
40
+ end
41
+ end
42
+
43
+ module PGOutput
44
+ class Begin < ::Data
45
+ def self.new: (Integer final_lsn, Time timestamp, Integer xid) -> instance
46
+ | (final_lsn: Integer, timestamp: Time, xid: Integer) -> instance
47
+
48
+ def self.[]: (Integer final_lsn, Time timestamp, Integer xid) -> instance
49
+ | (final_lsn: Integer, timestamp: Time, xid: Integer) -> instance
50
+
51
+ def self.members: () -> [ :final_lsn, :timestamp, :xid ]
52
+
53
+ def members: () -> [ :final_lsn, :timestamp, :xid ]
54
+
55
+ attr_reader final_lsn: Integer
56
+
57
+ attr_reader timestamp: Time
58
+
59
+ attr_reader xid: Integer
60
+ end
61
+
62
+ class Column < ::Data
63
+ def self.new: (Integer flags, String name, Integer oid, Integer modifier) -> instance
64
+ | (flags: Integer, name: String, oid: Integer, modifier: Integer) -> instance
65
+
66
+ def self.[]: (Integer flags, String name, Integer oid, Integer modifier) -> instance
67
+ | (flags: Integer, name: String, oid: Integer, modifier: Integer) -> instance
68
+
69
+ def self.members: () -> [ :flags, :name, :oid, :modifier ]
70
+
71
+ def members: () -> [ :flags, :name, :oid, :modifier ]
72
+
73
+ attr_reader flags: Integer
74
+
75
+ attr_reader name: String
76
+
77
+ attr_reader oid: Integer
78
+
79
+ attr_reader modifier: Integer
80
+
81
+ def key?: () -> Boolean
82
+ end
83
+
84
+ class Commit < ::Data
85
+ def self.new: (Integer lsn, Integer end_lsn, Time timestamp) -> instance
86
+ | (lsn: Integer, end_lsn: Integer, timestamp: Time) -> instance
87
+
88
+ def self.[]: (Integer lsn, Integer end_lsn, Time timestamp) -> instance
89
+ | (lsn: Integer, end_lsn: Integer, timestamp: Time) -> instance
90
+
91
+ def self.members: () -> [ :lsn, :end_lsn, :timestamp ]
92
+
93
+ def members: () -> [ :lsn, :end_lsn, :timestamp ]
94
+
95
+ attr_reader lsn: Integer
96
+
97
+ attr_reader end_lsn: Integer
98
+
99
+ attr_reader timestamp: Time
100
+ end
101
+
102
+ class Delete < ::Data
103
+ def self.new: (Integer oid, Array[String] key, Array[String] old) -> instance
104
+ | (oid: Integer, key: Array[String], old: Array[String]) -> instance
105
+
106
+ def self.[]: (Integer oid, Array[String] key, Array[String] old) -> instance
107
+ | (oid: Integer, key: Array[String], old: Array[String]) -> instance
108
+
109
+ def self.members: () -> [ :oid, :key, :old ]
110
+
111
+ def members: () -> [ :oid, :key, :old ]
112
+
113
+ attr_reader oid: Integer
114
+
115
+ attr_reader key: Array[String]
116
+
117
+ attr_reader old: Array[String]
118
+ end
119
+
120
+ class Insert < ::Data
121
+ def self.new: (Integer oid, Array[String] new) -> instance
122
+ | (oid: Integer, new: Array[String]) -> instance
123
+
124
+ def self.[]: (Integer oid, Array[String] new) -> instance
125
+ | (oid: Integer, new: Array[String]) -> instance
126
+
127
+ def self.members: () -> [ :oid, :new ]
128
+
129
+ def members: () -> [ :oid, :new ]
130
+
131
+ attr_reader oid: Integer
132
+
133
+ attr_reader new: Array[String]
134
+ end
135
+
136
+ class Message < ::Data
137
+ def self.new: (Boolean transactional, Integer lsn, String? prefix, String? content) -> instance
138
+ | (transactional: Boolean, lsn: Integer, prefix: String?, content: String?) -> instance
139
+
140
+ def self.[]: (Boolean transactional, Integer lsn, String? prefix, String? content) -> instance
141
+ | (transactional: Boolean, lsn: Integer, prefix: String?, content: String?) -> instance
142
+
143
+ def self.members: () -> [ :transactional, :lsn, :prefix, :content ]
144
+
145
+ def members: () -> [ :transactional, :lsn, :prefix, :content ]
146
+
147
+ attr_reader transactional: Boolean
148
+
149
+ attr_reader lsn: Integer
150
+
151
+ attr_reader prefix: String?
152
+
153
+ attr_reader content: String?
154
+ end
155
+
156
+ class Origin < ::Data
157
+ def self.new: (Integer commit_lsn, String name) -> instance
158
+ | (commit_lsn: Integer, name: String) -> instance
159
+
160
+ def self.[]: (Integer commit_lsn, String name) -> instance
161
+ | (commit_lsn: Integer, name: String) -> instance
162
+
163
+ def self.members: () -> [ :commit_lsn, :name ]
164
+
165
+ def members: () -> [ :commit_lsn, :name ]
166
+
167
+ attr_reader commit_lsn: Integer
168
+
169
+ attr_reader name: String
170
+ end
171
+
172
+ class Relation < ::Data
173
+ def self.new: (Integer oid, String namespace, String name, String replica_identity, Array[PG::Replication::PGOutput::Column] columns) -> instance
174
+ | (oid: Integer, namespace: String, name: String, replica_identity: String, columns: Array[PG::Replication::PGOutput::Column]) -> instance
175
+
176
+ def self.[]: (Integer oid, String namespace, String name, String replica_identity, Array[PG::Replication::PGOutput::Column] columns) -> instance
177
+ | (oid: Integer, namespace: String, name: String, replica_identity: String, columns: Array[PG::Replication::PGOutput::Column]) -> instance
178
+
179
+ def self.members: () -> [ :oid, :namespace, :name, :replica_identity, :columns ]
180
+
181
+ def members: () -> [ :oid, :namespace, :name, :replica_identity, :columns ]
182
+
183
+ attr_reader oid: Integer
184
+
185
+ attr_reader namespace: String
186
+
187
+ attr_reader name: String
188
+
189
+ attr_reader replica_identity: String
190
+
191
+ attr_reader columns: Array[PG::Replication::PGOutput::Column]
192
+ end
193
+
194
+ class Truncate < ::Data
195
+ def self.new: (Integer oid) -> instance
196
+ | (oid: Integer) -> instance
197
+
198
+ def self.[]: (Integer oid) -> instance
199
+ | (oid: Integer) -> instance
200
+
201
+ def self.members: () -> [ :oid ]
202
+
203
+ def members: () -> [ :oid ]
204
+
205
+ attr_reader oid: Integer
206
+ end
207
+
208
+ class Tuple < ::Data
209
+ def self.new: (String type, String? data) -> instance
210
+ | (type: String, data: String?) -> instance
211
+
212
+ def self.[]: (String type, String? data) -> instance
213
+ | (type: String, data: String?) -> instance
214
+
215
+ def self.members: () -> [ :type, :data ]
216
+
217
+ def members: () -> [ :type, :data ]
218
+
219
+ attr_reader type: String
220
+
221
+ attr_reader data: String?
222
+
223
+ def text?: () -> Boolean
224
+
225
+ def binary?: () -> Boolean
226
+
227
+ def toast?: () -> Boolean
228
+ end
229
+
230
+ class Type < ::Data
231
+ def self.new: (Integer oid, String namespace, String name) -> instance
232
+ | (oid: Integer, namespace: String, name: String) -> instance
233
+
234
+ def self.[]: (Integer oid, String namespace, String name) -> instance
235
+ | (oid: Integer, namespace: String, name: String) -> instance
236
+
237
+ def self.members: () -> [ :oid, :namespace, :name ]
238
+
239
+ def members: () -> [ :oid, :namespace, :name ]
240
+
241
+ attr_reader oid: Integer
242
+
243
+ attr_reader namespace: String
244
+
245
+ attr_reader name: String
246
+ end
247
+
248
+ class Update < ::Data
249
+ def self.new: (Integer oid, Array[String] key, Array[String] old, Array[String] new) -> instance
250
+ | (oid: Integer, key: Array[String], old: Array[String], new: Array[String]) -> instance
251
+
252
+ def self.[]: (Integer oid, Array[String] key, Array[String] old, Array[String] new) -> instance
253
+ | (oid: Integer, key: Array[String], old: Array[String], new: Array[String]) -> instance
254
+
255
+ def self.members: () -> [ :oid, :key, :old, :new ]
256
+
257
+ def members: () -> [ :oid, :key, :old, :new ]
258
+
259
+ attr_reader oid: Integer
260
+
261
+ attr_reader key: Array[String]
262
+
263
+ attr_reader old: Array[String]
264
+
265
+ attr_reader new: Array[String]
266
+ end
267
+ end
268
+
269
+ @last_confirmed_lsn: Integer?
270
+
271
+ def start_replication_slot: (String slot, ?logical: bool, ?auto_keep_alive: bool, ?location: ::String, **untyped params) -> Enumerator::Lazy[PG::Replication::Protocol::PrimaryKeepalive | PG::Replication::Protocol::XLogData[String]]
272
+
273
+ def start_pgoutput_replication_slot: (String slot, Array[String] publication_names, **untyped kwargs) -> Enumerator::Lazy[PG::Replication::Protocol::PrimaryKeepalive | PG::Replication::Protocol::XLogData[PG::Replication::PGOutput]]
274
+
275
+ def standby_status_update: (write_lsn: Integer, ?flush_lsn: Integer, ?apply_lsn: Integer, ?timestamp: Time, ?reply: bool) -> void
276
+
277
+ def last_confirmed_lsn: () -> Integer?
278
+
279
+ def wal_receiver_status_interval: () -> Integer
280
+
281
+ def confirmed_slot_lsn: (String slot) -> Integer?
282
+ end
283
+ end
metadata CHANGED
@@ -1,13 +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.6
4
+ version: 0.0.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rodrigo Navarro
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2025-06-20 00:00:00.000000000 Z
10
+ date: 2026-01-14 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: pg
@@ -41,6 +41,7 @@ files:
41
41
  - lib/pg/replication/protocol.rb
42
42
  - lib/pg/replication/version.rb
43
43
  - pg-replication-protocol.gemspec
44
+ - sig/pg/replication.rbs
44
45
  licenses:
45
46
  - MIT
46
47
  metadata: