// deleteRow adds to the batch the kv operations necessary to delete a table row // with the given values. func (rd *rowDeleter) deleteRow(ctx context.Context, b *client.Batch, values []parser.Datum) error { if err := rd.fks.checkAll(values); err != nil { return err } primaryIndexKey, secondaryIndexEntries, err := rd.helper.encodeIndexes(rd.fetchColIDtoRowIndex, values) if err != nil { return err } for _, secondaryIndexEntry := range secondaryIndexEntries { if log.V(2) { log.Infof(ctx, "Del %s", secondaryIndexEntry.Key) } b.Del(secondaryIndexEntry.Key) } // Delete the row. rd.startKey = roachpb.Key(primaryIndexKey) rd.endKey = roachpb.Key(encoding.EncodeNotNullDescending(primaryIndexKey)) if log.V(2) { log.Infof(ctx, "DelRange %s - %s", rd.startKey, rd.endKey) } b.DelRange(&rd.startKey, &rd.endKey, false) rd.startKey, rd.endKey = nil, nil return nil }
// TestTxnDelRangeIntentResolutionCounts ensures that intents left behind by a // DelRange with a MaxSpanRequestKeys limit are resolved correctly and by // using the minimal span of keys. func TestTxnDelRangeIntentResolutionCounts(t *testing.T) { defer leaktest.AfterTest(t)() var intentResolutionCount int64 params := base.TestServerArgs{ Knobs: base.TestingKnobs{ Store: &storage.StoreTestingKnobs{ NumKeysEvaluatedForRangeIntentResolution: &intentResolutionCount, TestingCommandFilter: func(filterArgs storagebase.FilterArgs) *roachpb.Error { req, ok := filterArgs.Req.(*roachpb.ResolveIntentRequest) if ok { key := req.Header().Key.String() // Check if the intent is from the range being // scanned below. if key >= "a" && key < "d" { t.Errorf("resolving intent on key %s", key) } } return nil }, }, }, } s, _, db := serverutils.StartServer(t, params) defer s.Stopper().Stop() for _, abortTxn := range []bool{false, true} { spanSize := int64(10) prefixes := []string{"a", "b", "c"} for i := int64(0); i < spanSize; i++ { for _, prefix := range prefixes { if err := db.Put(context.TODO(), fmt.Sprintf("%s%d", prefix, i), "v"); err != nil { t.Fatal(err) } } } totalNumKeys := int64(len(prefixes)) * spanSize atomic.StoreInt64(&intentResolutionCount, 0) limit := totalNumKeys / 2 if err := db.Txn(context.TODO(), func(txn *client.Txn) error { var b client.Batch // Fully deleted. b.DelRange("a", "b", false) // Partially deleted. b.DelRange("b", "c", false) // Not deleted. b.DelRange("c", "d", false) b.Header.MaxSpanRequestKeys = limit if err := txn.Run(&b); err != nil { return err } if abortTxn { return errors.New("aborting txn") } return nil }); err != nil && !abortTxn { t.Fatal(err) } // Ensure that the correct number of keys were evaluated for intents. if numKeys := atomic.LoadInt64(&intentResolutionCount); numKeys != limit { t.Fatalf("abortTxn: %v, resolved %d keys, expected %d", abortTxn, numKeys, limit) } // Ensure no intents are left behind. Scan the entire span to resolve // any intents that were left behind; intents are resolved internally // through ResolveIntentRequest(s). kvs, err := db.Scan(context.TODO(), "a", "d", totalNumKeys) if err != nil { t.Fatal(err) } expectedNumKeys := totalNumKeys / 2 if abortTxn { expectedNumKeys = totalNumKeys } if int64(len(kvs)) != expectedNumKeys { t.Errorf("abortTxn: %v, %d keys left behind, expected=%d", abortTxn, len(kvs), expectedNumKeys) } } }