92 lines
3.5 KiB
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);
|
|
}
|
|
}
|