Skip to content

Commit 4a69a51

Browse files
andinuxclaude
andcommitted
test(postgres): add drain+apply round-trip to positional-resume test
Part 1 of test 55 proves the positional cursor produces byte-identical chunks to a full scan. Part 2 now closes the loop end-to-end: a plpgsql helper drains the whole window the way the /check job will (legacy exclusive since=0, then the positional cursor one chunk per call, ORDER BY chunk_index LIMIT 1 so each value-per-call SRF runs to completion), the drained stream is applied to a fresh database, and the receiver's table content is hashed and compared to the source. This validates the real path (positional drain -> apply -> faithful replica), not just byte-identity against a baseline. Verified on PostgreSQL 17: 501 rows reproduced, hashes match. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 889d2a0 commit 4a69a51

1 file changed

Lines changed: 76 additions & 0 deletions

File tree

test/postgresql/55_payload_chunks_positional_resume.sql

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,20 @@
55
-- the following chunk byte-for-byte, including boundaries that fall inside a single
66
-- committed db_version and inside a value larger than the chunk budget. No spool
77
-- table, no idempotent overlap.
8+
--
9+
-- Part 2 is end-to-end: drain the whole window the way the /check job will (one
10+
-- chunk per call via the positional cursor), apply that stream to a fresh database,
11+
-- and assert the receiver's table content hashes identically to the source.
812

913
\set testid '55-positional'
1014
\ir helper_test_init.sql
1115

1216
\connect postgres
1317
\ir helper_psql_conn_setup.sql
1418
DROP DATABASE IF EXISTS cloudsync_test_55_positional;
19+
DROP DATABASE IF EXISTS cloudsync_test_55_positional_dst;
1520
CREATE DATABASE cloudsync_test_55_positional;
21+
CREATE DATABASE cloudsync_test_55_positional_dst;
1622

1723
\connect cloudsync_test_55_positional
1824
\ir helper_psql_conn_setup.sql
@@ -81,8 +87,78 @@ SELECT (:fail::int + 1) AS fail \gset
8187
SELECT (:fail::int + 1) AS fail \gset
8288
\endif
8389

90+
-- Part 2: end-to-end drain + apply round-trip.
91+
--
92+
-- Drain the window exactly as the /check job will: start with the legacy
93+
-- exclusive cursor (since=0), then step the positional cursor one chunk per call,
94+
-- collecting payloads in drain order. ORDER BY chunk_index LIMIT 1 forces each
95+
-- value-per-call SRF to run to completion (no early-terminated cursor). The drained
96+
-- chunks are returned hex-joined so they can cross \connect into the receiver DB.
97+
CREATE OR REPLACE FUNCTION _positional_drain_hex() RETURNS text LANGUAGE plpgsql AS $$
98+
DECLARE
99+
rdbv bigint; rseq bigint; rfrag bigint; wm bigint := 0;
100+
rec record; parts text[] := '{}'; guard int := 0;
101+
BEGIN
102+
LOOP
103+
guard := guard + 1;
104+
IF guard > 100000 THEN RAISE EXCEPTION 'positional drain did not terminate'; END IF;
105+
IF wm = 0 THEN
106+
SELECT * INTO rec FROM cloudsync_payload_chunks(0, cloudsync_siteid(), NULL, false)
107+
ORDER BY chunk_index LIMIT 1;
108+
IF NOT FOUND THEN EXIT; END IF;
109+
wm := rec.watermark_db_version;
110+
ELSE
111+
SELECT * INTO rec FROM cloudsync_payload_chunks(NULL, cloudsync_siteid(), wm, false, rdbv, rseq, rfrag)
112+
ORDER BY chunk_index LIMIT 1;
113+
IF NOT FOUND THEN EXIT; END IF;
114+
END IF;
115+
parts := array_append(parts, encode(rec.payload, 'hex'));
116+
rdbv := rec.next_db_version; rseq := rec.next_seq; rfrag := rec.next_frag_offset;
117+
EXIT WHEN rec.is_final;
118+
END LOOP;
119+
RETURN array_to_string(parts, ',');
120+
END $$;
121+
122+
SELECT _positional_drain_hex() AS chunks_hex \gset
123+
SELECT
124+
md5(string_agg(id || ':' || encode(body, 'hex'), '|' ORDER BY id)) AS src_hash,
125+
count(*) AS src_count
126+
FROM split_test \gset
127+
128+
\connect cloudsync_test_55_positional_dst
129+
\ir helper_psql_conn_setup.sql
130+
CREATE EXTENSION IF NOT EXISTS cloudsync;
131+
CREATE TABLE split_test (id TEXT PRIMARY KEY, body BYTEA DEFAULT '\x'::bytea);
132+
SELECT cloudsync_init('split_test', 'CLS', 1) AS _init_dst \gset
133+
SELECT cloudsync_set('payload_max_chunk_size', '262144');
134+
135+
-- Reconstitute the drained chunks and apply them (reverse order on purpose: apply
136+
-- must be order-independent and reassemble fragments regardless).
137+
CREATE TEMP TABLE chunk_transport(ord int, payload bytea);
138+
INSERT INTO chunk_transport(ord, payload)
139+
SELECT ord::int, decode(chunk_hex, 'hex')
140+
FROM unnest(string_to_array(:'chunks_hex', ',')) WITH ORDINALITY AS t(chunk_hex, ord);
141+
142+
SELECT coalesce(sum(cloudsync_payload_apply(payload)), 0) AS applied_rows
143+
FROM (SELECT payload FROM chunk_transport ORDER BY ord DESC) AS ordered \gset
144+
145+
SELECT
146+
md5(string_agg(id || ':' || encode(body, 'hex'), '|' ORDER BY id)) AS dst_hash,
147+
count(*) AS dst_count
148+
FROM split_test \gset
149+
150+
SELECT (:'dst_hash' = :'src_hash' AND :dst_count::int = :src_count::int
151+
AND :dst_count::int > 0) AS roundtrip_ok \gset
152+
\if :roundtrip_ok
153+
\echo [PASS] (:testid) positional drain applied to a fresh database reproduces the source (:dst_count rows)
154+
\else
155+
\echo [FAIL] (:testid) drain/apply mismatch (src_count=:src_count dst_count=:dst_count hashes :'src_hash' vs :'dst_hash')
156+
SELECT (:fail::int + 1) AS fail \gset
157+
\endif
158+
84159
\ir helper_test_cleanup.sql
85160
\if :should_cleanup
86161
\connect postgres
87162
DROP DATABASE IF EXISTS cloudsync_test_55_positional;
163+
DROP DATABASE IF EXISTS cloudsync_test_55_positional_dst;
88164
\endif

0 commit comments

Comments
 (0)