simplebroker-pg 1.0.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.
- simplebroker_pg-1.0.0/.gitignore +87 -0
- simplebroker_pg-1.0.0/PKG-INFO +102 -0
- simplebroker_pg-1.0.0/README.md +87 -0
- simplebroker_pg-1.0.0/pyproject.toml +42 -0
- simplebroker_pg-1.0.0/simplebroker_pg/__init__.py +6 -0
- simplebroker_pg-1.0.0/simplebroker_pg/_constants.py +3 -0
- simplebroker_pg-1.0.0/simplebroker_pg/_identifiers.py +18 -0
- simplebroker_pg-1.0.0/simplebroker_pg/_sql.py +269 -0
- simplebroker_pg-1.0.0/simplebroker_pg/plugin.py +601 -0
- simplebroker_pg-1.0.0/simplebroker_pg/runner.py +238 -0
- simplebroker_pg-1.0.0/simplebroker_pg/schema.py +159 -0
- simplebroker_pg-1.0.0/simplebroker_pg/validation.py +226 -0
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# Accidental database files created by str(BrokerCore) coercion
|
|
2
|
+
<simplebroker.*>
|
|
3
|
+
|
|
4
|
+
# Python
|
|
5
|
+
__pycache__/
|
|
6
|
+
*.py[cod]
|
|
7
|
+
*$py.class
|
|
8
|
+
*.so
|
|
9
|
+
.Python
|
|
10
|
+
.code
|
|
11
|
+
.claude
|
|
12
|
+
.codex
|
|
13
|
+
docs/
|
|
14
|
+
build/
|
|
15
|
+
develop-eggs/
|
|
16
|
+
dist/
|
|
17
|
+
downloads/
|
|
18
|
+
eggs/
|
|
19
|
+
.eggs/
|
|
20
|
+
lib/
|
|
21
|
+
lib64/
|
|
22
|
+
parts/
|
|
23
|
+
sdist/
|
|
24
|
+
var/
|
|
25
|
+
wheels/
|
|
26
|
+
*.egg-info/
|
|
27
|
+
.installed.cfg
|
|
28
|
+
*.egg
|
|
29
|
+
MANIFEST
|
|
30
|
+
|
|
31
|
+
# Virtual environments
|
|
32
|
+
venv/
|
|
33
|
+
ENV/
|
|
34
|
+
env/
|
|
35
|
+
.venv
|
|
36
|
+
|
|
37
|
+
# IDEs
|
|
38
|
+
.vscode/
|
|
39
|
+
.idea/
|
|
40
|
+
*.swp
|
|
41
|
+
*.swo
|
|
42
|
+
*~
|
|
43
|
+
|
|
44
|
+
# Testing
|
|
45
|
+
.coverage
|
|
46
|
+
.coverage.*
|
|
47
|
+
coverage.xml
|
|
48
|
+
.pytest_cache/
|
|
49
|
+
htmlcov/
|
|
50
|
+
.tox/
|
|
51
|
+
.nox/
|
|
52
|
+
.mypy_cache/
|
|
53
|
+
.dmypy.json
|
|
54
|
+
dmypy.json
|
|
55
|
+
.ruff_cache/
|
|
56
|
+
.ruff/
|
|
57
|
+
.pytest_cache/
|
|
58
|
+
|
|
59
|
+
# SimpleBroker specific
|
|
60
|
+
*.db
|
|
61
|
+
*.db-shm
|
|
62
|
+
*.db-wal
|
|
63
|
+
.broker.db*
|
|
64
|
+
test.db
|
|
65
|
+
benchmark_pragma.py
|
|
66
|
+
|
|
67
|
+
# OS
|
|
68
|
+
.DS_Store
|
|
69
|
+
Thumbs.db
|
|
70
|
+
|
|
71
|
+
# Temporary files
|
|
72
|
+
*.tmp
|
|
73
|
+
*.bak
|
|
74
|
+
*.log
|
|
75
|
+
|
|
76
|
+
# Multi-agent
|
|
77
|
+
.claude
|
|
78
|
+
.mcp.json
|
|
79
|
+
agent_history/
|
|
80
|
+
.broker.db
|
|
81
|
+
.broker.db-shm
|
|
82
|
+
.broker.db-wal
|
|
83
|
+
weft/
|
|
84
|
+
.aider*
|
|
85
|
+
.codex*
|
|
86
|
+
.code*
|
|
87
|
+
.coder*
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: simplebroker-pg
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Postgres backend plugin for SimpleBroker
|
|
5
|
+
Author-email: Van Lindberg <van.lindberg@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Requires-Python: >=3.11
|
|
8
|
+
Requires-Dist: psycopg-pool<4,>=3.1
|
|
9
|
+
Requires-Dist: psycopg[binary]<4,>=3
|
|
10
|
+
Requires-Dist: simplebroker>=3.0.0
|
|
11
|
+
Provides-Extra: dev
|
|
12
|
+
Requires-Dist: pytest-timeout>=2.4.0; extra == 'dev'
|
|
13
|
+
Requires-Dist: pytest>=7.0; extra == 'dev'
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
|
|
16
|
+
# simplebroker-pg
|
|
17
|
+
|
|
18
|
+
Postgres backend plugin for SimpleBroker.
|
|
19
|
+
|
|
20
|
+
This package is intentionally separate from `simplebroker` itself. SimpleBroker
|
|
21
|
+
remains SQLite-first. This package adds a Postgres backend through the public
|
|
22
|
+
backend plugin hook.
|
|
23
|
+
|
|
24
|
+
## Requirements
|
|
25
|
+
|
|
26
|
+
- PostgreSQL
|
|
27
|
+
- A dedicated schema for SimpleBroker tables
|
|
28
|
+
|
|
29
|
+
`public` is intentionally rejected.
|
|
30
|
+
|
|
31
|
+
## Installation
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
# Add to an existing pipx-installed simplebroker (recommended)
|
|
35
|
+
pipx inject simplebroker simplebroker-pg
|
|
36
|
+
|
|
37
|
+
# Or install with uv to use as a library
|
|
38
|
+
uv add simplebroker-pg
|
|
39
|
+
|
|
40
|
+
# Or with pip
|
|
41
|
+
pip install simplebroker-pg
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Python Usage
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
from simplebroker import Queue
|
|
48
|
+
from simplebroker_pg import PostgresRunner
|
|
49
|
+
|
|
50
|
+
runner = PostgresRunner(
|
|
51
|
+
"postgresql://postgres@127.0.0.1:54329/simplebroker_test",
|
|
52
|
+
schema="simplebroker_app",
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
queue = Queue("jobs", runner=runner, persistent=True)
|
|
56
|
+
queue.write("hello")
|
|
57
|
+
print(queue.read())
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## CLI Usage
|
|
61
|
+
|
|
62
|
+
Create `.simplebroker.toml` in the project root:
|
|
63
|
+
|
|
64
|
+
```toml
|
|
65
|
+
version = 1
|
|
66
|
+
backend = "postgres"
|
|
67
|
+
target = "postgresql://postgres@127.0.0.1:54329/simplebroker_test"
|
|
68
|
+
|
|
69
|
+
[backend_options]
|
|
70
|
+
schema = "simplebroker_app"
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Then use the normal CLI from any child directory with project scope enabled:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
broker init
|
|
77
|
+
broker write jobs hello
|
|
78
|
+
broker read jobs
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
You can also run entirely from environment variables without a project config:
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
BROKER_BACKEND=postgres \
|
|
85
|
+
BROKER_BACKEND_TARGET='postgresql://postgres@127.0.0.1:54329/simplebroker_test' \
|
|
86
|
+
BROKER_BACKEND_SCHEMA='simplebroker_app' \
|
|
87
|
+
BROKER_BACKEND_PASSWORD='postgres' \
|
|
88
|
+
broker init
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Notes:
|
|
92
|
+
|
|
93
|
+
- `BROKER_BACKEND_TARGET` overrides the whole target string.
|
|
94
|
+
- `BROKER_BACKEND_HOST`, `BROKER_BACKEND_PORT`, `BROKER_BACKEND_USER`,
|
|
95
|
+
`BROKER_BACKEND_PASSWORD`, and `BROKER_BACKEND_DATABASE` are only used when there is no
|
|
96
|
+
target from env or toml.
|
|
97
|
+
- `BROKER_BACKEND_PASSWORD` is never written to `.simplebroker.toml`.
|
|
98
|
+
- The Postgres database must already exist. `broker init` creates the managed schema/tables
|
|
99
|
+
inside that database; it does not create the database itself.
|
|
100
|
+
- Missing backend/plugin errors are distinct from target/auth errors. Invalid schema names,
|
|
101
|
+
bad passwords, malformed targets, and missing databases are reported as validation or
|
|
102
|
+
connection failures, not as "backend not available" errors.
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# simplebroker-pg
|
|
2
|
+
|
|
3
|
+
Postgres backend plugin for SimpleBroker.
|
|
4
|
+
|
|
5
|
+
This package is intentionally separate from `simplebroker` itself. SimpleBroker
|
|
6
|
+
remains SQLite-first. This package adds a Postgres backend through the public
|
|
7
|
+
backend plugin hook.
|
|
8
|
+
|
|
9
|
+
## Requirements
|
|
10
|
+
|
|
11
|
+
- PostgreSQL
|
|
12
|
+
- A dedicated schema for SimpleBroker tables
|
|
13
|
+
|
|
14
|
+
`public` is intentionally rejected.
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
# Add to an existing pipx-installed simplebroker (recommended)
|
|
20
|
+
pipx inject simplebroker simplebroker-pg
|
|
21
|
+
|
|
22
|
+
# Or install with uv to use as a library
|
|
23
|
+
uv add simplebroker-pg
|
|
24
|
+
|
|
25
|
+
# Or with pip
|
|
26
|
+
pip install simplebroker-pg
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Python Usage
|
|
30
|
+
|
|
31
|
+
```python
|
|
32
|
+
from simplebroker import Queue
|
|
33
|
+
from simplebroker_pg import PostgresRunner
|
|
34
|
+
|
|
35
|
+
runner = PostgresRunner(
|
|
36
|
+
"postgresql://postgres@127.0.0.1:54329/simplebroker_test",
|
|
37
|
+
schema="simplebroker_app",
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
queue = Queue("jobs", runner=runner, persistent=True)
|
|
41
|
+
queue.write("hello")
|
|
42
|
+
print(queue.read())
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## CLI Usage
|
|
46
|
+
|
|
47
|
+
Create `.simplebroker.toml` in the project root:
|
|
48
|
+
|
|
49
|
+
```toml
|
|
50
|
+
version = 1
|
|
51
|
+
backend = "postgres"
|
|
52
|
+
target = "postgresql://postgres@127.0.0.1:54329/simplebroker_test"
|
|
53
|
+
|
|
54
|
+
[backend_options]
|
|
55
|
+
schema = "simplebroker_app"
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Then use the normal CLI from any child directory with project scope enabled:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
broker init
|
|
62
|
+
broker write jobs hello
|
|
63
|
+
broker read jobs
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
You can also run entirely from environment variables without a project config:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
BROKER_BACKEND=postgres \
|
|
70
|
+
BROKER_BACKEND_TARGET='postgresql://postgres@127.0.0.1:54329/simplebroker_test' \
|
|
71
|
+
BROKER_BACKEND_SCHEMA='simplebroker_app' \
|
|
72
|
+
BROKER_BACKEND_PASSWORD='postgres' \
|
|
73
|
+
broker init
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Notes:
|
|
77
|
+
|
|
78
|
+
- `BROKER_BACKEND_TARGET` overrides the whole target string.
|
|
79
|
+
- `BROKER_BACKEND_HOST`, `BROKER_BACKEND_PORT`, `BROKER_BACKEND_USER`,
|
|
80
|
+
`BROKER_BACKEND_PASSWORD`, and `BROKER_BACKEND_DATABASE` are only used when there is no
|
|
81
|
+
target from env or toml.
|
|
82
|
+
- `BROKER_BACKEND_PASSWORD` is never written to `.simplebroker.toml`.
|
|
83
|
+
- The Postgres database must already exist. `broker init` creates the managed schema/tables
|
|
84
|
+
inside that database; it does not create the database itself.
|
|
85
|
+
- Missing backend/plugin errors are distinct from target/auth errors. Invalid schema names,
|
|
86
|
+
bad passwords, malformed targets, and missing databases are reported as validation or
|
|
87
|
+
connection failures, not as "backend not available" errors.
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "simplebroker-pg"
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
description = "Postgres backend plugin for SimpleBroker"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.11"
|
|
11
|
+
license = {text = "MIT"}
|
|
12
|
+
authors = [
|
|
13
|
+
{name = "Van Lindberg", email = "van.lindberg@gmail.com"},
|
|
14
|
+
]
|
|
15
|
+
dependencies = [
|
|
16
|
+
"simplebroker>=3.0.0",
|
|
17
|
+
"psycopg[binary]>=3,<4",
|
|
18
|
+
"psycopg-pool>=3.1,<4",
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
[project.optional-dependencies]
|
|
22
|
+
dev = [
|
|
23
|
+
"pytest>=7.0",
|
|
24
|
+
"pytest-timeout>=2.4.0",
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
[project.entry-points."simplebroker.backends"]
|
|
28
|
+
postgres = "simplebroker_pg.plugin:get_backend_plugin"
|
|
29
|
+
|
|
30
|
+
[tool.hatch.build]
|
|
31
|
+
include = [
|
|
32
|
+
"simplebroker_pg/**/*.py",
|
|
33
|
+
"README.md",
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
[tool.pytest.ini_options]
|
|
37
|
+
minversion = "7.0"
|
|
38
|
+
testpaths = ["tests"]
|
|
39
|
+
addopts = "-ra -q"
|
|
40
|
+
markers = [
|
|
41
|
+
"pg_only: tests that validate the Postgres extension package",
|
|
42
|
+
]
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""Stable identifiers for Postgres advisory locks and notification channels."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import hashlib
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def stable_lock_key(*parts: str) -> int:
|
|
9
|
+
"""Return a stable signed 64-bit advisory lock key."""
|
|
10
|
+
payload = "\x1f".join(parts).encode("utf-8")
|
|
11
|
+
digest = hashlib.blake2b(payload, digest_size=8).digest()
|
|
12
|
+
return int.from_bytes(digest, byteorder="big", signed=True)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def activity_channel_name(schema: str) -> str:
|
|
16
|
+
"""Return a stable, identifier-safe LISTEN/NOTIFY channel name."""
|
|
17
|
+
digest = hashlib.md5(schema.encode("utf-8"), usedforsecurity=False).hexdigest()
|
|
18
|
+
return f"simplebroker_{digest[:24]}"
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
"""Postgres SQL namespace for SimpleBroker."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from simplebroker._sql import RetrieveOperation, RetrieveQuerySpec
|
|
6
|
+
|
|
7
|
+
CHECK_PENDING_MESSAGES = """
|
|
8
|
+
SELECT EXISTS(
|
|
9
|
+
SELECT 1
|
|
10
|
+
FROM messages
|
|
11
|
+
WHERE queue = ? AND claimed = FALSE
|
|
12
|
+
LIMIT 1
|
|
13
|
+
)
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
CHECK_PENDING_MESSAGES_SINCE = """
|
|
17
|
+
SELECT EXISTS(
|
|
18
|
+
SELECT 1
|
|
19
|
+
FROM messages
|
|
20
|
+
WHERE queue = ? AND claimed = FALSE AND ts > ?
|
|
21
|
+
LIMIT 1
|
|
22
|
+
)
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
CHECK_QUEUE_EXISTS = """
|
|
26
|
+
SELECT EXISTS(
|
|
27
|
+
SELECT 1
|
|
28
|
+
FROM messages
|
|
29
|
+
WHERE queue = ?
|
|
30
|
+
LIMIT 1
|
|
31
|
+
)
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
COUNT_CLAIMED_MESSAGES = "SELECT COUNT(*) FROM messages WHERE claimed = TRUE"
|
|
35
|
+
DELETE_ALIAS = "DELETE FROM aliases WHERE alias = ?"
|
|
36
|
+
DELETE_ALL_MESSAGES_COUNT = """
|
|
37
|
+
WITH deleted AS (
|
|
38
|
+
DELETE FROM messages
|
|
39
|
+
RETURNING 1
|
|
40
|
+
)
|
|
41
|
+
SELECT COUNT(*) FROM deleted
|
|
42
|
+
"""
|
|
43
|
+
DELETE_QUEUE_MESSAGES_COUNT = """
|
|
44
|
+
WITH deleted AS (
|
|
45
|
+
DELETE FROM messages
|
|
46
|
+
WHERE queue = ?
|
|
47
|
+
RETURNING 1
|
|
48
|
+
)
|
|
49
|
+
SELECT COUNT(*) FROM deleted
|
|
50
|
+
"""
|
|
51
|
+
GET_ALIAS_VERSION = "SELECT alias_version FROM meta WHERE singleton = TRUE"
|
|
52
|
+
GET_DISTINCT_QUEUES = "SELECT DISTINCT queue FROM messages ORDER BY queue"
|
|
53
|
+
GET_LAST_TS = "SELECT last_ts FROM meta WHERE singleton = TRUE"
|
|
54
|
+
GET_MAX_MESSAGE_TS = "SELECT COALESCE(MAX(ts), 0) FROM messages"
|
|
55
|
+
GET_OVERALL_STATS = """
|
|
56
|
+
SELECT
|
|
57
|
+
COALESCE(SUM(CASE WHEN claimed THEN 1 ELSE 0 END), 0),
|
|
58
|
+
COUNT(*)
|
|
59
|
+
FROM messages
|
|
60
|
+
"""
|
|
61
|
+
GET_QUEUE_STATS = """
|
|
62
|
+
SELECT
|
|
63
|
+
queue,
|
|
64
|
+
SUM(CASE WHEN NOT claimed THEN 1 ELSE 0 END) AS unclaimed_count,
|
|
65
|
+
COUNT(*) AS total_count
|
|
66
|
+
FROM messages
|
|
67
|
+
GROUP BY queue
|
|
68
|
+
ORDER BY queue
|
|
69
|
+
"""
|
|
70
|
+
GET_TOTAL_MESSAGE_COUNT = "SELECT COUNT(*) FROM messages"
|
|
71
|
+
GET_VACUUM_STATS = """
|
|
72
|
+
SELECT
|
|
73
|
+
COALESCE(SUM(CASE WHEN claimed THEN 1 ELSE 0 END), 0),
|
|
74
|
+
COUNT(*)
|
|
75
|
+
FROM messages
|
|
76
|
+
"""
|
|
77
|
+
INSERT_ALIAS = "INSERT INTO aliases (alias, target) VALUES (?, ?)"
|
|
78
|
+
INSERT_MESSAGE = """
|
|
79
|
+
WITH inserted AS (
|
|
80
|
+
INSERT INTO messages (queue, body, ts)
|
|
81
|
+
VALUES (?, ?, ?)
|
|
82
|
+
RETURNING queue
|
|
83
|
+
)
|
|
84
|
+
SELECT pg_notify(
|
|
85
|
+
'simplebroker_' || substr(md5(current_schema()), 1, 24),
|
|
86
|
+
queue
|
|
87
|
+
)
|
|
88
|
+
FROM inserted
|
|
89
|
+
"""
|
|
90
|
+
LIST_QUEUES_UNCLAIMED = """
|
|
91
|
+
SELECT queue, COUNT(*)
|
|
92
|
+
FROM messages
|
|
93
|
+
WHERE claimed = FALSE
|
|
94
|
+
GROUP BY queue
|
|
95
|
+
ORDER BY queue
|
|
96
|
+
"""
|
|
97
|
+
SELECT_ALIASES = "SELECT alias, target FROM aliases ORDER BY alias"
|
|
98
|
+
SELECT_ALIASES_FOR_TARGET = """
|
|
99
|
+
SELECT alias
|
|
100
|
+
FROM aliases
|
|
101
|
+
WHERE target = ?
|
|
102
|
+
ORDER BY alias
|
|
103
|
+
"""
|
|
104
|
+
SELECT_META_ALL = """
|
|
105
|
+
SELECT key, value
|
|
106
|
+
FROM (
|
|
107
|
+
SELECT 'alias_version'::TEXT AS key, alias_version::TEXT AS value
|
|
108
|
+
FROM meta
|
|
109
|
+
WHERE singleton = TRUE
|
|
110
|
+
UNION ALL
|
|
111
|
+
SELECT 'last_ts'::TEXT AS key, last_ts::TEXT AS value
|
|
112
|
+
FROM meta
|
|
113
|
+
WHERE singleton = TRUE
|
|
114
|
+
UNION ALL
|
|
115
|
+
SELECT 'magic'::TEXT AS key, magic AS value
|
|
116
|
+
FROM meta
|
|
117
|
+
WHERE singleton = TRUE
|
|
118
|
+
UNION ALL
|
|
119
|
+
SELECT 'schema_version'::TEXT AS key, schema_version::TEXT AS value
|
|
120
|
+
FROM meta
|
|
121
|
+
WHERE singleton = TRUE
|
|
122
|
+
) AS meta_items
|
|
123
|
+
ORDER BY key
|
|
124
|
+
"""
|
|
125
|
+
UPDATE_ALIAS_VERSION = """
|
|
126
|
+
UPDATE meta
|
|
127
|
+
SET alias_version = ?
|
|
128
|
+
WHERE singleton = TRUE
|
|
129
|
+
"""
|
|
130
|
+
UPDATE_LAST_TS = "UPDATE meta SET last_ts = ? WHERE singleton = TRUE"
|
|
131
|
+
|
|
132
|
+
DELETE_CLAIMED_BATCH_COUNT = """
|
|
133
|
+
WITH deleted AS (
|
|
134
|
+
DELETE FROM messages
|
|
135
|
+
WHERE order_id IN (
|
|
136
|
+
SELECT order_id
|
|
137
|
+
FROM messages
|
|
138
|
+
WHERE claimed = TRUE
|
|
139
|
+
ORDER BY order_id
|
|
140
|
+
LIMIT ?
|
|
141
|
+
)
|
|
142
|
+
RETURNING 1
|
|
143
|
+
)
|
|
144
|
+
SELECT COUNT(*) FROM deleted
|
|
145
|
+
"""
|
|
146
|
+
|
|
147
|
+
DATABASE_SIZE_BYTES = """
|
|
148
|
+
SELECT COALESCE(SUM(pg_total_relation_size(c.oid)), 0)
|
|
149
|
+
FROM pg_class AS c
|
|
150
|
+
JOIN pg_namespace AS n
|
|
151
|
+
ON n.oid = c.relnamespace
|
|
152
|
+
WHERE n.nspname = ?
|
|
153
|
+
AND c.relname IN ('messages', 'meta', 'aliases')
|
|
154
|
+
"""
|
|
155
|
+
|
|
156
|
+
LOCK_BROADCAST_SCOPE = "LOCK TABLE messages IN SHARE ROW EXCLUSIVE MODE"
|
|
157
|
+
|
|
158
|
+
COMPACT_TABLE_MESSAGES = "VACUUM (ANALYZE) messages"
|
|
159
|
+
COMPACT_TABLE_META = "VACUUM (ANALYZE) meta"
|
|
160
|
+
COMPACT_TABLE_ALIASES = "VACUUM (ANALYZE) aliases"
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def _build_where_clause(spec: RetrieveQuerySpec) -> tuple[list[str], list[object]]:
|
|
164
|
+
"""Build Postgres WHERE conditions and parameters for retrieve operations."""
|
|
165
|
+
if spec.exact_timestamp is not None:
|
|
166
|
+
where_conditions = ["ts = ?", "queue = ?"]
|
|
167
|
+
params: list[object] = [spec.exact_timestamp, spec.queue]
|
|
168
|
+
if spec.require_unclaimed:
|
|
169
|
+
where_conditions.append("claimed = FALSE")
|
|
170
|
+
return where_conditions, params
|
|
171
|
+
|
|
172
|
+
where_conditions = ["queue = ?"]
|
|
173
|
+
params = [spec.queue]
|
|
174
|
+
if spec.require_unclaimed:
|
|
175
|
+
where_conditions.append("claimed = FALSE")
|
|
176
|
+
if spec.since_timestamp is not None:
|
|
177
|
+
where_conditions.append("ts > ?")
|
|
178
|
+
params.append(spec.since_timestamp)
|
|
179
|
+
return where_conditions, params
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def build_retrieve_query(
|
|
183
|
+
operation: RetrieveOperation,
|
|
184
|
+
spec: RetrieveQuerySpec,
|
|
185
|
+
) -> tuple[str, tuple[object, ...]]:
|
|
186
|
+
"""Build a Postgres retrieve query and its parameter tuple."""
|
|
187
|
+
where_conditions, params = _build_where_clause(spec)
|
|
188
|
+
where_clause = " AND ".join(where_conditions)
|
|
189
|
+
|
|
190
|
+
if operation == "peek":
|
|
191
|
+
return (
|
|
192
|
+
f"""
|
|
193
|
+
SELECT body, ts
|
|
194
|
+
FROM messages
|
|
195
|
+
WHERE {where_clause}
|
|
196
|
+
ORDER BY order_id
|
|
197
|
+
LIMIT ?
|
|
198
|
+
OFFSET ?
|
|
199
|
+
""",
|
|
200
|
+
tuple(params + [spec.limit, spec.offset]),
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
if operation == "claim":
|
|
204
|
+
return (
|
|
205
|
+
f"""
|
|
206
|
+
WITH selected AS (
|
|
207
|
+
SELECT order_id
|
|
208
|
+
FROM messages
|
|
209
|
+
WHERE {where_clause}
|
|
210
|
+
ORDER BY order_id
|
|
211
|
+
LIMIT ?
|
|
212
|
+
),
|
|
213
|
+
updated AS (
|
|
214
|
+
UPDATE messages
|
|
215
|
+
SET claimed = TRUE
|
|
216
|
+
WHERE order_id IN (SELECT order_id FROM selected)
|
|
217
|
+
RETURNING order_id, body, ts
|
|
218
|
+
)
|
|
219
|
+
SELECT updated.body, updated.ts
|
|
220
|
+
FROM updated
|
|
221
|
+
JOIN selected
|
|
222
|
+
ON selected.order_id = updated.order_id
|
|
223
|
+
ORDER BY selected.order_id
|
|
224
|
+
""",
|
|
225
|
+
tuple(params + [spec.limit]),
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
if operation == "move":
|
|
229
|
+
if spec.target_queue is None:
|
|
230
|
+
raise ValueError("Move retrieve query requires target_queue")
|
|
231
|
+
return (
|
|
232
|
+
f"""
|
|
233
|
+
WITH target_queue AS (
|
|
234
|
+
SELECT ? AS queue_name
|
|
235
|
+
),
|
|
236
|
+
selected AS (
|
|
237
|
+
SELECT order_id
|
|
238
|
+
FROM messages
|
|
239
|
+
WHERE {where_clause}
|
|
240
|
+
ORDER BY order_id
|
|
241
|
+
LIMIT ?
|
|
242
|
+
),
|
|
243
|
+
updated AS (
|
|
244
|
+
UPDATE messages
|
|
245
|
+
SET queue = (SELECT queue_name FROM target_queue),
|
|
246
|
+
claimed = FALSE
|
|
247
|
+
WHERE order_id IN (SELECT order_id FROM selected)
|
|
248
|
+
RETURNING order_id, body, ts
|
|
249
|
+
),
|
|
250
|
+
notified AS (
|
|
251
|
+
SELECT pg_notify(
|
|
252
|
+
'simplebroker_' || substr(md5(current_schema()), 1, 24),
|
|
253
|
+
(SELECT queue_name FROM target_queue)
|
|
254
|
+
)
|
|
255
|
+
FROM updated
|
|
256
|
+
LIMIT 1
|
|
257
|
+
)
|
|
258
|
+
SELECT updated.body, updated.ts
|
|
259
|
+
FROM updated
|
|
260
|
+
JOIN selected
|
|
261
|
+
ON selected.order_id = updated.order_id
|
|
262
|
+
LEFT JOIN notified
|
|
263
|
+
ON TRUE
|
|
264
|
+
ORDER BY selected.order_id
|
|
265
|
+
""",
|
|
266
|
+
tuple([spec.target_queue] + params + [spec.limit]),
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
raise ValueError(f"Unsupported retrieve operation: {operation}")
|