pg-boss 12.5.3 → 12.6.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.
- package/dist/attorney.d.ts.map +1 -1
- package/dist/attorney.js +57 -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.map +1 -1
- package/dist/boss.js +18 -0
- package/dist/index.d.ts +7 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +28 -1
- package/dist/manager.d.ts +3 -1
- package/dist/manager.d.ts.map +1 -1
- package/dist/manager.js +230 -50
- package/dist/migrationStore.d.ts.map +1 -1
- package/dist/migrationStore.js +302 -3
- package/dist/plans.d.ts +21 -2
- package/dist/plans.d.ts.map +1 -1
- package/dist/plans.js +376 -63
- package/dist/types.d.ts +73 -3
- package/dist/types.d.ts.map +1 -1
- package/package.json +7 -9
package/dist/plans.js
CHANGED
|
@@ -39,9 +39,13 @@ function create(schema, version, options) {
|
|
|
39
39
|
createTableQueue(schema),
|
|
40
40
|
createTableSchedule(schema),
|
|
41
41
|
createTableSubscription(schema),
|
|
42
|
+
createTableBam(schema),
|
|
43
|
+
jobTableFormatFunction(schema),
|
|
44
|
+
jobTableRunFunction(schema),
|
|
45
|
+
jobTableRunAsyncFunction(schema),
|
|
42
46
|
createTableJob(schema),
|
|
43
47
|
createPrimaryKeyJob(schema),
|
|
44
|
-
createTableJobCommon(schema
|
|
48
|
+
createTableJobCommon(schema),
|
|
45
49
|
createQueueFunction(schema),
|
|
46
50
|
deleteQueueFunction(schema),
|
|
47
51
|
insertVersion(schema, version)
|
|
@@ -69,7 +73,8 @@ function createTableVersion(schema) {
|
|
|
69
73
|
return `
|
|
70
74
|
CREATE TABLE ${schema}.version (
|
|
71
75
|
version int primary key,
|
|
72
|
-
cron_on timestamp with time zone
|
|
76
|
+
cron_on timestamp with time zone,
|
|
77
|
+
bam_on timestamp with time zone
|
|
73
78
|
)
|
|
74
79
|
`;
|
|
75
80
|
}
|
|
@@ -128,6 +133,113 @@ function createTableSubscription(schema) {
|
|
|
128
133
|
)
|
|
129
134
|
`;
|
|
130
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
|
+
}
|
|
131
243
|
function createTableJob(schema) {
|
|
132
244
|
return `
|
|
133
245
|
CREATE TABLE ${schema}.job (
|
|
@@ -145,6 +257,8 @@ function createTableJob(schema) {
|
|
|
145
257
|
deletion_seconds int not null default ${QUEUE_DEFAULTS.deletion_seconds},
|
|
146
258
|
singleton_key text,
|
|
147
259
|
singleton_on timestamp without time zone,
|
|
260
|
+
group_id text,
|
|
261
|
+
group_tier text,
|
|
148
262
|
start_after timestamp with time zone not null default now(),
|
|
149
263
|
created_on timestamp with time zone not null default now(),
|
|
150
264
|
started_on timestamp with time zone,
|
|
@@ -156,7 +270,7 @@ function createTableJob(schema) {
|
|
|
156
270
|
) PARTITION BY LIST (name)
|
|
157
271
|
`;
|
|
158
272
|
}
|
|
159
|
-
const JOB_COLUMNS_MIN = 'id, name, data, expire_seconds as "expireInSeconds"';
|
|
273
|
+
const JOB_COLUMNS_MIN = 'id, name, data, expire_seconds as "expireInSeconds", group_id as "groupId", group_tier as "groupTier"';
|
|
160
274
|
const JOB_COLUMNS_ALL = `${JOB_COLUMNS_MIN},
|
|
161
275
|
policy,
|
|
162
276
|
state,
|
|
@@ -177,21 +291,22 @@ const JOB_COLUMNS_ALL = `${JOB_COLUMNS_MIN},
|
|
|
177
291
|
dead_letter as "deadLetter",
|
|
178
292
|
output
|
|
179
293
|
`;
|
|
180
|
-
function createTableJobCommon(schema
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
CREATE TABLE ${schema}.${table} (LIKE ${schema}.job INCLUDING GENERATED INCLUDING DEFAULTS);
|
|
184
|
-
${format(createPrimaryKeyJob(schema))}
|
|
185
|
-
${format(createQueueForeignKeyJob(schema))}
|
|
186
|
-
${format(createQueueForeignKeyJobDeadLetter(schema))}
|
|
187
|
-
${format(createIndexJobPolicyShort(schema))}
|
|
188
|
-
${format(createIndexJobPolicySingleton(schema))}
|
|
189
|
-
${format(createIndexJobPolicyStately(schema))}
|
|
190
|
-
${format(createIndexJobPolicyExclusive(schema))}
|
|
191
|
-
${format(createIndexJobThrottle(schema))}
|
|
192
|
-
${format(createIndexJobFetch(schema))}
|
|
294
|
+
function createTableJobCommon(schema) {
|
|
295
|
+
return `
|
|
296
|
+
CREATE TABLE ${schema}.${COMMON_JOB_TABLE} (LIKE ${schema}.job INCLUDING GENERATED INCLUDING DEFAULTS);
|
|
193
297
|
|
|
194
|
-
|
|
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;
|
|
195
310
|
`;
|
|
196
311
|
}
|
|
197
312
|
function createQueueFunction(schema) {
|
|
@@ -248,22 +363,23 @@ function createQueueFunction(schema) {
|
|
|
248
363
|
END IF;
|
|
249
364
|
|
|
250
365
|
EXECUTE format('CREATE TABLE ${schema}.%I (LIKE ${schema}.job INCLUDING DEFAULTS)', tablename);
|
|
251
|
-
|
|
252
|
-
EXECUTE format('${formatPartitionCommand(createPrimaryKeyJob(schema))}', tablename);
|
|
253
|
-
EXECUTE format('${formatPartitionCommand(createQueueForeignKeyJob(schema))}', tablename);
|
|
254
|
-
EXECUTE format('${formatPartitionCommand(createQueueForeignKeyJobDeadLetter(schema))}', tablename);
|
|
255
366
|
|
|
256
|
-
EXECUTE
|
|
257
|
-
EXECUTE
|
|
258
|
-
|
|
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
|
+
|
|
259
375
|
IF options->>'policy' = 'short' THEN
|
|
260
|
-
EXECUTE
|
|
376
|
+
EXECUTE ${schema}.job_table_format($cmd$${createIndexJobPolicyShort(schema)}$cmd$, tablename);
|
|
261
377
|
ELSIF options->>'policy' = 'singleton' THEN
|
|
262
|
-
EXECUTE
|
|
378
|
+
EXECUTE ${schema}.job_table_format($cmd$${createIndexJobPolicySingleton(schema)}$cmd$, tablename);
|
|
263
379
|
ELSIF options->>'policy' = 'stately' THEN
|
|
264
|
-
EXECUTE
|
|
380
|
+
EXECUTE ${schema}.job_table_format($cmd$${createIndexJobPolicyStately(schema)}$cmd$, tablename);
|
|
265
381
|
ELSIF options->>'policy' = 'exclusive' THEN
|
|
266
|
-
EXECUTE
|
|
382
|
+
EXECUTE ${schema}.job_table_format($cmd$${createIndexJobPolicyExclusive(schema)}$cmd$, tablename);
|
|
267
383
|
END IF;
|
|
268
384
|
|
|
269
385
|
EXECUTE format('ALTER TABLE ${schema}.%I ADD CONSTRAINT cjc CHECK (name=%L)', tablename, queue_name);
|
|
@@ -273,12 +389,6 @@ function createQueueFunction(schema) {
|
|
|
273
389
|
LANGUAGE plpgsql;
|
|
274
390
|
`;
|
|
275
391
|
}
|
|
276
|
-
function formatPartitionCommand(command) {
|
|
277
|
-
return command
|
|
278
|
-
.replace('.job', '.%1$I')
|
|
279
|
-
.replace('job_i', '%1$s_i')
|
|
280
|
-
.replaceAll("'", "''");
|
|
281
|
-
}
|
|
282
392
|
function deleteQueueFunction(schema) {
|
|
283
393
|
return `
|
|
284
394
|
CREATE FUNCTION ${schema}.delete_queue(queue_name text)
|
|
@@ -340,6 +450,9 @@ function createIndexJobFetch(schema) {
|
|
|
340
450
|
function createIndexJobPolicyExclusive(schema) {
|
|
341
451
|
return `CREATE UNIQUE INDEX job_i6 ON ${schema}.job (name, COALESCE(singleton_key, '')) WHERE state <= '${JOB_STATES.active}' AND policy = '${QUEUE_POLICIES.exclusive}'`;
|
|
342
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
|
+
}
|
|
343
456
|
function trySetQueueMonitorTime(schema, queues, seconds) {
|
|
344
457
|
return trySetQueueTimestamp(schema, queues, 'monitor_on', seconds);
|
|
345
458
|
}
|
|
@@ -349,6 +462,9 @@ function trySetQueueDeletionTime(schema, queues, seconds) {
|
|
|
349
462
|
function trySetCronTime(schema, seconds) {
|
|
350
463
|
return trySetTimestamp(schema, 'cron_on', seconds);
|
|
351
464
|
}
|
|
465
|
+
function trySetBamTime(schema, seconds) {
|
|
466
|
+
return trySetTimestamp(schema, 'bam_on', seconds);
|
|
467
|
+
}
|
|
352
468
|
function trySetTimestamp(schema, column, seconds) {
|
|
353
469
|
return `
|
|
354
470
|
UPDATE ${schema}.version
|
|
@@ -507,34 +623,133 @@ function versionTableExists(schema) {
|
|
|
507
623
|
function insertVersion(schema, version) {
|
|
508
624
|
return `INSERT INTO ${schema}.version(version) VALUES ('${version}')`;
|
|
509
625
|
}
|
|
510
|
-
function
|
|
511
|
-
const
|
|
512
|
-
const cte = singletonFetch ? 'grouped' : 'next';
|
|
626
|
+
function buildFetchParams(options) {
|
|
627
|
+
const { ignoreSingletons, ignoreGroups, groupConcurrency } = options;
|
|
513
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, 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, ' : ''}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';
|
|
514
736
|
return {
|
|
515
737
|
text: `
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
retry_count = CASE WHEN started_on IS NOT NULL THEN retry_count + 1 ELSE retry_count END
|
|
532
|
-
FROM ${cte}
|
|
533
|
-
WHERE name = '${name}' AND j.id = ${cte}.id
|
|
534
|
-
${singletonFetch ? ` AND ${cte}.row_number = 1` : ''}
|
|
535
|
-
RETURNING j.${includeMetadata ? JOB_COLUMNS_ALL : JOB_COLUMNS_MIN}
|
|
536
|
-
`,
|
|
537
|
-
values: hasIgnoreSingletons ? [ignoreSingletons] : []
|
|
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
|
|
538
753
|
};
|
|
539
754
|
}
|
|
540
755
|
function completeJobs(schema, table) {
|
|
@@ -580,6 +795,14 @@ function resumeJobs(schema, table) {
|
|
|
580
795
|
SELECT COUNT(*) from results
|
|
581
796
|
`;
|
|
582
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
|
+
}
|
|
583
806
|
function insertJobs(schema, { table, name, returnId = true }) {
|
|
584
807
|
const sql = `
|
|
585
808
|
INSERT INTO ${schema}.${table} (
|
|
@@ -590,6 +813,8 @@ function insertJobs(schema, { table, name, returnId = true }) {
|
|
|
590
813
|
start_after,
|
|
591
814
|
singleton_key,
|
|
592
815
|
singleton_on,
|
|
816
|
+
group_id,
|
|
817
|
+
group_tier,
|
|
593
818
|
expire_seconds,
|
|
594
819
|
deletion_seconds,
|
|
595
820
|
keep_until,
|
|
@@ -611,6 +836,8 @@ function insertJobs(schema, { table, name, returnId = true }) {
|
|
|
611
836
|
WHEN "singletonSeconds" IS NOT NULL THEN 'epoch'::timestamp + '1s'::interval * ("singletonSeconds" * floor(( date_part('epoch', now()) + COALESCE("singletonOffset",0)) / "singletonSeconds" ))
|
|
612
837
|
ELSE NULL
|
|
613
838
|
END as singleton_on,
|
|
839
|
+
"groupId" as group_id,
|
|
840
|
+
"groupTier" as group_tier,
|
|
614
841
|
COALESCE("expireInSeconds", q.expire_seconds) as expire_seconds,
|
|
615
842
|
COALESCE("deleteAfterSeconds", q.deletion_seconds) as deletion_seconds,
|
|
616
843
|
j.start_after + (COALESCE("retentionSeconds", q.retention_seconds) * interval '1s') as keep_until,
|
|
@@ -638,10 +865,12 @@ function insertJobs(schema, { table, name, returnId = true }) {
|
|
|
638
865
|
"singletonKey" text,
|
|
639
866
|
"singletonSeconds" integer,
|
|
640
867
|
"singletonOffset" integer,
|
|
868
|
+
"groupId" text,
|
|
869
|
+
"groupTier" text,
|
|
641
870
|
"expireInSeconds" integer,
|
|
642
871
|
"deleteAfterSeconds" integer,
|
|
643
872
|
"retentionSeconds" integer
|
|
644
|
-
)
|
|
873
|
+
)
|
|
645
874
|
) j
|
|
646
875
|
JOIN ${schema}.queue q ON q.name = '${name}'
|
|
647
876
|
ON CONFLICT DO NOTHING
|
|
@@ -684,6 +913,8 @@ function failJobs(schema, table, where, output) {
|
|
|
684
913
|
started_on,
|
|
685
914
|
singleton_key,
|
|
686
915
|
singleton_on,
|
|
916
|
+
group_id,
|
|
917
|
+
group_tier,
|
|
687
918
|
expire_seconds,
|
|
688
919
|
deletion_seconds,
|
|
689
920
|
created_on,
|
|
@@ -711,7 +942,7 @@ function failJobs(schema, table, where, output) {
|
|
|
711
942
|
WHEN NOT retry_backoff THEN now() + retry_delay * interval '1'
|
|
712
943
|
ELSE now() + LEAST(
|
|
713
944
|
retry_delay_max,
|
|
714
|
-
retry_delay
|
|
945
|
+
retry_delay * (
|
|
715
946
|
2 ^ LEAST(16, retry_count + 1) / 2 +
|
|
716
947
|
2 ^ LEAST(16, retry_count + 1) / 2 * random()
|
|
717
948
|
)
|
|
@@ -720,6 +951,8 @@ function failJobs(schema, table, where, output) {
|
|
|
720
951
|
started_on,
|
|
721
952
|
singleton_key,
|
|
722
953
|
singleton_on,
|
|
954
|
+
group_id,
|
|
955
|
+
group_tier,
|
|
723
956
|
expire_seconds,
|
|
724
957
|
deletion_seconds,
|
|
725
958
|
created_on,
|
|
@@ -748,6 +981,8 @@ function failJobs(schema, table, where, output) {
|
|
|
748
981
|
started_on,
|
|
749
982
|
singleton_key,
|
|
750
983
|
singleton_on,
|
|
984
|
+
group_id,
|
|
985
|
+
group_tier,
|
|
751
986
|
expire_seconds,
|
|
752
987
|
deletion_seconds,
|
|
753
988
|
created_on,
|
|
@@ -772,6 +1007,8 @@ function failJobs(schema, table, where, output) {
|
|
|
772
1007
|
started_on,
|
|
773
1008
|
singleton_key,
|
|
774
1009
|
singleton_on,
|
|
1010
|
+
group_id,
|
|
1011
|
+
group_tier,
|
|
775
1012
|
expire_seconds,
|
|
776
1013
|
deletion_seconds,
|
|
777
1014
|
created_on,
|
|
@@ -901,7 +1138,83 @@ function assertMigration(schema, version) {
|
|
|
901
1138
|
// raises 'division by zero' if already on desired schema version
|
|
902
1139
|
return `SELECT version::int/(version::int-${version}) from ${schema}.version`;
|
|
903
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
|
+
}
|
|
904
1167
|
function getJobById(schema, table) {
|
|
905
|
-
return `
|
|
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
|
+
`;
|
|
906
1219
|
}
|
|
907
|
-
export { create, insertVersion, getVersion, setVersion, versionTableExists, fetchNextJob, completeJobs, cancelJobs, resumeJobs, retryJobs, 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, locked, assertMigration, getJobById, QUEUE_POLICIES, JOB_STATES, MIGRATE_RACE_MESSAGE, CREATE_RACE_MESSAGE, DEFAULT_SCHEMA, };
|
|
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, };
|