wal 0.0.2 → 0.0.3
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 +4 -4
- data/Rakefile +0 -7
- data/lib/wal/noop_watcher.rb +0 -4
- data/lib/wal/record_watcher.rb +4 -58
- data/lib/wal/replicator.rb +2 -21
- data/lib/wal/streaming_watcher.rb +0 -9
- data/lib/wal/version.rb +1 -2
- data/lib/wal/watcher.rb +0 -25
- data/lib/wal.rb +5 -64
- data/rbi/wal.rbi +1 -81
- metadata +2 -161
- data/sorbet/config +0 -7
- data/sorbet/rbi/annotations/.gitattributes +0 -1
- data/sorbet/rbi/annotations/activemodel.rbi +0 -89
- data/sorbet/rbi/annotations/activerecord.rbi +0 -98
- data/sorbet/rbi/annotations/activesupport.rbi +0 -463
- data/sorbet/rbi/annotations/minitest.rbi +0 -119
- data/sorbet/rbi/annotations/rainbow.rbi +0 -269
- data/sorbet/rbi/gems/.gitattributes +0 -1
- data/sorbet/rbi/gems/actioncable@8.0.2.rbi +0 -9
- data/sorbet/rbi/gems/actionmailbox@8.0.2.rbi +0 -9
- data/sorbet/rbi/gems/actionmailer@8.0.2.rbi +0 -9
- data/sorbet/rbi/gems/actionpack@8.0.2.rbi +0 -21122
- data/sorbet/rbi/gems/actiontext@8.0.2.rbi +0 -9
- data/sorbet/rbi/gems/actionview@8.0.2.rbi +0 -16423
- data/sorbet/rbi/gems/activejob@8.0.2.rbi +0 -9
- data/sorbet/rbi/gems/activemodel@8.0.2.rbi +0 -6866
- data/sorbet/rbi/gems/activerecord@8.0.2.rbi +0 -43227
- data/sorbet/rbi/gems/activestorage@8.0.2.rbi +0 -9
- data/sorbet/rbi/gems/activesupport@8.0.2.rbi +0 -21110
- data/sorbet/rbi/gems/ast@2.4.3.rbi +0 -585
- data/sorbet/rbi/gems/base64@0.3.0.rbi +0 -545
- data/sorbet/rbi/gems/benchmark@0.4.1.rbi +0 -619
- data/sorbet/rbi/gems/bigdecimal@3.2.2.rbi +0 -78
- data/sorbet/rbi/gems/builder@3.3.0.rbi +0 -9
- data/sorbet/rbi/gems/commander@5.0.0.rbi +0 -9
- data/sorbet/rbi/gems/concurrent-ruby@1.3.5.rbi +0 -11657
- data/sorbet/rbi/gems/connection_pool@2.5.3.rbi +0 -9
- data/sorbet/rbi/gems/crass@1.0.6.rbi +0 -623
- data/sorbet/rbi/gems/date@3.4.1.rbi +0 -75
- data/sorbet/rbi/gems/diff-lcs@1.6.2.rbi +0 -1134
- data/sorbet/rbi/gems/docker-api@2.4.0.rbi +0 -1719
- data/sorbet/rbi/gems/docopt@0.6.1.rbi +0 -9
- data/sorbet/rbi/gems/drb@2.2.3.rbi +0 -1661
- data/sorbet/rbi/gems/erubi@1.13.1.rbi +0 -155
- data/sorbet/rbi/gems/excon@1.2.7.rbi +0 -1514
- data/sorbet/rbi/gems/globalid@1.2.1.rbi +0 -9
- data/sorbet/rbi/gems/highline@3.0.1.rbi +0 -9
- data/sorbet/rbi/gems/i18n@1.14.7.rbi +0 -2359
- data/sorbet/rbi/gems/io-console@0.8.0.rbi +0 -9
- data/sorbet/rbi/gems/logger@1.7.0.rbi +0 -963
- data/sorbet/rbi/gems/loofah@2.24.1.rbi +0 -1105
- data/sorbet/rbi/gems/mail@2.8.1.rbi +0 -9
- data/sorbet/rbi/gems/marcel@1.0.4.rbi +0 -9
- data/sorbet/rbi/gems/mini_mime@1.1.5.rbi +0 -9
- data/sorbet/rbi/gems/minitest@5.25.5.rbi +0 -1704
- data/sorbet/rbi/gems/multi_json@1.15.0.rbi +0 -268
- data/sorbet/rbi/gems/net-imap@0.5.9.rbi +0 -9
- data/sorbet/rbi/gems/net-pop@0.1.2.rbi +0 -9
- data/sorbet/rbi/gems/net-protocol@0.2.2.rbi +0 -292
- data/sorbet/rbi/gems/net-smtp@0.5.1.rbi +0 -9
- data/sorbet/rbi/gems/netrc@0.11.0.rbi +0 -159
- data/sorbet/rbi/gems/nio4r@2.7.4.rbi +0 -9
- data/sorbet/rbi/gems/nokogiri@1.18.8.rbi +0 -8206
- data/sorbet/rbi/gems/ostruct@0.6.2.rbi +0 -354
- data/sorbet/rbi/gems/parallel@1.27.0.rbi +0 -291
- data/sorbet/rbi/gems/parlour@9.1.1.rbi +0 -3071
- data/sorbet/rbi/gems/parser@3.3.8.0.rbi +0 -7338
- data/sorbet/rbi/gems/pg-replication-protocol@0.0.7.rbi +0 -633
- data/sorbet/rbi/gems/pg@1.5.9.rbi +0 -2806
- data/sorbet/rbi/gems/pp@0.6.2.rbi +0 -368
- data/sorbet/rbi/gems/prettyprint@0.2.0.rbi +0 -477
- data/sorbet/rbi/gems/prism@1.4.0.rbi +0 -41732
- data/sorbet/rbi/gems/psych@5.2.3.rbi +0 -2435
- data/sorbet/rbi/gems/racc@1.8.1.rbi +0 -160
- data/sorbet/rbi/gems/rack-session@2.1.1.rbi +0 -727
- data/sorbet/rbi/gems/rack-test@2.2.0.rbi +0 -734
- data/sorbet/rbi/gems/rack@3.1.16.rbi +0 -4940
- data/sorbet/rbi/gems/rackup@2.2.1.rbi +0 -230
- data/sorbet/rbi/gems/rails-dom-testing@2.3.0.rbi +0 -858
- data/sorbet/rbi/gems/rails-html-sanitizer@1.6.2.rbi +0 -785
- data/sorbet/rbi/gems/rails@8.0.2.rbi +0 -9
- data/sorbet/rbi/gems/railties@8.0.2.rbi +0 -3865
- data/sorbet/rbi/gems/rainbow@3.1.1.rbi +0 -403
- data/sorbet/rbi/gems/rake@13.2.1.rbi +0 -3120
- data/sorbet/rbi/gems/rbi@0.3.6.rbi +0 -6893
- data/sorbet/rbi/gems/rbs@3.9.4.rbi +0 -6978
- data/sorbet/rbi/gems/rdoc@6.12.0.rbi +0 -12760
- data/sorbet/rbi/gems/reline@0.6.0.rbi +0 -2451
- data/sorbet/rbi/gems/rexml@3.4.1.rbi +0 -5240
- data/sorbet/rbi/gems/rspec-core@3.13.4.rbi +0 -11348
- data/sorbet/rbi/gems/rspec-expectations@3.13.5.rbi +0 -8189
- data/sorbet/rbi/gems/rspec-mocks@3.13.5.rbi +0 -5350
- data/sorbet/rbi/gems/rspec-sorbet@1.9.2.rbi +0 -164
- data/sorbet/rbi/gems/rspec-support@3.13.4.rbi +0 -1630
- data/sorbet/rbi/gems/rspec@3.13.1.rbi +0 -83
- data/sorbet/rbi/gems/securerandom@0.4.1.rbi +0 -75
- data/sorbet/rbi/gems/spoom@1.6.3.rbi +0 -6985
- data/sorbet/rbi/gems/stringio@3.1.5.rbi +0 -9
- data/sorbet/rbi/gems/tapioca@0.16.11.rbi +0 -3628
- data/sorbet/rbi/gems/testcontainers-core@0.2.0.rbi +0 -1005
- data/sorbet/rbi/gems/testcontainers-postgres@0.2.0.rbi +0 -145
- data/sorbet/rbi/gems/thor@1.3.2.rbi +0 -4378
- data/sorbet/rbi/gems/timeout@0.4.3.rbi +0 -157
- data/sorbet/rbi/gems/tzinfo@2.0.6.rbi +0 -5918
- data/sorbet/rbi/gems/uri@1.0.3.rbi +0 -2349
- data/sorbet/rbi/gems/useragent@0.16.11.rbi +0 -9
- data/sorbet/rbi/gems/websocket-driver@0.8.0.rbi +0 -9
- data/sorbet/rbi/gems/websocket-extensions@0.1.5.rbi +0 -9
- data/sorbet/rbi/gems/yard-sorbet@0.9.0.rbi +0 -435
- data/sorbet/rbi/gems/yard@0.9.37.rbi +0 -18379
- data/sorbet/rbi/gems/zeitwerk@2.7.3.rbi +0 -9
- data/sorbet/tapioca/config.yml +0 -5
- data/sorbet/tapioca/require.rb +0 -12
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3df0dd993bf343f90d54d4b60f52504ca2a84d4fd839c5c952e6f866b40a46cd
|
4
|
+
data.tar.gz: 8e8b568f3a26ed687e9010bd9c69ea4a656e6af59be20449a9142b61c61fe4ad
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: '09591c7506a7b29d9261574f9a512f120862b58d08ae71d31501147076bbae7f9ab65c9c0ded4fe3c15ddfaa9ec1881ebebf934069c08847d8b486d4fcdfc6f7'
|
7
|
+
data.tar.gz: c09c1417b8021fde2890f9dd38d32cec9e64e9fec6d1341613b2a8c4522edd60c0ae34e78fdee3c45e8257c6be66d480fb8ad5f3d52fcec5fb0955d79c054a63
|
data/Rakefile
CHANGED
@@ -3,10 +3,3 @@
|
|
3
3
|
require "bundler/gem_tasks"
|
4
4
|
|
5
5
|
task(:test) { sh "bundle exec rspec" }
|
6
|
-
|
7
|
-
task default: %i[build]
|
8
|
-
|
9
|
-
task("sig/wal.rbi") { sh "bundle exec parlour" }
|
10
|
-
task("rbi/wal.rbs") { sh "rbs prototype rbi rbi/wal.rbi > sig/wal.rbs" }
|
11
|
-
|
12
|
-
Rake::Task["build"].enhance(["sig/wal.rbi", "rbi/wal.rbs"])
|
data/lib/wal/noop_watcher.rb
CHANGED
@@ -1,12 +1,8 @@
|
|
1
|
-
# typed: strict
|
2
|
-
|
3
1
|
module Wal
|
4
2
|
# A watcher that does nothing. Just for performance testing in general. Useful in testing aswell.
|
5
3
|
class NoopWatcher
|
6
|
-
extend T::Sig
|
7
4
|
include Wal::Watcher
|
8
5
|
|
9
|
-
sig { override.params(event: Event).void }
|
10
6
|
def on_event(event); end
|
11
7
|
end
|
12
8
|
end
|
data/lib/wal/record_watcher.rb
CHANGED
@@ -1,5 +1,3 @@
|
|
1
|
-
# typed: true
|
2
|
-
|
3
1
|
module Wal
|
4
2
|
# Watcher that process records at the end of a transaction, keeping only its final state.
|
5
3
|
#
|
@@ -39,12 +37,7 @@ module Wal
|
|
39
37
|
# end
|
40
38
|
# ```
|
41
39
|
class RecordWatcher
|
42
|
-
extend T::Sig
|
43
|
-
extend T::Helpers
|
44
40
|
include Wal::Watcher
|
45
|
-
abstract!
|
46
|
-
|
47
|
-
RecordEvent = T.type_alias { T.any(InsertEvent, UpdateEvent, DeleteEvent) }
|
48
41
|
|
49
42
|
def self.inherited(subclass)
|
50
43
|
super
|
@@ -52,53 +45,26 @@ module Wal
|
|
52
45
|
@@delete_callbacks = Hash.new { |hash, key| hash[key] = [] }
|
53
46
|
end
|
54
47
|
|
55
|
-
sig do
|
56
|
-
params(
|
57
|
-
table: T.any(String, T.class_of(::ActiveRecord::Base)),
|
58
|
-
block: T.proc.bind(T.attached_class).params(event: InsertEvent).void,
|
59
|
-
).void
|
60
|
-
end
|
61
48
|
def self.on_insert(table, &block)
|
62
49
|
table = table.is_a?(String) ? table : table.table_name
|
63
50
|
@@change_callbacks[table].push(only: [:create], block: block)
|
64
51
|
end
|
65
52
|
|
66
|
-
sig do
|
67
|
-
params(
|
68
|
-
table: T.any(String, T.class_of(::ActiveRecord::Base)),
|
69
|
-
changed: T.nilable(T::Array[T.any(String, Symbol)]),
|
70
|
-
block: T.proc.bind(T.attached_class).params(event: UpdateEvent).void,
|
71
|
-
).void
|
72
|
-
end
|
73
53
|
def self.on_update(table, changed: nil, &block)
|
74
54
|
table = table.is_a?(String) ? table : table.table_name
|
75
55
|
@@change_callbacks[table].push(only: [:update], changed: changed&.map(&:to_s), block: block)
|
76
56
|
end
|
77
57
|
|
78
|
-
sig do
|
79
|
-
params(
|
80
|
-
table: T.any(String, T.class_of(::ActiveRecord::Base)),
|
81
|
-
changed: T.nilable(T::Array[T.any(String, Symbol)]),
|
82
|
-
block: T.proc.bind(T.attached_class).params(event: T.any(InsertEvent, UpdateEvent)).void,
|
83
|
-
).void
|
84
|
-
end
|
85
58
|
def self.on_save(table, changed: nil, &block)
|
86
59
|
table = table.is_a?(String) ? table : table.table_name
|
87
60
|
@@change_callbacks[table].push(only: [:create, :update], changed: changed&.map(&:to_s), block: block)
|
88
61
|
end
|
89
62
|
|
90
|
-
sig do
|
91
|
-
params(
|
92
|
-
table: T.any(String, T.class_of(::ActiveRecord::Base)),
|
93
|
-
block: T.proc.bind(T.attached_class).params(event: DeleteEvent).void,
|
94
|
-
).void
|
95
|
-
end
|
96
63
|
def self.on_destroy(table, &block)
|
97
64
|
table = table.is_a?(String) ? table : table.table_name
|
98
65
|
@@delete_callbacks[table].push(block: block)
|
99
66
|
end
|
100
67
|
|
101
|
-
sig { params(event: RecordEvent).void }
|
102
68
|
def on_record_changed(event)
|
103
69
|
case event
|
104
70
|
when InsertEvent
|
@@ -124,7 +90,6 @@ module Wal
|
|
124
90
|
end
|
125
91
|
end
|
126
92
|
|
127
|
-
sig { params(table: String).returns(T::Boolean) }
|
128
93
|
def should_watch_table?(table)
|
129
94
|
(@@change_callbacks.keys | @@delete_callbacks.keys).include? table
|
130
95
|
end
|
@@ -139,7 +104,6 @@ module Wal
|
|
139
104
|
#
|
140
105
|
# These strategies can be defined per transaction, and by default it will uses the memory one, and only fallback
|
141
106
|
# to the temporary table if the transaction size is roughly 2 gigabytes or more.
|
142
|
-
sig { params(event: BeginTransactionEvent).returns(Symbol) }
|
143
107
|
def aggregation_strategy(event)
|
144
108
|
if event.estimated_size > 1024.pow(3) * 2
|
145
109
|
:temporary_table
|
@@ -148,7 +112,6 @@ module Wal
|
|
148
112
|
end
|
149
113
|
end
|
150
114
|
|
151
|
-
sig { override.params(event: Event).void }
|
152
115
|
def on_event(event)
|
153
116
|
if event.is_a? BeginTransactionEvent
|
154
117
|
@current_record_watcher = case (strategy = aggregation_strategy(event))
|
@@ -164,21 +127,15 @@ module Wal
|
|
164
127
|
end
|
165
128
|
|
166
129
|
class MemoryRecordWatcher
|
167
|
-
extend T::Sig
|
168
|
-
extend T::Helpers
|
169
130
|
include Wal::Watcher
|
170
131
|
include Wal::Watcher::SeparatedEvents
|
171
132
|
|
172
|
-
# Records indexed by table and primary key
|
173
|
-
RecordsStorage = T.type_alias { T::Hash[[String, Integer], T.nilable(RecordEvent)] }
|
174
|
-
|
175
133
|
def initialize(watcher)
|
176
134
|
@watcher = watcher
|
177
135
|
end
|
178
136
|
|
179
|
-
sig { params(event: BeginTransactionEvent).void }
|
180
137
|
def on_begin(event)
|
181
|
-
@records =
|
138
|
+
@records = {}
|
182
139
|
end
|
183
140
|
|
184
141
|
def on_commit(_event)
|
@@ -188,7 +145,6 @@ module Wal
|
|
188
145
|
&.each { |event| @watcher.on_record_changed(event) if event }
|
189
146
|
end
|
190
147
|
|
191
|
-
sig { params(event: InsertEvent).void }
|
192
148
|
def on_insert(event)
|
193
149
|
if (id = event.primary_key)
|
194
150
|
@records ||= {}
|
@@ -196,7 +152,6 @@ module Wal
|
|
196
152
|
end
|
197
153
|
end
|
198
154
|
|
199
|
-
sig { params(event: UpdateEvent).void }
|
200
155
|
def on_update(event)
|
201
156
|
if (id = event.primary_key)
|
202
157
|
@records ||= {}
|
@@ -217,7 +172,6 @@ module Wal
|
|
217
172
|
end
|
218
173
|
end
|
219
174
|
|
220
|
-
sig { params(event: DeleteEvent).void }
|
221
175
|
def on_delete(event)
|
222
176
|
if (id = event.primary_key)
|
223
177
|
@records ||= {}
|
@@ -240,8 +194,6 @@ module Wal
|
|
240
194
|
end
|
241
195
|
|
242
196
|
class TemporaryTableRecordWatcher
|
243
|
-
extend T::Sig
|
244
|
-
extend T::Helpers
|
245
197
|
include Wal::Watcher
|
246
198
|
include Wal::Watcher::SeparatedEvents
|
247
199
|
|
@@ -255,7 +207,6 @@ module Wal
|
|
255
207
|
@batch_size = 5_000
|
256
208
|
end
|
257
209
|
|
258
|
-
sig { params(event: BeginTransactionEvent).void }
|
259
210
|
def on_begin(event)
|
260
211
|
@table = begin
|
261
212
|
table_name = "temp_record_watcher_#{SecureRandom.alphanumeric(10).downcase}"
|
@@ -276,8 +227,7 @@ module Wal
|
|
276
227
|
base_class.connection.add_index table_name, unique_index, unique: true
|
277
228
|
|
278
229
|
Class.new(base_class) do
|
279
|
-
|
280
|
-
T.cast(self, T.class_of(::ActiveRecord::Base)).table_name = table_name
|
230
|
+
self.table_name = table_name
|
281
231
|
|
282
232
|
# All this sh$#1t was necessary because AR schema cache doesn't work with temporary tables...
|
283
233
|
insert_all_class = Class.new(::ActiveRecord::InsertAll) do
|
@@ -290,8 +240,8 @@ module Wal
|
|
290
240
|
define_singleton_method(:upsert) do |attributes, update_only: nil|
|
291
241
|
insert_all_class
|
292
242
|
.new(
|
293
|
-
|
294
|
-
|
243
|
+
none,
|
244
|
+
connection,
|
295
245
|
[attributes],
|
296
246
|
on_duplicate: :update,
|
297
247
|
unique_by: unique_index,
|
@@ -316,17 +266,14 @@ module Wal
|
|
316
266
|
base_class.connection.drop_table @table.table_name
|
317
267
|
end
|
318
268
|
|
319
|
-
sig { params(event: InsertEvent).void }
|
320
269
|
def on_insert(event)
|
321
270
|
@table.upsert(serialize(event))
|
322
271
|
end
|
323
272
|
|
324
|
-
sig { params(event: UpdateEvent).void }
|
325
273
|
def on_update(event)
|
326
274
|
@table.upsert(serialize(event), update_only: %w[new])
|
327
275
|
end
|
328
276
|
|
329
|
-
sig { params(event: DeleteEvent).void }
|
330
277
|
def on_delete(event)
|
331
278
|
case @table.where(table_name: event.table, primary_key: event.primary_key).pluck(:action, :old).first
|
332
279
|
in ["insert", _]
|
@@ -342,7 +289,6 @@ module Wal
|
|
342
289
|
|
343
290
|
private
|
344
291
|
|
345
|
-
sig { returns(T.class_of(::ActiveRecord::Base)) }
|
346
292
|
def base_class
|
347
293
|
self.class.base_active_record_class || ::ActiveRecord::Base
|
348
294
|
end
|
data/lib/wal/replicator.rb
CHANGED
@@ -1,21 +1,11 @@
|
|
1
|
-
# typed: false
|
2
|
-
|
3
1
|
require "ostruct"
|
4
2
|
|
5
3
|
module Wal
|
6
4
|
# Responsible to hook into a Postgres logical replication slot and stream the changes to a specific `Watcher`.
|
7
5
|
# Also it supports inject "contexts" into the replication events.
|
8
6
|
class Replicator
|
9
|
-
extend T::Sig
|
10
7
|
include PG::Replication::Protocol
|
11
8
|
|
12
|
-
sig do
|
13
|
-
params(
|
14
|
-
replication_slot: String,
|
15
|
-
use_temporary_slot: T::Boolean,
|
16
|
-
db_config: T::Hash[Symbol, T.untyped],
|
17
|
-
).void
|
18
|
-
end
|
19
9
|
def initialize(
|
20
10
|
replication_slot:,
|
21
11
|
use_temporary_slot: false,
|
@@ -26,7 +16,6 @@ module Wal
|
|
26
16
|
@use_temporary_slot = use_temporary_slot
|
27
17
|
end
|
28
18
|
|
29
|
-
sig { params(watcher: Watcher, publications: T::Array[String]).void }
|
30
19
|
def replicate_forever(watcher, publications:)
|
31
20
|
replication = replicate(watcher, publications:)
|
32
21
|
loop { replication.next }
|
@@ -34,7 +23,6 @@ module Wal
|
|
34
23
|
nil
|
35
24
|
end
|
36
25
|
|
37
|
-
sig { params(watcher: Watcher, publications: T::Array[String]).returns(T::Enumerator::Lazy[Event]) }
|
38
26
|
def replicate(watcher, publications:)
|
39
27
|
watch_conn = PG.connect(
|
40
28
|
dbname: @db_config[:database],
|
@@ -162,20 +150,13 @@ module Wal
|
|
162
150
|
end
|
163
151
|
end
|
164
152
|
|
165
|
-
class Column <
|
166
|
-
const :name, String
|
167
|
-
const :decoder, T.untyped
|
168
|
-
|
153
|
+
class Column < Data.define(:name, :decoder)
|
169
154
|
def decode(value)
|
170
155
|
decoder.deserialize(value)
|
171
156
|
end
|
172
157
|
end
|
173
158
|
|
174
|
-
class Table <
|
175
|
-
const :name, String
|
176
|
-
const :primary_key_colums, T::Array[String]
|
177
|
-
const :columns, T::Array[Column]
|
178
|
-
|
159
|
+
class Table < Data.define(:name, :primary_key_colums, :columns)
|
179
160
|
def primary_key(decoded_row)
|
180
161
|
case primary_key_colums
|
181
162
|
in [key]
|
@@ -1,5 +1,3 @@
|
|
1
|
-
# typed: true
|
2
|
-
|
3
1
|
module Wal
|
4
2
|
# A watcher that streams all the events of each WAL transaction on a separate thread.
|
5
3
|
#
|
@@ -12,7 +10,6 @@ module Wal
|
|
12
10
|
#
|
13
11
|
# ```ruby
|
14
12
|
# class RegisterDeletesWalWatcher < Wal::StreamingWalWatcher
|
15
|
-
# sig { override.params(events: T::Enumerator[Event]).void }
|
16
13
|
# def on_transaction_events(events)
|
17
14
|
# DeletedApplicationRecord.transaction do
|
18
15
|
# events
|
@@ -24,20 +21,14 @@ module Wal
|
|
24
21
|
# end
|
25
22
|
# ```
|
26
23
|
class StreamingWatcher
|
27
|
-
extend T::Sig
|
28
|
-
extend T::Helpers
|
29
24
|
include Wal::Watcher
|
30
|
-
abstract!
|
31
25
|
|
32
|
-
sig { abstract.params(events: T::Enumerator[Event]).void }
|
33
26
|
def on_transaction_events(events); end
|
34
27
|
|
35
|
-
sig { params(event: BeginTransactionEvent).returns(Integer) }
|
36
28
|
def queue_size(event)
|
37
29
|
5_000
|
38
30
|
end
|
39
31
|
|
40
|
-
sig { override.params(event: Event).void }
|
41
32
|
def on_event(event)
|
42
33
|
case event
|
43
34
|
when BeginTransactionEvent
|
data/lib/wal/version.rb
CHANGED
data/lib/wal/watcher.rb
CHANGED
@@ -1,5 +1,3 @@
|
|
1
|
-
# typed: strict
|
2
|
-
|
3
1
|
module Wal
|
4
2
|
# Watcher is the core API used to hook into Postgres WAL log.
|
5
3
|
# The only required method on the API is the `on_event`, which will receive a WAL entry of the following events:
|
@@ -13,22 +11,14 @@ module Wal
|
|
13
11
|
# desired. In practice, it is rarelly useful to implement this module directly for application level business logic,
|
14
12
|
# and instead it is more recomended using more specific ones, such as the `RecordWatcher` and `StreamingWalWatcher`.
|
15
13
|
module Watcher
|
16
|
-
extend T::Sig
|
17
|
-
extend T::Helpers
|
18
|
-
include Wal
|
19
|
-
abstract!
|
20
|
-
|
21
|
-
sig { abstract.params(event: Event).void }
|
22
14
|
def on_event(event); end
|
23
15
|
|
24
16
|
# Allows dropping the processing of any table
|
25
|
-
sig { params(table: String).returns(T::Boolean) }
|
26
17
|
def should_watch_table?(table)
|
27
18
|
true
|
28
19
|
end
|
29
20
|
|
30
21
|
# Check if the given context prefix should be allowed for this watcher
|
31
|
-
sig { params(prefix: String).returns(T::Boolean) }
|
32
22
|
def valid_context_prefix?(prefix)
|
33
23
|
true
|
34
24
|
end
|
@@ -42,25 +32,19 @@ module Wal
|
|
42
32
|
#
|
43
33
|
# ```ruby
|
44
34
|
# class MeasureTransactionTimeWatcher
|
45
|
-
# extend T::Sig
|
46
35
|
# include Wal::Watcher
|
47
36
|
# include Wal::Watcher::SeparatedEvents
|
48
37
|
#
|
49
|
-
# sig { params(event: BeginTransactionEvent).void }
|
50
38
|
# def on_begin(event)
|
51
39
|
# @start_time = Time.current
|
52
40
|
# end
|
53
41
|
#
|
54
|
-
# sig { params(event: CommitTransactionEvent).void }
|
55
42
|
# def on_commit(event)
|
56
43
|
# puts "Transaction processing time: #{Time.current - @start_time}"
|
57
44
|
# end
|
58
45
|
# end
|
59
46
|
# ```
|
60
47
|
module SeparatedEvents
|
61
|
-
extend T::Sig
|
62
|
-
|
63
|
-
sig { params(event: Event).void }
|
64
48
|
def on_event(event)
|
65
49
|
case event
|
66
50
|
when BeginTransactionEvent
|
@@ -76,19 +60,10 @@ module Wal
|
|
76
60
|
end
|
77
61
|
end
|
78
62
|
|
79
|
-
sig { params(event: BeginTransactionEvent).void }
|
80
63
|
def on_begin(event); end
|
81
|
-
|
82
|
-
sig { params(event: InsertEvent).void }
|
83
64
|
def on_insert(event); end
|
84
|
-
|
85
|
-
sig { params(event: UpdateEvent).void }
|
86
65
|
def on_update(event); end
|
87
|
-
|
88
|
-
sig { params(event: DeleteEvent).void }
|
89
66
|
def on_delete(event); end
|
90
|
-
|
91
|
-
sig { params(event: CommitTransactionEvent).void }
|
92
67
|
def on_commit(event); end
|
93
68
|
end
|
94
69
|
end
|
data/lib/wal.rb
CHANGED
@@ -1,5 +1,3 @@
|
|
1
|
-
# typed: strict
|
2
|
-
|
3
1
|
require "pg"
|
4
2
|
require "pg/replication"
|
5
3
|
require "active_support"
|
@@ -13,63 +11,34 @@ require_relative "wal/active_record_context_extension"
|
|
13
11
|
require_relative "wal/version"
|
14
12
|
|
15
13
|
module Wal
|
16
|
-
|
17
|
-
T.any(
|
18
|
-
BeginTransactionEvent,
|
19
|
-
CommitTransactionEvent,
|
20
|
-
InsertEvent,
|
21
|
-
UpdateEvent,
|
22
|
-
DeleteEvent,
|
23
|
-
)
|
24
|
-
end
|
25
|
-
|
26
|
-
class BeginTransactionEvent < T::Struct
|
27
|
-
extend T::Sig
|
28
|
-
|
29
|
-
const :transaction_id, Integer
|
30
|
-
const :lsn, Integer
|
31
|
-
const :final_lsn, Integer
|
32
|
-
const :timestamp, Time
|
33
|
-
|
34
|
-
sig { returns(Integer) }
|
14
|
+
class BeginTransactionEvent < Data.define(:transaction_id, :lsn, :final_lsn, :timestamp)
|
35
15
|
def estimated_size
|
36
16
|
final_lsn - lsn
|
37
17
|
end
|
38
18
|
end
|
39
19
|
|
40
|
-
class CommitTransactionEvent <
|
41
|
-
const :transaction_id, Integer
|
42
|
-
const :lsn, Integer
|
43
|
-
const :context, T::Hash[String, T.untyped]
|
44
|
-
const :timestamp, Time
|
20
|
+
class CommitTransactionEvent < Data.define(:transaction_id, :lsn, :context, :timestamp)
|
45
21
|
end
|
46
22
|
|
47
23
|
module ChangeEvent
|
48
|
-
extend T::Sig
|
49
|
-
|
50
|
-
sig { returns(T::Hash[String, [T.untyped, T.untyped]]) }
|
51
24
|
def diff
|
52
25
|
{}
|
53
26
|
end
|
54
27
|
|
55
|
-
sig { params(attribute: T.any(Symbol, String)).returns(T::Boolean) }
|
56
28
|
def changed_attribute?(attribute)
|
57
29
|
diff.key? attribute.to_s
|
58
30
|
end
|
59
31
|
|
60
|
-
sig { params(attribute: T.any(Symbol, String)).returns(T.untyped) }
|
61
32
|
def attribute(attribute)
|
62
33
|
if (changes = diff[attribute.to_s])
|
63
34
|
changes[1]
|
64
35
|
end
|
65
36
|
end
|
66
37
|
|
67
|
-
sig { params(attribute: T.any(Symbol, String)).returns(T.nilable([T.untyped, T.untyped])) }
|
68
38
|
def attribute_changes(attribute)
|
69
39
|
diff[attribute.to_s]
|
70
40
|
end
|
71
41
|
|
72
|
-
sig { params(attribute: T.any(Symbol, String)).returns(T.untyped) }
|
73
42
|
def attribute_was(attribute)
|
74
43
|
if (changes = diff[attribute.to_s])
|
75
44
|
changes[0]
|
@@ -77,36 +46,17 @@ module Wal
|
|
77
46
|
end
|
78
47
|
end
|
79
48
|
|
80
|
-
class InsertEvent <
|
81
|
-
extend T::Sig
|
49
|
+
class InsertEvent < Data.define(:transaction_id, :lsn, :context, :table, :primary_key, :new)
|
82
50
|
include ::Wal::ChangeEvent
|
83
51
|
|
84
|
-
const :transaction_id, Integer
|
85
|
-
const :lsn, Integer
|
86
|
-
const :context, T::Hash[String, T.untyped]
|
87
|
-
const :table, String
|
88
|
-
const :primary_key, T.untyped
|
89
|
-
const :new, T::Hash[String, T.untyped]
|
90
|
-
|
91
|
-
sig { returns(T::Hash[String, [T.untyped, T.untyped]]) }
|
92
52
|
def diff
|
93
53
|
new.transform_values { |val| [nil, val] }
|
94
54
|
end
|
95
55
|
end
|
96
56
|
|
97
|
-
class UpdateEvent <
|
98
|
-
extend T::Sig
|
57
|
+
class UpdateEvent < Data.define(:transaction_id, :lsn, :context, :table, :primary_key, :old, :new)
|
99
58
|
include ::Wal::ChangeEvent
|
100
59
|
|
101
|
-
const :transaction_id, Integer
|
102
|
-
const :lsn, Integer
|
103
|
-
const :context, T::Hash[String, T.untyped]
|
104
|
-
const :table, String
|
105
|
-
const :primary_key, T.untyped
|
106
|
-
const :old, T::Hash[String, T.untyped]
|
107
|
-
const :new, T::Hash[String, T.untyped]
|
108
|
-
|
109
|
-
sig { returns(T::Hash[String, [T.untyped, T.untyped]]) }
|
110
60
|
def diff
|
111
61
|
(old.keys | new.keys).reduce({}) do |diff, key|
|
112
62
|
old[key] != new[key] ? diff.merge(key => [old[key], new[key]]) : diff
|
@@ -114,18 +64,9 @@ module Wal
|
|
114
64
|
end
|
115
65
|
end
|
116
66
|
|
117
|
-
class DeleteEvent <
|
118
|
-
extend T::Sig
|
67
|
+
class DeleteEvent < Data.define(:transaction_id, :lsn, :context, :table, :primary_key, :old)
|
119
68
|
include ::Wal::ChangeEvent
|
120
69
|
|
121
|
-
const :transaction_id, Integer
|
122
|
-
const :lsn, Integer
|
123
|
-
const :context, T::Hash[String, T.untyped]
|
124
|
-
const :table, String
|
125
|
-
const :primary_key, T.untyped
|
126
|
-
const :old, T::Hash[String, T.untyped]
|
127
|
-
|
128
|
-
sig { returns(T::Hash[String, [T.untyped, T.untyped]]) }
|
129
70
|
def diff
|
130
71
|
old.transform_values { |val| [val, nil] }
|
131
72
|
end
|
data/rbi/wal.rbi
CHANGED
@@ -7,7 +7,7 @@ module Wal
|
|
7
7
|
UpdateEvent,
|
8
8
|
DeleteEvent,
|
9
9
|
) }
|
10
|
-
VERSION = "0.0.
|
10
|
+
VERSION = "0.0.3"
|
11
11
|
|
12
12
|
class BeginTransactionEvent < T::Struct
|
13
13
|
prop :transaction_id, Integer, immutable: true
|
@@ -141,66 +141,6 @@ module Wal
|
|
141
141
|
|
142
142
|
sig { override.params(event: Event).void }
|
143
143
|
def on_event(event); end
|
144
|
-
|
145
|
-
class MemoryRecordWatcher
|
146
|
-
include Wal::Watcher
|
147
|
-
include Wal::Watcher::SeparatedEvents
|
148
|
-
extend T::Sig
|
149
|
-
extend T::Helpers
|
150
|
-
RecordsStorage = T.type_alias { T::Hash[[String, Integer], T.nilable(RecordEvent)] }
|
151
|
-
|
152
|
-
sig { params(watcher: T.untyped).void }
|
153
|
-
def initialize(watcher); end
|
154
|
-
|
155
|
-
sig { params(event: BeginTransactionEvent).void }
|
156
|
-
def on_begin(event); end
|
157
|
-
|
158
|
-
sig { params(_event: T.untyped).returns(T.untyped) }
|
159
|
-
def on_commit(_event); end
|
160
|
-
|
161
|
-
sig { params(event: InsertEvent).void }
|
162
|
-
def on_insert(event); end
|
163
|
-
|
164
|
-
sig { params(event: UpdateEvent).void }
|
165
|
-
def on_update(event); end
|
166
|
-
|
167
|
-
sig { params(event: DeleteEvent).void }
|
168
|
-
def on_delete(event); end
|
169
|
-
end
|
170
|
-
|
171
|
-
class TemporaryTableRecordWatcher
|
172
|
-
include Wal::Watcher
|
173
|
-
include Wal::Watcher::SeparatedEvents
|
174
|
-
extend T::Sig
|
175
|
-
extend T::Helpers
|
176
|
-
|
177
|
-
sig { params(watcher: T.untyped, batch_size: T.untyped).void }
|
178
|
-
def initialize(watcher, batch_size: 5_000); end
|
179
|
-
|
180
|
-
sig { params(event: BeginTransactionEvent).void }
|
181
|
-
def on_begin(event); end
|
182
|
-
|
183
|
-
sig { params(_event: T.untyped).returns(T.untyped) }
|
184
|
-
def on_commit(_event); end
|
185
|
-
|
186
|
-
sig { params(event: InsertEvent).void }
|
187
|
-
def on_insert(event); end
|
188
|
-
|
189
|
-
sig { params(event: UpdateEvent).void }
|
190
|
-
def on_update(event); end
|
191
|
-
|
192
|
-
sig { params(event: DeleteEvent).void }
|
193
|
-
def on_delete(event); end
|
194
|
-
|
195
|
-
sig { returns(T.class_of(::ActiveRecord::Base)) }
|
196
|
-
def base_class; end
|
197
|
-
|
198
|
-
sig { params(event: T.untyped).returns(T.untyped) }
|
199
|
-
def serialize(event); end
|
200
|
-
|
201
|
-
sig { params(persisted_event: T.untyped).returns(T.untyped) }
|
202
|
-
def deserialize(persisted_event); end
|
203
|
-
end
|
204
144
|
end
|
205
145
|
|
206
146
|
class Replicator
|
@@ -215,26 +155,6 @@ module Wal
|
|
215
155
|
|
216
156
|
sig { params(watcher: Watcher, publications: T::Array[String]).returns(T::Enumerator::Lazy[Event]) }
|
217
157
|
def replicate(watcher, publications:); end
|
218
|
-
|
219
|
-
class Column < T::Struct
|
220
|
-
prop :name, String, immutable: true
|
221
|
-
prop :decoder, T.untyped, immutable: true
|
222
|
-
|
223
|
-
sig { params(value: T.untyped).returns(T.untyped) }
|
224
|
-
def decode(value); end
|
225
|
-
end
|
226
|
-
|
227
|
-
class Table < T::Struct
|
228
|
-
prop :name, String, immutable: true
|
229
|
-
prop :primary_key_colums, T::Array[String], immutable: true
|
230
|
-
prop :columns, T::Array[Column], immutable: true
|
231
|
-
|
232
|
-
sig { params(decoded_row: T.untyped).returns(T.untyped) }
|
233
|
-
def primary_key(decoded_row); end
|
234
|
-
|
235
|
-
sig { params(values: T.untyped).returns(T.untyped) }
|
236
|
-
def decode_row(values); end
|
237
|
-
end
|
238
158
|
end
|
239
159
|
|
240
160
|
class StreamingWatcher
|