-- Copyright (c) Microsoft Corporation. -- Licensed under the PostgreSQL License. -- Regression tests for: df.loop whose body CONTAINS a JOIN or RACE -- (https://github.com/microsoft/pg_durable/issues/230) -- -- A loop iterates via continue_as_new. Duroxide 0.1.30 generation-qualifies automatic -- child ids, while pg_durable continues to set explicit ids that encode execution lineage -- for node-status inference. Either path must keep child ids distinct across generations; -- otherwise the second iteration can reuse stale subtree state. The loop must run >= 2 -- iterations and every branch must run once per iteration. SET SESSION AUTHORIZATION df_e2e_user; -- === Test 1: JOIN inside a NON-ROOT loop body, >= 3 iterations === -- -- Graph: INSERT prefix ~> df.loop( (INSERT left & INSERT right) ~> break after 3 ) -- Expected: prefix = 1 row, left = 3 rows, right = 3 rows (both branches every iteration). DROP TABLE IF EXISTS test_loopjoin_prefix; DROP TABLE IF EXISTS test_loopjoin_left; DROP TABLE IF EXISTS test_loopjoin_right; CREATE TABLE test_loopjoin_prefix (id SERIAL, ts TIMESTAMPTZ DEFAULT clock_timestamp()); CREATE TABLE test_loopjoin_left (id SERIAL, ts TIMESTAMPTZ DEFAULT clock_timestamp()); CREATE TABLE test_loopjoin_right (id SERIAL, ts TIMESTAMPTZ DEFAULT clock_timestamp()); CREATE TEMP TABLE _t1 AS SELECT df.start( df.seq( 'INSERT INTO test_loopjoin_prefix DEFAULT VALUES', df.loop( ( 'INSERT INTO test_loopjoin_left DEFAULT VALUES' & 'INSERT INTO test_loopjoin_right DEFAULT VALUES' ) ~> ( 'SELECT COUNT(*) >= 3 FROM test_loopjoin_left' ?> df.break() !> df.sleep(1) ) ) ), 'test-loop-contains-join' ) AS instance_id; DO $$ DECLARE v_id TEXT; v_status TEXT; v_prefix INT; v_left INT; v_right INT; BEGIN SELECT instance_id INTO v_id FROM _t1; RAISE NOTICE 'Test 1 - JOIN inside loop body: instance %', v_id; SELECT df.await_instance(v_id, 90) INTO v_status; IF v_status != 'completed' THEN RAISE EXCEPTION 'TEST FAILED [loop-join]: expected completed, got %', v_status; END IF; SELECT COUNT(*) INTO v_prefix FROM test_loopjoin_prefix; SELECT COUNT(*) INTO v_left FROM test_loopjoin_left; SELECT COUNT(*) INTO v_right FROM test_loopjoin_right; IF v_prefix != 1 THEN RAISE EXCEPTION 'TEST FAILED [loop-join]: prefix ran % time(s) (expected 1)', v_prefix; END IF; IF v_left != 3 THEN RAISE EXCEPTION 'TEST FAILED [loop-join]: left branch ran % time(s) (expected 3)', v_left; END IF; IF v_right != 3 THEN RAISE EXCEPTION 'TEST FAILED [loop-join]: right branch ran % time(s) (expected 3)', v_right; END IF; RAISE NOTICE 'PASSED: JOIN inside loop body — both branches ran once per iteration (3 iterations)'; END $$; DROP TABLE _t1; DROP TABLE test_loopjoin_prefix; DROP TABLE test_loopjoin_left; DROP TABLE test_loopjoin_right; -- === Test 2: RACE inside a NON-ROOT loop body, >= 2 iterations === -- -- Graph: INSERT prefix ~> df.loop( body ~> race(fast-or-break, slow) ) -- The race's first branch returns fast (no break) on iteration 1 and breaks on iteration 2, -- so the loop runs exactly 2 iterations. The slow branch (pg_sleep) never wins. DROP TABLE IF EXISTS test_looprace_prefix; DROP TABLE IF EXISTS test_looprace_log; CREATE TABLE test_looprace_prefix (id SERIAL, ts TIMESTAMPTZ DEFAULT clock_timestamp()); CREATE TABLE test_looprace_log (id SERIAL, iteration INT, ts TIMESTAMPTZ DEFAULT clock_timestamp()); CREATE TEMP TABLE _t2 AS SELECT df.start( df.seq( 'INSERT INTO test_looprace_prefix DEFAULT VALUES', df.loop( 'INSERT INTO test_looprace_log (iteration) VALUES ((SELECT COALESCE(MAX(iteration), 0) + 1 FROM test_looprace_log))' ~> df.race( df.if( 'SELECT COUNT(*) >= 2 FROM test_looprace_log', df.break('race-done'), 'SELECT 1' ), 'SELECT pg_sleep(30)' ) ) ), 'test-loop-contains-race' ) AS instance_id; DO $$ DECLARE v_id TEXT; v_status TEXT; v_prefix INT; v_iters INT; BEGIN SELECT instance_id INTO v_id FROM _t2; RAISE NOTICE 'Test 2 - RACE inside loop body: instance %', v_id; SELECT df.await_instance(v_id, 90) INTO v_status; IF v_status != 'completed' THEN RAISE EXCEPTION 'TEST FAILED [loop-race]: expected completed, got %', v_status; END IF; SELECT COUNT(*) INTO v_prefix FROM test_looprace_prefix; SELECT COUNT(*) INTO v_iters FROM test_looprace_log; IF v_prefix != 1 THEN RAISE EXCEPTION 'TEST FAILED [loop-race]: prefix ran % time(s) (expected 1)', v_prefix; END IF; IF v_iters != 2 THEN RAISE EXCEPTION 'TEST FAILED [loop-race]: loop ran % iteration(s) (expected 2)', v_iters; END IF; RAISE NOTICE 'PASSED: RACE inside loop body — loop ran 2 iterations and exited on break'; END $$; DROP TABLE _t2; DROP TABLE test_looprace_prefix; DROP TABLE test_looprace_log; SELECT 'TEST PASSED' AS result;