pgoutput-decoder 0.1.1 → 0.2.0
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/CHANGELOG.md +14 -0
- data/README.md +8 -2
- data/lib/pgoutput/decoder/row_builder.rb +27 -2
- data/lib/pgoutput/decoder/type_registry.rb +3 -0
- data/lib/pgoutput/decoder/version.rb +1 -1
- data/lib/pgoutput/decoder.rb +4 -3
- data/sig/pgoutput_decoder.rbs +4 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3a963632f6c43a96fee216f4f66e7ac5743f7b4374dec2a61bf5d5ab6c0111fe
|
|
4
|
+
data.tar.gz: 283c438cd2b5bc332483ba79a313fdbf5feda5134200f0478e8b283339fd1a68
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 05d855963feccb5872525b81318f80b58152153e344abeade63b20ede94e76e2c71932566e1ae3b8213695e2946a6b5cbb54df45e947745c0885b0d063feb7df
|
|
7
|
+
data.tar.gz: 07e90807168a43c177e448b47005b6e668d6bd1bee18b0f5e0665ed70f245d9734a22fee40a82f0162731b28c8f16ea38eecf34bdbec13c6a776ed3bc77bfff3
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## 0.2.0 - 2026-07-17
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
* Decode UPDATE and DELETE old-key tuples using relation key-column flags.
|
|
10
|
+
* Preserve composite and non-`id` keys while omitting non-key placeholders from
|
|
11
|
+
full-width PostgreSQL tuples.
|
|
12
|
+
* Restored SimpleCov result finalization, HTML report generation, and minimum
|
|
13
|
+
line/branch coverage enforcement.
|
|
14
|
+
|
|
15
|
+
### Documentation
|
|
16
|
+
|
|
17
|
+
* Documented exact replica-key tuple behavior.
|
|
18
|
+
|
|
5
19
|
## 0.1.1 - 2026-06-17
|
|
6
20
|
|
|
7
21
|
### Fixed
|
data/README.md
CHANGED
|
@@ -239,6 +239,11 @@ delete.old_values
|
|
|
239
239
|
# => { ... } or nil
|
|
240
240
|
```
|
|
241
241
|
|
|
242
|
+
`old_key` contains only columns marked as replica-key columns by the preceding
|
|
243
|
+
`Relation` message. Composite and non-`id` identities retain their declared
|
|
244
|
+
column order; full-width tuples with non-key placeholders do not leak those
|
|
245
|
+
placeholders into the decoded key.
|
|
246
|
+
|
|
242
247
|
---
|
|
243
248
|
|
|
244
249
|
## Ractor Safety
|
|
@@ -262,10 +267,11 @@ The decoder instance itself is stateful and should not be shared across Ractors.
|
|
|
262
267
|
bundle exec rake test
|
|
263
268
|
```
|
|
264
269
|
|
|
265
|
-
|
|
270
|
+
The test task generates line and branch coverage in `coverage/` and enforces
|
|
271
|
+
the configured minimums:
|
|
266
272
|
|
|
267
273
|
```bash
|
|
268
|
-
|
|
274
|
+
bundle exec rake test
|
|
269
275
|
```
|
|
270
276
|
|
|
271
277
|
---
|
|
@@ -19,11 +19,34 @@ module Pgoutput
|
|
|
19
19
|
# @param tuple [Array<Pgoutput::Messages::TupleValue>]
|
|
20
20
|
# @return [Hash<String, Object>]
|
|
21
21
|
def build(relation, tuple)
|
|
22
|
+
build_columns(relation.columns, tuple)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Build a decoded replica-key hash.
|
|
26
|
+
#
|
|
27
|
+
# PostgreSQL may encode an old-key tuple at full relation width with
|
|
28
|
+
# placeholders for non-key columns, while compact protocol fixtures may
|
|
29
|
+
# contain only key values. Relation flags determine the exact key columns
|
|
30
|
+
# in both representations.
|
|
31
|
+
#
|
|
32
|
+
# @param relation [Pgoutput::Messages::Relation]
|
|
33
|
+
# @param tuple [Array<Pgoutput::Messages::TupleValue>]
|
|
34
|
+
# @return [Hash<String, Object>]
|
|
35
|
+
def build_key(relation, tuple)
|
|
36
|
+
key_columns = relation.columns.select { |column| key_column?(column) }
|
|
37
|
+
columns = tuple.length == relation.columns.length ? relation.columns : key_columns
|
|
38
|
+
build_columns(columns, tuple, key_only: true)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
private
|
|
42
|
+
|
|
43
|
+
def build_columns(columns, tuple, key_only: false)
|
|
22
44
|
row = {} # : Hash[String, untyped]
|
|
23
45
|
|
|
24
46
|
tuple.each_with_index do |tuple_value, index|
|
|
25
|
-
column =
|
|
47
|
+
column = columns[index]
|
|
26
48
|
next unless column
|
|
49
|
+
next if key_only && !key_column?(column)
|
|
27
50
|
|
|
28
51
|
normalized_value = normalize_oid(tuple_value, column.oid)
|
|
29
52
|
row[column.name] = @value_decoder.decode(normalized_value)
|
|
@@ -32,7 +55,9 @@ module Pgoutput
|
|
|
32
55
|
Ractor.make_shareable(row.freeze)
|
|
33
56
|
end
|
|
34
57
|
|
|
35
|
-
|
|
58
|
+
def key_column?(column)
|
|
59
|
+
column.flags.allbits?(1)
|
|
60
|
+
end
|
|
36
61
|
|
|
37
62
|
def normalize_oid(tuple_value, oid)
|
|
38
63
|
return tuple_value unless tuple_value.oid.nil?
|
|
@@ -129,11 +129,14 @@ module Pgoutput
|
|
|
129
129
|
class << self
|
|
130
130
|
private
|
|
131
131
|
|
|
132
|
+
# This decodes a PostgreSQL boolean value; it is not an object predicate.
|
|
133
|
+
# rubocop:disable Naming/PredicateMethod
|
|
132
134
|
def decode_bool(raw, format)
|
|
133
135
|
return raw == "t" if format == :text
|
|
134
136
|
|
|
135
137
|
raw.unpack1("C") == 1
|
|
136
138
|
end
|
|
139
|
+
# rubocop:enable Naming/PredicateMethod
|
|
137
140
|
|
|
138
141
|
def decode_int(raw, format, expected_length, template)
|
|
139
142
|
return raw.to_i if format == :text
|
data/lib/pgoutput/decoder.rb
CHANGED
|
@@ -120,7 +120,7 @@ module Pgoutput
|
|
|
120
120
|
message.relation_id,
|
|
121
121
|
relation.schema,
|
|
122
122
|
relation.table,
|
|
123
|
-
optional_row(relation, message.old_key_tuple),
|
|
123
|
+
optional_row(relation, message.old_key_tuple, key: true),
|
|
124
124
|
optional_row(relation, message.old_tuple),
|
|
125
125
|
@row_builder.build(relation, message.new_tuple)
|
|
126
126
|
)
|
|
@@ -137,14 +137,15 @@ module Pgoutput
|
|
|
137
137
|
message.relation_id,
|
|
138
138
|
relation.schema,
|
|
139
139
|
relation.table,
|
|
140
|
-
optional_row(relation, message.old_key_tuple),
|
|
140
|
+
optional_row(relation, message.old_key_tuple, key: true),
|
|
141
141
|
optional_row(relation, message.old_tuple)
|
|
142
142
|
)
|
|
143
143
|
)
|
|
144
144
|
end
|
|
145
145
|
|
|
146
|
-
def optional_row(relation, tuple)
|
|
146
|
+
def optional_row(relation, tuple, key: false)
|
|
147
147
|
return nil if tuple.nil?
|
|
148
|
+
return @row_builder.build_key(relation, tuple) if key
|
|
148
149
|
|
|
149
150
|
@row_builder.build(relation, tuple)
|
|
150
151
|
end
|
data/sig/pgoutput_decoder.rbs
CHANGED
|
@@ -30,7 +30,7 @@ module Pgoutput
|
|
|
30
30
|
def decode_insert: (parser_message message) -> Events::Insert
|
|
31
31
|
def decode_update: (parser_message message) -> Events::Update
|
|
32
32
|
def decode_delete: (parser_message message) -> Events::Delete
|
|
33
|
-
def optional_row: (relation relation, tuple? tuple) -> Hash[String, untyped]?
|
|
33
|
+
def optional_row: (relation relation, tuple? tuple, ?key: bool) -> Hash[String, untyped]?
|
|
34
34
|
def relation_for: (Integer relation_id) -> relation
|
|
35
35
|
def require_transaction_id: () -> Integer
|
|
36
36
|
def clear_transaction!: () -> nil
|
|
@@ -156,9 +156,12 @@ module Pgoutput
|
|
|
156
156
|
|
|
157
157
|
def initialize: (?type_registry: TypeRegistry) -> void
|
|
158
158
|
def build: (relation relation, tuple tuple) -> Hash[String, untyped]
|
|
159
|
+
def build_key: (relation relation, tuple tuple) -> Hash[String, untyped]
|
|
159
160
|
|
|
160
161
|
private
|
|
161
162
|
|
|
163
|
+
def build_columns: (Array[untyped] columns, tuple tuple, ?key_only: bool) -> Hash[String, untyped]
|
|
164
|
+
def key_column?: (untyped column) -> bool
|
|
162
165
|
def normalize_oid: (untyped tuple_value, Integer oid) -> untyped
|
|
163
166
|
end
|
|
164
167
|
end
|