java-topology/defects/postgresql/unit/PostgresqlTest.java

274 lines
9.9 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
*
* Models 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<Integer> 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<Integer> 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<Integer> tlist = new ArrayList<>(T0 + E);
for (int i = 0; i < T0; i++) tlist.add(i);
List<Integer> exprs = new ArrayList<>(E);
for (int i = T0; i < T0 + E; i++) exprs.add(i);
long ops = 0;
// Seed seen-set: O(T0)
Set<Integer> 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<Integer> targetExprs = new ArrayList<>(T0 + E);
for (int i = 0; i < T0; i++) targetExprs.add(i);
// exprs to add: E new unique items
List<Integer> 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<Integer> targetExprs = new ArrayList<>(T0 + E);
for (int i = 0; i < T0; i++) targetExprs.add(i);
List<Integer> 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<Integer> 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;
}
// -----------------------------------------------------------------------
// Main
// -----------------------------------------------------------------------
public static void main(String[] args) {
System.out.println("postgresql CWE-407 benchmarks (postgresql-0006, postgresql-0007)");
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++;
}
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);
}
}