diff options
-rw-r--r-- | src/mongo/db/ops/SConscript | 4 | ||||
-rw-r--r-- | src/mongo/db/ops/field_checker.cpp | 72 | ||||
-rw-r--r-- | src/mongo/db/ops/field_checker.h | 55 | ||||
-rw-r--r-- | src/mongo/db/ops/field_checker_test.cpp | 118 |
4 files changed, 247 insertions, 2 deletions
diff --git a/src/mongo/db/ops/SConscript b/src/mongo/db/ops/SConscript index 23cde6d7c12..7eb9e5f64e8 100644 --- a/src/mongo/db/ops/SConscript +++ b/src/mongo/db/ops/SConscript @@ -2,7 +2,8 @@ Import("env") -env.StaticLibrary('update', ['path_support.cpp'], +env.StaticLibrary('update', ['field_checker.cpp', + 'path_support.cpp'], LIBDEPS=['$BUILD_DIR/mongo/bson', '$BUILD_DIR/mongo/foundation', '$BUILD_DIR/mongo/db/common']) @@ -11,4 +12,3 @@ env.CppUnitTest('field_checker_test', ['field_checker_test.cpp'], LIBDEPS=['upda env.CppUnitTest('path_support_test', ['path_support_test.cpp'], LIBDEPS=['update', '$BUILD_DIR/mongo/mutable_bson_test_utils']) - diff --git a/src/mongo/db/ops/field_checker.cpp b/src/mongo/db/ops/field_checker.cpp new file mode 100644 index 00000000000..7c097a8ac95 --- /dev/null +++ b/src/mongo/db/ops/field_checker.cpp @@ -0,0 +1,72 @@ +/** + * Copyright (C) 2013 10gen 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 <http://www.gnu.org/licenses/>. + */ + +#include "mongo/db/ops/field_checker.h" + +#include "mongo/base/error_codes.h" +#include "mongo/db/field_ref.h" + +namespace mongo { +namespace fieldchecker { + + Status basicIsUpdatable(const FieldRef& field) { + StringData firstPart = field.getPart(0); + if (firstPart.compare("_id") == 0) { + return Status(ErrorCodes::BadValue, "updated cannot affect the _id"); + } + return Status::OK(); + } + + Status isUpdatable(const FieldRef& field) { + Status status = basicIsUpdatable(field); + if (! status.isOK()) { + return status; + } + + StringData firstPart = field.getPart(0); + if (firstPart[0] == '$') { + return Status(ErrorCodes::BadValue, "field name cannot start with $"); + } + + return Status::OK(); + } + + Status isUpdatableLegacy(const FieldRef& field) { + return basicIsUpdatable(field); + } + + bool isPositional(const FieldRef& fieldRef, size_t* pos, size_t* count) { + + // 'count' is optional. + size_t dummy; + if (count == NULL) { + count = &dummy; + } + + *count = 0; + size_t size = fieldRef.numParts(); + for (size_t i=0; i<size; i++) { + StringData fieldPart = fieldRef.getPart(i); + if ((fieldPart.size() == 1) && (fieldPart[0] == '$')) { + if (*count == 0) *pos = i; + (*count)++; + } + } + return *count > 0; + } + +} // namespace fieldchecker +} // namespace mongo diff --git a/src/mongo/db/ops/field_checker.h b/src/mongo/db/ops/field_checker.h new file mode 100644 index 00000000000..1a375e16942 --- /dev/null +++ b/src/mongo/db/ops/field_checker.h @@ -0,0 +1,55 @@ +/** + * Copyright (C) 2013 10gen 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 <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include "mongo/base/status.h" + +namespace mongo { + + class FieldRef; + + namespace fieldchecker { + + /** + * Returns OK if all the below conditions on 'field' are valid: + * + Non-empty + * + Not the _id field (or a subfield of the _id field, such as _id.x.y) + * + Does not start or end with a '.' + * + Does not start with a $ + * Otherwise returns a code indicating cause of failure. + */ + Status isUpdatable(const FieldRef& field); + + /** + * Same behavior of isUpdatable but allowing update fields to start with '$'. This + * supports $unset on legacy fields. + */ + Status isUpdatableLegacy(const FieldRef& field); + + /** + * Returns true, the position 'pos' of the first $-sign if present in 'fieldRef', and + * how many other $-signs were found in 'count'. Otherwise return false. + * + * Note: + * isPositional assumes that the field is updatable. Call isUpdatable() above to + * verify. + */ + bool isPositional(const FieldRef& fieldRef, size_t* pos, size_t* count = NULL); + + } // namespace fieldchecker + +} // namespace mongo diff --git a/src/mongo/db/ops/field_checker_test.cpp b/src/mongo/db/ops/field_checker_test.cpp new file mode 100644 index 00000000000..715cc5c45ea --- /dev/null +++ b/src/mongo/db/ops/field_checker_test.cpp @@ -0,0 +1,118 @@ +/** + * Copyright 2013 10gen Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "mongo/db/ops/field_checker.h" + +#include "mongo/base/error_codes.h" +#include "mongo/base/status.h" +#include "mongo/db/field_ref.h" +#include "mongo/unittest/unittest.h" + +namespace { + + using mongo::ErrorCodes; + using mongo::FieldRef; + using mongo::fieldchecker::isUpdatable; + using mongo::fieldchecker::isUpdatableLegacy; + using mongo::fieldchecker::isPositional; + using mongo::Status; + + TEST(IsUpdatable, Basics) { + FieldRef fieldRef; + fieldRef.parse("x"); + ASSERT_OK(isUpdatable(fieldRef)); + } + + TEST(IsUpdatable, DottedFields) { + FieldRef fieldRef; + fieldRef.parse("x.y.z"); + ASSERT_OK(isUpdatable(fieldRef)); + } + + TEST(IsUpdatable, SpecialIDField) { + FieldRef fieldRefID; + fieldRefID.parse("_id"); + ASSERT_NOT_OK(isUpdatable(fieldRefID)); + + FieldRef fieldRefIDX; + fieldRefIDX.parse("_id.x"); + ASSERT_NOT_OK(isUpdatable(fieldRefIDX)); + + FieldRef fieldRefXID; + fieldRefXID.parse("x._id"); + ASSERT_OK(isUpdatable(fieldRefXID)); + + FieldRef fieldRefXIDZ; + fieldRefXIDZ.parse("x._id.z"); + ASSERT_OK(isUpdatable(fieldRefXIDZ)); + } + + TEST(IsUpdatable, PositionalFields) { + FieldRef fieldRefXDollar; + fieldRefXDollar.parse("x.$"); + ASSERT_OK(isUpdatable(fieldRefXDollar)); + + FieldRef fieldRefXDollarZ; + fieldRefXDollarZ.parse("x.$.z"); + ASSERT_OK(isUpdatable(fieldRefXDollarZ)); + + // A document never starts with an array. + FieldRef fieldRefDollarB; + fieldRefDollarB.parse("$.b"); + ASSERT_NOT_OK(isUpdatable(fieldRefDollarB)); + + FieldRef fieldRefDollar; + fieldRefDollar.parse("$foo"); + ASSERT_NOT_OK(isUpdatable(fieldRefDollar)); + } + + TEST(IsUpdatableLegacy, Basics) { + FieldRef fieldRefDollar; + fieldRefDollar.parse("$foo"); + ASSERT_OK(isUpdatableLegacy(fieldRefDollar)); + } + + TEST(isPositional, EntireArrayItem) { + FieldRef fieldRefPositional; + fieldRefPositional.parse("a.$"); + size_t pos; + size_t count; + ASSERT_TRUE(isPositional(fieldRefPositional, &pos, &count)); + ASSERT_EQUALS(pos, 1u); + ASSERT_EQUALS(count, 1u); + } + + TEST(isPositional, ArraySubObject) { + FieldRef fieldRefPositional; + fieldRefPositional.parse("a.$.b"); + size_t pos; + size_t count; + ASSERT_TRUE(isPositional(fieldRefPositional, &pos, &count)); + ASSERT_EQUALS(pos, 1u); + ASSERT_EQUALS(count, 1u); + } + + TEST(isPositional, MultiplePositional) { + FieldRef fieldRefPositional; + fieldRefPositional.parse("a.$.b.$.c"); + size_t pos; + size_t count; + ASSERT_TRUE(isPositional(fieldRefPositional, &pos, &count)); + ASSERT_EQUALS(pos, 1u); + ASSERT_EQUALS(count, 2u); + } + +} // unnamed namespace |