pgbus 0.21.3 → 0.22.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/CHANGELOG.md +51 -2
- data/README.md +1 -1
- data/Rakefile +8 -1
- data/lib/pgbus/active_job/executor.rb +58 -29
- data/lib/pgbus/batch/sweep.rb +2 -0
- data/lib/pgbus/batch.rb +4 -1
- data/lib/pgbus/client/native_statements.rb +420 -0
- data/lib/pgbus/client.rb +15 -7
- data/lib/pgbus/concurrency/blocked_execution.rb +1 -0
- data/lib/pgbus/configuration.rb +87 -0
- data/lib/pgbus/event.rb +10 -0
- data/lib/pgbus/event_bus/handler.rb +2 -0
- data/lib/pgbus/pgmq_schema/fixups/pgmq_v1.13.1.sql +265 -0
- data/lib/pgbus/pgmq_schema/pgmq_v1.13.1.sql +2324 -0
- data/lib/pgbus/pgmq_schema.rb +34 -4
- data/lib/pgbus/process/completer.rb +187 -0
- data/lib/pgbus/process/consumer.rb +15 -1
- data/lib/pgbus/process/worker.rb +29 -1
- data/lib/pgbus/serializer.rb +1 -0
- data/lib/pgbus/version.rb +1 -1
- data/lib/pgbus/visibility_heartbeat.rb +16 -12
- data/sig/pgbus/client.rbs +1 -0
- data/sig/pgbus/configuration.rbs +8 -0
- data/sig/pgbus/process/completer.rbs +26 -0
- metadata +6 -1
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
-- PGMQ 1.13.1 table fixup.
|
|
2
|
+
--
|
|
3
|
+
-- Adapted from the upstream migration
|
|
4
|
+
-- pgmq-extension/sql/pgmq--1.13.0--1.13.1.sql at tag v1.13.1
|
|
5
|
+
-- (blob 16cbbe2763ec6508570a80e7ef08b7bee3b11ffd). Only the parts pgbus's
|
|
6
|
+
-- upgrade cannot already reproduce are here: dropping every function and
|
|
7
|
+
-- composite type and re-running the target schema recreates functions and
|
|
8
|
+
-- types, but never touches an existing table, its column defaults or its
|
|
9
|
+
-- grants.
|
|
10
|
+
--
|
|
11
|
+
-- Left out on purpose: upstream's loop that re-creates each queue's NOTIFY
|
|
12
|
+
-- trigger with the queue name as its argument. The upgrade's
|
|
13
|
+
-- reinstall_notify_triggers_sql step replays the new pgmq.enable_notify_insert
|
|
14
|
+
-- for every row of pgmq.notify_insert_throttle, which re-creates the trigger
|
|
15
|
+
-- the same way.
|
|
16
|
+
--
|
|
17
|
+
-- One deliberate deviation from upstream: the rebind-defaults block selects
|
|
18
|
+
-- pgmq's tables by name in the pgmq schema instead of by extension membership
|
|
19
|
+
-- (pg_depend deptype 'e'). An embedded install has no extension, so upstream's
|
|
20
|
+
-- selection would find nothing and the block would never run. The pg_monitor
|
|
21
|
+
-- blocks are upstream's, unchanged.
|
|
22
|
+
--
|
|
23
|
+
-- Every file in this directory MUST be idempotent and self-guarded. The
|
|
24
|
+
-- upgrade migration applies each fixup between the recorded installed version
|
|
25
|
+
-- and the target, and an install whose version was never recorded gets all of
|
|
26
|
+
-- them, so a fixup can be replayed against a database that already has it.
|
|
27
|
+
|
|
28
|
+
-- Queue tables created by the old functions can retain defaults bound to a
|
|
29
|
+
-- function in a writable schema. Replacing the functions does not replace
|
|
30
|
+
-- those stored expression dependencies. Rebind only PGMQ's own defaults that
|
|
31
|
+
-- refer to a non-catalog now() or to_timestamp(). Include partition children,
|
|
32
|
+
-- since their defaults can be stored separately from the parent.
|
|
33
|
+
DO $pgmq_rebind_defaults$
|
|
34
|
+
DECLARE
|
|
35
|
+
def RECORD;
|
|
36
|
+
BEGIN
|
|
37
|
+
FOR def IN
|
|
38
|
+
WITH RECURSIVE pgmq_tables AS (
|
|
39
|
+
-- pgbus: pgmq schema tables by name, not extension members (see header).
|
|
40
|
+
SELECT c.oid, c.relname
|
|
41
|
+
FROM pg_catalog.pg_class c
|
|
42
|
+
WHERE c.relnamespace = 'pgmq'::pg_catalog.regnamespace
|
|
43
|
+
AND c.relkind IN ('r', 'p')
|
|
44
|
+
), roots AS (
|
|
45
|
+
SELECT c.oid AS relid,
|
|
46
|
+
CASE c.relname
|
|
47
|
+
WHEN 'meta' THEN 'meta'
|
|
48
|
+
WHEN 'topic_bindings' THEN 'topic_bindings'
|
|
49
|
+
ELSE 'notify_insert_throttle'
|
|
50
|
+
END AS kind
|
|
51
|
+
FROM pgmq_tables c
|
|
52
|
+
WHERE c.relname IN ('meta', 'topic_bindings', 'notify_insert_throttle')
|
|
53
|
+
UNION
|
|
54
|
+
SELECT c.oid, names.kind
|
|
55
|
+
FROM pgmq_tables core
|
|
56
|
+
JOIN pgmq.meta m ON core.relname = 'meta'
|
|
57
|
+
CROSS JOIN LATERAL (
|
|
58
|
+
VALUES (pgmq.format_table_name(m.queue_name::pg_catalog.text, 'q'::pg_catalog.text), 'queue'),
|
|
59
|
+
(pgmq.format_table_name(m.queue_name::pg_catalog.text, 'a'::pg_catalog.text), 'archive')
|
|
60
|
+
) AS names(relname, kind)
|
|
61
|
+
JOIN pg_catalog.pg_class c
|
|
62
|
+
ON c.relnamespace = 'pgmq'::pg_catalog.regnamespace
|
|
63
|
+
AND c.relname = names.relname
|
|
64
|
+
AND c.relkind IN ('r', 'p')
|
|
65
|
+
), managed AS (
|
|
66
|
+
SELECT relid, kind FROM roots
|
|
67
|
+
UNION
|
|
68
|
+
SELECT i.inhrelid, m.kind
|
|
69
|
+
FROM managed m
|
|
70
|
+
JOIN pg_catalog.pg_inherits i ON i.inhparent = m.relid
|
|
71
|
+
)
|
|
72
|
+
SELECT n.nspname, c.relname, a.attname,
|
|
73
|
+
pg_catalog.pg_get_expr(ad.adbin, ad.adrelid) AS old_expression,
|
|
74
|
+
CASE WHEN m.kind = 'notify_insert_throttle'
|
|
75
|
+
THEN 'pg_catalog.to_timestamp(0)'
|
|
76
|
+
ELSE 'pg_catalog.now()'
|
|
77
|
+
END AS replacement,
|
|
78
|
+
EXISTS (
|
|
79
|
+
SELECT 1
|
|
80
|
+
FROM pg_catalog.pg_depend d
|
|
81
|
+
JOIN pg_catalog.pg_proc p ON p.oid = d.refobjid
|
|
82
|
+
JOIN pg_catalog.pg_namespace pn ON pn.oid = p.pronamespace
|
|
83
|
+
WHERE d.classid = 'pg_catalog.pg_attrdef'::pg_catalog.regclass
|
|
84
|
+
AND d.objid = ad.oid
|
|
85
|
+
AND d.refclassid = 'pg_catalog.pg_proc'::pg_catalog.regclass
|
|
86
|
+
AND pn.nspname <> 'pg_catalog'
|
|
87
|
+
AND ((m.kind = 'notify_insert_throttle'
|
|
88
|
+
AND p.proname = 'to_timestamp'
|
|
89
|
+
AND p.proargtypes[0] = 'pg_catalog.int4'::pg_catalog.regtype
|
|
90
|
+
AND pg_catalog.pg_get_expr(ad.adbin, ad.adrelid) =
|
|
91
|
+
pg_catalog.format('%I.to_timestamp(0)', pn.nspname))
|
|
92
|
+
OR (m.kind <> 'notify_insert_throttle'
|
|
93
|
+
AND p.proname = 'now'
|
|
94
|
+
AND p.pronargs = 0
|
|
95
|
+
AND pg_catalog.pg_get_expr(ad.adbin, ad.adrelid) =
|
|
96
|
+
pg_catalog.format('%I.now()', pn.nspname)))
|
|
97
|
+
) AS known_shape
|
|
98
|
+
FROM managed m
|
|
99
|
+
JOIN pg_catalog.pg_class c ON c.oid = m.relid
|
|
100
|
+
JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
|
|
101
|
+
JOIN pg_catalog.pg_attribute a ON a.attrelid = c.oid
|
|
102
|
+
JOIN pg_catalog.pg_attrdef ad
|
|
103
|
+
ON ad.adrelid = c.oid AND ad.adnum = a.attnum
|
|
104
|
+
WHERE ((m.kind = 'meta' AND a.attname = 'created_at')
|
|
105
|
+
OR (m.kind = 'topic_bindings' AND a.attname = 'bound_at')
|
|
106
|
+
OR (m.kind = 'notify_insert_throttle' AND a.attname = 'last_notified_at')
|
|
107
|
+
OR (m.kind = 'queue' AND a.attname = 'enqueued_at')
|
|
108
|
+
OR (m.kind = 'archive' AND a.attname IN ('enqueued_at', 'archived_at')))
|
|
109
|
+
AND EXISTS (
|
|
110
|
+
SELECT 1
|
|
111
|
+
FROM pg_catalog.pg_depend d
|
|
112
|
+
JOIN pg_catalog.pg_proc p ON p.oid = d.refobjid
|
|
113
|
+
JOIN pg_catalog.pg_namespace pn ON pn.oid = p.pronamespace
|
|
114
|
+
WHERE d.classid = 'pg_catalog.pg_attrdef'::pg_catalog.regclass
|
|
115
|
+
AND d.objid = ad.oid
|
|
116
|
+
AND d.refclassid = 'pg_catalog.pg_proc'::pg_catalog.regclass
|
|
117
|
+
AND pn.nspname <> 'pg_catalog'
|
|
118
|
+
)
|
|
119
|
+
LOOP
|
|
120
|
+
IF NOT def.known_shape THEN
|
|
121
|
+
RAISE EXCEPTION 'PGMQ upgrade cannot safely rebind custom default on %.%.%: %; inspect the non-catalog function dependency before retrying',
|
|
122
|
+
def.nspname, def.relname, def.attname, def.old_expression;
|
|
123
|
+
END IF;
|
|
124
|
+
BEGIN
|
|
125
|
+
EXECUTE pg_catalog.format(
|
|
126
|
+
'ALTER TABLE %I.%I ALTER COLUMN %I SET DEFAULT %s',
|
|
127
|
+
def.nspname, def.relname, def.attname, def.replacement
|
|
128
|
+
);
|
|
129
|
+
EXCEPTION WHEN insufficient_privilege THEN
|
|
130
|
+
RAISE EXCEPTION 'PGMQ upgrade cannot rebind default on %.%.%; use a role that can update the extension and alter the table, or a superuser',
|
|
131
|
+
def.nspname, def.relname, def.attname;
|
|
132
|
+
END;
|
|
133
|
+
END LOOP;
|
|
134
|
+
END
|
|
135
|
+
$pgmq_rebind_defaults$;
|
|
136
|
+
|
|
137
|
+
-- pg_monitor could read every table and sequence in the pgmq schema, including
|
|
138
|
+
-- queue and archive tables. Replace that broad access with the minimum needed
|
|
139
|
+
-- by pgmq.metrics(), pgmq.metrics_all(), and pgmq.list_queues().
|
|
140
|
+
|
|
141
|
+
-- 1. Remove default privileges that granted SELECT on every new table and
|
|
142
|
+
-- sequence. The grantor can differ from the role running this upgrade.
|
|
143
|
+
DO $$
|
|
144
|
+
DECLARE
|
|
145
|
+
acl RECORD;
|
|
146
|
+
BEGIN
|
|
147
|
+
FOR acl IN
|
|
148
|
+
SELECT DISTINCT d.defaclrole::pg_catalog.regrole AS role_name
|
|
149
|
+
FROM pg_catalog.pg_default_acl d
|
|
150
|
+
JOIN pg_catalog.pg_namespace n ON n.oid = d.defaclnamespace
|
|
151
|
+
CROSS JOIN LATERAL pg_catalog.aclexplode(d.defaclacl) a
|
|
152
|
+
WHERE n.nspname = 'pgmq'
|
|
153
|
+
AND d.defaclobjtype IN ('r', 'S')
|
|
154
|
+
AND a.grantee = 'pg_monitor'::pg_catalog.regrole
|
|
155
|
+
LOOP
|
|
156
|
+
BEGIN
|
|
157
|
+
EXECUTE pg_catalog.format(
|
|
158
|
+
'ALTER DEFAULT PRIVILEGES FOR ROLE %s IN SCHEMA pgmq REVOKE SELECT ON TABLES FROM pg_monitor',
|
|
159
|
+
acl.role_name
|
|
160
|
+
);
|
|
161
|
+
EXECUTE pg_catalog.format(
|
|
162
|
+
'ALTER DEFAULT PRIVILEGES FOR ROLE %s IN SCHEMA pgmq REVOKE SELECT ON SEQUENCES FROM pg_monitor',
|
|
163
|
+
acl.role_name
|
|
164
|
+
);
|
|
165
|
+
EXCEPTION WHEN insufficient_privilege OR undefined_object THEN
|
|
166
|
+
RAISE WARNING 'pgmq: could not remove default privileges of role % granting SELECT to pg_monitor in schema pgmq; run "ALTER DEFAULT PRIVILEGES FOR ROLE % IN SCHEMA pgmq REVOKE SELECT ON TABLES FROM pg_monitor" (and the same ON SEQUENCES) as that role or a superuser', acl.role_name, acl.role_name;
|
|
167
|
+
END;
|
|
168
|
+
END LOOP;
|
|
169
|
+
END;
|
|
170
|
+
$$;
|
|
171
|
+
|
|
172
|
+
-- 2. Revoke whole-relation SELECT from pg_monitor on every relation in pgmq
|
|
173
|
+
-- except meta. Step 4 reports grants that the upgrade role cannot remove.
|
|
174
|
+
DO $$
|
|
175
|
+
DECLARE
|
|
176
|
+
rel RECORD;
|
|
177
|
+
BEGIN
|
|
178
|
+
FOR rel IN
|
|
179
|
+
SELECT DISTINCT c.oid::pg_catalog.regclass AS rel_name,
|
|
180
|
+
c.relkind = 'S' AS is_sequence
|
|
181
|
+
FROM pg_catalog.pg_class c
|
|
182
|
+
JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
|
|
183
|
+
CROSS JOIN LATERAL pg_catalog.aclexplode(c.relacl) a
|
|
184
|
+
WHERE n.nspname = 'pgmq'
|
|
185
|
+
AND c.relkind IN ('r', 'p', 'v', 'm', 'f', 'S')
|
|
186
|
+
AND c.relname <> 'meta'
|
|
187
|
+
AND a.grantee = 'pg_monitor'::pg_catalog.regrole
|
|
188
|
+
AND a.privilege_type = 'SELECT'
|
|
189
|
+
LOOP
|
|
190
|
+
BEGIN
|
|
191
|
+
EXECUTE pg_catalog.format(
|
|
192
|
+
'REVOKE SELECT ON %s %s FROM pg_monitor',
|
|
193
|
+
CASE WHEN rel.is_sequence THEN 'SEQUENCE' ELSE 'TABLE' END,
|
|
194
|
+
rel.rel_name
|
|
195
|
+
);
|
|
196
|
+
EXCEPTION WHEN insufficient_privilege THEN
|
|
197
|
+
RAISE WARNING 'pgmq: could not revoke SELECT on % from pg_monitor; run "REVOKE SELECT ON % FROM pg_monitor" as its owner or a superuser', rel.rel_name, rel.rel_name;
|
|
198
|
+
END;
|
|
199
|
+
END LOOP;
|
|
200
|
+
END;
|
|
201
|
+
$$;
|
|
202
|
+
|
|
203
|
+
-- 3. Grant the minimal metrics access on each existing queue. A grant on a
|
|
204
|
+
-- partitioned parent is sufficient because metrics query the parent.
|
|
205
|
+
DO $$
|
|
206
|
+
DECLARE
|
|
207
|
+
q RECORD;
|
|
208
|
+
qtable TEXT;
|
|
209
|
+
qseq TEXT;
|
|
210
|
+
BEGIN
|
|
211
|
+
FOR q IN SELECT queue_name FROM pgmq.meta LOOP
|
|
212
|
+
qtable := pgmq.format_table_name(
|
|
213
|
+
q.queue_name::pg_catalog.text,
|
|
214
|
+
'q'::pg_catalog.text
|
|
215
|
+
);
|
|
216
|
+
qseq := qtable || '_msg_id_seq';
|
|
217
|
+
IF pg_catalog.to_regclass(pg_catalog.format('pgmq.%I', qtable)) IS NOT NULL THEN
|
|
218
|
+
BEGIN
|
|
219
|
+
EXECUTE pg_catalog.format(
|
|
220
|
+
'GRANT SELECT (vt, enqueued_at) ON pgmq.%I TO pg_monitor',
|
|
221
|
+
qtable
|
|
222
|
+
);
|
|
223
|
+
EXCEPTION WHEN insufficient_privilege THEN
|
|
224
|
+
RAISE WARNING 'pgmq: could not grant SELECT (vt, enqueued_at) on pgmq.% to pg_monitor; run it as the table owner or a superuser so queue metrics work for pg_monitor', pg_catalog.quote_ident(qtable);
|
|
225
|
+
END;
|
|
226
|
+
END IF;
|
|
227
|
+
IF pg_catalog.to_regclass(pg_catalog.format('pgmq.%I', qseq)) IS NOT NULL THEN
|
|
228
|
+
BEGIN
|
|
229
|
+
EXECUTE pg_catalog.format(
|
|
230
|
+
'GRANT SELECT ON SEQUENCE pgmq.%I TO pg_monitor',
|
|
231
|
+
qseq
|
|
232
|
+
);
|
|
233
|
+
EXCEPTION WHEN insufficient_privilege THEN
|
|
234
|
+
RAISE WARNING 'pgmq: could not grant SELECT on sequence pgmq.% to pg_monitor; run it as the sequence owner or a superuser so queue metrics work for pg_monitor', pg_catalog.quote_ident(qseq);
|
|
235
|
+
END;
|
|
236
|
+
END IF;
|
|
237
|
+
END LOOP;
|
|
238
|
+
END;
|
|
239
|
+
$$;
|
|
240
|
+
|
|
241
|
+
-- 4. Report any heap-like relation that still exposes whole-table SELECT to
|
|
242
|
+
-- pg_monitor. Column grants live in pg_attribute and are not reported here.
|
|
243
|
+
DO $$
|
|
244
|
+
DECLARE
|
|
245
|
+
leftover TEXT;
|
|
246
|
+
BEGIN
|
|
247
|
+
SELECT pg_catalog.string_agg(
|
|
248
|
+
c.oid::pg_catalog.regclass::pg_catalog.text,
|
|
249
|
+
', ' ORDER BY c.oid::pg_catalog.regclass::pg_catalog.text
|
|
250
|
+
)
|
|
251
|
+
INTO leftover
|
|
252
|
+
FROM pg_catalog.pg_class c
|
|
253
|
+
JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
|
|
254
|
+
CROSS JOIN LATERAL pg_catalog.aclexplode(c.relacl) a
|
|
255
|
+
WHERE n.nspname = 'pgmq'
|
|
256
|
+
AND c.relkind IN ('r', 'p', 'v', 'm', 'f')
|
|
257
|
+
AND c.relname <> 'meta'
|
|
258
|
+
AND a.grantee = 'pg_monitor'::pg_catalog.regrole
|
|
259
|
+
AND a.privilege_type = 'SELECT';
|
|
260
|
+
|
|
261
|
+
IF leftover IS NOT NULL THEN
|
|
262
|
+
RAISE WARNING 'pgmq: pg_monitor still has whole-table SELECT on: %. These may expose message payloads; revoke them as the object owner or a superuser.', leftover;
|
|
263
|
+
END IF;
|
|
264
|
+
END;
|
|
265
|
+
$$;
|