/**
* Copyright (C) 2014 MongoDB Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* 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
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*
* 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 GNU Affero General 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/platform/basic.h"
#include "mongo/db/catalog/collection_options.h"
#include
#include "mongo/base/string_data.h"
#include "mongo/db/command_generic_argument.h"
#include "mongo/db/commands.h"
#include "mongo/db/query/collation/collator_factory_interface.h"
#include "mongo/db/query/collation/collator_interface.h"
#include "mongo/db/server_parameters.h"
#include "mongo/util/mongoutils/str.h"
namespace mongo {
// static
bool CollectionOptions::validMaxCappedDocs(long long* max) {
if (*max <= 0 || *max == std::numeric_limits::max()) {
*max = 0x7fffffff;
return true;
}
if (*max < (0x1LL << 31)) {
return true;
}
return false;
}
namespace {
Status checkStorageEngineOptions(const BSONElement& elem) {
invariant(elem.fieldNameStringData() == "storageEngine");
// Storage engine-specific collection options.
// "storageEngine" field must be of type "document".
// Every field inside "storageEngine" has to be a document.
// Format:
// {
// ...
// storageEngine: {
// storageEngine1: {
// ...
// },
// storageEngine2: {
// ...
// }
// },
// ...
// }
if (elem.type() != mongo::Object) {
return {ErrorCodes::BadValue, "'storageEngine' has to be a document."};
}
BSONForEach(storageEngineElement, elem.Obj()) {
StringData storageEngineName = storageEngineElement.fieldNameStringData();
if (storageEngineElement.type() != mongo::Object) {
return {ErrorCodes::BadValue,
str::stream() << "'storageEngine." << storageEngineName
<< "' has to be an embedded document."};
}
}
return Status::OK();
}
} // namespace
bool CollectionOptions::isView() const {
return !viewOn.empty();
}
Status CollectionOptions::validateForStorage() const {
return CollectionOptions().parse(toBSON(), ParseKind::parseForStorage);
}
Status CollectionOptions::parse(const BSONObj& options, ParseKind kind) {
*this = {};
// Versions 2.4 and earlier of the server store "create" inside the collection metadata when the
// user issues an explicit collection creation command. These versions also wrote any
// unrecognized fields into the catalog metadata and allowed the order of these fields to be
// changed. Therefore, if the "create" field is present, we must ignore any
// unknown fields during parsing. Otherwise, we disallow unknown collection options.
//
// Versions 2.6 through 3.2 ignored unknown collection options rather than failing but did not
// store the "create" field. These versions also refrained from materializing the unknown
// options in the catalog, so we are free to fail on unknown options in this case.
const bool createdOn24OrEarlier = static_cast(options["create"]);
// During parsing, ignore some validation errors in order to accept options objects that
// were valid in previous versions of the server. SERVER-13737.
BSONObjIterator i(options);
while (i.more()) {
BSONElement e = i.next();
StringData fieldName = e.fieldName();
if (fieldName == "uuid" && kind == parseForStorage) {
auto res = CollectionUUID::parse(e);
if (!res.isOK()) {
return res.getStatus();
}
uuid = res.getValue();
} else if (fieldName == "capped") {
capped = e.trueValue();
} else if (fieldName == "size") {
if (!e.isNumber()) {
// Ignoring for backwards compatibility.
continue;
}
cappedSize = e.safeNumberLong();
if (cappedSize < 0)
return Status(ErrorCodes::BadValue, "size has to be >= 0");
const long long kGB = 1024 * 1024 * 1024;
const long long kPB = 1024 * 1024 * kGB;
if (cappedSize > kPB)
return Status(ErrorCodes::BadValue, "size cannot exceed 1 PB");
cappedSize += 0xff;
cappedSize &= 0xffffffffffffff00LL;
} else if (fieldName == "max") {
if (!options["capped"].trueValue() || !e.isNumber()) {
// Ignoring for backwards compatibility.
continue;
}
cappedMaxDocs = e.safeNumberLong();
if (!validMaxCappedDocs(&cappedMaxDocs))
return Status(ErrorCodes::BadValue,
"max in a capped collection has to be < 2^31 or not set");
} else if (fieldName == "$nExtents") {
if (e.type() == Array) {
BSONObjIterator j(e.Obj());
while (j.more()) {
BSONElement inner = j.next();
initialExtentSizes.push_back(inner.numberInt());
}
} else {
initialNumExtents = e.safeNumberLong();
}
} else if (fieldName == "autoIndexId") {
if (e.trueValue())
autoIndexId = YES;
else
autoIndexId = NO;
} else if (fieldName == "flags") {
flags = e.numberInt();
flagsSet = true;
} else if (fieldName == "temp") {
temp = e.trueValue();
} else if (fieldName == "storageEngine") {
Status status = checkStorageEngineOptions(e);
if (!status.isOK()) {
return status;
}
storageEngine = e.Obj().getOwned();
} else if (fieldName == "indexOptionDefaults") {
if (e.type() != mongo::Object) {
return {ErrorCodes::TypeMismatch, "'indexOptionDefaults' has to be a document."};
}
BSONForEach(option, e.Obj()) {
if (option.fieldNameStringData() == "storageEngine") {
Status status = checkStorageEngineOptions(option);
if (!status.isOK()) {
return status.withContext("Error in indexOptionDefaults");
}
} else {
// Return an error on first unrecognized field.
return {ErrorCodes::InvalidOptions,
str::stream() << "indexOptionDefaults." << option.fieldNameStringData()
<< " is not a supported option."};
}
}
indexOptionDefaults = e.Obj().getOwned();
} else if (fieldName == "validator") {
if (e.type() != mongo::Object) {
return Status(ErrorCodes::BadValue, "'validator' has to be a document.");
}
validator = e.Obj().getOwned();
} else if (fieldName == "validationAction") {
if (e.type() != mongo::String) {
return Status(ErrorCodes::BadValue, "'validationAction' has to be a string.");
}
validationAction = e.String();
} else if (fieldName == "validationLevel") {
if (e.type() != mongo::String) {
return Status(ErrorCodes::BadValue, "'validationLevel' has to be a string.");
}
validationLevel = e.String();
} else if (fieldName == "collation") {
if (e.type() != mongo::Object) {
return Status(ErrorCodes::BadValue, "'collation' has to be a document.");
}
if (e.Obj().isEmpty()) {
return Status(ErrorCodes::BadValue, "'collation' cannot be an empty document.");
}
collation = e.Obj().getOwned();
} else if (fieldName == "viewOn") {
if (e.type() != mongo::String) {
return Status(ErrorCodes::BadValue, "'viewOn' has to be a string.");
}
viewOn = e.String();
if (viewOn.empty()) {
return Status(ErrorCodes::BadValue, "'viewOn' cannot be empty.'");
}
} else if (fieldName == "pipeline") {
if (e.type() != mongo::Array) {
return Status(ErrorCodes::BadValue, "'pipeline' has to be an array.");
}
pipeline = e.Obj().getOwned();
} else if (fieldName == "idIndex" && kind == parseForCommand) {
if (e.type() != mongo::Object) {
return Status(ErrorCodes::TypeMismatch, "'idIndex' has to be an object.");
}
auto tempIdIndex = e.Obj().getOwned();
if (tempIdIndex.isEmpty()) {
return {ErrorCodes::FailedToParse, "idIndex cannot be empty"};
}
idIndex = std::move(tempIdIndex);
} else if (!createdOn24OrEarlier && !mongo::isGenericArgument(fieldName)) {
return Status(ErrorCodes::InvalidOptions,
str::stream() << "The field '" << fieldName
<< "' is not a valid collection option. Options: "
<< options);
}
}
if (viewOn.empty() && !pipeline.isEmpty()) {
return Status(ErrorCodes::BadValue, "'pipeline' cannot be specified without 'viewOn'");
}
return Status::OK();
}
BSONObj CollectionOptions::toBSON() const {
BSONObjBuilder b;
appendBSON(&b);
return b.obj();
}
void CollectionOptions::appendBSON(BSONObjBuilder* builder) const {
if (uuid) {
builder->appendElements(uuid->toBSON());
}
if (capped) {
builder->appendBool("capped", true);
builder->appendNumber("size", cappedSize);
if (cappedMaxDocs)
builder->appendNumber("max", cappedMaxDocs);
}
if (initialNumExtents)
builder->appendNumber("$nExtents", initialNumExtents);
if (!initialExtentSizes.empty())
builder->append("$nExtents", initialExtentSizes);
if (autoIndexId != DEFAULT)
builder->appendBool("autoIndexId", autoIndexId == YES);
if (flagsSet)
builder->append("flags", flags);
if (temp)
builder->appendBool("temp", true);
if (!storageEngine.isEmpty()) {
builder->append("storageEngine", storageEngine);
}
if (!indexOptionDefaults.isEmpty()) {
builder->append("indexOptionDefaults", indexOptionDefaults);
}
if (!validator.isEmpty()) {
builder->append("validator", validator);
}
if (!validationLevel.empty()) {
builder->append("validationLevel", validationLevel);
}
if (!validationAction.empty()) {
builder->append("validationAction", validationAction);
}
if (!collation.isEmpty()) {
builder->append("collation", collation);
}
if (!viewOn.empty()) {
builder->append("viewOn", viewOn);
}
if (!pipeline.isEmpty()) {
builder->appendArray("pipeline", pipeline);
}
if (!idIndex.isEmpty()) {
builder->append("idIndex", idIndex);
}
}
bool CollectionOptions::matchesStorageOptions(const CollectionOptions& other,
CollatorFactoryInterface* collatorFactory) const {
if (capped != other.capped) {
return false;
}
if (cappedSize != other.cappedSize) {
return false;
}
if (cappedMaxDocs != other.cappedMaxDocs) {
return false;
}
if (initialNumExtents != other.initialNumExtents) {
return false;
}
if (initialExtentSizes.size() != other.initialExtentSizes.size()) {
return false;
}
if (!std::equal(other.initialExtentSizes.begin(),
other.initialExtentSizes.end(),
initialExtentSizes.begin())) {
return false;
}
if (autoIndexId != other.autoIndexId) {
return false;
}
if (flagsSet != other.flagsSet) {
return false;
}
if (flags != other.flags) {
return false;
}
if (temp != other.temp) {
return false;
}
if (storageEngine.woCompare(other.storageEngine) != 0) {
return false;
}
if (indexOptionDefaults.woCompare(other.indexOptionDefaults) != 0) {
return false;
}
if (validator.woCompare(other.validator) != 0) {
return false;
}
if (validationAction != other.validationAction) {
return false;
}
if (validationLevel != other.validationLevel) {
return false;
}
// Note: the server can add more stuff on the collation options that were not specified in
// the original user request. Use the collator to check for equivalence.
auto myCollator =
collation.isEmpty() ? nullptr : uassertStatusOK(collatorFactory->makeFromBSON(collation));
auto otherCollator = other.collation.isEmpty()
? nullptr
: uassertStatusOK(collatorFactory->makeFromBSON(other.collation));
if (!CollatorInterface::collatorsMatch(myCollator.get(), otherCollator.get())) {
return false;
}
if (viewOn != other.viewOn) {
return false;
}
if (pipeline.woCompare(other.pipeline) != 0) {
return false;
}
return true;
}
}