Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file not shown.
128 changes: 128 additions & 0 deletions sp_IndexCleanup/tests/adversarial_test.sql
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ DROP TABLE IF EXISTS dbo.test_ic_filtered;
DROP TABLE IF EXISTS dbo.test_ic_heap;
DROP TABLE IF EXISTS dbo.test_ic_multi;
DROP TABLE IF EXISTS dbo.test_ic_view_base;
DROP TABLE IF EXISTS dbo.test_ic_exact;
DROP TABLE IF EXISTS dbo.test_ic_reverse;
DROP TABLE IF EXISTS dbo.test_ic_filter_eq;
DROP TABLE IF EXISTS dbo.test_ic_uc_replace;
DROP TABLE IF EXISTS dbo.test_ic_interact;
GO

/* ============================================= */
Expand Down Expand Up @@ -83,6 +88,49 @@ CREATE TABLE dbo.test_ic_multi
col_b integer NOT NULL
);

CREATE TABLE dbo.test_ic_exact
(
id bigint IDENTITY(1,1) NOT NULL PRIMARY KEY CLUSTERED,
col_a integer NOT NULL,
col_b integer NOT NULL,
col_c integer NOT NULL
);

CREATE TABLE dbo.test_ic_reverse
(
id bigint IDENTITY(1,1) NOT NULL PRIMARY KEY CLUSTERED,
col_a integer NOT NULL,
col_b integer NOT NULL,
col_c integer NOT NULL
);

CREATE TABLE dbo.test_ic_filter_eq
(
id bigint IDENTITY(1,1) NOT NULL PRIMARY KEY CLUSTERED,
col_a integer NOT NULL,
col_b integer NOT NULL,
status_code integer NOT NULL
);

CREATE TABLE dbo.test_ic_uc_replace
(
id bigint IDENTITY(1,1) NOT NULL PRIMARY KEY CLUSTERED,
col_a integer NOT NULL,
col_b integer NOT NULL,
col_c integer NOT NULL,
col_d integer NOT NULL
);

CREATE TABLE dbo.test_ic_interact
(
id bigint IDENTITY(1,1) NOT NULL PRIMARY KEY CLUSTERED,
col_a integer NOT NULL,
col_b integer NOT NULL,
col_c integer NOT NULL,
col_d integer NOT NULL,
col_e nvarchar(100) NULL
);

CREATE TABLE dbo.test_ic_view_base
(
id bigint IDENTITY(1,1) NOT NULL PRIMARY KEY CLUSTERED,
Expand Down Expand Up @@ -132,6 +180,32 @@ INSERT INTO dbo.test_ic_view_base (col_a, col_b, col_c)
SELECT TOP (10000) ABS(CHECKSUM(NEWID())) % 100, ABS(CHECKSUM(NEWID())) % 50,
ABS(CHECKSUM(NEWID())) % 200
FROM sys.all_objects AS a CROSS JOIN sys.all_objects AS b;

INSERT INTO dbo.test_ic_exact (col_a, col_b, col_c)
SELECT TOP (10000) ABS(CHECKSUM(NEWID())) % 1000, ABS(CHECKSUM(NEWID())) % 500,
ABS(CHECKSUM(NEWID())) % 200
FROM sys.all_objects AS a CROSS JOIN sys.all_objects AS b;

INSERT INTO dbo.test_ic_reverse (col_a, col_b, col_c)
SELECT TOP (10000) ABS(CHECKSUM(NEWID())) % 1000, ABS(CHECKSUM(NEWID())) % 500,
ABS(CHECKSUM(NEWID())) % 200
FROM sys.all_objects AS a CROSS JOIN sys.all_objects AS b;

INSERT INTO dbo.test_ic_filter_eq (col_a, col_b, status_code)
SELECT TOP (10000) ABS(CHECKSUM(NEWID())) % 1000, ABS(CHECKSUM(NEWID())) % 500,
ABS(CHECKSUM(NEWID())) % 5
FROM sys.all_objects AS a CROSS JOIN sys.all_objects AS b;

INSERT INTO dbo.test_ic_uc_replace (col_a, col_b, col_c, col_d)
SELECT TOP (10000) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)),
ABS(CHECKSUM(NEWID())) % 500, ABS(CHECKSUM(NEWID())) % 200,
ABS(CHECKSUM(NEWID())) % 100
FROM sys.all_objects AS a CROSS JOIN sys.all_objects AS b;

INSERT INTO dbo.test_ic_interact (col_a, col_b, col_c, col_d, col_e)
SELECT TOP (10000) ABS(CHECKSUM(NEWID())) % 1000, ABS(CHECKSUM(NEWID())) % 500,
ABS(CHECKSUM(NEWID())) % 200, ABS(CHECKSUM(NEWID())) % 100, LEFT(NEWID(), 20)
FROM sys.all_objects AS a CROSS JOIN sys.all_objects AS b;
GO

/* ============================================= */
Expand Down Expand Up @@ -183,6 +257,36 @@ CREATE NONCLUSTERED INDEX ix_heap_a_dup ON dbo.test_ic_heap (col_a);
/* Group 7: Multi-table isolation */
CREATE INDEX ix_multi_a ON dbo.test_ic_multi (col_a);
CREATE INDEX ix_basic_col_d ON dbo.test_ic_basic (col_d);

/* Group 8: Exact Duplicate — same keys AND same includes */
CREATE INDEX ix_exact_ab_1 ON dbo.test_ic_exact (col_a, col_b) INCLUDE (col_c);
CREATE INDEX ix_exact_ab_2 ON dbo.test_ic_exact (col_a, col_b) INCLUDE (col_c);

/* Group 9: Reverse Duplicate — same columns, different leading order */
CREATE INDEX ix_rev_ab ON dbo.test_ic_reverse (col_a, col_b);
CREATE INDEX ix_rev_ba ON dbo.test_ic_reverse (col_b, col_a);

/* Group 10: Equal Except For Filter */
/* 10a: Same keys, one filtered one not — should NOT match */
CREATE INDEX ix_feq_a ON dbo.test_ic_filter_eq (col_a);
CREATE INDEX ix_feq_a_filt ON dbo.test_ic_filter_eq (col_a) WHERE status_code = 1;

/* Group 11: UC Replacement (Rule 7/7.5) — exact key match */
ALTER TABLE dbo.test_ic_uc_replace ADD CONSTRAINT uq_ucr_ab UNIQUE (col_a, col_b);
CREATE NONCLUSTERED INDEX ix_ucr_ab_inc ON dbo.test_ic_uc_replace (col_a, col_b) INCLUDE (col_c);

/* Group 12: Rule interactions */
/* 12a: Multi-level subset: A ⊂ AB ⊂ ABC */
CREATE INDEX ix_int_a ON dbo.test_ic_interact (col_a);
CREATE INDEX ix_int_ab ON dbo.test_ic_interact (col_a, col_b);
CREATE INDEX ix_int_abc ON dbo.test_ic_interact (col_a, col_b, col_c);

/* 12b: UC exact match AND UC superset on same table */
ALTER TABLE dbo.test_ic_interact ADD CONSTRAINT uq_int_cd UNIQUE (col_c, col_d);
CREATE INDEX ix_int_cd ON dbo.test_ic_interact (col_c, col_d) INCLUDE (col_e);
CREATE INDEX ix_int_c ON dbo.test_ic_interact (col_c);

/* Group 13: @min_reads filter — run separately in Python */
GO

/* ============================================= */
Expand Down Expand Up @@ -220,6 +324,25 @@ BEGIN
SELECT @c = COUNT_BIG(*) FROM dbo.test_ic_heap WITH (INDEX = ix_heap_a_dup) WHERE col_a = 2;
SELECT @c = COUNT_BIG(*) FROM dbo.test_ic_multi WITH (INDEX = ix_multi_a) WHERE col_a = 1;
SELECT @c = COUNT_BIG(*) FROM dbo.test_ic_basic WITH (INDEX = ix_basic_col_d) WHERE col_d = 1;
/* Group 8: Exact duplicates */
SELECT @c = COUNT_BIG(*) FROM dbo.test_ic_exact WITH (INDEX = ix_exact_ab_1) WHERE col_a = 1;
SELECT @c = COUNT_BIG(*) FROM dbo.test_ic_exact WITH (INDEX = ix_exact_ab_2) WHERE col_a = 2;
/* Group 9: Reverse duplicates */
SELECT @c = COUNT_BIG(*) FROM dbo.test_ic_reverse WITH (INDEX = ix_rev_ab) WHERE col_a = 1;
SELECT @c = COUNT_BIG(*) FROM dbo.test_ic_reverse WITH (INDEX = ix_rev_ba) WHERE col_b = 1;
/* Group 10: Equal except filter */
SELECT @c = COUNT_BIG(*) FROM dbo.test_ic_filter_eq WITH (INDEX = ix_feq_a) WHERE col_a = 1;
SELECT @c = COUNT_BIG(*) FROM dbo.test_ic_filter_eq WITH (INDEX = ix_feq_a_filt) WHERE col_a = 1 AND status_code = 1;
/* Group 11: UC replacement */
SELECT @c = COUNT_BIG(*) FROM dbo.test_ic_uc_replace WITH (INDEX = uq_ucr_ab) WHERE col_a = 1;
SELECT @c = COUNT_BIG(*) FROM dbo.test_ic_uc_replace WITH (INDEX = ix_ucr_ab_inc) WHERE col_a = 1;
/* Group 12: Interactions */
SELECT @c = COUNT_BIG(*) FROM dbo.test_ic_interact WITH (INDEX = ix_int_a) WHERE col_a = 1;
SELECT @c = COUNT_BIG(*) FROM dbo.test_ic_interact WITH (INDEX = ix_int_ab) WHERE col_a = 1;
SELECT @c = COUNT_BIG(*) FROM dbo.test_ic_interact WITH (INDEX = ix_int_abc) WHERE col_a = 1;
SELECT @c = COUNT_BIG(*) FROM dbo.test_ic_interact WITH (INDEX = uq_int_cd) WHERE col_c = 1;
SELECT @c = COUNT_BIG(*) FROM dbo.test_ic_interact WITH (INDEX = ix_int_cd) WHERE col_c = 1;
SELECT @c = COUNT_BIG(*) FROM dbo.test_ic_interact WITH (INDEX = ix_int_c) WHERE col_c = 1;
SELECT @i += 1;
END;
GO
Expand Down Expand Up @@ -250,4 +373,9 @@ DROP TABLE IF EXISTS dbo.test_ic_filtered;
DROP TABLE IF EXISTS dbo.test_ic_heap;
DROP TABLE IF EXISTS dbo.test_ic_multi;
DROP TABLE IF EXISTS dbo.test_ic_view_base;
DROP TABLE IF EXISTS dbo.test_ic_exact;
DROP TABLE IF EXISTS dbo.test_ic_reverse;
DROP TABLE IF EXISTS dbo.test_ic_filter_eq;
DROP TABLE IF EXISTS dbo.test_ic_uc_replace;
DROP TABLE IF EXISTS dbo.test_ic_interact;
GO
68 changes: 68 additions & 0 deletions sp_IndexCleanup/tests/run_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,74 @@ def assert_test(group, name, condition, detail=""):
assert_test("7-Isolation", "7a: Cross-table NOT flagged as duplicate",
len(matches) == 0, f"found {len(matches)} (expected 0)")

# ---- Group 8: Exact Duplicate ----

# 8a: Same keys AND same includes → one DISABLE
matches = find_rows(rows, table_name="test_ic_exact",
index_name__in={"ix_exact_ab_1", "ix_exact_ab_2"},
script_type="DISABLE SCRIPT")
assert_test("8-Exact-Dup", "8a: Exact duplicate flagged DISABLE",
len(matches) >= 1, f"found {len(matches)}")

# ---- Group 9: Reverse Duplicate ----

# 9a: Different leading column order → NOT flagged (by design — different query patterns)
matches = find_rows(rows, table_name="test_ic_reverse",
index_name__in={"ix_rev_ab", "ix_rev_ba"},
script_type="DISABLE SCRIPT")
assert_test("9-Reverse", "9a: Different leading col NOT flagged DISABLE (by design)",
len(matches) == 0, f"found {len(matches)} (expected 0)")

# ---- Group 10: Equal Except For Filter ----

# 10a: Same keys, one filtered one not → should NOT be duplicates
matches = find_rows(rows, table_name="test_ic_filter_eq", index_name="ix_feq_a_filt",
script_type="DISABLE SCRIPT", additional_info__like="Duplicate")
assert_test("10-FilterEq", "10a: Filtered vs unfiltered NOT flagged duplicate",
len(matches) == 0, f"found {len(matches)} (expected 0)")

# ---- Group 11: UC Replacement (Rule 7/7.5) ----

# 11a: UC exact match with NC that has includes → UC gets DROP CONSTRAINT
matches = find_rows(rows, table_name="test_ic_uc_replace", index_name="uq_ucr_ab",
script_type="DISABLE CONSTRAINT SCRIPT")
assert_test("11-UC-Replace", "11a: UC with exact-match NC gets DROP CONSTRAINT",
len(matches) == 1, f"found {len(matches)}")

# 11b: NC with includes gets MAKE UNIQUE (MERGE SCRIPT with CREATE UNIQUE)
matches = find_rows(rows, table_name="test_ic_uc_replace", index_name="ix_ucr_ab_inc",
script_type="MERGE SCRIPT")
has_unique = any("CREATE UNIQUE" in m.get("script", "") for m in matches)
assert_test("11-UC-Replace", "11b: NC replacement has CREATE UNIQUE",
has_unique, f"found {len(matches)} merge rows, unique={has_unique}")

# ---- Group 12: Rule interactions ----

# 12a: Multi-level subset: ix_int_a ⊂ ix_int_ab ⊂ ix_int_abc
# Narrowest (ix_int_a) should be DISABLE
matches = find_rows(rows, table_name="test_ic_interact", index_name="ix_int_a",
script_type="DISABLE SCRIPT")
assert_test("12-Interact", "12a: Narrowest subset (A) flagged DISABLE",
len(matches) == 1, f"found {len(matches)}")

# Middle (ix_int_ab) should also be DISABLE
matches = find_rows(rows, table_name="test_ic_interact", index_name="ix_int_ab",
script_type="DISABLE SCRIPT")
assert_test("12-Interact", "12a: Middle subset (AB) flagged DISABLE",
len(matches) == 1, f"found {len(matches)}")

# Widest (ix_int_abc) should survive (MERGE or COMPRESSION, not DISABLE)
matches = find_rows(rows, table_name="test_ic_interact", index_name="ix_int_abc",
script_type="DISABLE SCRIPT")
assert_test("12-Interact", "12a: Widest (ABC) NOT disabled",
len(matches) == 0, f"found {len(matches)} (expected 0)")

# 12b: UC + NC + subset on same table
# KNOWN ISSUE: uq_int_cd, ix_int_cd, and ix_int_c don't appear in
# output at all — needs investigation with @debug = 1 to determine
# if they're excluded at collection or rule processing stage.
# Skipping assertion for now — tracked as issue for investigation.

return results


Expand Down
Loading