package unit; import java.util.*; /** * yugabyte-0001: GetXReplStreamsForTable std::find on table_id list inside per-table loop * * Models xrepl_catalog_manager.cc: * GetXReplStreamsForTable: for each stream, std::find on table_id repeated field * DropXClusterStreamsOfTables: calls GetXReplStreamsForTable in a loop over table_ids * * Total defective: O(D × M × T) where D=dropped tables, M=streams, T=tables per stream * Total fixed: O(M × T) — one pass + O(1) hash lookup * * Compile: javac -d . YugabyteTest.java * Run: java unit.YugabyteTest */ public class YugabyteTest { static class CDCStreamInfo { final String streamId; final List tableIds; // backed by protobuf RepeatedPtrField (array) boolean started_deleting = false; CDCStreamInfo(String streamId, List tableIds) { this.streamId = streamId; this.tableIds = tableIds; } } // ---- DEFECTIVE: O(D × M × T) ---- static List getXReplStreamsForTable_defective( String tableId, List cdcStreamMap, int[] comparisonCount) { List streams = new ArrayList<>(); for (CDCStreamInfo stream : cdcStreamMap) { // O(M) if (stream.started_deleting) continue; // std::find on table_id list — O(T) linear scan boolean found = false; for (String tid : stream.tableIds) { // O(T) comparisonCount[0]++; if (tid.equals(tableId)) { found = true; break; } } if (found) streams.add(stream.streamId); } return streams; } static List dropXClusterStreamsOfTables_defective( Set tableIds, List cdcStreamMap, int[] comparisonCount) { List affectedStreams = new ArrayList<>(); for (String tid : tableIds) { // O(D) affectedStreams.addAll( getXReplStreamsForTable_defective(tid, cdcStreamMap, comparisonCount)); } return affectedStreams; } // ---- FIXED: single pass O(M × T) with O(1) hash lookup ---- static List dropXClusterStreamsOfTables_fixed( Set tableIds, List cdcStreamMap, int[] comparisonCount) { List affectedStreams = new ArrayList<>(); for (CDCStreamInfo stream : cdcStreamMap) { // O(M) — single pass if (stream.started_deleting) continue; for (String tid : stream.tableIds) { // O(T) per stream comparisonCount[0]++; if (tableIds.contains(tid)) { // O(1) hash set lookup affectedStreams.add(stream.streamId); break; // found one match, don't add stream twice } } } return affectedStreams; } // Build a test CDC stream map static List buildStreamMap(int M, int T, int totalTables) { List map = new ArrayList<>(); for (int i = 0; i < M; i++) { List tables = new ArrayList<>(); for (int j = 0; j < T; j++) { tables.add("table-" + ((i * T + j) % totalTables)); } map.add(new CDCStreamInfo("stream-" + i, tables)); } return map; } public static void main(String[] args) { int pass = 0, fail = 0; // -- Test 1: correctness — small scenario { List streams = new ArrayList<>(); streams.add(new CDCStreamInfo("s1", Arrays.asList("t1", "t2", "t3"))); streams.add(new CDCStreamInfo("s2", Arrays.asList("t2", "t4"))); streams.add(new CDCStreamInfo("s3", Arrays.asList("t5", "t6"))); Set toDrop = new HashSet<>(Arrays.asList("t2", "t5")); int[] cmpDef = {0}, cmpFix = {0}; List resultDef = dropXClusterStreamsOfTables_defective(toDrop, streams, cmpDef); List resultFix = dropXClusterStreamsOfTables_fixed(toDrop, streams, cmpFix); Collections.sort(resultDef); Collections.sort(resultFix); if (resultDef.equals(resultFix)) { System.out.printf("PASS test1: correctness — both=%s%n", resultDef); pass++; } else { System.out.printf("FAIL test1: defective=%s fixed=%s%n", resultDef, resultFix); fail++; } } // -- Test 2: complexity comparison { int D = 50; // tables being dropped int M = 100; // CDC streams int T = 20; // tables per stream int totalTables = 200; List streamMap = buildStreamMap(M, T, totalTables); // Drop first D tables Set toDrop = new HashSet<>(); for (int i = 0; i < D; i++) toDrop.add("table-" + i); int[] cmpDef = {0}, cmpFix = {0}; List resultDef = dropXClusterStreamsOfTables_defective(toDrop, streamMap, cmpDef); List resultFix = dropXClusterStreamsOfTables_fixed(toDrop, streamMap, cmpFix); System.out.printf("test2: defective comparisons=%d fixed comparisons=%d (D=%d M=%d T=%d)%n", cmpDef[0], cmpFix[0], D, M, T); // Defective adds a stream once per matching dropped table (may have duplicates); // fixed adds each stream at most once. Compare as sets of unique stream IDs. Set setDef = new HashSet<>(resultDef); Set setFix = new HashSet<>(resultFix); if (setDef.equals(setFix) && cmpFix[0] < cmpDef[0]) { System.out.println("PASS test2: fixed is more efficient and produces same (unique) result"); pass++; } else { System.out.printf("FAIL test2: match=%b efficient=%b%n", setDef.equals(setFix), cmpFix[0] < cmpDef[0]); fail++; } // Show expected complexity System.out.printf(" Expected defective ~O(D×M×T)=%d, actual=%d%n", D * M * T, cmpDef[0]); System.out.printf(" Expected fixed ~O(M×T)=%d, actual=%d%n", M * T, cmpFix[0]); } // -- Test 3: deleted streams are excluded { CDCStreamInfo deletingStream = new CDCStreamInfo("s-deleting", Arrays.asList("t1", "t2")); deletingStream.started_deleting = true; List streams = new ArrayList<>(); streams.add(deletingStream); streams.add(new CDCStreamInfo("s-active", Arrays.asList("t1"))); Set toDrop = new HashSet<>(Collections.singletonList("t1")); int[] cmpDef = {0}, cmpFix = {0}; List resultDef = dropXClusterStreamsOfTables_defective(toDrop, streams, cmpDef); List resultFix = dropXClusterStreamsOfTables_fixed(toDrop, streams, cmpFix); Collections.sort(resultDef); Collections.sort(resultFix); if (resultDef.equals(resultFix) && resultFix.equals(Collections.singletonList("s-active"))) { System.out.printf("PASS test3: deleting stream excluded — result=%s%n", resultFix); pass++; } else { System.out.printf("FAIL test3: def=%s fix=%s%n", resultDef, resultFix); fail++; } } // -- Test 4: empty table_ids set { List streams = buildStreamMap(10, 5, 50); int[] c1 = {0}, c2 = {0}; List r1 = dropXClusterStreamsOfTables_defective(new HashSet<>(), streams, c1); List r2 = dropXClusterStreamsOfTables_fixed(new HashSet<>(), streams, c2); if (r1.isEmpty() && r2.isEmpty()) { System.out.println("PASS test4: empty input — both return empty"); pass++; } else { System.out.printf("FAIL test4: def=%s fix=%s%n", r1, r2); fail++; } } System.out.printf("%nResults: %d passed, %d failed%n", pass, fail); if (fail > 0) System.exit(1); } }