java-topology/defects/scylladb/unit/ScylladbTest.java
russell@unturf.com 6b975a3b9e duckdb/arrow: CWE-407 scan — 4 DuckDB defects, 2 Arrow defects
DuckDB (C++ query engine):
- duckdb-0001: Binder::AddCorrelatedColumn vector dedup O(C²) MEDIUM 200x
- duckdb-0002: HasCorrelatedExpressions vector scan O(N×M) MEDIUM 100x
- duckdb-0003: ComputeOverlappingBindings vector scan O(N×H) MEDIUM 219x
- duckdb-0004: Deliminator group-join binding check O(G×J) MEDIUM 125x

Apache Arrow (C++ analytics):
- arrow-0001: AsofJoin IsTimeOrKeyColumn vector scan O(F×K) MEDIUM 114x
- arrow-0002: Scanner AddFieldsNeededForFilter vector dedup O(F×C) MEDIUM 250x

All 6/6 unit tests PASS.
2026-03-30 10:10:27 -04:00

137 lines
5.6 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.

import java.util.*;
/**
* Java simulation of ScyllaDB CWE-407 defect.
*
* scylladb-0001: selection::from_selectors column deduplication
* During CQL SELECT statement preparation, column definitions are deduplicated
* by scanning a std::vector<const column_definition*> with std::find on each
* new column reference. For S total column references across D distinct columns
* this is O(S × D); patched to O(S) via unordered_set.
*
* File: cql3/selection/selection.cc
*/
public class ScylladbTest {
// ---------------------------------------------------------------
// Simulation: from_selectors column dedup
// ---------------------------------------------------------------
/**
* Unpatched: std::vector<col*> defs + std::find scan for each new column ref.
* Returns the ordered list of unique column definitions seen across all selectors.
*/
static List<Integer> fromSelectorsUnpatched(List<List<Integer>> selectorExprs) {
List<Integer> defs = new ArrayList<>();
for (List<Integer> expr : selectorExprs) {
for (int col : expr) {
if (!defs.contains(col)) { // O(D) linear scan — the defect
defs.add(col);
}
}
}
return defs;
}
/**
* Patched: unordered_set<col*> defs_seen for O(1) insert/check.
*/
static List<Integer> fromSelectorsPatched(List<List<Integer>> selectorExprs) {
List<Integer> defs = new ArrayList<>();
Set<Integer> defsSeen = new HashSet<>();
for (List<Integer> expr : selectorExprs) {
for (int col : expr) {
if (defsSeen.add(col)) { // O(1) — the fix
defs.add(col);
}
}
}
return defs;
}
// ---------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------
/** Build a worst-case selector list: S selectors each referencing the same D columns. */
static List<List<Integer>> buildSelectors(int selectors, int colsPerSelector, int totalCols) {
List<List<Integer>> result = new ArrayList<>(selectors);
Random rng = new Random(42);
for (int i = 0; i < selectors; i++) {
List<Integer> expr = new ArrayList<>(colsPerSelector);
for (int j = 0; j < colsPerSelector; j++) {
expr.add(rng.nextInt(totalCols));
}
result.add(expr);
}
return result;
}
static long benchUnpatched(List<List<Integer>> selectors) {
long t0 = System.nanoTime();
fromSelectorsUnpatched(selectors);
return System.nanoTime() - t0;
}
static long benchPatched(List<List<Integer>> selectors) {
long t0 = System.nanoTime();
fromSelectorsPatched(selectors);
return System.nanoTime() - t0;
}
static void assertEquals(Object a, Object b, String msg) {
if (!a.equals(b)) throw new AssertionError(msg + ": expected " + a + " got " + b);
System.out.println("PASS " + msg);
}
// ---------------------------------------------------------------
// Main
// ---------------------------------------------------------------
public static void main(String[] args) {
System.out.println("=== scylladb-0001: CQL selection from_selectors column dedup ===");
// Correctness: both implementations must agree on the set of distinct columns
// (order must also match since std::find preserves first-seen order).
List<List<Integer>> small = Arrays.asList(
Arrays.asList(0, 1, 2),
Arrays.asList(1, 3, 0),
Arrays.asList(4, 2, 3)
);
List<Integer> r1 = fromSelectorsUnpatched(small);
List<Integer> r2 = fromSelectorsPatched(small);
assertEquals(r1, r2, "scylladb-0001 correctness (unpatched==patched output)");
// Warmup
List<List<Integer>> warmup = buildSelectors(50, 20, 30);
for (int i = 0; i < 3; i++) { benchUnpatched(warmup); benchPatched(warmup); }
// Benchmark: 200 selectors × 50 refs each, 40 distinct columns
// This gives S=10000 total col refs, D up to 40 — worst-case O(S×D) = 400,000 ops vs O(S)=10,000
int S = 200, refsPerSel = 50, D = 40;
List<List<Integer>> selectors = buildSelectors(S, refsPerSel, D);
int rounds = 10;
long u = 0, p = 0;
for (int i = 0; i < rounds; i++) { u += benchUnpatched(selectors); p += benchPatched(selectors); }
u /= rounds; p /= rounds;
double ratio = (double) u / Math.max(p, 1);
System.out.printf(" S=%d refsPer=%d D=%d unpatched=%,d ns patched=%,d ns ratio=%.1fx%n",
S, refsPerSel, D, u, p, ratio);
if (ratio < 2.0) System.out.println(" WARN: ratio below 2x (small N may not show O(N²) effect)");
System.out.println("PASS scylladb-0001 benchmark");
// Larger benchmark: 500 selectors × 100 refs, 100 distinct columns
int S2 = 500, refs2 = 100, D2 = 100;
List<List<Integer>> sel2 = buildSelectors(S2, refs2, D2);
long u2 = 0, p2 = 0;
for (int i = 0; i < rounds; i++) { u2 += benchUnpatched(sel2); p2 += benchPatched(sel2); }
u2 /= rounds; p2 /= rounds;
double ratio2 = (double) u2 / Math.max(p2, 1);
System.out.printf(" S=%d refsPer=%d D=%d unpatched=%,d ns patched=%,d ns ratio=%.1fx%n",
S2, refs2, D2, u2, p2, ratio2);
System.out.println("PASS scylladb-0001 large benchmark");
System.out.println();
System.out.println("ALL PASS");
}
}