Repository navigation
Expand file tree
/
Copy pathdd.sql
More file actions
135 lines (124 loc) · 5.71 KB
/
Copy pathdd.sql
File metadata and controls
135 lines (124 loc) · 5.71 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
-- fmsg-webapi database extensions.
--
-- This file extends the fmsgd schema at:
-- https://github.com/markmnl/fmsgd/blob/main/dd.sql
--
-- fmsg-webapi assumes fmsgd is the fmsg host implementation and uses the same
-- PostgreSQL database.
--
-- This script is IDEMPOTENT: every statement is safe to re-run (CREATE ... IF
-- NOT EXISTS, ALTER TABLE ... ADD COLUMN IF NOT EXISTS, DROP CONSTRAINT IF
-- EXISTS, guarded DO blocks). Migrating an existing database is therefore
-- just re-running the whole script, e.g.:
--
-- psql -d fmsgd -v ON_ERROR_STOP=1 -f dd.sql
--
-- Keep it that way: add new objects and columns only with idempotent
-- statements.
CREATE TABLE IF NOT EXISTS fmsg_api_sub_account (
owner_addr varchar(255) NOT NULL,
agent varchar(64) NOT NULL,
sub_addr varchar(255),
grant_type text NOT NULL DEFAULT 'derived_sub_account',
display_name text,
key_id varchar(64),
key_hash bytea,
allowed_cidrs cidr[],
key_expires_at timestamptz,
max_sub_accounts int NOT NULL DEFAULT 5,
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now(),
PRIMARY KEY (owner_addr, agent),
UNIQUE (key_id),
CHECK (max_sub_accounts > 0),
CHECK (grant_type IN ('derived_sub_account', 'delegated_identity')),
CHECK (
(agent = '' AND sub_addr IS NULL AND display_name IS NULL AND key_id IS NULL AND key_hash IS NULL AND allowed_cidrs IS NULL AND key_expires_at IS NULL)
OR
(agent <> '' AND sub_addr IS NOT NULL AND key_id IS NOT NULL AND key_hash IS NOT NULL AND allowed_cidrs IS NOT NULL AND cardinality(allowed_cidrs) > 0 AND key_expires_at IS NOT NULL)
),
CHECK (agent = '' OR agent NOT LIKE '%\_%' ESCAPE '\')
);
ALTER TABLE fmsg_api_sub_account
ADD COLUMN IF NOT EXISTS grant_type text NOT NULL DEFAULT 'derived_sub_account';
ALTER TABLE fmsg_api_sub_account
ADD COLUMN IF NOT EXISTS display_name text;
ALTER TABLE fmsg_api_sub_account
DROP CONSTRAINT IF EXISTS fmsg_api_sub_account_sub_addr_key;
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1
FROM pg_constraint
WHERE conname = 'fmsg_api_sub_account_grant_type_check'
) THEN
ALTER TABLE fmsg_api_sub_account
ADD CONSTRAINT fmsg_api_sub_account_grant_type_check
CHECK (grant_type IN ('derived_sub_account', 'delegated_identity'));
END IF;
END $$;
CREATE INDEX IF NOT EXISTS fmsg_api_sub_account_owner_idx
ON fmsg_api_sub_account ((lower(owner_addr)));
CREATE INDEX IF NOT EXISTS fmsg_api_sub_account_sub_idx
ON fmsg_api_sub_account ((lower(sub_addr)));
CREATE UNIQUE INDEX IF NOT EXISTS fmsg_api_sub_account_owner_sub_unique
ON fmsg_api_sub_account ((lower(owner_addr)), (lower(sub_addr)))
WHERE agent <> '';
-- Trash: what each address has deleted. A row marks one message for one
-- address (lower-cased). thread marks the whole thread below that message;
-- time_purged means deleted for good: never shown to that address again,
-- nor anything that replies to it. Rows go with their message when it is
-- removed from the host.
CREATE TABLE IF NOT EXISTS fmsg_api_trash (
addr text NOT NULL,
msg_id bigint NOT NULL REFERENCES msg (id) ON DELETE CASCADE,
thread boolean NOT NULL DEFAULT false,
time_deleted double precision NOT NULL,
time_purged double precision,
PRIMARY KEY (addr, msg_id)
);
-- fmsg_api_trash_state is where a message stands for an address: 'purged'
-- when it or an ancestor is deleted for good, 'thread' when it or an ancestor
-- carries a thread mark, 'message' when it is deleted itself, NULL when it
-- isn't deleted. addr must be lower-cased.
CREATE OR REPLACE FUNCTION fmsg_api_trash_state(p_addr text, p_msg bigint) RETURNS text
LANGUAGE sql STABLE AS $$
WITH RECURSIVE up(id, depth) AS (
SELECT p_msg, 0
UNION ALL
SELECT m.pid, up.depth + 1 FROM msg m JOIN up ON m.id = up.id
WHERE m.pid IS NOT NULL AND up.depth < 1000
)
SELECT CASE
WHEN bool_or(t.time_purged IS NOT NULL) THEN 'purged'
WHEN bool_or(t.thread) THEN 'thread'
WHEN bool_or(up.depth = 0) THEN 'message'
END
FROM up JOIN fmsg_api_trash t ON t.addr = p_addr AND t.msg_id = up.id
$$;
-- fmsg_api_delivery_pending is whether fmsgd may still deliver some part of
-- message p_msg: a recipient (msg_to, msg_add_to) or an add-to participant
-- notification (msg_add_to_notify) that is not delivered, whose last attempt
-- (if any) got a code in p_retryable, and that at p_now is younger than
-- p_max_age seconds, measured from the message's time_sent or its add-to
-- batch's time_added as fmsgd's sender measures it. fmsgd gives up on older
-- rows without marking them, so they no longer keep a message on the host.
CREATE OR REPLACE FUNCTION fmsg_api_delivery_pending(p_msg bigint, p_now double precision, p_max_age double precision, p_retryable smallint[]) RETURNS boolean
LANGUAGE sql STABLE AS $$
SELECT EXISTS (
SELECT 1 FROM msg_to t JOIN msg m ON m.id = t.msg_id
WHERE t.msg_id = p_msg AND m.time_sent IS NOT NULL
AND t.time_delivered IS NULL
AND (t.response_code IS NULL OR t.response_code = ANY (p_retryable))
AND p_now - m.time_sent < p_max_age)
OR EXISTS (
SELECT 1 FROM msg_add_to a JOIN msg_add_to_batch b ON b.id = a.batch_id
WHERE a.msg_id = p_msg AND a.time_delivered IS NULL
AND (a.response_code IS NULL OR a.response_code = ANY (p_retryable))
AND p_now - b.time_added < p_max_age)
OR EXISTS (
SELECT 1 FROM msg_add_to_notify n JOIN msg_add_to_batch b ON b.id = n.batch_id
WHERE b.msg_id = p_msg AND n.time_notified IS NULL
AND (n.response_code IS NULL OR n.response_code = ANY (p_retryable))
AND p_now - b.time_added < p_max_age)
$$;