@hot-updater/postgres 0.28.0 → 0.29.1
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/dist/index.cjs +527 -528
- package/dist/index.d.cts +4 -3
- package/dist/{index.d.ts → index.d.mts} +4 -3
- package/dist/{index.js → index.mjs} +515 -513
- package/package.json +14 -7
- package/sql/bundles.sql +7 -2
- package/sql/get_update_info.spec.ts +48 -12
- package/sql/get_update_info_by_app_version.sql +61 -25
- package/sql/get_update_info_by_fingerprint_hash.sql +61 -27
- package/sql/hash_user_id.sql +30 -0
- package/sql/is_device_eligible.sql +236 -0
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
-- HotUpdater.is_cohort_eligible
|
|
2
|
+
-- Cohort eligibility helpers matching @hot-updater/core rollout.ts
|
|
3
|
+
|
|
4
|
+
CREATE OR REPLACE FUNCTION positive_mod(
|
|
5
|
+
value INTEGER,
|
|
6
|
+
modulus INTEGER
|
|
7
|
+
)
|
|
8
|
+
RETURNS INTEGER
|
|
9
|
+
LANGUAGE plpgsql
|
|
10
|
+
IMMUTABLE
|
|
11
|
+
AS $$
|
|
12
|
+
BEGIN
|
|
13
|
+
RETURN ((value % modulus) + modulus) % modulus;
|
|
14
|
+
END;
|
|
15
|
+
$$;
|
|
16
|
+
|
|
17
|
+
CREATE OR REPLACE FUNCTION hash_rollout_value(input TEXT)
|
|
18
|
+
RETURNS INTEGER
|
|
19
|
+
LANGUAGE plpgsql
|
|
20
|
+
IMMUTABLE
|
|
21
|
+
AS $$
|
|
22
|
+
DECLARE
|
|
23
|
+
hash_value NUMERIC := 0;
|
|
24
|
+
char_code INTEGER;
|
|
25
|
+
i INTEGER;
|
|
26
|
+
BEGIN
|
|
27
|
+
FOR i IN 1..length(input) LOOP
|
|
28
|
+
char_code := ascii(substring(input from i for 1));
|
|
29
|
+
hash_value := mod((hash_value * 31) + char_code, 4294967296);
|
|
30
|
+
END LOOP;
|
|
31
|
+
|
|
32
|
+
IF hash_value >= 2147483648 THEN
|
|
33
|
+
hash_value := hash_value - 4294967296;
|
|
34
|
+
END IF;
|
|
35
|
+
|
|
36
|
+
RETURN hash_value::INTEGER;
|
|
37
|
+
END;
|
|
38
|
+
$$;
|
|
39
|
+
|
|
40
|
+
CREATE OR REPLACE FUNCTION normalize_cohort_value(cohort TEXT)
|
|
41
|
+
RETURNS TEXT
|
|
42
|
+
LANGUAGE plpgsql
|
|
43
|
+
IMMUTABLE
|
|
44
|
+
AS $$
|
|
45
|
+
DECLARE
|
|
46
|
+
normalized TEXT;
|
|
47
|
+
cohort_value INTEGER;
|
|
48
|
+
BEGIN
|
|
49
|
+
IF cohort IS NULL THEN
|
|
50
|
+
RETURN NULL;
|
|
51
|
+
END IF;
|
|
52
|
+
|
|
53
|
+
normalized := lower(btrim(cohort));
|
|
54
|
+
|
|
55
|
+
IF normalized ~ '^[0-9]+$' THEN
|
|
56
|
+
cohort_value := normalized::INTEGER;
|
|
57
|
+
IF cohort_value BETWEEN 1 AND 1000 THEN
|
|
58
|
+
RETURN cohort_value::TEXT;
|
|
59
|
+
END IF;
|
|
60
|
+
END IF;
|
|
61
|
+
|
|
62
|
+
RETURN normalized;
|
|
63
|
+
END;
|
|
64
|
+
$$;
|
|
65
|
+
|
|
66
|
+
CREATE OR REPLACE FUNCTION gcd_int(a INTEGER, b INTEGER)
|
|
67
|
+
RETURNS INTEGER
|
|
68
|
+
LANGUAGE plpgsql
|
|
69
|
+
IMMUTABLE
|
|
70
|
+
AS $$
|
|
71
|
+
DECLARE
|
|
72
|
+
x INTEGER := abs(a);
|
|
73
|
+
y INTEGER := abs(b);
|
|
74
|
+
next_value INTEGER;
|
|
75
|
+
BEGIN
|
|
76
|
+
WHILE y <> 0 LOOP
|
|
77
|
+
next_value := x % y;
|
|
78
|
+
x := y;
|
|
79
|
+
y := next_value;
|
|
80
|
+
END LOOP;
|
|
81
|
+
|
|
82
|
+
RETURN x;
|
|
83
|
+
END;
|
|
84
|
+
$$;
|
|
85
|
+
|
|
86
|
+
CREATE OR REPLACE FUNCTION get_rollout_multiplier(bundle_id UUID)
|
|
87
|
+
RETURNS INTEGER
|
|
88
|
+
LANGUAGE plpgsql
|
|
89
|
+
IMMUTABLE
|
|
90
|
+
AS $$
|
|
91
|
+
DECLARE
|
|
92
|
+
candidate INTEGER := positive_mod(
|
|
93
|
+
hash_rollout_value(bundle_id::TEXT || ':multiplier'),
|
|
94
|
+
997
|
|
95
|
+
);
|
|
96
|
+
BEGIN
|
|
97
|
+
IF candidate = 0 THEN
|
|
98
|
+
candidate := 1;
|
|
99
|
+
END IF;
|
|
100
|
+
|
|
101
|
+
WHILE gcd_int(candidate, 1000) <> 1 LOOP
|
|
102
|
+
candidate := positive_mod(candidate + 1, 1000);
|
|
103
|
+
IF candidate = 0 THEN
|
|
104
|
+
candidate := 1;
|
|
105
|
+
END IF;
|
|
106
|
+
END LOOP;
|
|
107
|
+
|
|
108
|
+
RETURN candidate;
|
|
109
|
+
END;
|
|
110
|
+
$$;
|
|
111
|
+
|
|
112
|
+
CREATE OR REPLACE FUNCTION get_rollout_offset(bundle_id UUID)
|
|
113
|
+
RETURNS INTEGER
|
|
114
|
+
LANGUAGE plpgsql
|
|
115
|
+
IMMUTABLE
|
|
116
|
+
AS $$
|
|
117
|
+
BEGIN
|
|
118
|
+
RETURN positive_mod(hash_rollout_value(bundle_id::TEXT || ':offset'), 1000);
|
|
119
|
+
END;
|
|
120
|
+
$$;
|
|
121
|
+
|
|
122
|
+
CREATE OR REPLACE FUNCTION get_modular_inverse(value INTEGER, modulus INTEGER)
|
|
123
|
+
RETURNS INTEGER
|
|
124
|
+
LANGUAGE plpgsql
|
|
125
|
+
IMMUTABLE
|
|
126
|
+
AS $$
|
|
127
|
+
DECLARE
|
|
128
|
+
candidate INTEGER;
|
|
129
|
+
BEGIN
|
|
130
|
+
FOR candidate IN 1..(modulus - 1) LOOP
|
|
131
|
+
IF positive_mod(value * candidate, modulus) = 1 THEN
|
|
132
|
+
RETURN candidate;
|
|
133
|
+
END IF;
|
|
134
|
+
END LOOP;
|
|
135
|
+
|
|
136
|
+
RAISE EXCEPTION 'No modular inverse for % mod %', value, modulus;
|
|
137
|
+
END;
|
|
138
|
+
$$;
|
|
139
|
+
|
|
140
|
+
CREATE OR REPLACE FUNCTION is_numeric_cohort(cohort TEXT)
|
|
141
|
+
RETURNS BOOLEAN
|
|
142
|
+
LANGUAGE plpgsql
|
|
143
|
+
IMMUTABLE
|
|
144
|
+
AS $$
|
|
145
|
+
DECLARE
|
|
146
|
+
normalized_cohort TEXT := normalize_cohort_value(cohort);
|
|
147
|
+
cohort_value INTEGER;
|
|
148
|
+
BEGIN
|
|
149
|
+
IF normalized_cohort IS NULL OR normalized_cohort !~ '^[0-9]+$' THEN
|
|
150
|
+
RETURN FALSE;
|
|
151
|
+
END IF;
|
|
152
|
+
|
|
153
|
+
cohort_value := normalized_cohort::INTEGER;
|
|
154
|
+
RETURN cohort_value BETWEEN 1 AND 1000;
|
|
155
|
+
END;
|
|
156
|
+
$$;
|
|
157
|
+
|
|
158
|
+
CREATE OR REPLACE FUNCTION get_numeric_cohort_rollout_position(
|
|
159
|
+
bundle_id UUID,
|
|
160
|
+
cohort TEXT
|
|
161
|
+
)
|
|
162
|
+
RETURNS INTEGER
|
|
163
|
+
LANGUAGE plpgsql
|
|
164
|
+
IMMUTABLE
|
|
165
|
+
AS $$
|
|
166
|
+
DECLARE
|
|
167
|
+
normalized_cohort TEXT := normalize_cohort_value(cohort);
|
|
168
|
+
cohort_value INTEGER;
|
|
169
|
+
multiplier INTEGER;
|
|
170
|
+
offset_value INTEGER;
|
|
171
|
+
inverse_multiplier INTEGER;
|
|
172
|
+
BEGIN
|
|
173
|
+
IF NOT is_numeric_cohort(normalized_cohort) THEN
|
|
174
|
+
RAISE EXCEPTION 'Invalid numeric cohort: %', cohort;
|
|
175
|
+
END IF;
|
|
176
|
+
|
|
177
|
+
cohort_value := normalized_cohort::INTEGER - 1;
|
|
178
|
+
multiplier := get_rollout_multiplier(bundle_id);
|
|
179
|
+
offset_value := get_rollout_offset(bundle_id);
|
|
180
|
+
inverse_multiplier := get_modular_inverse(multiplier, 1000);
|
|
181
|
+
|
|
182
|
+
RETURN positive_mod(
|
|
183
|
+
inverse_multiplier * (cohort_value - offset_value),
|
|
184
|
+
1000
|
|
185
|
+
);
|
|
186
|
+
END;
|
|
187
|
+
$$;
|
|
188
|
+
|
|
189
|
+
CREATE OR REPLACE FUNCTION is_cohort_eligible(
|
|
190
|
+
bundle_id UUID,
|
|
191
|
+
cohort TEXT,
|
|
192
|
+
rollout_cohort_count INTEGER,
|
|
193
|
+
target_cohorts TEXT[]
|
|
194
|
+
)
|
|
195
|
+
RETURNS BOOLEAN
|
|
196
|
+
LANGUAGE plpgsql
|
|
197
|
+
IMMUTABLE
|
|
198
|
+
AS $$
|
|
199
|
+
DECLARE
|
|
200
|
+
normalized_cohort TEXT := normalize_cohort_value(cohort);
|
|
201
|
+
normalized_rollout_count INTEGER := COALESCE(rollout_cohort_count, 1000);
|
|
202
|
+
normalized_target_cohorts TEXT[];
|
|
203
|
+
BEGIN
|
|
204
|
+
IF target_cohorts IS NOT NULL THEN
|
|
205
|
+
normalized_target_cohorts := ARRAY(
|
|
206
|
+
SELECT normalize_cohort_value(value)
|
|
207
|
+
FROM unnest(target_cohorts) AS value
|
|
208
|
+
);
|
|
209
|
+
END IF;
|
|
210
|
+
|
|
211
|
+
IF normalized_target_cohorts IS NOT NULL
|
|
212
|
+
AND array_length(normalized_target_cohorts, 1) > 0 THEN
|
|
213
|
+
RETURN normalized_cohort IS NOT NULL
|
|
214
|
+
AND normalized_cohort = ANY(normalized_target_cohorts);
|
|
215
|
+
END IF;
|
|
216
|
+
|
|
217
|
+
IF normalized_rollout_count <= 0 THEN
|
|
218
|
+
RETURN FALSE;
|
|
219
|
+
END IF;
|
|
220
|
+
|
|
221
|
+
IF normalized_cohort IS NULL THEN
|
|
222
|
+
RETURN normalized_rollout_count >= 1000;
|
|
223
|
+
END IF;
|
|
224
|
+
|
|
225
|
+
IF NOT is_numeric_cohort(normalized_cohort) THEN
|
|
226
|
+
RETURN FALSE;
|
|
227
|
+
END IF;
|
|
228
|
+
|
|
229
|
+
IF normalized_rollout_count >= 1000 THEN
|
|
230
|
+
RETURN TRUE;
|
|
231
|
+
END IF;
|
|
232
|
+
|
|
233
|
+
RETURN get_numeric_cohort_rollout_position(bundle_id, normalized_cohort)
|
|
234
|
+
< normalized_rollout_count;
|
|
235
|
+
END;
|
|
236
|
+
$$;
|