SERVER-127354 Cover container update testing gaps (#54225)
GitOrigin-RevId: 993f32d216cb152ddd3e809cf9412299248867fc
This commit is contained in:
parent
1722ac245f
commit
a200e3f91a
@ -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) {
|
||||
|
||||
@ -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};
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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");
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user