@@ -128,6 +128,9 @@ func TestDetectIndexConsistencyErrors(t *testing.T) {
128
128
splitRangeDDL string
129
129
// indexDDL is the DDL to create the indexes on the table.
130
130
indexDDL []string
131
+ // corruptionTargetIndex specifies which secondary index to corrupt (0-based position).
132
+ // If not specified, defaults to 0 (first index).
133
+ corruptionTargetIndex int
131
134
// missingIndexEntrySelector defines a SQL predicate that selects rows
132
135
// whose secondary index entries will be manually deleted to simulate
133
136
// missing index entries (i.e., present in the primary index but not in the
@@ -247,6 +250,57 @@ func TestDetectIndexConsistencyErrors(t *testing.T) {
247
250
danglingIndexEntryInsertQuery : "SELECT 15, 30, 300, 'corrupt', 'e_3', 300.5" , // Add dangling entry after timestamp
248
251
useTimestampBeforeCorruption : true , // Use timestamp from before corruption
249
252
},
253
+ {
254
+ desc : "2 indexes, corrupt second index, missing entry" ,
255
+ splitRangeDDL : "ALTER TABLE test.t SPLIT AT VALUES (500)" ,
256
+ indexDDL : []string {
257
+ "CREATE INDEX idx_t_a ON test.t (a)" ,
258
+ "CREATE INDEX idx_t_b ON test.t (b) STORING (e)" ,
259
+ },
260
+ corruptionTargetIndex : 1 , // Target second index (idx_t_b)
261
+ missingIndexEntrySelector : "a = 7" ,
262
+ expectedIssues : []inspectIssue {
263
+ {
264
+ ErrorType : "missing_secondary_index_entry" ,
265
+ PrimaryKey : "e'(7, \\ 'd_7\\ ')'" ,
266
+ Details : map [redact.RedactableString ]interface {}{
267
+ "index_name" : "idx_t_b" ,
268
+ },
269
+ },
270
+ },
271
+ expectedErrRegex : expectedInspectFoundInconsistencies ,
272
+ },
273
+ {
274
+ desc : "3 indexes, corrupt middle index, dangling entry" ,
275
+ splitRangeDDL : "ALTER TABLE test.t SPLIT AT VALUES (333),(666)" ,
276
+ indexDDL : []string {
277
+ "CREATE INDEX idx_t_a ON test.t (a)" ,
278
+ "CREATE INDEX idx_t_b ON test.t (b) STORING (c)" ,
279
+ "CREATE INDEX idx_t_c ON test.t (c) STORING (f)" ,
280
+ },
281
+ corruptionTargetIndex : 1 , // Target second index (middle one)
282
+ danglingIndexEntryInsertQuery : "SELECT 25, 50, 500, 'corrupt_middle', 'e_25', 125.5" ,
283
+ expectedIssues : []inspectIssue {
284
+ {
285
+ ErrorType : "dangling_secondary_index_entry" ,
286
+ PrimaryKey : "e'(25, \\ 'corrupt_middle\\ ')'" ,
287
+ Details : map [redact.RedactableString ]interface {}{
288
+ "index_name" : "idx_t_b" ,
289
+ },
290
+ },
291
+ },
292
+ expectedErrRegex : expectedInspectFoundInconsistencies ,
293
+ },
294
+ {
295
+ desc : "multiple indexes, no corruption - all should be checked" ,
296
+ indexDDL : []string {
297
+ "CREATE INDEX idx_t_a ON test.t (a)" ,
298
+ "CREATE INDEX idx_t_b ON test.t (b)" ,
299
+ "CREATE INDEX idx_t_c ON test.t (c)" ,
300
+ },
301
+ // No corruptionTargetIndex specified, no corruption
302
+ missingIndexEntrySelector : "" , // No corruption
303
+ },
250
304
} {
251
305
t .Run (tc .desc , func (t * testing.T ) {
252
306
issueLogger .reset ()
@@ -313,7 +367,14 @@ func TestDetectIndexConsistencyErrors(t *testing.T) {
313
367
}
314
368
315
369
tableDesc := desctestutils .TestingGetPublicTableDescriptor (kvDB , codec , "test" , "t" )
316
- secIndex := tableDesc .PublicNonPrimaryIndexes ()[0 ]
370
+
371
+ // Select target index based on corruptionTargetIndex with bounds checking
372
+ indexes := tableDesc .PublicNonPrimaryIndexes ()
373
+ targetIndexPos := tc .corruptionTargetIndex
374
+ if targetIndexPos < 0 || targetIndexPos >= len (indexes ) {
375
+ targetIndexPos = 0 // Default to first index for safety/backward compatibility
376
+ }
377
+ secIndex := indexes [targetIndexPos ]
317
378
318
379
// Apply test-specific corruption based on configured selectors:
319
380
// - If missingIndexEntrySelector is set, we delete the secondary index entries
@@ -438,6 +499,21 @@ func TestDetectIndexConsistencyErrors(t *testing.T) {
438
499
}
439
500
}
440
501
}
502
+
503
+ // Validate Details if provided in expected issue
504
+ if expectedIssue .Details != nil {
505
+ require .NotNil (t , foundIssue .Details , "issue should have details when expected" )
506
+
507
+ // Check that all expected detail keys and values match
508
+ for expectedKey , expectedValue := range expectedIssue .Details {
509
+ require .Contains (t , foundIssue .Details , expectedKey ,
510
+ "issue should contain detail key: %s" , expectedKey )
511
+
512
+ actualValue := foundIssue .Details [expectedKey ]
513
+ require .Equal (t , expectedValue , actualValue ,
514
+ "detail %s should be %v, got %v" , expectedKey , expectedValue , actualValue )
515
+ }
516
+ }
441
517
}
442
518
443
519
// Validate job status matches expected outcome
0 commit comments