pgtask 0.3.0__tar.gz → 0.4.0__tar.gz
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.
- {pgtask-0.3.0 → pgtask-0.4.0}/Cargo.lock +11 -11
- {pgtask-0.3.0 → pgtask-0.4.0}/Cargo.toml +1 -1
- {pgtask-0.3.0 → pgtask-0.4.0}/PKG-INFO +1 -1
- {pgtask-0.3.0 → pgtask-0.4.0}/crates/pgtask/Cargo.toml +4 -4
- {pgtask-0.3.0 → pgtask-0.4.0}/crates/pgtask-postgres/Cargo.toml +2 -2
- pgtask-0.4.0/crates/pgtask-postgres/migrations/0010_batch_task_transitions.sql +189 -0
- pgtask-0.4.0/crates/pgtask-postgres/migrations/0011_null_idle_delays.sql +79 -0
- pgtask-0.4.0/crates/pgtask-postgres/migrations/0013_resolve_waits_of_finished_tasks.sql +81 -0
- pgtask-0.4.0/crates/pgtask-postgres/migrations/0014_retention_throughput.sql +139 -0
- pgtask-0.4.0/crates/pgtask-postgres/migrations/0015_release_tasks.sql +104 -0
- {pgtask-0.3.0 → pgtask-0.4.0}/crates/pgtask-postgres/src/lib.rs +1 -1
- {pgtask-0.3.0 → pgtask-0.4.0}/crates/pgtask-postgres/src/store.rs +285 -49
- {pgtask-0.3.0 → pgtask-0.4.0}/crates/pgtask-postgres/tests/guarantees.rs +178 -1
- pgtask-0.4.0/crates/pgtask-postgres/tests/idle_delays.rs +169 -0
- {pgtask-0.3.0 → pgtask-0.4.0}/crates/pgtask-postgres/tests/linearizability.rs +52 -10
- {pgtask-0.3.0 → pgtask-0.4.0}/crates/pgtask-postgres/tests/queue.rs +216 -3
- {pgtask-0.3.0 → pgtask-0.4.0}/crates/pgtask-postgres/tests/results.rs +70 -0
- {pgtask-0.3.0 → pgtask-0.4.0}/crates/pgtask-postgres/tests/round_trip_properties.rs +17 -1
- {pgtask-0.3.0 → pgtask-0.4.0}/crates/pgtask-postgres/tests/schedule.rs +104 -2
- {pgtask-0.3.0 → pgtask-0.4.0}/crates/pgtask-postgres/tests/signals.rs +86 -0
- {pgtask-0.3.0 → pgtask-0.4.0}/crates/pgtask-postgres/tests/sql_surface.txt +12 -0
- {pgtask-0.3.0 → pgtask-0.4.0}/crates/pgtask-worker/Cargo.toml +3 -3
- {pgtask-0.3.0 → pgtask-0.4.0}/crates/pgtask-worker/src/lib.rs +1 -0
- pgtask-0.4.0/crates/pgtask-worker/src/outcome.rs +289 -0
- {pgtask-0.3.0 → pgtask-0.4.0}/crates/pgtask-worker/src/registry.rs +35 -12
- {pgtask-0.3.0 → pgtask-0.4.0}/crates/pgtask-worker/src/runtime.rs +564 -139
- pgtask-0.4.0/crates/pgtask-worker/tests/idle.rs +123 -0
- pgtask-0.4.0/crates/pgtask-worker/tests/lifecycle.rs +245 -0
- pgtask-0.4.0/crates/pgtask-worker/tests/outcomes.rs +298 -0
- pgtask-0.4.0/crates/pgtask-worker/tests/retention.rs +159 -0
- pgtask-0.4.0/crates/pgtask-worker/tests/schedule_failures.rs +168 -0
- {pgtask-0.3.0 → pgtask-0.4.0}/crates/pgtask-worker/tests/worker.rs +178 -8
- {pgtask-0.3.0 → pgtask-0.4.0}/python/pgtask/__init__.py +3 -0
- {pgtask-0.3.0 → pgtask-0.4.0}/python/pgtask/_native.pyi +3 -0
- {pgtask-0.3.0 → pgtask-0.4.0}/python/pgtask/client.py +41 -1
- {pgtask-0.3.0 → pgtask-0.4.0}/sdks/python/Cargo.toml +1 -1
- {pgtask-0.3.0 → pgtask-0.4.0}/sdks/python/src/lib.rs +121 -8
- {pgtask-0.3.0 → pgtask-0.4.0}/sdks/python/tests/test_python_sdk.py +342 -3
- {pgtask-0.3.0 → pgtask-0.4.0}/README.md +0 -0
- {pgtask-0.3.0 → pgtask-0.4.0}/crates/pgtask/README.md +0 -0
- {pgtask-0.3.0 → pgtask-0.4.0}/crates/pgtask/src/lib.rs +0 -0
- {pgtask-0.3.0 → pgtask-0.4.0}/crates/pgtask/tests/public_api.rs +0 -0
- {pgtask-0.3.0 → pgtask-0.4.0}/crates/pgtask-core/Cargo.toml +0 -0
- {pgtask-0.3.0 → pgtask-0.4.0}/crates/pgtask-core/README.md +0 -0
- {pgtask-0.3.0 → pgtask-0.4.0}/crates/pgtask-core/src/identifier.rs +0 -0
- {pgtask-0.3.0 → pgtask-0.4.0}/crates/pgtask-core/src/lib.rs +0 -0
- {pgtask-0.3.0 → pgtask-0.4.0}/crates/pgtask-core/src/retry.rs +0 -0
- {pgtask-0.3.0 → pgtask-0.4.0}/crates/pgtask-core/src/schedule.rs +0 -0
- {pgtask-0.3.0 → pgtask-0.4.0}/crates/pgtask-core/src/task.rs +0 -0
- {pgtask-0.3.0 → pgtask-0.4.0}/crates/pgtask-core/tests/.gitignore +0 -0
- {pgtask-0.3.0 → pgtask-0.4.0}/crates/pgtask-core/tests/retry_properties.rs +0 -0
- {pgtask-0.3.0 → pgtask-0.4.0}/crates/pgtask-core/tests/schedule_clock.rs +0 -0
- {pgtask-0.3.0 → pgtask-0.4.0}/crates/pgtask-core/tests/schedule_properties.rs +0 -0
- {pgtask-0.3.0 → pgtask-0.4.0}/crates/pgtask-core/tests/types.rs +0 -0
- {pgtask-0.3.0 → pgtask-0.4.0}/crates/pgtask-otel/Cargo.toml +0 -0
- {pgtask-0.3.0 → pgtask-0.4.0}/crates/pgtask-otel/README.md +0 -0
- {pgtask-0.3.0 → pgtask-0.4.0}/crates/pgtask-otel/src/lib.rs +0 -0
- {pgtask-0.3.0 → pgtask-0.4.0}/crates/pgtask-otel/src/metrics.rs +0 -0
- {pgtask-0.3.0 → pgtask-0.4.0}/crates/pgtask-otel/src/propagation.rs +0 -0
- {pgtask-0.3.0 → pgtask-0.4.0}/crates/pgtask-otel/tests/propagation.rs +0 -0
- {pgtask-0.3.0 → pgtask-0.4.0}/crates/pgtask-postgres/README.md +0 -0
- {pgtask-0.3.0 → pgtask-0.4.0}/crates/pgtask-postgres/build.rs +0 -0
- {pgtask-0.3.0 → pgtask-0.4.0}/crates/pgtask-postgres/migrations/0001_initial.sql +0 -0
- {pgtask-0.3.0 → pgtask-0.4.0}/crates/pgtask-postgres/migrations/0003_remove_redundant_notifications.sql +0 -0
- {pgtask-0.3.0 → pgtask-0.4.0}/crates/pgtask-postgres/migrations/0004_sample_queue_demand.sql +0 -0
- {pgtask-0.3.0 → pgtask-0.4.0}/crates/pgtask-postgres/migrations/0005_serialise_result_wait_registration.sql +0 -0
- {pgtask-0.3.0 → pgtask-0.4.0}/crates/pgtask-postgres/migrations/0006_keep_resumed_tasks_claimable.sql +0 -0
- {pgtask-0.3.0 → pgtask-0.4.0}/crates/pgtask-postgres/migrations/0007_backfill_failed_attempts.sql +0 -0
- {pgtask-0.3.0 → pgtask-0.4.0}/crates/pgtask-postgres/migrations/0008_index_worker_pagination.sql +0 -0
- {pgtask-0.3.0 → pgtask-0.4.0}/crates/pgtask-postgres/tests/.gitignore +0 -0
- {pgtask-0.3.0 → pgtask-0.4.0}/crates/pgtask-postgres/tests/corruption.rs +0 -0
- {pgtask-0.3.0 → pgtask-0.4.0}/crates/pgtask-postgres/tests/model.rs +0 -0
- {pgtask-0.3.0 → pgtask-0.4.0}/crates/pgtask-postgres/tests/races.rs +0 -0
- {pgtask-0.3.0 → pgtask-0.4.0}/crates/pgtask-postgres/tests/result_wait_locking.rs +0 -0
- {pgtask-0.3.0 → pgtask-0.4.0}/crates/pgtask-postgres/tests/sql_surface.rs +0 -0
- {pgtask-0.3.0 → pgtask-0.4.0}/crates/pgtask-postgres/tests/sql_surface_baseline.txt +0 -0
- {pgtask-0.3.0 → pgtask-0.4.0}/crates/pgtask-worker/README.md +0 -0
- {pgtask-0.3.0 → pgtask-0.4.0}/crates/pgtask-worker/src/health.rs +0 -0
- {pgtask-0.3.0 → pgtask-0.4.0}/crates/pgtask-worker/tests/durable_restarts.rs +0 -0
- {pgtask-0.3.0 → pgtask-0.4.0}/pyproject.toml +0 -0
- {pgtask-0.3.0 → pgtask-0.4.0}/python/pgtask/py.typed +0 -0
- {pgtask-0.3.0 → pgtask-0.4.0}/sdks/python/README.md +0 -0
- {pgtask-0.3.0 → pgtask-0.4.0}/sdks/python/tests/__init__.py +0 -0
- {pgtask-0.3.0 → pgtask-0.4.0}/sdks/python/uv.lock +0 -0
|
@@ -1195,7 +1195,7 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
|
|
|
1195
1195
|
|
|
1196
1196
|
[[package]]
|
|
1197
1197
|
name = "pgtask"
|
|
1198
|
-
version = "0.
|
|
1198
|
+
version = "0.4.0"
|
|
1199
1199
|
dependencies = [
|
|
1200
1200
|
"pgtask-core",
|
|
1201
1201
|
"pgtask-otel",
|
|
@@ -1208,7 +1208,7 @@ dependencies = [
|
|
|
1208
1208
|
|
|
1209
1209
|
[[package]]
|
|
1210
1210
|
name = "pgtask-bench"
|
|
1211
|
-
version = "0.
|
|
1211
|
+
version = "0.4.0"
|
|
1212
1212
|
dependencies = [
|
|
1213
1213
|
"chrono",
|
|
1214
1214
|
"pgtask",
|
|
@@ -1221,7 +1221,7 @@ dependencies = [
|
|
|
1221
1221
|
|
|
1222
1222
|
[[package]]
|
|
1223
1223
|
name = "pgtask-cli"
|
|
1224
|
-
version = "0.
|
|
1224
|
+
version = "0.4.0"
|
|
1225
1225
|
dependencies = [
|
|
1226
1226
|
"clap",
|
|
1227
1227
|
"pgtask",
|
|
@@ -1231,7 +1231,7 @@ dependencies = [
|
|
|
1231
1231
|
|
|
1232
1232
|
[[package]]
|
|
1233
1233
|
name = "pgtask-core"
|
|
1234
|
-
version = "0.
|
|
1234
|
+
version = "0.4.0"
|
|
1235
1235
|
dependencies = [
|
|
1236
1236
|
"chrono",
|
|
1237
1237
|
"cron",
|
|
@@ -1245,7 +1245,7 @@ dependencies = [
|
|
|
1245
1245
|
|
|
1246
1246
|
[[package]]
|
|
1247
1247
|
name = "pgtask-otel"
|
|
1248
|
-
version = "0.
|
|
1248
|
+
version = "0.4.0"
|
|
1249
1249
|
dependencies = [
|
|
1250
1250
|
"opentelemetry",
|
|
1251
1251
|
"opentelemetry_sdk",
|
|
@@ -1256,7 +1256,7 @@ dependencies = [
|
|
|
1256
1256
|
|
|
1257
1257
|
[[package]]
|
|
1258
1258
|
name = "pgtask-postgres"
|
|
1259
|
-
version = "0.
|
|
1259
|
+
version = "0.4.0"
|
|
1260
1260
|
dependencies = [
|
|
1261
1261
|
"chrono",
|
|
1262
1262
|
"pgtask-core",
|
|
@@ -1273,7 +1273,7 @@ dependencies = [
|
|
|
1273
1273
|
|
|
1274
1274
|
[[package]]
|
|
1275
1275
|
name = "pgtask-python"
|
|
1276
|
-
version = "0.
|
|
1276
|
+
version = "0.4.0"
|
|
1277
1277
|
dependencies = [
|
|
1278
1278
|
"chrono",
|
|
1279
1279
|
"pgtask",
|
|
@@ -1290,7 +1290,7 @@ dependencies = [
|
|
|
1290
1290
|
|
|
1291
1291
|
[[package]]
|
|
1292
1292
|
name = "pgtask-web"
|
|
1293
|
-
version = "0.
|
|
1293
|
+
version = "0.4.0"
|
|
1294
1294
|
dependencies = [
|
|
1295
1295
|
"axum",
|
|
1296
1296
|
"chrono",
|
|
@@ -1309,7 +1309,7 @@ dependencies = [
|
|
|
1309
1309
|
|
|
1310
1310
|
[[package]]
|
|
1311
1311
|
name = "pgtask-worker"
|
|
1312
|
-
version = "0.
|
|
1312
|
+
version = "0.4.0"
|
|
1313
1313
|
dependencies = [
|
|
1314
1314
|
"axum",
|
|
1315
1315
|
"chrono",
|
|
@@ -1674,9 +1674,9 @@ dependencies = [
|
|
|
1674
1674
|
|
|
1675
1675
|
[[package]]
|
|
1676
1676
|
name = "rustls"
|
|
1677
|
-
version = "0.23.
|
|
1677
|
+
version = "0.23.45"
|
|
1678
1678
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1679
|
-
checksum = "
|
|
1679
|
+
checksum = "0d41d731c7d2f962d1ccc364cec258de3c0e93b38c2fb3ba97ac74513048d634"
|
|
1680
1680
|
dependencies = [
|
|
1681
1681
|
"once_cell",
|
|
1682
1682
|
"ring",
|
|
@@ -9,10 +9,10 @@ version.workspace = true
|
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
|
|
11
11
|
[dependencies]
|
|
12
|
-
pgtask-core = { path = "../pgtask-core", version = "0.
|
|
13
|
-
pgtask-otel = { path = "../pgtask-otel", version = "0.
|
|
14
|
-
pgtask-postgres = { path = "../pgtask-postgres", version = "0.
|
|
15
|
-
pgtask-worker = { path = "../pgtask-worker", version = "0.
|
|
12
|
+
pgtask-core = { path = "../pgtask-core", version = "0.4.0" }
|
|
13
|
+
pgtask-otel = { path = "../pgtask-otel", version = "0.4.0" }
|
|
14
|
+
pgtask-postgres = { path = "../pgtask-postgres", version = "0.4.0" }
|
|
15
|
+
pgtask-worker = { path = "../pgtask-worker", version = "0.4.0" }
|
|
16
16
|
|
|
17
17
|
[dev-dependencies]
|
|
18
18
|
serde_json.workspace = true
|
|
@@ -10,8 +10,8 @@ readme = "README.md"
|
|
|
10
10
|
|
|
11
11
|
[dependencies]
|
|
12
12
|
chrono.workspace = true
|
|
13
|
-
pgtask-core = { path = "../pgtask-core", version = "0.
|
|
14
|
-
pgtask-otel = { path = "../pgtask-otel", version = "0.
|
|
13
|
+
pgtask-core = { path = "../pgtask-core", version = "0.4.0" }
|
|
14
|
+
pgtask-otel = { path = "../pgtask-otel", version = "0.4.0" }
|
|
15
15
|
serde_json.workspace = true
|
|
16
16
|
sqlx.workspace = true
|
|
17
17
|
thiserror.workspace = true
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
CREATE FUNCTION pgtask.complete_tasks(p_completions jsonb)
|
|
2
|
+
RETURNS TABLE(request_index bigint, completed boolean)
|
|
3
|
+
LANGUAGE plpgsql
|
|
4
|
+
SECURITY DEFINER
|
|
5
|
+
SET search_path = pg_catalog, pgtask
|
|
6
|
+
AS $$
|
|
7
|
+
BEGIN
|
|
8
|
+
IF p_completions IS NULL OR jsonb_typeof(p_completions) <> 'array' THEN
|
|
9
|
+
RAISE EXCEPTION 'completions must be a JSON array' USING ERRCODE = '22023';
|
|
10
|
+
END IF;
|
|
11
|
+
IF EXISTS (
|
|
12
|
+
SELECT 1
|
|
13
|
+
FROM jsonb_array_elements(p_completions) AS items(completion)
|
|
14
|
+
GROUP BY completion->>'task_id'
|
|
15
|
+
HAVING count(*) > 1
|
|
16
|
+
) THEN
|
|
17
|
+
RAISE EXCEPTION 'completions contain duplicate task IDs' USING ERRCODE = '22023';
|
|
18
|
+
END IF;
|
|
19
|
+
|
|
20
|
+
RETURN QUERY
|
|
21
|
+
WITH requested AS (
|
|
22
|
+
SELECT
|
|
23
|
+
items.ordinality - 1 AS request_index,
|
|
24
|
+
(items.completion->>'task_id')::uuid AS task_id,
|
|
25
|
+
(items.completion->>'attempt')::integer AS attempt,
|
|
26
|
+
(items.completion->>'lease_token')::uuid AS lease_token,
|
|
27
|
+
CASE
|
|
28
|
+
WHEN (items.completion->>'has_result')::boolean THEN items.completion->'result'
|
|
29
|
+
ELSE NULL
|
|
30
|
+
END AS result
|
|
31
|
+
FROM jsonb_array_elements(p_completions) WITH ORDINALITY AS items(completion, ordinality)
|
|
32
|
+
),
|
|
33
|
+
completed_tasks AS (
|
|
34
|
+
UPDATE pgtask.tasks
|
|
35
|
+
SET state = 'succeeded',
|
|
36
|
+
lease_token = NULL,
|
|
37
|
+
lease_owner = NULL,
|
|
38
|
+
lease_expires_at = NULL,
|
|
39
|
+
completed_at = statement_timestamp(),
|
|
40
|
+
updated_at = statement_timestamp(),
|
|
41
|
+
result = requested.result,
|
|
42
|
+
error = NULL
|
|
43
|
+
FROM requested
|
|
44
|
+
WHERE tasks.id = requested.task_id
|
|
45
|
+
AND tasks.state = 'running'
|
|
46
|
+
AND tasks.attempt = requested.attempt
|
|
47
|
+
AND tasks.lease_token = requested.lease_token
|
|
48
|
+
RETURNING requested.request_index, tasks.id, tasks.attempt
|
|
49
|
+
),
|
|
50
|
+
completed_attempts AS (
|
|
51
|
+
UPDATE pgtask.attempts
|
|
52
|
+
SET state = 'succeeded', finished_at = statement_timestamp(), error = NULL
|
|
53
|
+
FROM completed_tasks
|
|
54
|
+
WHERE attempts.task_id = completed_tasks.id AND attempts.attempt = completed_tasks.attempt
|
|
55
|
+
)
|
|
56
|
+
SELECT requested.request_index, completed_tasks.id IS NOT NULL
|
|
57
|
+
FROM requested
|
|
58
|
+
LEFT JOIN completed_tasks USING (request_index)
|
|
59
|
+
ORDER BY requested.request_index;
|
|
60
|
+
END;
|
|
61
|
+
$$;
|
|
62
|
+
|
|
63
|
+
CREATE FUNCTION pgtask.fail_tasks(p_failures jsonb)
|
|
64
|
+
RETURNS TABLE(request_index bigint, state text)
|
|
65
|
+
LANGUAGE plpgsql
|
|
66
|
+
SECURITY DEFINER
|
|
67
|
+
SET search_path = pg_catalog, pgtask
|
|
68
|
+
AS $$
|
|
69
|
+
BEGIN
|
|
70
|
+
IF p_failures IS NULL OR jsonb_typeof(p_failures) <> 'array' THEN
|
|
71
|
+
RAISE EXCEPTION 'failures must be a JSON array' USING ERRCODE = '22023';
|
|
72
|
+
END IF;
|
|
73
|
+
IF EXISTS (
|
|
74
|
+
SELECT 1
|
|
75
|
+
FROM jsonb_array_elements(p_failures) AS items(failure)
|
|
76
|
+
GROUP BY failure->>'task_id'
|
|
77
|
+
HAVING count(*) > 1
|
|
78
|
+
) THEN
|
|
79
|
+
RAISE EXCEPTION 'failures contain duplicate task IDs' USING ERRCODE = '22023';
|
|
80
|
+
END IF;
|
|
81
|
+
|
|
82
|
+
RETURN QUERY
|
|
83
|
+
WITH requested AS (
|
|
84
|
+
SELECT
|
|
85
|
+
items.ordinality - 1 AS request_index,
|
|
86
|
+
(items.failure->>'task_id')::uuid AS task_id,
|
|
87
|
+
(items.failure->>'attempt')::integer AS attempt,
|
|
88
|
+
(items.failure->>'lease_token')::uuid AS lease_token,
|
|
89
|
+
items.failure->'error' AS error,
|
|
90
|
+
(items.failure->>'retry_milliseconds')::bigint AS retry_milliseconds
|
|
91
|
+
FROM jsonb_array_elements(p_failures) WITH ORDINALITY AS items(failure, ordinality)
|
|
92
|
+
),
|
|
93
|
+
failed_tasks AS (
|
|
94
|
+
UPDATE pgtask.tasks
|
|
95
|
+
SET state = CASE
|
|
96
|
+
WHEN requested.retry_milliseconds IS NOT NULL
|
|
97
|
+
AND COALESCE(tasks.failed_attempts, (
|
|
98
|
+
SELECT count(*)::integer FROM pgtask.attempts AS history
|
|
99
|
+
WHERE history.task_id = tasks.id AND history.state IN ('failed', 'lost')
|
|
100
|
+
)) + 1 < tasks.max_attempts THEN 'pending'
|
|
101
|
+
ELSE 'failed'
|
|
102
|
+
END,
|
|
103
|
+
run_at = CASE
|
|
104
|
+
WHEN requested.retry_milliseconds IS NOT NULL
|
|
105
|
+
THEN statement_timestamp() + (requested.retry_milliseconds * interval '1 millisecond')
|
|
106
|
+
ELSE tasks.run_at
|
|
107
|
+
END,
|
|
108
|
+
lease_token = NULL,
|
|
109
|
+
lease_owner = NULL,
|
|
110
|
+
lease_expires_at = NULL,
|
|
111
|
+
completed_at = CASE
|
|
112
|
+
WHEN requested.retry_milliseconds IS NOT NULL
|
|
113
|
+
AND COALESCE(tasks.failed_attempts, (
|
|
114
|
+
SELECT count(*)::integer FROM pgtask.attempts AS history
|
|
115
|
+
WHERE history.task_id = tasks.id AND history.state IN ('failed', 'lost')
|
|
116
|
+
)) + 1 < tasks.max_attempts THEN NULL
|
|
117
|
+
ELSE statement_timestamp()
|
|
118
|
+
END,
|
|
119
|
+
updated_at = statement_timestamp(),
|
|
120
|
+
error = requested.error,
|
|
121
|
+
failed_attempts = COALESCE(tasks.failed_attempts, (
|
|
122
|
+
SELECT count(*)::integer FROM pgtask.attempts AS history
|
|
123
|
+
WHERE history.task_id = tasks.id AND history.state IN ('failed', 'lost')
|
|
124
|
+
)) + 1
|
|
125
|
+
FROM requested
|
|
126
|
+
WHERE tasks.id = requested.task_id
|
|
127
|
+
AND tasks.state = 'running'
|
|
128
|
+
AND tasks.attempt = requested.attempt
|
|
129
|
+
AND tasks.lease_token = requested.lease_token
|
|
130
|
+
RETURNING requested.request_index, tasks.id, tasks.attempt, tasks.state, requested.error
|
|
131
|
+
),
|
|
132
|
+
failed_attempts AS (
|
|
133
|
+
UPDATE pgtask.attempts
|
|
134
|
+
SET state = 'failed', finished_at = statement_timestamp(), error = failed_tasks.error
|
|
135
|
+
FROM failed_tasks
|
|
136
|
+
WHERE attempts.task_id = failed_tasks.id AND attempts.attempt = failed_tasks.attempt
|
|
137
|
+
)
|
|
138
|
+
SELECT requested.request_index, failed_tasks.state
|
|
139
|
+
FROM requested
|
|
140
|
+
LEFT JOIN failed_tasks USING (request_index)
|
|
141
|
+
ORDER BY requested.request_index;
|
|
142
|
+
END;
|
|
143
|
+
$$;
|
|
144
|
+
|
|
145
|
+
REVOKE ALL ON FUNCTION pgtask.complete_tasks(jsonb) FROM PUBLIC;
|
|
146
|
+
REVOKE ALL ON FUNCTION pgtask.fail_tasks(jsonb) FROM PUBLIC;
|
|
147
|
+
|
|
148
|
+
DO $$
|
|
149
|
+
DECLARE
|
|
150
|
+
definition text;
|
|
151
|
+
rewritten text;
|
|
152
|
+
target regrole;
|
|
153
|
+
BEGIN
|
|
154
|
+
FOR target IN
|
|
155
|
+
SELECT privileges.grantee::regrole
|
|
156
|
+
FROM pg_proc
|
|
157
|
+
CROSS JOIN LATERAL aclexplode(pg_proc.proacl) AS privileges
|
|
158
|
+
WHERE pg_proc.oid = 'pgtask.complete_task(uuid, integer, uuid, jsonb)'::regprocedure
|
|
159
|
+
AND privileges.privilege_type = 'EXECUTE'
|
|
160
|
+
LOOP
|
|
161
|
+
EXECUTE format('GRANT EXECUTE ON FUNCTION pgtask.complete_tasks(jsonb) TO %s', target);
|
|
162
|
+
END LOOP;
|
|
163
|
+
FOR target IN
|
|
164
|
+
SELECT privileges.grantee::regrole
|
|
165
|
+
FROM pg_proc
|
|
166
|
+
CROSS JOIN LATERAL aclexplode(pg_proc.proacl) AS privileges
|
|
167
|
+
WHERE pg_proc.oid = 'pgtask.fail_task(uuid, integer, uuid, jsonb, bigint)'::regprocedure
|
|
168
|
+
AND privileges.privilege_type = 'EXECUTE'
|
|
169
|
+
LOOP
|
|
170
|
+
EXECUTE format('GRANT EXECUTE ON FUNCTION pgtask.fail_tasks(jsonb) TO %s', target);
|
|
171
|
+
END LOOP;
|
|
172
|
+
|
|
173
|
+
definition := pg_get_functiondef('pgtask.configure_grants(regrole, regrole, regrole, regrole, regrole)'::regprocedure);
|
|
174
|
+
rewritten := replace(
|
|
175
|
+
definition,
|
|
176
|
+
E' EXECUTE format(''GRANT EXECUTE ON FUNCTION pgtask.complete_task(uuid, integer, uuid, jsonb) TO %s'', p_worker);\n',
|
|
177
|
+
E' EXECUTE format(''GRANT EXECUTE ON FUNCTION pgtask.complete_task(uuid, integer, uuid, jsonb) TO %s'', p_worker);\n EXECUTE format(''GRANT EXECUTE ON FUNCTION pgtask.complete_tasks(jsonb) TO %s'', p_worker);\n'
|
|
178
|
+
);
|
|
179
|
+
rewritten := replace(
|
|
180
|
+
rewritten,
|
|
181
|
+
E' EXECUTE format(''GRANT EXECUTE ON FUNCTION pgtask.fail_task(uuid, integer, uuid, jsonb, bigint) TO %s'', p_worker);\n',
|
|
182
|
+
E' EXECUTE format(''GRANT EXECUTE ON FUNCTION pgtask.fail_task(uuid, integer, uuid, jsonb, bigint) TO %s'', p_worker);\n EXECUTE format(''GRANT EXECUTE ON FUNCTION pgtask.fail_tasks(jsonb) TO %s'', p_worker);\n'
|
|
183
|
+
);
|
|
184
|
+
IF rewritten = definition THEN
|
|
185
|
+
RAISE EXCEPTION 'could not extend pgtask.configure_grants';
|
|
186
|
+
END IF;
|
|
187
|
+
EXECUTE rewritten;
|
|
188
|
+
END;
|
|
189
|
+
$$;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
-- The next_*_delay_milliseconds functions report how long a worker may sleep before the next
|
|
2
|
+
-- deadline, and NULL when there is no deadline at all. Wrapping min() in GREATEST(0, ...) broke the
|
|
3
|
+
-- NULL case: GREATEST ignores NULL arguments, so an empty set returned 0 and idle workers polled in
|
|
4
|
+
-- a tight loop. Filtering the aggregate with HAVING keeps the 0 floor for overdue deadlines and
|
|
5
|
+
-- returns no row, which a scalar SQL function reports as NULL, when nothing is due.
|
|
6
|
+
--
|
|
7
|
+
-- CREATE OR REPLACE keeps each function's owner and grants.
|
|
8
|
+
|
|
9
|
+
CREATE OR REPLACE FUNCTION pgtask.next_schedule_delay_milliseconds()
|
|
10
|
+
RETURNS bigint
|
|
11
|
+
LANGUAGE sql
|
|
12
|
+
STABLE
|
|
13
|
+
SECURITY DEFINER
|
|
14
|
+
SET search_path = pg_catalog, pgtask
|
|
15
|
+
AS $$
|
|
16
|
+
SELECT GREATEST(
|
|
17
|
+
0,
|
|
18
|
+
ceil(EXTRACT(epoch FROM (min(next_run_at) - statement_timestamp())) * 1000)::bigint
|
|
19
|
+
)
|
|
20
|
+
FROM pgtask.schedules
|
|
21
|
+
WHERE paused_at IS NULL
|
|
22
|
+
HAVING min(next_run_at) IS NOT NULL;
|
|
23
|
+
$$;
|
|
24
|
+
|
|
25
|
+
CREATE OR REPLACE FUNCTION pgtask.next_wait_delay_milliseconds()
|
|
26
|
+
RETURNS bigint
|
|
27
|
+
LANGUAGE sql
|
|
28
|
+
STABLE
|
|
29
|
+
SECURITY DEFINER
|
|
30
|
+
SET search_path = pg_catalog, pgtask
|
|
31
|
+
AS $$
|
|
32
|
+
SELECT GREATEST(
|
|
33
|
+
0,
|
|
34
|
+
ceil(EXTRACT(epoch FROM (min(deadlines.timeout_at) - statement_timestamp())) * 1000)::bigint
|
|
35
|
+
)
|
|
36
|
+
FROM (
|
|
37
|
+
SELECT waits.timeout_at
|
|
38
|
+
FROM pgtask.waits
|
|
39
|
+
WHERE waits.resolved_at IS NULL AND waits.timeout_at IS NOT NULL
|
|
40
|
+
UNION ALL
|
|
41
|
+
SELECT result_waits.timeout_at
|
|
42
|
+
FROM pgtask.result_waits
|
|
43
|
+
WHERE result_waits.resolved_at IS NULL AND result_waits.timeout_at IS NOT NULL
|
|
44
|
+
) AS deadlines
|
|
45
|
+
HAVING min(deadlines.timeout_at) IS NOT NULL;
|
|
46
|
+
$$;
|
|
47
|
+
|
|
48
|
+
CREATE OR REPLACE FUNCTION pgtask.next_task_delay_milliseconds(
|
|
49
|
+
p_queue_name text,
|
|
50
|
+
p_task_names text[],
|
|
51
|
+
p_handler_versions integer[]
|
|
52
|
+
)
|
|
53
|
+
RETURNS bigint
|
|
54
|
+
LANGUAGE sql
|
|
55
|
+
STABLE
|
|
56
|
+
SECURITY DEFINER
|
|
57
|
+
SET search_path = pg_catalog, pgtask
|
|
58
|
+
AS $$
|
|
59
|
+
SELECT GREATEST(
|
|
60
|
+
0,
|
|
61
|
+
ceil(EXTRACT(epoch FROM (min(tasks.run_at) - statement_timestamp())) * 1000)::bigint
|
|
62
|
+
)
|
|
63
|
+
FROM pgtask.tasks
|
|
64
|
+
WHERE tasks.queue_name = p_queue_name
|
|
65
|
+
AND tasks.state = 'pending'
|
|
66
|
+
AND COALESCE(tasks.failed_attempts, (SELECT count(*)::integer FROM pgtask.attempts AS history WHERE history.task_id = tasks.id AND history.state IN ('failed', 'lost'))) < tasks.max_attempts
|
|
67
|
+
AND EXISTS (
|
|
68
|
+
SELECT 1
|
|
69
|
+
FROM pgtask.queues
|
|
70
|
+
WHERE queues.name = tasks.queue_name AND queues.paused_at IS NULL
|
|
71
|
+
)
|
|
72
|
+
AND EXISTS (
|
|
73
|
+
SELECT 1
|
|
74
|
+
FROM unnest(p_task_names, p_handler_versions) AS handlers(task_name, handler_version)
|
|
75
|
+
WHERE handlers.task_name = tasks.task_name
|
|
76
|
+
AND handlers.handler_version = tasks.handler_version
|
|
77
|
+
)
|
|
78
|
+
HAVING min(tasks.run_at) IS NOT NULL;
|
|
79
|
+
$$;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
-- A task that finishes while it waits for a signal (cancellation is the only way) used to keep its
|
|
2
|
+
-- pgtask.waits row unresolved. When an administrator retried it, the handler replayed to the same
|
|
3
|
+
-- wait_for_signal step and inserted the same key again, which failed on waits_pkey every attempt.
|
|
4
|
+
--
|
|
5
|
+
-- 1. Finishing a task resolves its open signal waits with outcome 'cancelled', in the trigger that
|
|
6
|
+
-- already does this for its result waits, and existing leftovers are resolved the same way.
|
|
7
|
+
-- 2. wait_for_signal and wait_for_result re-arm a wait row left over from an earlier attempt. They
|
|
8
|
+
-- hold the task row lock and have just found no checkpoint for the step, so any row under that
|
|
9
|
+
-- key is from an attempt that ended without the wait completing; a completed wait always writes
|
|
10
|
+
-- its checkpoint in the same transaction that resolves the row.
|
|
11
|
+
--
|
|
12
|
+
-- The functions are rewritten from their installed definitions, so earlier rewrites are kept.
|
|
13
|
+
|
|
14
|
+
ALTER TABLE pgtask.waits DROP CONSTRAINT waits_outcome_check;
|
|
15
|
+
ALTER TABLE pgtask.waits
|
|
16
|
+
ADD CONSTRAINT waits_outcome_check CHECK (outcome IN ('signal', 'timeout', 'cancelled'));
|
|
17
|
+
|
|
18
|
+
UPDATE pgtask.waits
|
|
19
|
+
SET resolved_at = statement_timestamp(), outcome = 'cancelled'
|
|
20
|
+
FROM pgtask.tasks
|
|
21
|
+
WHERE waits.task_id = tasks.id
|
|
22
|
+
AND waits.resolved_at IS NULL
|
|
23
|
+
AND tasks.state IN ('succeeded', 'failed', 'cancelled');
|
|
24
|
+
|
|
25
|
+
DO $$
|
|
26
|
+
DECLARE
|
|
27
|
+
definition text;
|
|
28
|
+
rewritten text;
|
|
29
|
+
BEGIN
|
|
30
|
+
definition := pg_get_functiondef('pgtask.resolve_task_result()'::regprocedure);
|
|
31
|
+
rewritten := replace(
|
|
32
|
+
definition,
|
|
33
|
+
E' UPDATE pgtask.result_waits\n SET resolved_at = statement_timestamp(), outcome = ''cancelled''\n WHERE task_id = NEW.id AND resolved_at IS NULL;\n',
|
|
34
|
+
E' UPDATE pgtask.result_waits\n SET resolved_at = statement_timestamp(), outcome = ''cancelled''\n WHERE task_id = NEW.id AND resolved_at IS NULL;\n\n'
|
|
35
|
+
|| E' UPDATE pgtask.waits\n SET resolved_at = statement_timestamp(), outcome = ''cancelled''\n WHERE task_id = NEW.id AND resolved_at IS NULL;\n'
|
|
36
|
+
);
|
|
37
|
+
IF rewritten = definition THEN
|
|
38
|
+
RAISE EXCEPTION 'could not extend pgtask.resolve_task_result';
|
|
39
|
+
END IF;
|
|
40
|
+
EXECUTE rewritten;
|
|
41
|
+
|
|
42
|
+
definition := pg_get_functiondef(
|
|
43
|
+
'pgtask.wait_for_signal(uuid, integer, uuid, text, integer, text, integer, bigint)'::regprocedure
|
|
44
|
+
);
|
|
45
|
+
rewritten := replace(
|
|
46
|
+
definition,
|
|
47
|
+
E' p_task_id, target_handler_version, p_step_name, p_occurrence, p_signal_name, p_signal_occurrence, timeout_at\n );\n',
|
|
48
|
+
E' p_task_id, target_handler_version, p_step_name, p_occurrence, p_signal_name, p_signal_occurrence, timeout_at\n )\n'
|
|
49
|
+
|| E' ON CONFLICT (task_id, handler_version, step_name, occurrence) DO UPDATE\n'
|
|
50
|
+
|| E' SET signal_name = EXCLUDED.signal_name,\n'
|
|
51
|
+
|| E' signal_occurrence = EXCLUDED.signal_occurrence,\n'
|
|
52
|
+
|| E' timeout_at = EXCLUDED.timeout_at,\n'
|
|
53
|
+
|| E' created_at = EXCLUDED.created_at,\n'
|
|
54
|
+
|| E' resolved_at = NULL,\n'
|
|
55
|
+
|| E' outcome = NULL;\n'
|
|
56
|
+
);
|
|
57
|
+
IF rewritten = definition THEN
|
|
58
|
+
RAISE EXCEPTION 'could not re-arm stale waits in pgtask.wait_for_signal';
|
|
59
|
+
END IF;
|
|
60
|
+
EXECUTE rewritten;
|
|
61
|
+
|
|
62
|
+
definition := pg_get_functiondef(
|
|
63
|
+
'pgtask.wait_for_result(uuid, integer, uuid, text, integer, uuid, bigint)'::regprocedure
|
|
64
|
+
);
|
|
65
|
+
rewritten := replace(
|
|
66
|
+
definition,
|
|
67
|
+
E' ELSE statement_timestamp() + (p_timeout_milliseconds * interval ''1 millisecond'')\n END\n );\n',
|
|
68
|
+
E' ELSE statement_timestamp() + (p_timeout_milliseconds * interval ''1 millisecond'')\n END\n )\n'
|
|
69
|
+
|| E' ON CONFLICT (task_id, handler_version, step_name, occurrence) DO UPDATE\n'
|
|
70
|
+
|| E' SET result_task_id = EXCLUDED.result_task_id,\n'
|
|
71
|
+
|| E' timeout_at = EXCLUDED.timeout_at,\n'
|
|
72
|
+
|| E' created_at = EXCLUDED.created_at,\n'
|
|
73
|
+
|| E' resolved_at = NULL,\n'
|
|
74
|
+
|| E' outcome = NULL;\n'
|
|
75
|
+
);
|
|
76
|
+
IF rewritten = definition THEN
|
|
77
|
+
RAISE EXCEPTION 'could not re-arm stale waits in pgtask.wait_for_result';
|
|
78
|
+
END IF;
|
|
79
|
+
EXECUTE rewritten;
|
|
80
|
+
END;
|
|
81
|
+
$$;
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
-- Retention read every terminal task in a queue and sorted the expired ones to delete a batch of
|
|
2
|
+
-- 100, and every deleted task cascaded into result_waits with a sequential scan, because the only
|
|
3
|
+
-- index on result_waits.result_task_id is partial. Worker rows were never deleted at all.
|
|
4
|
+
--
|
|
5
|
+
-- On a large tasks table, building tasks_terminal_retention_idx blocks writes to tasks while it
|
|
6
|
+
-- runs. To avoid that, create the same index with CREATE INDEX CONCURRENTLY before upgrading;
|
|
7
|
+
-- IF NOT EXISTS then skips it here.
|
|
8
|
+
|
|
9
|
+
CREATE INDEX IF NOT EXISTS tasks_terminal_retention_idx
|
|
10
|
+
ON pgtask.tasks (queue_name, completed_at, id)
|
|
11
|
+
WHERE state IN ('succeeded', 'failed', 'cancelled');
|
|
12
|
+
|
|
13
|
+
-- The ON DELETE CASCADE from tasks looks rows up by result_task_id alone, resolved or not.
|
|
14
|
+
CREATE INDEX IF NOT EXISTS result_waits_result_task_idx
|
|
15
|
+
ON pgtask.result_waits (result_task_id);
|
|
16
|
+
|
|
17
|
+
-- Delete the locked candidates by tuple id instead of joining back on the key columns. CREATE OR
|
|
18
|
+
-- REPLACE keeps each function's owner and grants; signatures and results are unchanged.
|
|
19
|
+
|
|
20
|
+
CREATE OR REPLACE FUNCTION pgtask.delete_expired_terminal(p_queue_name text, p_limit integer)
|
|
21
|
+
RETURNS bigint
|
|
22
|
+
LANGUAGE sql
|
|
23
|
+
SECURITY DEFINER
|
|
24
|
+
SET search_path = pg_catalog, pgtask
|
|
25
|
+
AS $$
|
|
26
|
+
WITH candidates AS (
|
|
27
|
+
SELECT tasks.ctid AS tuple_id
|
|
28
|
+
FROM pgtask.tasks
|
|
29
|
+
WHERE tasks.queue_name = p_queue_name
|
|
30
|
+
AND tasks.state IN ('succeeded', 'failed', 'cancelled')
|
|
31
|
+
-- A scalar cutoff, not a join filter, so it bounds the index scan: a short final batch
|
|
32
|
+
-- stops at the first unexpired row instead of reading every terminal task in the queue.
|
|
33
|
+
AND tasks.completed_at <= (
|
|
34
|
+
SELECT statement_timestamp() - (queues.terminal_retention_seconds * interval '1 second')
|
|
35
|
+
FROM pgtask.queues
|
|
36
|
+
WHERE queues.name = p_queue_name
|
|
37
|
+
)
|
|
38
|
+
AND NOT EXISTS (
|
|
39
|
+
SELECT 1 FROM pgtask.tasks AS children WHERE children.parent_task_id = tasks.id
|
|
40
|
+
)
|
|
41
|
+
ORDER BY tasks.completed_at, tasks.id
|
|
42
|
+
FOR UPDATE OF tasks SKIP LOCKED
|
|
43
|
+
LIMIT p_limit
|
|
44
|
+
),
|
|
45
|
+
deleted AS (
|
|
46
|
+
DELETE FROM pgtask.tasks
|
|
47
|
+
WHERE tasks.ctid = ANY (ARRAY(SELECT candidates.tuple_id FROM candidates))
|
|
48
|
+
RETURNING tasks.id
|
|
49
|
+
)
|
|
50
|
+
SELECT count(*) FROM deleted;
|
|
51
|
+
$$;
|
|
52
|
+
|
|
53
|
+
CREATE OR REPLACE FUNCTION pgtask.delete_expired_idempotency_keys(p_queue_name text, p_limit integer)
|
|
54
|
+
RETURNS bigint
|
|
55
|
+
LANGUAGE sql
|
|
56
|
+
SECURITY DEFINER
|
|
57
|
+
SET search_path = pg_catalog, pgtask
|
|
58
|
+
AS $$
|
|
59
|
+
WITH candidates AS (
|
|
60
|
+
SELECT idempotency_keys.ctid AS tuple_id
|
|
61
|
+
FROM pgtask.idempotency_keys
|
|
62
|
+
WHERE idempotency_keys.queue_name = p_queue_name
|
|
63
|
+
AND idempotency_keys.expires_at <= statement_timestamp()
|
|
64
|
+
ORDER BY idempotency_keys.expires_at, idempotency_keys.idempotency_key
|
|
65
|
+
FOR UPDATE SKIP LOCKED
|
|
66
|
+
LIMIT p_limit
|
|
67
|
+
),
|
|
68
|
+
deleted AS (
|
|
69
|
+
DELETE FROM pgtask.idempotency_keys
|
|
70
|
+
WHERE idempotency_keys.ctid = ANY (ARRAY(SELECT candidates.tuple_id FROM candidates))
|
|
71
|
+
RETURNING idempotency_keys.idempotency_key
|
|
72
|
+
)
|
|
73
|
+
SELECT count(*) FROM deleted;
|
|
74
|
+
$$;
|
|
75
|
+
|
|
76
|
+
-- A worker row is inserted per process start and only ever expired. Delete rows that expired more
|
|
77
|
+
-- than p_grace_milliseconds ago; worker_capabilities follows by cascade.
|
|
78
|
+
CREATE FUNCTION pgtask.delete_expired_workers(p_grace_milliseconds bigint, p_limit integer)
|
|
79
|
+
RETURNS bigint
|
|
80
|
+
LANGUAGE sql
|
|
81
|
+
SECURITY DEFINER
|
|
82
|
+
SET search_path = pg_catalog, pgtask
|
|
83
|
+
AS $$
|
|
84
|
+
WITH candidates AS (
|
|
85
|
+
SELECT workers.ctid AS tuple_id
|
|
86
|
+
FROM pgtask.workers
|
|
87
|
+
WHERE workers.expires_at <= statement_timestamp() - (p_grace_milliseconds * interval '1 millisecond')
|
|
88
|
+
ORDER BY workers.expires_at, workers.id
|
|
89
|
+
FOR UPDATE SKIP LOCKED
|
|
90
|
+
LIMIT p_limit
|
|
91
|
+
),
|
|
92
|
+
deleted AS (
|
|
93
|
+
DELETE FROM pgtask.workers
|
|
94
|
+
WHERE workers.ctid = ANY (ARRAY(SELECT candidates.tuple_id FROM candidates))
|
|
95
|
+
RETURNING workers.id
|
|
96
|
+
)
|
|
97
|
+
SELECT count(*) FROM deleted;
|
|
98
|
+
$$;
|
|
99
|
+
|
|
100
|
+
REVOKE ALL ON FUNCTION pgtask.delete_expired_workers(bigint, integer) FROM PUBLIC;
|
|
101
|
+
|
|
102
|
+
-- Grant it to every role that may already run retention, and teach configure_grants to do the same.
|
|
103
|
+
DO $$
|
|
104
|
+
DECLARE
|
|
105
|
+
definition text;
|
|
106
|
+
rewritten text;
|
|
107
|
+
target regrole;
|
|
108
|
+
BEGIN
|
|
109
|
+
FOR target IN
|
|
110
|
+
SELECT privileges.grantee::regrole
|
|
111
|
+
FROM pg_proc
|
|
112
|
+
CROSS JOIN LATERAL aclexplode(pg_proc.proacl) AS privileges
|
|
113
|
+
WHERE pg_proc.oid = 'pgtask.delete_expired_terminal(text, integer)'::regprocedure
|
|
114
|
+
AND privileges.privilege_type = 'EXECUTE'
|
|
115
|
+
LOOP
|
|
116
|
+
EXECUTE format('GRANT EXECUTE ON FUNCTION pgtask.delete_expired_workers(bigint, integer) TO %s', target);
|
|
117
|
+
END LOOP;
|
|
118
|
+
|
|
119
|
+
definition := pg_get_functiondef('pgtask.configure_grants(regrole, regrole, regrole, regrole, regrole)'::regprocedure);
|
|
120
|
+
rewritten := replace(
|
|
121
|
+
definition,
|
|
122
|
+
E' EXECUTE format(''GRANT EXECUTE ON FUNCTION pgtask.delete_expired_terminal(text, integer) TO %s'', p_worker);\n',
|
|
123
|
+
E' EXECUTE format(''GRANT EXECUTE ON FUNCTION pgtask.delete_expired_terminal(text, integer) TO %s'', p_worker);\n'
|
|
124
|
+
|| E' EXECUTE format(''GRANT EXECUTE ON FUNCTION pgtask.delete_expired_workers(bigint, integer) TO %s'', p_worker);\n'
|
|
125
|
+
);
|
|
126
|
+
rewritten := replace(
|
|
127
|
+
rewritten,
|
|
128
|
+
E' EXECUTE format(''GRANT EXECUTE ON FUNCTION pgtask.delete_expired_terminal(text, integer) TO %s'', p_administrator);\n',
|
|
129
|
+
E' EXECUTE format(''GRANT EXECUTE ON FUNCTION pgtask.delete_expired_terminal(text, integer) TO %s'', p_administrator);\n'
|
|
130
|
+
|| E' EXECUTE format(''GRANT EXECUTE ON FUNCTION pgtask.delete_expired_workers(bigint, integer) TO %s'', p_administrator);\n'
|
|
131
|
+
);
|
|
132
|
+
IF rewritten NOT LIKE '%delete_expired_workers(bigint, integer) TO %%s'', p_worker)%'
|
|
133
|
+
OR rewritten NOT LIKE '%delete_expired_workers(bigint, integer) TO %%s'', p_administrator)%'
|
|
134
|
+
THEN
|
|
135
|
+
RAISE EXCEPTION 'could not extend pgtask.configure_grants';
|
|
136
|
+
END IF;
|
|
137
|
+
EXECUTE rewritten;
|
|
138
|
+
END;
|
|
139
|
+
$$;
|