wal 0.0.25 → 0.0.26

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: 7ff2f0a5d9392268db27728b053f29ac897649314c72b857b0d43303bb8face1
4
- data.tar.gz: a378a4d4c4e5f3f1e6d9a988fe61947683c52614223e27a67371b4042434a2cb
3
+ metadata.gz: f4e3079bb9acf7aca8959aca8f0e9aca650e4d615112e472bcc903a90517ad7c
4
+ data.tar.gz: 90db3db17387e674920dd72e31079b5345bc8e2f049458f1c0afce8ad68811ff
5
5
  SHA512:
6
- metadata.gz: 5514c83c92062744f25cdc2f7f08aaa79cdbd4bc7cd49c6028de964f3d5c02a0a0b8d2d3b8f3a6cdf69954adb45f5a89866f5fdd193cebfd1cb1fc24049d3ba9
7
- data.tar.gz: a29bb6e5c5cc9795e82d88d15f4e820cc88b651f58950c67120dc988454aa6230d0e85af25e016289402c5bb5a6b1db9341e039fdc350155a86710190be6bad2
6
+ metadata.gz: 80a7497a438b5ec3f8fce77ac15d09789f1e3adec6c121bf901bf0a01041de9b20819ba24cd4b9c9f4dbd9344314a02eb210518270751e60cc1bcc41ce8949f1
7
+ data.tar.gz: 91bf03a93fe7afb0b9320ec73e9cbaeb726494504813a77b166f43fe1d6f0118871cd147568372d7d8b277c69fcb74f1a49032ffaf4ea72e6e12fae7a7a88495
@@ -1,5 +1,3 @@
1
- require "ostruct"
2
-
3
1
  module Wal
4
2
  # Responsible to hook into a Postgres logical replication slot and stream the changes to a specific `Watcher`.
5
3
  # Also it supports inject "contexts" into the replication events.
@@ -23,8 +21,14 @@ module Wal
23
21
  nil
24
22
  end
25
23
 
24
+ def close
25
+ @watch_conn&.stop_replication
26
+ @watch_conn&.close
27
+ @watch_conn = nil
28
+ end
29
+
26
30
  def replicate(watcher, publications:)
27
- watch_conn = PG.connect(
31
+ @watch_conn = PG.connect(
28
32
  dbname: @db_config[:database],
29
33
  host: @db_config[:host],
30
34
  user: @db_config[:username],
@@ -34,7 +38,7 @@ module Wal
34
38
  )
35
39
 
36
40
  begin
37
- watch_conn.query(<<~SQL)
41
+ @watch_conn.query(<<~SQL)
38
42
  CREATE_REPLICATION_SLOT #{@replication_slot} #{@use_temporary_slot ? "TEMPORARY" : ""} LOGICAL "pgoutput"
39
43
  SQL
40
44
  rescue PG::DuplicateObject
@@ -45,7 +49,7 @@ module Wal
45
49
  context = {}
46
50
  transaction_id = nil
47
51
 
48
- watch_conn.start_pgoutput_replication_slot(@replication_slot, publications, messages: true).filter_map do |msg|
52
+ @watch_conn.start_pgoutput_replication_slot(@replication_slot, publications, messages: true).filter_map do |msg|
49
53
  case msg
50
54
  in XLogData(data: PG::Replication::PGOutput::Relation(oid:, name:, columns:, namespace:))
51
55
  tables[oid] = Table.new(
@@ -53,16 +57,7 @@ module Wal
53
57
  primary_key_colums: columns.any? { |col| col.name == "id" } ? ["id"] : [],
54
58
  schema: namespace,
55
59
  name:,
56
- columns: columns.map do |col|
57
- Column.new(
58
- name: col.name,
59
- decoder: ActiveRecord::Base.connection.lookup_cast_type_from_column(
60
- # We have to create this OpenStruct because weird AR API reasons...
61
- # And the `sql_type` param luckly doesn't really matter for our use case
62
- ::OpenStruct.new(oid: col.oid, fmod: col.modifier, sql_type: "")
63
- ),
64
- )
65
- end
60
+ columns: columns.map { |col| Column.new(oid: col.oid, name: col.name) },
66
61
  )
67
62
  next
68
63
 
@@ -84,11 +79,11 @@ module Wal
84
79
  timestamp:,
85
80
  ).tap do |event|
86
81
  watcher.on_event(event)
87
- watch_conn.standby_status_update(write_lsn: lsn)
82
+ @watch_conn.standby_status_update(write_lsn: lsn)
88
83
  end
89
84
 
90
85
  in XLogData(lsn:, data: PG::Replication::PGOutput::Message(prefix: "wal_ping"))
91
- watch_conn.standby_status_update(write_lsn: [watch_conn.last_confirmed_lsn, lsn].compact.max)
86
+ @watch_conn.standby_status_update(write_lsn: [@watch_conn.last_confirmed_lsn, lsn].compact.max)
92
87
  next
93
88
 
94
89
  in XLogData(data: PG::Replication::PGOutput::Message(prefix:, content:)) if watcher.valid_context_prefix? prefix
@@ -156,14 +151,186 @@ module Wal
156
151
  next
157
152
  end
158
153
  rescue
159
- watch_conn&.close
154
+ close
160
155
  raise
161
156
  end
162
157
  end
163
158
 
164
- class Column < Data.define(:name, :decoder)
159
+ class Column < Data.define(:name, :oid)
160
+ BOOLEAN_DECODER = PG::TextDecoder::Boolean.new
161
+ BYTEA_DECODER = PG::TextDecoder::Bytea.new
162
+ INTEGER_DECODER = PG::TextDecoder::Integer.new
163
+ FLOAT_DECODER = PG::TextDecoder::Float.new
164
+ NUMERIC_DECODER = PG::TextDecoder::Numeric.new
165
+ DATE_DECODER = PG::TextDecoder::Date.new
166
+ TIMESTAMP_DECODER = PG::TextDecoder::TimestampWithoutTimeZone.new
167
+ TIMESTAMPTZ_DECODER = PG::TextDecoder::TimestampWithTimeZone.new
168
+ JSON_DECODER = PG::TextDecoder::JSON.new
169
+ INET_DECODER = PG::TextDecoder::Inet.new
170
+ STRING_DECODER = PG::TextDecoder::String.new
171
+
172
+ BOOLEAN_ARRAY_DECODER = PG::TextDecoder::Array.new(elements_type: BOOLEAN_DECODER)
173
+ BYTEA_ARRAY_DECODER = PG::TextDecoder::Array.new(elements_type: BYTEA_DECODER)
174
+ INTEGER_ARRAY_DECODER = PG::TextDecoder::Array.new(elements_type: INTEGER_DECODER)
175
+ FLOAT_ARRAY_DECODER = PG::TextDecoder::Array.new(elements_type: FLOAT_DECODER)
176
+ NUMERIC_ARRAY_DECODER = PG::TextDecoder::Array.new(elements_type: NUMERIC_DECODER)
177
+ DATE_ARRAY_DECODER = PG::TextDecoder::Array.new(elements_type: DATE_DECODER)
178
+ TIMESTAMP_ARRAY_DECODER = PG::TextDecoder::Array.new(elements_type: TIMESTAMP_DECODER)
179
+ TIMESTAMPTZ_ARRAY_DECODER = PG::TextDecoder::Array.new(elements_type: TIMESTAMPTZ_DECODER)
180
+ JSON_ARRAY_DECODER = PG::TextDecoder::Array.new(elements_type: JSON_DECODER)
181
+ INET_ARRAY_DECODER = PG::TextDecoder::Array.new(elements_type: INET_DECODER)
182
+ STRING_ARRAY_DECODER = PG::TextDecoder::Array.new(elements_type: STRING_DECODER)
183
+
184
+ # Reference: https://github.com/postgres/postgres/blob/master/src/include/catalog/pg_type.dat
185
+ DECODERS = {
186
+ 16 => BOOLEAN_DECODER, # bool
187
+ 1000 => BOOLEAN_ARRAY_DECODER, # _bool
188
+
189
+ 17 => BYTEA_DECODER, # bytea
190
+ 1001 => BYTEA_ARRAY_DECODER, # _bytea
191
+
192
+ 20 => INTEGER_DECODER, # int8
193
+ 21 => INTEGER_DECODER, # int2
194
+ 23 => INTEGER_DECODER, # int4
195
+ 26 => INTEGER_DECODER, # oid
196
+ 28 => INTEGER_DECODER, # xid
197
+ 29 => INTEGER_DECODER, # cid
198
+ 5069 => INTEGER_DECODER, # xid8
199
+ 1005 => INTEGER_ARRAY_DECODER, # _int2
200
+ 1007 => INTEGER_ARRAY_DECODER, # _int4
201
+ 1016 => INTEGER_ARRAY_DECODER, # _int8
202
+ 1028 => INTEGER_ARRAY_DECODER, # _oid
203
+ 1011 => INTEGER_ARRAY_DECODER, # _xid
204
+ 1012 => INTEGER_ARRAY_DECODER, # _cid
205
+ 271 => INTEGER_ARRAY_DECODER, # _xid8
206
+
207
+ 700 => FLOAT_DECODER, # float4
208
+ 701 => FLOAT_DECODER, # float8
209
+ 1021 => FLOAT_ARRAY_DECODER, # _float4
210
+ 1022 => FLOAT_ARRAY_DECODER, # _float8
211
+
212
+ 1700 => NUMERIC_DECODER, # numeric
213
+ 790 => NUMERIC_DECODER, # money
214
+ 1231 => NUMERIC_ARRAY_DECODER, # _numeric
215
+ 791 => NUMERIC_ARRAY_DECODER, # _money
216
+
217
+ 1082 => DATE_DECODER, # date
218
+ 1114 => TIMESTAMP_DECODER, # timestamp
219
+ 1184 => TIMESTAMPTZ_DECODER, # timestamptz
220
+ 1083 => STRING_DECODER, # time (no dedicated decoder, use string)
221
+ 1266 => STRING_DECODER, # timetz
222
+ 1186 => STRING_DECODER, # interval
223
+ 1182 => DATE_ARRAY_DECODER, # _date
224
+ 1115 => TIMESTAMP_ARRAY_DECODER, # _timestamp
225
+ 1185 => TIMESTAMPTZ_ARRAY_DECODER, # _timestamptz
226
+ 1183 => STRING_ARRAY_DECODER, # _time
227
+ 1270 => STRING_ARRAY_DECODER, # _timetz
228
+ 1187 => STRING_ARRAY_DECODER, # _interval
229
+
230
+ 114 => JSON_DECODER, # json
231
+ 3802 => JSON_DECODER, # jsonb
232
+ 199 => JSON_ARRAY_DECODER, # _json
233
+ 3807 => JSON_ARRAY_DECODER, # _jsonb
234
+ 4072 => STRING_DECODER, # jsonpath
235
+ 4073 => STRING_ARRAY_DECODER, # _jsonpath
236
+
237
+ 869 => INET_DECODER, # inet
238
+ 650 => INET_DECODER, # cidr
239
+ 829 => STRING_DECODER, # macaddr
240
+ 774 => STRING_DECODER, # macaddr8
241
+ 1041 => INET_ARRAY_DECODER, # _inet
242
+ 651 => INET_ARRAY_DECODER, # _cidr
243
+ 1040 => STRING_ARRAY_DECODER, # _macaddr
244
+ 775 => STRING_ARRAY_DECODER, # _macaddr8
245
+
246
+ 18 => STRING_DECODER, # char
247
+ 19 => STRING_DECODER, # name
248
+ 25 => STRING_DECODER, # text
249
+ 1042 => STRING_DECODER, # bpchar
250
+ 1043 => STRING_DECODER, # varchar
251
+ 2950 => STRING_DECODER, # uuid
252
+ 142 => STRING_DECODER, # xml
253
+ 1002 => STRING_ARRAY_DECODER, # _char
254
+ 1003 => STRING_ARRAY_DECODER, # _name
255
+ 1009 => STRING_ARRAY_DECODER, # _text
256
+ 1014 => STRING_ARRAY_DECODER, # _bpchar
257
+ 1015 => STRING_ARRAY_DECODER, # _varchar
258
+ 2951 => STRING_ARRAY_DECODER, # _uuid
259
+ 143 => STRING_ARRAY_DECODER, # _xml
260
+
261
+ 1560 => STRING_DECODER, # bit
262
+ 1562 => STRING_DECODER, # varbit
263
+ 1561 => STRING_ARRAY_DECODER, # _bit
264
+ 1563 => STRING_ARRAY_DECODER, # _varbit
265
+
266
+ 600 => STRING_DECODER, # point
267
+ 601 => STRING_DECODER, # lseg
268
+ 602 => STRING_DECODER, # path
269
+ 603 => STRING_DECODER, # box
270
+ 604 => STRING_DECODER, # polygon
271
+ 628 => STRING_DECODER, # line
272
+ 718 => STRING_DECODER, # circle
273
+ 1017 => STRING_ARRAY_DECODER, # _point
274
+ 1018 => STRING_ARRAY_DECODER, # _lseg
275
+ 1019 => STRING_ARRAY_DECODER, # _path
276
+ 1020 => STRING_ARRAY_DECODER, # _box
277
+ 1027 => STRING_ARRAY_DECODER, # _polygon
278
+ 629 => STRING_ARRAY_DECODER, # _line
279
+ 719 => STRING_ARRAY_DECODER, # _circle
280
+
281
+ 3904 => STRING_DECODER, # int4range
282
+ 3906 => STRING_DECODER, # numrange
283
+ 3908 => STRING_DECODER, # tsrange
284
+ 3910 => STRING_DECODER, # tstzrange
285
+ 3912 => STRING_DECODER, # daterange
286
+ 3926 => STRING_DECODER, # int8range
287
+ 4451 => STRING_DECODER, # int4multirange
288
+ 4532 => STRING_DECODER, # nummultirange
289
+ 4533 => STRING_DECODER, # tsmultirange
290
+ 4534 => STRING_DECODER, # tstzmultirange
291
+ 4535 => STRING_DECODER, # datemultirange
292
+ 4536 => STRING_DECODER, # int8multirange
293
+ 3905 => STRING_ARRAY_DECODER, # _int4range
294
+ 3907 => STRING_ARRAY_DECODER, # _numrange
295
+ 3909 => STRING_ARRAY_DECODER, # _tsrange
296
+ 3911 => STRING_ARRAY_DECODER, # _tstzrange
297
+ 3913 => STRING_ARRAY_DECODER, # _daterange
298
+ 3927 => STRING_ARRAY_DECODER, # _int8range
299
+
300
+ 3614 => STRING_DECODER, # tsvector
301
+ 3615 => STRING_DECODER, # tsquery
302
+ 3643 => STRING_ARRAY_DECODER, # _tsvector
303
+ 3645 => STRING_ARRAY_DECODER, # _tsquery
304
+
305
+ 3220 => STRING_DECODER, # pg_lsn
306
+ 3221 => STRING_ARRAY_DECODER, # _pg_lsn
307
+
308
+ 24 => INTEGER_DECODER, # regproc
309
+ 2202 => INTEGER_DECODER, # regprocedure
310
+ 2203 => INTEGER_DECODER, # regoper
311
+ 2204 => INTEGER_DECODER, # regoperator
312
+ 2205 => INTEGER_DECODER, # regclass
313
+ 2206 => INTEGER_DECODER, # regtype
314
+ 4096 => INTEGER_DECODER, # regrole
315
+ 4089 => INTEGER_DECODER, # regnamespace
316
+ 3734 => INTEGER_DECODER, # regconfig
317
+ 3769 => INTEGER_DECODER, # regdictionary
318
+ 4191 => INTEGER_DECODER, # regcollation
319
+ 1008 => INTEGER_ARRAY_DECODER, # _regproc
320
+ 2207 => INTEGER_ARRAY_DECODER, # _regprocedure
321
+ 2208 => INTEGER_ARRAY_DECODER, # _regoper
322
+ 2209 => INTEGER_ARRAY_DECODER, # _regoperator
323
+ 2210 => INTEGER_ARRAY_DECODER, # _regclass
324
+ 2211 => INTEGER_ARRAY_DECODER, # _regtype
325
+ 4097 => INTEGER_ARRAY_DECODER, # _regrole
326
+ 4090 => INTEGER_ARRAY_DECODER, # _regnamespace
327
+ 3735 => INTEGER_ARRAY_DECODER, # _regconfig
328
+ 3770 => INTEGER_ARRAY_DECODER, # _regdictionary
329
+ 4192 => INTEGER_ARRAY_DECODER, # _regcollation
330
+ }.freeze
331
+
165
332
  def decode(value)
166
- decoder.deserialize(value)
333
+ value&.then { (DECODERS[oid] || STRING_DECODER).decode(_1) }
167
334
  end
168
335
  end
169
336
 
data/lib/wal/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Wal
4
- VERSION = "0.0.25"
4
+ VERSION = "0.0.26"
5
5
  end
data/rbi/wal.rbi CHANGED
@@ -7,7 +7,7 @@ module Wal
7
7
  UpdateEvent,
8
8
  DeleteEvent,
9
9
  ) }
10
- VERSION = "0.0.25"
10
+ VERSION = "0.0.26"
11
11
 
12
12
  class << self
13
13
  sig { returns(T.class_of(Logger)) }
@@ -176,6 +176,9 @@ module Wal
176
176
 
177
177
  sig { params(watcher: Wal::Watcher, publications: T::Array[String]).returns(T::Enumerator::Lazy[Wal::Event]) }
178
178
  def replicate(watcher, publications:); end
179
+
180
+ sig { void }
181
+ def close; end
179
182
  end
180
183
 
181
184
  class StreamingWatcher
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: wal
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.25
4
+ version: 0.0.26
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rodrigo Navarro
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2026-01-12 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