-- postgresql-0002-0004.sql — Functional tests for CWE-407 fixes -- -- Tests correctness of the three postgresql defect fixes: -- 0002 preptlist.c:180,206,316 tlist_member in MERGE/RETURNING var collection -- 0003 equivclass.c:1041 list_member in equivalence-class EC matching -- 0004 analyzejoins.c:1914 list_member in join-elimination reltarget merge -- -- Run with: psql -f postgresql-0002-0004.sql -- or: psql -v ON_ERROR_STOP=1 -f postgresql-0002-0004.sql -- -- All tests are idempotent. Schema is dropped and recreated at the top. -- Requires PostgreSQL 15+ (MERGE support for 0002 tests). \set ON_ERROR_STOP 1 BEGIN; -- ── Setup ──────────────────────────────────────────────────────────────────── DROP SCHEMA IF EXISTS cwe407_test CASCADE; CREATE SCHEMA cwe407_test; SET search_path TO cwe407_test; -- ═══════════════════════════════════════════════════════════════════════════ -- 0002 — preptlist.c: MERGE/UPDATE/RETURNING Var dedup -- -- Tests that MERGE, UPDATE...RETURNING, and multi-source queries produce -- correct results with many columns (exercising the tlist dedup code path). -- ═══════════════════════════════════════════════════════════════════════════ -- Wide target table (many columns to stress the tlist_member dedup loop) CREATE TABLE target_wide ( id int PRIMARY KEY, c01 int, c02 int, c03 int, c04 int, c05 int, c06 int, c07 int, c08 int, c09 int, c10 int, c11 int, c12 int, c13 int, c14 int, c15 int, c16 int, c17 int, c18 int, c19 int, c20 int ); CREATE TABLE source_wide ( id int PRIMARY KEY, c01 int, c02 int, c03 int, c04 int, c05 int, c06 int, c07 int, c08 int, c09 int, c10 int, c11 int, c12 int, c13 int, c14 int, c15 int, c16 int, c17 int, c18 int, c19 int, c20 int ); INSERT INTO target_wide SELECT g, g*1, g*2, g*3, g*4, g*5, g*6, g*7, g*8, g*9, g*10, g*11, g*12, g*13, g*14, g*15, g*16, g*17, g*18, g*19, g*20 FROM generate_series(1, 100) g; INSERT INTO source_wide SELECT g, g*10, g*20, g*30, g*40, g*50, g*60, g*70, g*80, g*90, g*100, g*110, g*120, g*130, g*140, g*150, g*160, g*170, g*180, g*190, g*200 FROM generate_series(51, 150) g; -- Test 0002-A: MERGE with UPDATE action referencing all columns -- Exercises preptlist.c:180 (action targetList Var dedup) MERGE INTO target_wide t USING source_wide s ON t.id = s.id WHEN MATCHED THEN UPDATE SET c01 = s.c01, c02 = s.c02, c03 = s.c03, c04 = s.c04, c05 = s.c05, c06 = s.c06, c07 = s.c07, c08 = s.c08, c09 = s.c09, c10 = s.c10, c11 = s.c11, c12 = s.c12, c13 = s.c13, c14 = s.c14, c15 = s.c15, c16 = s.c16, c17 = s.c17, c18 = s.c18, c19 = s.c19, c20 = s.c20 WHEN NOT MATCHED THEN INSERT (id, c01,c02,c03,c04,c05,c06,c07,c08,c09,c10, c11,c12,c13,c14,c15,c16,c17,c18,c19,c20) VALUES (s.id, s.c01,s.c02,s.c03,s.c04,s.c05, s.c06,s.c07,s.c08,s.c09,s.c10, s.c11,s.c12,s.c13,s.c14,s.c15, s.c16,s.c17,s.c18,s.c19,s.c20); -- Verify: matched rows (51..100) updated to source values; new rows (101..150) inserted DO $$ DECLARE matched_ok bool; inserted_ok bool; BEGIN SELECT bool_and(c01 = id * 10) INTO matched_ok FROM target_wide WHERE id BETWEEN 51 AND 100; SELECT bool_and(c01 = id * 10) INTO inserted_ok FROM target_wide WHERE id BETWEEN 101 AND 150; IF NOT matched_ok THEN RAISE EXCEPTION '0002-A: MERGE UPDATE produced wrong column values'; END IF; IF NOT inserted_ok THEN RAISE EXCEPTION '0002-A: MERGE INSERT produced wrong column values'; END IF; END; $$; \echo '0002-A PASS: MERGE with wide table — correct values after MERGE' -- Test 0002-B: MERGE with join condition Var dedup -- Exercises preptlist.c:206 (mergeJoinCondition Var dedup) CREATE TABLE join_source ( id int, extra1 int, extra2 int, extra3 int ); INSERT INTO join_source VALUES (1,10,20,30),(2,11,21,31),(999,99,99,99); CREATE TABLE join_target ( id int PRIMARY KEY, val int ); INSERT INTO join_target VALUES (1,0),(2,0); MERGE INTO join_target t USING join_source s ON t.id = s.id AND s.extra1 > 5 AND s.extra2 > 10 WHEN MATCHED THEN UPDATE SET val = s.extra3; DO $$ BEGIN IF (SELECT val FROM join_target WHERE id=1) <> 30 THEN RAISE EXCEPTION '0002-B: MERGE join condition Var not resolved correctly'; END IF; IF (SELECT val FROM join_target WHERE id=2) <> 31 THEN RAISE EXCEPTION '0002-B: MERGE join condition Var not resolved correctly for id=2'; END IF; END; $$; \echo '0002-B PASS: MERGE with join condition Vars — correct update' -- Test 0002-C: UPDATE...RETURNING with many columns -- Exercises preptlist.c:316 (RETURNING Var dedup) CREATE TABLE ret_tgt (id int PRIMARY KEY, a int, b int, c int, d int, e int); CREATE TABLE ret_src (id int PRIMARY KEY, a int, b int, c int, d int, e int); INSERT INTO ret_tgt VALUES (1,1,1,1,1,1),(2,2,2,2,2,2),(3,3,3,3,3,3); INSERT INTO ret_src VALUES (1,10,20,30,40,50),(2,11,21,31,41,51); UPDATE ret_tgt t SET a=s.a, b=s.b, c=s.c, d=s.d, e=s.e FROM ret_src s WHERE t.id = s.id RETURNING t.id, t.a, t.b, t.c, t.d, t.e, s.a AS src_a; DO $$ DECLARE rows int; BEGIN SELECT count(*) INTO rows FROM ( UPDATE cwe407_test.ret_tgt t SET a=s.a, b=s.b, c=s.c, d=s.d, e=s.e FROM cwe407_test.ret_src s WHERE t.id = s.id RETURNING t.id, t.a, s.a AS src_a ) sub WHERE sub.a = sub.src_a; IF rows <> 2 THEN RAISE EXCEPTION '0002-C: RETURNING produced unexpected row count: %', rows; END IF; END; $$; \echo '0002-C PASS: UPDATE...RETURNING with multi-table Vars — correct output' -- ═══════════════════════════════════════════════════════════════════════════ -- 0003 — equivclass.c: EC Var membership check -- -- Tests that query plans using equivalence classes for ORDER BY / sort -- produce correct results. Many equivalence-class members stress the -- list_member loop at equivclass.c:1041. -- ═══════════════════════════════════════════════════════════════════════════ CREATE TABLE ec_t1 (a int, b int, c int); CREATE TABLE ec_t2 (a int, b int, d int); CREATE TABLE ec_t3 (a int, b int, e int); INSERT INTO ec_t1 SELECT g, g*2, g*3 FROM generate_series(1,50) g; INSERT INTO ec_t2 SELECT g, g*2, g*4 FROM generate_series(1,50) g; INSERT INTO ec_t3 SELECT g, g*2, g*5 FROM generate_series(1,50) g; CREATE INDEX ON ec_t1(a,b); CREATE INDEX ON ec_t2(a,b); CREATE INDEX ON ec_t3(a,b); -- Test 0003-A: Three-way join with EC sort — exercises find_em_expr_for_rel -- The join creates equivalence classes {t1.a, t2.a, t3.a} and {t1.b, t2.b, t3.b}. -- The ORDER BY on t1.a + c forces EC member lookup to check Var availability. DO $$ DECLARE cnt int; prev_ab int := 0; ok bool := true; BEGIN FOR cnt IN SELECT t1.a + t1.b FROM ec_t1 t1 JOIN ec_t2 t2 ON t1.a = t2.a AND t1.b = t2.b JOIN ec_t3 t3 ON t1.a = t3.a AND t1.b = t3.b WHERE t1.c > 0 ORDER BY t1.a, t1.b LOOP IF cnt < prev_ab THEN ok := false; EXIT; END IF; prev_ab := cnt; END LOOP; IF NOT ok THEN RAISE EXCEPTION '0003-A: EC member sort produced out-of-order results'; END IF; END; $$; \echo '0003-A PASS: Three-way EC join with ORDER BY — correct sorted results' -- Test 0003-B: EC with expressions — forces non-Var EC member path DO $$ DECLARE cnt int; BEGIN SELECT count(*) INTO cnt FROM ( SELECT t1.a, t1.b, t1.a + t1.b AS sum_ab FROM ec_t1 t1 JOIN ec_t2 t2 ON t1.a = t2.a WHERE t1.a + t1.b > 10 ORDER BY t1.a + t1.b ) sub WHERE sum_ab <= 10; IF cnt > 0 THEN RAISE EXCEPTION '0003-B: EC expression filter produced rows outside range'; END IF; END; $$; \echo '0003-B PASS: EC with expression members — correct filter results' -- ═══════════════════════════════════════════════════════════════════════════ -- 0004 — analyzejoins.c: join-elimination reltarget merge -- -- Tests that self-join elimination produces correct query results. -- remove_useless_joins() calls remove_self_join_rel() when a join can be -- eliminated; the patched code merges reltarget->exprs. -- ═══════════════════════════════════════════════════════════════════════════ CREATE TABLE je_t ( id int PRIMARY KEY, k int NOT NULL, v int, w int, x int ); CREATE UNIQUE INDEX ON je_t(k); INSERT INTO je_t SELECT g, g, g*2, g*3, g*4 FROM generate_series(1,100) g; -- Test 0004-A: Self-join on unique key — should be eliminated -- After elimination, toRemove's exprs must be correctly merged into toKeep. DO $$ DECLARE direct_cnt int; selfjoin_cnt int; BEGIN SELECT count(*) INTO direct_cnt FROM je_t WHERE k > 50; SELECT count(*) INTO selfjoin_cnt FROM je_t t1 JOIN je_t t2 ON t1.k = t2.k -- eliminatable: unique key join WHERE t1.k > 50; IF direct_cnt <> selfjoin_cnt THEN RAISE EXCEPTION '0004-A: self-join elimination changed row count: direct=% selfjoin=%', direct_cnt, selfjoin_cnt; END IF; END; $$; \echo '0004-A PASS: Self-join elimination — row count correct' -- Test 0004-B: Self-join with projection of columns from both sides -- After elimination, columns from the eliminated side must still be available. DO $$ DECLARE mismatched int; BEGIN SELECT count(*) INTO mismatched FROM ( SELECT t1.v AS v1, t2.w AS w2, t1.x AS x1, t2.v AS v2 FROM je_t t1 JOIN je_t t2 ON t1.k = t2.k WHERE t1.id BETWEEN 1 AND 20 ) sub WHERE v1 <> v2 OR x1 <> id * 4; IF mismatched > 0 THEN RAISE EXCEPTION '0004-B: self-join elimination produced wrong column values: % rows mismatched', mismatched; END IF; END; $$; \echo '0004-B PASS: Self-join column projection — correct values after elimination' -- Test 0004-C: Self-join with WHERE clause referencing both sides DO $$ DECLARE cnt int; BEGIN SELECT count(*) INTO cnt FROM je_t t1 JOIN je_t t2 ON t1.k = t2.k WHERE t1.v > 100 AND t2.w > 150 ORDER BY t1.id; DECLARE direct_cnt int; SELECT count(*) INTO direct_cnt FROM je_t WHERE v > 100 AND w > 150; IF cnt <> direct_cnt THEN RAISE EXCEPTION '0004-C: self-join WHERE filter mismatch: eliminated=% direct=%', cnt, direct_cnt; END IF; END; $$; \echo '0004-C PASS: Self-join with dual-side WHERE — correct filter after elimination' -- ── Teardown ───────────────────────────────────────────────────────────────── ROLLBACK; \echo '' \echo 'All postgresql-0002/0003/0004 functional tests PASSED.'