-- Copyright (c) Microsoft Corporation. -- Licensed under the PostgreSQL License. -- Merged from: 05_conditional_true, 06_conditional_false, 33_malformed_condition_node, 40_if_rows -- Tests: conditional true branch, conditional false branch, malformed condition_node rejection, -- df.if_rows() branching on result row presence SET SESSION AUTHORIZATION df_e2e_user; -- === Test: 05_conditional_true === DROP TABLE IF EXISTS test_cond_log; CREATE TABLE test_cond_log (id SERIAL, branch TEXT, variant TEXT); CREATE TEMP TABLE _test_state (instance_id TEXT, variant TEXT); -- Variant A: df.if() function INSERT INTO _test_state SELECT df.start( df.if( 'SELECT true', 'INSERT INTO test_cond_log (branch, variant) VALUES (''then'', ''func'')', 'INSERT INTO test_cond_log (branch, variant) VALUES (''else'', ''func'')' ), 'test-cond-true-func' ), 'func'; -- Variant B: ?> !> operators INSERT INTO _test_state SELECT df.start( 'SELECT true' ?> 'INSERT INTO test_cond_log (branch, variant) VALUES (''then'', ''op'')' !> 'INSERT INTO test_cond_log (branch, variant) VALUES (''else'', ''op'')', 'test-cond-true-op' ), 'op'; DO $$ DECLARE rec RECORD; status TEXT; branch_val TEXT; BEGIN FOR rec IN SELECT instance_id, variant FROM _test_state LOOP RAISE NOTICE 'Testing % variant: %', rec.variant, rec.instance_id; SELECT df.await_instance(rec.instance_id) INTO status; IF status != 'completed' THEN RAISE EXCEPTION 'TEST FAILED [%]: status = %', rec.variant, status; END IF; SELECT branch INTO branch_val FROM test_cond_log WHERE variant = rec.variant ORDER BY id DESC LIMIT 1; IF branch_val != 'then' THEN RAISE EXCEPTION 'TEST FAILED [%]: expected then branch, got %', rec.variant, branch_val; END IF; RAISE NOTICE 'PASSED: conditional_true [%]', rec.variant; END LOOP; RAISE NOTICE 'TEST PASSED: conditional_true (func + ?> !> operators)'; END $$; DROP TABLE _test_state; DROP TABLE test_cond_log; -- === Test: 06_conditional_false === DROP TABLE IF EXISTS test_cond_false_log; CREATE TABLE test_cond_false_log (id SERIAL, branch TEXT, variant TEXT); CREATE TEMP TABLE _test_state (instance_id TEXT, variant TEXT); -- Variant A: df.if() function INSERT INTO _test_state SELECT df.start( df.if( 'SELECT false', 'INSERT INTO test_cond_false_log (branch, variant) VALUES (''then'', ''func'')', 'INSERT INTO test_cond_false_log (branch, variant) VALUES (''else'', ''func'')' ), 'test-cond-false-func' ), 'func'; -- Variant B: ?> !> operators INSERT INTO _test_state SELECT df.start( 'SELECT false' ?> 'INSERT INTO test_cond_false_log (branch, variant) VALUES (''then'', ''op'')' !> 'INSERT INTO test_cond_false_log (branch, variant) VALUES (''else'', ''op'')', 'test-cond-false-op' ), 'op'; DO $$ DECLARE rec RECORD; status TEXT; branch_val TEXT; BEGIN FOR rec IN SELECT instance_id, variant FROM _test_state LOOP RAISE NOTICE 'Testing % variant: %', rec.variant, rec.instance_id; SELECT df.await_instance(rec.instance_id) INTO status; IF status != 'completed' THEN RAISE EXCEPTION 'TEST FAILED [%]: status = %', rec.variant, status; END IF; SELECT branch INTO branch_val FROM test_cond_false_log WHERE variant = rec.variant ORDER BY id DESC LIMIT 1; IF branch_val != 'else' THEN RAISE EXCEPTION 'TEST FAILED [%]: expected else branch, got %', rec.variant, branch_val; END IF; RAISE NOTICE 'PASSED: conditional_false [%]', rec.variant; END LOOP; RAISE NOTICE 'TEST PASSED: conditional_false (func + ?> !> operators)'; END $$; DROP TABLE _test_state; DROP TABLE test_cond_false_log; -- === Test: 33_malformed_condition_node === -- Test 1: condition_node as a non-Durofut object should be rejected by df.start() DO $body$ BEGIN BEGIN PERFORM df.start('{ "node_type": "IF", "left_node": {"node_type": "SQL", "query": "SELECT 1"}, "right_node": {"node_type": "SQL", "query": "SELECT 2"}, "condition_node": {"foo": "bar"} }'); RAISE EXCEPTION 'TEST FAILED: df.start should have rejected malformed condition_node'; EXCEPTION WHEN OTHERS THEN IF SQLERRM LIKE '%condition_node%' THEN RAISE NOTICE 'Test 1 PASSED: Caught malformed condition_node object: %', SQLERRM; ELSE RAISE EXCEPTION 'TEST FAILED: Wrong error for malformed condition_node: %', SQLERRM; END IF; END; END $body$; -- Test 2: condition_node as a string ID (old format) should be rejected DO $body$ BEGIN BEGIN PERFORM df.start('{ "node_type": "LOOP", "left_node": {"node_type": "SQL", "query": "SELECT 1"}, "condition_node": "a1b2c3d4" }'); RAISE EXCEPTION 'TEST FAILED: df.start should have rejected string condition_node'; EXCEPTION WHEN OTHERS THEN IF SQLERRM LIKE '%condition_node%' THEN RAISE NOTICE 'Test 2 PASSED: Caught string condition_node: %', SQLERRM; ELSE RAISE EXCEPTION 'TEST FAILED: Wrong error for string condition_node: %', SQLERRM; END IF; END; END $body$; -- Test 3: condition_node as a number should be rejected DO $body$ BEGIN BEGIN PERFORM df.start('{ "node_type": "IF", "left_node": {"node_type": "SQL", "query": "SELECT 1"}, "right_node": {"node_type": "SQL", "query": "SELECT 2"}, "condition_node": 42 }'); RAISE EXCEPTION 'TEST FAILED: df.start should have rejected numeric condition_node'; EXCEPTION WHEN OTHERS THEN IF SQLERRM LIKE '%condition_node%' THEN RAISE NOTICE 'Test 3 PASSED: Caught numeric condition_node: %', SQLERRM; ELSE RAISE EXCEPTION 'TEST FAILED: Wrong error for numeric condition_node: %', SQLERRM; END IF; END; END $body$; -- Test 4: Valid condition_node (produced by DSL) should work fine DO $body$ DECLARE graph TEXT; BEGIN SELECT df.if('SELECT true', 'SELECT 1', 'SELECT 2') INTO graph; IF graph IS NULL THEN RAISE EXCEPTION 'TEST FAILED: df.if should return non-null graph'; END IF; RAISE NOTICE 'Test 4 PASSED: Valid IF graph produced: %', left(graph, 80); END $body$; -- Test 5: condition_node embedded in the legacy query format should be rejected before persistence DO $body$ DECLARE nodes_before BIGINT; BEGIN SELECT count(*) INTO nodes_before FROM df.nodes; BEGIN PERFORM df.start('{ "node_type": "IF", "left_node": {"node_type": "SQL", "query": "SELECT 1"}, "right_node": {"node_type": "SQL", "query": "SELECT 2"}, "query": "{\"condition_node\":{\"node_type\":\"SQL\",\"query\":\"SELECT true\"}}" }'); RAISE EXCEPTION 'TEST FAILED: df.start should have rejected legacy condition_node format'; EXCEPTION WHEN OTHERS THEN IF SQLERRM LIKE '%condition_node in IF must be a first-class Durofut field%' THEN RAISE NOTICE 'Test 5 PASSED: Caught legacy condition_node format: %', SQLERRM; ELSE RAISE EXCEPTION 'TEST FAILED: Wrong error for legacy condition_node format: %', SQLERRM; END IF; END; IF (SELECT count(*) FROM df.nodes) != nodes_before THEN RAISE EXCEPTION 'TEST FAILED: legacy condition_node format persisted node rows'; END IF; END $body$; -- Test 6: extra_nodes string IDs from the old envelope format should be rejected DO $body$ BEGIN BEGIN PERFORM df.start('{ "node_type": "JOIN", "left_node": {"node_type": "SQL", "query": "SELECT 1"}, "right_node": {"node_type": "SQL", "query": "SELECT 2"}, "extra_nodes": ["a1b2c3d4"] }'); RAISE EXCEPTION 'TEST FAILED: df.start should have rejected string extra_nodes'; EXCEPTION WHEN OTHERS THEN IF SQLERRM LIKE '%extra_nodes entries must be Durofut JSON objects%' THEN RAISE NOTICE 'Test 6 PASSED: Caught string extra_nodes: %', SQLERRM; ELSE RAISE EXCEPTION 'TEST FAILED: Wrong error for string extra_nodes: %', SQLERRM; END IF; END; END $body$; -- Test 7: invalid config query shape should be rejected before persistence DO $body$ DECLARE nodes_before BIGINT; BEGIN SELECT count(*) INTO nodes_before FROM df.nodes; BEGIN PERFORM df.start('{ "node_type": "IF", "left_node": {"node_type": "SQL", "query": "SELECT 1"}, "right_node": {"node_type": "SQL", "query": "SELECT 2"}, "condition_node": {"node_type": "SQL", "query": "SELECT true"}, "query": "not-json" }'); RAISE EXCEPTION 'TEST FAILED: df.start should have rejected invalid config query'; EXCEPTION WHEN OTHERS THEN IF SQLERRM LIKE '%query in IF must be valid JSON config%' THEN RAISE NOTICE 'Test 7 PASSED: Caught invalid config query: %', SQLERRM; ELSE RAISE EXCEPTION 'TEST FAILED: Wrong error for invalid config query: %', SQLERRM; END IF; END; IF (SELECT count(*) FROM df.nodes) != nodes_before THEN RAISE EXCEPTION 'TEST FAILED: invalid config query persisted node rows'; END IF; END $body$; -- === Test: 40_if_rows === -- Test 1: if_rows with rows present → then branch executes DROP TABLE IF EXISTS test_if_rows_log; CREATE TABLE test_if_rows_log (id SERIAL, branch TEXT, variant TEXT); CREATE TEMP TABLE _test_state (instance_id TEXT, variant TEXT); INSERT INTO _test_state SELECT df.start( $$SELECT 1 AS val$$ |=> 'data' ~> df.if_rows( 'data', $$INSERT INTO test_if_rows_log (branch, variant) VALUES ('then', 'has_rows')$$, $$INSERT INTO test_if_rows_log (branch, variant) VALUES ('else', 'has_rows')$$ ), 'test-if-rows-has-rows' ), 'has_rows'; -- Test 2: if_rows with zero rows → else branch executes INSERT INTO _test_state SELECT df.start( $$SELECT 1 WHERE false$$ |=> 'empty' ~> df.if_rows( 'empty', $$INSERT INTO test_if_rows_log (branch, variant) VALUES ('then', 'no_rows')$$, $$INSERT INTO test_if_rows_log (branch, variant) VALUES ('else', 'no_rows')$$ ), 'test-if-rows-no-rows' ), 'no_rows'; DO $$ DECLARE rec RECORD; status TEXT; branch_val TEXT; expected_branch TEXT; BEGIN FOR rec IN SELECT instance_id, variant FROM _test_state LOOP RAISE NOTICE 'Testing % variant: %', rec.variant, rec.instance_id; SELECT df.await_instance(rec.instance_id) INTO status; IF status != 'completed' THEN RAISE EXCEPTION 'TEST FAILED [%]: status = %', rec.variant, status; END IF; IF rec.variant = 'has_rows' THEN expected_branch := 'then'; ELSE expected_branch := 'else'; END IF; SELECT branch INTO branch_val FROM test_if_rows_log WHERE variant = rec.variant ORDER BY id DESC LIMIT 1; IF branch_val != expected_branch THEN RAISE EXCEPTION 'TEST FAILED [%]: expected % branch, got %', rec.variant, expected_branch, branch_val; END IF; RAISE NOTICE 'PASSED: if_rows [%]', rec.variant; END LOOP; RAISE NOTICE 'TEST PASSED: if_rows (both variants)'; END $$; DROP TABLE _test_state; DROP TABLE test_if_rows_log; -- Test 3: if_rows combined with dot-notation in then branch DROP TABLE IF EXISTS test_if_rows_dot; CREATE TABLE test_if_rows_dot (id SERIAL, val INT); CREATE TEMP TABLE _test_state2 (instance_id TEXT); INSERT INTO _test_state2 SELECT df.start( $$SELECT 99 AS num$$ |=> 'result' ~> df.if_rows( 'result', $$INSERT INTO test_if_rows_dot (val) VALUES ($result.num)$$, $$INSERT INTO test_if_rows_dot (val) VALUES (-1)$$ ), 'test-if-rows-dot' ); DO $$ DECLARE inst_id TEXT; status TEXT; val_result INT; 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 [if_rows+dot]: status = %', status; END IF; SELECT val INTO val_result FROM test_if_rows_dot ORDER BY id DESC LIMIT 1; IF val_result != 99 THEN RAISE EXCEPTION 'TEST FAILED [if_rows+dot]: expected 99, got %', val_result; END IF; RAISE NOTICE 'PASSED: if_rows combined with dot-notation'; END $$; DROP TABLE _test_state2; DROP TABLE test_if_rows_dot; -- Test 4: named result on the IF node itself is accessible downstream DROP TABLE IF EXISTS test_if_named; CREATE TABLE test_if_named (id SERIAL, chosen INT); CREATE TEMP TABLE _test_if_named (instance_id TEXT); INSERT INTO _test_if_named SELECT df.start( df.if( 'SELECT true', 'SELECT 41 AS chosen', 'SELECT 99 AS chosen' ) |=> 'decision' ~> $$INSERT INTO test_if_named (chosen) VALUES ($decision.chosen)$$, 'test-if-named-result' ); DO $$ DECLARE inst_id TEXT; status TEXT; chosen_val INT; BEGIN SELECT instance_id INTO inst_id FROM _test_if_named; SELECT df.await_instance(inst_id, 10) INTO status; IF status != 'completed' THEN RAISE EXCEPTION 'TEST FAILED [if-named]: status = %', status; END IF; SELECT chosen INTO chosen_val FROM test_if_named ORDER BY id DESC LIMIT 1; IF chosen_val != 41 THEN RAISE EXCEPTION 'TEST FAILED [if-named]: expected 41, got %', chosen_val; END IF; RAISE NOTICE 'PASSED: named IF result stored'; END $$; DROP TABLE _test_if_named; DROP TABLE test_if_named; RESET SESSION AUTHORIZATION; SELECT 'TEST PASSED' AS result;