package unit; import java.util.*; /** * django-0005: alt_constraints_name list → set * django-0006: remove_from_added / remove_from_removed lists → sets * * Models Django MigrationAutodetector.create_altered_constraints() and * create_altered_indexes() where a plain list is built during a double loop * and then searched with linear scans in filter comprehensions. * * Standalone Java — no JUnit required. */ public class AltConstraintsAlgorithm { // ----------------------------------------------------------------------- // Simulated constraint / index model objects // ----------------------------------------------------------------------- static class Constraint { final String name; final String definition; Constraint(String name, String definition) { this.name = name; this.definition = definition; } @Override public boolean equals(Object o) { if (!(o instanceof Constraint)) return false; Constraint c = (Constraint) o; return name.equals(c.name) && definition.equals(c.definition); } @Override public int hashCode() { return Objects.hash(name, definition); } } // ----------------------------------------------------------------------- // SLOW: alt_constraints_name as plain List (django-0005) // ----------------------------------------------------------------------- static Result slowAltConstraints(List oldConstraints, List newConstraints) { List altConstraints = new ArrayList<>(); List altConstraintsName = new ArrayList<>(); // ← plain list for (Constraint oldC : oldConstraints) { for (Constraint newC : newConstraints) { if (!oldC.definition.equals(newC.definition) && oldC.name.equals(newC.name)) { altConstraints.add(newC); altConstraintsName.add(newC.name); // ← append } } } List addConstraints = new ArrayList<>(); for (Constraint c : newConstraints) { if (!oldConstraints.contains(c) && !altConstraintsName.contains(c.name)) { // ← O(N) addConstraints.add(c); } } List remConstraints = new ArrayList<>(); for (Constraint c : oldConstraints) { if (!newConstraints.contains(c) && !altConstraintsName.contains(c.name)) { // ← O(N) remConstraints.add(c); } } return new Result(addConstraints, remConstraints, altConstraints); } // ----------------------------------------------------------------------- // FAST: alt_constraints_name as HashSet (django-0005 fix) // ----------------------------------------------------------------------- static Result fastAltConstraints(List oldConstraints, List newConstraints) { List altConstraints = new ArrayList<>(); Set altConstraintsName = new HashSet<>(); // ← set for (Constraint oldC : oldConstraints) { for (Constraint newC : newConstraints) { if (!oldC.definition.equals(newC.definition) && oldC.name.equals(newC.name)) { altConstraints.add(newC); altConstraintsName.add(newC.name); // ← O(1) } } } List addConstraints = new ArrayList<>(); for (Constraint c : newConstraints) { if (!oldConstraints.contains(c) && !altConstraintsName.contains(c.name)) { // ← O(1) addConstraints.add(c); } } List remConstraints = new ArrayList<>(); for (Constraint c : oldConstraints) { if (!newConstraints.contains(c) && !altConstraintsName.contains(c.name)) { // ← O(1) remConstraints.add(c); } } return new Result(addConstraints, remConstraints, altConstraints); } // ----------------------------------------------------------------------- // SLOW: remove_from_added / remove_from_removed as Lists (django-0006) // ----------------------------------------------------------------------- static long slowRemoveFromAdded(List addedIndexes, List removedIndexes) { List removeFromAdded = new ArrayList<>(); // ← plain list List removeFromRemoved = new ArrayList<>(); // ← plain list for (Constraint newIdx : addedIndexes) { for (Constraint oldIdx : removedIndexes) { // same fields, different name = rename if (newIdx.definition.equals(oldIdx.definition) && !newIdx.name.equals(oldIdx.name)) { removeFromAdded.add(newIdx); removeFromRemoved.add(oldIdx); } } } List finalAdded = new ArrayList<>(); for (Constraint idx : addedIndexes) { if (!removeFromAdded.contains(idx)) finalAdded.add(idx); // ← O(R) } List finalRemoved = new ArrayList<>(); for (Constraint idx : removedIndexes) { if (!removeFromRemoved.contains(idx)) finalRemoved.add(idx); // ← O(R) } return finalAdded.size() + finalRemoved.size(); } // ----------------------------------------------------------------------- // FAST: remove_from_added / remove_from_removed as HashSets (django-0006 fix) // ----------------------------------------------------------------------- static long fastRemoveFromAdded(List addedIndexes, List removedIndexes) { Set removeFromAdded = new HashSet<>(); // ← set Set removeFromRemoved = new HashSet<>(); // ← set for (Constraint newIdx : addedIndexes) { for (Constraint oldIdx : removedIndexes) { if (newIdx.definition.equals(oldIdx.definition) && !newIdx.name.equals(oldIdx.name)) { removeFromAdded.add(newIdx); removeFromRemoved.add(oldIdx); } } } List finalAdded = new ArrayList<>(); for (Constraint idx : addedIndexes) { if (!removeFromAdded.contains(idx)) finalAdded.add(idx); // ← O(1) } List finalRemoved = new ArrayList<>(); for (Constraint idx : removedIndexes) { if (!removeFromRemoved.contains(idx)) finalRemoved.add(idx); // ← O(1) } return finalAdded.size() + finalRemoved.size(); } // ----------------------------------------------------------------------- // Result container // ----------------------------------------------------------------------- static class Result { final List added; final List removed; final List altered; Result(List added, List removed, List altered) { this.added = added; this.removed = removed; this.altered = altered; } } // ----------------------------------------------------------------------- // Test helpers // ----------------------------------------------------------------------- static List makeConstraints(int n, String prefix) { List list = new ArrayList<>(); for (int i = 0; i < n; i++) { list.add(new Constraint(prefix + "_c" + i, "def_" + i)); } return list; } // ----------------------------------------------------------------------- // Main // ----------------------------------------------------------------------- public static void main(String[] args) { int passed = 0; int total = 0; // ---- Correctness: django-0005 (alt_constraints_name) ---- { List oldC = Arrays.asList( new Constraint("uq_a", "def_a"), new Constraint("uq_b", "def_b"), new Constraint("uq_c", "def_c") ); List newC = Arrays.asList( new Constraint("uq_a", "def_a_modified"), // altered new Constraint("uq_b", "def_b"), // unchanged new Constraint("uq_d", "def_d") // added ); Result slow = slowAltConstraints(oldC, newC); Result fast = fastAltConstraints(oldC, newC); total++; assert slow.altered.size() == fast.altered.size() : "altered size mismatch"; assert slow.added.size() == fast.added.size() : "added size: slow=" + slow.added.size() + " fast=" + fast.added.size(); assert slow.removed.size() == fast.removed.size() : "removed size mismatch"; System.out.println("PASS 1/5: correctness django-0005 (alt_constraints_name)"); passed++; } // ---- Correctness: django-0006 (remove_from_added) ---- { List added = Arrays.asList( new Constraint("idx_new_name", "fields_x_y"), // rename candidate new Constraint("idx_brand_new", "fields_z") // genuinely added ); List removed = Arrays.asList( new Constraint("idx_old_name", "fields_x_y"), // rename candidate new Constraint("idx_truly_removed", "fields_w") // truly removed ); long slow = slowRemoveFromAdded(added, removed); long fast = fastRemoveFromAdded(added, removed); total++; assert slow == fast : "remove_from_added result mismatch: slow=" + slow + " fast=" + fast; assert slow == 2 : "expected 2 remaining (1 added + 1 removed), got " + slow; System.out.println("PASS 2/5: correctness django-0006 (remove_from_added)"); passed++; } // ---- Performance: django-0005 — isolate the filter step (list vs set membership) ---- // Pre-build alt_constraints_name with N entries, then run the filter loop // over N candidates. This isolates the O(N) scan vs O(1) hash lookup. { int N = 800; // alt_constraints_name has N/2 entries; candidates has N entries List altNameList = new ArrayList<>(); Set altNameSet = new HashSet<>(); List candidates = new ArrayList<>(); for (int i = 0; i < N/2; i++) { altNameList.add("alt_" + i); altNameSet.add("alt_" + i); } for (int i = 0; i < N; i++) { // every other candidate has a name in the alt set → skip half candidates.add(new Constraint(i % 2 == 0 ? "alt_" + (i/2) : "new_" + i, "def_" + i)); } long t0 = System.nanoTime(); for (int iter = 0; iter < 200; iter++) { List r = new ArrayList<>(); for (Constraint c : candidates) { if (!altNameList.contains(c.name)) r.add(c); // ← O(N) scan } } long slowNs = (System.nanoTime() - t0) / 200; long t1 = System.nanoTime(); for (int iter = 0; iter < 200; iter++) { List r = new ArrayList<>(); for (Constraint c : candidates) { if (!altNameSet.contains(c.name)) r.add(c); // ← O(1) lookup } } long fastNs = (System.nanoTime() - t1) / 200; double ratio = (double) slowNs / fastNs; total++; System.out.printf("PERF 3/5: django-0005 N=%d slow=%dns fast=%dns ratio=%.1fx%n", N, slowNs, fastNs, ratio); assert ratio >= 5.0 : "ratio too low: " + ratio; System.out.println("PASS 3/5: django-0005 ratio >= 5x"); passed++; } // ---- Performance: django-0006 — isolate the filter step (list vs set membership) ---- { int N = 800; List removeFromAddedList = new ArrayList<>(); Set removeFromAddedSet = new HashSet<>(); List addedIndexes = new ArrayList<>(); for (int i = 0; i < N/2; i++) { Constraint c = new Constraint("rename_" + i, "fields_" + i); removeFromAddedList.add(c); removeFromAddedSet.add(c); } for (int i = 0; i < N; i++) { addedIndexes.add(new Constraint(i % 2 == 0 ? "rename_" + (i/2) : "keep_" + i, "fields_" + i)); } long t0 = System.nanoTime(); for (int iter = 0; iter < 200; iter++) { List r = new ArrayList<>(); for (Constraint idx : addedIndexes) { if (!removeFromAddedList.contains(idx)) r.add(idx); // ← O(R) } } long slowNs = (System.nanoTime() - t0) / 200; long t1 = System.nanoTime(); for (int iter = 0; iter < 200; iter++) { List r = new ArrayList<>(); for (Constraint idx : addedIndexes) { if (!removeFromAddedSet.contains(idx)) r.add(idx); // ← O(1) } } long fastNs = (System.nanoTime() - t1) / 200; double ratio = (double) slowNs / fastNs; total++; System.out.printf("PERF 4/5: django-0006 N=%d slow=%dns fast=%dns ratio=%.1fx%n", N, slowNs, fastNs, ratio); assert ratio >= 5.0 : "ratio too low: " + ratio; System.out.println("PASS 4/5: django-0006 ratio >= 5x"); passed++; } // ---- Edge case: empty old/new constraints ---- { Result slow = slowAltConstraints(new ArrayList<>(), new ArrayList<>()); Result fast = fastAltConstraints(new ArrayList<>(), new ArrayList<>()); total++; assert slow.added.isEmpty() && fast.added.isEmpty() : "empty case added"; assert slow.removed.isEmpty() && fast.removed.isEmpty() : "empty case removed"; System.out.println("PASS 5/5: edge case empty constraints"); passed++; } System.out.printf("%n%d/%d PASS%n", passed, total); if (passed < total) throw new AssertionError("Some tests failed"); } }