java-topology/defects/duckdb/unit/DuckDBTest.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

214 lines
8.8 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.*;
/**
* CWE-407 unit tests for DuckDB defects.
*
* duckdb-0001: src/planner/binder.cpp Binder::AddCorrelatedColumn()
* std::find over correlated_columns vector -> O(C²) dedup.
* Fix: shadow HashSet for O(1) Contains().
*
* duckdb-0002: src/planner/subquery/has_correlated_expressions.cpp
* outer loop over correlated_columns × inner std::find -> O(N×M).
* Fix: use CorrelatedColumns::Contains() O(1).
*
* duckdb-0003: src/optimizer/build_probe_side_optimizer.cpp
* ComputeOverlappingBindings: for each needle, std::find in haystack -> O(N×H).
* Fix: unordered_set for haystack -> O(N+H).
*
* duckdb-0004: src/optimizer/deliminator.cpp
* For each aggregate group, std::find in join_bindings -> O(G×J).
* Fix: unordered_set for join_bindings -> O(G+J).
*/
public class DuckDBTest {
// --- Simulate ColumnBinding ---
static class ColumnBinding {
final long tableIndex;
final long columnIndex;
ColumnBinding(long t, long c) { this.tableIndex = t; this.columnIndex = c; }
@Override public boolean equals(Object o) {
if (!(o instanceof ColumnBinding)) return false;
ColumnBinding b = (ColumnBinding) o;
return tableIndex == b.tableIndex && columnIndex == b.columnIndex;
}
@Override public int hashCode() { return Objects.hash(tableIndex, columnIndex); }
}
// =========================================================
// duckdb-0001: AddCorrelatedColumn O(C²) vs O(C)
// =========================================================
static class CorrelatedColumnsVector {
List<Integer> cols = new ArrayList<>();
void addColumn(int binding) {
if (!cols.contains(binding)) { cols.add(0, binding); }
}
int size() { return cols.size(); }
}
static class CorrelatedColumnsHashSet {
List<Integer> cols = new ArrayList<>();
Set<Integer> bindingSet = new HashSet<>();
void addColumn(int binding) {
if (!bindingSet.contains(binding)) { cols.add(0, binding); bindingSet.add(binding); }
}
int size() { return cols.size(); }
}
static long mergeCorrelatedVector(List<Integer> other, CorrelatedColumnsVector dst) {
long ops = 0;
for (int b : other) { ops += dst.cols.size(); dst.addColumn(b); }
return ops;
}
static long mergeCorrelatedHashSet(List<Integer> other, CorrelatedColumnsHashSet dst) {
long ops = 0;
for (int b : other) { ops += 1; dst.addColumn(b); }
return ops;
}
static boolean testDuckDB0001() {
System.out.println("=== duckdb-0001: AddCorrelatedColumn O(C^2) vs O(C) ===");
int C = 400;
List<Integer> other = new ArrayList<>();
for (int i = 0; i < C; i++) other.add(i);
CorrelatedColumnsVector vec = new CorrelatedColumnsVector();
CorrelatedColumnsHashSet hset = new CorrelatedColumnsHashSet();
long opsVec = mergeCorrelatedVector(other, vec);
long opsHash = mergeCorrelatedHashSet(other, hset);
double ratio = (double) opsVec / opsHash;
System.out.printf(" C=%d vector_ops=%,d hashset_ops=%,d ratio=%.1fx%n", C, opsVec, opsHash, ratio);
assert vec.size() == C && hset.size() == C : "size mismatch";
boolean pass = ratio > 10.0;
System.out.println(" " + (pass ? "PASS" : "FAIL"));
return pass;
}
// =========================================================
// duckdb-0002: HasCorrelatedExpressions O(N×M) vs O(N+M)
// =========================================================
static long hasCorrelatedVector(List<Integer> correlatedCols, List<Integer> binderCols) {
long ops = 0;
for (int b : correlatedCols) {
for (int bc : binderCols) { ops++; if (bc == b) break; }
}
return ops;
}
static long hasCorrelatedHashSet(List<Integer> correlatedCols, List<Integer> binderCols) {
Set<Integer> binderSet = new HashSet<>(binderCols);
long ops = binderCols.size();
for (int b : correlatedCols) { ops += 1; binderSet.contains(b); }
return ops;
}
static boolean testDuckDB0002() {
System.out.println("=== duckdb-0002: HasCorrelatedExpressions O(N*M) vs O(N+M) ===");
int N = 200;
List<Integer> correlatedCols = new ArrayList<>();
List<Integer> binderCols = new ArrayList<>();
for (int i = 0; i < N; i++) correlatedCols.add(i);
for (int i = 0; i < N; i++) binderCols.add(i + N); // non-overlapping worst case
long opsVec = hasCorrelatedVector(correlatedCols, binderCols);
long opsHash = hasCorrelatedHashSet(correlatedCols, binderCols);
double ratio = (double) opsVec / opsHash;
System.out.printf(" N=M=%d vector_ops=%,d hashset_ops=%,d ratio=%.1fx%n", N, opsVec, opsHash, ratio);
boolean pass = ratio > 10.0;
System.out.println(" " + (pass ? "PASS" : "FAIL"));
return pass;
}
// =========================================================
// duckdb-0003: ComputeOverlappingBindings O(N×H) vs O(N+H)
// =========================================================
static long computeOverlappingDefective(List<ColumnBinding> haystack, List<ColumnBinding> needles) {
long ops = 0;
for (ColumnBinding needle : needles) {
for (ColumnBinding h : haystack) { ops++; if (h.equals(needle)) break; }
}
return ops;
}
static long computeOverlappingFixed(List<ColumnBinding> haystack, List<ColumnBinding> needles) {
Set<ColumnBinding> haystackSet = new HashSet<>(haystack);
long ops = haystack.size(); // build set
for (ColumnBinding needle : needles) { ops++; haystackSet.contains(needle); }
return ops;
}
static boolean testDuckDB0003() {
System.out.println("=== duckdb-0003: ComputeOverlappingBindings O(N*H) vs O(N+H) ===");
int N = 500;
List<ColumnBinding> haystack = new ArrayList<>();
List<ColumnBinding> needles = new ArrayList<>();
for (int i = 0; i < N; i++) {
haystack.add(new ColumnBinding(0, i));
needles.add(new ColumnBinding(0, i + N / 2)); // half overlap, worst-case scan
}
long opsDefective = computeOverlappingDefective(haystack, needles);
long opsFixed = computeOverlappingFixed(haystack, needles);
double ratio = (double) opsDefective / opsFixed;
System.out.printf(" N=H=%d defective_ops=%,d fixed_ops=%,d ratio=%.1fx%n",
N, opsDefective, opsFixed, ratio);
boolean pass = ratio > 10.0;
System.out.println(" " + (pass ? "PASS" : "FAIL"));
return pass;
}
// =========================================================
// duckdb-0004: Deliminator group-join binding check O(G×J)
// =========================================================
static long checkGroupsDefective(List<ColumnBinding> joinBindings, long groupIndex, int groupCount) {
long ops = 0;
for (int g = 0; g < groupCount; g++) {
ColumnBinding target = new ColumnBinding(groupIndex, g);
for (ColumnBinding jb : joinBindings) { ops++; if (jb.equals(target)) break; }
}
return ops;
}
static long checkGroupsFixed(List<ColumnBinding> joinBindings, long groupIndex, int groupCount) {
Set<ColumnBinding> joinSet = new HashSet<>(joinBindings);
long ops = joinBindings.size(); // build set
for (int g = 0; g < groupCount; g++) {
ops++; joinSet.contains(new ColumnBinding(groupIndex, g));
}
return ops;
}
static boolean testDuckDB0004() {
System.out.println("=== duckdb-0004: Deliminator group-join check O(G*J) vs O(G+J) ===");
int N = 500;
List<ColumnBinding> joinBindings = new ArrayList<>();
for (int i = 0; i < N; i++) joinBindings.add(new ColumnBinding(42, i));
long opsDefective = checkGroupsDefective(joinBindings, 42, N);
long opsFixed = checkGroupsFixed(joinBindings, 42, N);
double ratio = (double) opsDefective / opsFixed;
System.out.printf(" G=J=%d defective_ops=%,d fixed_ops=%,d ratio=%.1fx%n",
N, opsDefective, opsFixed, ratio);
boolean pass = ratio > 10.0;
System.out.println(" " + (pass ? "PASS" : "FAIL"));
return pass;
}
// =========================================================
public static void main(String[] args) {
int pass = 0, fail = 0;
if (testDuckDB0001()) pass++; else fail++;
if (testDuckDB0002()) pass++; else fail++;
if (testDuckDB0003()) pass++; else fail++;
if (testDuckDB0004()) pass++; else fail++;
System.out.printf("%nDuckDB CWE-407: %d/%d PASS%n", pass, pass + fail);
if (fail > 0) System.exit(1);
}
}