@pgpm/jobs 0.4.0 → 0.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/Makefile +1 -1
- package/README.md +368 -1
- package/launchql-jobs.control +1 -1
- package/package.json +9 -9
- package/sql/{launchql-jobs--0.4.6.sql → launchql-jobs--0.5.0.sql} +450 -450
- /package/{launchql.plan → pgpm.plan} +0 -0
|
@@ -6,6 +6,53 @@ GRANT USAGE ON SCHEMA app_jobs TO administrator;
|
|
|
6
6
|
ALTER DEFAULT PRIVILEGES IN SCHEMA app_jobs
|
|
7
7
|
GRANT EXECUTE ON FUNCTIONS TO administrator;
|
|
8
8
|
|
|
9
|
+
CREATE FUNCTION app_jobs.tg_update_timestamps() RETURNS trigger AS $EOFCODE$
|
|
10
|
+
BEGIN
|
|
11
|
+
IF TG_OP = 'INSERT' THEN
|
|
12
|
+
NEW.created_at = NOW();
|
|
13
|
+
NEW.updated_at = NOW();
|
|
14
|
+
ELSIF TG_OP = 'UPDATE' THEN
|
|
15
|
+
NEW.created_at = OLD.created_at;
|
|
16
|
+
NEW.updated_at = greatest (now(), OLD.updated_at + interval '1 millisecond');
|
|
17
|
+
END IF;
|
|
18
|
+
RETURN NEW;
|
|
19
|
+
END;
|
|
20
|
+
$EOFCODE$ LANGUAGE plpgsql;
|
|
21
|
+
|
|
22
|
+
CREATE FUNCTION app_jobs.tg_add_job_with_row_id() RETURNS trigger AS $EOFCODE$
|
|
23
|
+
BEGIN
|
|
24
|
+
IF (TG_OP = 'INSERT' OR TG_OP = 'UPDATE') THEN
|
|
25
|
+
PERFORM
|
|
26
|
+
app_jobs.add_job (tg_argv[0], json_build_object('id', NEW.id));
|
|
27
|
+
RETURN NEW;
|
|
28
|
+
END IF;
|
|
29
|
+
IF (TG_OP = 'DELETE') THEN
|
|
30
|
+
PERFORM
|
|
31
|
+
app_jobs.add_job (tg_argv[0], json_build_object('id', OLD.id));
|
|
32
|
+
RETURN OLD;
|
|
33
|
+
END IF;
|
|
34
|
+
END;
|
|
35
|
+
$EOFCODE$ LANGUAGE plpgsql VOLATILE SECURITY DEFINER;
|
|
36
|
+
|
|
37
|
+
COMMENT ON FUNCTION app_jobs.tg_add_job_with_row_id IS 'Useful shortcut to create a job on insert or update. Pass the task name as the trigger argument, and the record id will automatically be available on the JSON payload.';
|
|
38
|
+
|
|
39
|
+
CREATE FUNCTION app_jobs.tg_add_job_with_row() RETURNS trigger AS $EOFCODE$
|
|
40
|
+
BEGIN
|
|
41
|
+
IF (TG_OP = 'INSERT' OR TG_OP = 'UPDATE') THEN
|
|
42
|
+
PERFORM
|
|
43
|
+
app_jobs.add_job (TG_ARGV[0], to_json(NEW));
|
|
44
|
+
RETURN NEW;
|
|
45
|
+
END IF;
|
|
46
|
+
IF (TG_OP = 'DELETE') THEN
|
|
47
|
+
PERFORM
|
|
48
|
+
app_jobs.add_job (TG_ARGV[0], to_json(OLD));
|
|
49
|
+
RETURN OLD;
|
|
50
|
+
END IF;
|
|
51
|
+
END;
|
|
52
|
+
$EOFCODE$ LANGUAGE plpgsql VOLATILE SECURITY DEFINER;
|
|
53
|
+
|
|
54
|
+
COMMENT ON FUNCTION app_jobs.tg_add_job_with_row IS 'Useful shortcut to create a job on insert or update. Pass the task name as the trigger argument, and the record data will automatically be available on the JSON payload.';
|
|
55
|
+
|
|
9
56
|
CREATE FUNCTION app_jobs.json_build_object_apply(arguments text[]) RETURNS pg_catalog.json AS $EOFCODE$
|
|
10
57
|
DECLARE
|
|
11
58
|
arg text;
|
|
@@ -26,19 +73,58 @@ BEGIN
|
|
|
26
73
|
END;
|
|
27
74
|
$EOFCODE$ LANGUAGE plpgsql;
|
|
28
75
|
|
|
29
|
-
CREATE
|
|
76
|
+
CREATE FUNCTION app_jobs.trigger_job_with_fields() RETURNS trigger AS $EOFCODE$
|
|
77
|
+
DECLARE
|
|
78
|
+
arg text;
|
|
79
|
+
fn text;
|
|
80
|
+
i int;
|
|
81
|
+
args text[];
|
|
82
|
+
BEGIN
|
|
83
|
+
FOR i IN
|
|
84
|
+
SELECT
|
|
85
|
+
*
|
|
86
|
+
FROM
|
|
87
|
+
generate_series(1, TG_NARGS) g (i)
|
|
88
|
+
LOOP
|
|
89
|
+
IF (i = 1) THEN
|
|
90
|
+
fn = TG_ARGV[i - 1];
|
|
91
|
+
ELSE
|
|
92
|
+
args = array_append(args, TG_ARGV[i - 1]);
|
|
93
|
+
IF (TG_OP = 'INSERT' OR TG_OP = 'UPDATE') THEN
|
|
94
|
+
EXECUTE format('SELECT ($1).%s::text', TG_ARGV[i - 1])
|
|
95
|
+
USING NEW INTO arg;
|
|
96
|
+
END IF;
|
|
97
|
+
IF (TG_OP = 'DELETE') THEN
|
|
98
|
+
EXECUTE format('SELECT ($1).%s::text', TG_ARGV[i - 1])
|
|
99
|
+
USING OLD INTO arg;
|
|
100
|
+
END IF;
|
|
101
|
+
args = array_append(args, arg);
|
|
102
|
+
END IF;
|
|
103
|
+
END LOOP;
|
|
104
|
+
PERFORM
|
|
105
|
+
app_jobs.add_job (fn, app_jobs.json_build_object_apply (args));
|
|
106
|
+
IF (TG_OP = 'INSERT' OR TG_OP = 'UPDATE') THEN
|
|
107
|
+
RETURN NEW;
|
|
108
|
+
END IF;
|
|
109
|
+
IF (TG_OP = 'DELETE') THEN
|
|
110
|
+
RETURN OLD;
|
|
111
|
+
END IF;
|
|
112
|
+
END;
|
|
113
|
+
$EOFCODE$ LANGUAGE plpgsql VOLATILE SECURITY DEFINER;
|
|
114
|
+
|
|
115
|
+
CREATE TABLE app_jobs.scheduled_jobs (
|
|
30
116
|
id bigserial PRIMARY KEY,
|
|
31
117
|
queue_name text DEFAULT public.gen_random_uuid()::text,
|
|
32
118
|
task_identifier text NOT NULL,
|
|
33
119
|
payload pg_catalog.json DEFAULT '{}'::json NOT NULL,
|
|
34
120
|
priority int DEFAULT 0 NOT NULL,
|
|
35
|
-
run_at timestamptz DEFAULT now() NOT NULL,
|
|
36
|
-
attempts int DEFAULT 0 NOT NULL,
|
|
37
121
|
max_attempts int DEFAULT 25 NOT NULL,
|
|
38
122
|
key text,
|
|
39
|
-
last_error text,
|
|
40
123
|
locked_at timestamptz,
|
|
41
124
|
locked_by text,
|
|
125
|
+
schedule_info pg_catalog.json NOT NULL,
|
|
126
|
+
last_scheduled timestamptz,
|
|
127
|
+
last_scheduled_id bigint,
|
|
42
128
|
CHECK (length(key) < 513),
|
|
43
129
|
CHECK (length(task_identifier) < 127),
|
|
44
130
|
CHECK (max_attempts > 0),
|
|
@@ -47,71 +133,39 @@ CREATE TABLE app_jobs.jobs (
|
|
|
47
133
|
UNIQUE (key)
|
|
48
134
|
);
|
|
49
135
|
|
|
50
|
-
CREATE
|
|
51
|
-
queue_name text NOT NULL PRIMARY KEY,
|
|
52
|
-
job_count int DEFAULT 0 NOT NULL,
|
|
53
|
-
locked_at timestamptz,
|
|
54
|
-
locked_by text
|
|
55
|
-
);
|
|
56
|
-
|
|
57
|
-
CREATE FUNCTION app_jobs.add_job(identifier text, payload pg_catalog.json DEFAULT '{}'::json, job_key text DEFAULT NULL, queue_name text DEFAULT NULL, run_at timestamptz DEFAULT now(), max_attempts int DEFAULT 25, priority int DEFAULT 0) RETURNS app_jobs.jobs AS $EOFCODE$
|
|
58
|
-
DECLARE
|
|
59
|
-
v_job app_jobs.jobs;
|
|
136
|
+
CREATE FUNCTION app_jobs.do_notify() RETURNS trigger AS $EOFCODE$
|
|
60
137
|
BEGIN
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
DO UPDATE SET
|
|
67
|
-
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,
|
|
68
|
-
-- always reset error/retry state
|
|
69
|
-
attempts = 0, last_error = NULL
|
|
70
|
-
WHERE
|
|
71
|
-
jobs.locked_at IS NULL
|
|
72
|
-
RETURNING
|
|
73
|
-
* INTO v_job;
|
|
138
|
+
PERFORM
|
|
139
|
+
pg_notify(TG_ARGV[0], '');
|
|
140
|
+
RETURN NEW;
|
|
141
|
+
END;
|
|
142
|
+
$EOFCODE$ LANGUAGE plpgsql;
|
|
74
143
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
144
|
+
CREATE TRIGGER _900_notify_scheduled_job
|
|
145
|
+
AFTER INSERT
|
|
146
|
+
ON app_jobs.scheduled_jobs
|
|
147
|
+
FOR EACH ROW
|
|
148
|
+
EXECUTE PROCEDURE app_jobs.do_notify('scheduled_jobs:insert');
|
|
80
149
|
|
|
81
|
-
|
|
82
|
-
-- existing key to allow a new one to be inserted, and prevent any
|
|
83
|
-
-- subsequent retries by bumping attempts to the max allowed.
|
|
150
|
+
CREATE INDEX scheduled_jobs_priority_id_idx ON app_jobs.scheduled_jobs (priority, id);
|
|
84
151
|
|
|
85
|
-
|
|
86
|
-
app_jobs.jobs
|
|
87
|
-
SET
|
|
88
|
-
KEY = NULL,
|
|
89
|
-
attempts = jobs.max_attempts
|
|
90
|
-
WHERE
|
|
91
|
-
KEY = job_key;
|
|
92
|
-
END IF;
|
|
152
|
+
CREATE INDEX scheduled_jobs_locked_by_idx ON app_jobs.scheduled_jobs (locked_by);
|
|
93
153
|
|
|
94
|
-
|
|
95
|
-
VALUES (identifier, payload, queue_name, run_at, max_attempts, priority)
|
|
96
|
-
RETURNING
|
|
97
|
-
* INTO v_job;
|
|
98
|
-
RETURN v_job;
|
|
99
|
-
END;
|
|
100
|
-
$EOFCODE$ LANGUAGE plpgsql VOLATILE SECURITY DEFINER;
|
|
154
|
+
GRANT SELECT, INSERT, UPDATE, DELETE ON app_jobs.scheduled_jobs TO administrator;
|
|
101
155
|
|
|
102
|
-
CREATE TABLE app_jobs.
|
|
156
|
+
CREATE TABLE app_jobs.jobs (
|
|
103
157
|
id bigserial PRIMARY KEY,
|
|
104
158
|
queue_name text DEFAULT public.gen_random_uuid()::text,
|
|
105
159
|
task_identifier text NOT NULL,
|
|
106
160
|
payload pg_catalog.json DEFAULT '{}'::json NOT NULL,
|
|
107
161
|
priority int DEFAULT 0 NOT NULL,
|
|
162
|
+
run_at timestamptz DEFAULT now() NOT NULL,
|
|
163
|
+
attempts int DEFAULT 0 NOT NULL,
|
|
108
164
|
max_attempts int DEFAULT 25 NOT NULL,
|
|
109
165
|
key text,
|
|
166
|
+
last_error text,
|
|
110
167
|
locked_at timestamptz,
|
|
111
168
|
locked_by text,
|
|
112
|
-
schedule_info pg_catalog.json NOT NULL,
|
|
113
|
-
last_scheduled timestamptz,
|
|
114
|
-
last_scheduled_id bigint,
|
|
115
169
|
CHECK (length(key) < 513),
|
|
116
170
|
CHECK (length(task_identifier) < 127),
|
|
117
171
|
CHECK (max_attempts > 0),
|
|
@@ -120,116 +174,267 @@ CREATE TABLE app_jobs.scheduled_jobs (
|
|
|
120
174
|
UNIQUE (key)
|
|
121
175
|
);
|
|
122
176
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
v_job app_jobs.scheduled_jobs;
|
|
126
|
-
BEGIN
|
|
127
|
-
IF job_key IS NOT NULL THEN
|
|
177
|
+
ALTER TABLE app_jobs.jobs
|
|
178
|
+
ADD COLUMN created_at timestamptz;
|
|
128
179
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
VALUES (identifier, coalesce(payload, '{}'::json), queue_name, schedule_info, coalesce(max_attempts, 25), job_key, coalesce(priority, 0))
|
|
132
|
-
ON CONFLICT (KEY)
|
|
133
|
-
DO UPDATE SET
|
|
134
|
-
task_identifier = excluded.task_identifier,
|
|
135
|
-
payload = excluded.payload,
|
|
136
|
-
queue_name = excluded.queue_name,
|
|
137
|
-
max_attempts = excluded.max_attempts,
|
|
138
|
-
schedule_info = excluded.schedule_info,
|
|
139
|
-
priority = excluded.priority
|
|
140
|
-
WHERE
|
|
141
|
-
scheduled_jobs.locked_at IS NULL
|
|
142
|
-
RETURNING
|
|
143
|
-
* INTO v_job;
|
|
180
|
+
ALTER TABLE app_jobs.jobs
|
|
181
|
+
ALTER COLUMN created_at SET DEFAULT now();
|
|
144
182
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
IF NOT (v_job IS NULL) THEN
|
|
148
|
-
RETURN v_job;
|
|
149
|
-
END IF;
|
|
183
|
+
ALTER TABLE app_jobs.jobs
|
|
184
|
+
ADD COLUMN updated_at timestamptz;
|
|
150
185
|
|
|
151
|
-
|
|
152
|
-
|
|
186
|
+
ALTER TABLE app_jobs.jobs
|
|
187
|
+
ALTER COLUMN updated_at SET DEFAULT now();
|
|
153
188
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
189
|
+
CREATE TRIGGER _100_update_jobs_modtime_tg
|
|
190
|
+
BEFORE INSERT OR UPDATE
|
|
191
|
+
ON app_jobs.jobs
|
|
192
|
+
FOR EACH ROW
|
|
193
|
+
EXECUTE PROCEDURE app_jobs.tg_update_timestamps();
|
|
159
194
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
195
|
+
CREATE FUNCTION app_jobs.tg_increase_job_queue_count() RETURNS trigger AS $EOFCODE$
|
|
196
|
+
BEGIN
|
|
197
|
+
INSERT INTO app_jobs.job_queues (queue_name, job_count)
|
|
198
|
+
VALUES (NEW.queue_name, 1)
|
|
199
|
+
ON CONFLICT (queue_name)
|
|
200
|
+
DO UPDATE SET
|
|
201
|
+
job_count = job_queues.job_count + 1;
|
|
202
|
+
RETURN NEW;
|
|
165
203
|
END;
|
|
166
|
-
$EOFCODE$ LANGUAGE plpgsql VOLATILE
|
|
204
|
+
$EOFCODE$ LANGUAGE plpgsql VOLATILE;
|
|
167
205
|
|
|
168
|
-
CREATE
|
|
206
|
+
CREATE TRIGGER _500_increase_job_queue_count_on_insert
|
|
207
|
+
AFTER INSERT
|
|
208
|
+
ON app_jobs.jobs
|
|
209
|
+
FOR EACH ROW
|
|
210
|
+
WHEN (new.queue_name IS NOT NULL)
|
|
211
|
+
EXECUTE PROCEDURE app_jobs.tg_increase_job_queue_count();
|
|
212
|
+
|
|
213
|
+
CREATE TRIGGER _500_increase_job_queue_count_on_update
|
|
214
|
+
AFTER UPDATE OF queue_name
|
|
215
|
+
ON app_jobs.jobs
|
|
216
|
+
FOR EACH ROW
|
|
217
|
+
WHEN (new.queue_name IS DISTINCT FROM old.queue_name
|
|
218
|
+
AND new.queue_name IS NOT NULL)
|
|
219
|
+
EXECUTE PROCEDURE app_jobs.tg_increase_job_queue_count();
|
|
220
|
+
|
|
221
|
+
CREATE TRIGGER _900_notify_worker
|
|
222
|
+
AFTER INSERT
|
|
223
|
+
ON app_jobs.jobs
|
|
224
|
+
FOR EACH ROW
|
|
225
|
+
EXECUTE PROCEDURE app_jobs.do_notify('jobs:insert');
|
|
226
|
+
|
|
227
|
+
CREATE FUNCTION app_jobs.tg_decrease_job_queue_count() RETURNS trigger AS $EOFCODE$
|
|
169
228
|
DECLARE
|
|
170
|
-
|
|
229
|
+
v_new_job_count int;
|
|
171
230
|
BEGIN
|
|
172
|
-
|
|
173
|
-
|
|
231
|
+
UPDATE
|
|
232
|
+
app_jobs.job_queues
|
|
233
|
+
SET
|
|
234
|
+
job_count = job_queues.job_count - 1
|
|
235
|
+
WHERE
|
|
236
|
+
queue_name = OLD.queue_name
|
|
174
237
|
RETURNING
|
|
175
|
-
|
|
176
|
-
IF
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
238
|
+
job_count INTO v_new_job_count;
|
|
239
|
+
IF v_new_job_count <= 0 THEN
|
|
240
|
+
DELETE FROM app_jobs.job_queues
|
|
241
|
+
WHERE queue_name = OLD.queue_name
|
|
242
|
+
AND job_count <= 0;
|
|
243
|
+
END IF;
|
|
244
|
+
RETURN OLD;
|
|
245
|
+
END;
|
|
246
|
+
$EOFCODE$ LANGUAGE plpgsql VOLATILE;
|
|
247
|
+
|
|
248
|
+
CREATE TRIGGER decrease_job_queue_count_on_delete
|
|
249
|
+
AFTER DELETE
|
|
250
|
+
ON app_jobs.jobs
|
|
251
|
+
FOR EACH ROW
|
|
252
|
+
WHEN (old.queue_name IS NOT NULL)
|
|
253
|
+
EXECUTE PROCEDURE app_jobs.tg_decrease_job_queue_count();
|
|
254
|
+
|
|
255
|
+
CREATE TRIGGER decrease_job_queue_count_on_update
|
|
256
|
+
AFTER UPDATE OF queue_name
|
|
257
|
+
ON app_jobs.jobs
|
|
258
|
+
FOR EACH ROW
|
|
259
|
+
WHEN (new.queue_name IS DISTINCT FROM old.queue_name
|
|
260
|
+
AND old.queue_name IS NOT NULL)
|
|
261
|
+
EXECUTE PROCEDURE app_jobs.tg_decrease_job_queue_count();
|
|
262
|
+
|
|
263
|
+
CREATE INDEX priority_run_at_id_idx ON app_jobs.jobs (priority, run_at, id);
|
|
264
|
+
|
|
265
|
+
CREATE INDEX jobs_locked_by_idx ON app_jobs.jobs (locked_by);
|
|
266
|
+
|
|
267
|
+
GRANT SELECT, INSERT, UPDATE, DELETE ON app_jobs.jobs TO administrator;
|
|
268
|
+
|
|
269
|
+
CREATE TABLE app_jobs.job_queues (
|
|
270
|
+
queue_name text NOT NULL PRIMARY KEY,
|
|
271
|
+
job_count int DEFAULT 0 NOT NULL,
|
|
272
|
+
locked_at timestamptz,
|
|
273
|
+
locked_by text
|
|
274
|
+
);
|
|
275
|
+
|
|
276
|
+
CREATE INDEX job_queues_locked_by_idx ON app_jobs.job_queues (locked_by);
|
|
277
|
+
|
|
278
|
+
GRANT SELECT, INSERT, UPDATE, DELETE ON app_jobs.job_queues TO administrator;
|
|
279
|
+
|
|
280
|
+
CREATE FUNCTION app_jobs.run_scheduled_job(id bigint, job_expiry interval DEFAULT '1 hours') RETURNS app_jobs.jobs AS $EOFCODE$
|
|
281
|
+
DECLARE
|
|
282
|
+
j app_jobs.jobs;
|
|
283
|
+
last_id bigint;
|
|
284
|
+
lkd_by text;
|
|
285
|
+
BEGIN
|
|
286
|
+
-- check last scheduled
|
|
287
|
+
SELECT
|
|
288
|
+
last_scheduled_id
|
|
289
|
+
FROM
|
|
290
|
+
app_jobs.scheduled_jobs s
|
|
291
|
+
WHERE
|
|
292
|
+
s.id = run_scheduled_job.id INTO last_id;
|
|
293
|
+
-- if it's been scheduled check if it's been run
|
|
294
|
+
IF (last_id IS NOT NULL) THEN
|
|
295
|
+
SELECT
|
|
296
|
+
locked_by
|
|
297
|
+
FROM
|
|
298
|
+
app_jobs.jobs js
|
|
182
299
|
WHERE
|
|
183
|
-
|
|
184
|
-
AND
|
|
300
|
+
js.id = last_id
|
|
301
|
+
AND (js.locked_at IS NULL -- never been run
|
|
302
|
+
OR js.locked_at >= (NOW() - job_expiry)
|
|
303
|
+
-- still running within a safe interval
|
|
304
|
+
) INTO lkd_by;
|
|
305
|
+
IF (FOUND) THEN
|
|
306
|
+
RAISE EXCEPTION 'ALREADY_SCHEDULED';
|
|
307
|
+
END IF;
|
|
185
308
|
END IF;
|
|
186
|
-
|
|
309
|
+
-- insert new job
|
|
310
|
+
INSERT INTO app_jobs.jobs (queue_name, task_identifier, payload, priority, max_attempts, key)
|
|
311
|
+
SELECT
|
|
312
|
+
queue_name,
|
|
313
|
+
task_identifier,
|
|
314
|
+
payload,
|
|
315
|
+
priority,
|
|
316
|
+
max_attempts,
|
|
317
|
+
key
|
|
318
|
+
FROM
|
|
319
|
+
app_jobs.scheduled_jobs s
|
|
320
|
+
WHERE
|
|
321
|
+
s.id = run_scheduled_job.id
|
|
322
|
+
RETURNING
|
|
323
|
+
* INTO j;
|
|
324
|
+
-- update the scheduled job
|
|
325
|
+
UPDATE
|
|
326
|
+
app_jobs.scheduled_jobs s
|
|
327
|
+
SET
|
|
328
|
+
last_scheduled = NOW(),
|
|
329
|
+
last_scheduled_id = j.id
|
|
330
|
+
WHERE
|
|
331
|
+
s.id = run_scheduled_job.id;
|
|
332
|
+
RETURN j;
|
|
187
333
|
END;
|
|
188
|
-
$EOFCODE
|
|
334
|
+
$EOFCODE$ LANGUAGE plpgsql VOLATILE;
|
|
189
335
|
|
|
190
|
-
CREATE FUNCTION app_jobs.
|
|
191
|
-
|
|
192
|
-
|
|
336
|
+
CREATE FUNCTION app_jobs.reschedule_jobs(job_ids bigint[], run_at timestamptz DEFAULT NULL, priority int DEFAULT NULL, attempts int DEFAULT NULL, max_attempts int DEFAULT NULL) RETURNS SETOF app_jobs.jobs LANGUAGE sql AS $EOFCODE$
|
|
337
|
+
UPDATE
|
|
338
|
+
app_jobs.jobs
|
|
339
|
+
SET
|
|
340
|
+
run_at = coalesce(reschedule_jobs.run_at, jobs.run_at),
|
|
341
|
+
priority = coalesce(reschedule_jobs.priority, jobs.priority),
|
|
342
|
+
attempts = coalesce(reschedule_jobs.attempts, jobs.attempts),
|
|
343
|
+
max_attempts = coalesce(reschedule_jobs.max_attempts, jobs.max_attempts)
|
|
344
|
+
WHERE
|
|
345
|
+
id = ANY (job_ids)
|
|
193
346
|
AND (locked_by IS NULL
|
|
194
347
|
OR locked_at < NOW() - interval '4 hours')
|
|
195
348
|
RETURNING
|
|
196
349
|
*;
|
|
197
350
|
$EOFCODE$;
|
|
198
351
|
|
|
199
|
-
CREATE FUNCTION app_jobs.
|
|
352
|
+
CREATE FUNCTION app_jobs.release_scheduled_jobs(worker_id text, ids bigint[] DEFAULT NULL) RETURNS void AS $EOFCODE$
|
|
353
|
+
DECLARE
|
|
200
354
|
BEGIN
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
355
|
+
-- clear the scheduled job
|
|
356
|
+
UPDATE
|
|
357
|
+
app_jobs.scheduled_jobs s
|
|
358
|
+
SET
|
|
359
|
+
locked_at = NULL,
|
|
360
|
+
locked_by = NULL
|
|
361
|
+
WHERE
|
|
362
|
+
locked_by = worker_id
|
|
363
|
+
AND (ids IS NULL
|
|
364
|
+
OR s.id = ANY (ids));
|
|
204
365
|
END;
|
|
205
|
-
$EOFCODE$ LANGUAGE plpgsql;
|
|
366
|
+
$EOFCODE$ LANGUAGE plpgsql VOLATILE;
|
|
206
367
|
|
|
207
|
-
CREATE FUNCTION app_jobs.
|
|
368
|
+
CREATE FUNCTION app_jobs.release_jobs(worker_id text) RETURNS void AS $EOFCODE$
|
|
208
369
|
DECLARE
|
|
209
|
-
v_row app_jobs.jobs;
|
|
210
370
|
BEGIN
|
|
371
|
+
-- clear the job
|
|
211
372
|
UPDATE
|
|
212
373
|
app_jobs.jobs
|
|
213
374
|
SET
|
|
214
|
-
|
|
215
|
-
run_at = greatest (now(), run_at) + (exp(least (attempts, 10))::text || ' seconds')::interval,
|
|
375
|
+
locked_at = NULL,
|
|
216
376
|
locked_by = NULL,
|
|
217
|
-
|
|
377
|
+
attempts = GREATEST (attempts - 1, 0)
|
|
218
378
|
WHERE
|
|
219
|
-
|
|
220
|
-
|
|
379
|
+
locked_by = worker_id;
|
|
380
|
+
-- clear the queue
|
|
381
|
+
UPDATE
|
|
382
|
+
app_jobs.job_queues
|
|
383
|
+
SET
|
|
384
|
+
locked_at = NULL,
|
|
385
|
+
locked_by = NULL
|
|
386
|
+
WHERE
|
|
387
|
+
locked_by = worker_id;
|
|
388
|
+
END;
|
|
389
|
+
$EOFCODE$ LANGUAGE plpgsql VOLATILE;
|
|
390
|
+
|
|
391
|
+
CREATE FUNCTION app_jobs.permanently_fail_jobs(job_ids bigint[], error_message text DEFAULT NULL) RETURNS SETOF app_jobs.jobs LANGUAGE sql AS $EOFCODE$
|
|
392
|
+
UPDATE
|
|
393
|
+
app_jobs.jobs
|
|
394
|
+
SET
|
|
395
|
+
last_error = coalesce(error_message, 'Manually marked as failed'),
|
|
396
|
+
attempts = max_attempts
|
|
397
|
+
WHERE
|
|
398
|
+
id = ANY (job_ids)
|
|
399
|
+
AND (locked_by IS NULL
|
|
400
|
+
OR locked_at < NOW() - interval '4 hours')
|
|
221
401
|
RETURNING
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
402
|
+
*;
|
|
403
|
+
$EOFCODE$;
|
|
404
|
+
|
|
405
|
+
CREATE FUNCTION app_jobs.get_scheduled_job(worker_id text, task_identifiers text[] DEFAULT NULL) RETURNS app_jobs.scheduled_jobs LANGUAGE plpgsql AS $EOFCODE$
|
|
406
|
+
DECLARE
|
|
407
|
+
v_job_id bigint;
|
|
408
|
+
v_row app_jobs.scheduled_jobs;
|
|
409
|
+
BEGIN
|
|
410
|
+
IF worker_id IS NULL THEN
|
|
411
|
+
RAISE exception 'INVALID_WORKER_ID';
|
|
412
|
+
END IF;
|
|
413
|
+
SELECT
|
|
414
|
+
scheduled_jobs.id INTO v_job_id
|
|
415
|
+
FROM
|
|
416
|
+
app_jobs.scheduled_jobs
|
|
417
|
+
WHERE (scheduled_jobs.locked_at IS NULL)
|
|
418
|
+
AND (task_identifiers IS NULL
|
|
419
|
+
OR task_identifier = ANY (task_identifiers))
|
|
420
|
+
ORDER BY
|
|
421
|
+
priority ASC,
|
|
422
|
+
id ASC
|
|
423
|
+
LIMIT 1
|
|
424
|
+
FOR UPDATE
|
|
425
|
+
SKIP LOCKED;
|
|
426
|
+
IF v_job_id IS NULL THEN
|
|
427
|
+
RETURN NULL;
|
|
232
428
|
END IF;
|
|
429
|
+
UPDATE
|
|
430
|
+
app_jobs.scheduled_jobs
|
|
431
|
+
SET
|
|
432
|
+
locked_by = worker_id,
|
|
433
|
+
locked_at = NOW()
|
|
434
|
+
WHERE
|
|
435
|
+
id = v_job_id
|
|
436
|
+
RETURNING
|
|
437
|
+
* INTO v_row;
|
|
233
438
|
RETURN v_row;
|
|
234
439
|
END;
|
|
235
440
|
$EOFCODE$;
|
|
@@ -301,358 +506,153 @@ BEGIN
|
|
|
301
506
|
END;
|
|
302
507
|
$EOFCODE$;
|
|
303
508
|
|
|
304
|
-
CREATE FUNCTION app_jobs.
|
|
509
|
+
CREATE FUNCTION app_jobs.fail_job(worker_id text, job_id bigint, error_message text) RETURNS app_jobs.jobs LANGUAGE plpgsql STRICT AS $EOFCODE$
|
|
305
510
|
DECLARE
|
|
306
|
-
|
|
307
|
-
v_row app_jobs.scheduled_jobs;
|
|
511
|
+
v_row app_jobs.jobs;
|
|
308
512
|
BEGIN
|
|
309
|
-
IF worker_id IS NULL THEN
|
|
310
|
-
RAISE exception 'INVALID_WORKER_ID';
|
|
311
|
-
END IF;
|
|
312
|
-
SELECT
|
|
313
|
-
scheduled_jobs.id INTO v_job_id
|
|
314
|
-
FROM
|
|
315
|
-
app_jobs.scheduled_jobs
|
|
316
|
-
WHERE (scheduled_jobs.locked_at IS NULL)
|
|
317
|
-
AND (task_identifiers IS NULL
|
|
318
|
-
OR task_identifier = ANY (task_identifiers))
|
|
319
|
-
ORDER BY
|
|
320
|
-
priority ASC,
|
|
321
|
-
id ASC
|
|
322
|
-
LIMIT 1
|
|
323
|
-
FOR UPDATE
|
|
324
|
-
SKIP LOCKED;
|
|
325
|
-
IF v_job_id IS NULL THEN
|
|
326
|
-
RETURN NULL;
|
|
327
|
-
END IF;
|
|
328
513
|
UPDATE
|
|
329
|
-
app_jobs.
|
|
514
|
+
app_jobs.jobs
|
|
330
515
|
SET
|
|
331
|
-
|
|
332
|
-
|
|
516
|
+
last_error = error_message,
|
|
517
|
+
run_at = greatest (now(), run_at) + (exp(least (attempts, 10))::text || ' seconds')::interval,
|
|
518
|
+
locked_by = NULL,
|
|
519
|
+
locked_at = NULL
|
|
333
520
|
WHERE
|
|
334
|
-
id =
|
|
521
|
+
id = job_id
|
|
522
|
+
AND locked_by = worker_id
|
|
335
523
|
RETURNING
|
|
336
524
|
* INTO v_row;
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
id = ANY (job_ids)
|
|
349
|
-
AND (locked_by IS NULL
|
|
350
|
-
OR locked_at < NOW() - interval '4 hours')
|
|
351
|
-
RETURNING
|
|
352
|
-
*;
|
|
353
|
-
$EOFCODE$;
|
|
354
|
-
|
|
355
|
-
CREATE FUNCTION app_jobs.release_jobs(worker_id text) RETURNS void AS $EOFCODE$
|
|
356
|
-
DECLARE
|
|
357
|
-
BEGIN
|
|
358
|
-
-- clear the job
|
|
359
|
-
UPDATE
|
|
360
|
-
app_jobs.jobs
|
|
361
|
-
SET
|
|
362
|
-
locked_at = NULL,
|
|
363
|
-
locked_by = NULL,
|
|
364
|
-
attempts = GREATEST (attempts - 1, 0)
|
|
365
|
-
WHERE
|
|
366
|
-
locked_by = worker_id;
|
|
367
|
-
-- clear the queue
|
|
368
|
-
UPDATE
|
|
369
|
-
app_jobs.job_queues
|
|
370
|
-
SET
|
|
371
|
-
locked_at = NULL,
|
|
372
|
-
locked_by = NULL
|
|
373
|
-
WHERE
|
|
374
|
-
locked_by = worker_id;
|
|
375
|
-
END;
|
|
376
|
-
$EOFCODE$ LANGUAGE plpgsql VOLATILE;
|
|
377
|
-
|
|
378
|
-
CREATE FUNCTION app_jobs.release_scheduled_jobs(worker_id text, ids bigint[] DEFAULT NULL) RETURNS void AS $EOFCODE$
|
|
379
|
-
DECLARE
|
|
380
|
-
BEGIN
|
|
381
|
-
-- clear the scheduled job
|
|
382
|
-
UPDATE
|
|
383
|
-
app_jobs.scheduled_jobs s
|
|
384
|
-
SET
|
|
385
|
-
locked_at = NULL,
|
|
386
|
-
locked_by = NULL
|
|
387
|
-
WHERE
|
|
388
|
-
locked_by = worker_id
|
|
389
|
-
AND (ids IS NULL
|
|
390
|
-
OR s.id = ANY (ids));
|
|
525
|
+
IF v_row.queue_name IS NOT NULL THEN
|
|
526
|
+
UPDATE
|
|
527
|
+
app_jobs.job_queues
|
|
528
|
+
SET
|
|
529
|
+
locked_by = NULL,
|
|
530
|
+
locked_at = NULL
|
|
531
|
+
WHERE
|
|
532
|
+
queue_name = v_row.queue_name
|
|
533
|
+
AND locked_by = worker_id;
|
|
534
|
+
END IF;
|
|
535
|
+
RETURN v_row;
|
|
391
536
|
END;
|
|
392
|
-
$EOFCODE
|
|
537
|
+
$EOFCODE$;
|
|
393
538
|
|
|
394
|
-
CREATE FUNCTION app_jobs.
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
SET
|
|
398
|
-
run_at = coalesce(reschedule_jobs.run_at, jobs.run_at),
|
|
399
|
-
priority = coalesce(reschedule_jobs.priority, jobs.priority),
|
|
400
|
-
attempts = coalesce(reschedule_jobs.attempts, jobs.attempts),
|
|
401
|
-
max_attempts = coalesce(reschedule_jobs.max_attempts, jobs.max_attempts)
|
|
402
|
-
WHERE
|
|
403
|
-
id = ANY (job_ids)
|
|
539
|
+
CREATE FUNCTION app_jobs.complete_jobs(job_ids bigint[]) RETURNS SETOF app_jobs.jobs LANGUAGE sql AS $EOFCODE$
|
|
540
|
+
DELETE FROM app_jobs.jobs
|
|
541
|
+
WHERE id = ANY (job_ids)
|
|
404
542
|
AND (locked_by IS NULL
|
|
405
543
|
OR locked_at < NOW() - interval '4 hours')
|
|
406
544
|
RETURNING
|
|
407
545
|
*;
|
|
408
546
|
$EOFCODE$;
|
|
409
547
|
|
|
410
|
-
CREATE FUNCTION app_jobs.
|
|
548
|
+
CREATE FUNCTION app_jobs.complete_job(worker_id text, job_id bigint) RETURNS app_jobs.jobs LANGUAGE plpgsql AS $EOFCODE$
|
|
411
549
|
DECLARE
|
|
412
|
-
|
|
413
|
-
last_id bigint;
|
|
414
|
-
lkd_by text;
|
|
550
|
+
v_row app_jobs.jobs;
|
|
415
551
|
BEGIN
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
locked_by
|
|
427
|
-
FROM
|
|
428
|
-
app_jobs.jobs js
|
|
552
|
+
DELETE FROM app_jobs.jobs
|
|
553
|
+
WHERE id = job_id
|
|
554
|
+
RETURNING
|
|
555
|
+
* INTO v_row;
|
|
556
|
+
IF v_row.queue_name IS NOT NULL THEN
|
|
557
|
+
UPDATE
|
|
558
|
+
app_jobs.job_queues
|
|
559
|
+
SET
|
|
560
|
+
locked_by = NULL,
|
|
561
|
+
locked_at = NULL
|
|
429
562
|
WHERE
|
|
430
|
-
|
|
431
|
-
AND
|
|
432
|
-
OR js.locked_at >= (NOW() - job_expiry)
|
|
433
|
-
-- still running within a safe interval
|
|
434
|
-
) INTO lkd_by;
|
|
435
|
-
IF (FOUND) THEN
|
|
436
|
-
RAISE EXCEPTION 'ALREADY_SCHEDULED';
|
|
437
|
-
END IF;
|
|
563
|
+
queue_name = v_row.queue_name
|
|
564
|
+
AND locked_by = worker_id;
|
|
438
565
|
END IF;
|
|
439
|
-
|
|
440
|
-
INSERT INTO app_jobs.jobs (queue_name, task_identifier, payload, priority, max_attempts, key)
|
|
441
|
-
SELECT
|
|
442
|
-
queue_name,
|
|
443
|
-
task_identifier,
|
|
444
|
-
payload,
|
|
445
|
-
priority,
|
|
446
|
-
max_attempts,
|
|
447
|
-
key
|
|
448
|
-
FROM
|
|
449
|
-
app_jobs.scheduled_jobs s
|
|
450
|
-
WHERE
|
|
451
|
-
s.id = run_scheduled_job.id
|
|
452
|
-
RETURNING
|
|
453
|
-
* INTO j;
|
|
454
|
-
-- update the scheduled job
|
|
455
|
-
UPDATE
|
|
456
|
-
app_jobs.scheduled_jobs s
|
|
457
|
-
SET
|
|
458
|
-
last_scheduled = NOW(),
|
|
459
|
-
last_scheduled_id = j.id
|
|
460
|
-
WHERE
|
|
461
|
-
s.id = run_scheduled_job.id;
|
|
462
|
-
RETURN j;
|
|
566
|
+
RETURN v_row;
|
|
463
567
|
END;
|
|
464
|
-
$EOFCODE
|
|
465
|
-
|
|
466
|
-
GRANT SELECT, INSERT, UPDATE, DELETE ON app_jobs.job_queues TO administrator;
|
|
467
|
-
|
|
468
|
-
CREATE INDEX job_queues_locked_by_idx ON app_jobs.job_queues (locked_by);
|
|
469
|
-
|
|
470
|
-
GRANT SELECT, INSERT, UPDATE, DELETE ON app_jobs.jobs TO administrator;
|
|
471
|
-
|
|
472
|
-
CREATE INDEX jobs_locked_by_idx ON app_jobs.jobs (locked_by);
|
|
473
|
-
|
|
474
|
-
CREATE INDEX priority_run_at_id_idx ON app_jobs.jobs (priority, run_at, id);
|
|
568
|
+
$EOFCODE$;
|
|
475
569
|
|
|
476
|
-
CREATE FUNCTION app_jobs.
|
|
570
|
+
CREATE FUNCTION app_jobs.add_scheduled_job(identifier text, payload pg_catalog.json DEFAULT '{}'::json, schedule_info pg_catalog.json DEFAULT '{}'::json, job_key text DEFAULT NULL, queue_name text DEFAULT NULL, max_attempts int DEFAULT 25, priority int DEFAULT 0) RETURNS app_jobs.scheduled_jobs AS $EOFCODE$
|
|
477
571
|
DECLARE
|
|
478
|
-
|
|
479
|
-
BEGIN
|
|
480
|
-
UPDATE
|
|
481
|
-
app_jobs.job_queues
|
|
482
|
-
SET
|
|
483
|
-
job_count = job_queues.job_count - 1
|
|
484
|
-
WHERE
|
|
485
|
-
queue_name = OLD.queue_name
|
|
486
|
-
RETURNING
|
|
487
|
-
job_count INTO v_new_job_count;
|
|
488
|
-
IF v_new_job_count <= 0 THEN
|
|
489
|
-
DELETE FROM app_jobs.job_queues
|
|
490
|
-
WHERE queue_name = OLD.queue_name
|
|
491
|
-
AND job_count <= 0;
|
|
492
|
-
END IF;
|
|
493
|
-
RETURN OLD;
|
|
494
|
-
END;
|
|
495
|
-
$EOFCODE$ LANGUAGE plpgsql VOLATILE;
|
|
496
|
-
|
|
497
|
-
CREATE TRIGGER decrease_job_queue_count_on_delete
|
|
498
|
-
AFTER DELETE
|
|
499
|
-
ON app_jobs.jobs
|
|
500
|
-
FOR EACH ROW
|
|
501
|
-
WHEN (old.queue_name IS NOT NULL)
|
|
502
|
-
EXECUTE PROCEDURE app_jobs.tg_decrease_job_queue_count();
|
|
503
|
-
|
|
504
|
-
CREATE TRIGGER decrease_job_queue_count_on_update
|
|
505
|
-
AFTER UPDATE OF queue_name
|
|
506
|
-
ON app_jobs.jobs
|
|
507
|
-
FOR EACH ROW
|
|
508
|
-
WHEN (new.queue_name IS DISTINCT FROM old.queue_name
|
|
509
|
-
AND old.queue_name IS NOT NULL)
|
|
510
|
-
EXECUTE PROCEDURE app_jobs.tg_decrease_job_queue_count();
|
|
511
|
-
|
|
512
|
-
CREATE FUNCTION app_jobs.tg_increase_job_queue_count() RETURNS trigger AS $EOFCODE$
|
|
572
|
+
v_job app_jobs.scheduled_jobs;
|
|
513
573
|
BEGIN
|
|
514
|
-
|
|
515
|
-
VALUES (NEW.queue_name, 1)
|
|
516
|
-
ON CONFLICT (queue_name)
|
|
517
|
-
DO UPDATE SET
|
|
518
|
-
job_count = job_queues.job_count + 1;
|
|
519
|
-
RETURN NEW;
|
|
520
|
-
END;
|
|
521
|
-
$EOFCODE$ LANGUAGE plpgsql VOLATILE;
|
|
574
|
+
IF job_key IS NOT NULL THEN
|
|
522
575
|
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
576
|
+
-- Upsert job
|
|
577
|
+
INSERT INTO app_jobs.scheduled_jobs (task_identifier, payload, queue_name, schedule_info, max_attempts, KEY, priority)
|
|
578
|
+
VALUES (identifier, coalesce(payload, '{}'::json), queue_name, schedule_info, coalesce(max_attempts, 25), job_key, coalesce(priority, 0))
|
|
579
|
+
ON CONFLICT (KEY)
|
|
580
|
+
DO UPDATE SET
|
|
581
|
+
task_identifier = excluded.task_identifier,
|
|
582
|
+
payload = excluded.payload,
|
|
583
|
+
queue_name = excluded.queue_name,
|
|
584
|
+
max_attempts = excluded.max_attempts,
|
|
585
|
+
schedule_info = excluded.schedule_info,
|
|
586
|
+
priority = excluded.priority
|
|
587
|
+
WHERE
|
|
588
|
+
scheduled_jobs.locked_at IS NULL
|
|
589
|
+
RETURNING
|
|
590
|
+
* INTO v_job;
|
|
529
591
|
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
AND new.queue_name IS NOT NULL)
|
|
536
|
-
EXECUTE PROCEDURE app_jobs.tg_increase_job_queue_count();
|
|
592
|
+
-- If upsert succeeded (insert or update), return early
|
|
593
|
+
|
|
594
|
+
IF NOT (v_job IS NULL) THEN
|
|
595
|
+
RETURN v_job;
|
|
596
|
+
END IF;
|
|
537
597
|
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
ON app_jobs.jobs
|
|
541
|
-
FOR EACH ROW
|
|
542
|
-
EXECUTE PROCEDURE app_jobs.do_notify('jobs:insert');
|
|
598
|
+
-- Upsert failed -> there must be an existing scheduled job that is locked. Remove
|
|
599
|
+
-- and allow a new one to be inserted
|
|
543
600
|
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
NEW.updated_at = NOW();
|
|
549
|
-
ELSIF TG_OP = 'UPDATE' THEN
|
|
550
|
-
NEW.created_at = OLD.created_at;
|
|
551
|
-
NEW.updated_at = greatest (now(), OLD.updated_at + interval '1 millisecond');
|
|
601
|
+
DELETE FROM
|
|
602
|
+
app_jobs.scheduled_jobs
|
|
603
|
+
WHERE
|
|
604
|
+
KEY = job_key;
|
|
552
605
|
END IF;
|
|
553
|
-
RETURN NEW;
|
|
554
|
-
END;
|
|
555
|
-
$EOFCODE$ LANGUAGE plpgsql;
|
|
556
|
-
|
|
557
|
-
ALTER TABLE app_jobs.jobs
|
|
558
|
-
ADD COLUMN created_at timestamptz;
|
|
559
|
-
|
|
560
|
-
ALTER TABLE app_jobs.jobs
|
|
561
|
-
ALTER COLUMN created_at SET DEFAULT now();
|
|
562
|
-
|
|
563
|
-
ALTER TABLE app_jobs.jobs
|
|
564
|
-
ADD COLUMN updated_at timestamptz;
|
|
565
|
-
|
|
566
|
-
ALTER TABLE app_jobs.jobs
|
|
567
|
-
ALTER COLUMN updated_at SET DEFAULT now();
|
|
568
|
-
|
|
569
|
-
CREATE TRIGGER _100_update_jobs_modtime_tg
|
|
570
|
-
BEFORE INSERT OR UPDATE
|
|
571
|
-
ON app_jobs.jobs
|
|
572
|
-
FOR EACH ROW
|
|
573
|
-
EXECUTE PROCEDURE app_jobs.tg_update_timestamps();
|
|
574
|
-
|
|
575
|
-
GRANT SELECT, INSERT, UPDATE, DELETE ON app_jobs.scheduled_jobs TO administrator;
|
|
576
|
-
|
|
577
|
-
CREATE INDEX scheduled_jobs_locked_by_idx ON app_jobs.scheduled_jobs (locked_by);
|
|
578
|
-
|
|
579
|
-
CREATE INDEX scheduled_jobs_priority_id_idx ON app_jobs.scheduled_jobs (priority, id);
|
|
580
|
-
|
|
581
|
-
CREATE TRIGGER _900_notify_scheduled_job
|
|
582
|
-
AFTER INSERT
|
|
583
|
-
ON app_jobs.scheduled_jobs
|
|
584
|
-
FOR EACH ROW
|
|
585
|
-
EXECUTE PROCEDURE app_jobs.do_notify('scheduled_jobs:insert');
|
|
586
606
|
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
args text[];
|
|
593
|
-
BEGIN
|
|
594
|
-
FOR i IN
|
|
595
|
-
SELECT
|
|
596
|
-
*
|
|
597
|
-
FROM
|
|
598
|
-
generate_series(1, TG_NARGS) g (i)
|
|
599
|
-
LOOP
|
|
600
|
-
IF (i = 1) THEN
|
|
601
|
-
fn = TG_ARGV[i - 1];
|
|
602
|
-
ELSE
|
|
603
|
-
args = array_append(args, TG_ARGV[i - 1]);
|
|
604
|
-
IF (TG_OP = 'INSERT' OR TG_OP = 'UPDATE') THEN
|
|
605
|
-
EXECUTE format('SELECT ($1).%s::text', TG_ARGV[i - 1])
|
|
606
|
-
USING NEW INTO arg;
|
|
607
|
-
END IF;
|
|
608
|
-
IF (TG_OP = 'DELETE') THEN
|
|
609
|
-
EXECUTE format('SELECT ($1).%s::text', TG_ARGV[i - 1])
|
|
610
|
-
USING OLD INTO arg;
|
|
611
|
-
END IF;
|
|
612
|
-
args = array_append(args, arg);
|
|
613
|
-
END IF;
|
|
614
|
-
END LOOP;
|
|
615
|
-
PERFORM
|
|
616
|
-
app_jobs.add_job (fn, app_jobs.json_build_object_apply (args));
|
|
617
|
-
IF (TG_OP = 'INSERT' OR TG_OP = 'UPDATE') THEN
|
|
618
|
-
RETURN NEW;
|
|
619
|
-
END IF;
|
|
620
|
-
IF (TG_OP = 'DELETE') THEN
|
|
621
|
-
RETURN OLD;
|
|
622
|
-
END IF;
|
|
607
|
+
INSERT INTO app_jobs.scheduled_jobs (task_identifier, payload, queue_name, schedule_info, max_attempts, priority)
|
|
608
|
+
VALUES (identifier, payload, queue_name, schedule_info, max_attempts, priority)
|
|
609
|
+
RETURNING
|
|
610
|
+
* INTO v_job;
|
|
611
|
+
RETURN v_job;
|
|
623
612
|
END;
|
|
624
613
|
$EOFCODE$ LANGUAGE plpgsql VOLATILE SECURITY DEFINER;
|
|
625
614
|
|
|
626
|
-
CREATE FUNCTION app_jobs.
|
|
615
|
+
CREATE FUNCTION app_jobs.add_job(identifier text, payload pg_catalog.json DEFAULT '{}'::json, job_key text DEFAULT NULL, queue_name text DEFAULT NULL, run_at timestamptz DEFAULT now(), max_attempts int DEFAULT 25, priority int DEFAULT 0) RETURNS app_jobs.jobs AS $EOFCODE$
|
|
616
|
+
DECLARE
|
|
617
|
+
v_job app_jobs.jobs;
|
|
627
618
|
BEGIN
|
|
628
|
-
IF
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
619
|
+
IF job_key IS NOT NULL THEN
|
|
620
|
+
-- Upsert job
|
|
621
|
+
INSERT INTO app_jobs.jobs (task_identifier, payload, queue_name, run_at, max_attempts, KEY, priority)
|
|
622
|
+
VALUES (identifier, coalesce(payload, '{}'::json), queue_name, coalesce(run_at, now()), coalesce(max_attempts, 25), job_key, coalesce(priority, 0))
|
|
623
|
+
ON CONFLICT (KEY)
|
|
624
|
+
DO UPDATE SET
|
|
625
|
+
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,
|
|
626
|
+
-- always reset error/retry state
|
|
627
|
+
attempts = 0, last_error = NULL
|
|
628
|
+
WHERE
|
|
629
|
+
jobs.locked_at IS NULL
|
|
630
|
+
RETURNING
|
|
631
|
+
* INTO v_job;
|
|
640
632
|
|
|
641
|
-
|
|
633
|
+
-- If upsert succeeded (insert or update), return early
|
|
634
|
+
|
|
635
|
+
IF NOT (v_job IS NULL) THEN
|
|
636
|
+
RETURN v_job;
|
|
637
|
+
END IF;
|
|
642
638
|
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
639
|
+
-- Upsert failed -> there must be an existing job that is locked. Remove
|
|
640
|
+
-- existing key to allow a new one to be inserted, and prevent any
|
|
641
|
+
-- subsequent retries by bumping attempts to the max allowed.
|
|
642
|
+
|
|
643
|
+
UPDATE
|
|
644
|
+
app_jobs.jobs
|
|
645
|
+
SET
|
|
646
|
+
KEY = NULL,
|
|
647
|
+
attempts = jobs.max_attempts
|
|
648
|
+
WHERE
|
|
649
|
+
KEY = job_key;
|
|
654
650
|
END IF;
|
|
655
|
-
END;
|
|
656
|
-
$EOFCODE$ LANGUAGE plpgsql VOLATILE SECURITY DEFINER;
|
|
657
651
|
|
|
658
|
-
|
|
652
|
+
INSERT INTO app_jobs.jobs (task_identifier, payload, queue_name, run_at, max_attempts, priority)
|
|
653
|
+
VALUES (identifier, payload, queue_name, run_at, max_attempts, priority)
|
|
654
|
+
RETURNING
|
|
655
|
+
* INTO v_job;
|
|
656
|
+
RETURN v_job;
|
|
657
|
+
END;
|
|
658
|
+
$EOFCODE$ LANGUAGE plpgsql VOLATILE SECURITY DEFINER;
|