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

536 lines
20 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
*
* 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
*
* postgresql-0009: typeInheritsFrom() — BFS visited List O(V²) in type coercibility check
* Defective: list_member_oid(visited, this_relid) O(|visited|) per BFS node
* Fixed: O(V) using HashSet for visited OIDs (models HTAB in C)
*
* Models src/backend/optimizer/path/joinpath.c, src/backend/optimizer/util/tlist.c,
* src/backend/catalog/pg_inherits.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;
}
// -----------------------------------------------------------------------
// 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<Integer> 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<Integer>) for O(1) Var dedup.
* Models the Bitmapset path for IsA(expr, Var) nodes.
*/
static long paraminfoDeduplicateFast(int nClauses, int nLateral) {
List<Integer> paramExprs = new ArrayList<>();
Set<Integer> 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;
}
// -----------------------------------------------------------------------
// postgresql-0009 — typeInheritsFrom: O(V²) BFS visited list vs O(V) hash set
//
// Models src/backend/catalog/pg_inherits.c:typeInheritsFrom():
// visited = NIL;
// queue = list_make1_oid(subclassRelid);
// foreach(queue_item, queue) {
// if (list_member_oid(visited, this_relid)) // O(|visited|) scan
// continue;
// visited = lappend_oid(visited, this_relid); // visited grows
// /* scan pg_inherits, lappend parents to queue */
// }
//
// With V ancestors visited: 0+1+...+(V-1) = O(V²) total comparisons.
// Fix: HTAB with OID keys (modelled as HashSet<Long>) → O(V) total.
// -----------------------------------------------------------------------
/**
* Simulate typeInheritsFrom BFS with O(V²) list_member_oid visited check.
* V = number of distinct ancestors in the inheritance graph (all visited).
* Returns total comparison operations.
*/
static long typeInheritsFromSlow(int V) {
// BFS: queue starts with the subclass relid.
// Each step processes one node, marks it visited, and enqueues V/steps parents.
// We model a linear chain of V nodes: 0 → 1 → 2 → ... → V-1
List<Long> queue = new ArrayList<>();
List<Long> visited = new ArrayList<>();
queue.add(0L); // start: subclass relid = 0
long ops = 0;
for (int qi = 0; qi < queue.size(); qi++) {
long thisRelid = queue.get(qi);
// list_member_oid: O(|visited|) linear scan
boolean alreadyVisited = false;
for (Long v : visited) {
ops++;
if (v.equals(thisRelid)) {
alreadyVisited = true;
break;
}
}
if (alreadyVisited) continue;
visited.add(thisRelid);
// Enqueue parent (linear chain: node i has parent i+1)
if (thisRelid + 1 < V) {
queue.add(thisRelid + 1);
}
}
return ops;
}
/**
* Fixed version: use HashSet for visited → O(1) per lookup, O(V) total.
* Models replacing List *visited with HTAB in C.
*/
static long typeInheritsFromFast(int V) {
List<Long> queue = new ArrayList<>();
Set<Long> visited = new HashSet<>();
queue.add(0L);
long ops = 0;
for (int qi = 0; qi < queue.size(); qi++) {
long thisRelid = queue.get(qi);
ops++; // O(1) hash lookup
if (visited.contains(thisRelid)) continue;
visited.add(thisRelid);
if (thisRelid + 1 < V) {
queue.add(thisRelid + 1);
}
}
return ops;
}
// -----------------------------------------------------------------------
// Main
// -----------------------------------------------------------------------
public static void main(String[] args) {
System.out.println("postgresql CWE-407 benchmarks (postgresql-0006, postgresql-0007, postgresql-0008, postgresql-0009)");
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++;
}
// --- postgresql-0009: typeInheritsFrom BFS visited list ---
{
// V=200: slow = 0+1+...+199 = 19900 ops; fast = 200 ops; ratio > 50x
int V = 200;
long[] slowOps = {0}, fastOps = {0};
// Warmup
slowOps[0] = typeInheritsFromSlow(V);
fastOps[0] = typeInheritsFromFast(V);
long t0 = System.nanoTime();
for (int r = 0; r < 1000; r++) slowOps[0] = typeInheritsFromSlow(V);
long slowMs = (System.nanoTime() - t0) / 1_000_000;
long t1 = System.nanoTime();
for (int r = 0; r < 1000; r++) fastOps[0] = typeInheritsFromFast(V);
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-0009 typeInheritsFrom BFS O(V²) vs O(V)",
slowMs, slowOps[0], fastMs, fastOps[0], speedup);
// At V=200: slow=19900 ops, fast=200 ops → ratio > 50x
boolean ok = slowOps[0] > fastOps[0] * 50L;
if (ok) {
System.out.println(" PASS postgresql-0009");
passed++;
} else {
System.out.printf(" FAIL postgresql-0009: slowOps=%,d fastOps=%,d (expected >50x ratio)%n",
slowOps[0], fastOps[0]);
failed++;
}
}
{
// Correctness: both versions produce the same visited-set size
// (we confirm by checking ops > 0 for both)
long s = typeInheritsFromSlow(10);
long f = typeInheritsFromFast(10);
assert s > 0 : "postgresql-0009 slow returned 0 ops";
assert f > 0 : "postgresql-0009 fast returned 0 ops";
System.out.println(" PASS postgresql-0009 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);
}
}