-- Copyright (c) Microsoft Corporation. -- Licensed under the PostgreSQL License. -- Merged from: 29_database_validation, 34_multi_database -- Tests: CREATE EXTENSION rejected in wrong database, workflows execute in correct database, -- df.start() with explicit database parameter, invalid database rejection, -- multi-node sequence in another database, dropped database failure handling -- Runs as postgres throughout (creates/drops databases) -- === Test: 29_database_validation === CREATE EXTENSION IF NOT EXISTS dblink; -- Verify we're running in the correct database DO $$ DECLARE current_db TEXT; target_db TEXT; BEGIN SELECT current_database() INTO current_db; SELECT df.target_database() INTO target_db; IF current_db != target_db THEN RAISE EXCEPTION 'TEST SETUP ERROR: This test must run in database "%" (currently in "%")', target_db, current_db; END IF; RAISE NOTICE 'Test running in correct database: %', current_db; END $$; -- Test 1: CREATE EXTENSION should succeed in the correct database SELECT public._e2e_drop_extension_safe(); CREATE EXTENSION pg_durable; SELECT df.grant_usage('df_e2e_user'); SELECT public._e2e_wait_for_worker_ready(); DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_extension WHERE extname = 'pg_durable') THEN RAISE EXCEPTION 'TEST FAILED: Extension should exist in correct database'; END IF; RAISE NOTICE 'PASSED: CREATE EXTENSION succeeded in correct database'; END $$; -- Test 2: Verify workflows can execute (BGW is connected to this database) CREATE TEMP TABLE _test_state (instance_id TEXT); INSERT INTO _test_state SELECT df.start('SELECT 42 as answer', 'test-correct-db'); DO $$ DECLARE inst_id TEXT; status TEXT; BEGIN SELECT instance_id INTO inst_id FROM _test_state; SELECT df.await_instance(inst_id) INTO status; IF status != 'completed' THEN RAISE EXCEPTION 'TEST FAILED: Workflow should complete in correct database, got status: %', status; END IF; RAISE NOTICE 'PASSED: Workflow executed successfully in correct database'; END $$; DROP TABLE _test_state; -- Test 3: CREATE EXTENSION must fail in a wrong database DROP DATABASE IF EXISTS _test_wrong_db; CREATE DATABASE _test_wrong_db; DO $$ DECLARE connstr TEXT; err_msg TEXT; BEGIN connstr := format( 'host=localhost dbname=_test_wrong_db port=%s user=postgres', current_setting('port') ); BEGIN PERFORM dblink_exec(connstr, 'CREATE EXTENSION pg_durable;'); RAISE EXCEPTION 'TEST FAILED: CREATE EXTENSION should have been rejected in wrong database'; EXCEPTION WHEN OTHERS THEN err_msg := SQLERRM; END; IF err_msg NOT ILIKE '%must be created in database%' THEN RAISE EXCEPTION 'TEST FAILED: Expected "must be created in database" in error, got: %', err_msg; END IF; IF err_msg NOT ILIKE '%_test_wrong_db%' THEN RAISE EXCEPTION 'TEST FAILED: Expected wrong db name in error, got: %', err_msg; END IF; RAISE NOTICE 'PASSED: CREATE EXTENSION correctly rejected in wrong database'; RAISE NOTICE 'Error was: %', err_msg; END $$; DROP DATABASE IF EXISTS _test_wrong_db; -- === Test: 34_multi_database === -- Test 1: Execute durable function in a different database DROP DATABASE IF EXISTS _test_multi_db; CREATE DATABASE _test_multi_db; GRANT CONNECT ON DATABASE _test_multi_db TO df_e2e_user; SELECT dblink_exec( format('host=localhost dbname=_test_multi_db port=%s user=postgres', current_setting('port')), 'CREATE TABLE test_multi (id INT, value TEXT)' ); SELECT dblink_exec( format('host=localhost dbname=_test_multi_db port=%s user=postgres', current_setting('port')), 'GRANT ALL ON test_multi TO df_e2e_user' ); SET SESSION AUTHORIZATION df_e2e_user; CREATE TEMP TABLE _test_state (instance_id TEXT); INSERT INTO _test_state SELECT df.start( 'INSERT INTO test_multi VALUES (1, ''hello from multi-db'')', 'test-multi-db', '_test_multi_db' ); RESET SESSION AUTHORIZATION; DO $$ DECLARE inst_id TEXT; status TEXT; BEGIN SELECT instance_id INTO inst_id FROM _test_state; SELECT df.await_instance(inst_id) INTO status; IF status != 'completed' THEN RAISE EXCEPTION 'TEST FAILED [multi-db]: status = %', status; END IF; RAISE NOTICE 'PASSED: durable function completed in target database'; END $$; DO $$ DECLARE connstr TEXT; row_value TEXT; BEGIN connstr := format( 'host=localhost dbname=_test_multi_db port=%s user=postgres', current_setting('port') ); SELECT val INTO row_value FROM dblink(connstr, 'SELECT value FROM test_multi WHERE id = 1') AS t(val TEXT); IF row_value IS NULL OR row_value != 'hello from multi-db' THEN RAISE EXCEPTION 'TEST FAILED [multi-db verify]: expected ''hello from multi-db'', got %', row_value; END IF; RAISE NOTICE 'PASSED: verified row exists in target database: %', row_value; END $$; DO $$ DECLARE inst_id TEXT; inst_db TEXT; node_db TEXT; BEGIN SELECT instance_id INTO inst_id FROM _test_state; SELECT database INTO inst_db FROM df.instances WHERE id = inst_id; IF inst_db != '_test_multi_db' THEN RAISE EXCEPTION 'TEST FAILED [instance.database]: expected _test_multi_db, got %', inst_db; END IF; SELECT database INTO node_db FROM df.nodes WHERE instance_id = inst_id LIMIT 1; IF node_db != '_test_multi_db' THEN RAISE EXCEPTION 'TEST FAILED [node.database]: expected _test_multi_db, got %', node_db; END IF; RAISE NOTICE 'PASSED: database column correctly set on instance and nodes'; END $$; DROP TABLE _test_state; -- Test 2: Invalid database raises immediate error SET SESSION AUTHORIZATION df_e2e_user; DO $$ DECLARE err_msg TEXT; BEGIN BEGIN PERFORM df.start( 'SELECT 1', 'test-bad-db', 'nonexistent_database_abc' ); RAISE EXCEPTION 'TEST FAILED: df.start() should have errored for nonexistent database'; EXCEPTION WHEN OTHERS THEN err_msg := SQLERRM; END; IF err_msg NOT ILIKE '%nonexistent_database_abc%' THEN RAISE EXCEPTION 'TEST FAILED [invalid db]: expected error about nonexistent_database_abc, got: %', err_msg; END IF; IF err_msg NOT ILIKE '%does not exist%' THEN RAISE EXCEPTION 'TEST FAILED [invalid db]: expected "does not exist" in error, got: %', err_msg; END IF; RAISE NOTICE 'PASSED: invalid database correctly rejected: %', err_msg; END $$; RESET SESSION AUTHORIZATION; -- Test 3: Regression - df.start() without database still works SET SESSION AUTHORIZATION df_e2e_user; CREATE TEMP TABLE _test_state2 (instance_id TEXT); INSERT INTO _test_state2 SELECT df.start( 'SELECT 99 as answer', 'test-no-db' ); RESET SESSION AUTHORIZATION; DO $$ DECLARE inst_id TEXT; status TEXT; inst_db TEXT; BEGIN SELECT instance_id INTO inst_id FROM _test_state2; SELECT df.await_instance(inst_id) INTO status; IF status != 'completed' THEN RAISE EXCEPTION 'TEST FAILED [regression]: status = %', status; END IF; SELECT database INTO inst_db FROM df.instances WHERE id = inst_id; IF inst_db IS NOT NULL THEN RAISE EXCEPTION 'TEST FAILED [regression]: database should be NULL, got %', inst_db; END IF; RAISE NOTICE 'PASSED: regression test - df.start() without database works'; END $$; DROP TABLE _test_state2; -- Test 4: Multi-node sequence graph targeting another database SET SESSION AUTHORIZATION df_e2e_user; CREATE TEMP TABLE _test_state3 (instance_id TEXT); INSERT INTO _test_state3 SELECT df.start( 'INSERT INTO test_multi VALUES (10, ''step1'')' ~> 'UPDATE test_multi SET value = ''step2'' WHERE id = 10' ~> 'INSERT INTO test_multi VALUES (11, ''step3'')', 'test-multi-db-seq', '_test_multi_db' ); RESET SESSION AUTHORIZATION; DO $$ DECLARE inst_id TEXT; status TEXT; node_count INT; nodes_with_db INT; BEGIN SELECT instance_id INTO inst_id FROM _test_state3; SELECT df.await_instance(inst_id) INTO status; IF status != 'completed' THEN RAISE EXCEPTION 'TEST FAILED [multi-node seq]: status = %', status; END IF; SELECT COUNT(*), COUNT(database) INTO node_count, nodes_with_db FROM df.nodes WHERE instance_id = inst_id; IF node_count != nodes_with_db THEN RAISE EXCEPTION 'TEST FAILED [multi-node seq]: only %/% nodes have database set', nodes_with_db, node_count; END IF; RAISE NOTICE 'PASSED: multi-node sequence completed (% nodes, all with database)', node_count; END $$; DO $$ DECLARE connstr TEXT; row_val TEXT; BEGIN connstr := format( 'host=localhost dbname=_test_multi_db port=%s user=postgres', current_setting('port') ); SELECT val INTO row_val FROM dblink(connstr, 'SELECT value FROM test_multi WHERE id = 10') AS t(val TEXT); IF row_val != 'step2' THEN RAISE EXCEPTION 'TEST FAILED [multi-node seq verify]: id=10 expected step2, got %', row_val; END IF; SELECT val INTO row_val FROM dblink(connstr, 'SELECT value FROM test_multi WHERE id = 11') AS t(val TEXT); IF row_val != 'step3' THEN RAISE EXCEPTION 'TEST FAILED [multi-node seq verify]: id=11 expected step3, got %', row_val; END IF; RAISE NOTICE 'PASSED: multi-node sequence results verified in target database'; END $$; DROP TABLE _test_state3; -- Test 5: Database dropped after df.start() — deferred connection failure DROP DATABASE IF EXISTS _test_drop_db; CREATE DATABASE _test_drop_db; GRANT CONNECT ON DATABASE _test_drop_db TO df_e2e_user; SELECT dblink_exec( format('host=localhost dbname=_test_drop_db port=%s user=postgres', current_setting('port')), 'CREATE TABLE drop_test (id SERIAL, ts TIMESTAMP DEFAULT now())' ); SELECT dblink_exec( format('host=localhost dbname=_test_drop_db port=%s user=postgres', current_setting('port')), 'GRANT ALL ON drop_test TO df_e2e_user' ); SELECT dblink_exec( format('host=localhost dbname=_test_drop_db port=%s user=postgres', current_setting('port')), 'GRANT USAGE, SELECT ON SEQUENCE drop_test_id_seq TO df_e2e_user' ); SET SESSION AUTHORIZATION df_e2e_user; CREATE TEMP TABLE _test_state4 (instance_id TEXT); INSERT INTO _test_state4 SELECT df.start( df.loop( 'INSERT INTO drop_test (id) VALUES (DEFAULT)' ~> df.sleep(2) ), 'test-drop-db-loop', '_test_drop_db' ); RESET SESSION AUTHORIZATION; SELECT pg_sleep(3); SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = '_test_drop_db' AND pid != pg_backend_pid(); DROP DATABASE IF EXISTS _test_drop_db; DO $$ DECLARE inst_id TEXT; status TEXT; attempts INT := 0; BEGIN SELECT instance_id INTO inst_id FROM _test_state4; LOOP SELECT s INTO status FROM df.status(inst_id) s; EXIT WHEN lower(status) IN ('completed', 'failed', 'cancelled') OR attempts > 300; PERFORM pg_sleep(0.1); attempts := attempts + 1; END LOOP; IF lower(status) != 'failed' THEN RAISE EXCEPTION 'TEST FAILED [drop-db]: expected failed, got %', status; END IF; RAISE NOTICE 'PASSED: deferred connection failure produced clean failed status'; END $$; DROP TABLE _test_state4; -- Cleanup DROP DATABASE IF EXISTS _test_multi_db; SELECT 'TEST PASSED' AS result;