TEST(CollectionOptions, NExtentsNumberLimits) { CollectionOptions options; ASSERT_OK(options.parse(fromjson("{$nExtents: 'a'}"))); ASSERT_EQ(options.initialNumExtents, 0); ASSERT_OK(options.parse(fromjson("{$nExtents: '-1'}"))); ASSERT_EQ(options.initialNumExtents, 0); ASSERT_OK(options.parse(fromjson("{$nExtents: '-9999999999999999999999999999999999'}"))); ASSERT_EQ(options.initialNumExtents, 0); ASSERT_OK(options.parse(fromjson("{$nExtents: 9999999999999999999999999999999}"))); ASSERT_EQ(options.initialNumExtents, LLONG_MAX); }
TEST(CollectionOptions, MaxNumberLimits) { CollectionOptions options; ASSERT_OK(options.parse(fromjson("{max: 'a'}"))); ASSERT_EQ(options.cappedMaxDocs, 0); ASSERT_OK(options.parse(fromjson("{max: '-1'}"))); ASSERT_EQ(options.cappedMaxDocs, 0); ASSERT_OK(options.parse(fromjson("{max: '-9999999999999999999999999999999999'}"))); ASSERT_EQ(options.cappedMaxDocs, 0); ASSERT_OK(options.parse(fromjson("{max: 99999999999999999999999999999}"))); ASSERT_EQ(options.cappedMaxDocs, 0); }
TEST(CollectionOptions, SizeNumberLimits) { CollectionOptions options; ASSERT_OK(options.parse(fromjson("{size: 'a'}"))); ASSERT_EQ(options.cappedSize, 0); ASSERT_OK(options.parse(fromjson("{size: '-1'}"))); ASSERT_EQ(options.cappedSize, 0); ASSERT_OK(options.parse(fromjson("{size: '-9999999999999999999999999999999'}"))); ASSERT_EQ(options.cappedSize, 0); // The test for size is redundant since size returns a status that's not ok if it's larger // than a petabyte, which is smaller than LLONG_MAX anyways. We test that here. ASSERT_NOT_OK(options.parse(fromjson("{size: 9999999999999999}"))); }
/** { ..., capped: true, size: ..., max: ... } * @param createDefaultIndexes - if false, defers id (and other) index creation. * @return true if successful */ Status userCreateNS( OperationContext* txn, Database* db, StringData ns, BSONObj options, bool logForReplication, bool createDefaultIndexes ) { invariant( db ); LOG(1) << "create collection " << ns << ' ' << options; if ( !NamespaceString::validCollectionComponent(ns) ) return Status( ErrorCodes::InvalidNamespace, str::stream() << "invalid ns: " << ns ); Collection* collection = db->getCollection( ns ); if ( collection ) return Status( ErrorCodes::NamespaceExists, "collection already exists" ); CollectionOptions collectionOptions; Status status = collectionOptions.parse(options); if ( !status.isOK() ) return status; status = validateStorageOptions(collectionOptions.storageEngine, &StorageEngine::Factory::validateCollectionStorageOptions); if ( !status.isOK() ) return status; invariant( db->createCollection( txn, ns, collectionOptions, true, createDefaultIndexes ) ); if ( logForReplication ) { if ( options.getField( "create" ).eoo() ) { BSONObjBuilder b; b << "create" << nsToCollectionSubstring( ns ); b.appendElements( options ); options = b.obj(); } string logNs = nsToDatabase(ns) + ".$cmd"; repl::logOp(txn, "c", logNs.c_str(), options); } return Status::OK(); }
Status MMAPV1DatabaseCatalogEntry::createCollection(OperationContext* txn, StringData ns, const CollectionOptions& options, bool allocateDefaultSpace) { if (_namespaceIndex.details(ns)) { return Status(ErrorCodes::NamespaceExists, str::stream() << "namespace already exists: " << ns); } BSONObj optionsAsBSON = options.toBSON(); RecordId rid = _addNamespaceToNamespaceCollection(txn, ns, &optionsAsBSON); _namespaceIndex.add_ns(txn, ns, DiskLoc(), options.capped); NamespaceDetails* details = _namespaceIndex.details(ns); // Set the flags. NamespaceDetailsRSV1MetaData(ns, details).replaceUserFlags(txn, options.flags); if (options.capped && options.cappedMaxDocs > 0) { txn->recoveryUnit()->writingInt(details->maxDocsInCapped) = options.cappedMaxDocs; } Entry*& entry = _collections[ns.toString()]; invariant(!entry); txn->recoveryUnit()->registerChange(new EntryInsertion(ns, this)); entry = new Entry(); _insertInCache(txn, ns, rid, entry); if (allocateDefaultSpace) { RecordStoreV1Base* rs = _getRecordStore(ns); if (options.initialNumExtents > 0) { int size = _massageExtentSize(&_extentManager, options.cappedSize); for (int i = 0; i < options.initialNumExtents; i++) { rs->increaseStorageSize(txn, size, false); } } else if (!options.initialExtentSizes.empty()) { for (size_t i = 0; i < options.initialExtentSizes.size(); i++) { int size = options.initialExtentSizes[i]; size = _massageExtentSize(&_extentManager, size); rs->increaseStorageSize(txn, size, false); } } else if (options.capped) { // normal do { // Must do this at least once, otherwise we leave the collection with no // extents, which is invalid. int sz = _massageExtentSize(&_extentManager, options.cappedSize - rs->storageSize(txn)); sz &= 0xffffff00; rs->increaseStorageSize(txn, sz, false); } while (rs->storageSize(txn) < options.cappedSize); } else { rs->increaseStorageSize(txn, _extentManager.initialSize(128), false); } } return Status::OK(); }
bool run(OperationContext* txn, const string& dbname, BSONObj& jsobj, int, string& errmsg, BSONObjBuilder& result, bool /*fromRepl*/) { Lock::DBRead lk( txn->lockState(), dbname ); const Database* d = dbHolder().get( txn, dbname ); const DatabaseCatalogEntry* dbEntry = NULL; list<string> names; if ( d ) { dbEntry = d->getDatabaseCatalogEntry(); dbEntry->getCollectionNamespaces( &names ); names.sort(); } BSONArrayBuilder arr; for ( list<string>::const_iterator i = names.begin(); i != names.end(); ++i ) { string ns = *i; StringData collection = nsToCollectionSubstring( ns ); if ( collection == "system.namespaces" ) { continue; } BSONObjBuilder b; b.append( "name", collection ); CollectionOptions options = dbEntry->getCollectionCatalogEntry( txn, ns )->getCollectionOptions(txn); b.append( "options", options.toBSON() ); arr.append( b.obj() ); } result.append( "collections", arr.arr() ); return true; }
TEST(CollectionOptions, ModifyStorageEngineField) { CollectionOptions opts; // Directly modify storageEngine field in collection options. opts.storageEngine = fromjson("{storageEngine1: {x: 1}}"); // Unrecognized field should not be present in BSON representation. BSONObj obj = opts.toBSON(); ASSERT_FALSE(obj.hasField("unknownField")); // Check "storageEngine" field. ASSERT_TRUE(obj.hasField("storageEngine")); ASSERT_TRUE(obj.getField("storageEngine").isABSONObj()); BSONObj storageEngine = obj.getObjectField("storageEngine"); // Check individual storage storageEngine fields. ASSERT_TRUE(storageEngine.getField("storageEngine1").isABSONObj()); BSONObj storageEngine1 = storageEngine.getObjectField("storageEngine1"); ASSERT_EQUALS(1, storageEngine1.getIntField("x")); }
CollectionOptions MMAPV1DatabaseCatalogEntry::getCollectionOptions(OperationContext* txn, RecordId rid) const { CollectionOptions options; if (rid.isNull()) { return options; } RecordStoreV1Base* rs = _getNamespaceRecordStore(); invariant(rs); RecordData data; invariant(rs->findRecord(txn, rid, &data)); if (data.releaseToBson()["options"].isABSONObj()) { Status status = options.parse(data.releaseToBson()["options"].Obj()); fassert(18523, status); } return options; }
TEST(CollectionOptions, ParseEngineField) { CollectionOptions opts; ASSERT_OK(opts.parse( fromjson("{storageEngine: {storageEngine1: {x: 1, y: 2}, storageEngine2: {a: 1, b:2}}}"))); checkRoundTrip(opts); BSONObj obj = opts.toBSON(); // Check "storageEngine" field. ASSERT_TRUE(obj.hasField("storageEngine")); ASSERT_TRUE(obj.getField("storageEngine").isABSONObj()); BSONObj storageEngine = obj.getObjectField("storageEngine"); // Check individual storage storageEngine fields. ASSERT_TRUE(storageEngine.getField("storageEngine1").isABSONObj()); BSONObj storageEngine1 = storageEngine.getObjectField("storageEngine1"); ASSERT_EQUALS(1, storageEngine1.getIntField("x")); ASSERT_EQUALS(2, storageEngine1.getIntField("y")); ASSERT_TRUE(storageEngine.getField("storageEngine2").isABSONObj()); BSONObj storageEngine2 = storageEngine.getObjectField("storageEngine2"); ASSERT_EQUALS(1, storageEngine2.getIntField("a")); ASSERT_EQUALS(2, storageEngine2.getIntField("b")); }
CollectionCloner::CollectionCloner(ReplicationExecutor* executor, const HostAndPort& source, const NamespaceString& sourceNss, const CollectionOptions& options, const CallbackFn& work, StorageInterface* storageInterface) : _executor(executor), _source(source), _sourceNss(sourceNss), _destNss(_sourceNss), _options(options), _work(work), _storageInterface(storageInterface), _active(false), _listIndexesFetcher(_executor, _source, _sourceNss.db().toString(), BSON("listIndexes" << _sourceNss.coll()), stdx::bind(&CollectionCloner::_listIndexesCallback, this, stdx::placeholders::_1, stdx::placeholders::_2, stdx::placeholders::_3)), _findFetcher(_executor, _source, _sourceNss.db().toString(), BSON("find" << _sourceNss.coll() << "noCursorTimeout" << true), // SERVER-1387 stdx::bind(&CollectionCloner::_findCallback, this, stdx::placeholders::_1, stdx::placeholders::_2, stdx::placeholders::_3)), _indexSpecs(), _documents(), _dbWorkCallbackHandle(), // TODO: replace with executor database worker when it is available. _scheduleDbWorkFn(stdx::bind(&ReplicationExecutor::scheduleWorkWithGlobalExclusiveLock, _executor, stdx::placeholders::_1)) { uassert(ErrorCodes::BadValue, "null replication executor", executor); uassert(ErrorCodes::BadValue, "invalid collection namespace: " + sourceNss.ns(), sourceNss.isValid()); uassertStatusOK(options.validate()); uassert(ErrorCodes::BadValue, "callback function cannot be null", work); uassert(ErrorCodes::BadValue, "null storage interface", storageInterface); }
Status Database::createView(OperationContext* txn, StringData ns, const CollectionOptions& options) { invariant(txn->lockState()->isDbLockedForMode(name(), MODE_X)); invariant(options.isView()); NamespaceString nss(ns); NamespaceString viewOnNss(nss.db(), options.viewOn); _checkCanCreateCollection(nss, options); audit::logCreateCollection(&cc(), ns); if (nss.isOplog()) return Status(ErrorCodes::InvalidNamespace, str::stream() << "invalid namespace name for a view: " + nss.toString()); return _views.createView(txn, nss, viewOnNss, BSONArray(options.pipeline), options.collation); }
void OpObserver::onCreateCollection(OperationContext* txn, const NamespaceString& collectionName, const CollectionOptions& options) { std::string dbName = collectionName.db().toString() + ".$cmd"; BSONObjBuilder b; b.append("create", collectionName.coll().toString()); b.appendElements(options.toBSON()); BSONObj cmdObj = b.obj(); if (!collectionName.isSystemDotProfile()) { // do not replicate system.profile modifications repl::logOp(txn, "c", dbName.c_str(), cmdObj, nullptr, false); } getGlobalAuthorizationManager()->logOp(txn, "c", dbName.c_str(), cmdObj, nullptr); logOpForDbHash(txn, dbName.c_str()); }
TEST(CollectionOptions, Validator) { CollectionOptions options; ASSERT_NOT_OK(options.parse(fromjson("{validator: 'notAnObject'}"))); ASSERT_OK(options.parse(fromjson("{validator: {a: 1}}"))); ASSERT_BSONOBJ_EQ(options.validator, fromjson("{a: 1}")); options.validator = fromjson("{b: 1}"); ASSERT_BSONOBJ_EQ(options.toBSON()["validator"].Obj(), fromjson("{b: 1}")); CollectionOptions defaultOptions; ASSERT_BSONOBJ_EQ(defaultOptions.validator, BSONObj()); ASSERT(!defaultOptions.toBSON()["validator"]); }
void ShardingCatalogManager::createCollection(OperationContext* opCtx, const NamespaceString& ns, const CollectionOptions& collOptions) { const auto catalogClient = Grid::get(opCtx)->catalogClient(); auto shardRegistry = Grid::get(opCtx)->shardRegistry(); auto dbEntry = uassertStatusOK(catalogClient->getDatabase( opCtx, ns.db().toString(), repl::ReadConcernLevel::kLocalReadConcern)) .value; const auto& primaryShardId = dbEntry.getPrimary(); auto primaryShard = uassertStatusOK(shardRegistry->getShard(opCtx, primaryShardId)); BSONObjBuilder createCmdBuilder; createCmdBuilder.append("create", ns.coll()); collOptions.appendBSON(&createCmdBuilder); createCmdBuilder.append(kWriteConcernField, opCtx->getWriteConcern().toBSON()); auto swResponse = primaryShard->runCommandWithFixedRetryAttempts( opCtx, ReadPreferenceSetting{ReadPreference::PrimaryOnly}, ns.db().toString(), createCmdBuilder.obj(), Shard::RetryPolicy::kIdempotent); auto createStatus = Shard::CommandResponse::getEffectiveStatus(swResponse); if (!createStatus.isOK() && createStatus != ErrorCodes::NamespaceExists) { uassertStatusOK(createStatus); } checkCollectionOptions(opCtx, primaryShard.get(), ns, collOptions); // TODO: SERVER-33094 use UUID returned to write config.collections entries. // Make sure to advance the opTime if writes didn't occur during the execution of this // command. This is to ensure that this request will wait for the opTime that at least // reflects the current state (that this command observed) while waiting for replication // to satisfy the write concern. repl::ReplClientInfo::forClient(opCtx->getClient()).setLastOpToSystemLastOpTime(opCtx); }
TEST(CollectionOptions, ParseUUID) { CollectionOptions options; CollectionUUID uuid = CollectionUUID::gen(); // Check required parse failures ASSERT_FALSE(options.uuid); ASSERT_NOT_OK(options.parse(uuid.toBSON())); ASSERT_NOT_OK(options.parse(BSON("uuid" << 1))); ASSERT_NOT_OK(options.parse(BSON("uuid" << 1), CollectionOptions::parseForStorage)); ASSERT_FALSE(options.uuid); // Check successful parse and roundtrip. ASSERT_OK(options.parse(uuid.toBSON(), CollectionOptions::parseForStorage)); ASSERT(options.uuid.get() == uuid); // Check that a collection options containing a UUID passes validation. ASSERT_OK(options.validateForStorage()); }
void checkRoundTrip(const CollectionOptions& options1) { CollectionOptions options2; options2.parse(options1.toBSON()); ASSERT_BSONOBJ_EQ(options1.toBSON(), options2.toBSON()); }
TEST(CollectionOptions, WriteConcernWhitelistedOptionIgnored) { CollectionOptions options; auto status = options.parse(fromjson("{writeConcern: 1}")); ASSERT_OK(status); }
TEST(CollectionOptions, MaxTimeMSWhitelistedOptionIgnored) { CollectionOptions options; auto status = options.parse(fromjson("{maxTimeMS: 1}")); ASSERT_OK(status); }
TEST(CollectionOptions, DuplicateCreateOptionIgnoredIfCreateOptionNotFirst) { CollectionOptions options; auto status = options.parse(BSON("capped" << true << "create" << 1 << "create" << 1 << "size" << 1024)); ASSERT_OK(status); }
TEST(CollectionOptions, CollationFieldLeftEmptyWhenOmitted) { CollectionOptions options; ASSERT_OK(options.parse(fromjson("{validator: {a: 1}}"))); ASSERT_TRUE(options.collation.isEmpty()); }
TEST(CollectionOptions, UnknownOptionRejectedIfCreateOptionNotPresent) { CollectionOptions options; auto status = options.parse(fromjson("{invalidOption: 1}")); ASSERT_NOT_OK(status); ASSERT_EQ(status.code(), ErrorCodes::InvalidOptions); }
TEST(CollectionOptions, UnknownOptionIgnoredIfCreateOptionPresent) { CollectionOptions options; ASSERT_OK(options.parse(fromjson("{invalidOption: 1, create: 1}"))); }
TEST(CollectionOptions, CreateOptionIgnoredIfFirst) { CollectionOptions options; auto status = options.parse(fromjson("{create: 1}")); ASSERT_OK(status); }
TEST(CollectionOptions, UnknownTopLevelOptionFailsToParse) { CollectionOptions options; auto status = options.parse(fromjson("{invalidOption: 1}")); ASSERT_NOT_OK(status); ASSERT_EQ(status.code(), ErrorCodes::InvalidOptions); }
TEST(CollectionOptions, PipelineFieldRequiresViewOn) { CollectionOptions options; ASSERT_NOT_OK(options.parse(fromjson("{pipeline: [{$match: {}}]}"))); }
TEST(CollectionOptions, ParsedCollationObjShouldBeOwned) { CollectionOptions options; ASSERT_OK(options.parse(fromjson("{collation: {locale: 'en'}}"))); ASSERT_BSONOBJ_EQ(options.collation, fromjson("{locale: 'en'}")); ASSERT_TRUE(options.collation.isOwned()); }
TEST(CollectionOptions, ViewParsesCorrectly) { CollectionOptions options; ASSERT_OK(options.parse(fromjson("{viewOn: 'c', pipeline: [{$match: {}}]}"))); ASSERT_EQ(options.viewOn, "c"); ASSERT_BSONOBJ_EQ(options.pipeline, fromjson("[{$match: {}}]")); }
TEST(CollectionOptions, DuplicateCreateOptionIgnoredIfCreateOptionFirst) { CollectionOptions options; auto status = options.parse(BSON("create" << 1 << "create" << 1)); ASSERT_OK(status); }
TEST(CollectionOptions, ViewParsesCorrectlyWithoutPipeline) { CollectionOptions options; ASSERT_OK(options.parse(fromjson("{viewOn: 'c'}"))); ASSERT_EQ(options.viewOn, "c"); ASSERT_BSONOBJ_EQ(options.pipeline, BSONObj()); }
TEST(CollectionOptions, ResetClearsCollationField) { CollectionOptions options; ASSERT_TRUE(options.collation.isEmpty()); ASSERT_OK(options.parse(fromjson("{collation: {locale: 'en'}}"))); ASSERT_FALSE(options.collation.isEmpty()); }