pgoutput-decoder 0.1.0 → 0.1.1
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 +22 -12
- data/README.md +6 -1
- data/lib/pgoutput/decoder/row_builder.rb +1 -1
- data/lib/pgoutput/decoder/type_registry.rb +6 -6
- data/lib/pgoutput/decoder/version.rb +1 -1
- data/sig/pgoutput_decoder.rbs +52 -6
- metadata +8 -93
- data/sig/pgoutput/decoder.rbs +0 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a1a32b04410b404b4eb936eac7e3cae1548830c13cdbe7a1eb5e1604fb2bdabe
|
|
4
|
+
data.tar.gz: b14c651c88ea56ef70b9674def89cfd774de17c1efaf9894544e6d5212061045
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: aa2a36f348bab0d37668e40909acefc4b101e38a8ee990ef217848087eaaa5b3473741e33238a5fff6106e660138a946451f42ba9db25ca069ee49117cc72973
|
|
7
|
+
data.tar.gz: a965ef38835a5134b0ebd7f9a5eca4a54bf3786421f8365fc9110907088b797a7d2d8e86b14d2321ed37af4633df1f03ccf68b317d9209a0b43d94adc64606ee
|
data/CHANGELOG.md
CHANGED
|
@@ -1,17 +1,32 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
## [Unreleased]
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
5
|
+
## 0.1.1 - 2026-06-17
|
|
7
6
|
|
|
8
|
-
|
|
7
|
+
### Fixed
|
|
9
8
|
|
|
10
|
-
|
|
9
|
+
* Curated RBS signatures shipped with the gem.
|
|
10
|
+
* Fixed duplicate `Pgoutput::Decoder` type declarations that caused Steep/RBS environment loading failures.
|
|
11
|
+
* Corrected decoder type signatures to align with actual runtime behavior.
|
|
12
|
+
* Improved `TypeRegistry` type annotations and value narrowing.
|
|
13
|
+
* Fixed Steep type-checking issues around decoder lookup, JSON decoding, UUID decoding, and numeric value decoding.
|
|
14
|
+
* Added missing standard library type dependencies required by Steep.
|
|
15
|
+
* Improved compatibility with downstream consumers using:
|
|
16
|
+
|
|
17
|
+
* `library "pgoutput-decoder"`
|
|
18
|
+
* `bundle exec steep check`
|
|
11
19
|
|
|
12
|
-
|
|
20
|
+
### Documentation
|
|
13
21
|
|
|
14
|
-
|
|
22
|
+
* Refined shipped type definitions to better reflect the public API surface.
|
|
23
|
+
|
|
24
|
+
### Internal
|
|
25
|
+
|
|
26
|
+
* No runtime behavior changes.
|
|
27
|
+
* No protocol decoding changes.
|
|
28
|
+
* No public API changes.
|
|
29
|
+
* This release focuses on RBS, Steep, and developer tooling correctness.
|
|
15
30
|
|
|
16
31
|
## [0.1.0] - 2026-06-01
|
|
17
32
|
|
|
@@ -32,8 +47,3 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
32
47
|
- Added RBS signatures.
|
|
33
48
|
- Added README documentation.
|
|
34
49
|
- Added CI and release workflow templates.
|
|
35
|
-
|
|
36
|
-
---
|
|
37
|
-
|
|
38
|
-
[Unreleased]: https://github.com/kanutocd/pgoutput-decoder/compare/v0.1.0...HEAD
|
|
39
|
-
[0.1.0]: https://github.com/kanutocd/pgoutput-decoder/releases/tag/v0.1.0
|
data/README.md
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# pgoutput-decoder
|
|
2
2
|
|
|
3
|
+
[](https://badge.fury.io/rb/pgoutput-decoder)
|
|
4
|
+
[](https://github.com/kanutocd/pgoutput-decoder/actions)
|
|
5
|
+
[](https://www.ruby-lang.org/en/)
|
|
6
|
+
[](https://opensource.org/licenses/MIT)
|
|
7
|
+
|
|
3
8
|
A high-level PostgreSQL `pgoutput` logical replication value decoder for Ruby.
|
|
4
9
|
|
|
5
10
|
`pgoutput-decoder` is the companion layer to [`pgoutput-parser`](https://rubygems.org/gems/pgoutput-parser). It accepts immutable protocol messages produced by `pgoutput-parser` and turns tuple payloads into application-friendly Ruby row-change events.
|
|
@@ -275,4 +280,4 @@ bundle exec steep check
|
|
|
275
280
|
|
|
276
281
|
## License
|
|
277
282
|
|
|
278
|
-
MIT.
|
|
283
|
+
[MIT](LICENSE.txt).
|
|
@@ -19,7 +19,7 @@ module Pgoutput
|
|
|
19
19
|
# @param tuple [Array<Pgoutput::Messages::TupleValue>]
|
|
20
20
|
# @return [Hash<String, Object>]
|
|
21
21
|
def build(relation, tuple)
|
|
22
|
-
row = {}
|
|
22
|
+
row = {} # : Hash[String, untyped]
|
|
23
23
|
|
|
24
24
|
tuple.each_with_index do |tuple_value, index|
|
|
25
25
|
column = relation.columns[index]
|
|
@@ -79,7 +79,7 @@ module Pgoutput
|
|
|
79
79
|
VARCHAR => ->(raw, _format) { raw.dup.freeze },
|
|
80
80
|
FLOAT4 => ->(raw, format) { decode_float(raw, format, 4, "g") },
|
|
81
81
|
FLOAT8 => ->(raw, format) { decode_float(raw, format, 8, "G") },
|
|
82
|
-
NUMERIC => ->(raw, format) { format == :text ? BigDecimal(raw) : raw.dup.freeze },
|
|
82
|
+
NUMERIC => ->(raw, format) { format == :text ? Kernel.BigDecimal(raw) : raw.dup.freeze },
|
|
83
83
|
JSON => ->(raw, format) { format == :text ? ::JSON.parse(raw) : raw.dup.freeze },
|
|
84
84
|
JSONB => ->(raw, format) { decode_jsonb(raw, format) },
|
|
85
85
|
UUID => ->(raw, format) { format == :text ? raw.dup.freeze : decode_uuid_binary(raw) },
|
|
@@ -107,7 +107,7 @@ module Pgoutput
|
|
|
107
107
|
def decode(oid, raw, format)
|
|
108
108
|
return nil if raw.nil?
|
|
109
109
|
|
|
110
|
-
decoder = @decoders[oid]
|
|
110
|
+
decoder = oid ? @decoders[oid] : nil
|
|
111
111
|
decoded = decoder ? decoder.call(raw, format) : raw.dup.freeze
|
|
112
112
|
Ractor.make_shareable(decoded)
|
|
113
113
|
end
|
|
@@ -139,14 +139,14 @@ module Pgoutput
|
|
|
139
139
|
return raw.to_i if format == :text
|
|
140
140
|
return raw.dup.freeze unless raw.bytesize == expected_length
|
|
141
141
|
|
|
142
|
-
raw.unpack1(template)
|
|
142
|
+
Integer(raw.unpack1(template))
|
|
143
143
|
end
|
|
144
144
|
|
|
145
145
|
def decode_float(raw, format, expected_length, template)
|
|
146
146
|
return Float(raw) if format == :text
|
|
147
147
|
return raw.dup.freeze unless raw.bytesize == expected_length
|
|
148
148
|
|
|
149
|
-
raw.unpack1(template)
|
|
149
|
+
Float(raw.unpack1(template))
|
|
150
150
|
end
|
|
151
151
|
|
|
152
152
|
def decode_jsonb(raw, format)
|
|
@@ -156,13 +156,13 @@ module Pgoutput
|
|
|
156
156
|
# current on-wire format; the remaining bytes contain JSON text.
|
|
157
157
|
return raw.dup.freeze unless raw.bytesize >= 2 && raw.getbyte(0) == 1
|
|
158
158
|
|
|
159
|
-
::JSON.parse(raw.byteslice(1..))
|
|
159
|
+
::JSON.parse(raw.byteslice(1..).to_s)
|
|
160
160
|
end
|
|
161
161
|
|
|
162
162
|
def decode_uuid_binary(raw)
|
|
163
163
|
return raw.dup.freeze unless raw.bytesize == 16
|
|
164
164
|
|
|
165
|
-
hex = raw.unpack1("H*")
|
|
165
|
+
hex = raw.unpack1("H*").to_s
|
|
166
166
|
"#{hex[0, 8]}-#{hex[8, 4]}-#{hex[12, 4]}-#{hex[16, 4]}-#{hex[20, 12]}".freeze
|
|
167
167
|
end
|
|
168
168
|
end
|
data/sig/pgoutput_decoder.rbs
CHANGED
|
@@ -2,6 +2,9 @@ module Pgoutput
|
|
|
2
2
|
class Decoder
|
|
3
3
|
VERSION: String
|
|
4
4
|
|
|
5
|
+
type parser_message = untyped
|
|
6
|
+
type relation = untyped
|
|
7
|
+
type tuple = Array[untyped]
|
|
5
8
|
type event =
|
|
6
9
|
Events::Begin |
|
|
7
10
|
Events::Commit |
|
|
@@ -9,10 +12,31 @@ module Pgoutput
|
|
|
9
12
|
Events::Update |
|
|
10
13
|
Events::Delete
|
|
11
14
|
|
|
15
|
+
@type_registry: TypeRegistry
|
|
16
|
+
@relations: RelationCache
|
|
17
|
+
@row_builder: RowBuilder
|
|
18
|
+
@current_transaction_id: Integer?
|
|
19
|
+
@current_final_lsn: Integer?
|
|
20
|
+
@current_commit_timestamp: Integer?
|
|
21
|
+
|
|
12
22
|
def initialize: (?type_registry: TypeRegistry) -> void
|
|
13
|
-
def decode: (
|
|
23
|
+
def decode: (parser_message message) -> event?
|
|
14
24
|
def type_registry: () -> TypeRegistry
|
|
15
25
|
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
def decode_begin: (parser_message message) -> Events::Begin
|
|
29
|
+
def decode_commit: (parser_message message) -> Events::Commit
|
|
30
|
+
def decode_insert: (parser_message message) -> Events::Insert
|
|
31
|
+
def decode_update: (parser_message message) -> Events::Update
|
|
32
|
+
def decode_delete: (parser_message message) -> Events::Delete
|
|
33
|
+
def optional_row: (relation relation, tuple? tuple) -> Hash[String, untyped]?
|
|
34
|
+
def relation_for: (Integer relation_id) -> relation
|
|
35
|
+
def require_transaction_id: () -> Integer
|
|
36
|
+
def clear_transaction!: () -> nil
|
|
37
|
+
def parser_messages: () -> untyped
|
|
38
|
+
def share: [T] (T object) -> T
|
|
39
|
+
|
|
16
40
|
class Error < StandardError
|
|
17
41
|
end
|
|
18
42
|
|
|
@@ -93,27 +117,49 @@ module Pgoutput
|
|
|
93
117
|
UUID: Integer
|
|
94
118
|
JSONB: Integer
|
|
95
119
|
|
|
120
|
+
type decoder = ^(String raw, Symbol format) -> untyped
|
|
121
|
+
|
|
122
|
+
@decoders: Hash[Integer, decoder]
|
|
123
|
+
|
|
96
124
|
def self.default: () -> TypeRegistry
|
|
97
|
-
def self.default_decoders: () -> Hash[Integer,
|
|
98
|
-
def initialize: (?Hash[Integer,
|
|
125
|
+
def self.default_decoders: () -> Hash[Integer, decoder]
|
|
126
|
+
def initialize: (?Hash[Integer, decoder] decoders) -> void
|
|
99
127
|
def decode: (Integer? oid, String? raw, Symbol format) -> untyped?
|
|
100
128
|
def with_decoder: (Integer oid) { (String raw, Symbol format) -> untyped } -> TypeRegistry
|
|
129
|
+
|
|
130
|
+
private
|
|
131
|
+
|
|
132
|
+
def self.decode_bool: (String raw, Symbol format) -> bool
|
|
133
|
+
def self.decode_int: (String raw, Symbol format, Integer expected_length, String template) -> (Integer | String)
|
|
134
|
+
def self.decode_float: (String raw, Symbol format, Integer expected_length, String template) -> (Float | String)
|
|
135
|
+
def self.decode_jsonb: (String raw, Symbol format) -> untyped
|
|
136
|
+
def self.decode_uuid_binary: (String raw) -> String
|
|
101
137
|
end
|
|
102
138
|
|
|
103
139
|
class ValueDecoder
|
|
140
|
+
@type_registry: TypeRegistry
|
|
141
|
+
|
|
104
142
|
def initialize: (?type_registry: TypeRegistry) -> void
|
|
105
143
|
def decode: (untyped tuple_value) -> untyped?
|
|
106
144
|
end
|
|
107
145
|
|
|
108
146
|
class RelationCache
|
|
147
|
+
@relations: Hash[Integer, relation]
|
|
148
|
+
|
|
109
149
|
def initialize: () -> void
|
|
110
|
-
def store: (
|
|
111
|
-
def fetch: (Integer relation_id) ->
|
|
150
|
+
def store: (relation relation) -> relation
|
|
151
|
+
def fetch: (Integer relation_id) -> relation
|
|
112
152
|
end
|
|
113
153
|
|
|
114
154
|
class RowBuilder
|
|
155
|
+
@value_decoder: ValueDecoder
|
|
156
|
+
|
|
115
157
|
def initialize: (?type_registry: TypeRegistry) -> void
|
|
116
|
-
def build: (
|
|
158
|
+
def build: (relation relation, tuple tuple) -> Hash[String, untyped]
|
|
159
|
+
|
|
160
|
+
private
|
|
161
|
+
|
|
162
|
+
def normalize_oid: (untyped tuple_value, Integer oid) -> untyped
|
|
117
163
|
end
|
|
118
164
|
end
|
|
119
165
|
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.
|
|
4
|
+
version: 0.1.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ken C. Demanawa
|
|
@@ -10,117 +10,33 @@ cert_chain: []
|
|
|
10
10
|
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
|
-
name:
|
|
13
|
+
name: bigdecimal
|
|
14
14
|
requirement: !ruby/object:Gem::Requirement
|
|
15
15
|
requirements:
|
|
16
16
|
- - "~>"
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
|
-
version: '
|
|
18
|
+
version: '4.1'
|
|
19
19
|
type: :runtime
|
|
20
20
|
prerelease: false
|
|
21
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
22
22
|
requirements:
|
|
23
23
|
- - "~>"
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
|
-
version: '
|
|
26
|
-
- !ruby/object:Gem::Dependency
|
|
27
|
-
name: minitest
|
|
28
|
-
requirement: !ruby/object:Gem::Requirement
|
|
29
|
-
requirements:
|
|
30
|
-
- - "~>"
|
|
31
|
-
- !ruby/object:Gem::Version
|
|
32
|
-
version: '5.27'
|
|
33
|
-
type: :development
|
|
34
|
-
prerelease: false
|
|
35
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
-
requirements:
|
|
37
|
-
- - "~>"
|
|
38
|
-
- !ruby/object:Gem::Version
|
|
39
|
-
version: '5.27'
|
|
40
|
-
- !ruby/object:Gem::Dependency
|
|
41
|
-
name: pry
|
|
42
|
-
requirement: !ruby/object:Gem::Requirement
|
|
43
|
-
requirements:
|
|
44
|
-
- - "~>"
|
|
45
|
-
- !ruby/object:Gem::Version
|
|
46
|
-
version: 0.16.0
|
|
47
|
-
type: :development
|
|
48
|
-
prerelease: false
|
|
49
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
-
requirements:
|
|
51
|
-
- - "~>"
|
|
52
|
-
- !ruby/object:Gem::Version
|
|
53
|
-
version: 0.16.0
|
|
25
|
+
version: '4.1'
|
|
54
26
|
- !ruby/object:Gem::Dependency
|
|
55
|
-
name:
|
|
56
|
-
requirement: !ruby/object:Gem::Requirement
|
|
57
|
-
requirements:
|
|
58
|
-
- - "~>"
|
|
59
|
-
- !ruby/object:Gem::Version
|
|
60
|
-
version: '13.4'
|
|
61
|
-
type: :development
|
|
62
|
-
prerelease: false
|
|
63
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
64
|
-
requirements:
|
|
65
|
-
- - "~>"
|
|
66
|
-
- !ruby/object:Gem::Version
|
|
67
|
-
version: '13.4'
|
|
68
|
-
- !ruby/object:Gem::Dependency
|
|
69
|
-
name: rubocop
|
|
70
|
-
requirement: !ruby/object:Gem::Requirement
|
|
71
|
-
requirements:
|
|
72
|
-
- - "~>"
|
|
73
|
-
- !ruby/object:Gem::Version
|
|
74
|
-
version: '1.87'
|
|
75
|
-
type: :development
|
|
76
|
-
prerelease: false
|
|
77
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
78
|
-
requirements:
|
|
79
|
-
- - "~>"
|
|
80
|
-
- !ruby/object:Gem::Version
|
|
81
|
-
version: '1.87'
|
|
82
|
-
- !ruby/object:Gem::Dependency
|
|
83
|
-
name: simplecov
|
|
84
|
-
requirement: !ruby/object:Gem::Requirement
|
|
85
|
-
requirements:
|
|
86
|
-
- - "~>"
|
|
87
|
-
- !ruby/object:Gem::Version
|
|
88
|
-
version: 0.22.0
|
|
89
|
-
type: :development
|
|
90
|
-
prerelease: false
|
|
91
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
92
|
-
requirements:
|
|
93
|
-
- - "~>"
|
|
94
|
-
- !ruby/object:Gem::Version
|
|
95
|
-
version: 0.22.0
|
|
96
|
-
- !ruby/object:Gem::Dependency
|
|
97
|
-
name: steep
|
|
98
|
-
requirement: !ruby/object:Gem::Requirement
|
|
99
|
-
requirements:
|
|
100
|
-
- - "~>"
|
|
101
|
-
- !ruby/object:Gem::Version
|
|
102
|
-
version: '1.10'
|
|
103
|
-
type: :development
|
|
104
|
-
prerelease: false
|
|
105
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
106
|
-
requirements:
|
|
107
|
-
- - "~>"
|
|
108
|
-
- !ruby/object:Gem::Version
|
|
109
|
-
version: '1.10'
|
|
110
|
-
- !ruby/object:Gem::Dependency
|
|
111
|
-
name: yard
|
|
27
|
+
name: pgoutput-parser
|
|
112
28
|
requirement: !ruby/object:Gem::Requirement
|
|
113
29
|
requirements:
|
|
114
30
|
- - "~>"
|
|
115
31
|
- !ruby/object:Gem::Version
|
|
116
|
-
version: 0.
|
|
117
|
-
type: :
|
|
32
|
+
version: '0.1'
|
|
33
|
+
type: :runtime
|
|
118
34
|
prerelease: false
|
|
119
35
|
version_requirements: !ruby/object:Gem::Requirement
|
|
120
36
|
requirements:
|
|
121
37
|
- - "~>"
|
|
122
38
|
- !ruby/object:Gem::Version
|
|
123
|
-
version: 0.
|
|
39
|
+
version: '0.1'
|
|
124
40
|
description: Decodes pgoutput-parser protocol messages into immutable Ruby row-change
|
|
125
41
|
events.
|
|
126
42
|
email:
|
|
@@ -141,7 +57,6 @@ files:
|
|
|
141
57
|
- lib/pgoutput/decoder/value_decoder.rb
|
|
142
58
|
- lib/pgoutput/decoder/version.rb
|
|
143
59
|
- lib/pgoutput_decoder.rb
|
|
144
|
-
- sig/pgoutput/decoder.rbs
|
|
145
60
|
- sig/pgoutput_decoder.rbs
|
|
146
61
|
homepage: https://github.com/kanutocd/pgoutput-decoder
|
|
147
62
|
licenses:
|