pg-boss 10.3.3 → 10.4.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.
- package/.claude/settings.local.json +25 -0
- package/dist/attorney.d.ts +19 -0
- package/dist/attorney.d.ts.map +1 -0
- package/dist/attorney.js +227 -0
- package/dist/bam.d.ts +14 -0
- package/dist/bam.d.ts.map +1 -0
- package/dist/bam.js +114 -0
- package/dist/boss.d.ts +16 -0
- package/dist/boss.d.ts.map +1 -0
- package/dist/boss.js +163 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +333 -0
- package/dist/contractor.d.ts +22 -0
- package/dist/contractor.d.ts.map +1 -0
- package/dist/contractor.js +81 -0
- package/dist/db.d.ts +16 -0
- package/dist/db.d.ts.map +1 -0
- package/dist/db.js +46 -0
- package/dist/index.d.ts +72 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +266 -0
- package/dist/manager.d.ts +93 -0
- package/dist/manager.d.ts.map +1 -0
- package/dist/manager.js +746 -0
- package/dist/migrationStore.d.ts +7 -0
- package/dist/migrationStore.d.ts.map +1 -0
- package/dist/migrationStore.js +425 -0
- package/dist/plans.d.ts +102 -0
- package/dist/plans.d.ts.map +1 -0
- package/dist/plans.js +1220 -0
- package/dist/spy.d.ts +23 -0
- package/dist/spy.d.ts.map +1 -0
- package/dist/spy.js +73 -0
- package/dist/timekeeper.d.ts +34 -0
- package/dist/timekeeper.d.ts.map +1 -0
- package/dist/timekeeper.js +158 -0
- package/dist/tools.d.ts +18 -0
- package/dist/tools.d.ts.map +1 -0
- package/dist/tools.js +45 -0
- package/dist/types.d.ts +301 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +1 -0
- package/dist/worker.d.ts +43 -0
- package/dist/worker.d.ts.map +1 -0
- package/dist/worker.js +113 -0
- package/package.json +1 -1
- package/src/plans.js +22 -7
package/dist/plans.js
ADDED
|
@@ -0,0 +1,1220 @@
|
|
|
1
|
+
const DEFAULT_SCHEMA = 'pgboss';
|
|
2
|
+
const MIGRATE_RACE_MESSAGE = 'division by zero';
|
|
3
|
+
const CREATE_RACE_MESSAGE = 'already exists';
|
|
4
|
+
const SINGLE_QUOTE_REGEX = /'/g;
|
|
5
|
+
const FIFTEEN_MINUTES = 60 * 15;
|
|
6
|
+
const FORTEEN_DAYS = 60 * 60 * 24 * 14;
|
|
7
|
+
const SEVEN_DAYS = 60 * 60 * 24 * 7;
|
|
8
|
+
const JOB_STATES = Object.freeze({
|
|
9
|
+
created: 'created',
|
|
10
|
+
retry: 'retry',
|
|
11
|
+
active: 'active',
|
|
12
|
+
completed: 'completed',
|
|
13
|
+
cancelled: 'cancelled',
|
|
14
|
+
failed: 'failed'
|
|
15
|
+
});
|
|
16
|
+
const QUEUE_POLICIES = Object.freeze({
|
|
17
|
+
standard: 'standard',
|
|
18
|
+
short: 'short',
|
|
19
|
+
singleton: 'singleton',
|
|
20
|
+
stately: 'stately',
|
|
21
|
+
exclusive: 'exclusive'
|
|
22
|
+
});
|
|
23
|
+
const QUEUE_DEFAULTS = {
|
|
24
|
+
expire_seconds: FIFTEEN_MINUTES,
|
|
25
|
+
retention_seconds: FORTEEN_DAYS,
|
|
26
|
+
deletion_seconds: SEVEN_DAYS,
|
|
27
|
+
retry_limit: 2,
|
|
28
|
+
retry_delay: 0,
|
|
29
|
+
warning_queued: 0,
|
|
30
|
+
retry_backoff: false,
|
|
31
|
+
partition: false
|
|
32
|
+
};
|
|
33
|
+
const COMMON_JOB_TABLE = 'job_common';
|
|
34
|
+
function create(schema, version, options) {
|
|
35
|
+
const commands = [
|
|
36
|
+
options?.createSchema ? createSchema(schema) : '',
|
|
37
|
+
createEnumJobState(schema),
|
|
38
|
+
createTableVersion(schema),
|
|
39
|
+
createTableQueue(schema),
|
|
40
|
+
createTableSchedule(schema),
|
|
41
|
+
createTableSubscription(schema),
|
|
42
|
+
createTableBam(schema),
|
|
43
|
+
jobTableFormatFunction(schema),
|
|
44
|
+
jobTableRunFunction(schema),
|
|
45
|
+
jobTableRunAsyncFunction(schema),
|
|
46
|
+
createTableJob(schema),
|
|
47
|
+
createPrimaryKeyJob(schema),
|
|
48
|
+
createTableJobCommon(schema),
|
|
49
|
+
createQueueFunction(schema),
|
|
50
|
+
deleteQueueFunction(schema),
|
|
51
|
+
insertVersion(schema, version)
|
|
52
|
+
];
|
|
53
|
+
return locked(schema, commands);
|
|
54
|
+
}
|
|
55
|
+
function createSchema(schema) {
|
|
56
|
+
return `CREATE SCHEMA IF NOT EXISTS ${schema}`;
|
|
57
|
+
}
|
|
58
|
+
function createEnumJobState(schema) {
|
|
59
|
+
// ENUM definition order is important
|
|
60
|
+
// base type is numeric and first values are less than last values
|
|
61
|
+
return `
|
|
62
|
+
CREATE TYPE ${schema}.job_state AS ENUM (
|
|
63
|
+
'${JOB_STATES.created}',
|
|
64
|
+
'${JOB_STATES.retry}',
|
|
65
|
+
'${JOB_STATES.active}',
|
|
66
|
+
'${JOB_STATES.completed}',
|
|
67
|
+
'${JOB_STATES.cancelled}',
|
|
68
|
+
'${JOB_STATES.failed}'
|
|
69
|
+
)
|
|
70
|
+
`;
|
|
71
|
+
}
|
|
72
|
+
function createTableVersion(schema) {
|
|
73
|
+
return `
|
|
74
|
+
CREATE TABLE ${schema}.version (
|
|
75
|
+
version int primary key,
|
|
76
|
+
cron_on timestamp with time zone,
|
|
77
|
+
bam_on timestamp with time zone
|
|
78
|
+
)
|
|
79
|
+
`;
|
|
80
|
+
}
|
|
81
|
+
function createTableQueue(schema) {
|
|
82
|
+
return `
|
|
83
|
+
CREATE TABLE ${schema}.queue (
|
|
84
|
+
name text NOT NULL,
|
|
85
|
+
policy text NOT NULL,
|
|
86
|
+
retry_limit int NOT NULL,
|
|
87
|
+
retry_delay int NOT NULL,
|
|
88
|
+
retry_backoff bool NOT NULL,
|
|
89
|
+
retry_delay_max int,
|
|
90
|
+
expire_seconds int NOT NULL,
|
|
91
|
+
retention_seconds int NOT NULL,
|
|
92
|
+
deletion_seconds int NOT NULL,
|
|
93
|
+
dead_letter text REFERENCES ${schema}.queue (name) CHECK (dead_letter IS DISTINCT FROM name),
|
|
94
|
+
partition bool NOT NULL,
|
|
95
|
+
table_name text NOT NULL,
|
|
96
|
+
deferred_count int NOT NULL default 0,
|
|
97
|
+
queued_count int NOT NULL default 0,
|
|
98
|
+
warning_queued int NOT NULL default 0,
|
|
99
|
+
active_count int NOT NULL default 0,
|
|
100
|
+
total_count int NOT NULL default 0,
|
|
101
|
+
singletons_active text[],
|
|
102
|
+
monitor_on timestamp with time zone,
|
|
103
|
+
maintain_on timestamp with time zone,
|
|
104
|
+
created_on timestamp with time zone not null default now(),
|
|
105
|
+
updated_on timestamp with time zone not null default now(),
|
|
106
|
+
PRIMARY KEY (name)
|
|
107
|
+
)
|
|
108
|
+
`;
|
|
109
|
+
}
|
|
110
|
+
function createTableSchedule(schema) {
|
|
111
|
+
return `
|
|
112
|
+
CREATE TABLE ${schema}.schedule (
|
|
113
|
+
name text REFERENCES ${schema}.queue ON DELETE CASCADE,
|
|
114
|
+
key text not null DEFAULT '',
|
|
115
|
+
cron text not null,
|
|
116
|
+
timezone text,
|
|
117
|
+
data jsonb,
|
|
118
|
+
options jsonb,
|
|
119
|
+
created_on timestamp with time zone not null default now(),
|
|
120
|
+
updated_on timestamp with time zone not null default now(),
|
|
121
|
+
PRIMARY KEY (name, key)
|
|
122
|
+
)
|
|
123
|
+
`;
|
|
124
|
+
}
|
|
125
|
+
function createTableSubscription(schema) {
|
|
126
|
+
return `
|
|
127
|
+
CREATE TABLE ${schema}.subscription (
|
|
128
|
+
event text not null,
|
|
129
|
+
name text not null REFERENCES ${schema}.queue ON DELETE CASCADE,
|
|
130
|
+
created_on timestamp with time zone not null default now(),
|
|
131
|
+
updated_on timestamp with time zone not null default now(),
|
|
132
|
+
PRIMARY KEY(event, name)
|
|
133
|
+
)
|
|
134
|
+
`;
|
|
135
|
+
}
|
|
136
|
+
function createTableBam(schema) {
|
|
137
|
+
return `
|
|
138
|
+
CREATE TABLE ${schema}.bam (
|
|
139
|
+
id uuid PRIMARY KEY default gen_random_uuid(),
|
|
140
|
+
name text NOT NULL,
|
|
141
|
+
version int NOT NULL,
|
|
142
|
+
status text NOT NULL DEFAULT 'pending',
|
|
143
|
+
queue text,
|
|
144
|
+
table_name text NOT NULL,
|
|
145
|
+
command text NOT NULL,
|
|
146
|
+
error text,
|
|
147
|
+
created_on timestamp with time zone NOT NULL DEFAULT now(),
|
|
148
|
+
started_on timestamp with time zone,
|
|
149
|
+
completed_on timestamp with time zone
|
|
150
|
+
)
|
|
151
|
+
`;
|
|
152
|
+
}
|
|
153
|
+
function jobTableFormatFunction(schema) {
|
|
154
|
+
return `
|
|
155
|
+
CREATE FUNCTION ${schema}.job_table_format(command text, table_name text)
|
|
156
|
+
RETURNS text AS
|
|
157
|
+
$$
|
|
158
|
+
SELECT format(
|
|
159
|
+
replace(
|
|
160
|
+
replace(command, '.job', '.%1$I'),
|
|
161
|
+
'job_i', '%1$s_i'
|
|
162
|
+
),
|
|
163
|
+
table_name
|
|
164
|
+
);
|
|
165
|
+
$$
|
|
166
|
+
LANGUAGE sql IMMUTABLE;
|
|
167
|
+
`;
|
|
168
|
+
}
|
|
169
|
+
function jobTableRunFunction(schema) {
|
|
170
|
+
return `
|
|
171
|
+
CREATE FUNCTION ${schema}.job_table_run(command text, tbl_name text DEFAULT NULL, queue_name text DEFAULT NULL)
|
|
172
|
+
RETURNS VOID AS
|
|
173
|
+
$$
|
|
174
|
+
DECLARE
|
|
175
|
+
tbl RECORD;
|
|
176
|
+
BEGIN
|
|
177
|
+
IF queue_name IS NOT NULL THEN
|
|
178
|
+
SELECT table_name INTO tbl_name FROM ${schema}.queue WHERE name = queue_name;
|
|
179
|
+
END IF;
|
|
180
|
+
|
|
181
|
+
IF tbl_name IS NOT NULL THEN
|
|
182
|
+
EXECUTE ${schema}.job_table_format(command, tbl_name);
|
|
183
|
+
RETURN;
|
|
184
|
+
END IF;
|
|
185
|
+
|
|
186
|
+
EXECUTE ${schema}.job_table_format(command, '${COMMON_JOB_TABLE}');
|
|
187
|
+
|
|
188
|
+
FOR tbl IN SELECT table_name FROM ${schema}.queue WHERE partition = true
|
|
189
|
+
LOOP
|
|
190
|
+
EXECUTE ${schema}.job_table_format(command, tbl.table_name);
|
|
191
|
+
END LOOP;
|
|
192
|
+
END;
|
|
193
|
+
$$
|
|
194
|
+
LANGUAGE plpgsql;
|
|
195
|
+
`;
|
|
196
|
+
}
|
|
197
|
+
function jobTableRunAsyncFunction(schema) {
|
|
198
|
+
return `
|
|
199
|
+
CREATE FUNCTION ${schema}.job_table_run_async(command_name text, version int, command text, tbl_name text DEFAULT NULL, queue_name text DEFAULT NULL)
|
|
200
|
+
RETURNS VOID AS
|
|
201
|
+
$$
|
|
202
|
+
BEGIN
|
|
203
|
+
IF queue_name IS NOT NULL THEN
|
|
204
|
+
SELECT table_name INTO tbl_name FROM ${schema}.queue WHERE name = queue_name;
|
|
205
|
+
END IF;
|
|
206
|
+
|
|
207
|
+
IF tbl_name IS NOT NULL THEN
|
|
208
|
+
INSERT INTO ${schema}.bam (name, version, status, queue, table_name, command)
|
|
209
|
+
VALUES (
|
|
210
|
+
command_name,
|
|
211
|
+
version,
|
|
212
|
+
'pending',
|
|
213
|
+
queue_name,
|
|
214
|
+
tbl_name,
|
|
215
|
+
${schema}.job_table_format(command, tbl_name)
|
|
216
|
+
);
|
|
217
|
+
RETURN;
|
|
218
|
+
END IF;
|
|
219
|
+
|
|
220
|
+
INSERT INTO ${schema}.bam (name, version, status, queue, table_name, command)
|
|
221
|
+
SELECT
|
|
222
|
+
command_name,
|
|
223
|
+
version,
|
|
224
|
+
'pending',
|
|
225
|
+
NULL,
|
|
226
|
+
'${COMMON_JOB_TABLE}',
|
|
227
|
+
${schema}.job_table_format(command, '${COMMON_JOB_TABLE}')
|
|
228
|
+
UNION ALL
|
|
229
|
+
SELECT
|
|
230
|
+
command_name,
|
|
231
|
+
version,
|
|
232
|
+
'pending',
|
|
233
|
+
queue.name,
|
|
234
|
+
queue.table_name,
|
|
235
|
+
${schema}.job_table_format(command, queue.table_name)
|
|
236
|
+
FROM ${schema}.queue
|
|
237
|
+
WHERE partition = true;
|
|
238
|
+
END;
|
|
239
|
+
$$
|
|
240
|
+
LANGUAGE plpgsql;
|
|
241
|
+
`;
|
|
242
|
+
}
|
|
243
|
+
function createTableJob(schema) {
|
|
244
|
+
return `
|
|
245
|
+
CREATE TABLE ${schema}.job (
|
|
246
|
+
id uuid not null default gen_random_uuid(),
|
|
247
|
+
name text not null,
|
|
248
|
+
priority integer not null default(0),
|
|
249
|
+
data jsonb,
|
|
250
|
+
state ${schema}.job_state not null default '${JOB_STATES.created}',
|
|
251
|
+
retry_limit integer not null default ${QUEUE_DEFAULTS.retry_limit},
|
|
252
|
+
retry_count integer not null default 0,
|
|
253
|
+
retry_delay integer not null default ${QUEUE_DEFAULTS.retry_delay},
|
|
254
|
+
retry_backoff boolean not null default ${QUEUE_DEFAULTS.retry_backoff},
|
|
255
|
+
retry_delay_max integer,
|
|
256
|
+
expire_seconds int not null default ${QUEUE_DEFAULTS.expire_seconds},
|
|
257
|
+
deletion_seconds int not null default ${QUEUE_DEFAULTS.deletion_seconds},
|
|
258
|
+
singleton_key text,
|
|
259
|
+
singleton_on timestamp without time zone,
|
|
260
|
+
group_id text,
|
|
261
|
+
group_tier text,
|
|
262
|
+
start_after timestamp with time zone not null default now(),
|
|
263
|
+
created_on timestamp with time zone not null default now(),
|
|
264
|
+
started_on timestamp with time zone,
|
|
265
|
+
completed_on timestamp with time zone,
|
|
266
|
+
keep_until timestamp with time zone NOT NULL default now() + interval '${QUEUE_DEFAULTS.retention_seconds}',
|
|
267
|
+
output jsonb,
|
|
268
|
+
dead_letter text,
|
|
269
|
+
policy text
|
|
270
|
+
) PARTITION BY LIST (name)
|
|
271
|
+
`;
|
|
272
|
+
}
|
|
273
|
+
const JOB_COLUMNS_MIN = 'id, name, data, expire_seconds as "expireInSeconds", group_id as "groupId", group_tier as "groupTier"';
|
|
274
|
+
const JOB_COLUMNS_ALL = `${JOB_COLUMNS_MIN},
|
|
275
|
+
policy,
|
|
276
|
+
state,
|
|
277
|
+
priority,
|
|
278
|
+
retry_limit as "retryLimit",
|
|
279
|
+
retry_count as "retryCount",
|
|
280
|
+
retry_delay as "retryDelay",
|
|
281
|
+
retry_backoff as "retryBackoff",
|
|
282
|
+
retry_delay_max as "retryDelayMax",
|
|
283
|
+
start_after as "startAfter",
|
|
284
|
+
started_on as "startedOn",
|
|
285
|
+
singleton_key as "singletonKey",
|
|
286
|
+
singleton_on as "singletonOn",
|
|
287
|
+
deletion_seconds as "deleteAfterSeconds",
|
|
288
|
+
created_on as "createdOn",
|
|
289
|
+
completed_on as "completedOn",
|
|
290
|
+
keep_until as "keepUntil",
|
|
291
|
+
dead_letter as "deadLetter",
|
|
292
|
+
output
|
|
293
|
+
`;
|
|
294
|
+
function createTableJobCommon(schema) {
|
|
295
|
+
return `
|
|
296
|
+
CREATE TABLE ${schema}.${COMMON_JOB_TABLE} (LIKE ${schema}.job INCLUDING GENERATED INCLUDING DEFAULTS);
|
|
297
|
+
|
|
298
|
+
SELECT ${schema}.job_table_run($cmd$${createPrimaryKeyJob(schema)}$cmd$, '${COMMON_JOB_TABLE}');
|
|
299
|
+
SELECT ${schema}.job_table_run($cmd$${createQueueForeignKeyJob(schema)}$cmd$, '${COMMON_JOB_TABLE}');
|
|
300
|
+
SELECT ${schema}.job_table_run($cmd$${createQueueForeignKeyJobDeadLetter(schema)}$cmd$, '${COMMON_JOB_TABLE}');
|
|
301
|
+
SELECT ${schema}.job_table_run($cmd$${createIndexJobPolicyShort(schema)}$cmd$, '${COMMON_JOB_TABLE}');
|
|
302
|
+
SELECT ${schema}.job_table_run($cmd$${createIndexJobPolicySingleton(schema)}$cmd$, '${COMMON_JOB_TABLE}');
|
|
303
|
+
SELECT ${schema}.job_table_run($cmd$${createIndexJobPolicyStately(schema)}$cmd$, '${COMMON_JOB_TABLE}');
|
|
304
|
+
SELECT ${schema}.job_table_run($cmd$${createIndexJobPolicyExclusive(schema)}$cmd$, '${COMMON_JOB_TABLE}');
|
|
305
|
+
SELECT ${schema}.job_table_run($cmd$${createIndexJobThrottle(schema)}$cmd$, '${COMMON_JOB_TABLE}');
|
|
306
|
+
SELECT ${schema}.job_table_run($cmd$${createIndexJobFetch(schema)}$cmd$, '${COMMON_JOB_TABLE}');
|
|
307
|
+
SELECT ${schema}.job_table_run($cmd$${createIndexJobGroupConcurrency(schema)}$cmd$, '${COMMON_JOB_TABLE}');
|
|
308
|
+
|
|
309
|
+
ALTER TABLE ${schema}.job ATTACH PARTITION ${schema}.${COMMON_JOB_TABLE} DEFAULT;
|
|
310
|
+
`;
|
|
311
|
+
}
|
|
312
|
+
function createQueueFunction(schema) {
|
|
313
|
+
return `
|
|
314
|
+
CREATE FUNCTION ${schema}.create_queue(queue_name text, options jsonb)
|
|
315
|
+
RETURNS VOID AS
|
|
316
|
+
$$
|
|
317
|
+
DECLARE
|
|
318
|
+
tablename varchar := CASE WHEN options->>'partition' = 'true'
|
|
319
|
+
THEN 'j' || encode(sha224(queue_name::bytea), 'hex')
|
|
320
|
+
ELSE '${COMMON_JOB_TABLE}'
|
|
321
|
+
END;
|
|
322
|
+
queue_created_on timestamptz;
|
|
323
|
+
BEGIN
|
|
324
|
+
|
|
325
|
+
WITH q as (
|
|
326
|
+
INSERT INTO ${schema}.queue (
|
|
327
|
+
name,
|
|
328
|
+
policy,
|
|
329
|
+
retry_limit,
|
|
330
|
+
retry_delay,
|
|
331
|
+
retry_backoff,
|
|
332
|
+
retry_delay_max,
|
|
333
|
+
expire_seconds,
|
|
334
|
+
retention_seconds,
|
|
335
|
+
deletion_seconds,
|
|
336
|
+
warning_queued,
|
|
337
|
+
dead_letter,
|
|
338
|
+
partition,
|
|
339
|
+
table_name
|
|
340
|
+
)
|
|
341
|
+
VALUES (
|
|
342
|
+
queue_name,
|
|
343
|
+
options->>'policy',
|
|
344
|
+
COALESCE((options->>'retryLimit')::int, ${QUEUE_DEFAULTS.retry_limit}),
|
|
345
|
+
COALESCE((options->>'retryDelay')::int, ${QUEUE_DEFAULTS.retry_delay}),
|
|
346
|
+
COALESCE((options->>'retryBackoff')::bool, ${QUEUE_DEFAULTS.retry_backoff}),
|
|
347
|
+
(options->>'retryDelayMax')::int,
|
|
348
|
+
COALESCE((options->>'expireInSeconds')::int, ${QUEUE_DEFAULTS.expire_seconds}),
|
|
349
|
+
COALESCE((options->>'retentionSeconds')::int, ${QUEUE_DEFAULTS.retention_seconds}),
|
|
350
|
+
COALESCE((options->>'deleteAfterSeconds')::int, ${QUEUE_DEFAULTS.deletion_seconds}),
|
|
351
|
+
COALESCE((options->>'warningQueueSize')::int, ${QUEUE_DEFAULTS.warning_queued}),
|
|
352
|
+
options->>'deadLetter',
|
|
353
|
+
COALESCE((options->>'partition')::bool, ${QUEUE_DEFAULTS.partition}),
|
|
354
|
+
tablename
|
|
355
|
+
)
|
|
356
|
+
ON CONFLICT DO NOTHING
|
|
357
|
+
RETURNING created_on
|
|
358
|
+
)
|
|
359
|
+
SELECT created_on into queue_created_on from q;
|
|
360
|
+
|
|
361
|
+
IF queue_created_on IS NULL OR options->>'partition' IS DISTINCT FROM 'true' THEN
|
|
362
|
+
RETURN;
|
|
363
|
+
END IF;
|
|
364
|
+
|
|
365
|
+
EXECUTE format('CREATE TABLE ${schema}.%I (LIKE ${schema}.job INCLUDING DEFAULTS)', tablename);
|
|
366
|
+
|
|
367
|
+
EXECUTE ${schema}.job_table_format($cmd$${createPrimaryKeyJob(schema)}$cmd$, tablename);
|
|
368
|
+
EXECUTE ${schema}.job_table_format($cmd$${createQueueForeignKeyJob(schema)}$cmd$, tablename);
|
|
369
|
+
EXECUTE ${schema}.job_table_format($cmd$${createQueueForeignKeyJobDeadLetter(schema)}$cmd$, tablename);
|
|
370
|
+
|
|
371
|
+
EXECUTE ${schema}.job_table_format($cmd$${createIndexJobFetch(schema)}$cmd$, tablename);
|
|
372
|
+
EXECUTE ${schema}.job_table_format($cmd$${createIndexJobThrottle(schema)}$cmd$, tablename);
|
|
373
|
+
EXECUTE ${schema}.job_table_format($cmd$${createIndexJobGroupConcurrency(schema)}$cmd$, tablename);
|
|
374
|
+
|
|
375
|
+
IF options->>'policy' = 'short' THEN
|
|
376
|
+
EXECUTE ${schema}.job_table_format($cmd$${createIndexJobPolicyShort(schema)}$cmd$, tablename);
|
|
377
|
+
ELSIF options->>'policy' = 'singleton' THEN
|
|
378
|
+
EXECUTE ${schema}.job_table_format($cmd$${createIndexJobPolicySingleton(schema)}$cmd$, tablename);
|
|
379
|
+
ELSIF options->>'policy' = 'stately' THEN
|
|
380
|
+
EXECUTE ${schema}.job_table_format($cmd$${createIndexJobPolicyStately(schema)}$cmd$, tablename);
|
|
381
|
+
ELSIF options->>'policy' = 'exclusive' THEN
|
|
382
|
+
EXECUTE ${schema}.job_table_format($cmd$${createIndexJobPolicyExclusive(schema)}$cmd$, tablename);
|
|
383
|
+
END IF;
|
|
384
|
+
|
|
385
|
+
EXECUTE format('ALTER TABLE ${schema}.%I ADD CONSTRAINT cjc CHECK (name=%L)', tablename, queue_name);
|
|
386
|
+
EXECUTE format('ALTER TABLE ${schema}.job ATTACH PARTITION ${schema}.%I FOR VALUES IN (%L)', tablename, queue_name);
|
|
387
|
+
END;
|
|
388
|
+
$$
|
|
389
|
+
LANGUAGE plpgsql;
|
|
390
|
+
`;
|
|
391
|
+
}
|
|
392
|
+
function deleteQueueFunction(schema) {
|
|
393
|
+
return `
|
|
394
|
+
CREATE FUNCTION ${schema}.delete_queue(queue_name text)
|
|
395
|
+
RETURNS VOID AS
|
|
396
|
+
$$
|
|
397
|
+
DECLARE
|
|
398
|
+
v_table varchar;
|
|
399
|
+
v_partition bool;
|
|
400
|
+
BEGIN
|
|
401
|
+
SELECT table_name, partition
|
|
402
|
+
FROM ${schema}.queue
|
|
403
|
+
WHERE name = queue_name
|
|
404
|
+
INTO v_table, v_partition;
|
|
405
|
+
|
|
406
|
+
IF v_partition THEN
|
|
407
|
+
EXECUTE format('DROP TABLE IF EXISTS ${schema}.%I', v_table);
|
|
408
|
+
ELSE
|
|
409
|
+
EXECUTE format('DELETE FROM ${schema}.%I WHERE name = %L', v_table, queue_name);
|
|
410
|
+
END IF;
|
|
411
|
+
|
|
412
|
+
DELETE FROM ${schema}.queue WHERE name = queue_name;
|
|
413
|
+
END;
|
|
414
|
+
$$
|
|
415
|
+
LANGUAGE plpgsql;
|
|
416
|
+
`;
|
|
417
|
+
}
|
|
418
|
+
function createQueue(schema, name, options) {
|
|
419
|
+
const sql = `SELECT ${schema}.create_queue('${name}', '${JSON.stringify(options)}'::jsonb)`;
|
|
420
|
+
return locked(schema, sql, 'create-queue');
|
|
421
|
+
}
|
|
422
|
+
function deleteQueue(schema, name) {
|
|
423
|
+
const sql = `SELECT ${schema}.delete_queue('${name}')`;
|
|
424
|
+
return locked(schema, sql, 'delete-queue');
|
|
425
|
+
}
|
|
426
|
+
function createPrimaryKeyJob(schema) {
|
|
427
|
+
return `ALTER TABLE ${schema}.job ADD PRIMARY KEY (name, id)`;
|
|
428
|
+
}
|
|
429
|
+
function createQueueForeignKeyJob(schema) {
|
|
430
|
+
return `ALTER TABLE ${schema}.job ADD CONSTRAINT q_fkey FOREIGN KEY (name) REFERENCES ${schema}.queue (name) ON DELETE RESTRICT DEFERRABLE INITIALLY DEFERRED`;
|
|
431
|
+
}
|
|
432
|
+
function createQueueForeignKeyJobDeadLetter(schema) {
|
|
433
|
+
return `ALTER TABLE ${schema}.job ADD CONSTRAINT dlq_fkey FOREIGN KEY (dead_letter) REFERENCES ${schema}.queue (name) ON DELETE RESTRICT DEFERRABLE INITIALLY DEFERRED`;
|
|
434
|
+
}
|
|
435
|
+
function createIndexJobPolicyShort(schema) {
|
|
436
|
+
return `CREATE UNIQUE INDEX job_i1 ON ${schema}.job (name, COALESCE(singleton_key, '')) WHERE state = '${JOB_STATES.created}' AND policy = '${QUEUE_POLICIES.short}'`;
|
|
437
|
+
}
|
|
438
|
+
function createIndexJobPolicySingleton(schema) {
|
|
439
|
+
return `CREATE UNIQUE INDEX job_i2 ON ${schema}.job (name, COALESCE(singleton_key, '')) WHERE state = '${JOB_STATES.active}' AND policy = '${QUEUE_POLICIES.singleton}'`;
|
|
440
|
+
}
|
|
441
|
+
function createIndexJobPolicyStately(schema) {
|
|
442
|
+
return `CREATE UNIQUE INDEX job_i3 ON ${schema}.job (name, state, COALESCE(singleton_key, '')) WHERE state <= '${JOB_STATES.active}' AND policy = '${QUEUE_POLICIES.stately}'`;
|
|
443
|
+
}
|
|
444
|
+
function createIndexJobThrottle(schema) {
|
|
445
|
+
return `CREATE UNIQUE INDEX job_i4 ON ${schema}.job (name, singleton_on, COALESCE(singleton_key, '')) WHERE state <> '${JOB_STATES.cancelled}' AND singleton_on IS NOT NULL`;
|
|
446
|
+
}
|
|
447
|
+
function createIndexJobFetch(schema) {
|
|
448
|
+
return `CREATE INDEX job_i5 ON ${schema}.job (name, start_after) INCLUDE (priority, created_on, id) WHERE state < '${JOB_STATES.active}'`;
|
|
449
|
+
}
|
|
450
|
+
function createIndexJobPolicyExclusive(schema) {
|
|
451
|
+
return `CREATE UNIQUE INDEX job_i6 ON ${schema}.job (name, COALESCE(singleton_key, '')) WHERE state <= '${JOB_STATES.active}' AND policy = '${QUEUE_POLICIES.exclusive}'`;
|
|
452
|
+
}
|
|
453
|
+
function createIndexJobGroupConcurrency(schema) {
|
|
454
|
+
return `CREATE INDEX job_i7 ON ${schema}.job (name, group_id) WHERE state = '${JOB_STATES.active}' AND group_id IS NOT NULL`;
|
|
455
|
+
}
|
|
456
|
+
function trySetQueueMonitorTime(schema, queues, seconds) {
|
|
457
|
+
return trySetQueueTimestamp(schema, queues, 'monitor_on', seconds);
|
|
458
|
+
}
|
|
459
|
+
function trySetQueueDeletionTime(schema, queues, seconds) {
|
|
460
|
+
return trySetQueueTimestamp(schema, queues, 'maintain_on', seconds);
|
|
461
|
+
}
|
|
462
|
+
function trySetCronTime(schema, seconds) {
|
|
463
|
+
return trySetTimestamp(schema, 'cron_on', seconds);
|
|
464
|
+
}
|
|
465
|
+
function trySetBamTime(schema, seconds) {
|
|
466
|
+
return trySetTimestamp(schema, 'bam_on', seconds);
|
|
467
|
+
}
|
|
468
|
+
function trySetTimestamp(schema, column, seconds) {
|
|
469
|
+
return `
|
|
470
|
+
UPDATE ${schema}.version
|
|
471
|
+
SET ${column} = now()
|
|
472
|
+
WHERE EXTRACT( EPOCH FROM (now() - COALESCE(${column}, now() - interval '1 week') ) ) > ${seconds}
|
|
473
|
+
RETURNING true
|
|
474
|
+
`;
|
|
475
|
+
}
|
|
476
|
+
function trySetQueueTimestamp(schema, queues, column, seconds) {
|
|
477
|
+
return {
|
|
478
|
+
text: `
|
|
479
|
+
UPDATE ${schema}.queue
|
|
480
|
+
SET ${column} = now()
|
|
481
|
+
WHERE name = ANY($1::text[])
|
|
482
|
+
AND EXTRACT( EPOCH FROM (now() - COALESCE(${column}, now() - interval '1 week') ) ) > ${seconds}
|
|
483
|
+
RETURNING name
|
|
484
|
+
`,
|
|
485
|
+
values: [queues]
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
function updateQueue(schema, { deadLetter } = {}) {
|
|
489
|
+
return `
|
|
490
|
+
WITH options as (SELECT $2::jsonb as data)
|
|
491
|
+
UPDATE ${schema}.queue SET
|
|
492
|
+
retry_limit = COALESCE((o.data->>'retryLimit')::int, retry_limit),
|
|
493
|
+
retry_delay = COALESCE((o.data->>'retryDelay')::int, retry_delay),
|
|
494
|
+
retry_backoff = COALESCE((o.data->>'retryBackoff')::bool, retry_backoff),
|
|
495
|
+
retry_delay_max = CASE WHEN o.data ? 'retryDelayMax'
|
|
496
|
+
THEN (o.data->>'retryDelayMax')::int
|
|
497
|
+
ELSE retry_delay_max END,
|
|
498
|
+
expire_seconds = COALESCE((o.data->>'expireInSeconds')::int, expire_seconds),
|
|
499
|
+
retention_seconds = COALESCE((o.data->>'retentionSeconds')::int, retention_seconds),
|
|
500
|
+
deletion_seconds = COALESCE((o.data->>'deleteAfterSeconds')::int, deletion_seconds),
|
|
501
|
+
warning_queued = COALESCE((o.data->>'warningQueueSize')::int, warning_queued),
|
|
502
|
+
${deadLetter === undefined
|
|
503
|
+
? ''
|
|
504
|
+
: `dead_letter = CASE WHEN '${deadLetter}' IS DISTINCT FROM dead_letter THEN '${deadLetter}' ELSE dead_letter END,`}
|
|
505
|
+
updated_on = now()
|
|
506
|
+
FROM options o
|
|
507
|
+
WHERE name = $1
|
|
508
|
+
`;
|
|
509
|
+
}
|
|
510
|
+
function getQueues(schema, names) {
|
|
511
|
+
const hasNames = names && names.length > 0;
|
|
512
|
+
return {
|
|
513
|
+
text: `
|
|
514
|
+
SELECT
|
|
515
|
+
q.name,
|
|
516
|
+
q.policy,
|
|
517
|
+
q.retry_limit as "retryLimit",
|
|
518
|
+
q.retry_delay as "retryDelay",
|
|
519
|
+
q.retry_backoff as "retryBackoff",
|
|
520
|
+
q.retry_delay_max as "retryDelayMax",
|
|
521
|
+
q.expire_seconds as "expireInSeconds",
|
|
522
|
+
q.retention_seconds as "retentionSeconds",
|
|
523
|
+
q.deletion_seconds as "deleteAfterSeconds",
|
|
524
|
+
q.partition,
|
|
525
|
+
q.dead_letter as "deadLetter",
|
|
526
|
+
q.deferred_count as "deferredCount",
|
|
527
|
+
q.warning_queued as "warningQueueSize",
|
|
528
|
+
q.queued_count as "queuedCount",
|
|
529
|
+
q.active_count as "activeCount",
|
|
530
|
+
q.total_count as "totalCount",
|
|
531
|
+
q.singletons_active as "singletonsActive",
|
|
532
|
+
q.table_name as "table",
|
|
533
|
+
q.created_on as "createdOn",
|
|
534
|
+
q.updated_on as "updatedOn"
|
|
535
|
+
FROM ${schema}.queue q
|
|
536
|
+
${hasNames ? 'WHERE q.name = ANY($1::text[])' : ''}
|
|
537
|
+
`,
|
|
538
|
+
values: hasNames ? [names] : []
|
|
539
|
+
};
|
|
540
|
+
}
|
|
541
|
+
function deleteJobsById(schema, table) {
|
|
542
|
+
return `
|
|
543
|
+
WITH results as (
|
|
544
|
+
DELETE FROM ${schema}.${table}
|
|
545
|
+
WHERE name = $1
|
|
546
|
+
AND id IN (SELECT UNNEST($2::uuid[]))
|
|
547
|
+
RETURNING 1
|
|
548
|
+
)
|
|
549
|
+
SELECT COUNT(*) from results
|
|
550
|
+
`;
|
|
551
|
+
}
|
|
552
|
+
function deleteQueuedJobs(schema, table) {
|
|
553
|
+
return `DELETE from ${schema}.${table} WHERE name = $1 and state < '${JOB_STATES.active}'`;
|
|
554
|
+
}
|
|
555
|
+
function deleteStoredJobs(schema, table) {
|
|
556
|
+
return `DELETE from ${schema}.${table} WHERE name = $1 and state > '${JOB_STATES.active}'`;
|
|
557
|
+
}
|
|
558
|
+
function truncateTable(schema, table) {
|
|
559
|
+
return `TRUNCATE ${schema}.${table}`;
|
|
560
|
+
}
|
|
561
|
+
function deleteAllJobs(schema, table) {
|
|
562
|
+
return `DELETE from ${schema}.${table} WHERE name = $1`;
|
|
563
|
+
}
|
|
564
|
+
function getSchedules(schema) {
|
|
565
|
+
return `SELECT * FROM ${schema}.schedule`;
|
|
566
|
+
}
|
|
567
|
+
function getSchedulesByQueue(schema) {
|
|
568
|
+
return `SELECT * FROM ${schema}.schedule WHERE name = $1 AND COALESCE(key, '') = $2`;
|
|
569
|
+
}
|
|
570
|
+
function schedule(schema) {
|
|
571
|
+
return `
|
|
572
|
+
INSERT INTO ${schema}.schedule (name, key, cron, timezone, data, options)
|
|
573
|
+
VALUES ($1, $2, $3, $4, $5, $6)
|
|
574
|
+
ON CONFLICT (name, key) DO UPDATE SET
|
|
575
|
+
cron = EXCLUDED.cron,
|
|
576
|
+
timezone = EXCLUDED.timezone,
|
|
577
|
+
data = EXCLUDED.data,
|
|
578
|
+
options = EXCLUDED.options,
|
|
579
|
+
updated_on = now()
|
|
580
|
+
`;
|
|
581
|
+
}
|
|
582
|
+
function unschedule(schema) {
|
|
583
|
+
return `
|
|
584
|
+
DELETE FROM ${schema}.schedule
|
|
585
|
+
WHERE name = $1
|
|
586
|
+
AND COALESCE(key, '') = $2
|
|
587
|
+
`;
|
|
588
|
+
}
|
|
589
|
+
function subscribe(schema) {
|
|
590
|
+
return `
|
|
591
|
+
INSERT INTO ${schema}.subscription (event, name)
|
|
592
|
+
VALUES ($1, $2)
|
|
593
|
+
ON CONFLICT (event, name) DO UPDATE SET
|
|
594
|
+
event = EXCLUDED.event,
|
|
595
|
+
name = EXCLUDED.name,
|
|
596
|
+
updated_on = now()
|
|
597
|
+
`;
|
|
598
|
+
}
|
|
599
|
+
function unsubscribe(schema) {
|
|
600
|
+
return `
|
|
601
|
+
DELETE FROM ${schema}.subscription
|
|
602
|
+
WHERE event = $1 and name = $2
|
|
603
|
+
`;
|
|
604
|
+
}
|
|
605
|
+
function getQueuesForEvent(schema) {
|
|
606
|
+
return `
|
|
607
|
+
SELECT name FROM ${schema}.subscription
|
|
608
|
+
WHERE event = $1
|
|
609
|
+
`;
|
|
610
|
+
}
|
|
611
|
+
function getTime() {
|
|
612
|
+
return "SELECT round(date_part('epoch', now()) * 1000) as time";
|
|
613
|
+
}
|
|
614
|
+
function getVersion(schema) {
|
|
615
|
+
return `SELECT version from ${schema}.version`;
|
|
616
|
+
}
|
|
617
|
+
function setVersion(schema, version) {
|
|
618
|
+
return `UPDATE ${schema}.version SET version = '${version}'`;
|
|
619
|
+
}
|
|
620
|
+
function versionTableExists(schema) {
|
|
621
|
+
return `SELECT to_regclass('${schema}.version') as name`;
|
|
622
|
+
}
|
|
623
|
+
function insertVersion(schema, version) {
|
|
624
|
+
return `INSERT INTO ${schema}.version(version) VALUES ('${version}')`;
|
|
625
|
+
}
|
|
626
|
+
function buildFetchParams(options) {
|
|
627
|
+
const { ignoreSingletons, ignoreGroups, groupConcurrency } = options;
|
|
628
|
+
const hasIgnoreSingletons = ignoreSingletons != null && ignoreSingletons.length > 0;
|
|
629
|
+
const hasIgnoreGroups = ignoreGroups != null && ignoreGroups.length > 0;
|
|
630
|
+
const hasGroupConcurrency = groupConcurrency != null;
|
|
631
|
+
const groupConcurrencyConfig = hasGroupConcurrency
|
|
632
|
+
? (typeof groupConcurrency === 'number' ? { default: groupConcurrency } : groupConcurrency)
|
|
633
|
+
: null;
|
|
634
|
+
const hasTiers = groupConcurrencyConfig?.tiers && Object.keys(groupConcurrencyConfig.tiers).length > 0;
|
|
635
|
+
const values = [];
|
|
636
|
+
let paramIndex = 0;
|
|
637
|
+
let ignoreSingletonsParam = '';
|
|
638
|
+
let ignoreGroupsParam = '';
|
|
639
|
+
let defaultGroupLimitParam = '';
|
|
640
|
+
let tiersParam = '';
|
|
641
|
+
if (hasIgnoreSingletons) {
|
|
642
|
+
paramIndex++;
|
|
643
|
+
ignoreSingletonsParam = `$${paramIndex}::text[]`;
|
|
644
|
+
values.push(ignoreSingletons);
|
|
645
|
+
}
|
|
646
|
+
if (hasIgnoreGroups) {
|
|
647
|
+
paramIndex++;
|
|
648
|
+
ignoreGroupsParam = `$${paramIndex}::text[]`;
|
|
649
|
+
values.push(ignoreGroups);
|
|
650
|
+
}
|
|
651
|
+
if (hasGroupConcurrency && groupConcurrencyConfig) {
|
|
652
|
+
paramIndex++;
|
|
653
|
+
defaultGroupLimitParam = `$${paramIndex}::int`;
|
|
654
|
+
values.push(groupConcurrencyConfig.default);
|
|
655
|
+
if (hasTiers) {
|
|
656
|
+
paramIndex++;
|
|
657
|
+
tiersParam = `$${paramIndex}::jsonb`;
|
|
658
|
+
values.push(JSON.stringify(groupConcurrencyConfig.tiers));
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
return { values, ignoreSingletonsParam, ignoreGroupsParam, defaultGroupLimitParam, tiersParam };
|
|
662
|
+
}
|
|
663
|
+
function fetchNextJob(options) {
|
|
664
|
+
const { schema, table, name, policy, limit, includeMetadata, priority = true, orderByCreatedOn = true, ignoreStartAfter = false, groupConcurrency } = options;
|
|
665
|
+
const singletonFetch = limit > 1 && (policy === QUEUE_POLICIES.singleton || policy === QUEUE_POLICIES.stately);
|
|
666
|
+
const hasIgnoreSingletons = options.ignoreSingletons != null && options.ignoreSingletons.length > 0;
|
|
667
|
+
const hasIgnoreGroups = options.ignoreGroups != null && options.ignoreGroups.length > 0;
|
|
668
|
+
const hasGroupConcurrency = groupConcurrency != null;
|
|
669
|
+
const hasTiers = hasGroupConcurrency &&
|
|
670
|
+
typeof groupConcurrency === 'object' &&
|
|
671
|
+
groupConcurrency.tiers &&
|
|
672
|
+
Object.keys(groupConcurrency.tiers).length > 0;
|
|
673
|
+
const params = buildFetchParams(options);
|
|
674
|
+
const whereConditions = [
|
|
675
|
+
`name = '${name}'`,
|
|
676
|
+
`state < '${JOB_STATES.active}'`,
|
|
677
|
+
!ignoreStartAfter ? 'start_after < now()' : '',
|
|
678
|
+
hasIgnoreSingletons ? `singleton_key <> ALL(${params.ignoreSingletonsParam})` : '',
|
|
679
|
+
hasIgnoreGroups ? `(group_id IS NULL OR group_id <> ALL(${params.ignoreGroupsParam}))` : ''
|
|
680
|
+
].filter(Boolean).join(' AND ');
|
|
681
|
+
const selectCols = [
|
|
682
|
+
'id',
|
|
683
|
+
singletonFetch ? 'singleton_key' : '',
|
|
684
|
+
hasGroupConcurrency ? 'group_id, group_tier' : ''
|
|
685
|
+
].filter(Boolean).join(', ');
|
|
686
|
+
const activeGroupCountsCte = hasGroupConcurrency
|
|
687
|
+
? `active_group_counts AS (
|
|
688
|
+
SELECT group_id, COUNT(*)::int as active_cnt
|
|
689
|
+
FROM ${schema}.${table}
|
|
690
|
+
WHERE name = '${name}' AND state = '${JOB_STATES.active}' AND group_id IS NOT NULL
|
|
691
|
+
GROUP BY group_id
|
|
692
|
+
), `
|
|
693
|
+
: '';
|
|
694
|
+
const nextCte = `
|
|
695
|
+
next AS (
|
|
696
|
+
SELECT ${selectCols}
|
|
697
|
+
FROM ${schema}.${table}
|
|
698
|
+
WHERE ${whereConditions}
|
|
699
|
+
ORDER BY ${priority ? 'priority desc, ' : ''}${orderByCreatedOn ? 'created_on, ' : ''}id
|
|
700
|
+
LIMIT ${limit}
|
|
701
|
+
FOR UPDATE SKIP LOCKED
|
|
702
|
+
)`;
|
|
703
|
+
const singletonCte = singletonFetch
|
|
704
|
+
? `, singleton_ranking AS (
|
|
705
|
+
SELECT id, ${hasGroupConcurrency ? 'group_id, group_tier, ' : ''}
|
|
706
|
+
row_number() OVER (PARTITION BY singleton_key) as singleton_rn
|
|
707
|
+
FROM next
|
|
708
|
+
)`
|
|
709
|
+
: '';
|
|
710
|
+
const groupConcurrencyCtes = hasGroupConcurrency
|
|
711
|
+
? `,
|
|
712
|
+
group_ranking AS (
|
|
713
|
+
SELECT t.id
|
|
714
|
+
, t.group_id
|
|
715
|
+
, t.group_tier
|
|
716
|
+
${singletonFetch ? ', singleton_rn' : ''}
|
|
717
|
+
, ROW_NUMBER() OVER (PARTITION BY t.group_id ORDER BY t.id) as group_rn
|
|
718
|
+
, COALESCE(agc.active_cnt, 0) as active_cnt
|
|
719
|
+
FROM ${singletonFetch ? 'singleton_ranking' : 'next'} t
|
|
720
|
+
LEFT JOIN active_group_counts agc ON t.group_id = agc.group_id
|
|
721
|
+
${singletonFetch ? 'WHERE singleton_rn = 1' : ''}
|
|
722
|
+
),
|
|
723
|
+
group_filtered AS (
|
|
724
|
+
SELECT id FROM group_ranking
|
|
725
|
+
WHERE group_id IS NULL
|
|
726
|
+
OR (active_cnt + group_rn) <= ${hasTiers
|
|
727
|
+
? `COALESCE((${params.tiersParam} ->> group_tier)::int, ${params.defaultGroupLimitParam})`
|
|
728
|
+
: params.defaultGroupLimitParam}
|
|
729
|
+
)`
|
|
730
|
+
: '';
|
|
731
|
+
const finalCte = (hasGroupConcurrency)
|
|
732
|
+
? 'group_filtered'
|
|
733
|
+
: (singletonFetch)
|
|
734
|
+
? 'singleton_ranking'
|
|
735
|
+
: 'next';
|
|
736
|
+
return {
|
|
737
|
+
text: `
|
|
738
|
+
WITH
|
|
739
|
+
${activeGroupCountsCte}
|
|
740
|
+
${nextCte}
|
|
741
|
+
${singletonCte}
|
|
742
|
+
${groupConcurrencyCtes}
|
|
743
|
+
UPDATE ${schema}.${table} j SET
|
|
744
|
+
state = '${JOB_STATES.active}',
|
|
745
|
+
started_on = now(),
|
|
746
|
+
retry_count = CASE WHEN started_on IS NOT NULL THEN retry_count + 1 ELSE retry_count END
|
|
747
|
+
FROM ${finalCte}
|
|
748
|
+
WHERE name = '${name}' AND j.id = ${finalCte}.id
|
|
749
|
+
${singletonFetch && !hasGroupConcurrency ? 'AND singleton_rn = 1' : ''}
|
|
750
|
+
RETURNING j.${includeMetadata ? JOB_COLUMNS_ALL : JOB_COLUMNS_MIN}
|
|
751
|
+
`,
|
|
752
|
+
values: params.values
|
|
753
|
+
};
|
|
754
|
+
}
|
|
755
|
+
function completeJobs(schema, table) {
|
|
756
|
+
return `
|
|
757
|
+
WITH results AS (
|
|
758
|
+
UPDATE ${schema}.${table}
|
|
759
|
+
SET completed_on = now(),
|
|
760
|
+
state = '${JOB_STATES.completed}',
|
|
761
|
+
output = $3::jsonb
|
|
762
|
+
WHERE name = $1
|
|
763
|
+
AND id IN (SELECT UNNEST($2::uuid[]))
|
|
764
|
+
AND state = '${JOB_STATES.active}'
|
|
765
|
+
RETURNING *
|
|
766
|
+
)
|
|
767
|
+
SELECT COUNT(*) FROM results
|
|
768
|
+
`;
|
|
769
|
+
}
|
|
770
|
+
function cancelJobs(schema, table) {
|
|
771
|
+
return `
|
|
772
|
+
WITH results as (
|
|
773
|
+
UPDATE ${schema}.${table}
|
|
774
|
+
SET completed_on = now(),
|
|
775
|
+
state = '${JOB_STATES.cancelled}'
|
|
776
|
+
WHERE name = $1
|
|
777
|
+
AND id IN (SELECT UNNEST($2::uuid[]))
|
|
778
|
+
AND state < '${JOB_STATES.completed}'
|
|
779
|
+
RETURNING 1
|
|
780
|
+
)
|
|
781
|
+
SELECT COUNT(*) from results
|
|
782
|
+
`;
|
|
783
|
+
}
|
|
784
|
+
function resumeJobs(schema, table) {
|
|
785
|
+
return `
|
|
786
|
+
WITH results as (
|
|
787
|
+
UPDATE ${schema}.${table}
|
|
788
|
+
SET completed_on = NULL,
|
|
789
|
+
state = '${JOB_STATES.created}'
|
|
790
|
+
WHERE name = $1
|
|
791
|
+
AND id IN (SELECT UNNEST($2::uuid[]))
|
|
792
|
+
AND state = '${JOB_STATES.cancelled}'
|
|
793
|
+
RETURNING 1
|
|
794
|
+
)
|
|
795
|
+
SELECT COUNT(*) from results
|
|
796
|
+
`;
|
|
797
|
+
}
|
|
798
|
+
function restoreJobs(schema, table) {
|
|
799
|
+
return `
|
|
800
|
+
UPDATE ${schema}.${table}
|
|
801
|
+
SET state = '${JOB_STATES.created}'
|
|
802
|
+
WHERE name = $1
|
|
803
|
+
AND id IN (SELECT UNNEST($2::uuid[]))
|
|
804
|
+
`;
|
|
805
|
+
}
|
|
806
|
+
function insertJobs(schema, { table, name, returnId = true }) {
|
|
807
|
+
const sql = `
|
|
808
|
+
INSERT INTO ${schema}.${table} (
|
|
809
|
+
id,
|
|
810
|
+
name,
|
|
811
|
+
data,
|
|
812
|
+
priority,
|
|
813
|
+
start_after,
|
|
814
|
+
singleton_key,
|
|
815
|
+
singleton_on,
|
|
816
|
+
group_id,
|
|
817
|
+
group_tier,
|
|
818
|
+
expire_seconds,
|
|
819
|
+
deletion_seconds,
|
|
820
|
+
keep_until,
|
|
821
|
+
retry_limit,
|
|
822
|
+
retry_delay,
|
|
823
|
+
retry_backoff,
|
|
824
|
+
retry_delay_max,
|
|
825
|
+
policy,
|
|
826
|
+
dead_letter
|
|
827
|
+
)
|
|
828
|
+
SELECT
|
|
829
|
+
COALESCE(id, gen_random_uuid()) as id,
|
|
830
|
+
'${name}' as name,
|
|
831
|
+
data,
|
|
832
|
+
COALESCE(priority, 0) as priority,
|
|
833
|
+
j.start_after,
|
|
834
|
+
"singletonKey",
|
|
835
|
+
CASE
|
|
836
|
+
WHEN "singletonSeconds" IS NOT NULL THEN 'epoch'::timestamp + '1s'::interval * ("singletonSeconds" * floor(( date_part('epoch', now()) + COALESCE("singletonOffset",0)) / "singletonSeconds" ))
|
|
837
|
+
ELSE NULL
|
|
838
|
+
END as singleton_on,
|
|
839
|
+
"groupId" as group_id,
|
|
840
|
+
"groupTier" as group_tier,
|
|
841
|
+
COALESCE("expireInSeconds", q.expire_seconds) as expire_seconds,
|
|
842
|
+
COALESCE("deleteAfterSeconds", q.deletion_seconds) as deletion_seconds,
|
|
843
|
+
j.start_after + (COALESCE("retentionSeconds", q.retention_seconds) * interval '1s') as keep_until,
|
|
844
|
+
COALESCE("retryLimit", q.retry_limit) as retry_limit,
|
|
845
|
+
COALESCE("retryDelay", q.retry_delay) as retry_delay,
|
|
846
|
+
COALESCE("retryBackoff", q.retry_backoff, false) as retry_backoff,
|
|
847
|
+
COALESCE("retryDelayMax", q.retry_delay_max) as retry_delay_max,
|
|
848
|
+
q.policy,
|
|
849
|
+
q.dead_letter
|
|
850
|
+
FROM (
|
|
851
|
+
SELECT *,
|
|
852
|
+
CASE
|
|
853
|
+
WHEN right("startAfter", 1) = 'Z' THEN CAST("startAfter" as timestamp with time zone)
|
|
854
|
+
ELSE now() + CAST(COALESCE("startAfter",'0') as interval)
|
|
855
|
+
END as start_after
|
|
856
|
+
FROM json_to_recordset($1::json) as x (
|
|
857
|
+
id uuid,
|
|
858
|
+
priority integer,
|
|
859
|
+
data jsonb,
|
|
860
|
+
"startAfter" text,
|
|
861
|
+
"retryLimit" integer,
|
|
862
|
+
"retryDelay" integer,
|
|
863
|
+
"retryDelayMax" integer,
|
|
864
|
+
"retryBackoff" boolean,
|
|
865
|
+
"singletonKey" text,
|
|
866
|
+
"singletonSeconds" integer,
|
|
867
|
+
"singletonOffset" integer,
|
|
868
|
+
"groupId" text,
|
|
869
|
+
"groupTier" text,
|
|
870
|
+
"expireInSeconds" integer,
|
|
871
|
+
"deleteAfterSeconds" integer,
|
|
872
|
+
"retentionSeconds" integer
|
|
873
|
+
)
|
|
874
|
+
) j
|
|
875
|
+
JOIN ${schema}.queue q ON q.name = '${name}'
|
|
876
|
+
ON CONFLICT DO NOTHING
|
|
877
|
+
${returnId ? 'RETURNING id' : ''}
|
|
878
|
+
`;
|
|
879
|
+
return sql;
|
|
880
|
+
}
|
|
881
|
+
function failJobsById(schema, table) {
|
|
882
|
+
const where = `name = $1 AND id IN (SELECT UNNEST($2::uuid[])) AND state < '${JOB_STATES.completed}'`;
|
|
883
|
+
const output = '$3::jsonb';
|
|
884
|
+
return failJobs(schema, table, where, output);
|
|
885
|
+
}
|
|
886
|
+
function failJobsByTimeout(schema, table, queues) {
|
|
887
|
+
const where = `state = '${JOB_STATES.active}'
|
|
888
|
+
AND (started_on + expire_seconds * interval '1s') < now()
|
|
889
|
+
AND name = ANY(${serializeArrayParam(queues)})`;
|
|
890
|
+
const output = '\'{ "value": { "message": "job timed out" } }\'::jsonb';
|
|
891
|
+
return locked(schema, failJobs(schema, table, where, output), table + 'failJobsByTimeout');
|
|
892
|
+
}
|
|
893
|
+
function failJobs(schema, table, where, output) {
|
|
894
|
+
return `
|
|
895
|
+
WITH deleted_jobs AS (
|
|
896
|
+
DELETE FROM ${schema}.${table}
|
|
897
|
+
WHERE ${where}
|
|
898
|
+
RETURNING *
|
|
899
|
+
),
|
|
900
|
+
retried_jobs AS (
|
|
901
|
+
INSERT INTO ${schema}.${table} (
|
|
902
|
+
id,
|
|
903
|
+
name,
|
|
904
|
+
priority,
|
|
905
|
+
data,
|
|
906
|
+
state,
|
|
907
|
+
retry_limit,
|
|
908
|
+
retry_count,
|
|
909
|
+
retry_delay,
|
|
910
|
+
retry_backoff,
|
|
911
|
+
retry_delay_max,
|
|
912
|
+
start_after,
|
|
913
|
+
started_on,
|
|
914
|
+
singleton_key,
|
|
915
|
+
singleton_on,
|
|
916
|
+
group_id,
|
|
917
|
+
group_tier,
|
|
918
|
+
expire_seconds,
|
|
919
|
+
deletion_seconds,
|
|
920
|
+
created_on,
|
|
921
|
+
completed_on,
|
|
922
|
+
keep_until,
|
|
923
|
+
policy,
|
|
924
|
+
output,
|
|
925
|
+
dead_letter
|
|
926
|
+
)
|
|
927
|
+
SELECT
|
|
928
|
+
id,
|
|
929
|
+
name,
|
|
930
|
+
priority,
|
|
931
|
+
data,
|
|
932
|
+
CASE
|
|
933
|
+
WHEN retry_count < retry_limit THEN '${JOB_STATES.retry}'::${schema}.job_state
|
|
934
|
+
ELSE '${JOB_STATES.failed}'::${schema}.job_state
|
|
935
|
+
END as state,
|
|
936
|
+
retry_limit,
|
|
937
|
+
retry_count,
|
|
938
|
+
retry_delay,
|
|
939
|
+
retry_backoff,
|
|
940
|
+
retry_delay_max,
|
|
941
|
+
CASE WHEN retry_count = retry_limit THEN start_after
|
|
942
|
+
WHEN NOT retry_backoff THEN now() + retry_delay * interval '1'
|
|
943
|
+
ELSE now() + LEAST(
|
|
944
|
+
retry_delay_max,
|
|
945
|
+
retry_delay * (
|
|
946
|
+
2 ^ LEAST(16, retry_count + 1) / 2 +
|
|
947
|
+
2 ^ LEAST(16, retry_count + 1) / 2 * random()
|
|
948
|
+
)
|
|
949
|
+
) * interval '1s'
|
|
950
|
+
END as start_after,
|
|
951
|
+
started_on,
|
|
952
|
+
singleton_key,
|
|
953
|
+
singleton_on,
|
|
954
|
+
group_id,
|
|
955
|
+
group_tier,
|
|
956
|
+
expire_seconds,
|
|
957
|
+
deletion_seconds,
|
|
958
|
+
created_on,
|
|
959
|
+
CASE WHEN retry_count < retry_limit THEN NULL ELSE now() END as completed_on,
|
|
960
|
+
keep_until,
|
|
961
|
+
policy,
|
|
962
|
+
${output},
|
|
963
|
+
dead_letter
|
|
964
|
+
FROM deleted_jobs
|
|
965
|
+
ON CONFLICT DO NOTHING
|
|
966
|
+
RETURNING *
|
|
967
|
+
),
|
|
968
|
+
failed_jobs as (
|
|
969
|
+
INSERT INTO ${schema}.${table} (
|
|
970
|
+
id,
|
|
971
|
+
name,
|
|
972
|
+
priority,
|
|
973
|
+
data,
|
|
974
|
+
state,
|
|
975
|
+
retry_limit,
|
|
976
|
+
retry_count,
|
|
977
|
+
retry_delay,
|
|
978
|
+
retry_backoff,
|
|
979
|
+
retry_delay_max,
|
|
980
|
+
start_after,
|
|
981
|
+
started_on,
|
|
982
|
+
singleton_key,
|
|
983
|
+
singleton_on,
|
|
984
|
+
group_id,
|
|
985
|
+
group_tier,
|
|
986
|
+
expire_seconds,
|
|
987
|
+
deletion_seconds,
|
|
988
|
+
created_on,
|
|
989
|
+
completed_on,
|
|
990
|
+
keep_until,
|
|
991
|
+
policy,
|
|
992
|
+
output,
|
|
993
|
+
dead_letter
|
|
994
|
+
)
|
|
995
|
+
SELECT
|
|
996
|
+
id,
|
|
997
|
+
name,
|
|
998
|
+
priority,
|
|
999
|
+
data,
|
|
1000
|
+
'${JOB_STATES.failed}'::${schema}.job_state as state,
|
|
1001
|
+
retry_limit,
|
|
1002
|
+
retry_count,
|
|
1003
|
+
retry_delay,
|
|
1004
|
+
retry_backoff,
|
|
1005
|
+
retry_delay_max,
|
|
1006
|
+
start_after,
|
|
1007
|
+
started_on,
|
|
1008
|
+
singleton_key,
|
|
1009
|
+
singleton_on,
|
|
1010
|
+
group_id,
|
|
1011
|
+
group_tier,
|
|
1012
|
+
expire_seconds,
|
|
1013
|
+
deletion_seconds,
|
|
1014
|
+
created_on,
|
|
1015
|
+
now() as completed_on,
|
|
1016
|
+
keep_until,
|
|
1017
|
+
policy,
|
|
1018
|
+
${output},
|
|
1019
|
+
dead_letter
|
|
1020
|
+
FROM deleted_jobs
|
|
1021
|
+
WHERE id NOT IN (SELECT id from retried_jobs)
|
|
1022
|
+
RETURNING *
|
|
1023
|
+
),
|
|
1024
|
+
results as (
|
|
1025
|
+
SELECT * FROM retried_jobs
|
|
1026
|
+
UNION ALL
|
|
1027
|
+
SELECT * FROM failed_jobs
|
|
1028
|
+
),
|
|
1029
|
+
dlq_jobs as (
|
|
1030
|
+
INSERT INTO ${schema}.job (name, data, output, retry_limit, retry_backoff, retry_delay, keep_until, deletion_seconds)
|
|
1031
|
+
SELECT
|
|
1032
|
+
r.dead_letter,
|
|
1033
|
+
data,
|
|
1034
|
+
output,
|
|
1035
|
+
q.retry_limit,
|
|
1036
|
+
q.retry_backoff,
|
|
1037
|
+
q.retry_delay,
|
|
1038
|
+
now() + q.retention_seconds * interval '1s',
|
|
1039
|
+
q.deletion_seconds
|
|
1040
|
+
FROM results r
|
|
1041
|
+
JOIN ${schema}.queue q ON q.name = r.dead_letter
|
|
1042
|
+
WHERE state = '${JOB_STATES.failed}'
|
|
1043
|
+
)
|
|
1044
|
+
SELECT COUNT(*) FROM results
|
|
1045
|
+
`;
|
|
1046
|
+
}
|
|
1047
|
+
function deletion(schema, table, queues) {
|
|
1048
|
+
const sql = `
|
|
1049
|
+
DELETE FROM ${schema}.${table}
|
|
1050
|
+
WHERE name = ANY(${serializeArrayParam(queues)})
|
|
1051
|
+
AND
|
|
1052
|
+
(
|
|
1053
|
+
(deletion_seconds > 0 AND completed_on + deletion_seconds * interval '1s' < now())
|
|
1054
|
+
OR
|
|
1055
|
+
(state < '${JOB_STATES.active}' AND keep_until < now())
|
|
1056
|
+
)
|
|
1057
|
+
`;
|
|
1058
|
+
return locked(schema, sql, table + 'deletion');
|
|
1059
|
+
}
|
|
1060
|
+
function retryJobs(schema, table) {
|
|
1061
|
+
return `
|
|
1062
|
+
WITH results as (
|
|
1063
|
+
UPDATE ${schema}.job
|
|
1064
|
+
SET state = '${JOB_STATES.retry}',
|
|
1065
|
+
retry_limit = retry_limit + 1
|
|
1066
|
+
WHERE name = $1
|
|
1067
|
+
AND id IN (SELECT UNNEST($2::uuid[]))
|
|
1068
|
+
AND state = '${JOB_STATES.failed}'
|
|
1069
|
+
RETURNING 1
|
|
1070
|
+
)
|
|
1071
|
+
SELECT COUNT(*) from results
|
|
1072
|
+
`;
|
|
1073
|
+
}
|
|
1074
|
+
function getQueueStats(schema, table, queues) {
|
|
1075
|
+
return {
|
|
1076
|
+
text: `
|
|
1077
|
+
SELECT
|
|
1078
|
+
name,
|
|
1079
|
+
(count(*) FILTER (WHERE start_after > now()))::int as "deferredCount",
|
|
1080
|
+
(count(*) FILTER (WHERE state < '${JOB_STATES.active}'))::int as "queuedCount",
|
|
1081
|
+
(count(*) FILTER (WHERE state = '${JOB_STATES.active}'))::int as "activeCount",
|
|
1082
|
+
count(*)::int as "totalCount",
|
|
1083
|
+
array_agg(singleton_key) FILTER (WHERE policy IN ('${QUEUE_POLICIES.singleton}','${QUEUE_POLICIES.stately}') AND state = '${JOB_STATES.active}') as "singletonsActive"
|
|
1084
|
+
FROM ${schema}.${table}
|
|
1085
|
+
WHERE name = ANY($1::text[])
|
|
1086
|
+
GROUP BY 1
|
|
1087
|
+
`,
|
|
1088
|
+
values: [queues]
|
|
1089
|
+
};
|
|
1090
|
+
}
|
|
1091
|
+
function cacheQueueStats(schema, table, queues) {
|
|
1092
|
+
const statsQuery = getQueueStats(schema, table, queues);
|
|
1093
|
+
// Serialize the $1 parameter for use in locked() multi-statement query
|
|
1094
|
+
const statsText = statsQuery.text.replace('$1::text[]', serializeArrayParam(queues));
|
|
1095
|
+
const sql = `
|
|
1096
|
+
WITH stats AS (${statsText})
|
|
1097
|
+
UPDATE ${schema}.queue SET
|
|
1098
|
+
deferred_count = COALESCE(stats."deferredCount", 0),
|
|
1099
|
+
queued_count = COALESCE(stats."queuedCount", 0),
|
|
1100
|
+
active_count = COALESCE(stats."activeCount", 0),
|
|
1101
|
+
total_count = COALESCE(stats."totalCount", 0),
|
|
1102
|
+
singletons_active = stats."singletonsActive"
|
|
1103
|
+
FROM (
|
|
1104
|
+
SELECT q.name
|
|
1105
|
+
FROM unnest(${serializeArrayParam(queues)}) AS q(name)
|
|
1106
|
+
) q
|
|
1107
|
+
LEFT JOIN stats ON stats.name = q.name
|
|
1108
|
+
WHERE queue.name = q.name
|
|
1109
|
+
RETURNING
|
|
1110
|
+
queue.name,
|
|
1111
|
+
queue.queued_count as "queuedCount",
|
|
1112
|
+
queue.warning_queued as "warningQueueSize"
|
|
1113
|
+
`;
|
|
1114
|
+
return locked(schema, sql, 'queue-stats');
|
|
1115
|
+
}
|
|
1116
|
+
// Serialize a string array for embedding directly in SQL as PostgreSQL array literal
|
|
1117
|
+
function serializeArrayParam(values) {
|
|
1118
|
+
const escaped = values.map(v => `'${v.replace(SINGLE_QUOTE_REGEX, "''")}'`);
|
|
1119
|
+
return `ARRAY[${escaped.join(',')}]::text[]`;
|
|
1120
|
+
}
|
|
1121
|
+
function locked(schema, query, key) {
|
|
1122
|
+
const sql = Array.isArray(query) ? query.join(';\n') : query;
|
|
1123
|
+
return `
|
|
1124
|
+
BEGIN;
|
|
1125
|
+
SET LOCAL lock_timeout = 30000;
|
|
1126
|
+
SET LOCAL idle_in_transaction_session_timeout = 30000;
|
|
1127
|
+
${advisoryLock(schema, key)};
|
|
1128
|
+
${sql};
|
|
1129
|
+
COMMIT;
|
|
1130
|
+
`;
|
|
1131
|
+
}
|
|
1132
|
+
function advisoryLock(schema, key) {
|
|
1133
|
+
return `SELECT pg_advisory_xact_lock(
|
|
1134
|
+
('x' || encode(sha224((current_database() || '.pgboss.${schema}${key || ''}')::bytea), 'hex'))::bit(64)::bigint
|
|
1135
|
+
)`;
|
|
1136
|
+
}
|
|
1137
|
+
function assertMigration(schema, version) {
|
|
1138
|
+
// raises 'division by zero' if already on desired schema version
|
|
1139
|
+
return `SELECT version::int/(version::int-${version}) from ${schema}.version`;
|
|
1140
|
+
}
|
|
1141
|
+
function findJobs(schema, table, options) {
|
|
1142
|
+
const { queued, byKey, byData, byId } = options;
|
|
1143
|
+
let paramIndex = 1;
|
|
1144
|
+
const whereConditions = [];
|
|
1145
|
+
if (byId) {
|
|
1146
|
+
++paramIndex;
|
|
1147
|
+
whereConditions.push(`AND id = $${paramIndex}`);
|
|
1148
|
+
}
|
|
1149
|
+
if (byKey) {
|
|
1150
|
+
++paramIndex;
|
|
1151
|
+
whereConditions.push(`AND singleton_key = $${paramIndex}`);
|
|
1152
|
+
}
|
|
1153
|
+
if (byData) {
|
|
1154
|
+
++paramIndex;
|
|
1155
|
+
whereConditions.push(`AND data @> $${paramIndex}`);
|
|
1156
|
+
}
|
|
1157
|
+
if (queued) {
|
|
1158
|
+
whereConditions.push(`AND state < '${JOB_STATES.active}'`);
|
|
1159
|
+
}
|
|
1160
|
+
return `
|
|
1161
|
+
SELECT ${JOB_COLUMNS_ALL}
|
|
1162
|
+
FROM ${schema}.${table}
|
|
1163
|
+
WHERE name = $1
|
|
1164
|
+
${whereConditions.join('\n ')}
|
|
1165
|
+
`;
|
|
1166
|
+
}
|
|
1167
|
+
function getJobById(schema, table) {
|
|
1168
|
+
return `
|
|
1169
|
+
SELECT ${JOB_COLUMNS_ALL}
|
|
1170
|
+
FROM ${schema}.${table}
|
|
1171
|
+
WHERE name = $1
|
|
1172
|
+
AND id = $2
|
|
1173
|
+
`;
|
|
1174
|
+
}
|
|
1175
|
+
function getNextBamCommand(schema) {
|
|
1176
|
+
return `
|
|
1177
|
+
UPDATE ${schema}.bam
|
|
1178
|
+
SET status = 'in_progress', started_on = now()
|
|
1179
|
+
WHERE id = (
|
|
1180
|
+
SELECT id FROM ${schema}.bam
|
|
1181
|
+
WHERE status IN ('pending', 'failed')
|
|
1182
|
+
AND NOT EXISTS (SELECT 1 FROM ${schema}.bam WHERE status = 'in_progress')
|
|
1183
|
+
ORDER BY created_on
|
|
1184
|
+
LIMIT 1
|
|
1185
|
+
)
|
|
1186
|
+
RETURNING id, name, version, status, queue, table_name as "table", command, error,
|
|
1187
|
+
created_on as "createdOn", started_on as "startedOn", completed_on as "completedOn"
|
|
1188
|
+
`;
|
|
1189
|
+
}
|
|
1190
|
+
function setBamCompleted(schema, id) {
|
|
1191
|
+
return `
|
|
1192
|
+
UPDATE ${schema}.bam
|
|
1193
|
+
SET status = 'completed', completed_on = now()
|
|
1194
|
+
WHERE id = '${id}'
|
|
1195
|
+
`;
|
|
1196
|
+
}
|
|
1197
|
+
function setBamFailed(schema, id, error) {
|
|
1198
|
+
const escapedError = error.replace(/'/g, "''");
|
|
1199
|
+
return `
|
|
1200
|
+
UPDATE ${schema}.bam
|
|
1201
|
+
SET status = 'failed', error = '${escapedError}', completed_on = now()
|
|
1202
|
+
WHERE id = '${id}'
|
|
1203
|
+
`;
|
|
1204
|
+
}
|
|
1205
|
+
function getBamStatus(schema) {
|
|
1206
|
+
return `
|
|
1207
|
+
SELECT status, count(*)::int as count, max(created_on) as "lastCreatedOn"
|
|
1208
|
+
FROM ${schema}.bam
|
|
1209
|
+
GROUP BY status
|
|
1210
|
+
`;
|
|
1211
|
+
}
|
|
1212
|
+
function getBamEntries(schema) {
|
|
1213
|
+
return `
|
|
1214
|
+
SELECT id, name, version, status, queue, table_name as "table", command, error,
|
|
1215
|
+
created_on as "createdOn", started_on as "startedOn", completed_on as "completedOn"
|
|
1216
|
+
FROM ${schema}.bam
|
|
1217
|
+
ORDER BY version, created_on
|
|
1218
|
+
`;
|
|
1219
|
+
}
|
|
1220
|
+
export { create, insertVersion, getVersion, setVersion, versionTableExists, fetchNextJob, completeJobs, cancelJobs, resumeJobs, restoreJobs, retryJobs, findJobs, deleteJobsById, deleteAllJobs, deleteQueuedJobs, deleteStoredJobs, truncateTable, failJobsById, failJobsByTimeout, insertJobs, getTime, getSchedules, getSchedulesByQueue, schedule, unschedule, subscribe, unsubscribe, getQueuesForEvent, deletion, cacheQueueStats, updateQueue, createQueue, deleteQueue, getQueues, getQueueStats, trySetQueueMonitorTime, trySetQueueDeletionTime, trySetCronTime, trySetBamTime, locked, assertMigration, getJobById, getNextBamCommand, setBamCompleted, setBamFailed, getBamStatus, getBamEntries, QUEUE_POLICIES, JOB_STATES, MIGRATE_RACE_MESSAGE, CREATE_RACE_MESSAGE, DEFAULT_SCHEMA, };
|