Skip to content

Commit 42c6d7e

Browse files
committed
test(postgresql): new multi-db tests
1 parent 13dce37 commit 42c6d7e

12 files changed

+2315
-8
lines changed

test/postgresql/01_unittest.sql

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
-- 'Unittest'
2+
3+
\echo '\nRunning unittest ...'
14
\connect postgres
25
\ir helper_psql_conn_setup.sql
36

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
-- '2 db roundtrip test'
2+
3+
\echo '\nRunning two-db roundtrip test ...'
14
DROP DATABASE IF EXISTS cloudsync_test_2;
25
CREATE DATABASE cloudsync_test_2;
36
\connect cloudsync_test_2

test/postgresql/03_3db_multiple_roundtrip.sql renamed to test/postgresql/03_multiple_roundtrip.sql

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
-- 'Test multi-db roundtrip with concurrent updates'
2+
3+
\echo '\nRunning multi-db roundtrip with concurrent updates ...'
24
\connect postgres
35
\ir helper_psql_conn_setup.sql
46
DROP DATABASE IF EXISTS cloudsync_test_a;
File renamed without changes.
Lines changed: 315 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,315 @@
1+
-- 'Test multi-db roundtrip with skewed col_version updates'
2+
-- - concurrent update pattern where A/B/C perform 2/1/3 updates respectively on id1 before syncing.
3+
-- - It follows the same apply order as the existing 3‑DB test and verifies final convergence across all three databases
4+
5+
\echo '\nRunning multi-db roundtrip with skewed col_version updates ...'
6+
\connect postgres
7+
\ir helper_psql_conn_setup.sql
8+
DROP DATABASE IF EXISTS cloudsync_test_a;
9+
DROP DATABASE IF EXISTS cloudsync_test_b;
10+
DROP DATABASE IF EXISTS cloudsync_test_c;
11+
CREATE DATABASE cloudsync_test_a;
12+
CREATE DATABASE cloudsync_test_b;
13+
CREATE DATABASE cloudsync_test_c;
14+
15+
\connect cloudsync_test_a
16+
\ir helper_psql_conn_setup.sql
17+
CREATE EXTENSION IF NOT EXISTS cloudsync;
18+
DROP TABLE IF EXISTS smoke_tbl;
19+
CREATE TABLE smoke_tbl (id TEXT PRIMARY KEY, val TEXT);
20+
SELECT cloudsync_init('smoke_tbl', 'CLS', true) AS _init_site_id_a \gset
21+
22+
\connect cloudsync_test_b
23+
\ir helper_psql_conn_setup.sql
24+
CREATE EXTENSION IF NOT EXISTS cloudsync;
25+
DROP TABLE IF EXISTS smoke_tbl;
26+
CREATE TABLE smoke_tbl (id TEXT PRIMARY KEY, val TEXT);
27+
SELECT cloudsync_init('smoke_tbl', 'CLS', true) AS _init_site_id_b \gset
28+
29+
\connect cloudsync_test_c
30+
\ir helper_psql_conn_setup.sql
31+
CREATE EXTENSION IF NOT EXISTS cloudsync;
32+
DROP TABLE IF EXISTS smoke_tbl;
33+
CREATE TABLE smoke_tbl (id TEXT PRIMARY KEY, val TEXT);
34+
SELECT cloudsync_init('smoke_tbl', 'CLS', true) AS _init_site_id_c \gset
35+
36+
-- Round 1: seed id1 on a single database, then sync
37+
\connect cloudsync_test_a
38+
\if :{?DEBUG_MERGE}
39+
\echo '[INFO] cloudsync_test_a INSERT id1=seed_a1'
40+
\endif
41+
INSERT INTO smoke_tbl VALUES ('id1', 'seed_a1');
42+
SELECT CASE WHEN payload IS NULL OR octet_length(payload) = 0
43+
THEN ''
44+
ELSE '\x' || encode(payload, 'hex')
45+
END AS payload_a_r1,
46+
(payload IS NOT NULL AND octet_length(payload) > 0) AS payload_a_r1_ok
47+
FROM (
48+
SELECT cloudsync_payload_encode(tbl, pk, col_name, col_value, col_version, db_version, site_id, cl, seq) AS payload
49+
FROM cloudsync_changes
50+
WHERE site_id = cloudsync_siteid()
51+
) AS p \gset
52+
53+
\connect cloudsync_test_b
54+
SELECT CASE WHEN payload IS NULL OR octet_length(payload) = 0
55+
THEN ''
56+
ELSE '\x' || encode(payload, 'hex')
57+
END AS payload_b_r1,
58+
(payload IS NOT NULL AND octet_length(payload) > 0) AS payload_b_r1_ok
59+
FROM (
60+
SELECT cloudsync_payload_encode(tbl, pk, col_name, col_value, col_version, db_version, site_id, cl, seq) AS payload
61+
FROM cloudsync_changes
62+
WHERE site_id = cloudsync_siteid()
63+
) AS p \gset
64+
65+
\connect cloudsync_test_c
66+
SELECT CASE WHEN payload IS NULL OR octet_length(payload) = 0
67+
THEN ''
68+
ELSE '\x' || encode(payload, 'hex')
69+
END AS payload_c_r1,
70+
(payload IS NOT NULL AND octet_length(payload) > 0) AS payload_c_r1_ok
71+
FROM (
72+
SELECT cloudsync_payload_encode(tbl, pk, col_name, col_value, col_version, db_version, site_id, cl, seq) AS payload
73+
FROM cloudsync_changes
74+
WHERE site_id = cloudsync_siteid()
75+
) AS p \gset
76+
77+
-- Round 1 apply: fan-out changes
78+
\connect cloudsync_test_a
79+
\if :{?DEBUG_MERGE}
80+
\echo '[INFO] round1 before merge cloudsync_test_a smoke_tbl'
81+
SELECT * FROM smoke_tbl ORDER BY id;
82+
\endif
83+
\if :payload_b_r1_ok
84+
\if :{?DEBUG_MERGE}
85+
\echo '[MERGE] round1 apply b -> a'
86+
\endif
87+
SELECT cloudsync_payload_apply(decode(substr(:'payload_b_r1', 3), 'hex')) AS _apply_a_r1_b \gset
88+
\else
89+
SELECT 0 AS _apply_a_r1_b \gset
90+
\endif
91+
\if :payload_c_r1_ok
92+
\if :{?DEBUG_MERGE}
93+
\echo '[MERGE] round1 apply c -> a'
94+
\endif
95+
SELECT cloudsync_payload_apply(decode(substr(:'payload_c_r1', 3), 'hex')) AS _apply_a_r1_c \gset
96+
\else
97+
SELECT 0 AS _apply_a_r1_c \gset
98+
\endif
99+
\if :{?DEBUG_MERGE}
100+
\echo '[INFO] round1 after merge cloudsync_test_a smoke_tbl'
101+
SELECT * FROM smoke_tbl ORDER BY id;
102+
\endif
103+
104+
\connect cloudsync_test_b
105+
\if :{?DEBUG_MERGE}
106+
\echo '[INFO] round1 before merge cloudsync_test_b smoke_tbl'
107+
SELECT * FROM smoke_tbl ORDER BY id;
108+
\endif
109+
\if :payload_a_r1_ok
110+
\if :{?DEBUG_MERGE}
111+
\echo '[MERGE] round1 apply a -> b'
112+
\endif
113+
SELECT cloudsync_payload_apply(decode(substr(:'payload_a_r1', 3), 'hex')) AS _apply_b_r1_a \gset
114+
\else
115+
SELECT 0 AS _apply_b_r1_a \gset
116+
\endif
117+
\if :payload_c_r1_ok
118+
\if :{?DEBUG_MERGE}
119+
\echo '[MERGE] round1 apply c -> b'
120+
\endif
121+
SELECT cloudsync_payload_apply(decode(substr(:'payload_c_r1', 3), 'hex')) AS _apply_b_r1_c \gset
122+
\else
123+
SELECT 0 AS _apply_b_r1_c \gset
124+
\endif
125+
\if :{?DEBUG_MERGE}
126+
\echo '[INFO] round1 after merge cloudsync_test_b smoke_tbl'
127+
SELECT * FROM smoke_tbl ORDER BY id;
128+
\endif
129+
130+
\connect cloudsync_test_c
131+
\if :{?DEBUG_MERGE}
132+
\echo '[INFO] round1 before merge cloudsync_test_c smoke_tbl'
133+
SELECT * FROM smoke_tbl ORDER BY id;
134+
\endif
135+
\if :payload_a_r1_ok
136+
\if :{?DEBUG_MERGE}
137+
\echo '[MERGE] round1 apply a -> c'
138+
\endif
139+
SELECT cloudsync_payload_apply(decode(substr(:'payload_a_r1', 3), 'hex')) AS _apply_c_r1_a \gset
140+
\else
141+
SELECT 0 AS _apply_c_r1_a \gset
142+
\endif
143+
\if :payload_b_r1_ok
144+
\if :{?DEBUG_MERGE}
145+
\echo '[MERGE] round1 apply b -> c'
146+
\endif
147+
SELECT cloudsync_payload_apply(decode(substr(:'payload_b_r1', 3), 'hex')) AS _apply_c_r1_b \gset
148+
\else
149+
SELECT 0 AS _apply_c_r1_b \gset
150+
\endif
151+
\if :{?DEBUG_MERGE}
152+
\echo '[INFO] round1 after merge cloudsync_test_c smoke_tbl'
153+
SELECT * FROM smoke_tbl ORDER BY id;
154+
\endif
155+
156+
-- Round 2: skewed concurrent updates on id1
157+
\connect cloudsync_test_a
158+
\if :{?DEBUG_MERGE}
159+
\echo '[INFO] cloudsync_test_a UPDATE id1=a1_u1'
160+
\endif
161+
UPDATE smoke_tbl SET val = 'a1_u1' WHERE id = 'id1';
162+
\if :{?DEBUG_MERGE}
163+
\echo '[INFO] cloudsync_test_a UPDATE id1=a1_u2'
164+
\endif
165+
UPDATE smoke_tbl SET val = 'a1_u2' WHERE id = 'id1';
166+
SELECT CASE WHEN payload IS NULL OR octet_length(payload) = 0
167+
THEN ''
168+
ELSE '\x' || encode(payload, 'hex')
169+
END AS payload_a_r2,
170+
(payload IS NOT NULL AND octet_length(payload) > 0) AS payload_a_r2_ok
171+
FROM (
172+
SELECT cloudsync_payload_encode(tbl, pk, col_name, col_value, col_version, db_version, site_id, cl, seq) AS payload
173+
FROM cloudsync_changes
174+
WHERE site_id = cloudsync_siteid()
175+
) AS p \gset
176+
177+
\connect cloudsync_test_b
178+
\if :{?DEBUG_MERGE}
179+
\echo '[INFO] cloudsync_test_b UPDATE id1=b1_u1'
180+
\endif
181+
UPDATE smoke_tbl SET val = 'b1_u1' WHERE id = 'id1';
182+
SELECT CASE WHEN payload IS NULL OR octet_length(payload) = 0
183+
THEN ''
184+
ELSE '\x' || encode(payload, 'hex')
185+
END AS payload_b_r2,
186+
(payload IS NOT NULL AND octet_length(payload) > 0) AS payload_b_r2_ok
187+
FROM (
188+
SELECT cloudsync_payload_encode(tbl, pk, col_name, col_value, col_version, db_version, site_id, cl, seq) AS payload
189+
FROM cloudsync_changes
190+
WHERE site_id = cloudsync_siteid()
191+
) AS p \gset
192+
193+
\connect cloudsync_test_c
194+
\if :{?DEBUG_MERGE}
195+
\echo '[INFO] cloudsync_test_c UPDATE id1=c1_u1'
196+
\endif
197+
UPDATE smoke_tbl SET val = 'c1_u1' WHERE id = 'id1';
198+
\if :{?DEBUG_MERGE}
199+
\echo '[INFO] cloudsync_test_c UPDATE id1=c1_u2'
200+
\endif
201+
UPDATE smoke_tbl SET val = 'c1_u2' WHERE id = 'id1';
202+
\if :{?DEBUG_MERGE}
203+
\echo '[INFO] cloudsync_test_c UPDATE id1=c1_u3'
204+
\endif
205+
UPDATE smoke_tbl SET val = 'c1_u3' WHERE id = 'id1';
206+
SELECT CASE WHEN payload IS NULL OR octet_length(payload) = 0
207+
THEN ''
208+
ELSE '\x' || encode(payload, 'hex')
209+
END AS payload_c_r2,
210+
(payload IS NOT NULL AND octet_length(payload) > 0) AS payload_c_r2_ok
211+
FROM (
212+
SELECT cloudsync_payload_encode(tbl, pk, col_name, col_value, col_version, db_version, site_id, cl, seq) AS payload
213+
FROM cloudsync_changes
214+
WHERE site_id = cloudsync_siteid()
215+
) AS p \gset
216+
217+
-- Round 2 apply: fan-out changes
218+
\connect cloudsync_test_a
219+
\if :{?DEBUG_MERGE}
220+
\echo '[INFO] round2 before merge cloudsync_test_a smoke_tbl'
221+
SELECT * FROM smoke_tbl ORDER BY id;
222+
\endif
223+
\if :payload_b_r2_ok
224+
\if :{?DEBUG_MERGE}
225+
\echo '[MERGE] round2 apply b -> a'
226+
\endif
227+
SELECT cloudsync_payload_apply(decode(substr(:'payload_b_r2', 3), 'hex')) AS _apply_a_r2_b \gset
228+
\else
229+
SELECT 0 AS _apply_a_r2_b \gset
230+
\endif
231+
\if :payload_c_r2_ok
232+
\if :{?DEBUG_MERGE}
233+
\echo '[MERGE] round2 apply c -> a'
234+
\endif
235+
SELECT cloudsync_payload_apply(decode(substr(:'payload_c_r2', 3), 'hex')) AS _apply_a_r2_c \gset
236+
\else
237+
SELECT 0 AS _apply_a_r2_c \gset
238+
\endif
239+
\if :{?DEBUG_MERGE}
240+
\echo '[INFO] round2 after merge cloudsync_test_a smoke_tbl'
241+
SELECT * FROM smoke_tbl ORDER BY id;
242+
\endif
243+
244+
\connect cloudsync_test_b
245+
\if :{?DEBUG_MERGE}
246+
\echo '[INFO] round2 before merge cloudsync_test_b smoke_tbl'
247+
SELECT * FROM smoke_tbl ORDER BY id;
248+
\endif
249+
\if :payload_a_r2_ok
250+
\if :{?DEBUG_MERGE}
251+
\echo '[MERGE] round2 apply a -> b'
252+
\endif
253+
SELECT cloudsync_payload_apply(decode(substr(:'payload_a_r2', 3), 'hex')) AS _apply_b_r2_a \gset
254+
\else
255+
SELECT 0 AS _apply_b_r2_a \gset
256+
\endif
257+
\if :payload_c_r2_ok
258+
\if :{?DEBUG_MERGE}
259+
\echo '[MERGE] round2 apply c -> b'
260+
\endif
261+
SELECT cloudsync_payload_apply(decode(substr(:'payload_c_r2', 3), 'hex')) AS _apply_b_r2_c \gset
262+
\else
263+
SELECT 0 AS _apply_b_r2_c \gset
264+
\endif
265+
\if :{?DEBUG_MERGE}
266+
\echo '[INFO] round2 after merge cloudsync_test_b smoke_tbl'
267+
SELECT * FROM smoke_tbl ORDER BY id;
268+
\endif
269+
270+
\connect cloudsync_test_c
271+
\if :{?DEBUG_MERGE}
272+
\echo '[INFO] round2 before merge cloudsync_test_c smoke_tbl'
273+
SELECT * FROM smoke_tbl ORDER BY id;
274+
\endif
275+
\if :payload_a_r2_ok
276+
\if :{?DEBUG_MERGE}
277+
\echo '[MERGE] round2 apply a -> c'
278+
\endif
279+
SELECT cloudsync_payload_apply(decode(substr(:'payload_a_r2', 3), 'hex')) AS _apply_c_r2_a \gset
280+
\else
281+
SELECT 0 AS _apply_c_r2_a \gset
282+
\endif
283+
\if :payload_b_r2_ok
284+
\if :{?DEBUG_MERGE}
285+
\echo '[MERGE] round2 apply b -> c'
286+
\endif
287+
SELECT cloudsync_payload_apply(decode(substr(:'payload_b_r2', 3), 'hex')) AS _apply_c_r2_b \gset
288+
\else
289+
SELECT 0 AS _apply_c_r2_b \gset
290+
\endif
291+
\if :{?DEBUG_MERGE}
292+
\echo '[INFO] round2 after merge cloudsync_test_c smoke_tbl'
293+
SELECT * FROM smoke_tbl ORDER BY id;
294+
\endif
295+
296+
-- Final consistency check across all three databases
297+
\connect cloudsync_test_a
298+
SELECT md5(COALESCE(string_agg(id || ':' || COALESCE(val, ''), ',' ORDER BY id), '')) AS smoke_hash_a
299+
FROM smoke_tbl \gset
300+
301+
\connect cloudsync_test_b
302+
SELECT md5(COALESCE(string_agg(id || ':' || COALESCE(val, ''), ',' ORDER BY id), '')) AS smoke_hash_b
303+
FROM smoke_tbl \gset
304+
305+
\connect cloudsync_test_c
306+
SELECT md5(COALESCE(string_agg(id || ':' || COALESCE(val, ''), ',' ORDER BY id), '')) AS smoke_hash_c
307+
FROM smoke_tbl \gset
308+
309+
SELECT (:'smoke_hash_a' = :'smoke_hash_b' AND :'smoke_hash_a' = :'smoke_hash_c') AS multi_db_roundtrip_ok \gset
310+
\if :multi_db_roundtrip_ok
311+
\echo '[PASS] Test multi-db roundtrip with skewed col_version updates'
312+
\else
313+
\echo '[FAIL] Test multi-db roundtrip with skewed col_version updates'
314+
SELECT (:fail::int + 1) AS fail \gset
315+
\endif

0 commit comments

Comments
 (0)