pgmq-ruby 0.6.1 → 0.7.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.
@@ -26,6 +26,92 @@ module PGMQ
26
26
  # Default connection pool timeout in seconds
27
27
  DEFAULT_POOL_TIMEOUT = 5
28
28
 
29
+ class << self
30
+ # Additional error message patterns (String or Regexp) that mean the connection is dead and a retry on a fresh
31
+ # socket is safe. Strings are matched as case-insensitive substrings; Regexps match the original message. The
32
+ # built-in LOST_CONNECTION_MESSAGES are always checked first - this list is appended to them.
33
+ #
34
+ # Thread-safe: reads are lock-free (frozen array swap); writes should be done at boot time before forking workers.
35
+ #
36
+ # @return [Array<String, Regexp>]
37
+ # @example
38
+ # PGMQ::Connection.reconnectable_error_patterns << "connection reset by peer"
39
+ # PGMQ::Connection.reconnectable_error_patterns << /\Abroken pipe\b/i
40
+ attr_reader :reconnectable_error_patterns
41
+
42
+ # Additional exception classes that mean the connection is dead. `PG::ConnectionBad` and `PG::UnableToSend` are
43
+ # always matched - this list is appended to them. Subclasses also match.
44
+ #
45
+ # Thread-safe: reads are lock-free; writes should be done at boot time.
46
+ #
47
+ # @return [Array<Class>]
48
+ # @example
49
+ # PGMQ::Connection.reconnectable_error_classes << PG::ConnectionRefused
50
+ attr_reader :reconnectable_error_classes
51
+
52
+ # Replaces the extra reconnectable error patterns.
53
+ #
54
+ # @param patterns [Array<String, Regexp>]
55
+ # @raise [PGMQ::Errors::ConfigurationError] if any element is invalid
56
+ def reconnectable_error_patterns=(patterns)
57
+ @reconnectable_error_patterns = normalize_patterns(patterns)
58
+ end
59
+
60
+ # Replaces the extra reconnectable error classes.
61
+ #
62
+ # @param classes [Array<Class>]
63
+ # @raise [PGMQ::Errors::ConfigurationError] if any element is invalid
64
+ def reconnectable_error_classes=(classes)
65
+ @reconnectable_error_classes = normalize_classes(classes)
66
+ end
67
+
68
+ private
69
+
70
+ # Normalizes user-supplied reconnectable error patterns.
71
+ #
72
+ # Strings are downcased once at configuration time so the hot path (`connection_lost_error?`) only does substring
73
+ # checks. Regexps are passed through unchanged.
74
+ #
75
+ # @param patterns [Array<String, Regexp>, String, Regexp, nil]
76
+ # @return [Array<String, Regexp>]
77
+ def normalize_patterns(patterns)
78
+ Array(patterns).map do |pattern|
79
+ case pattern
80
+ when Regexp
81
+ pattern
82
+ when String
83
+ pattern.downcase
84
+ else
85
+ raise(
86
+ PGMQ::Errors::ConfigurationError,
87
+ "reconnectable_error_patterns must contain Strings or Regexps, got #{pattern.class}"
88
+ )
89
+ end
90
+ end.freeze
91
+ end
92
+
93
+ # Normalizes user-supplied reconnectable error classes.
94
+ #
95
+ # @param classes [Array<Class>, Class, nil]
96
+ # @return [Array<Class>]
97
+ def normalize_classes(classes)
98
+ Array(classes).map do |klass|
99
+ unless klass.is_a?(Class) && klass <= Exception
100
+ raise(
101
+ PGMQ::Errors::ConfigurationError,
102
+ "reconnectable_error_classes must contain Exception subclasses, got #{klass.inspect}"
103
+ )
104
+ end
105
+
106
+ klass
107
+ end.freeze
108
+ end
109
+ end
110
+
111
+ # Initialize class-level defaults (empty arrays, users append at boot)
112
+ @reconnectable_error_patterns = [].freeze
113
+ @reconnectable_error_classes = [].freeze
114
+
29
115
  # @return [ConnectionPool] the connection pool
30
116
  attr_reader :pool
31
117
 
@@ -107,37 +193,58 @@ module PGMQ
107
193
 
108
194
  private
109
195
 
110
- # Checks if the error indicates a lost connection
196
+ # Messages libpq raises when the server/pooler has already torn down the socket. The list has grown organically with
197
+ # each pooler/TLS variant we see in the wild; the class check below catches future variants that libpq raises as
198
+ # `PG::ConnectionBad` or `PG::UnableToSend` without waiting for a new message to hit production.
199
+ LOST_CONNECTION_MESSAGES = [
200
+ "server closed the connection",
201
+ "connection not open",
202
+ "connection is closed",
203
+ "connection has been closed",
204
+ "no connection to the server",
205
+ "terminating connection",
206
+ "connection to server was lost",
207
+ "could not receive data from server",
208
+ "pqsocket() can't get socket descriptor",
209
+ "ssl error: unexpected eof",
210
+ "ssl syscall error"
211
+ ].freeze
212
+ private_constant :LOST_CONNECTION_MESSAGES
213
+
214
+ # Checks if the error indicates a lost connection.
215
+ #
216
+ # Matches in three steps: first by class (`PG::ConnectionBad` / `PG::UnableToSend` are dedicated connection-failure
217
+ # classes libpq raises regardless of message, plus any user-supplied classes), then by built-in message substrings
218
+ # for the bare `PG::Error` cases where libpq doesn't reach for the specific subclass, and finally by user-supplied
219
+ # patterns (strings matched as case-insensitive substrings, Regexps matched against the original message).
220
+ #
111
221
  # @param error [PG::Error] the error to check
112
- # @return [Boolean] true if connection was lost
222
+ # @return [Boolean] true if the connection was lost and a retry on a
223
+ # fresh connection is appropriate
113
224
  def connection_lost_error?(error)
114
- # Common connection lost errors. Include the pg-gem C-extension message
115
- # ("PQsocket() can't get socket descriptor") that is raised when the
116
- # cached libpq socket descriptor is gone — e.g. after a server-side
117
- # close by a connection pooler such as PgBouncer.
118
- lost_connection_messages = [
119
- "server closed the connection",
120
- "connection not open",
121
- "connection is closed",
122
- "connection has been closed",
123
- "no connection to the server",
124
- "terminating connection",
125
- "connection to server was lost",
126
- "could not receive data from server",
127
- "pqsocket() can't get socket descriptor"
128
- ]
129
-
130
- message = error.message.to_s.downcase
131
- lost_connection_messages.any? { |pattern| message.include?(pattern) }
225
+ return true if error.is_a?(PG::ConnectionBad) || error.is_a?(PG::UnableToSend)
226
+
227
+ extra_classes = self.class.reconnectable_error_classes
228
+ return true if extra_classes.any? { |klass| error.is_a?(klass) }
229
+
230
+ original_message = error.message.to_s
231
+ downcased = original_message.downcase
232
+
233
+ return true if LOST_CONNECTION_MESSAGES.any? { |pattern| downcased.include?(pattern) }
234
+
235
+ self.class.reconnectable_error_patterns.any? do |pattern|
236
+ case pattern
237
+ when Regexp then pattern.match?(original_message)
238
+ else downcased.include?(pattern)
239
+ end
240
+ end
132
241
  end
133
242
 
134
243
  # Verifies a connection is alive and working.
135
244
  #
136
- # Also resets when the connection reports `PG::CONNECTION_BAD`, which
137
- # happens when the server (or an intermediate pooler such as PgBouncer)
138
- # has closed the socket while the client-side `PG::Connection` object
139
- # still exists. `#finished?` alone only catches connections closed
140
- # explicitly from the client side.
245
+ # Also resets when the connection reports `PG::CONNECTION_BAD`, which happens when the server (or an intermediate
246
+ # pooler such as PgBouncer) has closed the socket while the client-side `PG::Connection` object still exists.
247
+ # `#finished?` alone only catches connections closed explicitly from the client side.
141
248
  #
142
249
  # @param conn [PG::Connection] connection to verify
143
250
  # @raise [PG::Error] if the reset itself fails
@@ -187,16 +294,15 @@ module PGMQ
187
294
  ConnectionPool.new(size: @pool_size, timeout: @pool_timeout) do
188
295
  conn = create_connection(params)
189
296
 
190
- # Detect shared connections: if a callable returns the same PG::Connection
191
- # object to multiple pool slots, concurrent use will corrupt libpq state
192
- # (nil results, segfaults, wrong data). Fail fast with a clear message.
297
+ # Detect shared connections: if a callable returns the same PG::Connection object to multiple pool slots,
298
+ # concurrent use will corrupt libpq state (nil results, segfaults, wrong data). Fail fast with a clear message.
193
299
  if conn.is_a?(PG::Connection)
194
300
  seen_mutex.synchronize do
195
301
  if seen_connections.key?(conn)
196
302
  raise PGMQ::Errors::ConfigurationError,
197
303
  "Connection callable returned the same PG::Connection object " \
198
304
  "(object_id: #{conn.object_id}) to multiple pool slots. " \
199
- "PG::Connection is NOT thread-safe concurrent use causes nil results, " \
305
+ "PG::Connection is NOT thread-safe - concurrent use causes nil results, " \
200
306
  "segfaults, and data corruption. Ensure your callable returns a unique " \
201
307
  "PG::Connection instance on each invocation (for example, by calling " \
202
308
  "PG.connect inside the callable)."
@@ -219,10 +325,10 @@ module PGMQ
219
325
  # If we have a callable (e.g., for Rails), call it to get the connection
220
326
  return params.call if params.respond_to?(:call)
221
327
 
222
- # Create new connection from parameters
223
- # Low-level library: return all values as strings from PostgreSQL
224
- # No automatic type conversion - let higher-level frameworks handle parsing
225
- # conn.type_map_for_results intentionally NOT set
328
+ # Create new connection from parameters.
329
+ # Low-level library: return all values as strings from PostgreSQL.
330
+ # No automatic type conversion - let higher-level frameworks handle parsing.
331
+ # conn.type_map_for_results intentionally NOT set.
226
332
  PG.connect(params[:conninfo] || params)
227
333
  rescue PG::Error => e
228
334
  raise PGMQ::Errors::ConnectionError, "Failed to connect to database: #{e.message}"
data/lib/pgmq/message.rb CHANGED
@@ -3,8 +3,8 @@
3
3
  module PGMQ
4
4
  # Represents a message read from a PGMQ queue
5
5
  #
6
- # Returns raw values from PostgreSQL without transformation.
7
- # Higher-level frameworks should handle parsing, deserialization, etc.
6
+ # Returns raw values from PostgreSQL without transformation. Higher-level frameworks should handle parsing,
7
+ # deserialization, etc.
8
8
  #
9
9
  # @example Reading a message (raw values)
10
10
  # msg = client.read("my_queue", vt: 30)
@@ -24,9 +24,8 @@ module PGMQ
24
24
  # @param row [Hash] database row from PG result
25
25
  # @return [Message]
26
26
  def new(row, **)
27
- # Return raw values as-is from PostgreSQL
28
- # No parsing, no deserialization, no transformation
29
- # The pg gem returns JSONB as String by default
27
+ # Return raw values as-is from PostgreSQL. No parsing, no deserialization, no transformation.
28
+ # The pg gem returns JSONB as String by default.
30
29
  super(
31
30
  msg_id: row["msg_id"],
32
31
  read_ct: row["read_ct"],
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PGMQ
4
+ # Represents the NOTIFY throttle configuration for a queue
5
+ #
6
+ # @example Inspecting notification configuration
7
+ # throttles = client.list_notify_insert_throttles
8
+ # throttles.each do |t|
9
+ # puts "#{t.queue_name}: #{t.throttle_interval_ms}ms (last notified: #{t.last_notified_at})"
10
+ # end
11
+ class NotifyThrottle < Data.define(:queue_name, :throttle_interval_ms, :last_notified_at)
12
+ class << self
13
+ # Creates a new NotifyThrottle from a database row
14
+ # @param row [Hash] database row from PG result
15
+ # @return [NotifyThrottle]
16
+ def new(row, **)
17
+ super(
18
+ queue_name: row["queue_name"],
19
+ throttle_interval_ms: row["throttle_interval_ms"],
20
+ last_notified_at: row["last_notified_at"]
21
+ )
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,153 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PGMQ
4
+ # Queue name validation, normalization, and sanitization.
5
+ #
6
+ # PGMQ interpolates a queue's name into PostgreSQL identifiers (it creates tables named +pgmq.q_<name>+ and
7
+ # +pgmq.a_<name>+), so a name has to be a valid, length-bounded SQL identifier. This module is the single source of
8
+ # truth for those rules and offers tiers depending on how much you trust the input:
9
+ #
10
+ # 1. {.validate!} / {.valid?} - assert a name is already valid. Use for names you control. {PGMQ::Client} calls
11
+ # {.validate!} before every operation.
12
+ # 2. {.normalize} - lightly rewrite a name that is *meant* to be valid but uses a friendlier separator. Maps the
13
+ # common stream separators (hyphens, dots, colons) to underscores, strips any other invalid character, then
14
+ # validates - so a Turbo Stream channel like +"chat:room-7"+ becomes +"chat_room_7"+. Raises if the result still
15
+ # can't be a valid name (empty, or starts with a digit).
16
+ # 3. {.sanitize!} - coerce *untrusted* input into a valid name by stripping every invalid character, then validate.
17
+ # Raises if nothing valid remains. Use this as a SQL-identifier guard: the result is always either a name you
18
+ # know is safe, or an exception - never a silent substitute.
19
+ # 4. {.sanitize} - the lenient sibling of {.sanitize!}: best-effort coercion that *never* raises for content and
20
+ # always returns a usable identifier (falling back to a default). Convenient, but see the collision caveat on
21
+ # the method - distinct inputs can map to the same name.
22
+ #
23
+ # @example
24
+ # PGMQ::QueueName.valid?("orders") # => true
25
+ # PGMQ::QueueName.validate!("my-queue") # => raises InvalidQueueNameError
26
+ # PGMQ::QueueName.normalize("chat:room-7") # => "chat_room_7"
27
+ # PGMQ::QueueName.sanitize!("orders!!") # => "orders"
28
+ # PGMQ::QueueName.sanitize!("!!!") # => raises InvalidQueueNameError
29
+ # PGMQ::QueueName.sanitize("99 Problems!") # => "q_99_problems"
30
+ module QueueName
31
+ # Maximum queue name length. PGMQ creates tables with prefixes (+q_+, +a_+) and PostgreSQL caps identifiers at 63
32
+ # characters; PGMQ enforces 48 to leave room for those prefixes and suffixes.
33
+ MAX_LENGTH = 48
34
+
35
+ # A valid queue name: starts with a letter or underscore, then letters, digits, or underscores.
36
+ PATTERN = /\A[a-zA-Z_][a-zA-Z0-9_]*\z/
37
+
38
+ # Prefix used by {.sanitize} when the input would otherwise start with an illegal leading character (e.g. a
39
+ # digit) but still has usable trailing characters.
40
+ SANITIZE_PREFIX = "q_"
41
+
42
+ # Name {.sanitize} falls back to when the input has no usable characters at all.
43
+ SANITIZE_FALLBACK = "queue"
44
+
45
+ module_function
46
+
47
+ # Returns true if the name is already a valid queue name.
48
+ #
49
+ # @param name [String, #to_s] candidate queue name
50
+ # @return [Boolean]
51
+ def valid?(name)
52
+ str = name.to_s
53
+ !str.empty? && str.length < MAX_LENGTH && str.match?(PATTERN)
54
+ end
55
+
56
+ # Validates a queue name, returning it unchanged when valid and raising otherwise.
57
+ #
58
+ # @param name [String, #to_s] candidate queue name
59
+ # @return [String] the validated name (as a String)
60
+ # @raise [PGMQ::Errors::InvalidQueueNameError] if the name is empty, too long, or not a valid identifier
61
+ def validate!(name)
62
+ str = name.to_s
63
+
64
+ if name.nil? || str.strip.empty?
65
+ raise Errors::InvalidQueueNameError, "Queue name cannot be empty"
66
+ end
67
+
68
+ if str.length >= MAX_LENGTH
69
+ raise Errors::InvalidQueueNameError,
70
+ "Queue name '#{str}' exceeds maximum length of #{MAX_LENGTH} characters " \
71
+ "(current length: #{str.length})"
72
+ end
73
+
74
+ return str if str.match?(PATTERN)
75
+
76
+ raise Errors::InvalidQueueNameError,
77
+ "Invalid queue name '#{str}': must start with a letter or underscore " \
78
+ "and contain only letters, digits, and underscores"
79
+ end
80
+
81
+ # Rewrites a name that is meant to be valid but uses friendlier separators, then validates it.
82
+ #
83
+ # Maps the common stream-name separators - hyphens, dots, and colons - to underscores, strips any *other* invalid
84
+ # character, then collapses repeated underscores and trims them from the ends. So +"chat:room-7"+ becomes
85
+ # +"chat_room_7"+ and +"order.events"+ becomes +"order_events"+, while a stray +"a@b"+ becomes +"ab"+ (the +@+ is
86
+ # dropped, not turned into a separator). The result is validated, so names that still can't be valid (empty, or
87
+ # starting with a digit) raise rather than being silently mangled.
88
+ #
89
+ # Colons in particular are the turbo-rails stream-name separator, so they are mapped to a safe character rather
90
+ # than stripped - otherwise +"a:b"+ and +"ab"+ would collide on the same queue.
91
+ #
92
+ # @param name [String, #to_s] a name using friendly separators
93
+ # @return [String] the normalized, validated queue name
94
+ # @raise [PGMQ::Errors::InvalidQueueNameError] if the normalized result is not a valid queue name
95
+ def normalize(name)
96
+ str = name.to_s
97
+ return validate!(str) if str.match?(PATTERN)
98
+
99
+ normalized = str
100
+ .gsub(/[-.:]/, "_") # hyphens / dots / colons -> underscores
101
+ .gsub(/[^a-zA-Z0-9_]/, "") # strip any remaining invalid character
102
+ .squeeze("_") # collapse consecutive underscores
103
+ .gsub(/\A_+|_+\z/, "") # trim leading / trailing underscores
104
+
105
+ validate!(normalized)
106
+ end
107
+
108
+ # Strips every invalid character from untrusted input, then validates the result.
109
+ #
110
+ # This is the SQL-identifier guard: it removes anything outside +[A-Za-z0-9_]+ and passes the remainder through
111
+ # {.validate!}. If nothing valid remains (empty result, leading digit, too long) it raises, so a caller can never
112
+ # accidentally operate on a different queue than intended. Use this for names from untrusted sources (URL params,
113
+ # external systems) where a wrong-but-valid name would be worse than an error.
114
+ #
115
+ # @param name [String, #to_s] untrusted input
116
+ # @return [String] the sanitized, validated queue name
117
+ # @raise [PGMQ::Errors::InvalidQueueNameError] if nothing valid remains after stripping
118
+ def sanitize!(name)
119
+ validate!(name.to_s.gsub(/[^a-zA-Z0-9_]/, ""))
120
+ end
121
+
122
+ # Best-effort coercion of arbitrary input into a valid queue name; the lenient sibling of {.sanitize!}.
123
+ #
124
+ # Never raises for content: it lowercases, replaces every illegal character run with an underscore, trims
125
+ # underscores, prefixes {SANITIZE_PREFIX} when the first surviving character is not a valid identifier start (e.g.
126
+ # a digit), truncates to fit {MAX_LENGTH}, and falls back to {SANITIZE_FALLBACK} when nothing usable remains. The
127
+ # return value always satisfies {.valid?}.
128
+ #
129
+ # @note Because it coerces rather than rejects, distinct inputs can map to the *same* name (e.g. +"a/b"+ and
130
+ # +"a-b"+ both become +"a_b"+; +"!!!"+ and +""+ both become +"queue"+). If your name selects a queue table,
131
+ # that means two logically different inputs could share one queue. When that matters - especially for untrusted
132
+ # input - prefer {.sanitize!}, which raises instead of substituting.
133
+ #
134
+ # @param name [String, #to_s] arbitrary input
135
+ # @return [String] a guaranteed-valid queue name
136
+ def sanitize(name)
137
+ cleaned = name.to_s.downcase
138
+ .gsub(/[^a-z0-9_]+/, "_").squeeze("_")
139
+ .gsub(/\A_+|_+\z/, "")
140
+
141
+ # Nothing usable survived the scrub - use the fallback rather than emit a bare prefix like "q_".
142
+ return SANITIZE_FALLBACK if cleaned.empty?
143
+
144
+ # A valid identifier can't start with a digit; prefix so the leading character is legal.
145
+ cleaned = "#{SANITIZE_PREFIX}#{cleaned}" unless cleaned.match?(/\A[a-z_]/)
146
+
147
+ # Keep under MAX_LENGTH (valid? requires strictly less than), trimming any underscore left at the cut.
148
+ cleaned = cleaned[0, MAX_LENGTH - 1].sub(/_+\z/, "") if cleaned.length >= MAX_LENGTH
149
+
150
+ cleaned.empty? ? SANITIZE_FALLBACK : cleaned
151
+ end
152
+ end
153
+ end
@@ -3,12 +3,11 @@
3
3
  module PGMQ
4
4
  # Low-level transaction support for PGMQ operations
5
5
  #
6
- # Provides atomic execution of PGMQ operations within PostgreSQL transactions.
7
- # Transactions are a database primitive - this is a thin wrapper around
8
- # PostgreSQL's native transaction support.
6
+ # Provides atomic execution of PGMQ operations within PostgreSQL transactions. Transactions are a database primitive -
7
+ # this is a thin wrapper around PostgreSQL's native transaction support.
9
8
  #
10
- # This is analogous to rdkafka-ruby providing Kafka transaction support -
11
- # it's a protocol/database feature, not a framework abstraction.
9
+ # This is analogous to rdkafka-ruby providing Kafka transaction support - it's a protocol/database feature, not a
10
+ # framework abstraction.
12
11
  #
13
12
  # @example Atomic multi-queue operations
14
13
  # client.transaction do |txn|
@@ -24,8 +23,8 @@ module PGMQ
24
23
  module Transaction
25
24
  # Executes PGMQ operations atomically within a database transaction
26
25
  #
27
- # Obtains a connection from the pool, starts a transaction, and yields
28
- # a client that uses that transaction for all operations.
26
+ # Obtains a connection from the pool, starts a transaction, and yields a client that uses that transaction for all
27
+ # operations.
29
28
  #
30
29
  # @yield [PGMQ::Client] transactional client using the same connection
31
30
  # @return [Object] result of the block
data/lib/pgmq/version.rb CHANGED
@@ -2,5 +2,5 @@
2
2
 
3
3
  module PGMQ
4
4
  # Current version of the pgmq-ruby gem
5
- VERSION = "0.6.1"
5
+ VERSION = "0.7.0"
6
6
  end
data/lib/pgmq.rb CHANGED
@@ -12,9 +12,8 @@ loader.eager_load
12
12
 
13
13
  # PGMQ - Low-level Ruby client for Postgres Message Queue
14
14
  #
15
- # This is a low-level library providing direct access to PGMQ operations.
16
- # For higher-level abstractions, job processing, and framework integrations,
17
- # see pgmq-framework (similar to how rdkafka-ruby relates to Karafka).
15
+ # This is a low-level library providing direct access to PGMQ operations. For higher-level abstractions, job processing,
16
+ # and framework integrations, see pgmq-framework (similar to how rdkafka-ruby relates to Karafka).
18
17
  #
19
18
  # @example Basic usage
20
19
  # require 'pgmq'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pgmq-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.1
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Maciej Mensfeld
@@ -64,6 +64,7 @@ files:
64
64
  - README.md
65
65
  - lib/pgmq.rb
66
66
  - lib/pgmq/client.rb
67
+ - lib/pgmq/client/autovacuum.rb
67
68
  - lib/pgmq/client/consumer.rb
68
69
  - lib/pgmq/client/maintenance.rb
69
70
  - lib/pgmq/client/message_lifecycle.rb
@@ -76,7 +77,9 @@ files:
76
77
  - lib/pgmq/errors.rb
77
78
  - lib/pgmq/message.rb
78
79
  - lib/pgmq/metrics.rb
80
+ - lib/pgmq/notify_throttle.rb
79
81
  - lib/pgmq/queue_metadata.rb
82
+ - lib/pgmq/queue_name.rb
80
83
  - lib/pgmq/transaction.rb
81
84
  - lib/pgmq/version.rb
82
85
  - pgmq-ruby.gemspec
@@ -104,7 +107,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
104
107
  - !ruby/object:Gem::Version
105
108
  version: '0'
106
109
  requirements: []
107
- rubygems_version: 4.0.6
110
+ rubygems_version: 4.0.10
108
111
  specification_version: 4
109
112
  summary: Ruby client for PGMQ (Postgres Message Queue)
110
113
  test_files: []