SERVER-127354 Cover container update testing gaps (#54225)

GitOrigin-RevId: 993f32d216cb152ddd3e809cf9412299248867fc
This commit is contained in:
Parker Felix 2026-05-26 13:27:12 -04:00 committed by MongoDB Bot
parent 1722ac245f
commit a200e3f91a
5 changed files with 444 additions and 77 deletions

View File

@ -1,5 +1,6 @@
/**
* Tests that container insert and delete operations on integer and string keyed containers appear on disk.
* Tests that container insert, update, and delete operations on integer and string keyed containers
* appear on disk.
*
* @tags: [requires_replication, requires_wiredtiger]
*/
@ -8,7 +9,6 @@ import {FeatureFlagUtil} from "jstests/libs/feature_flag_util.js";
import {ReplSetTest} from "jstests/libs/replsettest.js";
const SIGTERM = 15;
const collName = "coll";
const rst = new ReplSetTest({nodes: 1});
rst.startSet();
@ -18,18 +18,19 @@ let primary = rst.getPrimary();
const primaryDB = primary.getDB(jsTestName());
const dbpath = rst.getDbPath(primary);
if (!FeatureFlagUtil.isPresentAndEnabled(primaryDB, "PrimaryDrivenIndexBuilds")) {
if (!FeatureFlagUtil.isPresentAndEnabled(primaryDB, "ContainerWrites")) {
rst.stopSet();
quit();
}
// Namespace required by container ops. Unused otherwise, we operate on an unrelated container.
assert.commandWorked(primaryDB.createCollection(collName));
function makeCI(ns, uri, k, v) {
return {op: "ci", ns, container: uri, o: {k, v}};
}
function makeCU(ns, uri, k, v) {
return {op: "cu", ns, container: uri, o: {k, v, "$v": NumberLong(1)}};
}
function makeCD(ns, uri, k) {
return {op: "cd", ns, container: uri, o: {k}};
}
@ -53,9 +54,11 @@ function toDict(arr) {
return out;
}
const ns = `${primaryDB.getName()}.${collName}`;
// Reserved NamespaceString used for container ops.
const ns = "admin.$container";
const binA = BinData(0, "QQ==");
const binB = BinData(0, "Qg==");
const binC = BinData(0, "Qw==");
const cases = [
{
@ -82,6 +85,32 @@ const cases = [
"B": "B",
},
},
{
uri: "index-intkeys-update",
cfg: "key_format=q,value_format=u",
ops: [
(uri) => makeCI(ns, uri, NumberLong(1), binA),
(uri) => makeCI(ns, uri, NumberLong(2), binB),
(uri) => makeCU(ns, uri, NumberLong(1), binC),
],
expected: {
1: "C",
2: "B",
},
},
{
uri: "index-stringkeys-update",
cfg: "key_format=u,value_format=u",
ops: [
(uri) => makeCI(ns, uri, binA, binA),
(uri) => makeCI(ns, uri, binB, binB),
(uri) => makeCU(ns, uri, binA, binC),
],
expected: {
"A": "C",
"B": "B",
},
},
];
for (const t of cases) {

View File

@ -6905,6 +6905,58 @@ TEST_F(OpObserverTest, OnContainerDelete) {
ASSERT_EQ(std::string(entry2KeyBinData, entry2KeyBinDataLength), key2);
}
TEST_F(OpObserverTest, OnContainerUpdate) {
auto opCtx = cc().makeOperationContext();
Lock::GlobalLock lock{opCtx.get(), LockMode::MODE_IX};
auto ident = "ident";
int64_t key1 = 100;
std::string key2 = "stuff";
std::string value1 = "things";
std::string value2 = "other things";
OpObserverImpl opObserver{std::make_unique<OperationLoggerImpl>()};
opObserver.onContainerUpdate(opCtx.get(), ident, key1, value1);
opObserver.onContainerUpdate(opCtx.get(), ident, key2, value2);
auto entries = getNOplogEntries(opCtx.get(), 2);
auto entry1 = assertGet(OplogEntry::parse(entries[0]));
auto entry2 = assertGet(OplogEntry::parse(entries[1]));
ASSERT_EQ(entry1.getOpType(), repl::OpTypeEnum::kContainerUpdate);
ASSERT_EQ(entry2.getOpType(), repl::OpTypeEnum::kContainerUpdate);
ASSERT_EQ(entry1.getEntry().getContainer(), StringData{ident});
ASSERT_EQ(entry2.getEntry().getContainer(), StringData{ident});
auto entry1Object = entry1.getObject();
ASSERT_EQ(entry1Object.nFields(), 3);
auto entry1Key = entry1Object["k"];
ASSERT_EQ(entry1Key.type(), BSONType::numberLong);
ASSERT_EQ(entry1Key.numberLong(), key1);
auto entry1Value = entry1Object["v"];
ASSERT_EQ(entry1Value.type(), BSONType::binData);
ASSERT_EQ(entry1Value.binDataType(), BinDataType::BinDataGeneral);
int entry1ValueBinDataLength;
auto entry1ValueBinData = entry1Value.binData(entry1ValueBinDataLength);
ASSERT_EQ(std::string(entry1ValueBinData, entry1ValueBinDataLength), value1);
ASSERT_EQ(entry1Object["$v"].numberLong(), 1LL);
auto entry2Object = entry2.getObject();
ASSERT_EQ(entry2Object.nFields(), 3);
auto entry2Key = entry2Object["k"];
ASSERT_EQ(entry2Key.type(), BSONType::binData);
int entry2KeyBinDataLength;
auto entry2KeyBinData = entry2Key.binData(entry2KeyBinDataLength);
ASSERT_EQ(std::string(entry2KeyBinData, entry2KeyBinDataLength), key2);
auto entry2Value = entry2Object["v"];
ASSERT_EQ(entry2Value.type(), BSONType::binData);
ASSERT_EQ(entry2Value.binDataType(), BinDataType::BinDataGeneral);
int entry2ValueBinDataLength;
auto entry2ValueBinData = entry2Value.binData(entry2ValueBinDataLength);
ASSERT_EQ(std::string(entry2ValueBinData, entry2ValueBinDataLength), value2);
ASSERT_EQ(entry2Object["$v"].numberLong(), 1LL);
}
TEST_F(OpObserverTest, onDropIdent) {
OpObserverImpl opObserver(std::make_unique<OperationLoggerImpl>());
auto uniqueOpCtx = cc().makeOperationContext();
@ -7031,6 +7083,66 @@ TEST_F(BatchedWriteOutputsTest, OnContainerDeleteBatched) {
ASSERT_EQ(std::string(entry2KeyBinData, entry2KeyBinDataLength), key2);
}
TEST_F(BatchedWriteOutputsTest, OnContainerUpdateBatched) {
auto opCtx = cc().makeOperationContext();
Lock::GlobalLock lock{opCtx.get(), LockMode::MODE_IX};
WriteUnitOfWork wuow{opCtx.get(), WriteUnitOfWork::OplogEntryGroupType::kGroupForTransaction};
auto ident = "ident";
int64_t key1 = 100;
std::string key2 = "stuff";
std::string value1 = "things";
std::string value2 = "other things";
opCtx->getServiceContext()->getOpObserver()->onContainerUpdate(
opCtx.get(), ident, key1, value1);
opCtx->getServiceContext()->getOpObserver()->onContainerUpdate(
opCtx.get(), ident, key2, value2);
wuow.commit();
auto entry = assertGet(OplogEntry::parse(getNOplogEntries(opCtx.get(), 1)[0]));
ASSERT_EQ(entry.getOpType(), repl::OpTypeEnum::kCommand);
ASSERT_EQ(entry.getCommandType(), OplogEntry::CommandType::kApplyOps);
std::vector<repl::OplogEntry> innerEntries;
repl::ApplyOps::extractOperationsTo(entry, entry.getEntry().toBSON(), &innerEntries);
ASSERT_EQ(innerEntries.size(), 2);
ASSERT_EQ(innerEntries[0].getOpType(), repl::OpTypeEnum::kContainerUpdate);
ASSERT_EQ(innerEntries[1].getOpType(), repl::OpTypeEnum::kContainerUpdate);
ASSERT_EQ(innerEntries[0].getEntry().getContainer(), StringData{ident});
ASSERT_EQ(innerEntries[1].getEntry().getContainer(), StringData{ident});
auto entry1Object = innerEntries[0].getObject();
ASSERT_EQ(entry1Object.nFields(), 3);
auto entry1Key = entry1Object["k"];
ASSERT_EQ(entry1Key.type(), BSONType::numberLong);
ASSERT_EQ(entry1Key.numberLong(), key1);
auto entry1Value = entry1Object["v"];
ASSERT_EQ(entry1Value.type(), BSONType::binData);
ASSERT_EQ(entry1Value.binDataType(), BinDataType::BinDataGeneral);
int entry1ValueBinDataLength;
auto entry1ValueBinData = entry1Value.binData(entry1ValueBinDataLength);
ASSERT_EQ(std::string(entry1ValueBinData, entry1ValueBinDataLength), value1);
ASSERT_EQ(entry1Object["$v"].numberLong(), 1LL);
auto entry2Object = innerEntries[1].getObject();
ASSERT_EQ(entry2Object.nFields(), 3);
auto entry2Key = entry2Object["k"];
ASSERT_EQ(entry2Key.type(), BSONType::binData);
int entry2KeyBinDataLength;
auto entry2KeyBinData = entry2Key.binData(entry2KeyBinDataLength);
ASSERT_EQ(std::string(entry2KeyBinData, entry2KeyBinDataLength), key2);
auto entry2Value = entry2Object["v"];
ASSERT_EQ(entry2Value.type(), BSONType::binData);
ASSERT_EQ(entry2Value.binDataType(), BinDataType::BinDataGeneral);
int entry2ValueBinDataLength;
auto entry2ValueBinData = entry2Value.binData(entry2ValueBinDataLength);
ASSERT_EQ(std::string(entry2ValueBinData, entry2ValueBinDataLength), value2);
ASSERT_EQ(entry2Object["$v"].numberLong(), 1LL);
}
TEST_F(BatchedWriteOutputsTest, OnContainerInsertDeleteBatchedWithInsertDeleteUpdate) {
auto opCtx = cc().makeOperationContext();
reset(opCtx.get(), _nss);
@ -7220,6 +7332,65 @@ TEST_F(OpObserverTransactionTest, OnContainerDelete) {
ASSERT_EQ(std::string(entry2KeyBinData, entry2KeyBinDataLength), key2);
}
TEST_F(OpObserverTransactionTest, OnContainerUpdate) {
TransactionParticipant::get(opCtx()).unstashTransactionResources(opCtx(), "update");
auto ident = "ident";
int64_t key1 = 100;
std::string key2 = "stuff";
std::string value1 = "things";
std::string value2 = "other things";
opObserver().onContainerUpdate(opCtx(), ident, key1, value1);
opObserver().onContainerUpdate(opCtx(), ident, key2, value2);
commitUnpreparedTransaction<OpObserverImpl>(opCtx(), opObserver());
auto entryObj = getSingleOplogEntry(opCtx());
checkCommonFields(entryObj);
auto entry = assertGet(OplogEntry::parse(entryObj));
ASSERT_EQ(entry.getOpType(), repl::OpTypeEnum::kCommand);
ASSERT_EQ(entry.getCommandType(), OplogEntry::CommandType::kApplyOps);
std::vector<repl::OplogEntry> innerEntries;
repl::ApplyOps::extractOperationsTo(entry, entry.getEntry().toBSON(), &innerEntries);
ASSERT_EQ(innerEntries.size(), 2);
ASSERT_EQ(innerEntries[0].getOpType(), repl::OpTypeEnum::kContainerUpdate);
ASSERT_EQ(innerEntries[1].getOpType(), repl::OpTypeEnum::kContainerUpdate);
ASSERT_EQ(innerEntries[0].getEntry().getContainer(), StringData{ident});
ASSERT_EQ(innerEntries[1].getEntry().getContainer(), StringData{ident});
auto entry1Object = innerEntries[0].getObject();
ASSERT_EQ(entry1Object.nFields(), 3);
auto entry1Key = entry1Object["k"];
ASSERT_EQ(entry1Key.type(), BSONType::numberLong);
ASSERT_EQ(entry1Key.numberLong(), key1);
auto entry1Value = entry1Object["v"];
ASSERT_EQ(entry1Value.type(), BSONType::binData);
ASSERT_EQ(entry1Value.binDataType(), BinDataType::BinDataGeneral);
int entry1ValueBinDataLength;
auto entry1ValueBinData = entry1Value.binData(entry1ValueBinDataLength);
ASSERT_EQ(std::string(entry1ValueBinData, entry1ValueBinDataLength), value1);
ASSERT_EQ(entry1Object["$v"].numberLong(), 1LL);
auto entry2Object = innerEntries[1].getObject();
ASSERT_EQ(entry2Object.nFields(), 3);
auto entry2Key = entry2Object["k"];
ASSERT_EQ(entry2Key.type(), BSONType::binData);
int entry2KeyBinDataLength;
auto entry2KeyBinData = entry2Key.binData(entry2KeyBinDataLength);
ASSERT_EQ(std::string(entry2KeyBinData, entry2KeyBinDataLength), key2);
auto entry2Value = entry2Object["v"];
ASSERT_EQ(entry2Value.type(), BSONType::binData);
ASSERT_EQ(entry2Value.binDataType(), BinDataType::BinDataGeneral);
int entry2ValueBinDataLength;
auto entry2ValueBinData = entry2Value.binData(entry2ValueBinDataLength);
ASSERT_EQ(std::string(entry2ValueBinData, entry2ValueBinDataLength), value2);
ASSERT_EQ(entry2Object["$v"].numberLong(), 1LL);
}
TEST_F(OpObserverTransactionTest, OnContainerInsertDeleteWithInsertDeleteUpdate) {
TransactionParticipant::get(opCtx()).unstashTransactionResources(opCtx(), "insert");
AutoGetCollection coll{opCtx(), nss, LockMode::MODE_IX};

View File

@ -515,119 +515,137 @@ TEST_F(ApplyContainerOpsTest, ContainerOpsRejectMismatchedExistingCommitTimestam
ru->clearCommitTimestamp();
}
TEST_F(ApplyContainerOpsTest, ParseContainerUpdateFormatFailures) {
TEST_F(ApplyContainerOpsTest, ParseContainerFormatFailures) {
int64_t k = 1;
auto v = BSONBinData("V", 1, BinDataGeneral);
auto wrongTypeV = "notBinData";
auto wrongTypeK = "notBinDataOrInt64";
auto version = static_cast<int64_t>(container::UpdateOplogEntryVersion::kFullReplacementV1);
auto base = [&]() {
auto baseInsert = [&]() {
return makeBaseParams(
_nss, _intIdent, OpTypeEnum::kContainerInsert, BSON("k" << k << "v" << v));
};
auto baseDelete = [&]() {
return makeBaseParams(_nss, _intIdent, OpTypeEnum::kContainerDelete, BSON("k" << k));
};
auto baseUpdate = [&]() {
return makeBaseParams(_nss,
_intIdent,
OpTypeEnum::kContainerUpdate,
BSON("k" << k << "v" << v << "$v" << version));
};
// missing container
{
auto p = base();
p.container = boost::none;
ASSERT_THROWS_CODE(DurableOplogEntry(p), DBException, 10704701);
}
// missing key
{
auto p = base();
p.oField = BSON("v" << v << "$v" << 1);
ASSERT_THROWS_CODE(DurableOplogEntry(p), DBException, ErrorCodes::IDLFailedToParse);
}
// missing $v
{
auto p = base();
p.oField = BSON("k" << k << "v" << v);
ASSERT_THROWS_CODE(DurableOplogEntry(p), DBException, ErrorCodes::IDLFailedToParse);
}
// $v must be numeric
{
auto p = base();
p.oField = BSON("k" << k << "v" << v << "$v" << "notANumber");
ASSERT_THROWS_CODE(DurableOplogEntry(p), DBException, ErrorCodes::TypeMismatch);
}
// missing value
{
auto p = base();
p.oField = BSON("k" << k << "$v" << 1);
ASSERT_THROWS_CODE(DurableOplogEntry(p), DBException, ErrorCodes::IDLFailedToParse);
}
// value type must be binData
{
auto p = base();
p.oField = BSON("k" << k << "v" << wrongTypeV << "$v" << version);
ASSERT_THROWS_CODE(DurableOplogEntry(p), DBException, ErrorCodes::TypeMismatch);
}
}
TEST_F(ApplyContainerOpsTest, ParseContainerOpFormatFailures) {
int64_t k = 1;
auto v = BSONBinData("V", 1, BinDataGeneral);
auto wrongTypeV = "notBinData";
auto wrongTypeK = "notBinDataOrInt64";
auto base = [&]() {
return makeBaseParams(
_nss, _intIdent, OpTypeEnum::kContainerInsert, BSON("k" << k << "v" << v));
};
/*
* Container operations ('ci' and 'cd') must take the following form:
* Container operations ('ci', 'cd', and 'cu') must take the following form:
*
* {
* ... // base oplog entry fields
* "op": "ci" | "cd",
* "op": "ci" | "cd" | "cu",
* "container": <string>,
* "o": {
* "k": <BinData | NumberLong>,
* "v": <BinData> // only allowed for "ci"
* "v": <BinData> // only allowed for "ci" and "cu"
* "$v": <NumberLong> // only allowed for "cu"
* }
* }
*/
// missing container
// missing container - insert, delete, update
{
auto p = base();
auto p = baseInsert();
p.container = boost::none;
ASSERT_THROWS_CODE(DurableOplogEntry(p), DBException, 10704701);
}
// missing key
{
auto p = base();
auto p = baseDelete();
p.container = boost::none;
ASSERT_THROWS_CODE(DurableOplogEntry(p), DBException, 10704701);
}
{
auto p = baseUpdate();
p.container = boost::none;
ASSERT_THROWS_CODE(DurableOplogEntry(p), DBException, 10704701);
}
// missing key - insert, delete, update
{
auto p = baseInsert();
p.oField = BSON("v" << v);
ASSERT_THROWS_CODE(DurableOplogEntry(p), DBException, ErrorCodes::IDLFailedToParse);
}
// missing value
{
auto p = base();
p.oField = BSON("k" << k);
auto p = baseDelete();
p.oField = BSONObj();
ASSERT_THROWS_CODE(DurableOplogEntry(p), DBException, ErrorCodes::IDLFailedToParse);
}
// container delete cannot contain value
{
auto p = base();
p.opType = OpTypeEnum::kContainerDelete;
p.oField = BSON("k" << k << "v" << v);
ASSERT_THROWS_CODE(DurableOplogEntry(p), DBException, 10704704);
auto p = baseUpdate();
p.oField = BSON("v" << v << "$v" << version);
ASSERT_THROWS_CODE(DurableOplogEntry(p), DBException, ErrorCodes::IDLFailedToParse);
}
// key type must be binData or numberLong
// key type must be binData or numberLong - insert, delete, update
{
auto p = base();
auto p = baseInsert();
p.oField = BSON("k" << wrongTypeK << "v" << v);
ASSERT_THROWS_CODE(DurableOplogEntry(p), DBException, 12270900);
}
// value type must be binData
{
auto p = base();
auto p = baseDelete();
p.oField = BSON("k" << wrongTypeK);
ASSERT_THROWS_CODE(DurableOplogEntry(p), DBException, 12270900);
}
{
auto p = baseUpdate();
p.oField = BSON("k" << wrongTypeK << "v" << v << "$v" << version);
ASSERT_THROWS_CODE(DurableOplogEntry(p), DBException, 12270900);
}
// missing value - insert and update
{
auto p = baseInsert();
p.oField = BSON("k" << k);
ASSERT_THROWS_CODE(DurableOplogEntry(p), DBException, ErrorCodes::IDLFailedToParse);
}
{
auto p = baseUpdate();
p.oField = BSON("k" << k << "$v" << version);
ASSERT_THROWS_CODE(DurableOplogEntry(p), DBException, ErrorCodes::IDLFailedToParse);
}
// value type must be binData - insert and update
{
auto p = baseInsert();
p.oField = BSON("k" << k << "v" << wrongTypeV);
ASSERT_THROWS_CODE(DurableOplogEntry(p), DBException, ErrorCodes::TypeMismatch);
}
{
auto p = baseUpdate();
p.oField = BSON("k" << k << "v" << wrongTypeV << "$v" << version);
ASSERT_THROWS_CODE(DurableOplogEntry(p), DBException, ErrorCodes::TypeMismatch);
}
// delete: cannot contain value
{
auto p = baseDelete();
p.oField = BSON("k" << k << "v" << v);
ASSERT_THROWS_CODE(DurableOplogEntry(p), DBException, 10704704);
}
// update: missing version
{
auto p = baseUpdate();
p.oField = BSON("k" << k << "v" << v);
ASSERT_THROWS_CODE(DurableOplogEntry(p), DBException, ErrorCodes::IDLFailedToParse);
}
// update: version must be numeric
{
auto p = baseUpdate();
p.oField = BSON("k" << k << "v" << v << "$v" << "notANumber");
ASSERT_THROWS_CODE(DurableOplogEntry(p), DBException, ErrorCodes::TypeMismatch);
}
}
} // namespace

View File

@ -2329,6 +2329,66 @@ TEST_F(OplogApplierImplTest, ApplyApplyOpsContainerOperations) {
_opCtx.get(), ApplierOperation{&entry}, OplogApplication::Mode::kSecondary));
}
// TODO (SERVER-109556): Adjustments to suites coming from this ticket might result in a better
// candidate for this test's fixture.
TEST_F(OplogApplierImplTest, ApplyApplyOpsContainerUpdateOperation) {
// TODO (SERVER-116165): Remove.
RAIIServerParameterControllerForTest ffContainerWrites("featureFlagContainerWrites", true);
NamespaceString nss = NamespaceString::createNamespaceString_forTest("test.t");
auto storageEngine = serviceContext->getStorageEngine();
auto ident = storageEngine->generateNewInternalIdent();
auto ru = storageEngine->newRecoveryUnit();
StorageWriteTransaction swt(*ru);
auto trs = storageEngine->getEngine()->makeInternalRecordStore(*ru, ident, KeyFormat::String);
swt.commit();
auto k = BSONBinData("K", 1, BinDataGeneral);
auto v1 = BSONBinData("V1", 2, BinDataGeneral);
auto v2 = BSONBinData("V2", 2, BinDataGeneral);
const auto entryOpTime = nextOpTime();
ASSERT(!entryOpTime.isNull());
const BSONObj containerInsertOp =
BSON("op" << "ci"
<< "ns" << nss.ns_forTest() << "container" << ident << "o"
<< BSON("k" << k << "v" << v1) << "ts" << entryOpTime.getTimestamp());
const BSONObj containerUpdateOp = BSON("op" << "cu"
<< "ns" << nss.ns_forTest() << "container" << ident
<< "o" << BSON("k" << k << "v" << v2 << "$v" << 1LL)
<< "ts" << entryOpTime.getTimestamp());
BSONArray innerOps = BSON_ARRAY(containerInsertOp << containerUpdateOp);
/**
* o: {
* applyOps: [
* {
* op: "ci",
* ns: "<db>.<coll>",
* container: "<ident>",
* o: {k: <BinData>, v: <BinData>},
* ts: <entryOpTime>
* },
* {
* op: "cu",
* ns: "<db>.<coll>",
* container: "<ident>",
* o: {k: <BinData>, v: <BinData>, $v: 1},
* ts: <entryOpTime>
* }
* ]
* }
*/
BSONObj applyOpsCmd = BSON("applyOps" << innerOps);
auto entry = makeCommandOplogEntry(entryOpTime, nss, applyOpsCmd, boost::none, boost::none);
ASSERT_OK(_applyOplogEntryOrGroupedInsertsWrapper(
_opCtx.get(), ApplierOperation{&entry}, OplogApplication::Mode::kSecondary));
}
// TODO (SERVER-109556): Adjustments to suites coming from this ticket might result in a better
// candidate for this test's fixture.
TEST_F(OplogApplierImplTest, ApplyContainerOperations) {
@ -2356,6 +2416,34 @@ TEST_F(OplogApplierImplTest, ApplyContainerOperations) {
_opCtx.get(), ApplierOperation{&deleteEntry}, OplogApplication::Mode::kSecondary));
}
// TODO (SERVER-109556): Adjustments to suites coming from this ticket might result in a better
// candidate for this test's fixture.
TEST_F(OplogApplierImplTest, ApplyContainerUpdateOperation) {
// TODO (SERVER-116165): Remove.
RAIIServerParameterControllerForTest ffContainerWrites("featureFlagContainerWrites", true);
auto nss = NamespaceString::createNamespaceString_forTest("test.t");
auto storageEngine = serviceContext->getStorageEngine();
auto ident = storageEngine->generateNewInternalIdent();
auto ru = storageEngine->newRecoveryUnit();
StorageWriteTransaction swt(*ru);
auto trs = storageEngine->getEngine()->makeInternalRecordStore(*ru, ident, KeyFormat::String);
swt.commit();
auto k = BSONBinData("K", 1, BinDataGeneral);
auto v1 = BSONBinData("V1", 2, BinDataGeneral);
auto v2 = BSONBinData("V2", 2, BinDataGeneral);
auto insertEntry = makeContainerInsertOplogEntry(nextOpTime(), ident, k, v1);
auto updateEntry = makeContainerUpdateOplogEntry(nextOpTime(), ident, k, v2);
ASSERT_OK(_applyOplogEntryOrGroupedInsertsWrapper(
_opCtx.get(), ApplierOperation{&insertEntry}, OplogApplication::Mode::kSecondary));
ASSERT_OK(_applyOplogEntryOrGroupedInsertsWrapper(
_opCtx.get(), ApplierOperation{&updateEntry}, OplogApplication::Mode::kSecondary));
}
TEST_F(OplogApplierImplTest, ContainerOplogEntryHashesOnKey) {
auto nss = NamespaceString::createNamespaceString_forTest("test.hash");
auto ident1 = serviceContext->getStorageEngine()->generateNewInternalIdent();
@ -2383,6 +2471,37 @@ TEST_F(OplogApplierImplTest, ContainerOplogEntryHashesOnKey) {
ASSERT_NOT_EQUALS(id1, id4);
}
TEST_F(OplogApplierImplTest, ContainerUpdateOplogEntryHashesOnKey) {
auto nss = NamespaceString::createNamespaceString_forTest("test.hash");
auto ident1 = serviceContext->getStorageEngine()->generateNewInternalIdent();
auto ident2 = serviceContext->getStorageEngine()->generateNewInternalIdent();
auto k1 = BSONBinData("K", 1, BinDataGeneral);
auto k2 = BSONBinData("K", 2, BinDataGeneral);
auto v = BSONBinData("V", 1, BinDataGeneral);
auto insertEntry = makeContainerInsertOplogEntry(nextOpTime(), ident1, k1, v);
auto updateEntry = makeContainerUpdateOplogEntry(nextOpTime(), ident1, k1, v);
auto updateDiffKeyEntry = makeContainerUpdateOplogEntry(nextOpTime(), ident1, k2, v);
auto updateDiffIdentEntry = makeContainerUpdateOplogEntry(nextOpTime(), ident2, k1, v);
CachedCollectionProperties collPropertiesCache;
uint32_t insertHash =
OplogApplierUtils::getOplogEntryHash(_opCtx.get(), &insertEntry, &collPropertiesCache);
uint32_t updateHash =
OplogApplierUtils::getOplogEntryHash(_opCtx.get(), &updateEntry, &collPropertiesCache);
uint32_t updateDiffKeyHash = OplogApplierUtils::getOplogEntryHash(
_opCtx.get(), &updateDiffKeyEntry, &collPropertiesCache);
uint32_t updateDiffIdentHash = OplogApplierUtils::getOplogEntryHash(
_opCtx.get(), &updateDiffIdentEntry, &collPropertiesCache);
// Update on the same key should hash the same as insert on that key.
ASSERT_EQUALS(insertHash, updateHash);
// Updates on different keys should hash differently.
ASSERT_NOT_EQUALS(updateHash, updateDiffKeyHash);
// Updates on the same key but different idents should hash differently.
ASSERT_NOT_EQUALS(updateHash, updateDiffIdentHash);
}
class MultiOplogEntryOplogApplierImplTest : public OplogApplierImplTest {
public:
MultiOplogEntryOplogApplierImplTest()

View File

@ -479,6 +479,36 @@ TEST_F(OplogEntryTest, ContainerDeleteParse) {
ASSERT_EQ(*entry.getContainer(), ident);
}
TEST_F(OplogEntryTest, ContainerUpdateParse) {
const NamespaceString nss = NamespaceString::createNamespaceString_forTest("test.coll");
const std::string ident = "test_ident";
const BSONObj oplogBson = [&] {
BSONObjBuilder bob;
bob.append("ts", Timestamp(1, 1));
bob.append("t", 1LL);
bob.append("op", "cu");
bob.append("ns", nss.ns_forTest());
bob.append("container", ident);
bob.append("wall", Date_t());
BSONObjBuilder oBuilder(bob.subobjStart("o"));
oBuilder.appendBinData("k", 3, BinDataGeneral, "abc");
oBuilder.appendBinData("v", 4, BinDataGeneral, "defg");
oBuilder.append("$v", 1LL);
oBuilder.done();
return bob.obj();
}();
auto entry = unittest::assertGet(DurableOplogEntry::parse(oplogBson));
ASSERT_EQ(entry.getOpType(), repl::OpTypeEnum::kContainerUpdate);
ASSERT_EQ(entry.getNss(), nss);
ASSERT_TRUE(entry.getContainer());
ASSERT_EQ(*entry.getContainer(), ident);
}
TEST_F(OplogEntryTest, ContainerOpMissingContainer) {
const NamespaceString nss = NamespaceString::createNamespaceString_forTest("test.coll");