diff options
-rw-r--r-- | jstests/core/index_partial_write_ops.js | 19 | ||||
-rw-r--r-- | src/mongo/db/catalog/index_catalog.cpp | 9 | ||||
-rw-r--r-- | src/mongo/db/storage/wiredtiger/wiredtiger_index.cpp | 42 | ||||
-rw-r--r-- | src/mongo/db/storage/wiredtiger/wiredtiger_index.h | 3 |
4 files changed, 59 insertions, 14 deletions
diff --git a/jstests/core/index_partial_write_ops.js b/jstests/core/index_partial_write_ops.js index 2653a2edf00..41e0e0263ef 100644 --- a/jstests/core/index_partial_write_ops.js +++ b/jstests/core/index_partial_write_ops.js @@ -1,4 +1,5 @@ // Write ops tests for partial indexes. +// @tags: [cannot_create_unique_index_when_using_hashed_shard_key] (function() { "use strict"; @@ -20,7 +21,7 @@ coll.drop(); // Create partial index. - assert.commandWorked(coll.ensureIndex({x: 1}, {partialFilterExpression: {a: 1}})); + assert.commandWorked(coll.ensureIndex({x: 1}, {unique: true, partialFilterExpression: {a: 1}})); assert.writeOK(coll.insert({_id: 1, x: 5, a: 2, b: 1})); // Not in index. assert.writeOK(coll.insert({_id: 2, x: 6, a: 1, b: 1})); // In index. @@ -60,4 +61,20 @@ // Delete that does affect partial index. assert.writeOK(coll.remove({x: 6})); assert.eq(0, getNumKeys("x_1")); + + // Documents with duplicate keys that straddle the index. + assert.writeOK(coll.insert({_id: 3, x: 1, a: 1})); // In index. + assert.writeOK(coll.insert({_id: 4, x: 1, a: 0})); // Not in index. + assert.writeErrorWithCode( + coll.insert({_id: 5, x: 1, a: 1}), + ErrorCodes.DuplicateKey); // Duplicate key constraint prevents insertion. + + // Only _id 3 is in the index. + assert.eq(1, getNumKeys("x_1")); + + // Remove _id 4, _id 3 should remain in index. + assert.writeOK(coll.remove({_id: 4})); + + // _id 3 is still in the index. + assert.eq(1, getNumKeys("x_1")); })(); diff --git a/src/mongo/db/catalog/index_catalog.cpp b/src/mongo/db/catalog/index_catalog.cpp index 382c060bdbd..42ca3cbe768 100644 --- a/src/mongo/db/catalog/index_catalog.cpp +++ b/src/mongo/db/catalog/index_catalog.cpp @@ -1306,9 +1306,12 @@ Status IndexCatalog::_unindexRecord(OperationContext* opCtx, prepareInsertDeleteOptions(opCtx, index->descriptor(), &options); options.logIfError = logIfError; - // For unindex operations, dupsAllowed=false really means that it is safe to delete anything - // that matches the key, without checking the RecordID, since dups are impossible. We need - // to disable this behavior for in-progress indexes. See SERVER-17487 for more details. + // On WiredTiger, we do blind unindexing of records for efficiency. However, when duplicates + // are allowed in unique indexes, WiredTiger does not do blind unindexing, and instead confirms + // that the recordid matches the element we are removing. + // We need to disable blind-deletes for in-progress indexes, in order to force recordid-matching + // for unindex operations, since initial sync can build an index over a collection with + // duplicates. See SERVER-17487 for more details. options.dupsAllowed = options.dupsAllowed || !index->isReady(opCtx); int64_t removed; diff --git a/src/mongo/db/storage/wiredtiger/wiredtiger_index.cpp b/src/mongo/db/storage/wiredtiger/wiredtiger_index.cpp index 05bafb50c96..f9bdfb21c60 100644 --- a/src/mongo/db/storage/wiredtiger/wiredtiger_index.cpp +++ b/src/mongo/db/storage/wiredtiger/wiredtiger_index.cpp @@ -1004,7 +1004,7 @@ public: WiredTigerIndexUnique::WiredTigerIndexUnique(OperationContext* ctx, const std::string& uri, const IndexDescriptor* desc) - : WiredTigerIndex(ctx, uri, desc) {} + : WiredTigerIndex(ctx, uri, desc), _partial(desc->isPartial()) {} std::unique_ptr<SortedDataInterface::Cursor> WiredTigerIndexUnique::newCursor( OperationContext* opCtx, bool forward) const { @@ -1088,8 +1088,37 @@ void WiredTigerIndexUnique::_unindex(WT_CURSOR* c, WiredTigerItem keyItem(data.getBuffer(), data.getSize()); c->set_key(c, keyItem.Get()); + auto triggerWriteConflictAtPoint = [&keyItem](WT_CURSOR* point) { + // WT_NOTFOUND may occur during a background index build. Insert a dummy value and + // delete it again to trigger a write conflict in case this is being concurrently + // indexed by the background indexer. + point->set_key(point, keyItem.Get()); + point->set_value(point, emptyItem.Get()); + invariantWTOK(WT_OP_CHECK(point->insert(point))); + point->set_key(point, keyItem.Get()); + invariantWTOK(WT_OP_CHECK(point->remove(point))); + }; + if (!dupsAllowed) { - // nice and clear + if (_partial) { + // Check that the record id matches. We may be called to unindex records that are not + // present in the index due to the partial filter expression. + int ret = WT_READ_CHECK(c->search(c)); + if (ret == WT_NOTFOUND) { + triggerWriteConflictAtPoint(c); + return; + } + WT_ITEM value; + invariantWTOK(c->get_value(c, &value)); + BufReader br(value.data, value.size); + fassert(40416, br.remaining()); + if (KeyString::decodeRecordId(&br) != id) { + return; + } + // Ensure there aren't any other values in here. + KeyString::TypeBits::fromBuffer(keyStringVersion(), &br); + fassert(40417, !br.remaining()); + } int ret = WT_OP_CHECK(c->remove(c)); if (ret == WT_NOTFOUND) { return; @@ -1102,14 +1131,7 @@ void WiredTigerIndexUnique::_unindex(WT_CURSOR* c, int ret = WT_READ_CHECK(c->search(c)); if (ret == WT_NOTFOUND) { - // WT_NOTFOUND is only expected during a background index build. Insert a dummy value and - // delete it again to trigger a write conflict in case this is being concurrently indexed by - // the background indexer. - c->set_key(c, keyItem.Get()); - c->set_value(c, emptyItem.Get()); - invariantWTOK(WT_OP_CHECK(c->insert(c))); - c->set_key(c, keyItem.Get()); - invariantWTOK(WT_OP_CHECK(c->remove(c))); + triggerWriteConflictAtPoint(c); return; } invariantWTOK(ret); diff --git a/src/mongo/db/storage/wiredtiger/wiredtiger_index.h b/src/mongo/db/storage/wiredtiger/wiredtiger_index.h index 20485fa8f9d..78d8e788b07 100644 --- a/src/mongo/db/storage/wiredtiger/wiredtiger_index.h +++ b/src/mongo/db/storage/wiredtiger/wiredtiger_index.h @@ -174,6 +174,9 @@ public: Status _insert(WT_CURSOR* c, const BSONObj& key, const RecordId& id, bool dupsAllowed) override; void _unindex(WT_CURSOR* c, const BSONObj& key, const RecordId& id, bool dupsAllowed) override; + +private: + bool _partial; }; class WiredTigerIndexStandard : public WiredTigerIndex { |