pgmq-ruby 0.1.0 → 0.4.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/.coditsu/ci.yml +3 -0
- data/.github/workflows/ci.yml +163 -0
- data/.github/workflows/push.yml +35 -0
- data/.gitignore +67 -0
- data/.rspec +1 -0
- data/.ruby-version +1 -0
- data/.yard-lint.yml +275 -0
- data/CHANGELOG.md +125 -0
- data/Gemfile +15 -0
- data/Gemfile.lock +65 -0
- data/LICENSE +165 -0
- data/README.md +687 -0
- data/Rakefile +4 -0
- data/docker-compose.yml +22 -0
- data/lib/pgmq/client/consumer.rb +155 -0
- data/lib/pgmq/client/maintenance.rb +94 -0
- data/lib/pgmq/client/message_lifecycle.rb +332 -0
- data/lib/pgmq/client/metrics.rb +49 -0
- data/lib/pgmq/client/multi_queue.rb +193 -0
- data/lib/pgmq/client/producer.rb +125 -0
- data/lib/pgmq/client/queue_management.rb +128 -0
- data/lib/pgmq/client.rb +138 -0
- data/lib/pgmq/connection.rb +196 -0
- data/lib/pgmq/errors.rb +30 -0
- data/lib/pgmq/message.rb +45 -0
- data/lib/pgmq/metrics.rb +37 -0
- data/lib/pgmq/queue_metadata.rb +37 -0
- data/lib/pgmq/transaction.rb +92 -0
- data/lib/pgmq/version.rb +6 -0
- data/lib/pgmq.rb +53 -0
- data/pgmq-ruby.gemspec +32 -0
- data/renovate.json +11 -0
- metadata +67 -5
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'pg'
|
|
4
|
+
require 'connection_pool'
|
|
5
|
+
|
|
6
|
+
module PGMQ
|
|
7
|
+
# Manages database connections for PGMQ
|
|
8
|
+
#
|
|
9
|
+
# Supports multiple connection strategies:
|
|
10
|
+
# - Connection strings
|
|
11
|
+
# - Hash of connection parameters
|
|
12
|
+
# - Callable objects (for Rails ActiveRecord integration)
|
|
13
|
+
#
|
|
14
|
+
# @example With connection string
|
|
15
|
+
# conn = PGMQ::Connection.new("postgres://localhost/mydb")
|
|
16
|
+
#
|
|
17
|
+
# @example With connection hash
|
|
18
|
+
# conn = PGMQ::Connection.new(host: 'localhost', dbname: 'mydb')
|
|
19
|
+
#
|
|
20
|
+
# @example With Rails ActiveRecord (reuses Rails connection pool)
|
|
21
|
+
# conn = PGMQ::Connection.new(-> { ActiveRecord::Base.connection.raw_connection })
|
|
22
|
+
class Connection
|
|
23
|
+
# Default connection pool size
|
|
24
|
+
DEFAULT_POOL_SIZE = 5
|
|
25
|
+
|
|
26
|
+
# Default connection pool timeout in seconds
|
|
27
|
+
DEFAULT_POOL_TIMEOUT = 5
|
|
28
|
+
|
|
29
|
+
# @return [ConnectionPool] the connection pool
|
|
30
|
+
attr_reader :pool
|
|
31
|
+
|
|
32
|
+
# Creates a new connection manager
|
|
33
|
+
#
|
|
34
|
+
# @param conn_params [String, Hash, Proc] connection parameters or callable
|
|
35
|
+
# @param pool_size [Integer] size of the connection pool
|
|
36
|
+
# @param pool_timeout [Integer] connection pool timeout in seconds
|
|
37
|
+
# @param auto_reconnect [Boolean] automatically reconnect on connection errors
|
|
38
|
+
# @raise [PGMQ::Errors::ConfigurationError] if conn_params is nil or invalid
|
|
39
|
+
def initialize(
|
|
40
|
+
conn_params,
|
|
41
|
+
pool_size: DEFAULT_POOL_SIZE,
|
|
42
|
+
pool_timeout: DEFAULT_POOL_TIMEOUT,
|
|
43
|
+
auto_reconnect: true
|
|
44
|
+
)
|
|
45
|
+
if conn_params.nil?
|
|
46
|
+
raise(
|
|
47
|
+
PGMQ::Errors::ConfigurationError,
|
|
48
|
+
'Connection parameters are required'
|
|
49
|
+
)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
@conn_params = normalize_connection_params(conn_params)
|
|
53
|
+
@pool_size = pool_size
|
|
54
|
+
@pool_timeout = pool_timeout
|
|
55
|
+
@auto_reconnect = auto_reconnect
|
|
56
|
+
@pool = create_pool
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Executes a block with a connection from the pool
|
|
60
|
+
#
|
|
61
|
+
# @yield [PG::Connection] database connection
|
|
62
|
+
# @return [Object] result of the block
|
|
63
|
+
# @raise [PGMQ::Errors::ConnectionError] if connection fails
|
|
64
|
+
def with_connection
|
|
65
|
+
retries = @auto_reconnect ? 1 : 0
|
|
66
|
+
attempts = 0
|
|
67
|
+
|
|
68
|
+
begin
|
|
69
|
+
@pool.with do |conn|
|
|
70
|
+
# Health check: verify connection is alive
|
|
71
|
+
verify_connection!(conn) if @auto_reconnect
|
|
72
|
+
|
|
73
|
+
yield conn
|
|
74
|
+
end
|
|
75
|
+
rescue PG::Error => e
|
|
76
|
+
attempts += 1
|
|
77
|
+
|
|
78
|
+
# If connection error and auto_reconnect enabled, try once more
|
|
79
|
+
retry if attempts <= retries && connection_lost_error?(e)
|
|
80
|
+
|
|
81
|
+
raise PGMQ::Errors::ConnectionError, "Database connection error: #{e.message}"
|
|
82
|
+
rescue ConnectionPool::TimeoutError => e
|
|
83
|
+
raise PGMQ::Errors::ConnectionError, "Connection pool timeout: #{e.message}"
|
|
84
|
+
rescue ConnectionPool::PoolShuttingDownError => e
|
|
85
|
+
raise PGMQ::Errors::ConnectionError, "Connection pool is closed: #{e.message}"
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Closes all connections in the pool
|
|
90
|
+
# @return [void]
|
|
91
|
+
def close
|
|
92
|
+
@pool.shutdown { |conn| conn.close unless conn.finished? }
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Returns connection pool statistics
|
|
96
|
+
#
|
|
97
|
+
# @return [Hash] statistics about the connection pool
|
|
98
|
+
# @example
|
|
99
|
+
# stats = connection.stats
|
|
100
|
+
# # => { size: 5, available: 3 }
|
|
101
|
+
def stats
|
|
102
|
+
{
|
|
103
|
+
size: @pool_size,
|
|
104
|
+
available: @pool.available
|
|
105
|
+
}
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
private
|
|
109
|
+
|
|
110
|
+
# Checks if the error indicates a lost connection
|
|
111
|
+
# @param error [PG::Error] the error to check
|
|
112
|
+
# @return [Boolean] true if connection was lost
|
|
113
|
+
def connection_lost_error?(error)
|
|
114
|
+
# Common connection lost errors
|
|
115
|
+
lost_connection_messages = [
|
|
116
|
+
'server closed the connection',
|
|
117
|
+
'connection not open',
|
|
118
|
+
'no connection to the server',
|
|
119
|
+
'terminating connection',
|
|
120
|
+
'connection to server was lost',
|
|
121
|
+
'could not receive data from server'
|
|
122
|
+
]
|
|
123
|
+
|
|
124
|
+
message = error.message.downcase
|
|
125
|
+
lost_connection_messages.any? { |pattern| message.include?(pattern) }
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# Verifies a connection is alive and working
|
|
129
|
+
# @param conn [PG::Connection] connection to verify
|
|
130
|
+
# @raise [PG::Error] if connection is not working
|
|
131
|
+
def verify_connection!(conn)
|
|
132
|
+
# Quick check - is connection object in bad state?
|
|
133
|
+
return unless conn.finished?
|
|
134
|
+
|
|
135
|
+
# Connection is finished/closed, try to reset it
|
|
136
|
+
conn.reset
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# Normalizes various connection parameter formats
|
|
140
|
+
# @param params [String, Hash, Proc]
|
|
141
|
+
# @return [Hash, Proc]
|
|
142
|
+
# @raise [PGMQ::Errors::ConfigurationError] if params format is invalid
|
|
143
|
+
def normalize_connection_params(params)
|
|
144
|
+
return params if params.respond_to?(:call) # Callable (e.g., proc for Rails)
|
|
145
|
+
return parse_connection_string(params) if params.is_a?(String)
|
|
146
|
+
return params if params.is_a?(Hash) && !params.empty?
|
|
147
|
+
|
|
148
|
+
raise PGMQ::Errors::ConfigurationError, 'Invalid connection parameters format'
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# Parses a PostgreSQL connection string
|
|
152
|
+
# @param conn_string [String] connection string (e.g., "postgres://user:pass@host/db")
|
|
153
|
+
# @return [Hash] connection parameters
|
|
154
|
+
def parse_connection_string(conn_string)
|
|
155
|
+
# PG::Connection.conninfo_parse is available in pg >= 0.20
|
|
156
|
+
if PG::Connection.respond_to?(:conninfo_parse)
|
|
157
|
+
PG::Connection.conninfo_parse(conn_string).each_with_object({}) do |info, hash|
|
|
158
|
+
hash[info[:keyword].to_sym] = info[:val] if info[:val]
|
|
159
|
+
end
|
|
160
|
+
else
|
|
161
|
+
# Fallback: pass the string directly and let PG handle it
|
|
162
|
+
{ conninfo: conn_string }
|
|
163
|
+
end
|
|
164
|
+
rescue PG::Error => e
|
|
165
|
+
raise PGMQ::Errors::ConfigurationError, "Invalid connection string: #{e.message}"
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# Creates the connection pool
|
|
169
|
+
# @return [ConnectionPool]
|
|
170
|
+
def create_pool
|
|
171
|
+
params = @conn_params
|
|
172
|
+
|
|
173
|
+
ConnectionPool.new(size: @pool_size, timeout: @pool_timeout) do
|
|
174
|
+
create_connection(params)
|
|
175
|
+
end
|
|
176
|
+
rescue StandardError => e
|
|
177
|
+
raise PGMQ::Errors::ConnectionError, "Failed to create connection pool: #{e.message}"
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# Creates a single database connection
|
|
181
|
+
# @param params [Hash, Proc] connection parameters or callable
|
|
182
|
+
# @return [PG::Connection]
|
|
183
|
+
def create_connection(params)
|
|
184
|
+
# If we have a callable (e.g., for Rails), call it to get the connection
|
|
185
|
+
return params.call if params.respond_to?(:call)
|
|
186
|
+
|
|
187
|
+
# Create new connection from parameters
|
|
188
|
+
# Low-level library: return all values as strings from PostgreSQL
|
|
189
|
+
# No automatic type conversion - let higher-level frameworks handle parsing
|
|
190
|
+
# conn.type_map_for_results intentionally NOT set
|
|
191
|
+
PG.connect(params[:conninfo] || params)
|
|
192
|
+
rescue PG::Error => e
|
|
193
|
+
raise PGMQ::Errors::ConnectionError, "Failed to connect to database: #{e.message}"
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
end
|
data/lib/pgmq/errors.rb
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module PGMQ
|
|
4
|
+
# PGMQ errors namespace
|
|
5
|
+
module Errors
|
|
6
|
+
# Base error class for all PGMQ errors
|
|
7
|
+
class BaseError < StandardError; end
|
|
8
|
+
|
|
9
|
+
# Raised when connection to PostgreSQL fails or is lost
|
|
10
|
+
class ConnectionError < BaseError; end
|
|
11
|
+
|
|
12
|
+
# Raised when a queue operation is attempted on a non-existent queue
|
|
13
|
+
class QueueNotFoundError < BaseError; end
|
|
14
|
+
|
|
15
|
+
# Raised when a message cannot be found
|
|
16
|
+
class MessageNotFoundError < BaseError; end
|
|
17
|
+
|
|
18
|
+
# Raised when message serialization fails
|
|
19
|
+
class SerializationError < BaseError; end
|
|
20
|
+
|
|
21
|
+
# Raised when message deserialization fails
|
|
22
|
+
class DeserializationError < BaseError; end
|
|
23
|
+
|
|
24
|
+
# Raised when configuration is invalid
|
|
25
|
+
class ConfigurationError < BaseError; end
|
|
26
|
+
|
|
27
|
+
# Raised when an invalid queue name is provided
|
|
28
|
+
class InvalidQueueNameError < BaseError; end
|
|
29
|
+
end
|
|
30
|
+
end
|
data/lib/pgmq/message.rb
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module PGMQ
|
|
4
|
+
# Represents a message read from a PGMQ queue
|
|
5
|
+
#
|
|
6
|
+
# Returns raw values from PostgreSQL without transformation.
|
|
7
|
+
# Higher-level frameworks should handle parsing, deserialization, etc.
|
|
8
|
+
#
|
|
9
|
+
# @example Reading a message (raw values)
|
|
10
|
+
# msg = client.read("my_queue", vt: 30)
|
|
11
|
+
# puts msg.msg_id # => "123" (String from PG)
|
|
12
|
+
# puts msg.read_ct # => "1" (String from PG)
|
|
13
|
+
# puts msg.enqueued_at # => "2025-01-15 10:30:00+00" (String from PG)
|
|
14
|
+
# puts msg.vt # => "2025-01-15 10:30:30+00" (String from PG)
|
|
15
|
+
# puts msg.message # => "{\"order_id\":456}" (Raw JSONB string)
|
|
16
|
+
# puts msg.headers # => "{\"trace_id\":\"abc123\"}" (Raw JSONB string, optional)
|
|
17
|
+
# puts msg.queue_name # => "my_queue" (only present for multi-queue operations)
|
|
18
|
+
class Message < Data.define(
|
|
19
|
+
:msg_id, :read_ct, :enqueued_at, :vt, :message, :headers, :queue_name
|
|
20
|
+
)
|
|
21
|
+
class << self
|
|
22
|
+
# Creates a new Message from a database row
|
|
23
|
+
# @param row [Hash] database row from PG result
|
|
24
|
+
# @return [Message]
|
|
25
|
+
def new(row, **)
|
|
26
|
+
# Return raw values as-is from PostgreSQL
|
|
27
|
+
# No parsing, no deserialization, no transformation
|
|
28
|
+
# The pg gem returns JSONB as String by default
|
|
29
|
+
super(
|
|
30
|
+
msg_id: row['msg_id'],
|
|
31
|
+
read_ct: row['read_ct'],
|
|
32
|
+
enqueued_at: row['enqueued_at'],
|
|
33
|
+
vt: row['vt'],
|
|
34
|
+
message: row['message'],
|
|
35
|
+
headers: row['headers'], # JSONB column for metadata (optional)
|
|
36
|
+
queue_name: row['queue_name'] # nil for single-queue operations
|
|
37
|
+
)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Alias for msg_id (common in messaging systems)
|
|
42
|
+
# @return [String]
|
|
43
|
+
alias id msg_id
|
|
44
|
+
end
|
|
45
|
+
end
|
data/lib/pgmq/metrics.rb
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'time'
|
|
4
|
+
|
|
5
|
+
module PGMQ
|
|
6
|
+
# Represents metrics for a PGMQ queue
|
|
7
|
+
#
|
|
8
|
+
# @example Getting queue metrics
|
|
9
|
+
# metrics = client.metrics("my_queue")
|
|
10
|
+
# puts metrics.queue_length # => 42
|
|
11
|
+
# puts metrics.oldest_msg_age_sec # => 3600
|
|
12
|
+
class Metrics < Data.define(
|
|
13
|
+
:queue_name,
|
|
14
|
+
:queue_length,
|
|
15
|
+
:newest_msg_age_sec,
|
|
16
|
+
:oldest_msg_age_sec,
|
|
17
|
+
:total_messages,
|
|
18
|
+
:scrape_time
|
|
19
|
+
)
|
|
20
|
+
class << self
|
|
21
|
+
# Creates a new Metrics object from a database row
|
|
22
|
+
# @param row [Hash] database row from PG result
|
|
23
|
+
# @return [Metrics]
|
|
24
|
+
def new(row, **)
|
|
25
|
+
# Return raw values as-is from PostgreSQL
|
|
26
|
+
super(
|
|
27
|
+
queue_name: row['queue_name'],
|
|
28
|
+
queue_length: row['queue_length'],
|
|
29
|
+
newest_msg_age_sec: row['newest_msg_age_sec'],
|
|
30
|
+
oldest_msg_age_sec: row['oldest_msg_age_sec'],
|
|
31
|
+
total_messages: row['total_messages'],
|
|
32
|
+
scrape_time: row['scrape_time']
|
|
33
|
+
)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'time'
|
|
4
|
+
|
|
5
|
+
module PGMQ
|
|
6
|
+
# Represents metadata about a PGMQ queue
|
|
7
|
+
#
|
|
8
|
+
# @example Listing queues
|
|
9
|
+
# queues = client.list_queues
|
|
10
|
+
# queues.each do |q|
|
|
11
|
+
# puts "#{q.queue_name} (partitioned: #{q.is_partitioned})"
|
|
12
|
+
# end
|
|
13
|
+
class QueueMetadata < Data.define(:queue_name, :created_at, :is_partitioned, :is_unlogged)
|
|
14
|
+
class << self
|
|
15
|
+
# Creates a new QueueMetadata object from a database row
|
|
16
|
+
# @param row [Hash] database row from PG result
|
|
17
|
+
# @return [QueueMetadata]
|
|
18
|
+
def new(row, **)
|
|
19
|
+
# Return raw values as-is from PostgreSQL
|
|
20
|
+
super(
|
|
21
|
+
queue_name: row['queue_name'],
|
|
22
|
+
created_at: row['created_at'],
|
|
23
|
+
is_partitioned: row['is_partitioned'],
|
|
24
|
+
is_unlogged: row['is_unlogged']
|
|
25
|
+
)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Alias for is_partitioned
|
|
30
|
+
# @return [String] 't' or 'f' from PostgreSQL
|
|
31
|
+
alias partitioned? is_partitioned
|
|
32
|
+
|
|
33
|
+
# Alias for is_unlogged
|
|
34
|
+
# @return [String] 't' or 'f' from PostgreSQL
|
|
35
|
+
alias unlogged? is_unlogged
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module PGMQ
|
|
4
|
+
# Low-level transaction support for PGMQ operations
|
|
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.
|
|
9
|
+
#
|
|
10
|
+
# This is analogous to rdkafka-ruby providing Kafka transaction support -
|
|
11
|
+
# it's a protocol/database feature, not a framework abstraction.
|
|
12
|
+
#
|
|
13
|
+
# @example Atomic multi-queue operations
|
|
14
|
+
# client.transaction do |txn|
|
|
15
|
+
# txn.produce("orders", '{"order_id":123}')
|
|
16
|
+
# txn.produce("notifications", '{"type":"order_created"}')
|
|
17
|
+
# end
|
|
18
|
+
#
|
|
19
|
+
# @example Automatic rollback on error
|
|
20
|
+
# client.transaction do |txn|
|
|
21
|
+
# txn.produce("orders", '{"order_id":123}')
|
|
22
|
+
# raise "Error" # Both operations rolled back
|
|
23
|
+
# end
|
|
24
|
+
module Transaction
|
|
25
|
+
# Executes PGMQ operations atomically within a database transaction
|
|
26
|
+
#
|
|
27
|
+
# Obtains a connection from the pool, starts a transaction, and yields
|
|
28
|
+
# a client that uses that transaction for all operations.
|
|
29
|
+
#
|
|
30
|
+
# @yield [PGMQ::Client] transactional client using the same connection
|
|
31
|
+
# @return [Object] result of the block
|
|
32
|
+
# @raise [PGMQ::Errors::ConnectionError] if transaction fails
|
|
33
|
+
#
|
|
34
|
+
# @example
|
|
35
|
+
# client.transaction do |txn|
|
|
36
|
+
# msg_id = txn.produce("queue", '{"data":"test"}')
|
|
37
|
+
# txn.delete("queue", msg_id)
|
|
38
|
+
# end
|
|
39
|
+
def transaction
|
|
40
|
+
@connection.with_connection do |conn|
|
|
41
|
+
conn.transaction do
|
|
42
|
+
yield TransactionalClient.new(self, conn)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
rescue PG::Error, StandardError => e
|
|
46
|
+
raise PGMQ::Errors::ConnectionError, "Transaction failed: #{e.message}"
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Minimal wrapper that ensures all operations use the transaction connection
|
|
50
|
+
#
|
|
51
|
+
# @private
|
|
52
|
+
class TransactionalClient
|
|
53
|
+
# @param parent [PGMQ::Client] parent client instance
|
|
54
|
+
# @param conn [PG::Connection] transaction connection
|
|
55
|
+
def initialize(
|
|
56
|
+
parent,
|
|
57
|
+
conn
|
|
58
|
+
)
|
|
59
|
+
@parent = parent
|
|
60
|
+
@conn = conn
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Forward all method calls to parent, but use our transaction connection
|
|
64
|
+
# @param method [Symbol] method name
|
|
65
|
+
# @return [Object] result of method call
|
|
66
|
+
def method_missing(method, ...)
|
|
67
|
+
@parent.respond_to?(method, true) ? @parent.__send__(method, ...) : super
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Check if method exists on parent
|
|
71
|
+
# @param method [Symbol] method name
|
|
72
|
+
# @param include_private [Boolean] include private methods
|
|
73
|
+
# @return [Boolean] true if method exists
|
|
74
|
+
def respond_to_missing?(
|
|
75
|
+
method,
|
|
76
|
+
include_private = false
|
|
77
|
+
)
|
|
78
|
+
@parent.respond_to?(method, include_private) || super
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Inject our transaction connection instead of using the pool
|
|
82
|
+
def with_connection
|
|
83
|
+
yield @conn
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Expose parent's connection (for tests/inspection)
|
|
87
|
+
def connection
|
|
88
|
+
@parent.connection
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
data/lib/pgmq/version.rb
ADDED
data/lib/pgmq.rb
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'zeitwerk'
|
|
4
|
+
require 'time'
|
|
5
|
+
|
|
6
|
+
loader = Zeitwerk::Loader.for_gem
|
|
7
|
+
loader.inflector.inflect(
|
|
8
|
+
'pgmq' => 'PGMQ'
|
|
9
|
+
)
|
|
10
|
+
loader.setup
|
|
11
|
+
loader.eager_load
|
|
12
|
+
|
|
13
|
+
# PGMQ - Low-level Ruby client for Postgres Message Queue
|
|
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).
|
|
18
|
+
#
|
|
19
|
+
# @example Basic usage
|
|
20
|
+
# require 'pgmq'
|
|
21
|
+
#
|
|
22
|
+
# # Create client with connection parameters
|
|
23
|
+
# client = PGMQ::Client.new(
|
|
24
|
+
# host: 'localhost',
|
|
25
|
+
# port: 5432,
|
|
26
|
+
# dbname: 'mydb',
|
|
27
|
+
# user: 'postgres',
|
|
28
|
+
# password: 'postgres'
|
|
29
|
+
# )
|
|
30
|
+
#
|
|
31
|
+
# # Or with connection string
|
|
32
|
+
# client = PGMQ::Client.new('postgres://localhost/mydb')
|
|
33
|
+
#
|
|
34
|
+
# # Basic queue operations
|
|
35
|
+
# client.create('orders')
|
|
36
|
+
# msg_id = client.produce('orders', '{"order_id":123}')
|
|
37
|
+
# msg = client.read('orders', vt: 30)
|
|
38
|
+
# client.delete('orders', msg.msg_id)
|
|
39
|
+
# client.drop_queue('orders')
|
|
40
|
+
module PGMQ
|
|
41
|
+
class << self
|
|
42
|
+
# Convenience method to create a new client
|
|
43
|
+
#
|
|
44
|
+
# @return [PGMQ::Client] new client instance
|
|
45
|
+
# @see PGMQ::Client#initialize
|
|
46
|
+
#
|
|
47
|
+
# @example
|
|
48
|
+
# client = PGMQ.new('postgres://localhost/mydb')
|
|
49
|
+
def new(*, **)
|
|
50
|
+
Client.new(*, **)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
data/pgmq-ruby.gemspec
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'lib/pgmq/version'
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |spec|
|
|
6
|
+
spec.name = 'pgmq-ruby'
|
|
7
|
+
spec.version = PGMQ::VERSION
|
|
8
|
+
spec.authors = ['Maciej Mensfeld']
|
|
9
|
+
spec.email = ['maciej@mensfeld.pl']
|
|
10
|
+
|
|
11
|
+
spec.summary = 'Ruby client for PGMQ (Postgres Message Queue)'
|
|
12
|
+
spec.description = 'A Ruby driver for PGMQ - a lightweight message queue built on PostgreSQL. ' \
|
|
13
|
+
'Like AWS SQS and RSMQ, but on Postgres.'
|
|
14
|
+
spec.homepage = 'https://github.com/mensfeld/pgmq-ruby'
|
|
15
|
+
spec.license = 'LGPL-3.0'
|
|
16
|
+
spec.required_ruby_version = '>= 3.2.0'
|
|
17
|
+
|
|
18
|
+
spec.metadata['homepage_uri'] = spec.homepage
|
|
19
|
+
spec.metadata['source_code_uri'] = 'https://github.com/mensfeld/pgmq-ruby'
|
|
20
|
+
spec.metadata['changelog_uri'] = 'https://github.com/mensfeld/pgmq-ruby/blob/master/CHANGELOG.md'
|
|
21
|
+
spec.metadata['bug_tracker_uri'] = 'https://github.com/mensfeld/pgmq-ruby/issues'
|
|
22
|
+
spec.metadata['documentation_uri'] = 'https://github.com/mensfeld/pgmq-ruby#readme'
|
|
23
|
+
spec.metadata['rubygems_mfa_required'] = 'true'
|
|
24
|
+
|
|
25
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(spec|examples)/}) }
|
|
26
|
+
spec.require_paths = ['lib']
|
|
27
|
+
|
|
28
|
+
# Runtime dependencies
|
|
29
|
+
spec.add_dependency 'connection_pool', '~> 2.4'
|
|
30
|
+
spec.add_dependency 'pg', '~> 1.5'
|
|
31
|
+
spec.add_dependency 'zeitwerk', '~> 2.6'
|
|
32
|
+
end
|
data/renovate.json
ADDED
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.
|
|
4
|
+
version: 0.4.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Maciej Mensfeld
|
|
@@ -9,6 +9,20 @@ bindir: bin
|
|
|
9
9
|
cert_chain: []
|
|
10
10
|
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: connection_pool
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '2.4'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '2.4'
|
|
12
26
|
- !ruby/object:Gem::Dependency
|
|
13
27
|
name: pg
|
|
14
28
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -23,13 +37,60 @@ dependencies:
|
|
|
23
37
|
- - "~>"
|
|
24
38
|
- !ruby/object:Gem::Version
|
|
25
39
|
version: '1.5'
|
|
26
|
-
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: zeitwerk
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - "~>"
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '2.6'
|
|
47
|
+
type: :runtime
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - "~>"
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '2.6'
|
|
54
|
+
description: A Ruby driver for PGMQ - a lightweight message queue built on PostgreSQL.
|
|
55
|
+
Like AWS SQS and RSMQ, but on Postgres.
|
|
27
56
|
email:
|
|
28
|
-
-
|
|
57
|
+
- maciej@mensfeld.pl
|
|
29
58
|
executables: []
|
|
30
59
|
extensions: []
|
|
31
60
|
extra_rdoc_files: []
|
|
32
|
-
files:
|
|
61
|
+
files:
|
|
62
|
+
- ".coditsu/ci.yml"
|
|
63
|
+
- ".github/workflows/ci.yml"
|
|
64
|
+
- ".github/workflows/push.yml"
|
|
65
|
+
- ".gitignore"
|
|
66
|
+
- ".rspec"
|
|
67
|
+
- ".ruby-version"
|
|
68
|
+
- ".yard-lint.yml"
|
|
69
|
+
- CHANGELOG.md
|
|
70
|
+
- Gemfile
|
|
71
|
+
- Gemfile.lock
|
|
72
|
+
- LICENSE
|
|
73
|
+
- README.md
|
|
74
|
+
- Rakefile
|
|
75
|
+
- docker-compose.yml
|
|
76
|
+
- lib/pgmq.rb
|
|
77
|
+
- lib/pgmq/client.rb
|
|
78
|
+
- lib/pgmq/client/consumer.rb
|
|
79
|
+
- lib/pgmq/client/maintenance.rb
|
|
80
|
+
- lib/pgmq/client/message_lifecycle.rb
|
|
81
|
+
- lib/pgmq/client/metrics.rb
|
|
82
|
+
- lib/pgmq/client/multi_queue.rb
|
|
83
|
+
- lib/pgmq/client/producer.rb
|
|
84
|
+
- lib/pgmq/client/queue_management.rb
|
|
85
|
+
- lib/pgmq/connection.rb
|
|
86
|
+
- lib/pgmq/errors.rb
|
|
87
|
+
- lib/pgmq/message.rb
|
|
88
|
+
- lib/pgmq/metrics.rb
|
|
89
|
+
- lib/pgmq/queue_metadata.rb
|
|
90
|
+
- lib/pgmq/transaction.rb
|
|
91
|
+
- lib/pgmq/version.rb
|
|
92
|
+
- pgmq-ruby.gemspec
|
|
93
|
+
- renovate.json
|
|
33
94
|
homepage: https://github.com/mensfeld/pgmq-ruby
|
|
34
95
|
licenses:
|
|
35
96
|
- LGPL-3.0
|
|
@@ -38,6 +99,7 @@ metadata:
|
|
|
38
99
|
source_code_uri: https://github.com/mensfeld/pgmq-ruby
|
|
39
100
|
changelog_uri: https://github.com/mensfeld/pgmq-ruby/blob/master/CHANGELOG.md
|
|
40
101
|
bug_tracker_uri: https://github.com/mensfeld/pgmq-ruby/issues
|
|
102
|
+
documentation_uri: https://github.com/mensfeld/pgmq-ruby#readme
|
|
41
103
|
rubygems_mfa_required: 'true'
|
|
42
104
|
rdoc_options: []
|
|
43
105
|
require_paths:
|
|
@@ -53,7 +115,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
53
115
|
- !ruby/object:Gem::Version
|
|
54
116
|
version: '0'
|
|
55
117
|
requirements: []
|
|
56
|
-
rubygems_version:
|
|
118
|
+
rubygems_version: 4.0.3
|
|
57
119
|
specification_version: 4
|
|
58
120
|
summary: Ruby client for PGMQ (Postgres Message Queue)
|
|
59
121
|
test_files: []
|