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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a1a32b04410b404b4eb936eac7e3cae1548830c13cdbe7a1eb5e1604fb2bdabe
4
- data.tar.gz: b14c651c88ea56ef70b9674def89cfd774de17c1efaf9894544e6d5212061045
3
+ metadata.gz: 3a963632f6c43a96fee216f4f66e7ac5743f7b4374dec2a61bf5d5ab6c0111fe
4
+ data.tar.gz: 283c438cd2b5bc332483ba79a313fdbf5feda5134200f0478e8b283339fd1a68
5
5
  SHA512:
6
- metadata.gz: aa2a36f348bab0d37668e40909acefc4b101e38a8ee990ef217848087eaaa5b3473741e33238a5fff6106e660138a946451f42ba9db25ca069ee49117cc72973
7
- data.tar.gz: a965ef38835a5134b0ebd7f9a5eca4a54bf3786421f8365fc9110907088b797a7d2d8e86b14d2321ed37af4633df1f03ccf68b317d9209a0b43d94adc64606ee
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
- With coverage:
270
+ The test task generates line and branch coverage in `coverage/` and enforces
271
+ the configured minimums:
266
272
 
267
273
  ```bash
268
- COVERAGE=true bundle exec rake test
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 = relation.columns[index]
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
- private
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
@@ -3,6 +3,6 @@
3
3
  module Pgoutput
4
4
  class Decoder
5
5
  # Gem version.
6
- VERSION = "0.1.1"
6
+ VERSION = "0.2.0"
7
7
  end
8
8
  end
@@ -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
@@ -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
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pgoutput-decoder
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ken C. Demanawa