Authors: russell@unturf.com · brackishbert@gmail.com · foxhop.net · TimeHexOn.com Patches, unit tests, benchmarks, whitepaper, and outreach briefs. Public domain — no copyright claimed. Use freely.
334 lines
12 KiB
PL/PgSQL
334 lines
12 KiB
PL/PgSQL
-- 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.'
|