Skip to content

Commit e0ca087

Browse files
committed
test: new alter table test for postgres
1 parent 3bc7fdd commit e0ca087

File tree

2 files changed

+385
-0
lines changed

2 files changed

+385
-0
lines changed
Lines changed: 383 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,383 @@
1+
-- Alter Table Sync Test
2+
-- Tests cloudsync_begin_alter and cloudsync_commit_alter functions.
3+
-- Verifies that schema changes (add column) are handled correctly
4+
-- and data syncs after alteration.
5+
6+
\set testid '31'
7+
\ir helper_test_init.sql
8+
9+
\connect postgres
10+
\ir helper_psql_conn_setup.sql
11+
12+
-- Cleanup and create test databases
13+
DROP DATABASE IF EXISTS cloudsync_test_31a;
14+
DROP DATABASE IF EXISTS cloudsync_test_31b;
15+
CREATE DATABASE cloudsync_test_31a;
16+
CREATE DATABASE cloudsync_test_31b;
17+
18+
-- ============================================================================
19+
-- Setup Database A
20+
-- ============================================================================
21+
22+
\connect cloudsync_test_31a
23+
\ir helper_psql_conn_setup.sql
24+
CREATE EXTENSION IF NOT EXISTS cloudsync;
25+
26+
CREATE TABLE products (
27+
id UUID PRIMARY KEY,
28+
name TEXT NOT NULL DEFAULT '',
29+
price DOUBLE PRECISION NOT NULL DEFAULT 0.0,
30+
quantity INTEGER NOT NULL DEFAULT 0
31+
);
32+
33+
SELECT cloudsync_init('products', 'CLS', false) AS _init_a \gset
34+
35+
INSERT INTO products VALUES ('11111111-1111-1111-1111-111111111111', 'Product A1', 10.99, 100);
36+
INSERT INTO products VALUES ('22222222-2222-2222-2222-222222222222', 'Product A2', 20.50, 200);
37+
38+
-- ============================================================================
39+
-- Setup Database B with same schema
40+
-- ============================================================================
41+
42+
\connect cloudsync_test_31b
43+
\ir helper_psql_conn_setup.sql
44+
CREATE EXTENSION IF NOT EXISTS cloudsync;
45+
46+
CREATE TABLE products (
47+
id UUID PRIMARY KEY,
48+
name TEXT NOT NULL DEFAULT '',
49+
price DOUBLE PRECISION NOT NULL DEFAULT 0.0,
50+
quantity INTEGER NOT NULL DEFAULT 0
51+
);
52+
53+
SELECT cloudsync_init('products', 'CLS', false) AS _init_b \gset
54+
55+
INSERT INTO products VALUES ('33333333-3333-3333-3333-333333333333', 'Product B1', 30.00, 300);
56+
INSERT INTO products VALUES ('44444444-4444-4444-4444-444444444444', 'Product B2', 40.75, 400);
57+
58+
-- ============================================================================
59+
-- Initial Sync: A -> B and B -> A
60+
-- ============================================================================
61+
62+
\echo [INFO] (:testid) === Initial Sync Before ALTER ===
63+
64+
-- Encode payload from A
65+
\connect cloudsync_test_31a
66+
\ir helper_psql_conn_setup.sql
67+
SELECT cloudsync_init('products', 'CLS', false) AS _reinit \gset
68+
SELECT encode(
69+
cloudsync_payload_encode(tbl, pk, col_name, col_value, col_version, db_version, site_id, cl, seq),
70+
'hex'
71+
) AS payload_a_hex
72+
FROM cloudsync_changes
73+
WHERE site_id = cloudsync_siteid() \gset
74+
75+
-- Apply A's payload to B, encode B's payload
76+
\connect cloudsync_test_31b
77+
\ir helper_psql_conn_setup.sql
78+
SELECT cloudsync_init('products', 'CLS', false) AS _reinit \gset
79+
SELECT cloudsync_payload_apply(decode(:'payload_a_hex', 'hex')) AS apply_a_to_b \gset
80+
81+
SELECT encode(
82+
cloudsync_payload_encode(tbl, pk, col_name, col_value, col_version, db_version, site_id, cl, seq),
83+
'hex'
84+
) AS payload_b_hex
85+
FROM cloudsync_changes
86+
WHERE site_id = cloudsync_siteid() \gset
87+
88+
-- Apply B's payload to A, verify initial sync
89+
\connect cloudsync_test_31a
90+
\ir helper_psql_conn_setup.sql
91+
SELECT cloudsync_init('products', 'CLS', false) AS _reinit \gset
92+
SELECT cloudsync_payload_apply(decode(:'payload_b_hex', 'hex')) AS apply_b_to_a \gset
93+
94+
SELECT COUNT(*) AS count_a_initial FROM products \gset
95+
96+
\connect cloudsync_test_31b
97+
\ir helper_psql_conn_setup.sql
98+
SELECT COUNT(*) AS count_b_initial FROM products \gset
99+
100+
SELECT (:count_a_initial = 4 AND :count_b_initial = 4) AS initial_sync_ok \gset
101+
\if :initial_sync_ok
102+
\echo [PASS] (:testid) Initial sync complete - both databases have 4 rows
103+
\else
104+
\echo [FAIL] (:testid) Initial sync failed - A: :count_a_initial, B: :count_b_initial
105+
SELECT (:fail::int + 1) AS fail \gset
106+
\endif
107+
108+
-- ============================================================================
109+
-- ALTER TABLE on Database A (begin_alter + ALTER + commit_alter on SAME connection)
110+
-- ============================================================================
111+
112+
\echo [INFO] (:testid) === ALTER TABLE on Database A ===
113+
114+
\connect cloudsync_test_31a
115+
\ir helper_psql_conn_setup.sql
116+
SELECT cloudsync_init('products', 'CLS', false) AS _reinit \gset
117+
118+
SELECT cloudsync_begin_alter('products') AS begin_alter_a \gset
119+
\if :begin_alter_a
120+
\echo [PASS] (:testid) cloudsync_begin_alter succeeded on Database A
121+
\else
122+
\echo [FAIL] (:testid) cloudsync_begin_alter failed on Database A
123+
SELECT (:fail::int + 1) AS fail \gset
124+
\endif
125+
126+
ALTER TABLE products ADD COLUMN description TEXT NOT NULL DEFAULT '';
127+
128+
SELECT cloudsync_commit_alter('products') AS commit_alter_a \gset
129+
\if :commit_alter_a
130+
\echo [PASS] (:testid) cloudsync_commit_alter succeeded on Database A
131+
\else
132+
\echo [FAIL] (:testid) cloudsync_commit_alter failed on Database A
133+
SELECT (:fail::int + 1) AS fail \gset
134+
\endif
135+
136+
-- Insert and update post-ALTER data on A
137+
INSERT INTO products (id, name, price, quantity, description)
138+
VALUES ('55555555-5555-5555-5555-555555555555', 'New Product A', 55.55, 555, 'Added after alter on A');
139+
140+
UPDATE products SET description = 'Updated on A' WHERE id = '11111111-1111-1111-1111-111111111111';
141+
UPDATE products SET quantity = 150 WHERE id = '11111111-1111-1111-1111-111111111111';
142+
143+
-- Encode post-ALTER payload from A
144+
SELECT encode(
145+
cloudsync_payload_encode(tbl, pk, col_name, col_value, col_version, db_version, site_id, cl, seq),
146+
'hex'
147+
) AS payload_a2_hex
148+
FROM cloudsync_changes
149+
WHERE site_id = cloudsync_siteid() \gset
150+
151+
SELECT (length(:'payload_a2_hex') > 0) AS payload_a2_created \gset
152+
\if :payload_a2_created
153+
\echo [PASS] (:testid) Post-alter payload encoded from Database A
154+
\else
155+
\echo [FAIL] (:testid) Post-alter payload empty from Database A
156+
SELECT (:fail::int + 1) AS fail \gset
157+
\endif
158+
159+
-- ============================================================================
160+
-- ALTER TABLE on Database B (begin_alter + ALTER + commit_alter on SAME connection)
161+
-- Apply A's payload, insert/update, encode B's payload
162+
-- ============================================================================
163+
164+
\echo [INFO] (:testid) === ALTER TABLE on Database B ===
165+
166+
\connect cloudsync_test_31b
167+
\ir helper_psql_conn_setup.sql
168+
SELECT cloudsync_init('products', 'CLS', false) AS _reinit \gset
169+
170+
SELECT cloudsync_begin_alter('products') AS begin_alter_b \gset
171+
\if :begin_alter_b
172+
\echo [PASS] (:testid) cloudsync_begin_alter succeeded on Database B
173+
\else
174+
\echo [FAIL] (:testid) cloudsync_begin_alter failed on Database B
175+
SELECT (:fail::int + 1) AS fail \gset
176+
\endif
177+
178+
ALTER TABLE products ADD COLUMN description TEXT NOT NULL DEFAULT '';
179+
180+
SELECT cloudsync_commit_alter('products') AS commit_alter_b \gset
181+
\if :commit_alter_b
182+
\echo [PASS] (:testid) cloudsync_commit_alter succeeded on Database B
183+
\else
184+
\echo [FAIL] (:testid) cloudsync_commit_alter failed on Database B
185+
SELECT (:fail::int + 1) AS fail \gset
186+
\endif
187+
188+
-- Insert and update post-ALTER data on B
189+
INSERT INTO products (id, name, price, quantity, description)
190+
VALUES ('66666666-6666-6666-6666-666666666666', 'New Product B', 66.66, 666, 'Added after alter on B');
191+
192+
UPDATE products SET description = 'Updated on B' WHERE id = '33333333-3333-3333-3333-333333333333';
193+
UPDATE products SET quantity = 350 WHERE id = '33333333-3333-3333-3333-333333333333';
194+
195+
-- Apply A's post-alter payload to B
196+
SELECT cloudsync_payload_apply(decode(:'payload_a2_hex', 'hex')) AS apply_a2_to_b \gset
197+
198+
SELECT (:apply_a2_to_b >= 0) AS apply_a2_ok \gset
199+
\if :apply_a2_ok
200+
\echo [PASS] (:testid) Post-alter payload from A applied to B
201+
\else
202+
\echo [FAIL] (:testid) Post-alter payload from A failed to apply to B: :apply_a2_to_b
203+
SELECT (:fail::int + 1) AS fail \gset
204+
\endif
205+
206+
-- Encode post-ALTER payload from B
207+
SELECT encode(
208+
cloudsync_payload_encode(tbl, pk, col_name, col_value, col_version, db_version, site_id, cl, seq),
209+
'hex'
210+
) AS payload_b2_hex
211+
FROM cloudsync_changes
212+
WHERE site_id = cloudsync_siteid() \gset
213+
214+
-- ============================================================================
215+
-- Apply B's payload to A, then verify final state
216+
-- ============================================================================
217+
218+
\echo [INFO] (:testid) === Apply B payload to A and verify ===
219+
220+
\connect cloudsync_test_31a
221+
\ir helper_psql_conn_setup.sql
222+
SELECT cloudsync_init('products', 'CLS', false) AS _reinit \gset
223+
SELECT cloudsync_payload_apply(decode(:'payload_b2_hex', 'hex')) AS apply_b2_to_a \gset
224+
225+
SELECT (:apply_b2_to_a >= 0) AS apply_b2_ok \gset
226+
\if :apply_b2_ok
227+
\echo [PASS] (:testid) Post-alter payload from B applied to A
228+
\else
229+
\echo [FAIL] (:testid) Post-alter payload from B failed to apply to A: :apply_b2_to_a
230+
SELECT (:fail::int + 1) AS fail \gset
231+
\endif
232+
233+
-- ============================================================================
234+
-- Verify final state
235+
-- ============================================================================
236+
237+
\echo [INFO] (:testid) === Verify Final State ===
238+
239+
-- Compute hash of Database A
240+
SELECT md5(
241+
COALESCE(
242+
string_agg(
243+
id::text || ':' ||
244+
COALESCE(name, 'NULL') || ':' ||
245+
COALESCE(price::text, 'NULL') || ':' ||
246+
COALESCE(quantity::text, 'NULL') || ':' ||
247+
COALESCE(description, 'NULL'),
248+
'|' ORDER BY id
249+
),
250+
''
251+
)
252+
) AS hash_a_final FROM products \gset
253+
254+
\echo [INFO] (:testid) Database A final hash: :hash_a_final
255+
256+
-- Row count on A
257+
SELECT COUNT(*) AS count_a_final FROM products \gset
258+
259+
-- Verify new row from B exists in A
260+
SELECT COUNT(*) = 1 AS new_row_b_ok
261+
FROM products
262+
WHERE id = '66666666-6666-6666-6666-666666666666'
263+
AND name = 'New Product B'
264+
AND price = 66.66
265+
AND quantity = 666
266+
AND description = 'Added after alter on B' \gset
267+
268+
-- Verify updated row from B synced to A
269+
SELECT COUNT(*) = 1 AS updated_row_b_ok
270+
FROM products
271+
WHERE id = '33333333-3333-3333-3333-333333333333'
272+
AND description = 'Updated on B'
273+
AND quantity = 350 \gset
274+
275+
\connect cloudsync_test_31b
276+
\ir helper_psql_conn_setup.sql
277+
278+
-- Compute hash of Database B
279+
SELECT md5(
280+
COALESCE(
281+
string_agg(
282+
id::text || ':' ||
283+
COALESCE(name, 'NULL') || ':' ||
284+
COALESCE(price::text, 'NULL') || ':' ||
285+
COALESCE(quantity::text, 'NULL') || ':' ||
286+
COALESCE(description, 'NULL'),
287+
'|' ORDER BY id
288+
),
289+
''
290+
)
291+
) AS hash_b_final FROM products \gset
292+
293+
\echo [INFO] (:testid) Database B final hash: :hash_b_final
294+
295+
-- Row count on B
296+
SELECT COUNT(*) AS count_b_final FROM products \gset
297+
298+
-- Verify new row from A exists in B
299+
SELECT COUNT(*) = 1 AS new_row_a_ok
300+
FROM products
301+
WHERE id = '55555555-5555-5555-5555-555555555555'
302+
AND name = 'New Product A'
303+
AND price = 55.55
304+
AND quantity = 555
305+
AND description = 'Added after alter on A' \gset
306+
307+
-- Verify updated row from A synced to B
308+
SELECT COUNT(*) = 1 AS updated_row_a_ok
309+
FROM products
310+
WHERE id = '11111111-1111-1111-1111-111111111111'
311+
AND description = 'Updated on A'
312+
AND quantity = 150 \gset
313+
314+
-- Verify new column exists
315+
SELECT COUNT(*) = 1 AS description_column_exists
316+
FROM information_schema.columns
317+
WHERE table_name = 'products' AND column_name = 'description' \gset
318+
319+
-- ============================================================================
320+
-- Report results
321+
-- ============================================================================
322+
323+
-- Compare final hashes
324+
SELECT (:'hash_a_final' = :'hash_b_final') AS final_hashes_match \gset
325+
\if :final_hashes_match
326+
\echo [PASS] (:testid) Final data integrity verified - hashes match after ALTER
327+
\else
328+
\echo [FAIL] (:testid) Final data integrity check failed - A: :hash_a_final, B: :hash_b_final
329+
SELECT (:fail::int + 1) AS fail \gset
330+
\endif
331+
332+
SELECT (:count_a_final = 6 AND :count_b_final = 6) AS row_counts_ok \gset
333+
\if :row_counts_ok
334+
\echo [PASS] (:testid) Row counts match (6 rows each)
335+
\else
336+
\echo [FAIL] (:testid) Row counts mismatch - A: :count_a_final, B: :count_b_final
337+
SELECT (:fail::int + 1) AS fail \gset
338+
\endif
339+
340+
\if :new_row_a_ok
341+
\echo [PASS] (:testid) New row from A synced to B with new schema
342+
\else
343+
\echo [FAIL] (:testid) New row from A not found or incorrect in B
344+
SELECT (:fail::int + 1) AS fail \gset
345+
\endif
346+
347+
\if :new_row_b_ok
348+
\echo [PASS] (:testid) New row from B synced to A with new schema
349+
\else
350+
\echo [FAIL] (:testid) New row from B not found or incorrect in A
351+
SELECT (:fail::int + 1) AS fail \gset
352+
\endif
353+
354+
\if :updated_row_a_ok
355+
\echo [PASS] (:testid) Updated row from A synced with new column values
356+
\else
357+
\echo [FAIL] (:testid) Updated row from A not synced correctly
358+
SELECT (:fail::int + 1) AS fail \gset
359+
\endif
360+
361+
\if :updated_row_b_ok
362+
\echo [PASS] (:testid) Updated row from B synced with new column values
363+
\else
364+
\echo [FAIL] (:testid) Updated row from B not synced correctly
365+
SELECT (:fail::int + 1) AS fail \gset
366+
\endif
367+
368+
\if :description_column_exists
369+
\echo [PASS] (:testid) Added column 'description' exists
370+
\else
371+
\echo [FAIL] (:testid) Added column 'description' not found
372+
SELECT (:fail::int + 1) AS fail \gset
373+
\endif
374+
375+
-- ============================================================================
376+
-- Cleanup
377+
-- ============================================================================
378+
379+
\ir helper_test_cleanup.sql
380+
\if :should_cleanup
381+
DROP DATABASE IF EXISTS cloudsync_test_31a;
382+
DROP DATABASE IF EXISTS cloudsync_test_31b;
383+
\endif

test/postgresql/full_test.sql

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@
3939
\ir 29_rls_multicol.sql
4040
\ir 30_null_prikey_insert.sql
4141

42+
\ir 31_alter_table_sync.sql
43+
4244
-- 'Test summary'
4345
\echo '\nTest summary:'
4446
\echo - Failures: :fail

0 commit comments

Comments
 (0)