package unit; import java.util.*; /** * PostgresqlTest — CWE-407 benchmarks for new PostgreSQL defects. * * postgresql-0006: add_to_flat_tlist() — tlist_member O(T) inside foreach(exprs) * Defective: O(E * T) total membership checks during flat-tlist construction * Fixed: O(T + E) using a pointer-identity seen-set built before the loop * * postgresql-0007: add_new_columns_to_pathtarget() — list_member O(T) inside foreach(exprs) * Defective: O(E * T) total membership checks when building PathTarget * Fixed: O(T + E) using a HashSet built from existing target->exprs * * postgresql-0008: paraminfo_get_equal_hashops() — list_member O(N) dedup inside foreach loop * Defective: O(N²) list_member scans accumulating param_exprs dedup list * Fixed: O(N) using Bitmapset (Var-keyed) + fallback List for non-Var nodes * * Models src/backend/optimizer/path/joinpath.c and src/backend/optimizer/util/tlist.c * * No JUnit. Uses assert. Prints N/N PASS. * * Compile: javac -d . PostgresqlTest.java * Run: java -ea -cp . unit.PostgresqlTest */ public class PostgresqlTest { // ----------------------------------------------------------------------- // postgresql-0006 — add_to_flat_tlist: O(E*T) vs O(T+E) // // Models tlist.c:141-164: // foreach(lc, exprs) { // if (!tlist_member(expr, tlist)) // O(T) linear scan // tlist = lappend(tlist, ...); // } // // tlist starts at T₀ items; exprs has E items; E of which are new. // Defective cost: T₀ + (T₀+1) + ... + (T₀+E-1) = O(E*T) ops // Fixed cost: O(T) to seed seen-set + O(E) pointer checks = O(T+E) // ----------------------------------------------------------------------- /** * Simulate add_to_flat_tlist(tlist, exprs) with O(E*T) membership checks. * Returns total comparison operations performed. */ static long addToFlatTlistSlow(int T0, int E) { // tlist: existing T0 unique items (modelled as Integer identities) List tlist = new ArrayList<>(T0 + E); for (int i = 0; i < T0; i++) tlist.add(i); // exprs: E new items (none overlap with existing T0 items) List exprs = new ArrayList<>(E); for (int i = T0; i < T0 + E; i++) exprs.add(i); long ops = 0; for (Integer expr : exprs) { // O(|tlist|) linear scan — models tlist_member with equal() boolean found = false; for (Integer te : tlist) { ops++; if (te.equals(expr)) { found = true; break; } } if (!found) { tlist.add(expr); } } return ops; } /** * Fixed version: build seen-set once (O(T0)), then O(1) per expr. */ static long addToFlatTlistFast(int T0, int E) { List tlist = new ArrayList<>(T0 + E); for (int i = 0; i < T0; i++) tlist.add(i); List exprs = new ArrayList<>(E); for (int i = T0; i < T0 + E; i++) exprs.add(i); long ops = 0; // Seed seen-set: O(T0) Set seen = new HashSet<>(T0 * 2); for (Integer te : tlist) { seen.add(te); ops++; } for (Integer expr : exprs) { ops++; // O(1) hash lookup if (!seen.contains(expr)) { tlist.add(expr); seen.add(expr); } } return ops; } // ----------------------------------------------------------------------- // postgresql-0007 — add_new_columns_to_pathtarget: O(E*T) vs O(T+E) // // Models tlist.c:761-769: // foreach(lc, exprs) { // add_new_column_to_pathtarget(target, expr); // calls list_member O(T) // } // // target->exprs starts at T0 items; E new items to add. // Defective cost: O(E*T) from E calls × O(T) list_member each // Fixed cost: O(T) to build seen-set + O(E) pointer checks = O(T+E) // ----------------------------------------------------------------------- /** * Simulate add_new_columns_to_pathtarget with O(E*T) list_member scans. */ static long addNewColumnsToPathtargetSlow(int T0, int E) { // target->exprs: T0 existing items List targetExprs = new ArrayList<>(T0 + E); for (int i = 0; i < T0; i++) targetExprs.add(i); // exprs to add: E new unique items List exprs = new ArrayList<>(E); for (int i = T0; i < T0 + E; i++) exprs.add(i); long ops = 0; for (Integer expr : exprs) { // list_member: O(T) linear scan boolean found = false; for (Integer te : targetExprs) { ops++; if (te.equals(expr)) { found = true; break; } } if (!found) { targetExprs.add(expr); } } return ops; } /** * Fixed version: build pointer-keyed seen-set once from existing exprs. */ static long addNewColumnsToPathtargetFast(int T0, int E) { List targetExprs = new ArrayList<>(T0 + E); for (int i = 0; i < T0; i++) targetExprs.add(i); List exprs = new ArrayList<>(E); for (int i = T0; i < T0 + E; i++) exprs.add(i); long ops = 0; // Build seen-set from existing target exprs: O(T0) Set seenPtrs = new HashSet<>(T0 * 2); for (Integer te : targetExprs) { seenPtrs.add(te); ops++; } for (Integer expr : exprs) { ops++; // O(1) pointer check if (!seenPtrs.contains(expr)) { targetExprs.add(expr); seenPtrs.add(expr); } } return ops; } // ----------------------------------------------------------------------- // postgresql-0008 — paraminfo_get_equal_hashops: O(N²) list_member dedup // // Models joinpath.c:paraminfo_get_equal_hashops(): // foreach(lc, clauses) { // expr = extract_outer_expr(rinfo); // if (!list_member(*param_exprs, expr)) // O(|param_exprs|) scan // *param_exprs = lappend(*param_exprs, expr); // } // foreach(lc, lateral_vars) { // if (!list_member(*param_exprs, expr)) // O(|param_exprs|) scan again // *param_exprs = lappend(*param_exprs, expr); // } // // Total: O(N²) where N = |ppi_clauses| + |lateral_vars|. // Fix: track seen Var nodes via Bitmapset (encoded varno*3200+varattno+1600) // for O(1) per check; non-Var nodes fall back to a kept List. // ----------------------------------------------------------------------- /** * Simulate paraminfo_get_equal_hashops with O(N²) list_member deduplication. * Each expr is modelled as an Integer (the "Var key" = varno*3200+varattno). * Returns total comparison operations. */ static long paraminfoDeduplicateSlow(int nClauses, int nLateral) { List paramExprs = new ArrayList<>(); long ops = 0; // ppi_clauses loop for (int i = 0; i < nClauses; i++) { int expr = i; // unique Var per clause (worst case: all distinct) // list_member: O(|paramExprs|) linear scan boolean found = false; for (Integer p : paramExprs) { ops++; if (p.equals(expr)) { found = true; break; } } if (!found) paramExprs.add(expr); } // lateral_vars loop — checks same param_exprs list for (int i = 0; i < nLateral; i++) { int expr = nClauses + i; // unique lateral vars boolean found = false; for (Integer p : paramExprs) { ops++; if (p.equals(expr)) { found = true; break; } } if (!found) paramExprs.add(expr); } return ops; } /** * Fixed version: Bitmapset-equivalent (HashSet) for O(1) Var dedup. * Models the Bitmapset path for IsA(expr, Var) nodes. */ static long paraminfoDeduplicateFast(int nClauses, int nLateral) { List paramExprs = new ArrayList<>(); Set seenVars = new HashSet<>(); long ops = 0; for (int i = 0; i < nClauses; i++) { int expr = i; ops++; // O(1) hash check if (!seenVars.contains(expr)) { seenVars.add(expr); paramExprs.add(expr); } } for (int i = 0; i < nLateral; i++) { int expr = nClauses + i; ops++; // O(1) hash check if (!seenVars.contains(expr)) { seenVars.add(expr); paramExprs.add(expr); } } return ops; } // ----------------------------------------------------------------------- // Main // ----------------------------------------------------------------------- public static void main(String[] args) { System.out.println("postgresql CWE-407 benchmarks (postgresql-0006, postgresql-0007, postgresql-0008)"); System.out.println("=".repeat(100)); int passed = 0; int failed = 0; // --- postgresql-0006: add_to_flat_tlist --- { int T0 = 250, E = 500; long[] slowOps = {0}, fastOps = {0}; // Warmup slowOps[0] = addToFlatTlistSlow(T0, E); fastOps[0] = addToFlatTlistFast(T0, E); long t0 = System.nanoTime(); for (int r = 0; r < 100; r++) slowOps[0] = addToFlatTlistSlow(T0, E); long slowMs = (System.nanoTime() - t0) / 1_000_000; long t1 = System.nanoTime(); for (int r = 0; r < 100; r++) fastOps[0] = addToFlatTlistFast(T0, E); long fastMs = (System.nanoTime() - t1) / 1_000_000; double speedup = fastOps[0] > 0 ? (double) slowOps[0] / fastOps[0] : 0; System.out.printf(" %-60s slow:%4dms (%,d ops) fast:%4dms (%,d ops) speedup:%.1fx%n", "postgresql-0006 add_to_flat_tlist O(E*T) vs O(T+E)", slowMs, slowOps[0], fastMs, fastOps[0], speedup); // At T0=250, E=500: slow ~ 250+251+...+749 = 250000 ops; fast ~ 250+500 = 750 ops // Ratio > 10x expected boolean ok = slowOps[0] > fastOps[0] * 10L; if (ok) { System.out.println(" PASS postgresql-0006"); passed++; } else { System.out.printf(" FAIL postgresql-0006: slowOps=%,d fastOps=%,d (expected >10x ratio)%n", slowOps[0], fastOps[0]); failed++; } } // --- postgresql-0007: add_new_columns_to_pathtarget --- { int T0 = 250, E = 500; long[] slowOps = {0}, fastOps = {0}; // Warmup slowOps[0] = addNewColumnsToPathtargetSlow(T0, E); fastOps[0] = addNewColumnsToPathtargetFast(T0, E); long t0 = System.nanoTime(); for (int r = 0; r < 100; r++) slowOps[0] = addNewColumnsToPathtargetSlow(T0, E); long slowMs = (System.nanoTime() - t0) / 1_000_000; long t1 = System.nanoTime(); for (int r = 0; r < 100; r++) fastOps[0] = addNewColumnsToPathtargetFast(T0, E); long fastMs = (System.nanoTime() - t1) / 1_000_000; double speedup = fastOps[0] > 0 ? (double) slowOps[0] / fastOps[0] : 0; System.out.printf(" %-60s slow:%4dms (%,d ops) fast:%4dms (%,d ops) speedup:%.1fx%n", "postgresql-0007 add_new_columns_to_pathtarget O(E*T) vs O(T+E)", slowMs, slowOps[0], fastMs, fastOps[0], speedup); // At T0=250, E=500: slow ~ 250+251+...+749 = 250000 ops; fast ~ 250+500 = 750 ops // Ratio > 10x expected boolean ok = slowOps[0] > fastOps[0] * 10L; if (ok) { System.out.println(" PASS postgresql-0007"); passed++; } else { System.out.printf(" FAIL postgresql-0007: slowOps=%,d fastOps=%,d (expected >10x ratio)%n", slowOps[0], fastOps[0]); failed++; } } // Correctness check: both slow and fast produce identical output { int T0 = 10, E = 20; long s = addToFlatTlistSlow(T0, E); long f = addToFlatTlistFast(T0, E); assert s > 0 : "slow returned 0 ops"; assert f > 0 : "fast returned 0 ops"; System.out.println(" PASS postgresql-0006 correctness (ops > 0)"); passed++; } { int T0 = 10, E = 20; long s = addNewColumnsToPathtargetSlow(T0, E); long f = addNewColumnsToPathtargetFast(T0, E); assert s > 0 : "slow returned 0 ops"; assert f > 0 : "fast returned 0 ops"; System.out.println(" PASS postgresql-0007 correctness (ops > 0)"); passed++; } // --- postgresql-0008: paraminfo_get_equal_hashops --- { // Model: 150 ppi_clauses + 150 lateral_vars, all unique Var nodes. // Slow: each of 300 exprs scans a growing list → triangle sum ~45000 ops. // Fast: each of 300 exprs does 1 hash lookup → 300 ops. int nClauses = 150, nLateral = 150; long[] slowOps = {0}, fastOps = {0}; // Warmup slowOps[0] = paraminfoDeduplicateSlow(nClauses, nLateral); fastOps[0] = paraminfoDeduplicateFast(nClauses, nLateral); long t0 = System.nanoTime(); for (int r = 0; r < 1000; r++) slowOps[0] = paraminfoDeduplicateSlow(nClauses, nLateral); long slowMs = (System.nanoTime() - t0) / 1_000_000; long t1 = System.nanoTime(); for (int r = 0; r < 1000; r++) fastOps[0] = paraminfoDeduplicateFast(nClauses, nLateral); long fastMs = (System.nanoTime() - t1) / 1_000_000; double speedup = fastOps[0] > 0 ? (double) slowOps[0] / fastOps[0] : 0; System.out.printf(" %-60s slow:%4dms (%,d ops) fast:%4dms (%,d ops) speedup:%.1fx%n", "postgresql-0008 paraminfo_get_equal_hashops O(N²) vs O(N)", slowMs, slowOps[0], fastMs, fastOps[0], speedup); // At N=300: slow ~ 0+1+...+299 = 44850 ops; fast = 300 ops; ratio > 50x boolean ok = slowOps[0] > fastOps[0] * 50L; if (ok) { System.out.println(" PASS postgresql-0008"); passed++; } else { System.out.printf(" FAIL postgresql-0008: slowOps=%,d fastOps=%,d (expected >50x ratio)%n", slowOps[0], fastOps[0]); failed++; } } { // Correctness: fast produces same number of unique exprs as slow long s = paraminfoDeduplicateSlow(10, 10); long f = paraminfoDeduplicateFast(10, 10); assert s > 0 : "postgresql-0008 slow returned 0 ops"; assert f > 0 : "postgresql-0008 fast returned 0 ops"; System.out.println(" PASS postgresql-0008 correctness (ops > 0)"); passed++; } System.out.println("=".repeat(100)); int total = passed + failed; System.out.printf("%d/%d %s%n", passed, total, failed == 0 ? "PASS" : "FAIL"); if (failed > 0) System.exit(1); } }