package unit; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; /** * Unit test for CWE-407 defect in ClickHouse ReplaceColumnTransformerNode::findReplacementExpression. * * Defect: replacements_names is a std::vector. findReplacementExpression() does * std::find (O(n)) over it. It is called inside a double loop: * for each column (C) → for each transformer (T) → findReplacementExpression() → O(R) * Total: O(C * T * R). For wide-table queries with compound REPLACE lists this is measurable. * * Fix: add std::unordered_map replacements_index alongside replacements_names. * findReplacementExpression() becomes O(1) via map lookup. * * This test models slow (list scan) vs fast (map lookup) and asserts * slow ops > fast ops * 10x at N=200 replacements, 500 column lookups. */ public class ClickHouseReplaceTransformerAlgorithm { // ----------------------------------------------------------------------- // slow(): models std::find scan over Names (vector). // Returns total comparison ops. // ----------------------------------------------------------------------- static Result slow(int numReplacements, int numLookups) { List names = new ArrayList<>(); for (int i = 0; i < numReplacements; i++) { names.add("col_" + i); } long ops = 0; // Simulate findReplacementExpression called numLookups times, // always looking for the last element (worst case). String target = "col_" + (numReplacements - 1); for (int q = 0; q < numLookups; q++) { for (int j = 0; j < names.size(); j++) { ops++; if (names.get(j).equals(target)) { break; } } } return new Result(ops); } // ----------------------------------------------------------------------- // fast(): models unordered_map lookup → O(1). // Returns total lookup ops (1 per call). // ----------------------------------------------------------------------- static Result fast(int numReplacements, int numLookups) { Map index = new HashMap<>(); for (int i = 0; i < numReplacements; i++) { index.put("col_" + i, i); } long ops = 0; String target = "col_" + (numReplacements - 1); for (int q = 0; q < numLookups; q++) { ops++; // O(1) hash lookup @SuppressWarnings("unused") Integer idx = index.get(target); } return new Result(ops); } // ----------------------------------------------------------------------- static class Result { final long ops; Result(long ops) { this.ops = ops; } } // ----------------------------------------------------------------------- public static void main(String[] args) { int N = 200; int lookups = 500; int NX = 10; Result s = slow(N, lookups); Result f = fast(N, lookups); System.out.printf("slow ops=%d fast ops=%d ratio=%.1fx%n", s.ops, f.ops, (double) s.ops / f.ops); if (s.ops <= f.ops * NX) { System.out.printf("FAIL: expected slow(%d) > fast(%d) * %d%n", s.ops, f.ops, NX); System.exit(1); } System.out.printf("1/1 PASS (slow=%d >> fast=%d, N=%d lookups=%d)%n", s.ops, f.ops, N, lookups); } }