java-topology/defects/proxysql/test/ProxySQLFTSIndexedColsTest.java

92 lines
3.5 KiB
Java

import java.util.*;
/**
* CWE-407 unit test for proxysql-0002: MySQL_FTS indexed_cols O(R*C*I) linear scan.
*
* Simulates ProxySQL's MySQL_FTS::index_table() inner loop:
* for each of R rows, for each of C columns, std::find on indexed_cols vector of I entries.
* Fix: replace vector<string> with unordered_set<string> for O(1) membership test.
*/
public class ProxySQLFTSIndexedColsTest {
// --- DEFECTIVE: vector linear scan per column per row ---
static long ftsIndexSlow(int rows, int cols, List<String> indexedCols) {
long ops = 0;
for (int r = 0; r < rows; r++) {
for (int c = 0; c < cols; c++) {
String colName = "col_" + c;
// std::find equivalent — O(I) scan
for (String ic : indexedCols) {
ops++;
if (ic.equals(colName)) break;
}
}
}
return ops;
}
// --- PATCHED: unordered_set O(1) lookup per column per row ---
static long ftsIndexFast(int rows, int cols, Set<String> indexedColsSet) {
long ops = 0;
for (int r = 0; r < rows; r++) {
for (int c = 0; c < cols; c++) {
String colName = "col_" + c;
ops++; // O(1) hash lookup
boolean found = indexedColsSet.contains(colName);
}
}
return ops;
}
public static void main(String[] args) {
System.out.println("proxysql-0002: MySQL_FTS indexed_cols O(R*C*I) benchmark");
System.out.println("=".repeat(80));
int failures = 0;
int total = 0;
// R=5000 rows, C=20 columns, I=10 indexed columns (half of cols are indexed)
int ROWS = 5000;
int COLS = 20;
int INDEXED = 10;
// Build indexed_cols: first INDEXED column names
List<String> indexedColsList = new ArrayList<>();
Set<String> indexedColsSet = new HashSet<>();
for (int i = 0; i < INDEXED; i++) {
String name = "col_" + i;
indexedColsList.add(name);
indexedColsSet.add(name);
}
long[] slowOps = new long[1], fastOps = new long[1];
Runnable slow = () -> slowOps[0] = ftsIndexSlow(ROWS, COLS, indexedColsList);
Runnable fast = () -> fastOps[0] = ftsIndexFast(ROWS, COLS, indexedColsSet);
// Warmup
slow.run(); fast.run();
long t0 = System.nanoTime(); slow.run(); long sMs = (System.nanoTime() - t0) / 1_000_000;
long t1 = System.nanoTime(); fast.run(); long fMs = (System.nanoTime() - t1) / 1_000_000;
double speedup = fastOps[0] > 0 ? (double) slowOps[0] / fastOps[0] : 0;
System.out.printf(" %-58s slow:%4dms (%,d ops) fast:%4dms (%,d ops) speedup:%.1fx%n",
"proxysql-0002 FTS indexed_cols O(R*C*I) vs O(R*C)", sMs, slowOps[0], fMs, fastOps[0], speedup);
total++;
// slow: R * C * I (worst case each col scans all I); fast: R * C
// At R=5000, C=20, I=10: slow=1,000,000 ops, fast=100,000 ops -> 10x ratio
boolean pass = slowOps[0] > fastOps[0] * 5L;
if (!pass) {
System.out.printf(" FAIL proxysql-0002: slowOps=%,d fastOps=%,d (expected >5x)%n",
slowOps[0], fastOps[0]);
failures++;
} else {
System.out.println(" PASS proxysql-0002");
}
System.out.println("=".repeat(80));
System.out.printf("%d/%d %s%n", total - failures, total, failures == 0 ? "PASS" : "FAIL");
if (failures > 0) System.exit(1);
}
}