java-topology/defects/druid/unit/ScanQueryAlgorithm.java

191 lines
7.2 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.

package unit;
// NO JUnit. Compile: javac -d . *.java Run: java -ea unit.ScanQueryAlgorithm
//
// Druid druid-0001: ScanQuery columns List.contains in orderBy validation loop O(N×M) → O(N)
//
// Simulates: for each orderBy column, check if it exists in the selected columns list
//
// SLOW: List<String>.contains() per orderBy column — O(N × M)
// FAST: HashSet<String> built once, .contains() — O(N)
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class ScanQueryAlgorithm {
// Simulates the defective ScanQuery constructor validation:
// for each orderBy column, call List<String>.contains()
static class SlowImpl {
long opCount = 0;
// Returns list of invalid orderBy columns (those not in selectedColumns)
// O(N × M) where N=orderBys, M=selectedColumns
List<String> validateOrderBy(List<String> selectedColumns, List<String> orderBys) {
List<String> invalid = new ArrayList<>();
for (String orderByCol : orderBys) {
// Simulate List.contains: linear scan
boolean found = false;
for (int i = 0; i < selectedColumns.size(); i++) {
opCount++;
if (selectedColumns.get(i).equals(orderByCol)) {
found = true;
break;
}
}
if (!found) {
invalid.add(orderByCol);
}
}
return invalid;
}
}
// Simulates the fixed HashSet pattern:
// build set once, O(1) per orderBy column check
static class FastImpl {
long opCount = 0;
// Returns list of invalid orderBy columns (those not in selectedColumns)
// O(N) after O(M) set construction
List<String> validateOrderBy(List<String> selectedColumns, List<String> orderBys) {
// Build O(1) lookup set once
Set<String> colSet = new HashSet<>(selectedColumns);
opCount += selectedColumns.size(); // count set-build cost
List<String> invalid = new ArrayList<>();
for (String orderByCol : orderBys) {
opCount++;
if (!colSet.contains(orderByCol)) {
invalid.add(orderByCol);
}
}
return invalid;
}
}
static List<String> buildColumns(int M) {
List<String> cols = new ArrayList<>();
for (int i = 0; i < M; i++) {
cols.add("col_" + i);
}
return cols;
}
static List<String> buildOrderBys(List<String> columns, int N) {
// orderBy columns are at the end of the selected list (worst case for linear scan)
List<String> orderBys = new ArrayList<>();
for (int i = columns.size() - N; i < columns.size(); i++) {
orderBys.add(columns.get(i));
}
return orderBys;
}
public static void main(String[] args) {
int passed = 0;
int total = 0;
// Test 1: correctness — both impls agree on valid/invalid columns
{
total++;
List<String> cols = buildColumns(20);
List<String> validOrderBys = buildOrderBys(cols, 3);
List<String> invalidOrderBys = new ArrayList<>(validOrderBys);
invalidOrderBys.add("nonexistent_col");
SlowImpl slow = new SlowImpl();
FastImpl fast = new FastImpl();
List<String> slowInvalid = slow.validateOrderBy(cols, invalidOrderBys);
List<String> fastInvalid = fast.validateOrderBy(cols, invalidOrderBys);
assert slowInvalid.equals(fastInvalid) :
"Mismatch: slow=" + slowInvalid + " fast=" + fastInvalid;
assert slowInvalid.size() == 1 :
"Expected 1 invalid column, got " + slowInvalid.size();
assert slowInvalid.get(0).equals("nonexistent_col") :
"Expected nonexistent_col, got " + slowInvalid.get(0);
System.out.println("Test 1 PASS: correctness verified, invalid=[" + slowInvalid.get(0) + "]");
passed++;
}
// Test 2: op ratio at M=500 selected columns, N=5 orderBy columns
// ScanQuery is constructed per segment scan — with M=500 wide-table analytics
{
total++;
int M = 500;
int N = 5;
List<String> cols = buildColumns(M);
// orderBy columns at the end — worst case for linear scan
List<String> orderBys = buildOrderBys(cols, N);
SlowImpl slow = new SlowImpl();
slow.validateOrderBy(cols, orderBys);
long slowOps = slow.opCount;
FastImpl fast = new FastImpl();
fast.validateOrderBy(cols, orderBys);
long fastOps = fast.opCount;
System.out.println("Test 2: M=" + M + ", N=" + N + ", slow_ops=" + slowOps + ", fast_ops=" + fastOps);
// Slow: each of N orderBys must scan to end of M (they're at the end)
// worst case: N*M = 5*500 = 2500 ops
long minQuadratic = (long) N * (M / 2); // at least half-scan
assert slowOps >= minQuadratic :
"slow_ops=" + slowOps + " should be >= " + minQuadratic;
// Fast: M (set build) + N (lookups) = 505
long maxLinear = M + N + 10;
assert fastOps <= maxLinear :
"fast_ops=" + fastOps + " should be <= " + maxLinear;
// Ratio: N*M / (M+N) = 5*500/505 ≈ 4.95 — clear speedup, conservative threshold 4x
assert slowOps * 4 >= fastOps * 10 :
"Expected slow/fast ratio >= 2.5x, got slow=" + slowOps + " fast=" + fastOps;
System.out.println("Test 2 PASS: slow_ops=" + slowOps + " (fast=" + fastOps + ")");
passed++;
}
// Test 3: large schema M=2000, N=10 (analytical query over very wide table)
{
total++;
int M = 2000;
int N = 10;
List<String> cols = buildColumns(M);
List<String> orderBys = buildOrderBys(cols, N);
SlowImpl slow = new SlowImpl();
slow.validateOrderBy(cols, orderBys);
long slowOps = slow.opCount;
FastImpl fast = new FastImpl();
fast.validateOrderBy(cols, orderBys);
long fastOps = fast.opCount;
System.out.println("Test 3: M=" + M + ", N=" + N + ", slow_ops=" + slowOps + ", fast_ops=" + fastOps);
// Slow: N*M minimum (all orderBys at end, full scan)
long minQuadratic = (long) N * (M / 2);
assert slowOps >= minQuadratic :
"slow_ops=" + slowOps + " should be >= " + minQuadratic;
long maxLinear = M + N + 10;
assert fastOps <= maxLinear :
"fast_ops=" + fastOps + " should be <= " + maxLinear;
// At M=2000, N=10: ratio = N*M/(M+N) = 10*2000/2010 ≈ 9.95x
assert slowOps >= fastOps * 9 :
"Expected >= 9x op ratio, got slow=" + slowOps + " fast=" + fastOps;
System.out.println("Test 3 PASS: " + (slowOps / fastOps) + "x speedup at M=" + M + ", N=" + N);
passed++;
}
System.out.println(passed + "/" + total + " PASS");
}
}