java-topology/defects/django/unit/AltConstraintsAlgorithm.java

336 lines
15 KiB
Java

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<Constraint> oldConstraints,
List<Constraint> newConstraints) {
List<Constraint> altConstraints = new ArrayList<>();
List<String> 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<Constraint> addConstraints = new ArrayList<>();
for (Constraint c : newConstraints) {
if (!oldConstraints.contains(c) && !altConstraintsName.contains(c.name)) { // ← O(N)
addConstraints.add(c);
}
}
List<Constraint> 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<Constraint> oldConstraints,
List<Constraint> newConstraints) {
List<Constraint> altConstraints = new ArrayList<>();
Set<String> 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<Constraint> addConstraints = new ArrayList<>();
for (Constraint c : newConstraints) {
if (!oldConstraints.contains(c) && !altConstraintsName.contains(c.name)) { // ← O(1)
addConstraints.add(c);
}
}
List<Constraint> 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<Constraint> addedIndexes,
List<Constraint> removedIndexes) {
List<Constraint> removeFromAdded = new ArrayList<>(); // ← plain list
List<Constraint> 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<Constraint> finalAdded = new ArrayList<>();
for (Constraint idx : addedIndexes) {
if (!removeFromAdded.contains(idx)) finalAdded.add(idx); // ← O(R)
}
List<Constraint> 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<Constraint> addedIndexes,
List<Constraint> removedIndexes) {
Set<Constraint> removeFromAdded = new HashSet<>(); // ← set
Set<Constraint> 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<Constraint> finalAdded = new ArrayList<>();
for (Constraint idx : addedIndexes) {
if (!removeFromAdded.contains(idx)) finalAdded.add(idx); // ← O(1)
}
List<Constraint> 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<Constraint> added;
final List<Constraint> removed;
final List<Constraint> altered;
Result(List<Constraint> added, List<Constraint> removed, List<Constraint> altered) {
this.added = added; this.removed = removed; this.altered = altered;
}
}
// -----------------------------------------------------------------------
// Test helpers
// -----------------------------------------------------------------------
static List<Constraint> makeConstraints(int n, String prefix) {
List<Constraint> 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<Constraint> oldC = Arrays.asList(
new Constraint("uq_a", "def_a"),
new Constraint("uq_b", "def_b"),
new Constraint("uq_c", "def_c")
);
List<Constraint> 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<Constraint> added = Arrays.asList(
new Constraint("idx_new_name", "fields_x_y"), // rename candidate
new Constraint("idx_brand_new", "fields_z") // genuinely added
);
List<Constraint> 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<String> altNameList = new ArrayList<>();
Set<String> altNameSet = new HashSet<>();
List<Constraint> 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<Constraint> 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<Constraint> 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<Constraint> removeFromAddedList = new ArrayList<>();
Set<Constraint> removeFromAddedSet = new HashSet<>();
List<Constraint> 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<Constraint> 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<Constraint> 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");
}
}