-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathinstall.sql
More file actions
246 lines (210 loc) · 9.66 KB
/
install.sql
File metadata and controls
246 lines (210 loc) · 9.66 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
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE;
SET statement_timeout = '120s';
SET lock_timeout = '15s';
-- =====================================================
-- EXTENSIONS
-- =====================================================
CREATE EXTENSION IF NOT EXISTS pgcrypto;
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
-- =====================================================
-- DOMAINS
-- =====================================================
CREATE FUNCTION public.is_tenant_id(id uuid) RETURNS boolean
LANGUAGE plpgsql IMMUTABLE STRICT
AS $$
DECLARE
bytes bytea;
BEGIN
-- Check if first 3 bits are 000 and that the version is correct (1000 for v8)
bytes := uuid_send(id);
RETURN (get_byte(bytes, 0) & B'11100000'::int) = 0 AND (get_byte(bytes, 6) & B'11110000'::int) = 128;
END;
$$;
CREATE DOMAIN public.tenant_id AS uuid
CONSTRAINT tenant_id_check CHECK (public.is_tenant_id(VALUE));
CREATE FUNCTION public.is_tenant_scoped_id(id uuid) RETURNS boolean
LANGUAGE plpgsql IMMUTABLE STRICT
AS $$
DECLARE
bytes bytea;
BEGIN
-- Check if first bit is 1 and that the version is correct (1000 for v8)
-- "get_bit [numbers] bits from the right within each byte; for example [...] bit
-- 15 is the most significant bit of the second byte."
bytes := uuid_send(id);
RETURN get_bit(bytes, 7) = 1 AND (get_byte(bytes, 6) & B'11110000'::int) = 128;
END;
$$;
CREATE DOMAIN public.tenant_scoped_id AS uuid
CONSTRAINT tenant_scoped_id_check CHECK (public.is_tenant_scoped_id(VALUE));
CREATE FUNCTION public.is_cross_tenant_id(id uuid) RETURNS boolean
LANGUAGE plpgsql IMMUTABLE STRICT
AS $$
DECLARE
bytes bytea;
BEGIN
-- Check if first 3 bits are 001 and that the version is correct (1000 for v8)
bytes := uuid_send(id);
RETURN (get_byte(bytes, 0) & B'11100000'::int) = B'00100000'::int
AND (get_byte(bytes, 6) & B'11110000'::int) = 128;
END;
$$;
CREATE DOMAIN public.cross_tenant_id AS uuid
CONSTRAINT cross_tenant_id_check CHECK (public.is_cross_tenant_id(VALUE));
-- =====================================================
-- FUNCTIONS
-- =====================================================
CREATE FUNCTION public.get_tenant_short_id_from_full_id(tenant_id public.tenant_id) RETURNS bit
LANGUAGE plpgsql IMMUTABLE STRICT LEAKPROOF PARALLEL SAFE
AS $$
DECLARE
trailing_bytes bytea;
BEGIN
-- Convert UUID to bytea, by getting the type's defined binary representation,
-- which happens to be the expected one (just the bytes of the id, in uuid order),
-- and take the first 4 bytes
trailing_bytes := substring(uuid_send(tenant_id) from 13 for 4);
-- trim off the leading bit, to keep the 31 LSB, by shifting in a 0 and then discarding it
RETURN (('x' || encode(trailing_bytes, 'hex'))::bit(32) << 1)::bit(31);
END;
$$;
CREATE FUNCTION public.get_tenant_short_id_from_scoped_id(tenant_scoped_id public.tenant_scoped_id) RETURNS bit(31)
LANGUAGE plpgsql IMMUTABLE STRICT LEAKPROOF PARALLEL SAFE
AS $$
DECLARE
leading_bytes bytea;
BEGIN
-- Convert UUID to bytea, by getting the type's defined binary representation,
-- which happens to be the expected one (just the bytes of the id, in uuid order),
-- and take the first 4 bytes
leading_bytes := substring(uuid_send(tenant_scoped_id) from 1 for 4);
-- trim off the leading bit, to keep the 31 LSB, by shifting in a 0 and then discarding it
RETURN (('x' || encode(leading_bytes, 'hex'))::bit(32) << 1)::bit(31);
END;
$$;
CREATE FUNCTION public.is_id_scoped_to_tenant(scoped_id public.tenant_scoped_id, tenant_id public.tenant_id) RETURNS boolean
LANGUAGE sql IMMUTABLE STRICT LEAKPROOF PARALLEL SAFE
AS $$
SELECT public.get_tenant_short_id_from_full_id(tenant_id) = public.get_tenant_short_id_from_scoped_id(scoped_id);
$$;
CREATE FUNCTION public.are_ids_in_same_tenant(id_1 public.tenant_scoped_id, id_2 public.tenant_scoped_id) RETURNS boolean
LANGUAGE plpgsql IMMUTABLE STRICT
AS $$
DECLARE
bytes_1 bytea;
bytes_2 bytea;
BEGIN
-- Convert UUIDs to bytea
bytes_1 := uuid_send(id_1);
bytes_2 := uuid_send(id_2);
-- Owned ids owned by the same tenant will have the first 32 bits matching.
RETURN substring(bytes_1 FROM 1 FOR 4) = substring(bytes_2 FROM 1 FOR 4);
END;
$$;
CREATE FUNCTION public.make_tenant_id_from_uuid(uuid uuid) RETURNS public.tenant_id
LANGUAGE plpgsql IMMUTABLE STRICT
AS $$
DECLARE
bytes bytea;
BEGIN
-- Convert UUID to bytea, by getting the type's defined binary representation,
-- which happens to be the expected one (just the bytes of the id, in uuid order)
bytes := uuid_send(uuid);
-- Force the leading bits to be 000, per tenant id rules
bytes := set_byte(bytes, 0, (get_byte(bytes, 0) & B'00011111'::int));
-- Override the old (likely v4) version + variant bits, without shifting any data down
bytes := set_byte(bytes, 6, (get_byte(bytes, 6) | B'10000000'::int)); -- force leading bit to 1
bytes := set_byte(bytes, 6, (get_byte(bytes, 6) & B'10001111'::int)); -- force subsequent 3 bits to 0
bytes := set_byte(bytes, 8, (get_byte(bytes, 8) | B'10000000'::int)); -- force leading bit to 1
bytes := set_byte(bytes, 8, (get_byte(bytes, 8) & B'10111111'::int)); -- force subsequent bit to 0
-- Convert back to UUID
RETURN encode(bytes, 'hex')::tenant_id;
END;
$$;
CREATE FUNCTION public.make_tenant_scoped_entity_id_from_tenant_short_id(tenant_short_id bit, insert_rate text, entity_type_hint bit, desired_timestamp timestamp with time zone DEFAULT CURRENT_TIMESTAMP) RETURNS public.tenant_scoped_id
LANGUAGE plpgsql STRICT
AS $$
DECLARE
ms_since_epoch_bitstring bit(42);
random_data bytea;
BEGIN
IF insert_rate != 'VERY_LOW' THEN
RAISE EXCEPTION 'Only very low insert rate is currently supported.';
END IF;
-- ms since epoch, according to desired timestamp
ms_since_epoch_bitstring := (EXTRACT(EPOCH FROM desired_timestamp) * 1000)::bigint::bit(42);
random_data = gen_random_bytes(5);
-- Convert back to UUID;
-- See https://stackoverflow.com/a/78084730
RETURN ENCODE(
substr(varbit_send(
B'1' || tenant_short_id || -- 32 bits
-- the only remaining bytes code currently defined, then the timesamp for it
B'000' || substring(ms_since_epoch_bitstring from 1 for 13) || -- 16 bits
-- but intersperse version here, per spec
B'1000' || substring(ms_since_epoch_bitstring from 14 for 12) || -- 16 bits
-- and intersperse varint here, per spec
B'10' || substring(ms_since_epoch_bitstring from 26) || -- 19 bits
entity_type_hint || -- 10 bits
-- Fill the remainder w/ 35 random bits
get_byte(random_data, 0)::bit(8) ||
get_byte(random_data, 1)::bit(8) ||
get_byte(random_data, 2)::bit(8) ||
get_byte(random_data, 3)::bit(8) ||
substring(get_byte(random_data, 4)::bit(8) from 1 for 3)),
5
),
'hex'
)::uuid;
END;
$$;
CREATE FUNCTION public.make_tenant_scoped_entity_id(owning_tenant_id public.tenant_id, insert_rate text, entity_type_hint bit, desired_timestamp timestamp with time zone DEFAULT CURRENT_TIMESTAMP) RETURNS public.tenant_scoped_id
LANGUAGE sql STRICT
AS $$
SELECT public.make_tenant_scoped_entity_id_from_tenant_short_id(
public.get_tenant_short_id_from_full_id(owning_tenant_id),
insert_rate,
entity_type_hint,
desired_timestamp
);
$$;
CREATE FUNCTION public.make_cross_tenant_entity_id(insert_rate text, entity_type_hint bit, desired_timestamp timestamp with time zone DEFAULT CURRENT_TIMESTAMP) RETURNS public.cross_tenant_id
LANGUAGE plpgsql STRICT
AS $$
DECLARE
ms_since_epoch_bitstring bit(42);
random_data bytea;
BEGIN
IF insert_rate != 'VERY_LOW' THEN
RAISE EXCEPTION 'Only very low insert rate is currently supported.';
END IF;
-- ms since epoch, according to desired timestamp
ms_since_epoch_bitstring := (EXTRACT(EPOCH FROM desired_timestamp) * 1000)::bigint::bit(42);
random_data = gen_random_bytes(8);
-- Convert back to UUID;
-- See https://stackoverflow.com/a/78084730
RETURN ENCODE(
substr(varbit_send(
B'001' || -- Leading bits for unowned entity id
B'000' || -- the only remaining bytes code currently defined
ms_since_epoch_bitstring || -- then the timesamp, which fits in perfectly before the version bits
B'1000' || -- version bits, interspersed here per spec
entity_type_hint || -- 10 bits
substring(get_byte(random_data, 0)::bit(8) from 1 for 2) || -- random pre-variant padding
B'10' || -- and intersperse variant here, per spec
-- Fill remaining 62 bits with random data
substring(get_byte(random_data, 0)::bit(8) from 3 for 6) ||
get_byte(random_data, 1)::bit(8) ||
get_byte(random_data, 2)::bit(8) ||
get_byte(random_data, 3)::bit(8) ||
get_byte(random_data, 4)::bit(8) ||
get_byte(random_data, 5)::bit(8) ||
get_byte(random_data, 6)::bit(8) ||
get_byte(random_data, 7)::bit(8)),
5
),
'hex'
)::uuid;
END;
$$;
COMMIT;