@@ -69,6 +69,7 @@ MONGO_FAIL_POINT_DEFINE(assertAfterIndexUpdate);
6969struct CollModRequest {
7070 const IndexDescriptor* idx = nullptr ;
7171 BSONElement indexExpireAfterSeconds = {};
72+ BSONElement indexHidden = {};
7273 BSONElement viewPipeLine = {};
7374 std::string viewOn = {};
7475 BSONElement collValidator = {};
@@ -125,14 +126,18 @@ StatusWith<CollModRequest> parseCollModRequest(OperationContext* opCtx,
125126 }
126127
127128 cmr.indexExpireAfterSeconds = indexObj[" expireAfterSeconds" ];
128- if (cmr.indexExpireAfterSeconds .eoo ()) {
129- return Status (ErrorCodes::InvalidOptions, " no expireAfterSeconds field" );
129+ cmr.indexHidden = indexObj[" hidden" ];
130+
131+ if (cmr.indexExpireAfterSeconds .eoo () && cmr.indexHidden .eoo ()) {
132+ return Status (ErrorCodes::InvalidOptions, " no expireAfterSeconds or hidden field" );
130133 }
131- if (!cmr.indexExpireAfterSeconds .isNumber ()) {
134+ if (!cmr.indexExpireAfterSeconds .eoo () && !cmr. indexExpireAfterSeconds . isNumber ()) {
132135 return Status (ErrorCodes::InvalidOptions,
133136 " expireAfterSeconds field must be a number" );
134137 }
135-
138+ if (!cmr.indexHidden .eoo () && !cmr.indexHidden .isBoolean ()) {
139+ return Status (ErrorCodes::InvalidOptions, " hidden field must be a boolean" );
140+ }
136141 if (!indexName.empty ()) {
137142 cmr.idx = coll->getIndexCatalog ()->findIndexByName (opCtx, indexName);
138143 if (!cmr.idx ) {
@@ -161,15 +166,17 @@ StatusWith<CollModRequest> parseCollModRequest(OperationContext* opCtx,
161166 cmr.idx = indexes[0 ];
162167 }
163168
164- BSONElement oldExpireSecs = cmr.idx ->infoObj ().getField (" expireAfterSeconds" );
165- if (oldExpireSecs.eoo ()) {
166- return Status (ErrorCodes::InvalidOptions, " no expireAfterSeconds field to update" );
167- }
168- if (!oldExpireSecs.isNumber ()) {
169- return Status (ErrorCodes::InvalidOptions,
170- " existing expireAfterSeconds field is not a number" );
169+ if (!cmr.indexExpireAfterSeconds .eoo ()) {
170+ BSONElement oldExpireSecs = cmr.idx ->infoObj ().getField (" expireAfterSeconds" );
171+ if (oldExpireSecs.eoo ()) {
172+ return Status (ErrorCodes::InvalidOptions,
173+ " no expireAfterSeconds field to update" );
174+ }
175+ if (!oldExpireSecs.isNumber ()) {
176+ return Status (ErrorCodes::InvalidOptions,
177+ " existing expireAfterSeconds field is not a number" );
178+ }
171179 }
172-
173180 } else if (fieldName == " validator" && !isView) {
174181 // Save this to a variable to avoid reading the atomic variable multiple times.
175182 const auto currentFCV = serverGlobalParams.featureCompatibility .getVersion ();
@@ -250,20 +257,35 @@ class CollModResultChange : public RecoveryUnit::Change {
250257public:
251258 CollModResultChange (const BSONElement& oldExpireSecs,
252259 const BSONElement& newExpireSecs,
260+ const BSONElement& oldHidden,
261+ const BSONElement& newHidden,
253262 BSONObjBuilder* result)
254- : _oldExpireSecs(oldExpireSecs), _newExpireSecs(newExpireSecs), _result(result) {}
263+ : _oldExpireSecs(oldExpireSecs),
264+ _newExpireSecs (newExpireSecs),
265+ _oldHidden(oldHidden),
266+ _newHidden(newHidden),
267+ _result(result) {}
255268
256269 void commit (boost::optional<Timestamp>) override {
257270 // add the fields to BSONObjBuilder result
258- _result->appendAs (_oldExpireSecs, " expireAfterSeconds_old" );
259- _result->appendAs (_newExpireSecs, " expireAfterSeconds_new" );
271+ if (!_oldExpireSecs.eoo ()) {
272+ _result->appendAs (_oldExpireSecs, " expireAfterSeconds_old" );
273+ _result->appendAs (_newExpireSecs, " expireAfterSeconds_new" );
274+ }
275+ if (!_newHidden.eoo ()) {
276+ bool oldValue = _oldHidden.eoo () ? false : _oldHidden.booleanSafe ();
277+ _result->append (" hidden_old" , oldValue);
278+ _result->appendAs (_newHidden, " hidden_new" );
279+ }
260280 }
261281
262282 void rollback () override {}
263283
264284private:
265285 const BSONElement _oldExpireSecs;
266286 const BSONElement _newExpireSecs;
287+ const BSONElement _oldHidden;
288+ const BSONElement _newHidden;
267289 BSONObjBuilder* _result;
268290};
269291
@@ -326,6 +348,19 @@ Status _collModInternal(OperationContext* opCtx,
326348 const CollModRequest cmrOld = statusW.getValue ();
327349 CollModRequest cmrNew = statusW.getValue ();
328350
351+ if (!cmrOld.indexHidden .eoo ()) {
352+
353+ if (serverGlobalParams.featureCompatibility .getVersion () <
354+ ServerGlobalParams::FeatureCompatibility::Version::kFullyUpgradedTo46 &&
355+ cmrOld.indexHidden .booleanSafe ()) {
356+ return Status (ErrorCodes::BadValue, " Hidden indexes can only be created with FCV 4.6" );
357+ }
358+ if (coll->ns ().isSystem ())
359+ return Status (ErrorCodes::BadValue, " Can't hide index on system collection" );
360+ if (cmrOld.idx ->isIdIndex ())
361+ return Status (ErrorCodes::BadValue, " can't hide _id index" );
362+ }
363+
329364 return writeConflictRetry (opCtx, " collMod" , nss.ns (), [&] {
330365 WriteUnitOfWork wunit (opCtx);
331366
@@ -362,40 +397,64 @@ Status _collModInternal(OperationContext* opCtx,
362397 CollectionOptions oldCollOptions =
363398 DurableCatalog::get (opCtx)->getCollectionOptions (opCtx, coll->getCatalogId ());
364399
365- boost::optional<TTLCollModInfo> ttlInfo ;
400+ boost::optional<IndexCollModInfo> indexCollModInfo ;
366401
367402 // Handle collMod operation type appropriately.
368403
369- // TTLIndex
370- if (!cmrOld.indexExpireAfterSeconds .eoo ()) {
371- BSONElement newExpireSecs = cmrOld.indexExpireAfterSeconds ;
372- BSONElement oldExpireSecs = cmrOld.idx ->infoObj ().getField (" expireAfterSeconds" );
373-
374- if (SimpleBSONElementComparator::kInstance .evaluate (oldExpireSecs != newExpireSecs)) {
375- // Change the value of "expireAfterSeconds" on disk.
376- DurableCatalog::get (opCtx)->updateTTLSetting (opCtx,
377- coll->getCatalogId (),
378- cmrOld.idx ->indexName (),
379- newExpireSecs.safeNumberLong ());
380-
381- // Notify the index catalog that the definition of this index changed. This will
382- // invalidate the idx pointer in cmrOld. On rollback of this WUOW, the idx pointer
383- // in cmrNew will be invalidated and the idx pointer in cmrOld will be valid again.
384- cmrNew.idx = coll->getIndexCatalog ()->refreshEntry (opCtx, cmrOld.idx );
385- opCtx->recoveryUnit ()->registerChange (
386- std::make_unique<CollModResultChange>(oldExpireSecs, newExpireSecs, result));
387-
388- if (MONGO_unlikely (assertAfterIndexUpdate.shouldFail ())) {
389- LOGV2 (20307 , " collMod - assertAfterIndexUpdate fail point enabled." );
390- uasserted (50970 , " trigger rollback after the index update" );
404+ if (!cmrOld.indexExpireAfterSeconds .eoo () || !cmrOld.indexHidden .eoo ()) {
405+ BSONElement newExpireSecs = {};
406+ BSONElement oldExpireSecs = {};
407+ BSONElement newHidden = {};
408+ BSONElement oldHidden = {};
409+ // TTL Index
410+ if (!cmrOld.indexExpireAfterSeconds .eoo ()) {
411+ newExpireSecs = cmrOld.indexExpireAfterSeconds ;
412+ oldExpireSecs = cmrOld.idx ->infoObj ().getField (" expireAfterSeconds" );
413+ if (SimpleBSONElementComparator::kInstance .evaluate (oldExpireSecs !=
414+ newExpireSecs)) {
415+ // Change the value of "expireAfterSeconds" on disk.
416+ DurableCatalog::get (opCtx)->updateTTLSetting (opCtx,
417+ coll->getCatalogId (),
418+ cmrOld.idx ->indexName (),
419+ newExpireSecs.safeNumberLong ());
420+ }
421+ }
422+
423+ // User wants to hide or unhide index.
424+ if (!cmrOld.indexHidden .eoo ()) {
425+ newHidden = cmrOld.indexHidden ;
426+ oldHidden = cmrOld.idx ->infoObj ().getField (" hidden" );
427+ // Make sure when we set 'hidden' to false, we can remove the hidden field from
428+ // catalog.
429+ if (SimpleBSONElementComparator::kInstance .evaluate (oldHidden != newHidden)) {
430+ DurableCatalog::get (opCtx)->updateHiddenSetting (opCtx,
431+ coll->getCatalogId (),
432+ cmrOld.idx ->indexName (),
433+ newHidden.booleanSafe ());
391434 }
392435 }
393436
394437
395- // Save previous TTL index expiration.
396- ttlInfo = TTLCollModInfo{Seconds (newExpireSecs.safeNumberLong ()),
397- Seconds (oldExpireSecs.safeNumberLong ()),
398- cmrNew.idx ->indexName ()};
438+ indexCollModInfo = IndexCollModInfo{
439+ cmrOld.indexExpireAfterSeconds .eoo () ? boost::optional<Seconds>()
440+ : Seconds (newExpireSecs.safeNumberLong ()),
441+ cmrOld.indexExpireAfterSeconds .eoo () ? boost::optional<Seconds>()
442+ : Seconds (oldExpireSecs.safeNumberLong ()),
443+ cmrOld.indexHidden .eoo () ? boost::optional<bool >() : newHidden.booleanSafe (),
444+ cmrOld.indexHidden .eoo () ? boost::optional<bool >() : oldHidden.booleanSafe (),
445+ cmrNew.idx ->indexName ()};
446+
447+ // Notify the index catalog that the definition of this index changed. This will
448+ // invalidate the idx pointer in cmrOld. On rollback of this WUOW, the idx pointer
449+ // in cmrNew will be invalidated and the idx pointer in cmrOld will be valid again.
450+ cmrNew.idx = coll->getIndexCatalog ()->refreshEntry (opCtx, cmrOld.idx );
451+ opCtx->recoveryUnit ()->registerChange (std::make_unique<CollModResultChange>(
452+ oldExpireSecs, newExpireSecs, oldHidden, newHidden, result));
453+
454+ if (MONGO_unlikely (assertAfterIndexUpdate.shouldFail ())) {
455+ LOGV2 (20307 , " collMod - assertAfterIndexUpdate fail point enabled." );
456+ uasserted (50970 , " trigger rollback after the index update" );
457+ }
399458 }
400459
401460 // The Validator, ValidationAction and ValidationLevel are already parsed and must be OK.
@@ -412,8 +471,10 @@ Status _collModInternal(OperationContext* opCtx,
412471
413472 // Only observe non-view collMods, as view operations are observed as operations on the
414473 // system.views collection.
474+
415475 auto * const opObserver = opCtx->getServiceContext ()->getOpObserver ();
416- opObserver->onCollMod (opCtx, nss, coll->uuid (), oplogEntryObj, oldCollOptions, ttlInfo);
476+ opObserver->onCollMod (
477+ opCtx, nss, coll->uuid (), oplogEntryObj, oldCollOptions, indexCollModInfo);
417478
418479 wunit.commit ();
419480 return Status::OK ();
0 commit comments