github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/sql/scrub_test.go (about) 1 // Copyright 2017 The Cockroach Authors. 2 // 3 // Use of this software is governed by the Business Source License 4 // included in the file licenses/BSL.txt. 5 // 6 // As of the Change Date specified in that file, in accordance with 7 // the Business Source License, use of this software will be governed 8 // by the Apache License, Version 2.0, included in the file 9 // licenses/APL.txt. 10 11 package sql_test 12 13 import ( 14 "context" 15 gosql "database/sql" 16 "fmt" 17 "regexp" 18 "strings" 19 "testing" 20 "time" 21 22 "github.com/cockroachdb/cockroach/pkg/base" 23 "github.com/cockroachdb/cockroach/pkg/keys" 24 "github.com/cockroachdb/cockroach/pkg/roachpb" 25 "github.com/cockroachdb/cockroach/pkg/sql/scrub" 26 "github.com/cockroachdb/cockroach/pkg/sql/sem/tree" 27 "github.com/cockroachdb/cockroach/pkg/sql/sqlbase" 28 "github.com/cockroachdb/cockroach/pkg/testutils/serverutils" 29 "github.com/cockroachdb/cockroach/pkg/testutils/sqlutils" 30 "github.com/cockroachdb/cockroach/pkg/util/leaktest" 31 ) 32 33 // TestScrubIndexMissingIndexEntry tests that 34 // `SCRUB TABLE ... INDEX ALL`` will find missing index entries. To test 35 // this, a row's underlying secondary index k/v is deleted using the KV 36 // client. This causes a missing index entry error as the row is missing 37 // the expected secondary index k/v. 38 func TestScrubIndexMissingIndexEntry(t *testing.T) { 39 defer leaktest.AfterTest(t)() 40 s, db, kvDB := serverutils.StartServer(t, base.TestServerArgs{}) 41 defer s.Stopper().Stop(context.Background()) 42 r := sqlutils.MakeSQLRunner(db) 43 44 // Create the table and the row entry. 45 // We use a table with mixed as a regression case for #38184. 46 if _, err := db.Exec(` 47 CREATE DATABASE t; 48 CREATE TABLE t."tEst" ("K" INT PRIMARY KEY, v INT); 49 CREATE INDEX secondary ON t."tEst" (v); 50 INSERT INTO t."tEst" VALUES (10, 20); 51 `); err != nil { 52 t.Fatalf("unexpected error: %s", err) 53 } 54 55 // Construct datums for our row values (k, v). 56 values := []tree.Datum{tree.NewDInt(10), tree.NewDInt(20)} 57 tableDesc := sqlbase.GetTableDescriptor(kvDB, keys.SystemSQLCodec, "t", "tEst") 58 secondaryIndex := &tableDesc.Indexes[0] 59 60 colIDtoRowIndex := make(map[sqlbase.ColumnID]int) 61 colIDtoRowIndex[tableDesc.Columns[0].ID] = 0 62 colIDtoRowIndex[tableDesc.Columns[1].ID] = 1 63 64 // Construct the secondary index key that is currently in the 65 // database. 66 secondaryIndexKey, err := sqlbase.EncodeSecondaryIndex( 67 keys.SystemSQLCodec, tableDesc, secondaryIndex, colIDtoRowIndex, values, true /* includeEmpty */) 68 if err != nil { 69 t.Fatalf("unexpected error: %s", err) 70 } 71 72 if len(secondaryIndexKey) != 1 { 73 t.Fatalf("expected 1 index entry, got %d. got %#v", len(secondaryIndexKey), secondaryIndexKey) 74 } 75 76 // Delete the entry. 77 if err := kvDB.Del(context.Background(), secondaryIndexKey[0].Key); err != nil { 78 t.Fatalf("unexpected error: %s", err) 79 } 80 81 // Run SCRUB and find the index errors we created. 82 exp := expectedScrubResult{ 83 ErrorType: scrub.MissingIndexEntryError, 84 Database: "t", 85 Table: "tEst", 86 PrimaryKey: "(10)", 87 Repaired: false, 88 DetailsRegex: `"v": "20"`, 89 } 90 runScrub(t, db, `EXPERIMENTAL SCRUB TABLE t."tEst" WITH OPTIONS INDEX ALL`, exp) 91 // Run again with AS OF SYSTEM TIME. 92 time.Sleep(1 * time.Millisecond) 93 runScrub(t, db, `EXPERIMENTAL SCRUB TABLE t."tEst" AS OF SYSTEM TIME '-1ms' WITH OPTIONS INDEX ALL`, exp) 94 95 // Verify that AS OF SYSTEM TIME actually operates in the past. 96 ts := r.QueryStr(t, `SELECT cluster_logical_timestamp()`)[0][0] 97 r.Exec(t, `DELETE FROM t."tEst"`) 98 runScrub( 99 t, db, fmt.Sprintf( 100 `EXPERIMENTAL SCRUB TABLE t."tEst" AS OF SYSTEM TIME '%s' WITH OPTIONS INDEX ALL`, ts, 101 ), 102 exp, 103 ) 104 } 105 106 // TestScrubIndexDanglingIndexReference tests that 107 // `SCRUB TABLE ... INDEX`` will find dangling index references, which 108 // are index entries that have no corresponding primary k/v. To test 109 // this an index entry is generated and inserted. This creates a 110 // dangling index error as the corresponding primary k/v is not equal. 111 func TestScrubIndexDanglingIndexReference(t *testing.T) { 112 defer leaktest.AfterTest(t)() 113 s, db, kvDB := serverutils.StartServer(t, base.TestServerArgs{}) 114 defer s.Stopper().Stop(context.Background()) 115 116 // Create the table and the row entry. 117 if _, err := db.Exec(` 118 CREATE DATABASE t; 119 CREATE TABLE t.test (k INT PRIMARY KEY, v INT); 120 CREATE INDEX secondary ON t.test (v); 121 `); err != nil { 122 t.Fatalf("unexpected error: %s", err) 123 } 124 125 tableDesc := sqlbase.GetTableDescriptor(kvDB, keys.SystemSQLCodec, "t", "test") 126 secondaryIndexDesc := &tableDesc.Indexes[0] 127 128 colIDtoRowIndex := make(map[sqlbase.ColumnID]int) 129 colIDtoRowIndex[tableDesc.Columns[0].ID] = 0 130 colIDtoRowIndex[tableDesc.Columns[1].ID] = 1 131 132 // Construct datums and secondary k/v for our row values (k, v). 133 values := []tree.Datum{tree.NewDInt(10), tree.NewDInt(314)} 134 secondaryIndex, err := sqlbase.EncodeSecondaryIndex( 135 keys.SystemSQLCodec, tableDesc, secondaryIndexDesc, colIDtoRowIndex, values, true /* includeEmpty */) 136 if err != nil { 137 t.Fatalf("unexpected error: %s", err) 138 } 139 140 if len(secondaryIndex) != 1 { 141 t.Fatalf("expected 1 index entry, got %d. got %#v", len(secondaryIndex), secondaryIndex) 142 } 143 144 // Put the new secondary k/v into the database. 145 if err := kvDB.Put(context.Background(), secondaryIndex[0].Key, &secondaryIndex[0].Value); err != nil { 146 t.Fatalf("unexpected error: %s", err) 147 } 148 149 // Run SCRUB and find the index errors we created. 150 rows, err := db.Query(`EXPERIMENTAL SCRUB TABLE t.test WITH OPTIONS INDEX ALL`) 151 if err != nil { 152 t.Fatalf("unexpected error: %s", err) 153 } 154 defer rows.Close() 155 156 results, err := sqlutils.GetScrubResultRows(rows) 157 if err != nil { 158 t.Fatalf("unexpected error: %s", err) 159 } 160 161 if len(results) != 1 { 162 t.Fatalf("expected 1 result, got %d. got %#v", len(results), results) 163 } 164 if result := results[0]; result.ErrorType != scrub.DanglingIndexReferenceError { 165 t.Fatalf("expected %q error, instead got: %s", 166 scrub.DanglingIndexReferenceError, result.ErrorType) 167 } else if result.Database != "t" { 168 t.Fatalf("expected database %q, got %q", "t", result.Database) 169 } else if result.Table != "test" { 170 t.Fatalf("expected table %q, got %q", "test", result.Table) 171 } else if result.PrimaryKey != "(10)" { 172 t.Fatalf("expected primaryKey %q, got %q", "(10)", result.PrimaryKey) 173 } else if result.Repaired { 174 t.Fatalf("expected repaired %v, got %v", false, result.Repaired) 175 } else if !strings.Contains(result.Details, `"v": "314"`) { 176 t.Fatalf("expected error details to contain `%s`, got %s", `"v": "314"`, result.Details) 177 } 178 179 // Run SCRUB DATABASE to make sure it also catches the problem. 180 rows, err = db.Query(`EXPERIMENTAL SCRUB DATABASE t`) 181 if err != nil { 182 t.Fatalf("unexpected error: %+v", err) 183 } 184 defer rows.Close() 185 scrubDatabaseResults, err := sqlutils.GetScrubResultRows(rows) 186 if err != nil { 187 t.Fatalf("unexpected error: %s", err) 188 } else if len(scrubDatabaseResults) != 1 { 189 t.Fatalf("expected 1 result, got %d. got %#v", len(scrubDatabaseResults), scrubDatabaseResults) 190 } else if !(scrubDatabaseResults[0].ErrorType == results[0].ErrorType && 191 scrubDatabaseResults[0].Database == results[0].Database && 192 scrubDatabaseResults[0].Table == results[0].Table && 193 scrubDatabaseResults[0].Details == results[0].Details) { 194 t.Fatalf("expected results to be equal, SCRUB TABLE got %v. SCRUB DATABASE got %v", 195 results, scrubDatabaseResults) 196 } 197 } 198 199 // TestScrubIndexCatchesStoringMismatch tests that 200 // `SCRUB TABLE ... INDEX ALL` will fail if an index entry only differs 201 // by its STORING values. To test this, a row's underlying secondary 202 // index k/v is updated using the KV client to have a different value. 203 func TestScrubIndexCatchesStoringMismatch(t *testing.T) { 204 defer leaktest.AfterTest(t)() 205 s, db, kvDB := serverutils.StartServer(t, base.TestServerArgs{}) 206 defer s.Stopper().Stop(context.Background()) 207 208 // Create the table and the row entry. 209 if _, err := db.Exec(` 210 CREATE DATABASE t; 211 CREATE TABLE t.test (k INT PRIMARY KEY, v INT, data INT); 212 CREATE INDEX secondary ON t.test (v) STORING (data); 213 INSERT INTO t.test VALUES (10, 20, 1337); 214 `); err != nil { 215 t.Fatalf("unexpected error: %s", err) 216 } 217 218 tableDesc := sqlbase.GetTableDescriptor(kvDB, keys.SystemSQLCodec, "t", "test") 219 secondaryIndexDesc := &tableDesc.Indexes[0] 220 221 colIDtoRowIndex := make(map[sqlbase.ColumnID]int) 222 colIDtoRowIndex[tableDesc.Columns[0].ID] = 0 223 colIDtoRowIndex[tableDesc.Columns[1].ID] = 1 224 colIDtoRowIndex[tableDesc.Columns[2].ID] = 2 225 226 // Generate the existing secondary index key. 227 values := []tree.Datum{tree.NewDInt(10), tree.NewDInt(20), tree.NewDInt(1337)} 228 secondaryIndex, err := sqlbase.EncodeSecondaryIndex( 229 keys.SystemSQLCodec, tableDesc, secondaryIndexDesc, colIDtoRowIndex, values, true /* includeEmpty */) 230 231 if len(secondaryIndex) != 1 { 232 t.Fatalf("expected 1 index entry, got %d. got %#v", len(secondaryIndex), secondaryIndex) 233 } 234 235 if err != nil { 236 t.Fatalf("unexpected error: %s", err) 237 } 238 // Delete the existing secondary k/v. 239 if err := kvDB.Del(context.Background(), secondaryIndex[0].Key); err != nil { 240 t.Fatalf("unexpected error: %s", err) 241 } 242 243 // Generate a secondary index k/v that has a different value. 244 values = []tree.Datum{tree.NewDInt(10), tree.NewDInt(20), tree.NewDInt(314)} 245 secondaryIndex, err = sqlbase.EncodeSecondaryIndex( 246 keys.SystemSQLCodec, tableDesc, secondaryIndexDesc, colIDtoRowIndex, values, true /* includeEmpty */) 247 if err != nil { 248 t.Fatalf("unexpected error: %s", err) 249 } 250 // Put the incorrect secondary k/v. 251 if err := kvDB.Put(context.Background(), secondaryIndex[0].Key, &secondaryIndex[0].Value); err != nil { 252 t.Fatalf("unexpected error: %s", err) 253 } 254 255 // Run SCRUB and find the index errors we created. 256 rows, err := db.Query(`EXPERIMENTAL SCRUB TABLE t.test WITH OPTIONS INDEX ALL`) 257 if err != nil { 258 t.Fatalf("unexpected error: %s", err) 259 } 260 defer rows.Close() 261 262 results, err := sqlutils.GetScrubResultRows(rows) 263 if err != nil { 264 t.Fatalf("unexpected error: %s", err) 265 } 266 267 // We will receive both a missing_index_entry and dangling_index_reference. 268 if len(results) != 2 { 269 t.Fatalf("expected 2 result, got %d. got %#v", len(results), results) 270 } 271 272 // Assert the missing index error is correct. 273 var missingIndexError *sqlutils.ScrubResult 274 for _, result := range results { 275 if result.ErrorType == scrub.MissingIndexEntryError { 276 missingIndexError = &result 277 break 278 } 279 } 280 if result := missingIndexError; result == nil { 281 t.Fatalf("expected errors to include %q error, but got errors: %#v", 282 scrub.MissingIndexEntryError, results) 283 } else if result.Database != "t" { 284 t.Fatalf("expected database %q, got %q", "t", result.Database) 285 } else if result.Table != "test" { 286 t.Fatalf("expected table %q, got %q", "test", result.Table) 287 } else if result.PrimaryKey != "(10)" { 288 t.Fatalf("expected primaryKey %q, got %q", "(10)", result.PrimaryKey) 289 } else if result.Repaired { 290 t.Fatalf("expected repaired %v, got %v", false, result.Repaired) 291 } else if !strings.Contains(result.Details, `"data": "1337"`) { 292 t.Fatalf("expected error details to contain `%s`, got %s", `"data": "1337"`, result.Details) 293 } 294 295 // Assert the dangling index error is correct. 296 var danglingIndexResult *sqlutils.ScrubResult 297 for _, result := range results { 298 if result.ErrorType == scrub.DanglingIndexReferenceError { 299 danglingIndexResult = &result 300 break 301 } 302 } 303 if result := danglingIndexResult; result == nil { 304 t.Fatalf("expected errors to include %q error, but got errors: %#v", 305 scrub.DanglingIndexReferenceError, results) 306 } else if result.Database != "t" { 307 t.Fatalf("expected database %q, got %q", "t", result.Database) 308 } else if result.Table != "test" { 309 t.Fatalf("expected table %q, got %q", "test", result.Table) 310 } else if result.PrimaryKey != "(10)" { 311 t.Fatalf("expected primaryKey %q, got %q", "(10)", result.PrimaryKey) 312 } else if result.Repaired { 313 t.Fatalf("expected repaired %v, got %v", false, result.Repaired) 314 } else if !strings.Contains(result.Details, `"data": "314"`) { 315 t.Fatalf("expected error details to contain `%s`, got %s", `"data": "314"`, result.Details) 316 } 317 } 318 319 // TestScrubCheckConstraint tests that `SCRUB TABLE ... CONSTRAINT ALL` 320 // will fail if a check constraint is violated. To test this, a row's 321 // underlying value is updated using the KV client so the row violates 322 // the constraint.. 323 func TestScrubCheckConstraint(t *testing.T) { 324 defer leaktest.AfterTest(t)() 325 s, db, kvDB := serverutils.StartServer(t, base.TestServerArgs{}) 326 defer s.Stopper().Stop(context.Background()) 327 328 // Create the table and the row entry. 329 if _, err := db.Exec(` 330 CREATE DATABASE t; 331 CREATE TABLE t.test (k INT PRIMARY KEY, v INT, CHECK (v > 1)); 332 INSERT INTO t.test VALUES (10, 2); 333 `); err != nil { 334 t.Fatalf("unexpected error: %s", err) 335 } 336 337 tableDesc := sqlbase.GetTableDescriptor(kvDB, keys.SystemSQLCodec, "t", "test") 338 339 colIDtoRowIndex := make(map[sqlbase.ColumnID]int) 340 colIDtoRowIndex[tableDesc.Columns[0].ID] = 0 341 colIDtoRowIndex[tableDesc.Columns[1].ID] = 1 342 343 // Create the primary index key. 344 values := []tree.Datum{tree.NewDInt(10), tree.NewDInt(2)} 345 primaryIndexKeyPrefix := sqlbase.MakeIndexKeyPrefix( 346 keys.SystemSQLCodec, tableDesc, tableDesc.PrimaryIndex.ID) 347 primaryIndexKey, _, err := sqlbase.EncodeIndexKey( 348 tableDesc, &tableDesc.PrimaryIndex, colIDtoRowIndex, values, primaryIndexKeyPrefix) 349 if err != nil { 350 t.Fatalf("unexpected error: %s", err) 351 } 352 353 // Add the family suffix to the key. 354 family := tableDesc.Families[0] 355 primaryIndexKey = keys.MakeFamilyKey(primaryIndexKey, uint32(family.ID)) 356 357 // Generate a k/v that has a different value that violates the 358 // constraint. 359 values = []tree.Datum{tree.NewDInt(10), tree.NewDInt(0)} 360 // Encode the column value. 361 valueBuf, err := sqlbase.EncodeTableValue( 362 []byte(nil), tableDesc.Columns[1].ID, values[1], []byte(nil)) 363 if err != nil { 364 t.Fatalf("unexpected error: %s", err) 365 } 366 // Construct the tuple for the family value. 367 var value roachpb.Value 368 value.SetTuple(valueBuf) 369 370 // Overwrite the existing value. 371 if err := kvDB.Put(context.Background(), primaryIndexKey, &value); err != nil { 372 t.Fatalf("unexpected error: %s", err) 373 } 374 // Run SCRUB and find the CHECK violation created. 375 rows, err := db.Query(`EXPERIMENTAL SCRUB TABLE t.test WITH OPTIONS CONSTRAINT ALL`) 376 if err != nil { 377 t.Fatalf("unexpected error: %s", err) 378 } 379 defer rows.Close() 380 results, err := sqlutils.GetScrubResultRows(rows) 381 if err != nil { 382 t.Fatalf("unexpected error: %s", err) 383 } 384 385 if len(results) != 1 { 386 t.Fatalf("expected 1 result, got %d. got %#v", len(results), results) 387 } 388 389 if result := results[0]; result.ErrorType != string(scrub.CheckConstraintViolation) { 390 t.Fatalf("expected %q error, instead got: %s", 391 scrub.CheckConstraintViolation, result.ErrorType) 392 } else if result.Database != "t" { 393 t.Fatalf("expected database %q, got %q", "t", result.Database) 394 } else if result.Table != "test" { 395 t.Fatalf("expected table %q, got %q", "test", result.Table) 396 } else if result.PrimaryKey != "(10)" { 397 t.Fatalf("expected primaryKey %q, got %q", "(10)", result.PrimaryKey) 398 } else if result.Repaired { 399 t.Fatalf("expected repaired %v, got %v", false, result.Repaired) 400 } else if !strings.Contains(result.Details, 401 `{"constraint_name": "check_v", "row_data": {"k": "10", "v": "0"}}`) { 402 t.Fatalf("expected error details to contain `%s`, got %s", 403 `{"constraint_name": "check_v", "row_data": {"k": "10", "v": "0"}}`, 404 result.Details) 405 } 406 } 407 408 // TestScrubFKConstraintFKMissing tests that `SCRUB TABLE ... CONSTRAINT 409 // ALL` will report an error when a foreign key constraint is violated. 410 // To test this, the secondary index used for the foreign key lookup is 411 // modified using the KV client to change the value and cause a 412 // violation. 413 func TestScrubFKConstraintFKMissing(t *testing.T) { 414 defer leaktest.AfterTest(t)() 415 s, db, kvDB := serverutils.StartServer(t, base.TestServerArgs{}) 416 defer s.Stopper().Stop(context.Background()) 417 r := sqlutils.MakeSQLRunner(db) 418 419 // Create the table and the row entry. 420 r.Exec(t, ` 421 CREATE DATABASE t; 422 CREATE TABLE t.parent ( 423 id INT PRIMARY KEY 424 ); 425 CREATE TABLE t.child ( 426 child_id INT PRIMARY KEY, 427 parent_id INT, 428 INDEX (parent_id), 429 FOREIGN KEY (parent_id) REFERENCES t.parent (id) 430 ); 431 INSERT INTO t.parent VALUES (314); 432 INSERT INTO t.child VALUES (10, 314); 433 `) 434 435 tableDesc := sqlbase.GetTableDescriptor(kvDB, keys.SystemSQLCodec, "t", "child") 436 437 // Construct datums for the child row values (child_id, parent_id). 438 values := []tree.Datum{tree.NewDInt(10), tree.NewDInt(314)} 439 secondaryIndex := &tableDesc.Indexes[0] 440 441 colIDtoRowIndex := make(map[sqlbase.ColumnID]int) 442 colIDtoRowIndex[tableDesc.Columns[0].ID] = 0 443 colIDtoRowIndex[tableDesc.Columns[1].ID] = 1 444 445 // Construct the secondary index key entry as it exists in the 446 // database. 447 secondaryIndexKey, err := sqlbase.EncodeSecondaryIndex( 448 keys.SystemSQLCodec, tableDesc, secondaryIndex, colIDtoRowIndex, values, true /* includeEmpty */) 449 if err != nil { 450 t.Fatalf("unexpected error: %s", err) 451 } 452 453 if len(secondaryIndexKey) != 1 { 454 t.Fatalf("expected 1 index entry, got %d. got %#v", len(secondaryIndexKey), secondaryIndexKey) 455 } 456 457 // Delete the existing secondary key entry, as we will later replace 458 // it. 459 if err := kvDB.Del(context.Background(), secondaryIndexKey[0].Key); err != nil { 460 t.Fatalf("unexpected error: %s", err) 461 } 462 463 // Replace the foreign key value. 464 values[1] = tree.NewDInt(0) 465 466 // Construct the new secondary index key that will be inserted. 467 secondaryIndexKey, err = sqlbase.EncodeSecondaryIndex( 468 keys.SystemSQLCodec, tableDesc, secondaryIndex, colIDtoRowIndex, values, true /* includeEmpty */) 469 if err != nil { 470 t.Fatalf("unexpected error: %s", err) 471 } 472 473 if len(secondaryIndexKey) != 1 { 474 t.Fatalf("expected 1 index entry, got %d. got %#v", len(secondaryIndexKey), secondaryIndexKey) 475 } 476 477 // Add the new, replacement secondary index entry. 478 if err := kvDB.Put(context.Background(), secondaryIndexKey[0].Key, &secondaryIndexKey[0].Value); err != nil { 479 t.Fatalf("unexpected error: %s", err) 480 } 481 482 // Run SCRUB and find the FOREIGN KEY violation created. 483 exp := expectedScrubResult{ 484 ErrorType: scrub.ForeignKeyConstraintViolation, 485 Database: "t", 486 Table: "child", 487 PrimaryKey: "(10)", 488 DetailsRegex: `{"constraint_name": "fk_parent_id_ref_parent", "row_data": {"child_id": "10", "parent_id": "0"}}`, 489 } 490 runScrub(t, db, `EXPERIMENTAL SCRUB TABLE t.child WITH OPTIONS CONSTRAINT ALL`, exp) 491 // Run again with AS OF SYSTEM TIME. 492 time.Sleep(1 * time.Millisecond) 493 runScrub(t, db, `EXPERIMENTAL SCRUB TABLE t.child AS OF SYSTEM TIME '-1ms' WITH OPTIONS CONSTRAINT ALL`, exp) 494 495 // Verify that AS OF SYSTEM TIME actually operates in the past. 496 ts := r.QueryStr(t, `SELECT cluster_logical_timestamp()`)[0][0] 497 r.Exec(t, "INSERT INTO t.parent VALUES (0)") 498 runScrub( 499 t, db, fmt.Sprintf( 500 `EXPERIMENTAL SCRUB TABLE t.child AS OF SYSTEM TIME '%s' WITH OPTIONS CONSTRAINT ALL`, ts, 501 ), 502 exp, 503 ) 504 } 505 506 // TestScrubFKConstraintFKNulls tests that `SCRUB TABLE ... CONSTRAINT ALL` will 507 // fail if a MATCH FULL foreign key constraint is violated when foreign key 508 // values are partially null. 509 // TODO (lucy): This is making use of the fact that SCRUB reports errors for 510 // unvalidated FKs, even when it's fine for rows to violate the constraint. 511 // Ideally we would have SCRUB not report errors for those, and use a validated 512 // constraint in this test with corrupted KVs. 513 func TestScrubFKConstraintFKNulls(t *testing.T) { 514 defer leaktest.AfterTest(t)() 515 s, db, _ := serverutils.StartServer(t, base.TestServerArgs{}) 516 defer s.Stopper().Stop(context.Background()) 517 518 // Create the table and the row entry. 519 if _, err := db.Exec(` 520 CREATE DATABASE t; 521 CREATE TABLE t.parent ( 522 id INT PRIMARY KEY, 523 id2 INT, 524 UNIQUE INDEX (id, id2) 525 ); 526 CREATE TABLE t.child ( 527 child_id INT PRIMARY KEY, 528 parent_id INT, 529 parent_id2 INT, 530 INDEX (parent_id, parent_id2) 531 ); 532 INSERT INTO t.parent VALUES (1337, NULL); 533 INSERT INTO t.child VALUES (11, 1337, NULL); 534 ALTER TABLE t.child ADD FOREIGN KEY (parent_id, parent_id2) REFERENCES t.parent (id, id2) MATCH FULL NOT VALID; 535 `); err != nil { 536 t.Fatalf("unexpected error: %s", err) 537 } 538 539 // Run SCRUB and find the FOREIGN KEY violation created. 540 exp := expectedScrubResult{ 541 ErrorType: scrub.ForeignKeyConstraintViolation, 542 Database: "t", 543 Table: "child", 544 PrimaryKey: "(11)", 545 DetailsRegex: `{"constraint_name": "fk_parent_id_ref_parent", "row_data": {"child_id": "11", "parent_id": "1337", "parent_id2": "NULL"}}`, 546 } 547 runScrub(t, db, `EXPERIMENTAL SCRUB TABLE t.child WITH OPTIONS CONSTRAINT ALL`, exp) 548 time.Sleep(1 * time.Millisecond) 549 runScrub(t, db, `EXPERIMENTAL SCRUB TABLE t.child AS OF SYSTEM TIME '-1ms' WITH OPTIONS CONSTRAINT ALL`, exp) 550 } 551 552 // TestScrubPhysicalNonnullableNullInSingleColumnFamily tests that 553 // `SCRUB TABLE ... WITH OPTIONS PHYSICAL` will find any rows where a 554 // value is NULL for a column that is not-nullable and the only column 555 // in a family. To test this, a row is created that we later overwrite 556 // the value for. The value that is inserted is the sentinel value as 557 // the column is the only one in the family. 558 func TestScrubPhysicalNonnullableNullInSingleColumnFamily(t *testing.T) { 559 defer leaktest.AfterTest(t)() 560 s, db, kvDB := serverutils.StartServer(t, base.TestServerArgs{}) 561 defer s.Stopper().Stop(context.Background()) 562 563 // Create the table and the row entry. 564 if _, err := db.Exec(` 565 CREATE DATABASE t; 566 CREATE TABLE t.test (k INT PRIMARY KEY, v INT NOT NULL); 567 INSERT INTO t.test VALUES (217, 314); 568 `); err != nil { 569 t.Fatalf("unexpected error: %s", err) 570 } 571 572 tableDesc := sqlbase.GetTableDescriptor(kvDB, keys.SystemSQLCodec, "t", "test") 573 574 // Construct datums for our row values (k, v). 575 values := []tree.Datum{tree.NewDInt(217), tree.NewDInt(314)} 576 577 colIDtoRowIndex := make(map[sqlbase.ColumnID]int) 578 colIDtoRowIndex[tableDesc.Columns[0].ID] = 0 579 colIDtoRowIndex[tableDesc.Columns[1].ID] = 1 580 581 // Create the primary index key 582 primaryIndexKeyPrefix := sqlbase.MakeIndexKeyPrefix( 583 keys.SystemSQLCodec, tableDesc, tableDesc.PrimaryIndex.ID) 584 primaryIndexKey, _, err := sqlbase.EncodeIndexKey( 585 tableDesc, &tableDesc.PrimaryIndex, colIDtoRowIndex, values, primaryIndexKeyPrefix) 586 if err != nil { 587 t.Fatalf("unexpected error: %s", err) 588 } 589 590 // Add the family suffix to the key. 591 family := tableDesc.Families[0] 592 primaryIndexKey = keys.MakeFamilyKey(primaryIndexKey, uint32(family.ID)) 593 594 // Create an empty sentinel value. 595 var value roachpb.Value 596 value.SetTuple([]byte(nil)) 597 598 if err := kvDB.Put(context.Background(), primaryIndexKey, &value); err != nil { 599 t.Fatalf("unexpected error: %s", err) 600 } 601 602 // Run SCRUB and find the errors we created. 603 rows, err := db.Query(`EXPERIMENTAL SCRUB TABLE t.test WITH OPTIONS PHYSICAL`) 604 if err != nil { 605 t.Fatalf("unexpected error: %s", err) 606 } 607 defer rows.Close() 608 results, err := sqlutils.GetScrubResultRows(rows) 609 if err != nil { 610 t.Fatalf("unexpected error: %s", err) 611 } else if len(results) != 1 { 612 t.Fatalf("expected 1 result, got %d. got %#v", len(results), results) 613 } 614 615 if result := results[0]; result.ErrorType != string(scrub.UnexpectedNullValueError) { 616 t.Fatalf("expected %q error, instead got: %s", 617 scrub.UnexpectedNullValueError, result.ErrorType) 618 } else if result.Database != "t" { 619 t.Fatalf("expected database %q, got %q", "t", result.Database) 620 } else if result.Table != "test" { 621 t.Fatalf("expected table %q, got %q", "test", result.Table) 622 } else if result.PrimaryKey != "(217)" { 623 t.Fatalf("expected primaryKey %q, got %q", "(217)", result.PrimaryKey) 624 } else if result.Repaired { 625 t.Fatalf("expected repaired %v, got %v", false, result.Repaired) 626 } else if !strings.Contains(result.Details, `"k": "217"`) { 627 t.Fatalf("expected error details to contain `%s`, got %s", `"k": "217"`, result.Details) 628 } else if !strings.Contains(result.Details, `"v": "<unset>"`) { 629 t.Fatalf("expected error details to contain `%s`, got %s", `"v": "<unset>"`, result.Details) 630 } 631 } 632 633 // TestScrubPhysicalNonnullableNullInMulticolumnFamily tests that 634 // `SCRUB TABLE ... WITH OPTIONS PHYSICAL` will find any rows where a 635 // value is NULL for a column that is not-nullable and is not the only 636 // column in a family. To test this, a row is created that we later 637 // overwrite the value for. The value that is inserted is missing one of 638 // the columns that belongs in the family. 639 func TestScrubPhysicalNonnullableNullInMulticolumnFamily(t *testing.T) { 640 defer leaktest.AfterTest(t)() 641 s, db, kvDB := serverutils.StartServer(t, base.TestServerArgs{}) 642 defer s.Stopper().Stop(context.Background()) 643 644 // Create the table and the row entry. 645 if _, err := db.Exec(` 646 CREATE DATABASE t; 647 CREATE TABLE t.test (k INT PRIMARY KEY, v INT NOT NULL, b INT NOT NULL, FAMILY (k), FAMILY (v, b)); 648 INSERT INTO t.test VALUES (217, 314, 1337); 649 `); err != nil { 650 t.Fatalf("unexpected error: %s", err) 651 } 652 653 tableDesc := sqlbase.GetTableDescriptor(kvDB, keys.SystemSQLCodec, "t", "test") 654 655 // Construct datums for our row values (k, v, b). 656 values := []tree.Datum{tree.NewDInt(217), tree.NewDInt(314), tree.NewDInt(1337)} 657 658 colIDtoRowIndex := make(map[sqlbase.ColumnID]int) 659 colIDtoRowIndex[tableDesc.Columns[0].ID] = 0 660 colIDtoRowIndex[tableDesc.Columns[1].ID] = 1 661 colIDtoRowIndex[tableDesc.Columns[2].ID] = 2 662 663 // Create the primary index key 664 primaryIndexKeyPrefix := sqlbase.MakeIndexKeyPrefix( 665 keys.SystemSQLCodec, tableDesc, tableDesc.PrimaryIndex.ID) 666 primaryIndexKey, _, err := sqlbase.EncodeIndexKey( 667 tableDesc, &tableDesc.PrimaryIndex, colIDtoRowIndex, values, primaryIndexKeyPrefix) 668 if err != nil { 669 t.Fatalf("unexpected error: %s", err) 670 } 671 672 // Add the family suffix to the key, in particular we care about the 673 // second column family. 674 family := tableDesc.Families[1] 675 primaryIndexKey = keys.MakeFamilyKey(primaryIndexKey, uint32(family.ID)) 676 677 // Encode the second column value. 678 valueBuf, err := sqlbase.EncodeTableValue( 679 []byte(nil), tableDesc.Columns[1].ID, values[1], []byte(nil)) 680 if err != nil { 681 t.Fatalf("unexpected error: %s", err) 682 } 683 684 // Construct the tuple for the family that is missing a column value, i.e. it is NULL. 685 var value roachpb.Value 686 value.SetTuple(valueBuf) 687 688 // Overwrite the existing value. 689 if err := kvDB.Put(context.Background(), primaryIndexKey, &value); err != nil { 690 t.Fatalf("unexpected error: %s", err) 691 } 692 693 // Run SCRUB and find the errors we created. 694 rows, err := db.Query(`EXPERIMENTAL SCRUB TABLE t.test WITH OPTIONS PHYSICAL`) 695 if err != nil { 696 t.Fatalf("unexpected error: %s", err) 697 } 698 defer rows.Close() 699 results, err := sqlutils.GetScrubResultRows(rows) 700 if err != nil { 701 t.Fatalf("unexpected error: %s", err) 702 } else if len(results) != 1 { 703 t.Fatalf("expected 1 result, got %d. got %#v", len(results), results) 704 } 705 706 if result := results[0]; result.ErrorType != string(scrub.UnexpectedNullValueError) { 707 t.Fatalf("expected %q error, instead got: %s", 708 scrub.UnexpectedNullValueError, result.ErrorType) 709 } else if result.Database != "t" { 710 t.Fatalf("expected database %q, got %q", "t", result.Database) 711 } else if result.Table != "test" { 712 t.Fatalf("expected table %q, got %q", "test", result.Table) 713 } else if result.PrimaryKey != "(217)" { 714 t.Fatalf("expected primaryKey %q, got %q", "(217)", result.PrimaryKey) 715 } else if result.Repaired { 716 t.Fatalf("expected repaired %v, got %v", false, result.Repaired) 717 } else if !strings.Contains(result.Details, `"k": "217"`) { 718 t.Fatalf("expected error details to contain `%s`, got %s", `"k": "217"`, result.Details) 719 } else if !strings.Contains(result.Details, `"v": "314"`) { 720 t.Fatalf("expected error details to contain `%s`, got %s", `"v": "314"`, result.Details) 721 } else if !strings.Contains(result.Details, `"b": "<unset>"`) { 722 t.Fatalf("expected error details to contain `%s`, got %s", `"b": "<unset>"`, result.Details) 723 } 724 } 725 726 // TestScrubPhysicalUnexpectedFamilyID tests that `SCRUB TABLE ... WITH 727 // OPTIONS PHYSICAL` will find any rows where a primary index as key 728 // with an invalid family ID. To test this, a table is made with 2 729 // families and then the first family is dropped. A row is then inserted 730 // using the KV client which has the ID of the first family. 731 func TestScrubPhysicalUnexpectedFamilyID(t *testing.T) { 732 defer leaktest.AfterTest(t)() 733 t.Skip("currently KV pairs with unexpected family IDs are not noticed by the fetcher") 734 s, db, kvDB := serverutils.StartServer(t, base.TestServerArgs{}) 735 defer s.Stopper().Stop(context.Background()) 736 737 // Create the table and the row entry. 738 if _, err := db.Exec(` 739 CREATE DATABASE t; 740 CREATE TABLE t.test ( 741 k INT PRIMARY KEY, 742 v1 INT NOT NULL, 743 v2 INT NOT NULL, 744 FAMILY first (v1), 745 FAMILY second (v2) 746 ); 747 `); err != nil { 748 t.Fatalf("unexpected error: %s", err) 749 } 750 751 oldTableDesc := sqlbase.GetTableDescriptor(kvDB, keys.SystemSQLCodec, "t", "test") 752 753 // Drop the first column family. 754 if _, err := db.Exec(`ALTER TABLE t.test DROP COLUMN v1`); err != nil { 755 t.Fatalf("unexpected error: %s", err) 756 } 757 758 tableDesc := sqlbase.GetTableDescriptor(kvDB, keys.SystemSQLCodec, "t", "test") 759 760 // Construct datums for our row values (k, v1). 761 values := []tree.Datum{tree.NewDInt(217), tree.NewDInt(314)} 762 763 colIDtoRowIndex := make(map[sqlbase.ColumnID]int) 764 colIDtoRowIndex[tableDesc.Columns[0].ID] = 0 765 colIDtoRowIndex[tableDesc.Columns[1].ID] = 1 766 767 // Create the primary index key 768 primaryIndexKeyPrefix := sqlbase.MakeIndexKeyPrefix( 769 keys.SystemSQLCodec, tableDesc, tableDesc.PrimaryIndex.ID) 770 primaryIndexKey, _, err := sqlbase.EncodeIndexKey( 771 tableDesc, &tableDesc.PrimaryIndex, colIDtoRowIndex, values, primaryIndexKeyPrefix) 772 if err != nil { 773 t.Fatalf("unexpected error: %s", err) 774 } 775 776 // Add the correct family suffix to the key. 777 primaryIndexKeyWithFamily := keys.MakeFamilyKey(primaryIndexKey, uint32(tableDesc.Families[1].ID)) 778 779 // Encode the second column value. 780 valueBuf, err := sqlbase.EncodeTableValue( 781 []byte(nil), tableDesc.Columns[1].ID, values[1], []byte(nil)) 782 if err != nil { 783 t.Fatalf("unexpected error: %s", err) 784 } 785 var value roachpb.Value 786 value.SetTuple(valueBuf) 787 788 // Insert the value. 789 if err := kvDB.Put(context.Background(), primaryIndexKeyWithFamily, &value); err != nil { 790 t.Fatalf("unexpected error: %s", err) 791 } 792 793 // Create a k/v with an incorrect family suffix to the key. 794 primaryIndexKeyWithFamily = keys.MakeFamilyKey(primaryIndexKey, 795 uint32(oldTableDesc.Families[1].ID)) 796 797 // Encode the second column value. 798 valueBuf, err = sqlbase.EncodeTableValue( 799 []byte(nil), tableDesc.Columns[1].ID, values[1], []byte(nil)) 800 if err != nil { 801 t.Fatalf("unexpected error: %s", err) 802 } 803 value = roachpb.Value{} 804 value.SetTuple(valueBuf) 805 806 // Insert the incorrect family k/v. 807 if err := kvDB.Put(context.Background(), primaryIndexKeyWithFamily, &value); err != nil { 808 t.Fatalf("unexpected error: %s", err) 809 } 810 811 // Run SCRUB and find the errors we created. 812 rows, err := db.Query(`EXPERIMENTAL SCRUB TABLE t.test WITH OPTIONS PHYSICAL`) 813 if err != nil { 814 t.Fatalf("unexpected error: %s", err) 815 } 816 defer rows.Close() 817 results, err := sqlutils.GetScrubResultRows(rows) 818 if err != nil { 819 t.Fatalf("unexpected error: %s", err) 820 } else if len(results) != 1 { 821 t.Fatalf("expected 1 result, got %d. got %#v", len(results), results) 822 } 823 824 if result := results[0]; result.ErrorType != string(scrub.UnexpectedNullValueError) { 825 t.Fatalf("expected %q error, instead got: %s", 826 scrub.UnexpectedNullValueError, result.ErrorType) 827 } else if result.Database != "t" { 828 t.Fatalf("expected database %q, got %q", "t", result.Database) 829 } else if result.Table != "test" { 830 t.Fatalf("expected table %q, got %q", "test", result.Table) 831 } else if result.PrimaryKey != "(217)" { 832 t.Fatalf("expected primaryKey %q, got %q", "(217)", result.PrimaryKey) 833 } else if result.Repaired { 834 t.Fatalf("expected repaired %v, got %v", false, result.Repaired) 835 } else if !strings.Contains(result.Details, `"k": "217"`) { 836 t.Fatalf("expected error details to contain `%s`, got %s", `"k": "217"`, result.Details) 837 } else if !strings.Contains(result.Details, `"v": "314"`) { 838 t.Fatalf("expected error details to contain `%s`, got %s", `"v": "314"`, result.Details) 839 } else if !strings.Contains(result.Details, `"b": "<unset>"`) { 840 t.Fatalf("expected error details to contain `%s`, got %s", `"b": "<unset>"`, result.Details) 841 } 842 } 843 844 // TestScrubPhysicalIncorrectPrimaryIndexValueColumn tests that 845 // `SCRUB TABLE ... WITH OPTIONS PHYSICAL` will find any rows where a 846 // value has an encoded column ID that does not correspond to the table 847 // descriptor. To test this, a row is inserted using the KV client. 848 func TestScrubPhysicalIncorrectPrimaryIndexValueColumn(t *testing.T) { 849 defer leaktest.AfterTest(t)() 850 t.Skip("the test is not failing, as it would be expected") 851 s, db, kvDB := serverutils.StartServer(t, base.TestServerArgs{}) 852 defer s.Stopper().Stop(context.Background()) 853 854 // Create the table and the row entry. 855 if _, err := db.Exec(` 856 CREATE DATABASE t; 857 CREATE TABLE t.test (k INT PRIMARY KEY, v1 INT, v2 INT); 858 `); err != nil { 859 t.Fatalf("unexpected error: %s", err) 860 } 861 tableDesc := sqlbase.GetTableDescriptor(kvDB, keys.SystemSQLCodec, "t", "test") 862 863 // Construct datums for our row values (k, v1, v2). 864 values := []tree.Datum{tree.NewDInt(217), tree.NewDInt(314), tree.NewDInt(1337)} 865 866 colIDtoRowIndex := make(map[sqlbase.ColumnID]int) 867 colIDtoRowIndex[tableDesc.Columns[0].ID] = 0 868 colIDtoRowIndex[tableDesc.Columns[1].ID] = 1 869 colIDtoRowIndex[tableDesc.Columns[2].ID] = 2 870 871 // Create the primary index key 872 primaryIndexKeyPrefix := sqlbase.MakeIndexKeyPrefix( 873 keys.SystemSQLCodec, tableDesc, tableDesc.PrimaryIndex.ID) 874 primaryIndexKey, _, err := sqlbase.EncodeIndexKey( 875 tableDesc, &tableDesc.PrimaryIndex, colIDtoRowIndex, values, primaryIndexKeyPrefix) 876 if err != nil { 877 t.Fatalf("unexpected error: %s", err) 878 } 879 // Add the default family suffix to the key. 880 primaryIndexKey = keys.MakeFamilyKey(primaryIndexKey, uint32(tableDesc.Families[0].ID)) 881 882 // Encode the second column values. The second column is encoded with 883 // a garbage colIDDiff. 884 valueBuf, err := sqlbase.EncodeTableValue( 885 []byte(nil), tableDesc.Columns[1].ID, values[1], []byte(nil)) 886 if err != nil { 887 t.Fatalf("unexpected error: %s", err) 888 } 889 890 valueBuf, err = sqlbase.EncodeTableValue(valueBuf, 1000, values[2], []byte(nil)) 891 if err != nil { 892 t.Fatalf("unexpected error: %s", err) 893 } 894 895 // Construct the tuple for the family that is missing a column value, i.e. it is NULL. 896 var value roachpb.Value 897 value.SetTuple(valueBuf) 898 899 // Overwrite the existing value. 900 if err := kvDB.Put(context.Background(), primaryIndexKey, &value); err != nil { 901 t.Fatalf("unexpected error: %s", err) 902 } 903 904 // Run SCRUB and find the errors we created. 905 rows, err := db.Query(`EXPERIMENTAL SCRUB TABLE t.test WITH OPTIONS PHYSICAL`) 906 if err != nil { 907 t.Fatalf("unexpected error: %s", err) 908 } 909 defer rows.Close() 910 911 results, err := sqlutils.GetScrubResultRows(rows) 912 if err != nil { 913 t.Fatalf("unexpected error: %s", err) 914 } else if len(results) != 1 { 915 t.Fatalf("expected 1 result, got %d. got %#v", len(results), results) 916 } 917 918 if result := results[0]; result.ErrorType != string(scrub.UnexpectedNullValueError) { 919 t.Fatalf("expected %q error, instead got: %s", 920 scrub.UnexpectedNullValueError, result.ErrorType) 921 } else if result.Database != "t" { 922 t.Fatalf("expected database %q, got %q", "t", result.Database) 923 } else if result.Table != "test" { 924 t.Fatalf("expected table %q, got %q", "test", result.Table) 925 } else if result.PrimaryKey != "(217)" { 926 t.Fatalf("expected primaryKey %q, got %q", "(217)", result.PrimaryKey) 927 } else if result.Repaired { 928 t.Fatalf("expected repaired %v, got %v", false, result.Repaired) 929 } else if !strings.Contains(result.Details, `"k": "217"`) { 930 t.Fatalf("expected error details to contain `%s`, got %s", `"k": "217"`, result.Details) 931 } else if !strings.Contains(result.Details, `"v": "314"`) { 932 t.Fatalf("expected error details to contain `%s`, got %s", `"v": "314"`, result.Details) 933 } else if !strings.Contains(result.Details, `"b": "<unset>"`) { 934 t.Fatalf("expected error details to contain `%s`, got %s", `"b": "<unset>"`, result.Details) 935 } 936 } 937 938 type expectedScrubResult struct { 939 ErrorType string 940 Database string 941 Table string 942 PrimaryKey string 943 Repaired bool 944 DetailsRegex string 945 } 946 947 func checkScrubResult(t *testing.T, res sqlutils.ScrubResult, exp expectedScrubResult) { 948 t.Helper() 949 950 if res.ErrorType != exp.ErrorType { 951 t.Errorf("expected %q error, instead got: %s", exp.ErrorType, res.ErrorType) 952 } 953 954 if res.Database != exp.Database { 955 t.Errorf("expected database %q, got %q", exp.Database, res.Database) 956 } 957 958 if res.Table != exp.Table { 959 t.Errorf("expected table %q, got %q", exp.Table, res.Table) 960 } 961 962 if res.PrimaryKey != exp.PrimaryKey { 963 t.Errorf("expected primary key %q, got %q", exp.PrimaryKey, res.PrimaryKey) 964 } 965 if res.Repaired != exp.Repaired { 966 t.Fatalf("expected repaired %v, got %v", exp.Repaired, res.Repaired) 967 } 968 969 if matched, err := regexp.MatchString(exp.DetailsRegex, res.Details); err != nil { 970 t.Fatal(err) 971 } else if !matched { 972 t.Errorf("expected error details to contain `%s`, got `%s`", exp.DetailsRegex, res.Details) 973 } 974 } 975 976 // runScrub runs a SCRUB statement and checks that it returns exactly one scrub 977 // result and that it matches the expected result. 978 func runScrub(t *testing.T, db *gosql.DB, scrubStmt string, exp expectedScrubResult) { 979 t.Helper() 980 981 // Run SCRUB and find the FOREIGN KEY violation created. 982 rows, err := db.Query(scrubStmt) 983 if err != nil { 984 t.Fatalf("unexpected error: %s", err) 985 } 986 defer rows.Close() 987 988 results, err := sqlutils.GetScrubResultRows(rows) 989 if err != nil { 990 t.Fatalf("unexpected error: %s", err) 991 } 992 993 if len(results) != 1 { 994 t.Fatalf("expected 1 result, got %d. got %#v", len(results), results) 995 } 996 checkScrubResult(t, results[0], exp) 997 }