pgmq-ruby 0.4.0 → 0.5.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/.github/workflows/ci.yml +42 -22
- data/.github/workflows/push.yml +1 -1
- data/.rspec +1 -0
- data/.rubocop.yml +66 -0
- data/.yard-lint.yml +1 -3
- data/CHANGELOG.md +35 -0
- data/CLAUDE.md +310 -0
- data/Gemfile +5 -5
- data/Gemfile.lint +16 -0
- data/Gemfile.lint.lock +120 -0
- data/Gemfile.lock +20 -6
- data/README.md +213 -10
- data/Rakefile +71 -2
- data/docker-compose.yml +2 -2
- data/lib/pgmq/client/consumer.rb +80 -7
- data/lib/pgmq/client/maintenance.rb +4 -21
- data/lib/pgmq/client/message_lifecycle.rb +69 -44
- data/lib/pgmq/client/metrics.rb +2 -2
- data/lib/pgmq/client/multi_queue.rb +9 -9
- data/lib/pgmq/client/producer.rb +7 -7
- data/lib/pgmq/client/queue_management.rb +9 -9
- data/lib/pgmq/client/topics.rb +268 -0
- data/lib/pgmq/client.rb +13 -12
- data/lib/pgmq/connection.rb +11 -11
- data/lib/pgmq/message.rb +11 -9
- data/lib/pgmq/metrics.rb +7 -7
- data/lib/pgmq/queue_metadata.rb +7 -7
- data/lib/pgmq/version.rb +1 -1
- data/lib/pgmq.rb +3 -3
- data/package-lock.json +331 -0
- data/package.json +9 -0
- data/pgmq-ruby.gemspec +20 -20
- data/renovate.json +20 -1
- metadata +8 -2
- data/.coditsu/ci.yml +0 -3
data/lib/pgmq/client/consumer.rb
CHANGED
|
@@ -33,12 +33,12 @@ module PGMQ
|
|
|
33
33
|
result = with_connection do |conn|
|
|
34
34
|
if conditional.empty?
|
|
35
35
|
conn.exec_params(
|
|
36
|
-
|
|
36
|
+
"SELECT * FROM pgmq.read($1::text, $2::integer, $3::integer)",
|
|
37
37
|
[queue_name, vt, 1]
|
|
38
38
|
)
|
|
39
39
|
else
|
|
40
40
|
conn.exec_params(
|
|
41
|
-
|
|
41
|
+
"SELECT * FROM pgmq.read($1::text, $2::integer, $3::integer, $4::jsonb)",
|
|
42
42
|
[queue_name, vt, 1, conditional.to_json]
|
|
43
43
|
)
|
|
44
44
|
end
|
|
@@ -82,12 +82,12 @@ module PGMQ
|
|
|
82
82
|
result = with_connection do |conn|
|
|
83
83
|
if conditional.empty?
|
|
84
84
|
conn.exec_params(
|
|
85
|
-
|
|
85
|
+
"SELECT * FROM pgmq.read($1::text, $2::integer, $3::integer)",
|
|
86
86
|
[queue_name, vt, qty]
|
|
87
87
|
)
|
|
88
88
|
else
|
|
89
89
|
conn.exec_params(
|
|
90
|
-
|
|
90
|
+
"SELECT * FROM pgmq.read($1::text, $2::integer, $3::integer, $4::jsonb)",
|
|
91
91
|
[queue_name, vt, qty, conditional.to_json]
|
|
92
92
|
)
|
|
93
93
|
end
|
|
@@ -135,12 +135,12 @@ module PGMQ
|
|
|
135
135
|
result = with_connection do |conn|
|
|
136
136
|
if conditional.empty?
|
|
137
137
|
conn.exec_params(
|
|
138
|
-
|
|
138
|
+
"SELECT * FROM pgmq.read_with_poll($1::text, $2::integer, $3::integer, $4::integer, $5::integer)",
|
|
139
139
|
[queue_name, vt, qty, max_poll_seconds, poll_interval_ms]
|
|
140
140
|
)
|
|
141
141
|
else
|
|
142
|
-
sql =
|
|
143
|
-
|
|
142
|
+
sql = "SELECT * FROM pgmq.read_with_poll($1::text, $2::integer, $3::integer, " \
|
|
143
|
+
"$4::integer, $5::integer, $6::jsonb)"
|
|
144
144
|
conn.exec_params(
|
|
145
145
|
sql,
|
|
146
146
|
[queue_name, vt, qty, max_poll_seconds, poll_interval_ms, conditional.to_json]
|
|
@@ -150,6 +150,79 @@ module PGMQ
|
|
|
150
150
|
|
|
151
151
|
result.map { |row| Message.new(row) }
|
|
152
152
|
end
|
|
153
|
+
|
|
154
|
+
# Reads messages using grouped round-robin ordering
|
|
155
|
+
#
|
|
156
|
+
# Messages are grouped by the first key in their JSON payload and returned
|
|
157
|
+
# in round-robin order across groups. This ensures fair processing when
|
|
158
|
+
# messages from different entities (users, orders, etc.) are in the queue.
|
|
159
|
+
#
|
|
160
|
+
# @param queue_name [String] name of the queue
|
|
161
|
+
# @param vt [Integer] visibility timeout in seconds
|
|
162
|
+
# @param qty [Integer] number of messages to read
|
|
163
|
+
# @return [Array<PGMQ::Message>] array of messages in round-robin order
|
|
164
|
+
#
|
|
165
|
+
# @example Fair processing across users
|
|
166
|
+
# # Queue contains: user1_msg1, user1_msg2, user2_msg1, user3_msg1
|
|
167
|
+
# messages = client.read_grouped_rr("tasks", vt: 30, qty: 4)
|
|
168
|
+
# # Returns in round-robin: user1_msg1, user2_msg1, user3_msg1, user1_msg2
|
|
169
|
+
#
|
|
170
|
+
# @example Prevent single entity from monopolizing worker
|
|
171
|
+
# loop do
|
|
172
|
+
# messages = client.read_grouped_rr("orders", vt: 30, qty: 10)
|
|
173
|
+
# break if messages.empty?
|
|
174
|
+
# messages.each { |msg| process(msg) }
|
|
175
|
+
# end
|
|
176
|
+
def read_grouped_rr(queue_name, vt: DEFAULT_VT, qty: 1)
|
|
177
|
+
validate_queue_name!(queue_name)
|
|
178
|
+
|
|
179
|
+
result = with_connection do |conn|
|
|
180
|
+
conn.exec_params(
|
|
181
|
+
"SELECT * FROM pgmq.read_grouped_rr($1::text, $2::integer, $3::integer)",
|
|
182
|
+
[queue_name, vt, qty]
|
|
183
|
+
)
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
result.map { |row| Message.new(row) }
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
# Reads messages using grouped round-robin with long-polling support
|
|
190
|
+
#
|
|
191
|
+
# Combines grouped round-robin ordering with long-polling for efficient
|
|
192
|
+
# and fair message consumption.
|
|
193
|
+
#
|
|
194
|
+
# @param queue_name [String] name of the queue
|
|
195
|
+
# @param vt [Integer] visibility timeout in seconds
|
|
196
|
+
# @param qty [Integer] number of messages to read
|
|
197
|
+
# @param max_poll_seconds [Integer] maximum time to poll in seconds
|
|
198
|
+
# @param poll_interval_ms [Integer] interval between polls in milliseconds
|
|
199
|
+
# @return [Array<PGMQ::Message>] array of messages in round-robin order
|
|
200
|
+
#
|
|
201
|
+
# @example Long-polling with fair ordering
|
|
202
|
+
# messages = client.read_grouped_rr_with_poll("tasks",
|
|
203
|
+
# vt: 30,
|
|
204
|
+
# qty: 10,
|
|
205
|
+
# max_poll_seconds: 5,
|
|
206
|
+
# poll_interval_ms: 100
|
|
207
|
+
# )
|
|
208
|
+
def read_grouped_rr_with_poll(
|
|
209
|
+
queue_name,
|
|
210
|
+
vt: DEFAULT_VT,
|
|
211
|
+
qty: 1,
|
|
212
|
+
max_poll_seconds: 5,
|
|
213
|
+
poll_interval_ms: 100
|
|
214
|
+
)
|
|
215
|
+
validate_queue_name!(queue_name)
|
|
216
|
+
|
|
217
|
+
result = with_connection do |conn|
|
|
218
|
+
conn.exec_params(
|
|
219
|
+
"SELECT * FROM pgmq.read_grouped_rr_with_poll($1::text, $2::integer, $3::integer, $4::integer, $5::integer)",
|
|
220
|
+
[queue_name, vt, qty, max_poll_seconds, poll_interval_ms]
|
|
221
|
+
)
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
result.map { |row| Message.new(row) }
|
|
225
|
+
end
|
|
153
226
|
end
|
|
154
227
|
end
|
|
155
228
|
end
|
|
@@ -19,27 +19,10 @@ module PGMQ
|
|
|
19
19
|
validate_queue_name!(queue_name)
|
|
20
20
|
|
|
21
21
|
result = with_connection do |conn|
|
|
22
|
-
conn.exec_params(
|
|
22
|
+
conn.exec_params("SELECT pgmq.purge_queue($1::text)", [queue_name])
|
|
23
23
|
end
|
|
24
24
|
|
|
25
|
-
result[0][
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
# Detaches the archive table from PGMQ management
|
|
29
|
-
#
|
|
30
|
-
# @param queue_name [String] name of the queue
|
|
31
|
-
# @return [void]
|
|
32
|
-
#
|
|
33
|
-
# @example
|
|
34
|
-
# client.detach_archive("orders")
|
|
35
|
-
def detach_archive(queue_name)
|
|
36
|
-
validate_queue_name!(queue_name)
|
|
37
|
-
|
|
38
|
-
with_connection do |conn|
|
|
39
|
-
conn.exec_params('SELECT pgmq.detach_archive($1::text)', [queue_name])
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
nil
|
|
25
|
+
result[0]["purge_queue"]
|
|
43
26
|
end
|
|
44
27
|
|
|
45
28
|
# Enables PostgreSQL NOTIFY when messages are inserted into a queue
|
|
@@ -65,7 +48,7 @@ module PGMQ
|
|
|
65
48
|
|
|
66
49
|
with_connection do |conn|
|
|
67
50
|
conn.exec_params(
|
|
68
|
-
|
|
51
|
+
"SELECT pgmq.enable_notify_insert($1::text, $2::integer)",
|
|
69
52
|
[queue_name, throttle_interval_ms]
|
|
70
53
|
)
|
|
71
54
|
end
|
|
@@ -84,7 +67,7 @@ module PGMQ
|
|
|
84
67
|
validate_queue_name!(queue_name)
|
|
85
68
|
|
|
86
69
|
with_connection do |conn|
|
|
87
|
-
conn.exec_params(
|
|
70
|
+
conn.exec_params("SELECT pgmq.disable_notify_insert($1::text)", [queue_name])
|
|
88
71
|
end
|
|
89
72
|
|
|
90
73
|
nil
|
|
@@ -19,7 +19,7 @@ module PGMQ
|
|
|
19
19
|
validate_queue_name!(queue_name)
|
|
20
20
|
|
|
21
21
|
result = with_connection do |conn|
|
|
22
|
-
conn.exec_params(
|
|
22
|
+
conn.exec_params("SELECT * FROM pgmq.pop($1::text)", [queue_name])
|
|
23
23
|
end
|
|
24
24
|
|
|
25
25
|
return nil if result.ntuples.zero?
|
|
@@ -41,7 +41,7 @@ module PGMQ
|
|
|
41
41
|
return [] if qty <= 0
|
|
42
42
|
|
|
43
43
|
result = with_connection do |conn|
|
|
44
|
-
conn.exec_params(
|
|
44
|
+
conn.exec_params("SELECT * FROM pgmq.pop($1::text, $2::integer)", [queue_name, qty])
|
|
45
45
|
end
|
|
46
46
|
|
|
47
47
|
result.map { |row| Message.new(row) }
|
|
@@ -63,14 +63,14 @@ module PGMQ
|
|
|
63
63
|
|
|
64
64
|
result = with_connection do |conn|
|
|
65
65
|
conn.exec_params(
|
|
66
|
-
|
|
66
|
+
"SELECT pgmq.delete($1::text, $2::bigint)",
|
|
67
67
|
[queue_name, msg_id]
|
|
68
68
|
)
|
|
69
69
|
end
|
|
70
70
|
|
|
71
71
|
return false if result.ntuples.zero?
|
|
72
72
|
|
|
73
|
-
result[0][
|
|
73
|
+
result[0]["delete"] == "t"
|
|
74
74
|
end
|
|
75
75
|
|
|
76
76
|
# Deletes multiple messages from the queue
|
|
@@ -94,12 +94,12 @@ module PGMQ
|
|
|
94
94
|
encoded_array = encoder.encode(msg_ids)
|
|
95
95
|
|
|
96
96
|
conn.exec_params(
|
|
97
|
-
|
|
97
|
+
"SELECT * FROM pgmq.delete($1::text, $2::bigint[])",
|
|
98
98
|
[queue_name, encoded_array]
|
|
99
99
|
)
|
|
100
100
|
end
|
|
101
101
|
|
|
102
|
-
result.map { |row| row[
|
|
102
|
+
result.map { |row| row["delete"] }
|
|
103
103
|
end
|
|
104
104
|
|
|
105
105
|
# Deletes specific messages from multiple queues in a single transaction
|
|
@@ -123,7 +123,7 @@ module PGMQ
|
|
|
123
123
|
# deletions = messages.group_by(&:queue_name).transform_values { |mss| mss.map(&:msg_id) }
|
|
124
124
|
# client.delete_multi(deletions)
|
|
125
125
|
def delete_multi(deletions)
|
|
126
|
-
raise ArgumentError,
|
|
126
|
+
raise ArgumentError, "deletions must be a hash" unless deletions.is_a?(Hash)
|
|
127
127
|
return {} if deletions.empty?
|
|
128
128
|
|
|
129
129
|
# Validate all queue names
|
|
@@ -157,14 +157,14 @@ module PGMQ
|
|
|
157
157
|
|
|
158
158
|
result = with_connection do |conn|
|
|
159
159
|
conn.exec_params(
|
|
160
|
-
|
|
160
|
+
"SELECT pgmq.archive($1::text, $2::bigint)",
|
|
161
161
|
[queue_name, msg_id]
|
|
162
162
|
)
|
|
163
163
|
end
|
|
164
164
|
|
|
165
165
|
return false if result.ntuples.zero?
|
|
166
166
|
|
|
167
|
-
result[0][
|
|
167
|
+
result[0]["archive"] == "t"
|
|
168
168
|
end
|
|
169
169
|
|
|
170
170
|
# Archives multiple messages
|
|
@@ -188,12 +188,12 @@ module PGMQ
|
|
|
188
188
|
encoded_array = encoder.encode(msg_ids)
|
|
189
189
|
|
|
190
190
|
conn.exec_params(
|
|
191
|
-
|
|
191
|
+
"SELECT * FROM pgmq.archive($1::text, $2::bigint[])",
|
|
192
192
|
[queue_name, encoded_array]
|
|
193
193
|
)
|
|
194
194
|
end
|
|
195
195
|
|
|
196
|
-
result.map { |row| row[
|
|
196
|
+
result.map { |row| row["archive"] }
|
|
197
197
|
end
|
|
198
198
|
|
|
199
199
|
# Archives specific messages from multiple queues in a single transaction
|
|
@@ -209,7 +209,7 @@ module PGMQ
|
|
|
209
209
|
# 'notifications' => [5]
|
|
210
210
|
# })
|
|
211
211
|
def archive_multi(archives)
|
|
212
|
-
raise ArgumentError,
|
|
212
|
+
raise ArgumentError, "archives must be a hash" unless archives.is_a?(Hash)
|
|
213
213
|
return {} if archives.empty?
|
|
214
214
|
|
|
215
215
|
# Validate all queue names
|
|
@@ -229,26 +229,35 @@ module PGMQ
|
|
|
229
229
|
|
|
230
230
|
# Updates the visibility timeout for a message
|
|
231
231
|
#
|
|
232
|
+
# Supports two modes:
|
|
233
|
+
# - Integer offset (seconds from now): `vt: 60` - message visible in 60 seconds
|
|
234
|
+
# - Absolute timestamp: `vt: Time.now + 300` - message visible at specific time
|
|
235
|
+
#
|
|
232
236
|
# @param queue_name [String] name of the queue
|
|
233
237
|
# @param msg_id [Integer] message ID
|
|
234
|
-
# @param
|
|
238
|
+
# @param vt [Integer, Time] visibility timeout as seconds offset or absolute timestamp
|
|
235
239
|
# @return [PGMQ::Message, nil] updated message or nil if not found
|
|
236
240
|
#
|
|
237
|
-
# @example
|
|
238
|
-
#
|
|
239
|
-
#
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
vt_offset:
|
|
244
|
-
)
|
|
241
|
+
# @example Extend processing time by 60 more seconds (offset)
|
|
242
|
+
# msg = client.set_vt("orders", 123, vt: 60)
|
|
243
|
+
#
|
|
244
|
+
# @example Set absolute visibility time (timestamp)
|
|
245
|
+
# msg = client.set_vt("orders", 123, vt: Time.now + 300)
|
|
246
|
+
def set_vt(queue_name, msg_id, vt:)
|
|
245
247
|
validate_queue_name!(queue_name)
|
|
246
248
|
|
|
247
249
|
result = with_connection do |conn|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
250
|
+
if vt.is_a?(Time)
|
|
251
|
+
conn.exec_params(
|
|
252
|
+
"SELECT * FROM pgmq.set_vt($1::text, $2::bigint, $3::timestamptz)",
|
|
253
|
+
[queue_name, msg_id, vt.utc.iso8601(6)]
|
|
254
|
+
)
|
|
255
|
+
else
|
|
256
|
+
conn.exec_params(
|
|
257
|
+
"SELECT * FROM pgmq.set_vt($1::text, $2::bigint, $3::integer)",
|
|
258
|
+
[queue_name, msg_id, vt]
|
|
259
|
+
)
|
|
260
|
+
end
|
|
252
261
|
end
|
|
253
262
|
|
|
254
263
|
return nil if result.ntuples.zero?
|
|
@@ -258,19 +267,21 @@ module PGMQ
|
|
|
258
267
|
|
|
259
268
|
# Updates visibility timeout for multiple messages
|
|
260
269
|
#
|
|
270
|
+
# Supports two modes:
|
|
271
|
+
# - Integer offset (seconds from now): `vt: 60` - messages visible in 60 seconds
|
|
272
|
+
# - Absolute timestamp: `vt: Time.now + 300` - messages visible at specific time
|
|
273
|
+
#
|
|
261
274
|
# @param queue_name [String] name of the queue
|
|
262
275
|
# @param msg_ids [Array<Integer>] array of message IDs
|
|
263
|
-
# @param
|
|
276
|
+
# @param vt [Integer, Time] visibility timeout as seconds offset or absolute timestamp
|
|
264
277
|
# @return [Array<PGMQ::Message>] array of updated messages
|
|
265
278
|
#
|
|
266
|
-
# @example
|
|
267
|
-
#
|
|
268
|
-
#
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
vt_offset:
|
|
273
|
-
)
|
|
279
|
+
# @example Extend processing time for multiple messages (offset)
|
|
280
|
+
# messages = client.set_vt_batch("orders", [101, 102, 103], vt: 60)
|
|
281
|
+
#
|
|
282
|
+
# @example Set absolute visibility time (timestamp)
|
|
283
|
+
# messages = client.set_vt_batch("orders", [101, 102], vt: Time.now + 300)
|
|
284
|
+
def set_vt_batch(queue_name, msg_ids, vt:)
|
|
274
285
|
validate_queue_name!(queue_name)
|
|
275
286
|
return [] if msg_ids.empty?
|
|
276
287
|
|
|
@@ -278,10 +289,17 @@ module PGMQ
|
|
|
278
289
|
encoder = PG::TextEncoder::Array.new
|
|
279
290
|
encoded_array = encoder.encode(msg_ids)
|
|
280
291
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
292
|
+
if vt.is_a?(Time)
|
|
293
|
+
conn.exec_params(
|
|
294
|
+
"SELECT * FROM pgmq.set_vt($1::text, $2::bigint[], $3::timestamptz)",
|
|
295
|
+
[queue_name, encoded_array, vt.utc.iso8601(6)]
|
|
296
|
+
)
|
|
297
|
+
else
|
|
298
|
+
conn.exec_params(
|
|
299
|
+
"SELECT * FROM pgmq.set_vt($1::text, $2::bigint[], $3::integer)",
|
|
300
|
+
[queue_name, encoded_array, vt]
|
|
301
|
+
)
|
|
302
|
+
end
|
|
285
303
|
end
|
|
286
304
|
|
|
287
305
|
result.map { |row| Message.new(row) }
|
|
@@ -293,8 +311,12 @@ module PGMQ
|
|
|
293
311
|
# Useful when processing related messages from different queues and needing
|
|
294
312
|
# to extend their visibility timeouts together.
|
|
295
313
|
#
|
|
314
|
+
# Supports two modes:
|
|
315
|
+
# - Integer offset (seconds from now): `vt: 60` - messages visible in 60 seconds
|
|
316
|
+
# - Absolute timestamp: `vt: Time.now + 300` - messages visible at specific time
|
|
317
|
+
#
|
|
296
318
|
# @param updates [Hash] hash of queue_name => array of msg_ids
|
|
297
|
-
# @param
|
|
319
|
+
# @param vt [Integer, Time] visibility timeout as seconds offset or absolute timestamp
|
|
298
320
|
# @return [Hash] hash of queue_name => array of updated PGMQ::Message objects
|
|
299
321
|
#
|
|
300
322
|
# @example Extend visibility timeout for messages from multiple queues
|
|
@@ -302,15 +324,18 @@ module PGMQ
|
|
|
302
324
|
# 'orders' => [1, 2, 3],
|
|
303
325
|
# 'notifications' => [5, 6],
|
|
304
326
|
# 'emails' => [10]
|
|
305
|
-
# },
|
|
327
|
+
# }, vt: 60)
|
|
306
328
|
# # => { 'orders' => [<Message>, ...], 'notifications' => [...], 'emails' => [...] }
|
|
307
329
|
#
|
|
330
|
+
# @example Set absolute visibility time
|
|
331
|
+
# client.set_vt_multi(updates, vt: Time.now + 300)
|
|
332
|
+
#
|
|
308
333
|
# @example Extend timeout after batch reading from multiple queues
|
|
309
334
|
# messages = client.read_multi(['q1', 'q2', 'q3'], qty: 10)
|
|
310
335
|
# updates = messages.group_by(&:queue_name).transform_values { |msgs| msgs.map(&:msg_id) }
|
|
311
|
-
# client.set_vt_multi(updates,
|
|
312
|
-
def set_vt_multi(updates,
|
|
313
|
-
raise ArgumentError,
|
|
336
|
+
# client.set_vt_multi(updates, vt: 120)
|
|
337
|
+
def set_vt_multi(updates, vt:)
|
|
338
|
+
raise ArgumentError, "updates must be a hash" unless updates.is_a?(Hash)
|
|
314
339
|
return {} if updates.empty?
|
|
315
340
|
|
|
316
341
|
# Validate all queue names
|
|
@@ -321,7 +346,7 @@ module PGMQ
|
|
|
321
346
|
updates.each do |queue_name, msg_ids|
|
|
322
347
|
next if msg_ids.empty?
|
|
323
348
|
|
|
324
|
-
updated_messages = txn.set_vt_batch(queue_name, msg_ids,
|
|
349
|
+
updated_messages = txn.set_vt_batch(queue_name, msg_ids, vt: vt)
|
|
325
350
|
result[queue_name] = updated_messages
|
|
326
351
|
end
|
|
327
352
|
result
|
data/lib/pgmq/client/metrics.rb
CHANGED
|
@@ -20,7 +20,7 @@ module PGMQ
|
|
|
20
20
|
validate_queue_name!(queue_name)
|
|
21
21
|
|
|
22
22
|
result = with_connection do |conn|
|
|
23
|
-
conn.exec_params(
|
|
23
|
+
conn.exec_params("SELECT * FROM pgmq.metrics($1::text)", [queue_name])
|
|
24
24
|
end
|
|
25
25
|
|
|
26
26
|
return nil if result.ntuples.zero?
|
|
@@ -39,7 +39,7 @@ module PGMQ
|
|
|
39
39
|
# end
|
|
40
40
|
def metrics_all
|
|
41
41
|
result = with_connection do |conn|
|
|
42
|
-
conn.exec(
|
|
42
|
+
conn.exec("SELECT * FROM pgmq.metrics_all()")
|
|
43
43
|
end
|
|
44
44
|
|
|
45
45
|
result.map { |row| PGMQ::Metrics.new(row) }
|
|
@@ -46,9 +46,9 @@ module PGMQ
|
|
|
46
46
|
qty: 1,
|
|
47
47
|
limit: nil
|
|
48
48
|
)
|
|
49
|
-
raise ArgumentError,
|
|
50
|
-
raise ArgumentError,
|
|
51
|
-
raise ArgumentError,
|
|
49
|
+
raise ArgumentError, "queue_names must be an array" unless queue_names.is_a?(Array)
|
|
50
|
+
raise ArgumentError, "queue_names cannot be empty" if queue_names.empty?
|
|
51
|
+
raise ArgumentError, "queue_names cannot exceed 50 queues" if queue_names.size > 50
|
|
52
52
|
|
|
53
53
|
# Validate all queue names (prevents SQL injection)
|
|
54
54
|
queue_names.each { |qn| validate_queue_name!(qn) }
|
|
@@ -118,9 +118,9 @@ module PGMQ
|
|
|
118
118
|
max_poll_seconds: 5,
|
|
119
119
|
poll_interval_ms: 100
|
|
120
120
|
)
|
|
121
|
-
raise ArgumentError,
|
|
122
|
-
raise ArgumentError,
|
|
123
|
-
raise ArgumentError,
|
|
121
|
+
raise ArgumentError, "queue_names must be an array" unless queue_names.is_a?(Array)
|
|
122
|
+
raise ArgumentError, "queue_names cannot be empty" if queue_names.empty?
|
|
123
|
+
raise ArgumentError, "queue_names cannot exceed 50 queues" if queue_names.size > 50
|
|
124
124
|
|
|
125
125
|
start_time = Time.now
|
|
126
126
|
poll_interval_seconds = poll_interval_ms / 1000.0
|
|
@@ -165,9 +165,9 @@ module PGMQ
|
|
|
165
165
|
# process(msg.queue_name, msg.payload)
|
|
166
166
|
# end
|
|
167
167
|
def pop_multi(queue_names)
|
|
168
|
-
raise ArgumentError,
|
|
169
|
-
raise ArgumentError,
|
|
170
|
-
raise ArgumentError,
|
|
168
|
+
raise ArgumentError, "queue_names must be an array" unless queue_names.is_a?(Array)
|
|
169
|
+
raise ArgumentError, "queue_names cannot be empty" if queue_names.empty?
|
|
170
|
+
raise ArgumentError, "queue_names cannot exceed 50 queues" if queue_names.size > 50
|
|
171
171
|
|
|
172
172
|
# Validate all queue names
|
|
173
173
|
queue_names.each { |qn| validate_queue_name!(qn) }
|
data/lib/pgmq/client/producer.rb
CHANGED
|
@@ -43,18 +43,18 @@ module PGMQ
|
|
|
43
43
|
result = with_connection do |conn|
|
|
44
44
|
if headers
|
|
45
45
|
conn.exec_params(
|
|
46
|
-
|
|
46
|
+
"SELECT * FROM pgmq.send($1::text, $2::jsonb, $3::jsonb, $4::integer)",
|
|
47
47
|
[queue_name, message, headers, delay]
|
|
48
48
|
)
|
|
49
49
|
else
|
|
50
50
|
conn.exec_params(
|
|
51
|
-
|
|
51
|
+
"SELECT * FROM pgmq.send($1::text, $2::jsonb, $3::integer)",
|
|
52
52
|
[queue_name, message, delay]
|
|
53
53
|
)
|
|
54
54
|
end
|
|
55
55
|
end
|
|
56
56
|
|
|
57
|
-
result[0][
|
|
57
|
+
result[0]["send"]
|
|
58
58
|
end
|
|
59
59
|
|
|
60
60
|
# Produces multiple messages to a queue in a batch
|
|
@@ -94,7 +94,7 @@ module PGMQ
|
|
|
94
94
|
|
|
95
95
|
if headers && headers.length != messages.length
|
|
96
96
|
raise ArgumentError,
|
|
97
|
-
|
|
97
|
+
"headers array length (#{headers.length}) must match messages array length (#{messages.length})"
|
|
98
98
|
end
|
|
99
99
|
|
|
100
100
|
# Use PostgreSQL array parameter binding for security
|
|
@@ -107,18 +107,18 @@ module PGMQ
|
|
|
107
107
|
if headers
|
|
108
108
|
encoded_headers = encoder.encode(headers)
|
|
109
109
|
conn.exec_params(
|
|
110
|
-
|
|
110
|
+
"SELECT * FROM pgmq.send_batch($1::text, $2::jsonb[], $3::jsonb[], $4::integer)",
|
|
111
111
|
[queue_name, encoded_messages, encoded_headers, delay]
|
|
112
112
|
)
|
|
113
113
|
else
|
|
114
114
|
conn.exec_params(
|
|
115
|
-
|
|
115
|
+
"SELECT * FROM pgmq.send_batch($1::text, $2::jsonb[], $3::integer)",
|
|
116
116
|
[queue_name, encoded_messages, delay]
|
|
117
117
|
)
|
|
118
118
|
end
|
|
119
119
|
end
|
|
120
120
|
|
|
121
|
-
result.map { |row| row[
|
|
121
|
+
result.map { |row| row["send_batch"] }
|
|
122
122
|
end
|
|
123
123
|
end
|
|
124
124
|
end
|
|
@@ -22,7 +22,7 @@ module PGMQ
|
|
|
22
22
|
|
|
23
23
|
with_connection do |conn|
|
|
24
24
|
existed = queue_exists?(conn, queue_name)
|
|
25
|
-
conn.exec_params(
|
|
25
|
+
conn.exec_params("SELECT pgmq.create($1::text)", [queue_name])
|
|
26
26
|
!existed
|
|
27
27
|
end
|
|
28
28
|
end
|
|
@@ -43,15 +43,15 @@ module PGMQ
|
|
|
43
43
|
# ) # => true
|
|
44
44
|
def create_partitioned(
|
|
45
45
|
queue_name,
|
|
46
|
-
partition_interval:
|
|
47
|
-
retention_interval:
|
|
46
|
+
partition_interval: "10000",
|
|
47
|
+
retention_interval: "100000"
|
|
48
48
|
)
|
|
49
49
|
validate_queue_name!(queue_name)
|
|
50
50
|
|
|
51
51
|
with_connection do |conn|
|
|
52
52
|
existed = queue_exists?(conn, queue_name)
|
|
53
53
|
conn.exec_params(
|
|
54
|
-
|
|
54
|
+
"SELECT pgmq.create_partitioned($1::text, $2::text, $3::text)",
|
|
55
55
|
[queue_name, partition_interval, retention_interval]
|
|
56
56
|
)
|
|
57
57
|
!existed
|
|
@@ -70,7 +70,7 @@ module PGMQ
|
|
|
70
70
|
|
|
71
71
|
with_connection do |conn|
|
|
72
72
|
existed = queue_exists?(conn, queue_name)
|
|
73
|
-
conn.exec_params(
|
|
73
|
+
conn.exec_params("SELECT pgmq.create_unlogged($1::text)", [queue_name])
|
|
74
74
|
!existed
|
|
75
75
|
end
|
|
76
76
|
end
|
|
@@ -86,12 +86,12 @@ module PGMQ
|
|
|
86
86
|
validate_queue_name!(queue_name)
|
|
87
87
|
|
|
88
88
|
result = with_connection do |conn|
|
|
89
|
-
conn.exec_params(
|
|
89
|
+
conn.exec_params("SELECT pgmq.drop_queue($1::text)", [queue_name])
|
|
90
90
|
end
|
|
91
91
|
|
|
92
92
|
return false if result.ntuples.zero?
|
|
93
93
|
|
|
94
|
-
result[0][
|
|
94
|
+
result[0]["drop_queue"] == "t"
|
|
95
95
|
end
|
|
96
96
|
|
|
97
97
|
# Lists all queues
|
|
@@ -103,7 +103,7 @@ module PGMQ
|
|
|
103
103
|
# queues.each { |q| puts q.queue_name }
|
|
104
104
|
def list_queues
|
|
105
105
|
result = with_connection do |conn|
|
|
106
|
-
conn.exec(
|
|
106
|
+
conn.exec("SELECT * FROM pgmq.list_queues()")
|
|
107
107
|
end
|
|
108
108
|
|
|
109
109
|
result.map { |row| QueueMetadata.new(row) }
|
|
@@ -118,7 +118,7 @@ module PGMQ
|
|
|
118
118
|
# @return [Boolean] true if queue exists, false otherwise
|
|
119
119
|
def queue_exists?(conn, queue_name)
|
|
120
120
|
result = conn.exec_params(
|
|
121
|
-
|
|
121
|
+
"SELECT 1 FROM pgmq.meta WHERE queue_name = $1 LIMIT 1",
|
|
122
122
|
[queue_name]
|
|
123
123
|
)
|
|
124
124
|
result.ntuples.positive?
|