package unit; import java.util.*; /** * Standalone unit test for lmdb-001: * mdb_dbi_open() named-database linear scan — O(D) per open. * * Models the exact C algorithm from libraries/liblmdb/mdb.c lines 10932-10943. * Compile: javac -d . *.java * Run: java unit.LmdbDbiOpenAlgorithm */ public class LmdbDbiOpenAlgorithm { // ------------------------------------------------------------------------- // Model types // ------------------------------------------------------------------------- static class DbEntry { final String name; final int dbi; DbEntry(String name, int dbi) { this.name = name; this.dbi = dbi; } } static class Result { final int dbi; // -1 = not found final int comparisons; Result(int dbi, int cmp) { this.dbi = dbi; this.comparisons = cmp; } } // ------------------------------------------------------------------------- // Defective algorithm (exact port from LMDB mdb_dbi_open, mdb.c:10932-10943) // ------------------------------------------------------------------------- static class DefectiveDbiOpen { // dbxs: array of open named databases (index = dbi) // name: name to look up static Result lookup(List dbxs, String name) { int comparisons = 0; int len = name.length(); for (int i = 0; i < dbxs.size(); i++) { DbEntry entry = dbxs.get(i); if (entry == null || entry.name == null || entry.name.isEmpty()) { // free slot — skip comparisons++; continue; } comparisons++; if (len == entry.name.length() && name.equals(entry.name)) { return new Result(entry.dbi, comparisons); } } return new Result(-1, comparisons); // not found } } // ------------------------------------------------------------------------- // Fixed algorithm: O(log D) binary search on sorted name array // ------------------------------------------------------------------------- static class FixedDbiOpen { // Maintains a sorted index of (name, dbi) pairs private final TreeMap index = new TreeMap<>(); int comparisons = 0; void register(String name, int dbi) { index.put(name, dbi); } Result lookup(String name) { comparisons = 0; // TreeMap.get is O(log D) // We simulate comparison count as ceil(log2(size)) for binary search int size = index.size(); int steps = size == 0 ? 0 : (int) Math.ceil(Math.log(size + 1) / Math.log(2)); comparisons = Math.max(1, steps); Integer dbi = index.get(name); return new Result(dbi == null ? -1 : dbi, comparisons); } } // ------------------------------------------------------------------------- // Tests // ------------------------------------------------------------------------- static int passed = 0, failed = 0; static void expect(String label, boolean condition) { if (condition) { System.out.println(" PASS: " + label); passed++; } else { System.out.println(" FAIL: " + label); failed++; } } static List buildDbxs(int d) { List dbxs = new ArrayList<>(); for (int i = 0; i < d; i++) { dbxs.add(new DbEntry("db_" + String.format("%04d", i), i)); } return dbxs; } static FixedDbiOpen buildFixed(int d) { FixedDbiOpen f = new FixedDbiOpen(); for (int i = 0; i < d; i++) { f.register("db_" + String.format("%04d", i), i); } return f; } static void testBasicLookup() { System.out.println("[testBasicLookup]"); List dbxs = buildDbxs(10); FixedDbiOpen fixed = buildFixed(10); Result slow = DefectiveDbiOpen.lookup(dbxs, "db_0005"); Result fast = fixed.lookup("db_0005"); expect("defective finds dbi=5", slow.dbi == 5); expect("fixed finds dbi=5", fast.dbi == 5); } static void testNotFound() { System.out.println("[testNotFound]"); List dbxs = buildDbxs(10); FixedDbiOpen fixed = buildFixed(10); Result slow = DefectiveDbiOpen.lookup(dbxs, "no_such_db"); Result fast = fixed.lookup("no_such_db"); expect("defective returns -1 (not found)", slow.dbi == -1); expect("fixed returns -1 (not found)", fast.dbi == -1); expect("defective scanned all D=10 entries", slow.comparisons == 10); } static void testFirstEntry() { System.out.println("[testFirstEntry]"); List dbxs = buildDbxs(50); FixedDbiOpen fixed = buildFixed(50); Result slow = DefectiveDbiOpen.lookup(dbxs, "db_0000"); Result fast = fixed.lookup("db_0000"); expect("defective finds first entry", slow.dbi == 0); expect("fixed finds first entry", fast.dbi == 0); // Defective finds it at position 0 — O(1) best case, O(D) worst case } static void testLinearVsLogScaling_D100() { System.out.println("[testLinearVsLogScaling D=100]"); int D = 100; int N = 10000; // lookup operations List dbxs = buildDbxs(D); FixedDbiOpen fixed = buildFixed(D); // Worst case: look up last entry every time String target = "db_" + String.format("%04d", D - 1); long slowTotal = 0; long fastTotal = 0; for (int i = 0; i < N; i++) { slowTotal += DefectiveDbiOpen.lookup(dbxs, target).comparisons; fastTotal += fixed.lookup(target).comparisons; } System.out.println(" defective total comparisons: " + slowTotal); System.out.println(" fixed total comparisons: " + fastTotal); System.out.println(" expected slowTotal ~ N*D = " + (long)N * D); // Defective: worst case = D comparisons per lookup → N*D total expect("defective is O(D) per lookup (total ~ N*D)", slowTotal >= (long)(N * D * 9 / 10)); // Fixed: O(log D) per lookup → N*log2(D) total double expectedFast = N * Math.ceil(Math.log(D + 1) / Math.log(2)); expect("fixed is O(log D) per lookup", fastTotal <= (long)(expectedFast * 2)); double ratio = (double) slowTotal / fastTotal; System.out.printf(" speedup ratio: %.1fx%n", ratio); expect("speedup >= 5x at D=100", ratio >= 5.0); } static void testLinearVsLogScaling_D1000() { System.out.println("[testLinearVsLogScaling D=1000]"); int D = 1000; int N = 1000; List dbxs = buildDbxs(D); FixedDbiOpen fixed = buildFixed(D); String target = "db_" + String.format("%04d", D - 1); long slowTotal = 0; long fastTotal = 0; for (int i = 0; i < N; i++) { slowTotal += DefectiveDbiOpen.lookup(dbxs, target).comparisons; fastTotal += fixed.lookup(target).comparisons; } System.out.println(" defective total comparisons: " + slowTotal); System.out.println(" fixed total comparisons: " + fastTotal); expect("defective is O(D) per lookup", slowTotal >= (long)(N * D * 9 / 10)); double ratio = (double) slowTotal / fastTotal; System.out.printf(" speedup ratio: %.1fx%n", ratio); expect("speedup >= 50x at D=1000", ratio >= 50.0); } static void testMultipleOpensPerTransaction() { System.out.println("[testMultipleOpensPerTransaction]"); int D = 50; // 50 named databases int K = 10; // databases opened per transaction int N = 500; // transactions List dbxs = buildDbxs(D); FixedDbiOpen fixed = buildFixed(D); long slowTotal = 0; long fastTotal = 0; Random rng = new Random(123); for (int txn = 0; txn < N; txn++) { for (int k = 0; k < K; k++) { String name = "db_" + String.format("%04d", rng.nextInt(D)); slowTotal += DefectiveDbiOpen.lookup(dbxs, name).comparisons; fastTotal += fixed.lookup(name).comparisons; } } System.out.println(" defective total comparisons over " + N + " txns: " + slowTotal); System.out.println(" fixed total comparisons over " + N + " txns: " + fastTotal); expect("defective uses more comparisons", slowTotal > fastTotal); double ratio = (double) slowTotal / fastTotal; System.out.printf(" speedup ratio: %.1fx%n", ratio); expect("speedup >= 3x", ratio >= 3.0); } static void testEmptyDatabase() { System.out.println("[testEmptyDatabase]"); List dbxs = new ArrayList<>(); FixedDbiOpen fixed = new FixedDbiOpen(); Result slow = DefectiveDbiOpen.lookup(dbxs, "anydb"); Result fast = fixed.lookup("anydb"); expect("defective: not found in empty", slow.dbi == -1); expect("fixed: not found in empty", fast.dbi == -1); } // ------------------------------------------------------------------------- // Main // ------------------------------------------------------------------------- public static void main(String[] args) { System.out.println("=== lmdb-001: mdb_dbi_open named-database linear scan ==="); testBasicLookup(); testNotFound(); testFirstEntry(); testEmptyDatabase(); testLinearVsLogScaling_D100(); testLinearVsLogScaling_D1000(); testMultipleOpensPerTransaction(); System.out.println(); System.out.println("Results: " + passed + " passed, " + failed + " failed"); if (failed > 0) System.exit(1); } }