SERVER-116272: Introduce 'validated' document schema validation level (#46706)

Co-authored-by: Niels Lohmann <niels.lohmann@mongodb.com>
GitOrigin-RevId: 367679c83d10a0ae7c312d545da5130573694d82
This commit is contained in:
Colin Stolley 2026-02-06 13:09:25 -06:00 committed by MongoDB Bot
parent 784adb1029
commit d5122c7b20
42 changed files with 624 additions and 238 deletions

View File

@ -0,0 +1,194 @@
/**
* Test that constraints on collections with validation level 'validated' are correctly enforced.
*
* @tags: [
* # 'validated' level was introduced in 8.3.
* requires_fcv_83,
* featureFlagValidatedValidationLevel,
* ]
*/
const dbName = "validated_tests";
const collName = "test";
const validator = {a: {$exists: true}};
const myDb = db.getSiblingDB(dbName);
function createValidatedCollection() {
myDb.runCommand({drop: collName, writeConcern: {w: "majority"}});
assert.commandWorked(
myDb.createCollection(collName, {
validator: validator,
validationLevel: "validated",
validationAction: "error",
writeConcern: {w: "majority"},
}),
);
}
function validationActionMustNotBeWarn() {
myDb.runCommand({drop: collName, writeConcern: {w: "majority"}});
assert.commandFailed(
myDb.createCollection(collName, {
validator: validator,
validationLevel: "validated",
validationAction: "warn",
writeConcern: {w: "majority"},
}),
);
createValidatedCollection();
assert.commandFailed(myDb.runCommand({"collMod": collName, validationAction: "warn"}));
assert.commandWorked(myDb.runCommand({"collMod": collName, validationAction: "errorAndLog"}));
// Warn should be ok for any other level.
assert.commandWorked(
myDb.runCommand({
"collMod": collName,
validationLevel: "strict",
validator: {b: {$exists: true}},
writeConcern: {w: "majority"},
validationAction: "error",
}),
);
assert.commandWorked(myDb.runCommand({"collMod": collName, validationAction: "warn"}));
assert.commandWorked(
myDb.runCommand({
"collMod": collName,
validationLevel: "moderate",
validator: {b: {$exists: true}},
writeConcern: {w: "majority"},
validationAction: "error",
}),
);
assert.commandWorked(myDb.runCommand({"collMod": collName, validationAction: "warn"}));
assert.commandWorked(
myDb.runCommand({
"collMod": collName,
validationLevel: "off",
validator: {b: {$exists: true}},
writeConcern: {w: "majority"},
validationAction: "error",
}),
);
assert.commandWorked(myDb.runCommand({"collMod": collName, validationAction: "warn"}));
}
function noValidatedOnExisting() {
myDb.runCommand({drop: collName, writeConcern: {w: "majority"}});
assert.commandWorked(myDb.createCollection(collName, {writeConcern: {w: "majority"}}));
assert.commandFailed(
myDb.runCommand({
"collMod": collName,
validator: validator,
validationLevel: "validated",
validationAction: "error",
writeConcern: {w: "majority"},
}),
);
assert.commandFailed(
myDb.runCommand({
"collMod": collName,
validationLevel: "validated",
validationAction: "warn",
writeConcern: {w: "majority"},
}),
);
assert.commandFailed(
myDb.runCommand({
"collMod": collName,
validationLevel: "validated",
writeConcern: {w: "majority"},
}),
);
}
function noSchemaRuleChanges() {
createValidatedCollection();
assert.commandFailed(
myDb.runCommand({"collMod": collName, writeConcern: {w: "majority"}, validator: {b: {$exists: true}}}),
);
// Rules changes are ok if we also downgrade to strict.
assert.commandWorked(
myDb.runCommand({
"collMod": collName,
validationLevel: "strict",
writeConcern: {w: "majority"},
validator: {b: {$exists: true}},
}),
);
}
function downgradeValidatedToStrict() {
createValidatedCollection();
assert.commandWorked(
myDb.runCommand({
"collMod": collName,
validationLevel: "strict",
writeConcern: {w: "majority"},
}),
);
}
function noNonConformingDocs() {
createValidatedCollection();
assert.commandFailed(
myDb.runCommand({
insert: collName,
documents: [{b: 1}],
writeConcern: {w: "majority"},
}),
);
}
function noBypassDocValidation() {
createValidatedCollection();
// conforming doc (bypassDocumentValidation:true always fails on validated collections)
assert.commandFailed(
myDb.runCommand({
insert: collName,
documents: [{a: 1}],
bypassDocumentValidation: true,
writeConcern: {w: "majority"},
}),
);
// non-conforming doc
assert.commandFailed(
myDb.runCommand({
insert: collName,
documents: [{c: 1}],
bypassDocumentValidation: true,
writeConcern: {w: "majority"},
}),
);
// conforming doc
assert.commandWorked(
myDb.runCommand({
insert: collName,
documents: [{a: 1}],
bypassDocumentValidation: false,
writeConcern: {w: "majority"},
}),
);
// non-conforming doc
assert.commandFailed(
myDb.runCommand({
insert: collName,
documents: [{c: 1}],
bypassDocumentValidation: false,
writeConcern: {w: "majority"},
}),
);
}
validationActionMustNotBeWarn();
downgradeValidatedToStrict();
noSchemaRuleChanges();
noValidatedOnExisting();
noNonConformingDocs();
noBypassDocValidation();
// avoid downgrade problems (validated collections can't be downgraded)
myDb.runCommand({drop: collName, writeConcern: {w: "majority"}});

View File

@ -0,0 +1,35 @@
/**
* Test edge cases for the schema validation level 'validated'
*/
import "jstests/multiVersion/libs/multi_rs.js";
import {ReplSetTest} from "jstests/libs/replsettest.js";
const rst = new ReplSetTest({
nodes: 2,
nodeOptions: {binVersion: "latest", setParameter: {featureFlagValidatedValidationLevel: true}},
});
rst.startSet();
rst.initiate();
const db = rst.getPrimary().getDB("test");
assert.commandWorked(
db.createCollection("test", {validator: {a: 1}, validationAction: "error", validationLevel: "validated"}),
);
// Can't downgrade FCV to lastLTS when collection has validated validation level.
assert.commandFailedWithCode(
rst.getPrimary().adminCommand({setFeatureCompatibilityVersion: lastLTSFCV, confirm: true}),
ErrorCodes.CannotDowngrade,
);
assert.commandWorked(db.runCommand({collMod: "test", validationLevel: "strict"}));
// After removing validated validation level, we should eventually be able to downgrade
assert.soon(() => {
assert.commandWorked(rst.getPrimary().adminCommand({setFeatureCompatibilityVersion: lastLTSFCV, confirm: true}));
return true;
});
rst.stopSet();

View File

@ -69,7 +69,7 @@ using logv2::LogComponent;
StatusWith<int64_t> compactCollection(OperationContext* opCtx,
const CompactOptions& options,
const CollectionPtr& collection) {
DisableDocumentValidation validationDisabler(opCtx);
DisableDocumentValidationForInternalOp validationDisabler(opCtx);
auto collectionNss = collection->ns();
auto recordStore = collection->getRecordStore();

View File

@ -151,7 +151,7 @@ void cloneCollectionAsCapped(OperationContext* opCtx,
BSONObj objToClone;
RecordId loc;
DisableDocumentValidation validationDisabler(opCtx);
DisableDocumentValidationForInternalOp validationDisabler(opCtx);
auto replCoord = repl::ReplicationCoordinator::get(opCtx);
bool isOplogDisabledForCappedCollection = replCoord->isOplogDisabledFor(opCtx, toNss);

View File

@ -648,8 +648,8 @@ void updateDocument(OperationContext* opCtx,
{
auto status = collection->checkValidationAndParseResult(opCtx, newDoc);
if (!status.isOK()) {
if (validationLevelOrDefault(collection->getCollectionOptions().validationLevel) ==
ValidationLevelEnum::strict) {
if (validationLevelIsMandatory(
validationLevelOrDefault(collection->getCollectionOptions().validationLevel))) {
uassertStatusOK(status);
}
// moderate means we have to check the old doc

View File

@ -252,7 +252,7 @@ public:
validateApplyOpsCommand(cmdObj);
boost::optional<DisableDocumentValidation> maybeDisableValidation;
boost::optional<DisableDocumentValidationForInternalOp> maybeDisableValidation;
if (shouldBypassDocumentValidationForCommand(cmdObj))
maybeDisableValidation.emplace(opCtx);

View File

@ -290,7 +290,7 @@ Status OplogApplicationChecks::checkAuthForOperation(OperationContext* opCtx,
}
fassert(40314, validity == OplogApplicationValidity::kOk);
boost::optional<DisableDocumentValidation> maybeDisableValidation;
boost::optional<DisableDocumentValidationForInternalOp> maybeDisableValidation;
if (shouldBypassDocumentValidationForCommand(cmdObj))
maybeDisableValidation.emplace(opCtx);

View File

@ -1875,8 +1875,8 @@ BulkWriteReply performWrites(OperationContext* opCtx, const BulkWriteCommandRequ
}
const auto& bypassDocumentValidation = req.getBypassDocumentValidation();
DisableDocumentSchemaValidationIfTrue docSchemaValidationDisabler(opCtx,
bypassDocumentValidation);
DisableDocumentSchemaValidationRequestedByUserIfTrue docSchemaValidationDisabler(
opCtx, bypassDocumentValidation);
const auto& firstNsInfo = req.getNsInfo()[0];
const bool fleCrudProcessed = write_ops_exec::getFleCrudProcessed(

View File

@ -506,8 +506,8 @@ write_ops::FindAndModifyCommandReply CmdFindAndModify::Invocation::typedRun(
auto fleCrudProcessed = write_ops_exec::getFleCrudProcessed(
opCtx, req.getEncryptionInformation(), nsString.tenantId());
DisableDocumentSchemaValidationIfTrue docSchemaValidationDisabler(opCtx,
disableDocumentValidation);
DisableDocumentSchemaValidationRequestedByUserIfTrue docSchemaValidationDisabler(
opCtx, disableDocumentValidation);
DisableSafeContentValidationIfTrue safeContentValidationDisabler(
opCtx, disableDocumentValidation, fleCrudProcessed);

View File

@ -1465,28 +1465,39 @@ private:
"Simulated dry-run validation failure via fail point.");
}
if (gFeatureFlagErrorAndLogValidationAction.isDisabledOnTargetFCVButEnabledOnOriginalFCV(
requestedVersion, originalVersion)) {
bool errorAndLogValidationDisabled =
(gFeatureFlagErrorAndLogValidationAction.isDisabledOnTargetFCVButEnabledOnOriginalFCV(
requestedVersion, originalVersion));
bool validatedValidationLevelDisabled =
(gFeatureFlagValidatedValidationLevel.isDisabledOnTargetFCVButEnabledOnOriginalFCV(
requestedVersion, originalVersion));
if (errorAndLogValidationDisabled || validatedValidationLevelDisabled) {
for (const auto& dbName : DatabaseHolder::get(opCtx)->getNames()) {
Lock::DBLock dbLock(opCtx, dbName, MODE_IS);
catalog::forEachCollectionFromDb(
opCtx,
dbName,
MODE_IS,
[&](const Collection* collection) -> bool {
uasserted(
ErrorCodes::CannotDowngrade,
fmt::format(
"Cannot downgrade the cluster when there are collections with "
"'errorAndLog' validation action. Please unset the option or "
"drop the collection(s) before downgrading. First detected "
"collection with 'errorAndLog' enabled: {} (UUID: {}).",
collection->ns().toStringForErrorMsg(),
collection->uuid().toString()));
},
[&](const Collection* collection) {
return collection->getValidationAction() ==
ValidationActionEnum::errorAndLog;
opCtx, dbName, MODE_IS, [&](const Collection* collection) -> bool {
uassert(ErrorCodes::CannotDowngrade,
fmt::format(
"Cannot downgrade the cluster when there are collections with "
"'errorAndLog' validation action. Please unset the option or "
"drop the collection(s) before downgrading. First detected "
"collection with 'errorAndLog' enabled: {} (UUID: {}).",
collection->ns().toStringForErrorMsg(),
collection->uuid().toString()),
collection->getValidationAction() !=
ValidationActionEnum::errorAndLog);
uassert(ErrorCodes::CannotDowngrade,
fmt::format(
"Cannot downgrade the cluster when there are collections with "
"'validated' validation level. Please unset the option or "
"drop the collection(s) before downgrading. First detected "
"collection with 'validated' enabled: {} (UUID: {}).",
collection->ns().toStringForErrorMsg(),
collection->uuid().toString()),
collection->getValidationLevel() != ValidationLevelEnum::validated);
return true;
});
}
}

View File

@ -1217,8 +1217,8 @@ WriteResult performInserts(
const auto [disableDocumentValidation, fleCrudProcessed] = getDocumentValidationFlags(
opCtx, wholeOp.getWriteCommandRequestBase(), wholeOp.getDbName().tenantId());
DisableDocumentSchemaValidationIfTrue docSchemaValidationDisabler(opCtx,
disableDocumentValidation);
DisableDocumentSchemaValidationRequestedByUserIfTrue docSchemaValidationDisabler(
opCtx, disableDocumentValidation);
DisableSafeContentValidationIfTrue safeContentValidationDisabler(
opCtx, disableDocumentValidation, fleCrudProcessed);
@ -1821,8 +1821,8 @@ WriteResult performUpdates(
const auto [disableDocumentValidation, fleCrudProcessed] = getDocumentValidationFlags(
opCtx, wholeOp.getWriteCommandRequestBase(), wholeOp.getDbName().tenantId());
DisableDocumentSchemaValidationIfTrue docSchemaValidationDisabler(opCtx,
disableDocumentValidation);
DisableDocumentSchemaValidationRequestedByUserIfTrue docSchemaValidationDisabler(
opCtx, disableDocumentValidation);
DisableSafeContentValidationIfTrue safeContentValidationDisabler(
opCtx, disableDocumentValidation, fleCrudProcessed);
@ -2157,8 +2157,8 @@ WriteResult performDeletes(
const auto [disableDocumentValidation, fleCrudProcessed] = getDocumentValidationFlags(
opCtx, wholeOp.getWriteCommandRequestBase(), wholeOp.getDbName().tenantId());
DisableDocumentSchemaValidationIfTrue docSchemaValidationDisabler(opCtx,
disableDocumentValidation);
DisableDocumentSchemaValidationRequestedByUserIfTrue docSchemaValidationDisabler(
opCtx, disableDocumentValidation);
DisableSafeContentValidationIfTrue safeContentValidationDisabler(
opCtx, disableDocumentValidation, fleCrudProcessed);

View File

@ -176,7 +176,7 @@ Status repairCollections(OperationContext* opCtx,
namespace repair {
Status repairDatabase(OperationContext* opCtx, StorageEngine* engine, const DatabaseName& dbName) {
DisableDocumentValidation validationDisabler(opCtx);
DisableDocumentValidationForInternalOp validationDisabler(opCtx);
// We must hold some form of lock here
invariant(shard_role_details::getLocker(opCtx)->isW());

View File

@ -887,7 +887,7 @@ TEST_F(OplogApplierImplTest, applyOplogEntryToInvalidateChangeStreamPreImages) {
// Apply the oplog entry.
{
repl::UnreplicatedWritesBlock uwb(_opCtx.get());
DisableDocumentValidation validationDisabler(_opCtx.get());
DisableDocumentValidationForInternalOp validationDisabler(_opCtx.get());
ASSERT_THROWS(applyOplogEntryOrGroupedInserts(_opCtx.get(),
ApplierOperation{&invalidateOp},
OplogApplication::Mode::kInitialSync,
@ -964,7 +964,7 @@ TEST_F(OplogApplierImplTest, applyOplogEntryToInvalidateNonModPreImages) {
// Apply the oplog entry.
{
repl::UnreplicatedWritesBlock uwb(_opCtx.get());
DisableDocumentValidation validationDisabler(_opCtx.get());
DisableDocumentValidationForInternalOp validationDisabler(_opCtx.get());
ASSERT_NOT_OK(applyOplogEntryOrGroupedInserts(_opCtx.get(),
ApplierOperation{&updateOp},
OplogApplication::Mode::kInitialSync,
@ -1039,7 +1039,7 @@ TEST_F(OplogApplierImplTest, ImageCollectionInvalidationInInitialSyncHandlesConf
// Apply the first oplog entry which should lead us to write an invalidate entry.
{
repl::UnreplicatedWritesBlock uwb(_opCtx.get());
DisableDocumentValidation validationDisabler(_opCtx.get());
DisableDocumentValidationForInternalOp validationDisabler(_opCtx.get());
ASSERT_THROWS(applyOplogEntryOrGroupedInserts(_opCtx.get(),
ApplierOperation{&invalidateOp},
OplogApplication::Mode::kInitialSync,
@ -1071,7 +1071,7 @@ TEST_F(OplogApplierImplTest, ImageCollectionInvalidationInInitialSyncHandlesConf
{
repl::UnreplicatedWritesBlock uwb(_opCtx.get());
DisableDocumentValidation validationDisabler(_opCtx.get());
DisableDocumentValidationForInternalOp validationDisabler(_opCtx.get());
ASSERT_THROWS(applyOplogEntryOrGroupedInserts(_opCtx.get(),
ApplierOperation{&earlierInvalidateOp},
OplogApplication::Mode::kInitialSync,
@ -3933,7 +3933,8 @@ TEST_F(OplogApplierImplTest,
[&](OperationContext* opCtx, const NamespaceString&, const std::vector<BSONObj>&) {
onInsertsCalled = true;
ASSERT_FALSE(opCtx->writesAreReplicated());
ASSERT_TRUE(DocumentValidationSettings::get(opCtx).isSchemaValidationDisabled());
ASSERT_TRUE(
DocumentValidationSettings::get(opCtx).isSchemaValidationDisabledForInternalOp());
};
createCollectionWithUuid(_opCtx.get(), nss);
auto op = makeInsertDocumentOplogEntry({Timestamp(Seconds(1), 0), 1LL}, nss, BSON("_id" << 0));

View File

@ -274,7 +274,7 @@ Status OplogApplierImplTest::_applyOplogEntryOrGroupedInsertsWrapper(
const OplogEntryOrGroupedInserts& batch,
OplogApplication::Mode oplogApplicationMode) {
UnreplicatedWritesBlock uwb(opCtx);
DisableDocumentValidation validationDisabler(opCtx);
DisableDocumentValidationForInternalOp validationDisabler(opCtx);
const bool dataIsConsistent = true;
return applyOplogEntryOrGroupedInserts(opCtx, batch, oplogApplicationMode, dataIsConsistent);
}
@ -295,7 +295,8 @@ void OplogApplierImplTest::_testApplyOplogEntryOrGroupedInsertsCrudOperation(
ASSERT_TRUE(
shard_role_details::getLocker(opCtx)->isCollectionLockedForMode(targetNss, MODE_IX));
ASSERT_FALSE(opCtx->writesAreReplicated());
ASSERT_TRUE(DocumentValidationSettings::get(opCtx).isSchemaValidationDisabled());
ASSERT_TRUE(
DocumentValidationSettings::get(opCtx).isSchemaValidationDisabledForInternalOp());
};
_opObserver->onInsertsFn =

View File

@ -426,7 +426,7 @@ Status OplogApplierUtils::applyOplogEntryOrGroupedInsertsCommon(
const bool isDataConsistent,
IncrementOpsAppliedStatsFn incrementOpsAppliedStats,
OpCounters* opCounters) {
invariant(DocumentValidationSettings::get(opCtx).isSchemaValidationDisabled());
invariant(DocumentValidationSettings::get(opCtx).isSchemaValidationDisabledForInternalOp());
const auto& op = entryOrGroupedInserts.getOp();
// Count each log op application as a separate operation, for reporting purposes.
@ -632,7 +632,7 @@ Status OplogApplierUtils::applyOplogBatchCommon(
// We cannot do document validation, because document validation could have been disabled when
// these oplog entries were generated.
DisableDocumentValidation validationDisabler(opCtx);
DisableDocumentValidationForInternalOp validationDisabler(opCtx);
// Group the operations by namespace in order to get larger groups for bulk inserts, but do not
// mix up the current order of oplog entries within the same namespace (thus *stable* sort).
stableSortByNamespace(ops);

View File

@ -223,7 +223,7 @@ void OplogBufferCollection::_push(WithLock,
// (16MB user data + additional bytes for oplog fields like op”, “ns”, “ui”).
DisableDocumentValidation documentValidationDisabler(
opCtx,
DocumentValidationSettings::kDisableSchemaValidation |
DocumentValidationSettings::kDisableSchemaValidationForInternalOp |
DocumentValidationSettings::kDisableInternalValidation);
write_ops::InsertCommandRequest insertOp(_nss);

View File

@ -258,7 +258,7 @@ StorageInterfaceImpl::createCollectionForBulkLoading(
// But, it's logically ok to disable internal validation as this function gets called
// only during initial sync.
DocumentValidationSettings::get(opCtx.get())
.setFlags(DocumentValidationSettings::kDisableSchemaValidation |
.setFlags(DocumentValidationSettings::kDisableSchemaValidationForInternalOp |
DocumentValidationSettings::kDisableInternalValidation);
// Retry if WCE.

View File

@ -242,12 +242,12 @@ private:
_opCtx = cc().makeOperationContext();
// We are not replicating nor validating these writes.
_uwb = std::make_unique<UnreplicatedWritesBlock>(_opCtx.get());
_ddv = std::make_unique<DisableDocumentValidation>(_opCtx.get());
_ddv = std::make_unique<DisableDocumentValidationForInternalOp>(_opCtx.get());
}
ServiceContext::UniqueOperationContext _opCtx;
std::unique_ptr<UnreplicatedWritesBlock> _uwb;
std::unique_ptr<DisableDocumentValidation> _ddv;
std::unique_ptr<DisableDocumentValidationForInternalOp> _ddv;
ReplicationCoordinatorMock* _replicationCoordinatorMock = nullptr;
};

View File

@ -881,7 +881,7 @@ TEST_F(StorageTimestampTest, SecondaryArrayInsertTimes) {
// In order for oplog application to assign timestamps, we must be in non-replicated mode
// and disable document validation.
repl::UnreplicatedWritesBlock uwb(_opCtx);
DisableDocumentValidation validationDisabler(_opCtx);
DisableDocumentValidationForInternalOp validationDisabler(_opCtx);
// Create a new collection.
NamespaceString nss =
@ -2789,7 +2789,7 @@ TEST_F(StorageTimestampTest, TimestampIndexOplogApplicationOnPrimary) {
// In order for oplog application to assign timestamps, we must be in non-replicated mode
// and disable document validation.
repl::UnreplicatedWritesBlock uwb(_opCtx);
DisableDocumentValidation validationDisabler(_opCtx);
DisableDocumentValidationForInternalOp validationDisabler(_opCtx);
std::string dbName = "unittest";
NamespaceString nss =

View File

@ -749,7 +749,7 @@ void _reconstructPreparedTransaction(OperationContext* opCtx,
repl::OplogApplication::Mode mode) {
repl::UnreplicatedWritesBlock uwb(opCtx);
// The transaction may have been prepared originally with document validation bypassed.
DisableDocumentValidation validationDisabler(opCtx);
DisableDocumentValidationForInternalOp validationDisabler(opCtx);
// The operations here are reconstructed at their prepare time. However, that time
// will be ignored because there is an outer write unit of work during their

View File

@ -189,7 +189,7 @@ void MigrationBatchInserter::run(Status status) const try {
// and any internal validation for opCtx for performInserts()
DisableDocumentValidation documentValidationDisabler(
opCtx,
DocumentValidationSettings::kDisableSchemaValidation |
DocumentValidationSettings::kDisableSchemaValidationForInternalOp |
DocumentValidationSettings::kDisableInternalValidation);
const auto reply = write_ops_exec::performInserts(
opCtx, insertOp, /*preConditions=*/boost::none, OperationSource::kFromMigrate);

View File

@ -251,15 +251,10 @@ public:
return _coll->parseValidator(opCtx, validator, allowedFeatures);
}
void setValidator(OperationContext* opCtx, Validator validator) override {
MONGO_UNREACHABLE;
}
Status setValidationLevel(OperationContext* opCtx, ValidationLevelEnum newLevel) override {
MONGO_UNREACHABLE;
}
Status setValidationAction(OperationContext* opCtx, ValidationActionEnum newAction) override {
Status setValidationOptions(OperationContext* opCtx,
boost::optional<ValidationLevelEnum> newLevel,
boost::optional<ValidationActionEnum> newAction,
boost::optional<Validator> newValidator) override {
MONGO_UNREACHABLE;
}

View File

@ -659,7 +659,7 @@ repl::OpTime MigrationDestinationManager::fetchAndApplyBatch(
while (true) {
DisableDocumentValidation documentValidationDisabler(
applicationOpCtx.get(),
DocumentValidationSettings::kDisableSchemaValidation |
DocumentValidationSettings::kDisableSchemaValidationForInternalOp |
DocumentValidationSettings::kDisableInternalValidation);
auto nextBatch = batches.pop(applicationOpCtx.get());
if (!applyBatchFn(applicationOpCtx.get(), nextBatch)) {

View File

@ -118,6 +118,11 @@ feature_flags:
default: true
version: 8.1
fcv_gated: true
featureFlagValidatedValidationLevel:
description: "Enables the use of the validated schema validation level."
cpp_varname: gFeatureFlagValidatedValidationLevel
default: false
fcv_gated: true
featureFlagDistinguishMetadataInconsistencyFromConflictingOperation:
description: "Feature flag to distinguish ChunkMetadataInconsistency from ConflictingOperationInProgress error"
cpp_varname: gDistinguishMetadataInconsistencyFromConflictingOperation

View File

@ -88,7 +88,7 @@ void cloneDatabase(OperationContext* opCtx,
unsplittableCollections.end(),
std::back_inserter(trackedColls));
DisableDocumentValidation disableValidation(opCtx);
DisableDocumentValidationForInternalOp disableValidation(opCtx);
// Clone the non-ignored collections.
std::set<std::string> clonedColls;

View File

@ -165,14 +165,23 @@ public:
str::stream() << "Document validators not allowed on system collection "
<< nss.toStringForErrorMsg(),
nss != NamespaceString::kConfigSettingsNamespace);
}
if (cmd.getValidationAction() == ValidationActionEnum::errorAndLog) {
uassert(
ErrorCodes::InvalidOptions,
"Validation action 'errorAndLog' is not supported with current FCV",
gFeatureFlagErrorAndLogValidationAction.isEnabledUseLastLTSFCVWhenUninitialized(
VersionContext::getDecoration(opCtx),
serverGlobalParams.featureCompatibility.acquireFCVSnapshot()));
const auto fcvSnapshot =
serverGlobalParams.featureCompatibility.acquireFCVSnapshot();
if (cmd.getValidationAction() == ValidationActionEnum::errorAndLog) {
uassert(ErrorCodes::InvalidOptions,
"Validation action 'errorAndLog' is not supported with current FCV",
gFeatureFlagErrorAndLogValidationAction
.isEnabledUseLastLTSFCVWhenUninitialized(
VersionContext::getDecoration(opCtx), fcvSnapshot));
}
if (cmd.getValidationLevel() == ValidationLevelEnum::validated) {
uassert(ErrorCodes::InvalidOptions,
"Validation level 'validated' is not supported with current FCV",
gFeatureFlagValidatedValidationLevel
.isEnabledUseLastLTSFCVWhenUninitialized(
VersionContext::getDecoration(opCtx), fcvSnapshot));
}
}
// We do not use the serialization context for reply object serialization as the reply

View File

@ -104,7 +104,7 @@ structs:
description:
"Determines how strictly to apply the validation rules to existing
documents during an update.
Can be one of following values: 'off', 'strict' or 'moderate'."
Can be one of following values: 'off', 'strict', 'moderate' or 'validated'."
type: ValidationLevel
optional: true
stability: stable

View File

@ -523,14 +523,23 @@ public:
str::stream() << "Document validators not allowed on system collection "
<< ns().toStringForErrorMsg(),
ns() != NamespaceString::kConfigSettingsNamespace);
}
if (cmd.getValidationAction() == ValidationActionEnum::errorAndLog) {
uassert(
ErrorCodes::InvalidOptions,
"Validation action 'errorAndLog' is not supported with current FCV",
gFeatureFlagErrorAndLogValidationAction.isEnabledUseLastLTSFCVWhenUninitialized(
VersionContext::getDecoration(opCtx),
serverGlobalParams.featureCompatibility.acquireFCVSnapshot()));
const auto fcvSnapshot =
serverGlobalParams.featureCompatibility.acquireFCVSnapshot();
if (cmd.getValidationAction() == ValidationActionEnum::errorAndLog) {
uassert(ErrorCodes::InvalidOptions,
"Validation action 'errorAndLog' is not supported with current FCV",
gFeatureFlagErrorAndLogValidationAction
.isEnabledUseLastLTSFCVWhenUninitialized(
VersionContext::getDecoration(opCtx), fcvSnapshot));
}
if (cmd.getValidationLevel() == ValidationLevelEnum::validated) {
uassert(ErrorCodes::InvalidOptions,
"Validation level 'validated' is not supported with current FCV",
gFeatureFlagValidatedValidationLevel
.isEnabledUseLastLTSFCVWhenUninitialized(
VersionContext::getDecoration(opCtx), fcvSnapshot));
}
}
OperationShardingState::ScopedAllowImplicitCollectionCreate_UNSAFE

View File

@ -167,6 +167,7 @@ mongo_cc_library(
name = "document_validation",
srcs = [
"document_validation.cpp",
"document_validation_helpers.cpp",
],
deps = [
"//src/mongo/db:service_context",

View File

@ -1011,17 +1011,13 @@ Status _collModInternal(OperationContext* opCtx,
processCollModIndexRequest(
opCtx, writableColl, cmrNew.indexRequest, &indexCollModInfo, result, mode);
if (cmrNew.collValidator) {
writableColl->setValidator(opCtx, *cmrNew.collValidator);
}
if (cmrNew.collValidationAction)
if (cmrNew.collValidationLevel || cmrNew.collValidationAction || cmrNew.collValidator) {
uassertStatusOKWithContext(
writableColl->setValidationAction(opCtx, *cmrNew.collValidationAction),
"Failed to set validationAction");
if (cmrNew.collValidationLevel) {
uassertStatusOKWithContext(
writableColl->setValidationLevel(opCtx, *cmrNew.collValidationLevel),
"Failed to set validationLevel");
writableColl->setValidationOptions(opCtx,
cmrNew.collValidationLevel,
cmrNew.collValidationAction,
cmrNew.collValidator),
"Failed to set validation options");
}
if (cmrNew.changeStreamPreAndPostImagesOptions.has_value() &&

View File

@ -367,15 +367,15 @@ public:
MatchExpressionParser::AllowedFeatureSet allowedFeatures) const = 0;
/**
* Sets the validator for this collection.
* Sets the validation options for this collection.
*
* An empty validator removes all validation.
* Requires an exclusive lock on the collection.
* A boost::none parameter means that it should not be changed or that the default will be
* used.
*/
virtual void setValidator(OperationContext* opCtx, Validator validator) = 0;
virtual Status setValidationLevel(OperationContext* opCtx, ValidationLevelEnum newLevel) = 0;
virtual Status setValidationAction(OperationContext* opCtx, ValidationActionEnum newAction) = 0;
virtual Status setValidationOptions(OperationContext* opCtx,
boost::optional<ValidationLevelEnum> newLevel,
boost::optional<ValidationActionEnum> newAction,
boost::optional<Validator> newValidator) = 0;
virtual boost::optional<ValidationLevelEnum> getValidationLevel() const = 0;
virtual boost::optional<ValidationActionEnum> getValidationAction() const = 0;
@ -986,11 +986,32 @@ inline ValidationActionEnum validationActionOrDefault(
return action.value_or(ValidationActionEnum::error);
}
MONGO_MOD_PUBLIC
inline ValidationActionEnum validationActionOrCurrent(
const CollectionOptions& opts, boost::optional<ValidationActionEnum> action) {
return action.value_or(validationActionOrDefault(opts.validationAction));
}
MONGO_MOD_PUBLIC
inline ValidationLevelEnum validationLevelOrDefault(boost::optional<ValidationLevelEnum> level) {
return level.value_or(ValidationLevelEnum::strict);
}
MONGO_MOD_PUBLIC
inline ValidationLevelEnum validationLevelOrCurrent(const CollectionOptions& opts,
boost::optional<ValidationLevelEnum> level) {
return level.value_or(validationLevelOrDefault(opts.validationLevel));
}
/**
* Mandatory level means that the schema validator is strictly enforced on all inserts and updates.
* 'validated' and 'strict' both forbid inserting/updating non-conforming documents.
*/
MONGO_MOD_PUBLIC
inline bool validationLevelIsMandatory(ValidationLevelEnum level) {
return level == ValidationLevelEnum::strict || level == ValidationLevelEnum::validated;
}
/**
* Returns a collator for the user-specified collation 'userCollation'.
*

View File

@ -87,6 +87,7 @@
#include "mongo/db/shard_role/shard_catalog/catalog_stats.h"
#include "mongo/db/shard_role/shard_catalog/collection_catalog.h"
#include "mongo/db/shard_role/shard_catalog/document_validation.h"
#include "mongo/db/shard_role/shard_catalog/document_validation_helpers.h"
#include "mongo/db/shard_role/shard_catalog/durable_catalog.h"
#include "mongo/db/shard_role/shard_catalog/index_catalog_impl.h"
#include "mongo/db/shard_role/shard_catalog/index_descriptor.h"
@ -126,26 +127,6 @@ MONGO_FAIL_POINT_DEFINE(skipCappedDeletes);
// and clear the new durable flag which is stored inside the collection options.
MONGO_FAIL_POINT_DEFINE(simulateLegacyTimeseriesMixedSchemaFlag);
Status checkValidationOptionsCanBeUsed(const CollectionOptions& opts,
boost::optional<ValidationLevelEnum> newLevel,
boost::optional<ValidationActionEnum> newAction) {
if (!opts.encryptedFieldConfig) {
return Status::OK();
}
if (validationLevelOrDefault(newLevel) != ValidationLevelEnum::strict) {
return Status(
ErrorCodes::BadValue,
"Validation levels other than 'strict' are not allowed on encrypted collections");
}
auto action = validationActionOrDefault(newAction);
if (action == ValidationActionEnum::warn || action == ValidationActionEnum::errorAndLog) {
return Status(ErrorCodes::BadValue,
"Validation action of 'warn' and 'errorAndLog' are not allowed on encrypted "
"collections");
}
return Status::OK();
}
/**
* Returns true if we are running retryable write or retryable internal multi-document transaction.
*/
@ -435,8 +416,8 @@ void CollectionImpl::_initCommon(OperationContext* opCtx) {
uassertStatusOK(_checkValidatorCanBeUsed(validatorDoc));
// Make sure validationAction and validationLevel are allowed on this collection
uassertStatusOK(checkValidationOptionsCanBeUsed(
collectionOptions, collectionOptions.validationLevel, collectionOptions.validationAction));
uassertStatusOK(
checkValidationOptionsCanBeUsed(collectionOptions, boost::none, boost::none, boost::none));
// Make sure to copy the action and level before parsing MatchExpression, since certain features
// are not supported with certain combinations of action and level.
@ -594,8 +575,19 @@ std::pair<Collection::SchemaValidationResult, Status> CollectionImpl::checkValid
if (validationLevelOrDefault(_metadata->options.validationLevel) == ValidationLevelEnum::off)
return {SchemaValidationResult::kPass, Status::OK()};
if (DocumentValidationSettings::get(opCtx).isSchemaValidationDisabled())
if (DocumentValidationSettings::get(opCtx).isSchemaValidationDisabledForInternalOp()) {
return {SchemaValidationResult::kPass, Status::OK()};
}
if (DocumentValidationSettings::get(opCtx).isSchemaValidationDisabled()) {
if (_metadata->options.validationLevel == ValidationLevelEnum::validated) {
return {SchemaValidationResult::kError,
Status(ErrorCodes::BadValue,
"bypassDocumentValidation is not permitted on 'validated' collections")};
} else {
return {SchemaValidationResult::kPass, Status::OK()};
}
}
if (ns().isTemporaryReshardingCollection()) {
// In resharding, the donor shard primary is responsible for performing document validation
@ -623,6 +615,11 @@ std::pair<Collection::SchemaValidationResult, Status> CollectionImpl::checkValid
switch (validationActionOrDefault(_metadata->options.validationAction)) {
case ValidationActionEnum::warn:
if (validationLevelOrDefault(_metadata->options.validationLevel) ==
ValidationLevelEnum::validated) {
// Warn is prohibited for validated collections
return {SchemaValidationResult::kError, status};
}
return {SchemaValidationResult::kWarn, status};
case ValidationActionEnum::error:
return {SchemaValidationResult::kError, status};
@ -1210,20 +1207,37 @@ Status CollectionImpl::truncate(OperationContext* opCtx) {
return Status::OK();
}
void CollectionImpl::setValidator(OperationContext* opCtx, Validator validator) {
Status CollectionImpl::setValidationOptions(OperationContext* opCtx,
boost::optional<ValidationLevelEnum> newLevel,
boost::optional<ValidationActionEnum> newAction,
boost::optional<Validator> newValidator) {
invariant(shard_role_details::getLocker(opCtx)->isCollectionLockedForMode(ns(), MODE_X));
auto status =
checkValidationOptionsCanBeUsed(_metadata->options, newLevel, newAction, newValidator);
if (!status.isOK()) {
return status;
}
auto validatorDoc = validator.validatorDoc.getOwned();
auto validationLevel = validationLevelOrDefault(_metadata->options.validationLevel);
auto validationAction = validationActionOrDefault(_metadata->options.validationAction);
auto validationLevel = validationLevelOrCurrent(_metadata->options, newLevel);
auto validationAction = validationActionOrCurrent(_metadata->options, newAction);
if (newValidator) {
_validator = std::move(*newValidator);
}
if (auto [mustReparse, allowedFeatures] = mustReparseValidator(newLevel, newAction);
mustReparse) {
_validator = parseValidator(opCtx, _validator.validatorDoc, allowedFeatures);
if (!_validator.isOK()) {
return _validator.getStatus();
}
}
_writeMetadata(opCtx, [&](durable_catalog::CatalogEntryMetaData& md) {
md.options.validator = validatorDoc;
md.options.validator = _validator.validatorDoc;
md.options.validationLevel = validationLevel;
md.options.validationAction = validationAction;
});
_validator = std::move(validator);
return Status::OK();
}
boost::optional<ValidationLevelEnum> CollectionImpl::getValidationLevel() const {
@ -1234,74 +1248,17 @@ boost::optional<ValidationActionEnum> CollectionImpl::getValidationAction() cons
return _metadata->options.validationAction;
}
Status CollectionImpl::setValidationLevel(OperationContext* opCtx, ValidationLevelEnum newLevel) {
invariant(shard_role_details::getLocker(opCtx)->isCollectionLockedForMode(ns(), MODE_X));
auto status = checkValidationOptionsCanBeUsed(_metadata->options, newLevel, boost::none);
if (!status.isOK()) {
return status;
}
auto storedValidationLevel = validationLevelOrDefault(newLevel);
// Reparse the validator as there are some features which are only supported with certain
// validation levels.
auto allowedFeatures = MatchExpressionParser::kAllowAllSpecialFeatures;
if (storedValidationLevel == ValidationLevelEnum::moderate)
allowedFeatures &= ~MatchExpressionParser::AllowedFeatures::kEncryptKeywords;
_validator = parseValidator(opCtx, _validator.validatorDoc, allowedFeatures);
if (!_validator.isOK()) {
return _validator.getStatus();
}
_writeMetadata(opCtx, [&](durable_catalog::CatalogEntryMetaData& md) {
md.options.validator = _validator.validatorDoc;
md.options.validationLevel = storedValidationLevel;
md.options.validationAction = validationActionOrDefault(md.options.validationAction);
});
return Status::OK();
}
Status CollectionImpl::setValidationAction(OperationContext* opCtx,
ValidationActionEnum newAction) {
invariant(shard_role_details::getLocker(opCtx)->isCollectionLockedForMode(ns(), MODE_X));
auto status = checkValidationOptionsCanBeUsed(_metadata->options, boost::none, newAction);
if (!status.isOK()) {
return status;
}
auto storedValidationAction = validationActionOrDefault(newAction);
// Reparse the validator as there are some features which are only supported with certain
// validation actions.
auto allowedFeatures = MatchExpressionParser::kAllowAllSpecialFeatures;
if (storedValidationAction == ValidationActionEnum::warn ||
storedValidationAction == ValidationActionEnum::errorAndLog)
allowedFeatures &= ~MatchExpressionParser::AllowedFeatures::kEncryptKeywords;
_validator = parseValidator(opCtx, _validator.validatorDoc, allowedFeatures);
if (!_validator.isOK()) {
return _validator.getStatus();
}
_writeMetadata(opCtx, [&](durable_catalog::CatalogEntryMetaData& md) {
md.options.validator = _validator.validatorDoc;
md.options.validationLevel = validationLevelOrDefault(md.options.validationLevel);
md.options.validationAction = storedValidationAction;
});
return Status::OK();
}
Status CollectionImpl::updateValidator(OperationContext* opCtx,
BSONObj newValidatorDoc,
boost::optional<ValidationLevelEnum> newLevel,
boost::optional<ValidationActionEnum> newAction) {
invariant(shard_role_details::getLocker(opCtx)->isCollectionLockedForMode(ns(), MODE_X));
auto validationLevel = validationLevelOrCurrent(_metadata->options, newLevel);
if (validationLevel == ValidationLevelEnum::validated) {
return Status(ErrorCodes::BadValue,
"Validator can not be changed on 'validated' collections");
}
tassert(11738200,
fmt::format("Illegal attempt to set a non-empty validator on viewless timeseries "
"collection '{}'",
@ -1309,17 +1266,18 @@ Status CollectionImpl::updateValidator(OperationContext* opCtx,
!isTimeseriesCollection() || !isNewTimeseriesWithoutView() ||
(newValidatorDoc.isEmpty() && !newLevel.has_value() && !newAction.has_value()));
auto status = checkValidationOptionsCanBeUsed(_metadata->options, newLevel, newAction);
if (!status.isOK()) {
return status;
}
auto newValidator =
parseValidator(opCtx, newValidatorDoc, MatchExpressionParser::kAllowAllSpecialFeatures);
if (!newValidator.isOK()) {
return newValidator.getStatus();
}
if (auto status =
checkValidationOptionsCanBeUsed(_metadata->options, newLevel, newAction, newValidator);
!status.isOK()) {
return status;
}
_writeMetadata(opCtx, [&](durable_catalog::CatalogEntryMetaData& md) {
md.options.validator = newValidatorDoc;
md.options.validationLevel = newLevel;

View File

@ -191,13 +191,15 @@ public:
/**
* Sets the validator for this collection.
*
* An empty validator removes all validation.
* A boost::empty parameter means that it should not be changed or that the default will be
* used.
* Requires an exclusive lock on the collection.
*/
void setValidator(OperationContext* opCtx, Validator validator) final;
Status setValidationOptions(OperationContext* opCtx,
boost::optional<ValidationLevelEnum> newLevel,
boost::optional<ValidationActionEnum> newAction,
boost::optional<Validator> newValidator) final;
Status setValidationLevel(OperationContext* opCtx, ValidationLevelEnum newLevel) final;
Status setValidationAction(OperationContext* opCtx, ValidationActionEnum newAction) final;
boost::optional<ValidationLevelEnum> getValidationLevel() const final;
boost::optional<ValidationActionEnum> getValidationAction() const final;

View File

@ -160,14 +160,10 @@ public:
MONGO_UNREACHABLE;
}
void setValidator(OperationContext* opCtx, Validator validator) override {
MONGO_UNREACHABLE;
}
Status setValidationLevel(OperationContext* opCtx, ValidationLevelEnum newLevel) override {
MONGO_UNREACHABLE;
}
Status setValidationAction(OperationContext* opCtx, ValidationActionEnum newAction) override {
Status setValidationOptions(OperationContext* opCtx,
boost::optional<ValidationLevelEnum> newLevel,
boost::optional<ValidationActionEnum> newAction,
boost::optional<Validator> newValidator) override {
MONGO_UNREACHABLE;
}

View File

@ -43,6 +43,7 @@ enums:
"off": "off"
strict: strict
moderate: moderate
validated: validated
ValidationAction:
description: "Determines an action on invalid documents being written:

View File

@ -67,23 +67,29 @@ public:
*/
kEnableValidation = 0x00,
/*
* Disables the schema validation during document inserts and updates.
* This flag should be enabled if WriteCommandRequestBase::_bypassDocumentValidation
* is set to true.
* Disables the schema validation during document inserts and updates for user-initiated
* operations. This flag should be enabled if
* WriteCommandRequestBase::_bypassDocumentValidation is set to true.
*/
kDisableSchemaValidation = 0x01,
kDisableSchemaValidationRequestedByUser = 0x01,
/*
* Disables the schema validation during all document inserts and updates including internal
* operations such as oplog application and initial sync. This flag should only be set for
* internal operations.
*/
kDisableSchemaValidationForInternalOp = 0x02,
/*
* Disables any internal validation (like fixDocumentForInsert()). This flag
* should be enabled only for trusted internal writes or internal writes that
* doesn't comply with internal validation rules.
*/
kDisableInternalValidation = 0x02,
kDisableInternalValidation = 0x04,
/*
* If set, modifications to the safeContent array are allowed. This flag is only
* enabled when bypass document validation is enabled or if crudProcessed is true
* in the query.
*/
kDisableSafeContentValidation = 0x04,
kDisableSafeContentValidation = 0x08,
};
using Flags = std::uint8_t;
@ -102,7 +108,12 @@ public:
}
bool isSchemaValidationDisabled() const {
return _flags & kDisableSchemaValidation;
return _flags &
(kDisableSchemaValidationRequestedByUser | kDisableSchemaValidationForInternalOp);
}
bool isSchemaValidationDisabledForInternalOp() const {
return _flags & kDisableSchemaValidationForInternalOp;
}
bool isInternalValidationDisabled() const {
@ -130,9 +141,7 @@ class MONGO_MOD_NEEDS_REPLACEMENT DisableDocumentValidation {
DisableDocumentValidation& operator=(const DisableDocumentValidation&) = delete;
public:
DisableDocumentValidation(OperationContext* opCtx,
DocumentValidationSettings::Flags flags =
DocumentValidationSettings::kDisableSchemaValidation)
DisableDocumentValidation(OperationContext* opCtx, DocumentValidationSettings::Flags flags)
: _opCtx(opCtx) {
auto& documentValidationSettings = DocumentValidationSettings::get(_opCtx);
_initialState = documentValidationSettings;
@ -148,16 +157,27 @@ private:
DocumentValidationSettings _initialState;
};
/**
* Disables document schema validation while in scope if the constructor is passed true.
*/
class MONGO_MOD_NEEDS_REPLACEMENT DisableDocumentSchemaValidationIfTrue {
class MONGO_MOD_NEEDS_REPLACEMENT DisableDocumentValidationForInternalOp {
public:
DisableDocumentSchemaValidationIfTrue(OperationContext* opCtx,
bool shouldDisableSchemaValidation) {
DisableDocumentValidationForInternalOp(OperationContext* opCtx)
: _documentSchemaValidationDisabler(
opCtx, DocumentValidationSettings::kDisableSchemaValidationForInternalOp) {}
private:
DisableDocumentValidation _documentSchemaValidationDisabler;
};
/**
* Disables document schema validation for user requests while in scope if the constructor is passed
* true.
*/
class MONGO_MOD_NEEDS_REPLACEMENT DisableDocumentSchemaValidationRequestedByUserIfTrue {
public:
DisableDocumentSchemaValidationRequestedByUserIfTrue(OperationContext* opCtx,
bool shouldDisableSchemaValidation) {
if (shouldDisableSchemaValidation) {
_documentSchemaValidationDisabler.emplace(
opCtx, DocumentValidationSettings::kDisableSchemaValidation);
opCtx, DocumentValidationSettings::kDisableSchemaValidationRequestedByUser);
}
}

View File

@ -0,0 +1,92 @@
/**
* Copyright (C) 2026-present MongoDB, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the Server Side Public License, version 1,
* as published by MongoDB, Inc.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* Server Side Public License for more details.
*
* You should have received a copy of the Server Side Public License
* along with this program. If not, see
* <http://www.mongodb.com/licensing/server-side-public-license>.
*
* As a special exception, the copyright holders give permission to link the
* code of portions of this program with the OpenSSL library under certain
* conditions as described in each individual source file and distribute
* linked combinations including the program with the OpenSSL library. You
* must comply with the Server Side Public License in all respects for
* all of the code used other than as permitted herein. If you modify file(s)
* with this exception, you may extend this exception to your version of the
* file(s), but you are not obligated to do so. If you do not wish to do so,
* delete this exception statement from your version. If you delete this
* exception statement from all source files in the program, then also delete
* it in the license file.
*/
#include "mongo/db/shard_role/shard_catalog/document_validation_helpers.h"
namespace mongo {
Status checkValidationOptionsCanBeUsed(const CollectionOptions& opts,
boost::optional<ValidationLevelEnum> newLevel,
boost::optional<ValidationActionEnum> newAction,
boost::optional<Collection::Validator> newValidator) {
auto validationAction = validationActionOrCurrent(opts, newAction);
auto validationLevel = validationLevelOrCurrent(opts, newLevel);
if (opts.encryptedFieldConfig) {
if (!validationLevelIsMandatory(validationLevel)) {
return Status(ErrorCodes::BadValue,
"Validation levels other than 'strict' or 'validated' are not allowed on "
"encrypted collections");
}
if (validationAction == ValidationActionEnum::warn ||
validationAction == ValidationActionEnum::errorAndLog) {
return Status(
ErrorCodes::BadValue,
"Validation action of 'warn' and 'errorAndLog' are not allowed on encrypted "
"collections");
}
}
if (validationLevel == ValidationLevelEnum::validated) {
if (validationAction == ValidationActionEnum::warn) {
return Status(
ErrorCodes::BadValue,
"Validation action of 'warn' is not allowed when Validation level is 'validated'");
}
if (opts.uuid) { // existing collection
if (opts.validationLevel != ValidationLevelEnum::validated) {
return Status(
ErrorCodes::BadValue,
"Validation level 'validated' can not be set on existing collections.");
}
if (newValidator) {
return Status(ErrorCodes::BadValue,
"Validator can not be changed when Validation level is 'validated'");
}
}
}
return Status::OK();
}
std::pair<bool, MatchExpressionParser::AllowedFeatureSet> mustReparseValidator(
boost::optional<ValidationLevelEnum> newLevel,
boost::optional<ValidationActionEnum> newAction) {
auto allowedFeatures = MatchExpressionParser::kAllowAllSpecialFeatures;
if (!newAction && !newLevel) {
return {false, allowedFeatures};
}
if (newLevel == ValidationLevelEnum::moderate || newAction == ValidationActionEnum::warn ||
newAction == ValidationActionEnum::errorAndLog) {
allowedFeatures &= ~MatchExpressionParser::AllowedFeatures::kEncryptKeywords;
}
return {true, allowedFeatures};
}
} // namespace mongo

View File

@ -0,0 +1,44 @@
/**
* Copyright (C) 2026-present MongoDB, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the Server Side Public License, version 1,
* as published by MongoDB, Inc.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* Server Side Public License for more details.
*
* You should have received a copy of the Server Side Public License
* along with this program. If not, see
* <http://www.mongodb.com/licensing/server-side-public-license>.
*
* As a special exception, the copyright holders give permission to link the
* code of portions of this program with the OpenSSL library under certain
* conditions as described in each individual source file and distribute
* linked combinations including the program with the OpenSSL library. You
* must comply with the Server Side Public License in all respects for
* all of the code used other than as permitted herein. If you modify file(s)
* with this exception, you may extend this exception to your version of the
* file(s), but you are not obligated to do so. If you do not wish to do so,
* delete this exception statement from your version. If you delete this
* exception statement from all source files in the program, then also delete
* it in the license file.
*/
#include "mongo/db/shard_role/shard_catalog/collection.h"
#include "mongo/db/shard_role/shard_catalog/collection_options.h"
namespace mongo {
MONGO_MOD_PRIVATE Status
checkValidationOptionsCanBeUsed(const CollectionOptions& opts,
boost::optional<ValidationLevelEnum> newLevel,
boost::optional<ValidationActionEnum> newAction,
boost::optional<Collection::Validator> newValidator);
MONGO_MOD_PRIVATE std::pair<bool, MatchExpressionParser::AllowedFeatureSet> mustReparseValidator(
boost::optional<ValidationLevelEnum> newLevel, boost::optional<ValidationActionEnum> newAction);
} // namespace mongo

View File

@ -347,7 +347,7 @@ Status renameCollectionWithinDB(OperationContext* opCtx,
const NamespaceString& target,
RenameCollectionOptions options) {
invariant(source.isEqualDb(target));
DisableDocumentValidation validationDisabler(opCtx);
DisableDocumentValidationForInternalOp validationDisabler(opCtx);
CollectionOrViewAcquisitionRequests acquisitionRequests = {
CollectionOrViewAcquisitionRequest::fromOpCtx(
@ -516,7 +516,7 @@ Status renameCollectionWithinDBForApplyOps(OperationContext* opCtx,
repl::OpTime renameOpTimeFromApplyOps,
const RenameCollectionOptions& options) {
invariant(source.isEqualDb(target));
DisableDocumentValidation validationDisabler(opCtx);
DisableDocumentValidationForInternalOp validationDisabler(opCtx);
AutoGetDb autoDb(opCtx, source.dbName(), MODE_IX);
auto acqStatus =
@ -874,7 +874,7 @@ Status renameCollectionAcrossDatabases(OperationContext* opCtx,
return acqStatus.getStatus();
auto& [sourceColl, tmpName, tempCollLock] = acqStatus.getValue();
DisableDocumentValidation validationDisabler(opCtx);
DisableDocumentValidationForInternalOp validationDisabler(opCtx);
if (!sourceDB.getDb())
return Status(ErrorCodes::NamespaceNotFound, "source namespace does not exist");

View File

@ -212,16 +212,11 @@ public:
return Validator();
}
void setValidator(OperationContext* opCtx, Validator validator) final {
unimplementedTasserted();
}
Status setValidationLevel(OperationContext* opCtx, ValidationLevelEnum newLevel) final {
unimplementedTasserted();
return Status(ErrorCodes::UnknownError, "unknown");
}
Status setValidationAction(OperationContext* opCtx, ValidationActionEnum newAction) final {
Status setValidationOptions(OperationContext* opCtx,
boost::optional<ValidationLevelEnum> newLevel,
boost::optional<ValidationActionEnum> newAction,
boost::optional<Validator> newValidator) final {
unimplementedTasserted();
return Status(ErrorCodes::UnknownError, "unknown");
}

View File

@ -198,7 +198,7 @@ void performAtomicWrites(
7655102, "must specify at least one type of write", modificationOp || !insertOps.empty());
NamespaceString ns = coll->ns();
DisableDocumentValidation disableDocumentValidation{opCtx};
DisableDocumentValidationForInternalOp disableDocumentValidation{opCtx};
write_ops_exec::LastOpFixer lastOpFixer{opCtx};
lastOpFixer.startingOp(ns);

View File

@ -930,7 +930,7 @@ Status performAtomicTimeseriesWrites(
auto originalNss =
!insertOps.empty() ? insertOps.front().getNamespace() : updateOps.front().getNamespace();
DisableDocumentValidation disableDocumentValidation{opCtx};
DisableDocumentValidationForInternalOp disableDocumentValidation{opCtx};
write_ops_exec::LastOpFixer lastOpFixer(opCtx);
lastOpFixer.startingOp(originalNss);