@pgpm/jobs 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +22 -0
- package/Makefile +6 -0
- package/README.md +5 -0
- package/__tests__/__snapshots__/jobs.test.ts.snap +16 -0
- package/__tests__/jobs.test.ts +139 -0
- package/deploy/schemas/app_jobs/helpers/json_build_object_apply.sql +28 -0
- package/deploy/schemas/app_jobs/procedures/add_job.sql +65 -0
- package/deploy/schemas/app_jobs/procedures/add_scheduled_job.sql +66 -0
- package/deploy/schemas/app_jobs/procedures/complete_job.sql +32 -0
- package/deploy/schemas/app_jobs/procedures/complete_jobs.sql +19 -0
- package/deploy/schemas/app_jobs/procedures/do_notify.sql +16 -0
- package/deploy/schemas/app_jobs/procedures/fail_job.sql +41 -0
- package/deploy/schemas/app_jobs/procedures/get_job.sql +77 -0
- package/deploy/schemas/app_jobs/procedures/get_scheduled_job.sql +46 -0
- package/deploy/schemas/app_jobs/procedures/permanently_fail_jobs.sql +24 -0
- package/deploy/schemas/app_jobs/procedures/release_jobs.sql +34 -0
- package/deploy/schemas/app_jobs/procedures/release_scheduled_jobs.sql +26 -0
- package/deploy/schemas/app_jobs/procedures/reschedule_jobs.sql +26 -0
- package/deploy/schemas/app_jobs/procedures/run_scheduled_job.sql +67 -0
- package/deploy/schemas/app_jobs/schema.sql +7 -0
- package/deploy/schemas/app_jobs/tables/job_queues/grants/grant_select_insert_update_delete_to_administrator.sql +12 -0
- package/deploy/schemas/app_jobs/tables/job_queues/indexes/job_queues_locked_by_idx.sql +8 -0
- package/deploy/schemas/app_jobs/tables/job_queues/table.sql +12 -0
- package/deploy/schemas/app_jobs/tables/jobs/grants/grant_select_insert_update_delete_to_administrator.sql +12 -0
- package/deploy/schemas/app_jobs/tables/jobs/indexes/jobs_locked_by_idx.sql +8 -0
- package/deploy/schemas/app_jobs/tables/jobs/indexes/priority_run_at_id_idx.sql +8 -0
- package/deploy/schemas/app_jobs/tables/jobs/table.sql +26 -0
- package/deploy/schemas/app_jobs/tables/jobs/triggers/decrease_job_queue_count.sql +42 -0
- package/deploy/schemas/app_jobs/tables/jobs/triggers/increase_job_queue_count.sql +32 -0
- package/deploy/schemas/app_jobs/tables/jobs/triggers/notify_worker.sql +13 -0
- package/deploy/schemas/app_jobs/tables/jobs/triggers/timestamps.sql +20 -0
- package/deploy/schemas/app_jobs/tables/scheduled_jobs/grants/grant_select_insert_update_delete_to_administrator.sql +12 -0
- package/deploy/schemas/app_jobs/tables/scheduled_jobs/indexes/scheduled_jobs_locked_by_idx.sql +8 -0
- package/deploy/schemas/app_jobs/tables/scheduled_jobs/indexes/scheduled_jobs_priority_id_idx.sql +8 -0
- package/deploy/schemas/app_jobs/tables/scheduled_jobs/table.sql +26 -0
- package/deploy/schemas/app_jobs/tables/scheduled_jobs/triggers/notify_scheduled_job.sql +12 -0
- package/deploy/schemas/app_jobs/triggers/tg_add_job_with_fields.sql +50 -0
- package/deploy/schemas/app_jobs/triggers/tg_add_job_with_row.sql +26 -0
- package/deploy/schemas/app_jobs/triggers/tg_add_job_with_row_id.sql +26 -0
- package/deploy/schemas/app_jobs/triggers/tg_update_timestamps.sql +21 -0
- package/jest.config.js +15 -0
- package/launchql-jobs.control +8 -0
- package/launchql.plan +38 -0
- package/package.json +29 -0
- package/revert/schemas/app_jobs/helpers/json_build_object_apply.sql +7 -0
- package/revert/schemas/app_jobs/procedures/add_job.sql +7 -0
- package/revert/schemas/app_jobs/procedures/add_scheduled_job.sql +7 -0
- package/revert/schemas/app_jobs/procedures/complete_job.sql +7 -0
- package/revert/schemas/app_jobs/procedures/complete_jobs.sql +7 -0
- package/revert/schemas/app_jobs/procedures/do_notify.sql +7 -0
- package/revert/schemas/app_jobs/procedures/fail_job.sql +7 -0
- package/revert/schemas/app_jobs/procedures/get_job.sql +7 -0
- package/revert/schemas/app_jobs/procedures/get_scheduled_job.sql +7 -0
- package/revert/schemas/app_jobs/procedures/permanently_fail_jobs.sql +7 -0
- package/revert/schemas/app_jobs/procedures/release_jobs.sql +7 -0
- package/revert/schemas/app_jobs/procedures/release_scheduled_jobs.sql +7 -0
- package/revert/schemas/app_jobs/procedures/reschedule_jobs.sql +7 -0
- package/revert/schemas/app_jobs/procedures/run_scheduled_job.sql +7 -0
- package/revert/schemas/app_jobs/schema.sql +7 -0
- package/revert/schemas/app_jobs/tables/job_queues/grants/grant_select_insert_update_delete_to_administrator.sql +7 -0
- package/revert/schemas/app_jobs/tables/job_queues/indexes/job_queues_locked_by_idx.sql +7 -0
- package/revert/schemas/app_jobs/tables/job_queues/table.sql +7 -0
- package/revert/schemas/app_jobs/tables/jobs/grants/grant_select_insert_update_delete_to_administrator.sql +7 -0
- package/revert/schemas/app_jobs/tables/jobs/indexes/jobs_locked_by_idx.sql +7 -0
- package/revert/schemas/app_jobs/tables/jobs/indexes/priority_run_at_id_idx.sql +7 -0
- package/revert/schemas/app_jobs/tables/jobs/table.sql +7 -0
- package/revert/schemas/app_jobs/tables/jobs/triggers/decrease_job_queue_count.sql +7 -0
- package/revert/schemas/app_jobs/tables/jobs/triggers/increase_job_queue_count.sql +7 -0
- package/revert/schemas/app_jobs/tables/jobs/triggers/notify_worker.sql +5 -0
- package/revert/schemas/app_jobs/tables/jobs/triggers/timestamps.sql +9 -0
- package/revert/schemas/app_jobs/tables/scheduled_jobs/grants/grant_select_insert_update_delete_to_administrator.sql +7 -0
- package/revert/schemas/app_jobs/tables/scheduled_jobs/indexes/scheduled_jobs_locked_by_idx.sql +7 -0
- package/revert/schemas/app_jobs/tables/scheduled_jobs/indexes/scheduled_jobs_priority_id_idx.sql +7 -0
- package/revert/schemas/app_jobs/tables/scheduled_jobs/table.sql +7 -0
- package/revert/schemas/app_jobs/tables/scheduled_jobs/triggers/notify_scheduled_job.sql +8 -0
- package/revert/schemas/app_jobs/triggers/tg_add_job_with_fields.sql +7 -0
- package/revert/schemas/app_jobs/triggers/tg_add_job_with_row.sql +7 -0
- package/revert/schemas/app_jobs/triggers/tg_add_job_with_row_id.sql +5 -0
- package/revert/schemas/app_jobs/triggers/tg_update_timestamps.sql +7 -0
- package/sql/launchql-jobs--0.4.6.sql +658 -0
- package/verify/schemas/app_jobs/helpers/json_build_object_apply.sql +7 -0
- package/verify/schemas/app_jobs/procedures/add_job.sql +7 -0
- package/verify/schemas/app_jobs/procedures/add_scheduled_job.sql +7 -0
- package/verify/schemas/app_jobs/procedures/complete_job.sql +7 -0
- package/verify/schemas/app_jobs/procedures/complete_jobs.sql +7 -0
- package/verify/schemas/app_jobs/procedures/do_notify.sql +7 -0
- package/verify/schemas/app_jobs/procedures/fail_job.sql +7 -0
- package/verify/schemas/app_jobs/procedures/get_job.sql +7 -0
- package/verify/schemas/app_jobs/procedures/get_scheduled_job.sql +7 -0
- package/verify/schemas/app_jobs/procedures/permanently_fail_jobs.sql +7 -0
- package/verify/schemas/app_jobs/procedures/release_jobs.sql +7 -0
- package/verify/schemas/app_jobs/procedures/release_scheduled_jobs.sql +7 -0
- package/verify/schemas/app_jobs/procedures/reschedule_jobs.sql +7 -0
- package/verify/schemas/app_jobs/procedures/run_scheduled_job.sql +7 -0
- package/verify/schemas/app_jobs/schema.sql +7 -0
- package/verify/schemas/app_jobs/tables/job_queues/grants/grant_select_insert_update_delete_to_administrator.sql +10 -0
- package/verify/schemas/app_jobs/tables/job_queues/indexes/job_queues_locked_by_idx.sql +7 -0
- package/verify/schemas/app_jobs/tables/job_queues/table.sql +7 -0
- package/verify/schemas/app_jobs/tables/jobs/grants/grant_select_insert_update_delete_to_administrator.sql +10 -0
- package/verify/schemas/app_jobs/tables/jobs/indexes/jobs_locked_by_idx.sql +7 -0
- package/verify/schemas/app_jobs/tables/jobs/indexes/priority_run_at_id_idx.sql +7 -0
- package/verify/schemas/app_jobs/tables/jobs/table.sql +7 -0
- package/verify/schemas/app_jobs/tables/jobs/triggers/decrease_job_queue_count.sql +10 -0
- package/verify/schemas/app_jobs/tables/jobs/triggers/increase_job_queue_count.sql +10 -0
- package/verify/schemas/app_jobs/tables/jobs/triggers/notify_worker.sql +6 -0
- package/verify/schemas/app_jobs/tables/jobs/triggers/timestamps.sql +16 -0
- package/verify/schemas/app_jobs/tables/scheduled_jobs/grants/grant_select_insert_update_delete_to_administrator.sql +10 -0
- package/verify/schemas/app_jobs/tables/scheduled_jobs/indexes/scheduled_jobs_locked_by_idx.sql +7 -0
- package/verify/schemas/app_jobs/tables/scheduled_jobs/indexes/scheduled_jobs_priority_id_idx.sql +7 -0
- package/verify/schemas/app_jobs/tables/scheduled_jobs/table.sql +7 -0
- package/verify/schemas/app_jobs/tables/scheduled_jobs/triggers/notify_scheduled_job.sql +8 -0
- package/verify/schemas/app_jobs/triggers/tg_add_job_with_fields.sql +7 -0
- package/verify/schemas/app_jobs/triggers/tg_add_job_with_row.sql +7 -0
- package/verify/schemas/app_jobs/triggers/tg_add_job_with_row_id.sql +6 -0
- package/verify/schemas/app_jobs/triggers/tg_update_timestamps.sql +7 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Dan Lynch <pyramation@gmail.com>
|
|
4
|
+
Copyright (c) 2025 Interweb, Inc.
|
|
5
|
+
|
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
in the Software without restriction, including without limitation the rights
|
|
9
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
furnished to do so, subject to the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
+
SOFTWARE.
|
package/Makefile
ADDED
package/README.md
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
2
|
+
|
|
3
|
+
exports[`scheduled jobs schedule jobs 1`] = `
|
|
4
|
+
{
|
|
5
|
+
"attempts": null,
|
|
6
|
+
"id": null,
|
|
7
|
+
"key": null,
|
|
8
|
+
"last_error": null,
|
|
9
|
+
"locked_at": null,
|
|
10
|
+
"locked_by": null,
|
|
11
|
+
"max_attempts": null,
|
|
12
|
+
"payload": null,
|
|
13
|
+
"priority": null,
|
|
14
|
+
"task_identifier": null,
|
|
15
|
+
}
|
|
16
|
+
`;
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { getConnections, PgTestClient } from 'pgsql-test';
|
|
2
|
+
|
|
3
|
+
let pg: PgTestClient;
|
|
4
|
+
let teardown: () => Promise<void>;
|
|
5
|
+
|
|
6
|
+
const objs: Record<string, any> = {};
|
|
7
|
+
|
|
8
|
+
describe('scheduled jobs', () => {
|
|
9
|
+
beforeAll(async () => {
|
|
10
|
+
({ pg, teardown } = await getConnections());
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
beforeEach(async () => {
|
|
14
|
+
await pg.beforeEach();
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
afterEach(async () => {
|
|
18
|
+
await pg.afterEach();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
afterAll(async () => {
|
|
22
|
+
await teardown();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('schedule jobs by cron', async () => {
|
|
26
|
+
const result = await pg.one(
|
|
27
|
+
`INSERT INTO app_jobs.scheduled_jobs (task_identifier, schedule_info)
|
|
28
|
+
VALUES ($1, $2)
|
|
29
|
+
RETURNING *`,
|
|
30
|
+
[
|
|
31
|
+
'my_job',
|
|
32
|
+
{
|
|
33
|
+
hour: Array.from({ length: 23 }, (_: unknown, i: number) => i),
|
|
34
|
+
minute: [0, 15, 30, 45],
|
|
35
|
+
dayOfWeek: Array.from({ length: 6 }, (_: unknown, i: number) => i),
|
|
36
|
+
},
|
|
37
|
+
]
|
|
38
|
+
);
|
|
39
|
+
objs.scheduled1 = result;
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('schedule jobs by rule', async () => {
|
|
43
|
+
const start = new Date(Date.now() + 10000);
|
|
44
|
+
const end = new Date(start.getTime() + 180000);
|
|
45
|
+
|
|
46
|
+
const result = await pg.one(
|
|
47
|
+
`INSERT INTO app_jobs.scheduled_jobs (task_identifier, payload, schedule_info)
|
|
48
|
+
VALUES ($1, $2, $3)
|
|
49
|
+
RETURNING *`,
|
|
50
|
+
[
|
|
51
|
+
'my_job',
|
|
52
|
+
{ just: 'run it' },
|
|
53
|
+
{ start, end, rule: '*/1 * * * *' }
|
|
54
|
+
]
|
|
55
|
+
);
|
|
56
|
+
objs.scheduled2 = result;
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('schedule jobs', async () => {
|
|
60
|
+
const [result] = await pg.any(
|
|
61
|
+
`SELECT * FROM app_jobs.run_scheduled_job($1)`,
|
|
62
|
+
[objs.scheduled2.id]
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
const { queue_name, run_at, created_at, updated_at, ...obj } = result;
|
|
66
|
+
expect(obj).toMatchSnapshot();
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('schedule jobs with keys', async () => {
|
|
70
|
+
const start = new Date(Date.now() + 10000);
|
|
71
|
+
const end = new Date(start.getTime() + 180000);
|
|
72
|
+
|
|
73
|
+
const [result] = await pg.any(
|
|
74
|
+
`SELECT * FROM app_jobs.add_scheduled_job(
|
|
75
|
+
identifier := $1::text,
|
|
76
|
+
payload := $2::json,
|
|
77
|
+
schedule_info := $3::json,
|
|
78
|
+
job_key := $4::text,
|
|
79
|
+
queue_name := $5::text,
|
|
80
|
+
max_attempts := $6::integer,
|
|
81
|
+
priority := $7::integer
|
|
82
|
+
)`,
|
|
83
|
+
[
|
|
84
|
+
'my_job',
|
|
85
|
+
{ just: 'run it' },
|
|
86
|
+
{ start, end, rule: '*/1 * * * *' },
|
|
87
|
+
'new_key',
|
|
88
|
+
null,
|
|
89
|
+
25,
|
|
90
|
+
0
|
|
91
|
+
]
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
const {
|
|
95
|
+
queue_name,
|
|
96
|
+
run_at,
|
|
97
|
+
created_at,
|
|
98
|
+
updated_at,
|
|
99
|
+
schedule_info: sch,
|
|
100
|
+
start: s1,
|
|
101
|
+
end: d1,
|
|
102
|
+
...obj
|
|
103
|
+
} = result;
|
|
104
|
+
|
|
105
|
+
const [result2] = await pg.any(
|
|
106
|
+
`SELECT * FROM app_jobs.add_scheduled_job(
|
|
107
|
+
identifier := $1,
|
|
108
|
+
payload := $2,
|
|
109
|
+
schedule_info := $3,
|
|
110
|
+
job_key := $4,
|
|
111
|
+
queue_name := $5,
|
|
112
|
+
max_attempts := $6,
|
|
113
|
+
priority := $7
|
|
114
|
+
)`,
|
|
115
|
+
[
|
|
116
|
+
'my_job',
|
|
117
|
+
{ just: 'run it' },
|
|
118
|
+
{ start, end, rule: '*/1 * * * *' },
|
|
119
|
+
'new_key',
|
|
120
|
+
null,
|
|
121
|
+
25,
|
|
122
|
+
0
|
|
123
|
+
]
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
const {
|
|
127
|
+
queue_name: qn,
|
|
128
|
+
created_at: ca,
|
|
129
|
+
updated_at: ua,
|
|
130
|
+
schedule_info: sch2,
|
|
131
|
+
start: s,
|
|
132
|
+
end: e,
|
|
133
|
+
...obj2
|
|
134
|
+
} = result2;
|
|
135
|
+
|
|
136
|
+
console.log('First insert:', obj);
|
|
137
|
+
console.log('Duplicate insert (job_key conflict):', obj2);
|
|
138
|
+
});
|
|
139
|
+
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
-- Deploy schemas/app_jobs/helpers/json_build_object_apply to pg
|
|
2
|
+
-- requires: schemas/app_jobs/schema
|
|
3
|
+
|
|
4
|
+
BEGIN;
|
|
5
|
+
CREATE FUNCTION app_jobs.json_build_object_apply (arguments text[])
|
|
6
|
+
RETURNS json
|
|
7
|
+
AS $$
|
|
8
|
+
DECLARE
|
|
9
|
+
arg text;
|
|
10
|
+
_sql text;
|
|
11
|
+
_res json;
|
|
12
|
+
args text[];
|
|
13
|
+
BEGIN
|
|
14
|
+
_sql = 'SELECT json_build_object(';
|
|
15
|
+
FOR arg IN
|
|
16
|
+
SELECT
|
|
17
|
+
unnest(arguments)
|
|
18
|
+
LOOP
|
|
19
|
+
args = array_append(args, format('''%s''', arg));
|
|
20
|
+
END LOOP;
|
|
21
|
+
_sql = _sql || format('%s);', array_to_string(args, ','));
|
|
22
|
+
EXECUTE _sql INTO _res;
|
|
23
|
+
RETURN _res;
|
|
24
|
+
END;
|
|
25
|
+
$$
|
|
26
|
+
LANGUAGE 'plpgsql';
|
|
27
|
+
COMMIT;
|
|
28
|
+
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
-- Deploy schemas/app_jobs/procedures/add_job to pg
|
|
2
|
+
-- requires: schemas/app_jobs/schema
|
|
3
|
+
-- requires: schemas/app_jobs/tables/jobs/table
|
|
4
|
+
-- requires: schemas/app_jobs/tables/job_queues/table
|
|
5
|
+
|
|
6
|
+
BEGIN;
|
|
7
|
+
CREATE FUNCTION app_jobs.add_job (
|
|
8
|
+
identifier text,
|
|
9
|
+
payload json DEFAULT '{}' ::json,
|
|
10
|
+
job_key text DEFAULT NULL,
|
|
11
|
+
queue_name text DEFAULT NULL,
|
|
12
|
+
run_at timestamptz DEFAULT now(),
|
|
13
|
+
max_attempts integer DEFAULT 25,
|
|
14
|
+
priority integer DEFAULT 0
|
|
15
|
+
)
|
|
16
|
+
RETURNS app_jobs.jobs
|
|
17
|
+
AS $$
|
|
18
|
+
DECLARE
|
|
19
|
+
v_job app_jobs.jobs;
|
|
20
|
+
BEGIN
|
|
21
|
+
IF job_key IS NOT NULL THEN
|
|
22
|
+
-- Upsert job
|
|
23
|
+
INSERT INTO app_jobs.jobs (task_identifier, payload, queue_name, run_at, max_attempts, KEY, priority)
|
|
24
|
+
VALUES (identifier, coalesce(payload, '{}'::json), queue_name, coalesce(run_at, now()), coalesce(max_attempts, 25), job_key, coalesce(priority, 0))
|
|
25
|
+
ON CONFLICT (KEY)
|
|
26
|
+
DO UPDATE SET
|
|
27
|
+
task_identifier = excluded.task_identifier, payload = excluded.payload, queue_name = excluded.queue_name, max_attempts = excluded.max_attempts, run_at = excluded.run_at, priority = excluded.priority,
|
|
28
|
+
-- always reset error/retry state
|
|
29
|
+
attempts = 0, last_error = NULL
|
|
30
|
+
WHERE
|
|
31
|
+
jobs.locked_at IS NULL
|
|
32
|
+
RETURNING
|
|
33
|
+
* INTO v_job;
|
|
34
|
+
|
|
35
|
+
-- If upsert succeeded (insert or update), return early
|
|
36
|
+
|
|
37
|
+
IF NOT (v_job IS NULL) THEN
|
|
38
|
+
RETURN v_job;
|
|
39
|
+
END IF;
|
|
40
|
+
|
|
41
|
+
-- Upsert failed -> there must be an existing job that is locked. Remove
|
|
42
|
+
-- existing key to allow a new one to be inserted, and prevent any
|
|
43
|
+
-- subsequent retries by bumping attempts to the max allowed.
|
|
44
|
+
|
|
45
|
+
UPDATE
|
|
46
|
+
app_jobs.jobs
|
|
47
|
+
SET
|
|
48
|
+
KEY = NULL,
|
|
49
|
+
attempts = jobs.max_attempts
|
|
50
|
+
WHERE
|
|
51
|
+
KEY = job_key;
|
|
52
|
+
END IF;
|
|
53
|
+
|
|
54
|
+
INSERT INTO app_jobs.jobs (task_identifier, payload, queue_name, run_at, max_attempts, priority)
|
|
55
|
+
VALUES (identifier, payload, queue_name, run_at, max_attempts, priority)
|
|
56
|
+
RETURNING
|
|
57
|
+
* INTO v_job;
|
|
58
|
+
RETURN v_job;
|
|
59
|
+
END;
|
|
60
|
+
$$
|
|
61
|
+
LANGUAGE 'plpgsql'
|
|
62
|
+
VOLATILE
|
|
63
|
+
SECURITY DEFINER;
|
|
64
|
+
COMMIT;
|
|
65
|
+
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
-- Deploy schemas/app_jobs/procedures/add_scheduled_job to pg
|
|
2
|
+
|
|
3
|
+
-- requires: schemas/app_jobs/schema
|
|
4
|
+
-- requires: schemas/app_jobs/tables/scheduled_jobs/table
|
|
5
|
+
|
|
6
|
+
BEGIN;
|
|
7
|
+
|
|
8
|
+
CREATE FUNCTION app_jobs.add_scheduled_job(
|
|
9
|
+
identifier text,
|
|
10
|
+
payload json DEFAULT '{}'::json,
|
|
11
|
+
schedule_info json DEFAULT '{}'::json,
|
|
12
|
+
job_key text DEFAULT NULL,
|
|
13
|
+
queue_name text DEFAULT NULL,
|
|
14
|
+
max_attempts integer DEFAULT 25,
|
|
15
|
+
priority integer DEFAULT 0
|
|
16
|
+
)
|
|
17
|
+
RETURNS app_jobs.scheduled_jobs
|
|
18
|
+
AS $$
|
|
19
|
+
DECLARE
|
|
20
|
+
v_job app_jobs.scheduled_jobs;
|
|
21
|
+
BEGIN
|
|
22
|
+
IF job_key IS NOT NULL THEN
|
|
23
|
+
|
|
24
|
+
-- Upsert job
|
|
25
|
+
INSERT INTO app_jobs.scheduled_jobs (task_identifier, payload, queue_name, schedule_info, max_attempts, KEY, priority)
|
|
26
|
+
VALUES (identifier, coalesce(payload, '{}'::json), queue_name, schedule_info, coalesce(max_attempts, 25), job_key, coalesce(priority, 0))
|
|
27
|
+
ON CONFLICT (KEY)
|
|
28
|
+
DO UPDATE SET
|
|
29
|
+
task_identifier = excluded.task_identifier,
|
|
30
|
+
payload = excluded.payload,
|
|
31
|
+
queue_name = excluded.queue_name,
|
|
32
|
+
max_attempts = excluded.max_attempts,
|
|
33
|
+
schedule_info = excluded.schedule_info,
|
|
34
|
+
priority = excluded.priority
|
|
35
|
+
WHERE
|
|
36
|
+
scheduled_jobs.locked_at IS NULL
|
|
37
|
+
RETURNING
|
|
38
|
+
* INTO v_job;
|
|
39
|
+
|
|
40
|
+
-- If upsert succeeded (insert or update), return early
|
|
41
|
+
|
|
42
|
+
IF NOT (v_job IS NULL) THEN
|
|
43
|
+
RETURN v_job;
|
|
44
|
+
END IF;
|
|
45
|
+
|
|
46
|
+
-- Upsert failed -> there must be an existing scheduled job that is locked. Remove
|
|
47
|
+
-- and allow a new one to be inserted
|
|
48
|
+
|
|
49
|
+
DELETE FROM
|
|
50
|
+
app_jobs.scheduled_jobs
|
|
51
|
+
WHERE
|
|
52
|
+
KEY = job_key;
|
|
53
|
+
END IF;
|
|
54
|
+
|
|
55
|
+
INSERT INTO app_jobs.scheduled_jobs (task_identifier, payload, queue_name, schedule_info, max_attempts, priority)
|
|
56
|
+
VALUES (identifier, payload, queue_name, schedule_info, max_attempts, priority)
|
|
57
|
+
RETURNING
|
|
58
|
+
* INTO v_job;
|
|
59
|
+
RETURN v_job;
|
|
60
|
+
END;
|
|
61
|
+
$$
|
|
62
|
+
LANGUAGE 'plpgsql'
|
|
63
|
+
VOLATILE
|
|
64
|
+
SECURITY DEFINER;
|
|
65
|
+
COMMIT;
|
|
66
|
+
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
-- Deploy schemas/app_jobs/procedures/complete_job to pg
|
|
2
|
+
-- requires: schemas/app_jobs/schema
|
|
3
|
+
-- requires: schemas/app_jobs/tables/jobs/table
|
|
4
|
+
-- requires: schemas/app_jobs/tables/job_queues/table
|
|
5
|
+
|
|
6
|
+
BEGIN;
|
|
7
|
+
CREATE FUNCTION app_jobs.complete_job (worker_id text, job_id bigint)
|
|
8
|
+
RETURNS app_jobs.jobs
|
|
9
|
+
LANGUAGE plpgsql
|
|
10
|
+
AS $$
|
|
11
|
+
DECLARE
|
|
12
|
+
v_row app_jobs.jobs;
|
|
13
|
+
BEGIN
|
|
14
|
+
DELETE FROM app_jobs.jobs
|
|
15
|
+
WHERE id = job_id
|
|
16
|
+
RETURNING
|
|
17
|
+
* INTO v_row;
|
|
18
|
+
IF v_row.queue_name IS NOT NULL THEN
|
|
19
|
+
UPDATE
|
|
20
|
+
app_jobs.job_queues
|
|
21
|
+
SET
|
|
22
|
+
locked_by = NULL,
|
|
23
|
+
locked_at = NULL
|
|
24
|
+
WHERE
|
|
25
|
+
queue_name = v_row.queue_name
|
|
26
|
+
AND locked_by = worker_id;
|
|
27
|
+
END IF;
|
|
28
|
+
RETURN v_row;
|
|
29
|
+
END;
|
|
30
|
+
$$;
|
|
31
|
+
COMMIT;
|
|
32
|
+
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
-- Deploy schemas/app_jobs/procedures/complete_jobs to pg
|
|
2
|
+
-- requires: schemas/app_jobs/schema
|
|
3
|
+
-- requires: schemas/app_jobs/tables/job_queues/table
|
|
4
|
+
-- requires: schemas/app_jobs/tables/jobs/table
|
|
5
|
+
|
|
6
|
+
BEGIN;
|
|
7
|
+
CREATE FUNCTION app_jobs.complete_jobs (job_ids bigint[])
|
|
8
|
+
RETURNS SETOF app_jobs.jobs
|
|
9
|
+
LANGUAGE sql
|
|
10
|
+
AS $$
|
|
11
|
+
DELETE FROM app_jobs.jobs
|
|
12
|
+
WHERE id = ANY (job_ids)
|
|
13
|
+
AND (locked_by IS NULL
|
|
14
|
+
OR locked_at < NOW() - interval '4 hours')
|
|
15
|
+
RETURNING
|
|
16
|
+
*;
|
|
17
|
+
$$;
|
|
18
|
+
COMMIT;
|
|
19
|
+
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
-- Deploy schemas/app_jobs/procedures/do_notify to pg
|
|
2
|
+
-- requires: schemas/app_jobs/schema
|
|
3
|
+
|
|
4
|
+
BEGIN;
|
|
5
|
+
CREATE FUNCTION app_jobs.do_notify ()
|
|
6
|
+
RETURNS TRIGGER
|
|
7
|
+
AS $$
|
|
8
|
+
BEGIN
|
|
9
|
+
PERFORM
|
|
10
|
+
pg_notify(TG_ARGV[0], '');
|
|
11
|
+
RETURN NEW;
|
|
12
|
+
END;
|
|
13
|
+
$$
|
|
14
|
+
LANGUAGE plpgsql;
|
|
15
|
+
COMMIT;
|
|
16
|
+
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
-- Deploy schemas/app_jobs/procedures/fail_job to pg
|
|
2
|
+
-- requires: schemas/app_jobs/schema
|
|
3
|
+
-- requires: schemas/app_jobs/tables/jobs/table
|
|
4
|
+
-- requires: schemas/app_jobs/tables/job_queues/table
|
|
5
|
+
|
|
6
|
+
BEGIN;
|
|
7
|
+
CREATE FUNCTION app_jobs.fail_job (worker_id text, job_id bigint, error_message text)
|
|
8
|
+
RETURNS app_jobs.jobs
|
|
9
|
+
LANGUAGE plpgsql
|
|
10
|
+
STRICT
|
|
11
|
+
AS $$
|
|
12
|
+
DECLARE
|
|
13
|
+
v_row app_jobs.jobs;
|
|
14
|
+
BEGIN
|
|
15
|
+
UPDATE
|
|
16
|
+
app_jobs.jobs
|
|
17
|
+
SET
|
|
18
|
+
last_error = error_message,
|
|
19
|
+
run_at = greatest (now(), run_at) + (exp(least (attempts, 10))::text || ' seconds')::interval,
|
|
20
|
+
locked_by = NULL,
|
|
21
|
+
locked_at = NULL
|
|
22
|
+
WHERE
|
|
23
|
+
id = job_id
|
|
24
|
+
AND locked_by = worker_id
|
|
25
|
+
RETURNING
|
|
26
|
+
* INTO v_row;
|
|
27
|
+
IF v_row.queue_name IS NOT NULL THEN
|
|
28
|
+
UPDATE
|
|
29
|
+
app_jobs.job_queues
|
|
30
|
+
SET
|
|
31
|
+
locked_by = NULL,
|
|
32
|
+
locked_at = NULL
|
|
33
|
+
WHERE
|
|
34
|
+
queue_name = v_row.queue_name
|
|
35
|
+
AND locked_by = worker_id;
|
|
36
|
+
END IF;
|
|
37
|
+
RETURN v_row;
|
|
38
|
+
END;
|
|
39
|
+
$$;
|
|
40
|
+
COMMIT;
|
|
41
|
+
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
-- Deploy schemas/app_jobs/procedures/get_job to pg
|
|
2
|
+
-- requires: schemas/app_jobs/schema
|
|
3
|
+
-- requires: schemas/app_jobs/tables/job_queues/table
|
|
4
|
+
-- requires: schemas/app_jobs/tables/jobs/table
|
|
5
|
+
|
|
6
|
+
BEGIN;
|
|
7
|
+
CREATE FUNCTION app_jobs.get_job (worker_id text, task_identifiers text[] DEFAULT NULL, job_expiry interval DEFAULT '4 hours')
|
|
8
|
+
RETURNS app_jobs.jobs
|
|
9
|
+
LANGUAGE plpgsql
|
|
10
|
+
AS $$
|
|
11
|
+
DECLARE
|
|
12
|
+
v_job_id bigint;
|
|
13
|
+
v_queue_name text;
|
|
14
|
+
v_row app_jobs.jobs;
|
|
15
|
+
v_now timestamptz = now();
|
|
16
|
+
BEGIN
|
|
17
|
+
IF worker_id IS NULL THEN
|
|
18
|
+
RAISE exception 'INVALID_WORKER_ID';
|
|
19
|
+
END IF;
|
|
20
|
+
SELECT
|
|
21
|
+
jobs.queue_name,
|
|
22
|
+
jobs.id INTO v_queue_name,
|
|
23
|
+
v_job_id
|
|
24
|
+
FROM
|
|
25
|
+
app_jobs.jobs
|
|
26
|
+
WHERE (jobs.locked_at IS NULL
|
|
27
|
+
OR jobs.locked_at < (v_now - job_expiry))
|
|
28
|
+
AND (jobs.queue_name IS NULL
|
|
29
|
+
OR EXISTS (
|
|
30
|
+
SELECT
|
|
31
|
+
1
|
|
32
|
+
FROM
|
|
33
|
+
app_jobs.job_queues
|
|
34
|
+
WHERE
|
|
35
|
+
job_queues.queue_name = jobs.queue_name
|
|
36
|
+
AND (job_queues.locked_at IS NULL
|
|
37
|
+
OR job_queues.locked_at < (v_now - job_expiry))
|
|
38
|
+
FOR UPDATE
|
|
39
|
+
SKIP LOCKED))
|
|
40
|
+
AND run_at <= v_now
|
|
41
|
+
AND attempts < max_attempts
|
|
42
|
+
AND (task_identifiers IS NULL
|
|
43
|
+
OR task_identifier = ANY (task_identifiers))
|
|
44
|
+
ORDER BY
|
|
45
|
+
priority ASC,
|
|
46
|
+
run_at ASC,
|
|
47
|
+
id ASC
|
|
48
|
+
LIMIT 1
|
|
49
|
+
FOR UPDATE
|
|
50
|
+
SKIP LOCKED;
|
|
51
|
+
IF v_job_id IS NULL THEN
|
|
52
|
+
RETURN NULL;
|
|
53
|
+
END IF;
|
|
54
|
+
IF v_queue_name IS NOT NULL THEN
|
|
55
|
+
UPDATE
|
|
56
|
+
app_jobs.job_queues
|
|
57
|
+
SET
|
|
58
|
+
locked_by = worker_id,
|
|
59
|
+
locked_at = v_now
|
|
60
|
+
WHERE
|
|
61
|
+
job_queues.queue_name = v_queue_name;
|
|
62
|
+
END IF;
|
|
63
|
+
UPDATE
|
|
64
|
+
app_jobs.jobs
|
|
65
|
+
SET
|
|
66
|
+
attempts = attempts + 1,
|
|
67
|
+
locked_by = worker_id,
|
|
68
|
+
locked_at = v_now
|
|
69
|
+
WHERE
|
|
70
|
+
id = v_job_id
|
|
71
|
+
RETURNING
|
|
72
|
+
* INTO v_row;
|
|
73
|
+
RETURN v_row;
|
|
74
|
+
END;
|
|
75
|
+
$$;
|
|
76
|
+
COMMIT;
|
|
77
|
+
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
-- Deploy schemas/app_jobs/procedures/get_scheduled_job to pg
|
|
2
|
+
-- requires: schemas/app_jobs/schema
|
|
3
|
+
-- requires: schemas/app_jobs/tables/scheduled_jobs/table
|
|
4
|
+
|
|
5
|
+
BEGIN;
|
|
6
|
+
CREATE FUNCTION app_jobs.get_scheduled_job (worker_id text, task_identifiers text[] DEFAULT NULL)
|
|
7
|
+
RETURNS app_jobs.scheduled_jobs
|
|
8
|
+
LANGUAGE plpgsql
|
|
9
|
+
AS $$
|
|
10
|
+
DECLARE
|
|
11
|
+
v_job_id bigint;
|
|
12
|
+
v_row app_jobs.scheduled_jobs;
|
|
13
|
+
BEGIN
|
|
14
|
+
IF worker_id IS NULL THEN
|
|
15
|
+
RAISE exception 'INVALID_WORKER_ID';
|
|
16
|
+
END IF;
|
|
17
|
+
SELECT
|
|
18
|
+
scheduled_jobs.id INTO v_job_id
|
|
19
|
+
FROM
|
|
20
|
+
app_jobs.scheduled_jobs
|
|
21
|
+
WHERE (scheduled_jobs.locked_at IS NULL)
|
|
22
|
+
AND (task_identifiers IS NULL
|
|
23
|
+
OR task_identifier = ANY (task_identifiers))
|
|
24
|
+
ORDER BY
|
|
25
|
+
priority ASC,
|
|
26
|
+
id ASC
|
|
27
|
+
LIMIT 1
|
|
28
|
+
FOR UPDATE
|
|
29
|
+
SKIP LOCKED;
|
|
30
|
+
IF v_job_id IS NULL THEN
|
|
31
|
+
RETURN NULL;
|
|
32
|
+
END IF;
|
|
33
|
+
UPDATE
|
|
34
|
+
app_jobs.scheduled_jobs
|
|
35
|
+
SET
|
|
36
|
+
locked_by = worker_id,
|
|
37
|
+
locked_at = NOW()
|
|
38
|
+
WHERE
|
|
39
|
+
id = v_job_id
|
|
40
|
+
RETURNING
|
|
41
|
+
* INTO v_row;
|
|
42
|
+
RETURN v_row;
|
|
43
|
+
END;
|
|
44
|
+
$$;
|
|
45
|
+
COMMIT;
|
|
46
|
+
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
-- Deploy schemas/app_jobs/procedures/permanently_fail_jobs to pg
|
|
2
|
+
-- requires: schemas/app_jobs/schema
|
|
3
|
+
-- requires: schemas/app_jobs/tables/job_queues/table
|
|
4
|
+
-- requires: schemas/app_jobs/tables/jobs/table
|
|
5
|
+
|
|
6
|
+
BEGIN;
|
|
7
|
+
CREATE FUNCTION app_jobs.permanently_fail_jobs (job_ids bigint[], error_message text DEFAULT NULL)
|
|
8
|
+
RETURNS SETOF app_jobs.jobs
|
|
9
|
+
LANGUAGE sql
|
|
10
|
+
AS $$
|
|
11
|
+
UPDATE
|
|
12
|
+
app_jobs.jobs
|
|
13
|
+
SET
|
|
14
|
+
last_error = coalesce(error_message, 'Manually marked as failed'),
|
|
15
|
+
attempts = max_attempts
|
|
16
|
+
WHERE
|
|
17
|
+
id = ANY (job_ids)
|
|
18
|
+
AND (locked_by IS NULL
|
|
19
|
+
OR locked_at < NOW() - interval '4 hours')
|
|
20
|
+
RETURNING
|
|
21
|
+
*;
|
|
22
|
+
$$;
|
|
23
|
+
COMMIT;
|
|
24
|
+
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
-- Deploy schemas/app_jobs/procedures/release_jobs to pg
|
|
2
|
+
-- requires: schemas/app_jobs/schema
|
|
3
|
+
-- requires: schemas/app_jobs/tables/jobs/table
|
|
4
|
+
-- requires: schemas/app_jobs/tables/job_queues/table
|
|
5
|
+
|
|
6
|
+
BEGIN;
|
|
7
|
+
CREATE FUNCTION app_jobs.release_jobs (worker_id text)
|
|
8
|
+
RETURNS void
|
|
9
|
+
AS $$
|
|
10
|
+
DECLARE
|
|
11
|
+
BEGIN
|
|
12
|
+
-- clear the job
|
|
13
|
+
UPDATE
|
|
14
|
+
app_jobs.jobs
|
|
15
|
+
SET
|
|
16
|
+
locked_at = NULL,
|
|
17
|
+
locked_by = NULL,
|
|
18
|
+
attempts = GREATEST (attempts - 1, 0)
|
|
19
|
+
WHERE
|
|
20
|
+
locked_by = worker_id;
|
|
21
|
+
-- clear the queue
|
|
22
|
+
UPDATE
|
|
23
|
+
app_jobs.job_queues
|
|
24
|
+
SET
|
|
25
|
+
locked_at = NULL,
|
|
26
|
+
locked_by = NULL
|
|
27
|
+
WHERE
|
|
28
|
+
locked_by = worker_id;
|
|
29
|
+
END;
|
|
30
|
+
$$
|
|
31
|
+
LANGUAGE 'plpgsql'
|
|
32
|
+
VOLATILE;
|
|
33
|
+
COMMIT;
|
|
34
|
+
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
-- Deploy schemas/app_jobs/procedures/release_scheduled_jobs to pg
|
|
2
|
+
-- requires: schemas/app_jobs/schema
|
|
3
|
+
-- requires: schemas/app_jobs/tables/scheduled_jobs/table
|
|
4
|
+
|
|
5
|
+
BEGIN;
|
|
6
|
+
CREATE FUNCTION app_jobs.release_scheduled_jobs (worker_id text, ids bigint[] DEFAULT NULL)
|
|
7
|
+
RETURNS void
|
|
8
|
+
AS $$
|
|
9
|
+
DECLARE
|
|
10
|
+
BEGIN
|
|
11
|
+
-- clear the scheduled job
|
|
12
|
+
UPDATE
|
|
13
|
+
app_jobs.scheduled_jobs s
|
|
14
|
+
SET
|
|
15
|
+
locked_at = NULL,
|
|
16
|
+
locked_by = NULL
|
|
17
|
+
WHERE
|
|
18
|
+
locked_by = worker_id
|
|
19
|
+
AND (ids IS NULL
|
|
20
|
+
OR s.id = ANY (ids));
|
|
21
|
+
END;
|
|
22
|
+
$$
|
|
23
|
+
LANGUAGE 'plpgsql'
|
|
24
|
+
VOLATILE;
|
|
25
|
+
COMMIT;
|
|
26
|
+
|