|
| 1 | +/* |
| 2 | +sp_IndexCleanup Adversarial Test Suite — Setup & Execute |
| 3 | +======================================================== |
| 4 | +This script: |
| 5 | + 1. Creates test tables with data and edge-case index configurations |
| 6 | + 2. Generates usage stats |
| 7 | + 3. Runs sp_IndexCleanup @dedupe_only = 1 |
| 8 | +
|
| 9 | +Output is captured by the test runner (run_tests.py) for validation. |
| 10 | +Run with: python run_tests.py |
| 11 | +
|
| 12 | +Direct execution (visual inspection only): |
| 13 | + sqlcmd -S SQL2022 -U sa -P "password" -d StackOverflow2013 -i adversarial_test.sql |
| 14 | +*/ |
| 15 | +SET NOCOUNT ON; |
| 16 | + |
| 17 | +USE StackOverflow2013; |
| 18 | +GO |
| 19 | + |
| 20 | +/* ============================================= */ |
| 21 | +/* Cleanup previous test artifacts */ |
| 22 | +/* ============================================= */ |
| 23 | +IF OBJECT_ID('dbo.test_ic_view') IS NOT NULL |
| 24 | +BEGIN |
| 25 | + IF EXISTS (SELECT 1/0 FROM sys.indexes WHERE object_id = OBJECT_ID('dbo.test_ic_view') AND name = N'cx_test_ic_view') |
| 26 | + DROP INDEX cx_test_ic_view ON dbo.test_ic_view; |
| 27 | +END; |
| 28 | +GO |
| 29 | +IF OBJECT_ID('dbo.test_ic_view') IS NOT NULL DROP VIEW dbo.test_ic_view; |
| 30 | +GO |
| 31 | +DROP TABLE IF EXISTS dbo.test_ic_basic; |
| 32 | +DROP TABLE IF EXISTS dbo.test_ic_uc; |
| 33 | +DROP TABLE IF EXISTS dbo.test_ic_filtered; |
| 34 | +DROP TABLE IF EXISTS dbo.test_ic_heap; |
| 35 | +DROP TABLE IF EXISTS dbo.test_ic_multi; |
| 36 | +DROP TABLE IF EXISTS dbo.test_ic_view_base; |
| 37 | +GO |
| 38 | + |
| 39 | +/* ============================================= */ |
| 40 | +/* Create test tables with data */ |
| 41 | +/* ============================================= */ |
| 42 | + |
| 43 | +CREATE TABLE dbo.test_ic_basic |
| 44 | +( |
| 45 | + id bigint IDENTITY(1,1) NOT NULL PRIMARY KEY CLUSTERED, |
| 46 | + col_a integer NOT NULL, |
| 47 | + col_b integer NOT NULL, |
| 48 | + col_c integer NOT NULL, |
| 49 | + col_d integer NOT NULL, |
| 50 | + col_e nvarchar(100) NULL, |
| 51 | + col_f datetime NOT NULL DEFAULT GETDATE() |
| 52 | +); |
| 53 | + |
| 54 | +CREATE TABLE dbo.test_ic_uc |
| 55 | +( |
| 56 | + id bigint IDENTITY(1,1) NOT NULL PRIMARY KEY CLUSTERED, |
| 57 | + col_a integer NOT NULL, |
| 58 | + col_b integer NOT NULL, |
| 59 | + col_c integer NOT NULL, |
| 60 | + col_d integer NOT NULL, |
| 61 | + col_e nvarchar(100) NULL |
| 62 | +); |
| 63 | + |
| 64 | +CREATE TABLE dbo.test_ic_filtered |
| 65 | +( |
| 66 | + id bigint IDENTITY(1,1) NOT NULL PRIMARY KEY CLUSTERED, |
| 67 | + col_a integer NOT NULL, |
| 68 | + col_b integer NOT NULL, |
| 69 | + status_code integer NOT NULL |
| 70 | +); |
| 71 | + |
| 72 | +CREATE TABLE dbo.test_ic_heap |
| 73 | +( |
| 74 | + col_a integer NOT NULL, |
| 75 | + col_b integer NOT NULL, |
| 76 | + col_c integer NOT NULL |
| 77 | +); |
| 78 | + |
| 79 | +CREATE TABLE dbo.test_ic_multi |
| 80 | +( |
| 81 | + id bigint IDENTITY(1,1) NOT NULL PRIMARY KEY CLUSTERED, |
| 82 | + col_a integer NOT NULL, |
| 83 | + col_b integer NOT NULL |
| 84 | +); |
| 85 | + |
| 86 | +CREATE TABLE dbo.test_ic_view_base |
| 87 | +( |
| 88 | + id bigint IDENTITY(1,1) NOT NULL PRIMARY KEY CLUSTERED, |
| 89 | + col_a integer NOT NULL, |
| 90 | + col_b integer NOT NULL, |
| 91 | + col_c integer NOT NULL |
| 92 | +); |
| 93 | +GO |
| 94 | + |
| 95 | +CREATE VIEW dbo.test_ic_view WITH SCHEMABINDING |
| 96 | +AS |
| 97 | +SELECT |
| 98 | + col_a = tvb.col_a, |
| 99 | + col_b = tvb.col_b, |
| 100 | + row_count = COUNT_BIG(*) |
| 101 | +FROM dbo.test_ic_view_base AS tvb |
| 102 | +GROUP BY tvb.col_a, tvb.col_b; |
| 103 | +GO |
| 104 | + |
| 105 | +/* Populate with 10K+ rows */ |
| 106 | +INSERT INTO dbo.test_ic_basic (col_a, col_b, col_c, col_d, col_e) |
| 107 | +SELECT TOP (10000) ABS(CHECKSUM(NEWID())) % 1000, ABS(CHECKSUM(NEWID())) % 500, |
| 108 | + ABS(CHECKSUM(NEWID())) % 200, ABS(CHECKSUM(NEWID())) % 100, LEFT(NEWID(), 20) |
| 109 | +FROM sys.all_objects AS a CROSS JOIN sys.all_objects AS b; |
| 110 | + |
| 111 | +INSERT INTO dbo.test_ic_uc (col_a, col_b, col_c, col_d, col_e) |
| 112 | +SELECT TOP (10000) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)), |
| 113 | + ABS(CHECKSUM(NEWID())) % 500, ABS(CHECKSUM(NEWID())) % 200, |
| 114 | + ABS(CHECKSUM(NEWID())) % 100, LEFT(NEWID(), 20) |
| 115 | +FROM sys.all_objects AS a CROSS JOIN sys.all_objects AS b; |
| 116 | + |
| 117 | +INSERT INTO dbo.test_ic_filtered (col_a, col_b, status_code) |
| 118 | +SELECT TOP (10000) ABS(CHECKSUM(NEWID())) % 1000, ABS(CHECKSUM(NEWID())) % 500, |
| 119 | + ABS(CHECKSUM(NEWID())) % 5 |
| 120 | +FROM sys.all_objects AS a CROSS JOIN sys.all_objects AS b; |
| 121 | + |
| 122 | +INSERT INTO dbo.test_ic_heap (col_a, col_b, col_c) |
| 123 | +SELECT TOP (10000) ABS(CHECKSUM(NEWID())) % 1000, ABS(CHECKSUM(NEWID())) % 500, |
| 124 | + ABS(CHECKSUM(NEWID())) % 200 |
| 125 | +FROM sys.all_objects AS a CROSS JOIN sys.all_objects AS b; |
| 126 | + |
| 127 | +INSERT INTO dbo.test_ic_multi (col_a, col_b) |
| 128 | +SELECT TOP (10000) ABS(CHECKSUM(NEWID())) % 1000, ABS(CHECKSUM(NEWID())) % 500 |
| 129 | +FROM sys.all_objects AS a CROSS JOIN sys.all_objects AS b; |
| 130 | + |
| 131 | +INSERT INTO dbo.test_ic_view_base (col_a, col_b, col_c) |
| 132 | +SELECT TOP (10000) ABS(CHECKSUM(NEWID())) % 100, ABS(CHECKSUM(NEWID())) % 50, |
| 133 | + ABS(CHECKSUM(NEWID())) % 200 |
| 134 | +FROM sys.all_objects AS a CROSS JOIN sys.all_objects AS b; |
| 135 | +GO |
| 136 | + |
| 137 | +/* ============================================= */ |
| 138 | +/* Create test indexes */ |
| 139 | +/* ============================================= */ |
| 140 | + |
| 141 | +/* Group 1: UC as superset (#721, #724) */ |
| 142 | +ALTER TABLE dbo.test_ic_uc ADD CONSTRAINT uq_uc_abc UNIQUE (col_a, col_b, col_c); |
| 143 | +CREATE NONCLUSTERED INDEX ix_uc_ab ON dbo.test_ic_uc (col_a, col_b); |
| 144 | +CREATE NONCLUSTERED INDEX ix_uc_ab_inc ON dbo.test_ic_uc (col_a, col_b) INCLUDE (col_e); |
| 145 | +CREATE NONCLUSTERED INDEX ix_uc_bc ON dbo.test_ic_uc (col_b, col_c); |
| 146 | +CREATE UNIQUE NONCLUSTERED INDEX uix_uc_acd ON dbo.test_ic_uc (col_a, col_c, col_d); |
| 147 | +CREATE NONCLUSTERED INDEX ix_uc_ac ON dbo.test_ic_uc (col_a, col_c); |
| 148 | +ALTER TABLE dbo.test_ic_uc ADD CONSTRAINT uq_uc_ad UNIQUE (col_a, col_d); |
| 149 | + |
| 150 | +/* Group 2: Sort direction */ |
| 151 | +CREATE INDEX ix_sort_a_desc ON dbo.test_ic_basic (col_a DESC); |
| 152 | +CREATE INDEX ix_sort_a_desc2 ON dbo.test_ic_basic (col_a DESC); |
| 153 | +CREATE INDEX ix_sort_a_asc ON dbo.test_ic_basic (col_a ASC); |
| 154 | +CREATE INDEX ix_sort_ab_asc ON dbo.test_ic_basic (col_a ASC, col_b ASC); |
| 155 | +CREATE INDEX ix_sort_ab_mixed ON dbo.test_ic_basic (col_a DESC, col_b ASC); |
| 156 | + |
| 157 | +/* Group 3: Filtered indexes */ |
| 158 | +CREATE INDEX ix_filt_a_s1 ON dbo.test_ic_filtered (col_a) WHERE status_code = 1; |
| 159 | +CREATE INDEX ix_filt_a_s1_dup ON dbo.test_ic_filtered (col_a) WHERE status_code = 1; |
| 160 | +CREATE INDEX ix_filt_a_s2 ON dbo.test_ic_filtered (col_a) WHERE status_code = 2; |
| 161 | +CREATE INDEX ix_filt_ab_s3 ON dbo.test_ic_filtered (col_a, col_b) WHERE status_code = 3; |
| 162 | +CREATE INDEX ix_filt_a_s3 ON dbo.test_ic_filtered (col_a) WHERE status_code = 3; |
| 163 | +CREATE INDEX ix_filt_ab_s4 ON dbo.test_ic_filtered (col_a, col_b) WHERE status_code = 4; |
| 164 | +CREATE INDEX ix_filt_a_s0 ON dbo.test_ic_filtered (col_a) WHERE status_code = 0; |
| 165 | + |
| 166 | +/* Group 4a: Key Duplicate — same keys, different includes, no wider index */ |
| 167 | +CREATE INDEX ix_inc_f_inc_b ON dbo.test_ic_basic (col_f) INCLUDE (col_b); |
| 168 | +CREATE INDEX ix_inc_f_inc_c ON dbo.test_ic_basic (col_f) INCLUDE (col_c); |
| 169 | + |
| 170 | +/* Group 4b: Key Subset — narrower key with includes absorbed by wider key */ |
| 171 | +CREATE INDEX ix_inc_cd_inc_e ON dbo.test_ic_basic (col_c, col_d) INCLUDE (col_e); |
| 172 | +CREATE INDEX ix_inc_c_inc_b ON dbo.test_ic_basic (col_c) INCLUDE (col_b); |
| 173 | + |
| 174 | +/* Group 5: Indexed view */ |
| 175 | +CREATE UNIQUE CLUSTERED INDEX cx_test_ic_view ON dbo.test_ic_view (col_a, col_b); |
| 176 | +CREATE NONCLUSTERED INDEX ix_view_a ON dbo.test_ic_view (col_a); |
| 177 | +CREATE NONCLUSTERED INDEX ix_view_a_dup ON dbo.test_ic_view (col_a); |
| 178 | + |
| 179 | +/* Group 6: Heap */ |
| 180 | +CREATE NONCLUSTERED INDEX ix_heap_a ON dbo.test_ic_heap (col_a); |
| 181 | +CREATE NONCLUSTERED INDEX ix_heap_a_dup ON dbo.test_ic_heap (col_a); |
| 182 | + |
| 183 | +/* Group 7: Multi-table isolation */ |
| 184 | +CREATE INDEX ix_multi_a ON dbo.test_ic_multi (col_a); |
| 185 | +CREATE INDEX ix_basic_col_d ON dbo.test_ic_basic (col_d); |
| 186 | +GO |
| 187 | + |
| 188 | +/* ============================================= */ |
| 189 | +/* Generate usage stats */ |
| 190 | +/* ============================================= */ |
| 191 | +DECLARE @c bigint, @i integer = 0; |
| 192 | +WHILE @i < 10 |
| 193 | +BEGIN |
| 194 | + SELECT @c = COUNT_BIG(*) FROM dbo.test_ic_uc WITH (INDEX = uq_uc_abc) WHERE col_a = 1; |
| 195 | + SELECT @c = COUNT_BIG(*) FROM dbo.test_ic_uc WITH (INDEX = ix_uc_ab) WHERE col_a = 1; |
| 196 | + SELECT @c = COUNT_BIG(*) FROM dbo.test_ic_uc WITH (INDEX = ix_uc_ab_inc) WHERE col_a = 1; |
| 197 | + SELECT @c = COUNT_BIG(*) FROM dbo.test_ic_uc WITH (INDEX = ix_uc_bc) WHERE col_b = 1; |
| 198 | + SELECT @c = COUNT_BIG(*) FROM dbo.test_ic_uc WITH (INDEX = uix_uc_acd) WHERE col_a = 1; |
| 199 | + SELECT @c = COUNT_BIG(*) FROM dbo.test_ic_uc WITH (INDEX = ix_uc_ac) WHERE col_a = 1; |
| 200 | + SELECT @c = COUNT_BIG(*) FROM dbo.test_ic_uc WITH (INDEX = uq_uc_ad) WHERE col_a = 1; |
| 201 | + SELECT @c = COUNT_BIG(*) FROM dbo.test_ic_basic WITH (INDEX = ix_sort_a_desc) WHERE col_a > 500; |
| 202 | + SELECT @c = COUNT_BIG(*) FROM dbo.test_ic_basic WITH (INDEX = ix_sort_a_desc2) WHERE col_a > 600; |
| 203 | + SELECT @c = COUNT_BIG(*) FROM dbo.test_ic_basic WITH (INDEX = ix_sort_a_asc) WHERE col_a < 100; |
| 204 | + SELECT @c = COUNT_BIG(*) FROM dbo.test_ic_basic WITH (INDEX = ix_sort_ab_asc) WHERE col_a = 1; |
| 205 | + SELECT @c = COUNT_BIG(*) FROM dbo.test_ic_basic WITH (INDEX = ix_sort_ab_mixed) WHERE col_a = 2; |
| 206 | + SELECT @c = COUNT_BIG(*) FROM dbo.test_ic_filtered WITH (INDEX = ix_filt_a_s1) WHERE col_a > 500 AND status_code = 1; |
| 207 | + SELECT @c = COUNT_BIG(*) FROM dbo.test_ic_filtered WITH (INDEX = ix_filt_a_s1_dup) WHERE col_a > 600 AND status_code = 1; |
| 208 | + SELECT @c = COUNT_BIG(*) FROM dbo.test_ic_filtered WITH (INDEX = ix_filt_a_s2) WHERE col_a > 500 AND status_code = 2; |
| 209 | + SELECT @c = COUNT_BIG(*) FROM dbo.test_ic_filtered WITH (INDEX = ix_filt_ab_s3) WHERE col_a = 1 AND status_code = 3; |
| 210 | + SELECT @c = COUNT_BIG(*) FROM dbo.test_ic_filtered WITH (INDEX = ix_filt_a_s3) WHERE col_a = 2 AND status_code = 3; |
| 211 | + SELECT @c = COUNT_BIG(*) FROM dbo.test_ic_filtered WITH (INDEX = ix_filt_ab_s4) WHERE col_a = 1 AND status_code = 4; |
| 212 | + SELECT @c = COUNT_BIG(*) FROM dbo.test_ic_filtered WITH (INDEX = ix_filt_a_s0) WHERE col_a = 1 AND status_code = 0; |
| 213 | + SELECT @c = COUNT_BIG(*) FROM dbo.test_ic_basic WITH (INDEX = ix_inc_f_inc_b) WHERE col_f > '2020-01-01'; |
| 214 | + SELECT @c = COUNT_BIG(*) FROM dbo.test_ic_basic WITH (INDEX = ix_inc_f_inc_c) WHERE col_f > '2021-01-01'; |
| 215 | + SELECT @c = COUNT_BIG(*) FROM dbo.test_ic_basic WITH (INDEX = ix_inc_cd_inc_e) WHERE col_c = 1 AND col_d = 1; |
| 216 | + SELECT @c = COUNT_BIG(*) FROM dbo.test_ic_basic WITH (INDEX = ix_inc_c_inc_b) WHERE col_c = 2; |
| 217 | + SELECT @c = COUNT_BIG(*) FROM dbo.test_ic_view WITH (INDEX = ix_view_a, NOEXPAND) WHERE col_a = 1; |
| 218 | + SELECT @c = COUNT_BIG(*) FROM dbo.test_ic_view WITH (INDEX = ix_view_a_dup, NOEXPAND) WHERE col_a = 2; |
| 219 | + SELECT @c = COUNT_BIG(*) FROM dbo.test_ic_heap WITH (INDEX = ix_heap_a) WHERE col_a = 1; |
| 220 | + SELECT @c = COUNT_BIG(*) FROM dbo.test_ic_heap WITH (INDEX = ix_heap_a_dup) WHERE col_a = 2; |
| 221 | + SELECT @c = COUNT_BIG(*) FROM dbo.test_ic_multi WITH (INDEX = ix_multi_a) WHERE col_a = 1; |
| 222 | + SELECT @c = COUNT_BIG(*) FROM dbo.test_ic_basic WITH (INDEX = ix_basic_col_d) WHERE col_d = 1; |
| 223 | + SELECT @i += 1; |
| 224 | +END; |
| 225 | +GO |
| 226 | + |
| 227 | +/* ============================================= */ |
| 228 | +/* Run sp_IndexCleanup */ |
| 229 | +/* ============================================= */ |
| 230 | +EXECUTE dbo.sp_IndexCleanup |
| 231 | + @database_name = N'StackOverflow2013', |
| 232 | + @dedupe_only = 1; |
| 233 | +GO |
| 234 | + |
| 235 | +/* ============================================= */ |
| 236 | +/* Cleanup */ |
| 237 | +/* ============================================= */ |
| 238 | +IF OBJECT_ID('dbo.test_ic_view') IS NOT NULL |
| 239 | +BEGIN |
| 240 | + DROP INDEX ix_view_a ON dbo.test_ic_view; |
| 241 | + DROP INDEX ix_view_a_dup ON dbo.test_ic_view; |
| 242 | + DROP INDEX cx_test_ic_view ON dbo.test_ic_view; |
| 243 | +END; |
| 244 | +GO |
| 245 | +IF OBJECT_ID('dbo.test_ic_view') IS NOT NULL DROP VIEW dbo.test_ic_view; |
| 246 | +GO |
| 247 | +DROP TABLE IF EXISTS dbo.test_ic_basic; |
| 248 | +DROP TABLE IF EXISTS dbo.test_ic_uc; |
| 249 | +DROP TABLE IF EXISTS dbo.test_ic_filtered; |
| 250 | +DROP TABLE IF EXISTS dbo.test_ic_heap; |
| 251 | +DROP TABLE IF EXISTS dbo.test_ic_multi; |
| 252 | +DROP TABLE IF EXISTS dbo.test_ic_view_base; |
| 253 | +GO |
0 commit comments