mysql+proxysql: 5-MOAD scan; mysql-0005 CWE-407 row0sel template_col O(T*C) per row 31.5x, proxysql-0002 CWE-407 FTS indexed_cols O(R*C*I) 7.8x

This commit is contained in:
russell@unturf.com 2026-04-03 15:57:11 -04:00
parent decacb5dbd
commit 7822bb0e85
6 changed files with 437 additions and 0 deletions

View file

@ -9,6 +9,7 @@ import java.util.*;
* mysql-0002: has_global_grant fallback — O(P) multimap equal_range+find vs O(1) map lookup
* mysql-0003: setup_fields() iterator recovery — O(F²) std::find vs O(F) index loop
* mysql-0004: dict_index_find_and_set_cols() col_added — O(F²) std::find vs O(F) unordered_set
* mysql-0005: row_sel_store_mysql_rec() template_col — O(T*C) std::find per row vs O(T) hashmap
*
* No JUnit. Prints N/N PASS.
*/
@ -356,8 +357,96 @@ public class MysqlTest {
else System.out.println(" PASS mysql-0004");
}
// --- mysql-0005: row_sel_store_mysql_rec template_col O(T*C) std::find per row ---
{
int T = 50; // template fields (SELECT columns)
int C = 50; // secondary index columns
int ROWS = 100_000;
long[] slowOps = new long[1], fastOps = new long[1];
Runnable slow = () -> slowOps[0] = templateColSlow(T, C, ROWS);
Runnable fast = () -> fastOps[0] = templateColFast(T, C, ROWS);
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(" %-60s slow:%4dms (%,d ops) fast:%4dms (%,d ops) speedup:%.1fx%n",
"mysql-0005 row0sel template_col O(T*C) vs O(T) hashmap", sMs, slowOps[0], fMs, fastOps[0], speedup);
total++;
// slow: T * C ops per row * ROWS; fast: T ops per row * ROWS; ratio = C = 50
boolean pass = slowOps[0] > fastOps[0] * 10L;
if (!pass) { System.out.printf(" FAIL mysql-0005: slowOps=%,d fastOps=%,d (expected >10x)%n",
slowOps[0], fastOps[0]); failures++; }
else System.out.println(" PASS mysql-0005");
}
System.out.println("=".repeat(100));
System.out.printf("%d/%d %s%n", total - failures, total, failures == 0 ? "PASS" : "FAIL");
if (failures > 0) System.exit(1);
}
// -----------------------------------------------------------------------
// mysql-0005 — row_sel_store_mysql_rec: O(T*C) vector std::find vs O(T) hashmap
//
// Models storage/innobase/row/row0sel.cc:row_sel_store_mysql_rec()
// template_col: vector<const dict_col_t*> of C secondary index cols
// For each of T template fields: std::find(template_col, col) -> O(C)
// Total per row: O(T * C)
// Fix: unordered_map<col*, sec_field_no> -> O(1) per template field
// -----------------------------------------------------------------------
static long templateColSlow(int T, int C, int rows) {
// Build template_col vector (C pointer-like integers)
long[] templateCol = new long[C];
for (int i = 0; i < C; i++) templateCol[i] = i + 1000L; // simulate column pointers
// T template fields - half will be found in templateCol
long[] templateFields = new long[T];
for (int i = 0; i < T; i++) {
templateFields[i] = (i % 2 == 0) ? (i / 2 + 1000L) : (i + 9000L); // half hit, half miss
}
long ops = 0;
for (int r = 0; r < rows; r++) {
for (int t = 0; t < T; t++) {
long col = templateFields[t];
// std::find equivalent: O(C) linear scan
int foundIdx = -1;
for (int c = 0; c < C; c++) {
ops++;
if (templateCol[c] == col) {
foundIdx = c;
break;
}
}
// foundIdx is the sec_field_no (or -1 if not found)
long dummy = foundIdx; // prevent dead-code elimination
}
}
return ops;
}
static long templateColFast(int T, int C, int rows) {
// Build template_col_map: HashMap<col, sec_field_no> for O(1) lookup
Map<Long, Integer> templateColMap = new HashMap<>(C * 2);
for (int i = 0; i < C; i++) templateColMap.put(i + 1000L, i);
// T template fields
long[] templateFields = new long[T];
for (int i = 0; i < T; i++) {
templateFields[i] = (i % 2 == 0) ? (i / 2 + 1000L) : (i + 9000L);
}
long ops = 0;
for (int r = 0; r < rows; r++) {
for (int t = 0; t < T; t++) {
long col = templateFields[t];
ops++; // O(1) hash lookup
Integer secFieldNo = templateColMap.get(col);
long dummy = (secFieldNo != null) ? secFieldNo : -1;
}
}
return ops;
}
}