diff options
Diffstat (limited to 'src/mongo/bson')
47 files changed, 15426 insertions, 15331 deletions
diff --git a/src/mongo/bson/bson_field.h b/src/mongo/bson/bson_field.h index eea8fa7027d..16f3cff72ab 100644 --- a/src/mongo/bson/bson_field.h +++ b/src/mongo/bson/bson_field.h @@ -33,107 +33,109 @@ namespace mongo { - /** - * A BSONField holds the name and the type intended for a given BSON element. The - * class helps documenting and enforcing that field's type. - * - * Example usages: - * - * In a header file: - * // Determines the types for the fields used in a collection. - * static const std::string MyColl; - * struct MyCollFields { - * static BSONField<std::string> name; - * static BSONField<bool> draining; - * static BSONField<int> count; - * }; - * - * In a cpp file: - * const std::string MyColl = "my_collection_name"; - * - * // determines the names used for the fields - * BSONField<std::string> MyCollFields::name("_id"); - * BSONField<bool> MyCollFields::draining("draining"); - * BSONField<int> MyCollFields::count("count"); - * - * In an insert: - * conn->insert(myColl, - * BSON(MyCollFields::name("id_for_this_doc") << - * MyCollFields::draining(true) << - * MyCollFields::count(0))); - * - * In a query: - * conn->findOne(myColl, BSON(MyCollFields::count.gt(10))) ; - * - * In a command: - * conn->ensureIndex(mycoll, BSON(MyCollFields::draining() << 1), true); - */ - - template<typename T> - class BSONFieldValue { - public: - BSONFieldValue(const std::string& name, const T& t) - : _name(name), _t(t) { } - - const T& value() const { return _t; } - const std::string& name() const { return _name; } - - private: - std::string _name; - T _t; - }; - - template<typename T> - class BSONField { - public: - BSONField(const std::string& name) - : _name(name), _defaultSet(false) {} - - BSONField(const std::string& name, const T& defaultVal) - : _name(name), _default(defaultVal) , _defaultSet(true) {} - - BSONFieldValue<T> make(const T& t) const { - return BSONFieldValue<T>(_name, t); - } - - BSONFieldValue<T> operator()(const T& t) const { - return BSONFieldValue<T>(_name, t); - } - - const std::string& name() const { - return _name; - } - - const T& getDefault() const { - return _default; - } - - bool hasDefault() const { - return _defaultSet; - } - - std::string operator()() const { - return _name; - } - - BSONFieldValue<BSONObj> query(const char * q, const T& t) const; - - BSONFieldValue<BSONObj> gt(const T& t) const { - return query("$gt", t); - } - - BSONFieldValue<BSONObj> lt(const T& t) const { - return query("$lt", t); - } - - BSONFieldValue<BSONObj> ne(const T& t) const { - return query("$ne", t); - } - - private: - std::string _name; - T _default; - bool _defaultSet; - }; - -} // namespace mongo +/** + * A BSONField holds the name and the type intended for a given BSON element. The + * class helps documenting and enforcing that field's type. + * + * Example usages: + * + * In a header file: + * // Determines the types for the fields used in a collection. + * static const std::string MyColl; + * struct MyCollFields { + * static BSONField<std::string> name; + * static BSONField<bool> draining; + * static BSONField<int> count; + * }; + * + * In a cpp file: + * const std::string MyColl = "my_collection_name"; + * + * // determines the names used for the fields + * BSONField<std::string> MyCollFields::name("_id"); + * BSONField<bool> MyCollFields::draining("draining"); + * BSONField<int> MyCollFields::count("count"); + * + * In an insert: + * conn->insert(myColl, + * BSON(MyCollFields::name("id_for_this_doc") << + * MyCollFields::draining(true) << + * MyCollFields::count(0))); + * + * In a query: + * conn->findOne(myColl, BSON(MyCollFields::count.gt(10))) ; + * + * In a command: + * conn->ensureIndex(mycoll, BSON(MyCollFields::draining() << 1), true); + */ + +template <typename T> +class BSONFieldValue { +public: + BSONFieldValue(const std::string& name, const T& t) : _name(name), _t(t) {} + + const T& value() const { + return _t; + } + const std::string& name() const { + return _name; + } + +private: + std::string _name; + T _t; +}; + +template <typename T> +class BSONField { +public: + BSONField(const std::string& name) : _name(name), _defaultSet(false) {} + + BSONField(const std::string& name, const T& defaultVal) + : _name(name), _default(defaultVal), _defaultSet(true) {} + + BSONFieldValue<T> make(const T& t) const { + return BSONFieldValue<T>(_name, t); + } + + BSONFieldValue<T> operator()(const T& t) const { + return BSONFieldValue<T>(_name, t); + } + + const std::string& name() const { + return _name; + } + + const T& getDefault() const { + return _default; + } + + bool hasDefault() const { + return _defaultSet; + } + + std::string operator()() const { + return _name; + } + + BSONFieldValue<BSONObj> query(const char* q, const T& t) const; + + BSONFieldValue<BSONObj> gt(const T& t) const { + return query("$gt", t); + } + + BSONFieldValue<BSONObj> lt(const T& t) const { + return query("$lt", t); + } + + BSONFieldValue<BSONObj> ne(const T& t) const { + return query("$ne", t); + } + +private: + std::string _name; + T _default; + bool _defaultSet; +}; + +} // namespace mongo diff --git a/src/mongo/bson/bson_field_test.cpp b/src/mongo/bson/bson_field_test.cpp index 7e0925470b9..b6fbb0fd565 100644 --- a/src/mongo/bson/bson_field_test.cpp +++ b/src/mongo/bson/bson_field_test.cpp @@ -30,37 +30,37 @@ namespace { - using mongo::BSONField; - using mongo::BSONObj; +using mongo::BSONField; +using mongo::BSONObj; - TEST(Assignment, Simple) { - BSONField<int> x("x"); - BSONObj o = BSON(x << 5); - ASSERT_EQUALS(BSON("x" << 5), o); - } +TEST(Assignment, Simple) { + BSONField<int> x("x"); + BSONObj o = BSON(x << 5); + ASSERT_EQUALS(BSON("x" << 5), o); +} - TEST(Make, Simple) { - BSONField<int> x("x"); - BSONObj o = BSON(x.make(5)); - ASSERT_EQUALS(BSON("x" << 5), o); - } +TEST(Make, Simple) { + BSONField<int> x("x"); + BSONObj o = BSON(x.make(5)); + ASSERT_EQUALS(BSON("x" << 5), o); +} - TEST(Query, GreaterThan) { - BSONField<int> x("x"); - BSONObj o = BSON(x(5)); - ASSERT_EQUALS(BSON("x" << 5), o); +TEST(Query, GreaterThan) { + BSONField<int> x("x"); + BSONObj o = BSON(x(5)); + ASSERT_EQUALS(BSON("x" << 5), o); - o = BSON(x.gt(5)); - ASSERT_EQUALS(BSON("x" << BSON("$gt" << 5)), o); - } + o = BSON(x.gt(5)); + ASSERT_EQUALS(BSON("x" << BSON("$gt" << 5)), o); +} - TEST(Query, NotEqual) { - BSONField<int> x("x"); - BSONObj o = BSON(x(10)); - ASSERT_EQUALS(BSON("x" << 10), o); +TEST(Query, NotEqual) { + BSONField<int> x("x"); + BSONObj o = BSON(x(10)); + ASSERT_EQUALS(BSON("x" << 10), o); - o = BSON(x.ne(5)); - ASSERT_EQUALS(BSON("x" << BSON("$ne" << 5)), o); - } + o = BSON(x.ne(5)); + ASSERT_EQUALS(BSON("x" << BSON("$ne" << 5)), o); +} -} // unnamed namespace +} // unnamed namespace diff --git a/src/mongo/bson/bson_obj_data_type_test.cpp b/src/mongo/bson/bson_obj_data_type_test.cpp index bb99979ac12..cbf5305e45c 100644 --- a/src/mongo/bson/bson_obj_data_type_test.cpp +++ b/src/mongo/bson/bson_obj_data_type_test.cpp @@ -34,35 +34,35 @@ namespace mongo { - TEST(BSONObjDataType, ConstDataTypeRangeBSON) { - char buf[1000] = { 0 }; +TEST(BSONObjDataType, ConstDataTypeRangeBSON) { + char buf[1000] = {0}; - DataRangeCursor drc(buf, buf + sizeof(buf)); + DataRangeCursor drc(buf, buf + sizeof(buf)); - { - BSONObjBuilder b; - b.append("a", 1); + { + BSONObjBuilder b; + b.append("a", 1); - ASSERT_OK(drc.writeAndAdvance(b.obj())); - } - { - BSONObjBuilder b; - b.append("b", "fooo"); + ASSERT_OK(drc.writeAndAdvance(b.obj())); + } + { + BSONObjBuilder b; + b.append("b", "fooo"); - ASSERT_OK(drc.writeAndAdvance(b.obj())); - } - { - BSONObjBuilder b; - b.append("c", 3); + ASSERT_OK(drc.writeAndAdvance(b.obj())); + } + { + BSONObjBuilder b; + b.append("c", 3); - ASSERT_OK(drc.writeAndAdvance(b.obj())); - } + ASSERT_OK(drc.writeAndAdvance(b.obj())); + } - ConstDataRangeCursor cdrc(buf, buf + sizeof(buf)); + ConstDataRangeCursor cdrc(buf, buf + sizeof(buf)); - ASSERT_EQUALS(1, cdrc.readAndAdvance<BSONObj>().getValue().getField("a").numberInt()); - ASSERT_EQUALS("fooo", cdrc.readAndAdvance<BSONObj>().getValue().getField("b").str()); - ASSERT_EQUALS(3, cdrc.readAndAdvance<BSONObj>().getValue().getField("c").numberInt()); - } + ASSERT_EQUALS(1, cdrc.readAndAdvance<BSONObj>().getValue().getField("a").numberInt()); + ASSERT_EQUALS("fooo", cdrc.readAndAdvance<BSONObj>().getValue().getField("b").str()); + ASSERT_EQUALS(3, cdrc.readAndAdvance<BSONObj>().getValue().getField("c").numberInt()); +} -} // namespace mongo +} // namespace mongo diff --git a/src/mongo/bson/bson_obj_test.cpp b/src/mongo/bson/bson_obj_test.cpp index ab676f2af73..6a80deb3609 100644 --- a/src/mongo/bson/bson_obj_test.cpp +++ b/src/mongo/bson/bson_obj_test.cpp @@ -31,271 +31,310 @@ #include "mongo/unittest/unittest.h" namespace { - using namespace mongo; - - TEST(BSONObjToString, EmptyArray) { - const char text[] = "{ x: [] }"; - mongo::BSONObj o1 = mongo::fromjson(text); - const std::string o1_str = o1.toString(); - ASSERT_EQUALS(text, o1_str); - } +using namespace mongo; + +TEST(BSONObjToString, EmptyArray) { + const char text[] = "{ x: [] }"; + mongo::BSONObj o1 = mongo::fromjson(text); + const std::string o1_str = o1.toString(); + ASSERT_EQUALS(text, o1_str); +} + +TEST(BSONObjCompare, NumberDouble) { + ASSERT_LT(BSON("" << 0.0), BSON("" << 1.0)); + ASSERT_LT(BSON("" << -1.0), BSON("" << 0.0)); + ASSERT_LT(BSON("" << -1.0), BSON("" << 1.0)); + + ASSERT_LT(BSON("" << 0.0), BSON("" << 0.1)); + ASSERT_LT(BSON("" << 0.1), BSON("" << 1.0)); + ASSERT_LT(BSON("" << -1.0), BSON("" << -0.1)); + ASSERT_LT(BSON("" << -0.1), BSON("" << 0.0)); + ASSERT_LT(BSON("" << -0.1), BSON("" << 0.1)); + + ASSERT_LT(BSON("" << 0.0), BSON("" << std::numeric_limits<double>::denorm_min())); + ASSERT_GT(BSON("" << 0.0), BSON("" << -std::numeric_limits<double>::denorm_min())); + + ASSERT_LT(BSON("" << 1.0), BSON("" << (1.0 + std::numeric_limits<double>::epsilon()))); + ASSERT_GT(BSON("" << -1.0), BSON("" << (-1.0 - std::numeric_limits<double>::epsilon()))); + + ASSERT_EQ(BSON("" << 0.0), BSON("" << -0.0)); + + ASSERT_GT(BSON("" << std::numeric_limits<double>::infinity()), BSON("" << 0.0)); + ASSERT_GT(BSON("" << std::numeric_limits<double>::infinity()), + BSON("" << std::numeric_limits<double>::max())); // max is finite + ASSERT_GT(BSON("" << std::numeric_limits<double>::infinity()), + BSON("" << -std::numeric_limits<double>::infinity())); + + ASSERT_LT(BSON("" << -std::numeric_limits<double>::infinity()), BSON("" << 0.0)); + ASSERT_LT(BSON("" << -std::numeric_limits<double>::infinity()), + BSON("" << -std::numeric_limits<double>::max())); + ASSERT_LT(BSON("" << -std::numeric_limits<double>::infinity()), + BSON("" << std::numeric_limits<double>::infinity())); + + ASSERT_LT(BSON("" << std::numeric_limits<double>::quiet_NaN()), BSON("" << 0.0)); + ASSERT_LT(BSON("" << std::numeric_limits<double>::quiet_NaN()), + BSON("" << -std::numeric_limits<double>::max())); + ASSERT_LT(BSON("" << std::numeric_limits<double>::quiet_NaN()), + BSON("" << std::numeric_limits<double>::infinity())); + ASSERT_LT(BSON("" << std::numeric_limits<double>::quiet_NaN()), + BSON("" << -std::numeric_limits<double>::infinity())); + + // TODO in C++11 use hex floating point to test distinct NaN representations + ASSERT_EQ(BSON("" << std::numeric_limits<double>::quiet_NaN()), + BSON("" << std::numeric_limits<double>::signaling_NaN())); +} + +TEST(BSONObjCompare, NumberLong_Double) { + ASSERT_EQ(BSON("" << 0ll), BSON("" << 0.0)); + ASSERT_EQ(BSON("" << 0ll), BSON("" << -0.0)); + + ASSERT_EQ(BSON("" << 1ll), BSON("" << 1.0)); + ASSERT_EQ(BSON("" << -1ll), BSON("" << -1.0)); + + ASSERT_LT(BSON("" << 0ll), BSON("" << 1.0)); + ASSERT_LT(BSON("" << -1ll), BSON("" << 0.0)); + ASSERT_LT(BSON("" << -1ll), BSON("" << 1.0)); + + ASSERT_LT(BSON("" << 0ll), BSON("" << 0.1)); + ASSERT_LT(BSON("" << 0.1), BSON("" << 1ll)); + ASSERT_LT(BSON("" << -1ll), BSON("" << -0.1)); + ASSERT_LT(BSON("" << -0.1), BSON("" << 0ll)); + + ASSERT_LT(BSON("" << 0ll), BSON("" << std::numeric_limits<double>::denorm_min())); + ASSERT_GT(BSON("" << 0ll), BSON("" << -std::numeric_limits<double>::denorm_min())); + + ASSERT_LT(BSON("" << 1ll), BSON("" << (1.0 + std::numeric_limits<double>::epsilon()))); + ASSERT_GT(BSON("" << -1ll), BSON("" << (-1.0 - std::numeric_limits<double>::epsilon()))); + + ASSERT_GT(BSON("" << std::numeric_limits<double>::infinity()), BSON("" << 0ll)); + ASSERT_GT(BSON("" << std::numeric_limits<double>::infinity()), + BSON("" << std::numeric_limits<long long>::max())); + ASSERT_GT(BSON("" << std::numeric_limits<double>::infinity()), + BSON("" << std::numeric_limits<long long>::min())); + + ASSERT_LT(BSON("" << -std::numeric_limits<double>::infinity()), BSON("" << 0ll)); + ASSERT_LT(BSON("" << -std::numeric_limits<double>::infinity()), + BSON("" << std::numeric_limits<long long>::max())); + ASSERT_LT(BSON("" << -std::numeric_limits<double>::infinity()), + BSON("" << std::numeric_limits<long long>::min())); + + ASSERT_LT(BSON("" << std::numeric_limits<double>::quiet_NaN()), BSON("" << 0ll)); + ASSERT_LT(BSON("" << std::numeric_limits<double>::quiet_NaN()), + BSON("" << std::numeric_limits<long long>::min())); + + for (int powerOfTwo = 0; powerOfTwo < 63; powerOfTwo++) { + const long long lNum = 1ll << powerOfTwo; + const double dNum = double(lNum); + + // All powers of two in this range can be represented exactly as doubles. + invariant(lNum == static_cast<long long>(dNum)); + + ASSERT_EQ(BSON("" << lNum), BSON("" << dNum)); + ASSERT_EQ(BSON("" << -lNum), BSON("" << -dNum)); + + ASSERT_GT(BSON("" << (lNum + 1)), BSON("" << dNum)); + ASSERT_LT(BSON("" << (lNum - 1)), BSON("" << dNum)); + ASSERT_GT(BSON("" << (-lNum + 1)), BSON("" << -dNum)); + ASSERT_LT(BSON("" << (-lNum - 1)), BSON("" << -dNum)); + + if (powerOfTwo <= 52) { // is dNum - 0.5 representable? + ASSERT_GT(BSON("" << lNum), BSON("" << (dNum - 0.5))); + ASSERT_LT(BSON("" << -lNum), BSON("" << -(dNum - 0.5))); + } - TEST(BSONObjCompare, NumberDouble) { - ASSERT_LT(BSON("" << 0.0), BSON("" << 1.0)); - ASSERT_LT(BSON("" << -1.0), BSON("" << 0.0)); - ASSERT_LT(BSON("" << -1.0), BSON("" << 1.0)); - - ASSERT_LT(BSON("" << 0.0), BSON("" << 0.1)); - ASSERT_LT(BSON("" << 0.1), BSON("" << 1.0)); - ASSERT_LT(BSON("" << -1.0), BSON("" << -0.1)); - ASSERT_LT(BSON("" << -0.1), BSON("" << 0.0)); - ASSERT_LT(BSON("" << -0.1), BSON("" << 0.1)); - - ASSERT_LT(BSON("" << 0.0), BSON("" << std::numeric_limits<double>::denorm_min())); - ASSERT_GT(BSON("" << 0.0), BSON("" << -std::numeric_limits<double>::denorm_min())); - - ASSERT_LT(BSON("" << 1.0), BSON("" << (1.0 + std::numeric_limits<double>::epsilon()))); - ASSERT_GT(BSON("" << -1.0), BSON("" << (-1.0 - std::numeric_limits<double>::epsilon()))); - - ASSERT_EQ(BSON("" << 0.0), BSON("" << -0.0)); - - ASSERT_GT(BSON("" << std::numeric_limits<double>::infinity()), BSON("" << 0.0)); - ASSERT_GT(BSON("" << std::numeric_limits<double>::infinity()), - BSON("" << std::numeric_limits<double>::max())); // max is finite - ASSERT_GT(BSON("" << std::numeric_limits<double>::infinity()), - BSON("" << -std::numeric_limits<double>::infinity())); - - ASSERT_LT(BSON("" << -std::numeric_limits<double>::infinity()), BSON("" << 0.0)); - ASSERT_LT(BSON("" << -std::numeric_limits<double>::infinity()), - BSON("" << -std::numeric_limits<double>::max())); - ASSERT_LT(BSON("" << -std::numeric_limits<double>::infinity()), - BSON("" << std::numeric_limits<double>::infinity())); - - ASSERT_LT(BSON("" << std::numeric_limits<double>::quiet_NaN()), BSON("" << 0.0)); - ASSERT_LT(BSON("" << std::numeric_limits<double>::quiet_NaN()), - BSON("" << -std::numeric_limits<double>::max())); - ASSERT_LT(BSON("" << std::numeric_limits<double>::quiet_NaN()), - BSON("" << std::numeric_limits<double>::infinity())); - ASSERT_LT(BSON("" << std::numeric_limits<double>::quiet_NaN()), - BSON("" << -std::numeric_limits<double>::infinity())); - - // TODO in C++11 use hex floating point to test distinct NaN representations - ASSERT_EQ(BSON("" << std::numeric_limits<double>::quiet_NaN()), - BSON("" << std::numeric_limits<double>::signaling_NaN())); + if (powerOfTwo <= 51) { // is dNum + 0.5 representable? + ASSERT_LT(BSON("" << lNum), BSON("" << (dNum + 0.5))); + ASSERT_GT(BSON("" << -lNum), BSON("" << -(dNum + 0.5))); + } } - TEST(BSONObjCompare, NumberLong_Double) { - ASSERT_EQ(BSON("" << 0ll), BSON("" << 0.0)); - ASSERT_EQ(BSON("" << 0ll), BSON("" << -0.0)); - - ASSERT_EQ(BSON("" << 1ll), BSON("" << 1.0)); - ASSERT_EQ(BSON("" << -1ll), BSON("" << -1.0)); - - ASSERT_LT(BSON("" << 0ll), BSON("" << 1.0)); - ASSERT_LT(BSON("" << -1ll), BSON("" << 0.0)); - ASSERT_LT(BSON("" << -1ll), BSON("" << 1.0)); + { + // Numbers around +/- numeric_limits<long long>::max() which can't be represented + // precisely as a double. + const long long maxLL = std::numeric_limits<long long>::max(); + const double closestAbove = 9223372036854775808.0; // 2**63 + const double closestBelow = 9223372036854774784.0; // 2**63 - epsilon - ASSERT_LT(BSON("" << 0ll), BSON("" << 0.1)); - ASSERT_LT(BSON("" << 0.1), BSON("" << 1ll)); - ASSERT_LT(BSON("" << -1ll), BSON("" << -0.1)); - ASSERT_LT(BSON("" << -0.1), BSON("" << 0ll)); + ASSERT_GT(BSON("" << maxLL), BSON("" << (maxLL - 1))); + ASSERT_LT(BSON("" << maxLL), BSON("" << closestAbove)); + ASSERT_GT(BSON("" << maxLL), BSON("" << closestBelow)); - ASSERT_LT(BSON("" << 0ll), BSON("" << std::numeric_limits<double>::denorm_min())); - ASSERT_GT(BSON("" << 0ll), BSON("" << -std::numeric_limits<double>::denorm_min())); - - ASSERT_LT(BSON("" << 1ll), BSON("" << (1.0 + std::numeric_limits<double>::epsilon()))); - ASSERT_GT(BSON("" << -1ll), BSON("" << (-1.0 - std::numeric_limits<double>::epsilon()))); + ASSERT_LT(BSON("" << -maxLL), BSON("" << -(maxLL - 1))); + ASSERT_GT(BSON("" << -maxLL), BSON("" << -closestAbove)); + ASSERT_LT(BSON("" << -maxLL), BSON("" << -closestBelow)); + } - ASSERT_GT(BSON("" << std::numeric_limits<double>::infinity()), BSON("" << 0ll)); - ASSERT_GT(BSON("" << std::numeric_limits<double>::infinity()), - BSON("" << std::numeric_limits<long long>::max())); - ASSERT_GT(BSON("" << std::numeric_limits<double>::infinity()), - BSON("" << std::numeric_limits<long long>::min())); + { + // Numbers around numeric_limits<long long>::min() which can be represented precisely as + // a double, but not as a positive long long. + const long long minLL = std::numeric_limits<long long>::min(); + const double closestBelow = -9223372036854777856.0; // -2**63 - epsilon + const double equal = -9223372036854775808.0; // 2**63 + const double closestAbove = -9223372036854774784.0; // -2**63 + epsilon - ASSERT_LT(BSON("" << -std::numeric_limits<double>::infinity()), BSON("" << 0ll)); - ASSERT_LT(BSON("" << -std::numeric_limits<double>::infinity()), - BSON("" << std::numeric_limits<long long>::max())); - ASSERT_LT(BSON("" << -std::numeric_limits<double>::infinity()), - BSON("" << std::numeric_limits<long long>::min())); + invariant(static_cast<double>(minLL) == equal); + invariant(static_cast<long long>(equal) == minLL); - ASSERT_LT(BSON("" << std::numeric_limits<double>::quiet_NaN()), BSON("" << 0ll)); - ASSERT_LT(BSON("" << std::numeric_limits<double>::quiet_NaN()), - BSON("" << std::numeric_limits<long long>::min())); + ASSERT_LT(BSON("" << minLL), BSON("" << (minLL + 1))); - for (int powerOfTwo = 0; powerOfTwo < 63; powerOfTwo++) { - const long long lNum = 1ll << powerOfTwo; - const double dNum = double(lNum); + ASSERT_EQ(BSON("" << minLL), BSON("" << equal)); + ASSERT_LT(BSON("" << minLL), BSON("" << closestAbove)); + ASSERT_GT(BSON("" << minLL), BSON("" << closestBelow)); + } +} - // All powers of two in this range can be represented exactly as doubles. - invariant(lNum == static_cast<long long>(dNum)); +TEST(BSONObjCompare, StringSymbol) { + BSONObj l, r; + { + BSONObjBuilder b; + b.append("x", "eliot"); + l = b.obj(); + } + { + BSONObjBuilder b; + b.appendSymbol("x", "eliot"); + r = b.obj(); + } - ASSERT_EQ(BSON("" << lNum), BSON("" << dNum)); - ASSERT_EQ(BSON("" << -lNum), BSON("" << -dNum)); + ASSERT_EQ(l.woCompare(r), 0); + ASSERT_EQ(r.woCompare(l), 0); +} - ASSERT_GT(BSON("" << (lNum + 1)), BSON("" << dNum)); - ASSERT_LT(BSON("" << (lNum - 1)), BSON("" << dNum)); - ASSERT_GT(BSON("" << (-lNum + 1)), BSON("" << -dNum)); - ASSERT_LT(BSON("" << (-lNum - 1)), BSON("" << -dNum)); +TEST(BSONObjCompare, StringOrder1) { + BSONObjBuilder b; + b.appendRegex("x", "foo"); + BSONObj o = b.done(); - if (powerOfTwo <= 52) { // is dNum - 0.5 representable? - ASSERT_GT(BSON("" << lNum), BSON("" << (dNum - 0.5))); - ASSERT_LT(BSON("" << -lNum), BSON("" << -(dNum - 0.5))); - } + BSONObjBuilder c; + c.appendRegex("x", "goo"); + BSONObj p = c.done(); - if (powerOfTwo <= 51) { // is dNum + 0.5 representable? - ASSERT_LT(BSON("" << lNum), BSON("" << (dNum + 0.5))); - ASSERT_GT(BSON("" << -lNum), BSON("" << -(dNum + 0.5))); - } - } + ASSERT(!o.binaryEqual(p)); + ASSERT_LT(o.woCompare(p), 0); +} - { - // Numbers around +/- numeric_limits<long long>::max() which can't be represented - // precisely as a double. - const long long maxLL = std::numeric_limits<long long>::max(); - const double closestAbove = 9223372036854775808.0; // 2**63 - const double closestBelow = 9223372036854774784.0; // 2**63 - epsilon +TEST(BSONObjCompare, StringOrder2) { + BSONObj x, y, z; + { + BSONObjBuilder b; + b.append("x", (long long)2); + x = b.obj(); + } + { + BSONObjBuilder b; + b.append("x", (int)3); + y = b.obj(); + } + { + BSONObjBuilder b; + b.append("x", (long long)4); + z = b.obj(); + } - ASSERT_GT(BSON("" << maxLL), BSON("" << (maxLL - 1))); - ASSERT_LT(BSON("" << maxLL), BSON("" << closestAbove)); - ASSERT_GT(BSON("" << maxLL), BSON("" << closestBelow)); + ASSERT_LT(x.woCompare(y), 0); + ASSERT_LT(x.woCompare(z), 0); + ASSERT_GT(y.woCompare(x), 0); + ASSERT_GT(z.woCompare(x), 0); + ASSERT_LT(y.woCompare(z), 0); + ASSERT_GT(z.woCompare(y), 0); +} + +TEST(BSONObjCompare, StringOrder3) { + BSONObj ll, d, i, n, u; + { + BSONObjBuilder b; + b.append("x", (long long)2); + ll = b.obj(); + } + { + BSONObjBuilder b; + b.append("x", (double)2); + d = b.obj(); + } + { + BSONObjBuilder b; + b.append("x", (int)2); + i = b.obj(); + } + { + BSONObjBuilder b; + b.appendNull("x"); + n = b.obj(); + } + { + BSONObjBuilder b; + u = b.obj(); + } - ASSERT_LT(BSON("" << -maxLL), BSON("" << -(maxLL - 1))); - ASSERT_GT(BSON("" << -maxLL), BSON("" << -closestAbove)); - ASSERT_LT(BSON("" << -maxLL), BSON("" << -closestBelow)); - } + ASSERT_TRUE(ll.woCompare(u) == d.woCompare(u)); + ASSERT_TRUE(ll.woCompare(u) == i.woCompare(u)); - { - // Numbers around numeric_limits<long long>::min() which can be represented precisely as - // a double, but not as a positive long long. - const long long minLL = std::numeric_limits<long long>::min(); - const double closestBelow = -9223372036854777856.0; // -2**63 - epsilon - const double equal = -9223372036854775808.0; // 2**63 - const double closestAbove = -9223372036854774784.0; // -2**63 + epsilon + BSONObj k = BSON("x" << 1); + ASSERT_TRUE(ll.woCompare(u, k) == d.woCompare(u, k)); + ASSERT_TRUE(ll.woCompare(u, k) == i.woCompare(u, k)); - invariant(static_cast<double>(minLL) == equal); - invariant(static_cast<long long>(equal) == minLL); + ASSERT_TRUE(u.woCompare(ll) == u.woCompare(d)); + ASSERT_TRUE(u.woCompare(ll) == u.woCompare(i)); + ASSERT_TRUE(u.woCompare(ll, k) == u.woCompare(d, k)); + ASSERT_TRUE(u.woCompare(ll, k) == u.woCompare(d, k)); - ASSERT_LT(BSON("" << minLL), BSON("" << (minLL + 1))); + ASSERT_TRUE(i.woCompare(n) == d.woCompare(n)); - ASSERT_EQ(BSON("" << minLL), BSON("" << equal)); - ASSERT_LT(BSON("" << minLL), BSON("" << closestAbove)); - ASSERT_GT(BSON("" << minLL), BSON("" << closestBelow)); - } - } + ASSERT_TRUE(ll.woCompare(n) == d.woCompare(n)); + ASSERT_TRUE(ll.woCompare(n) == i.woCompare(n)); + ASSERT_TRUE(ll.woCompare(n, k) == d.woCompare(n, k)); + ASSERT_TRUE(ll.woCompare(n, k) == i.woCompare(n, k)); - TEST(BSONObjCompare, StringSymbol) { - BSONObj l, r; - { BSONObjBuilder b; b.append("x", "eliot"); l = b.obj(); } - { BSONObjBuilder b; b.appendSymbol("x", "eliot"); r = b.obj(); } + ASSERT_TRUE(n.woCompare(ll) == n.woCompare(d)); + ASSERT_TRUE(n.woCompare(ll) == n.woCompare(i)); + ASSERT_TRUE(n.woCompare(ll, k) == n.woCompare(d, k)); + ASSERT_TRUE(n.woCompare(ll, k) == n.woCompare(d, k)); +} - ASSERT_EQ(l.woCompare(r), 0); - ASSERT_EQ(r.woCompare(l), 0); +TEST(BSONObjCompare, NumericBounds) { + BSONObj l, r; + { + BSONObjBuilder b; + b.append("x", std::numeric_limits<long long>::max()); + l = b.obj(); } - - TEST(BSONObjCompare, StringOrder1) { + { BSONObjBuilder b; - b.appendRegex("x", "foo"); - BSONObj o = b.done(); - - BSONObjBuilder c; - c.appendRegex("x", "goo"); - BSONObj p = c.done(); - - ASSERT(!o.binaryEqual(p)); - ASSERT_LT(o.woCompare(p), 0); + b.append("x", std::numeric_limits<double>::max()); + r = b.obj(); } - TEST(BSONObjCompare, StringOrder2) { - BSONObj x, y, z; - { BSONObjBuilder b; b.append("x", (long long)2); x = b.obj(); } - { BSONObjBuilder b; b.append("x", (int)3); y = b.obj(); } - { BSONObjBuilder b; b.append("x", (long long)4); z = b.obj(); } - - ASSERT_LT(x.woCompare(y), 0); - ASSERT_LT(x.woCompare(z), 0); - ASSERT_GT(y.woCompare(x), 0); - ASSERT_GT(z.woCompare(x), 0); - ASSERT_LT(y.woCompare(z), 0); - ASSERT_GT(z.woCompare(y), 0); - } + ASSERT_LT(l.woCompare(r), 0); + ASSERT_GT(r.woCompare(l), 0); - TEST(BSONObjCompare, StringOrder3) { - BSONObj ll,d,i,n,u; - { BSONObjBuilder b; b.append( "x" , (long long)2 ); ll = b.obj(); } - { BSONObjBuilder b; b.append( "x" , (double)2 ); d = b.obj(); } - { BSONObjBuilder b; b.append( "x" , (int)2 ); i = b.obj(); } - { BSONObjBuilder b; b.appendNull( "x" ); n = b.obj(); } - { BSONObjBuilder b; u = b.obj(); } - - ASSERT_TRUE( ll.woCompare( u ) == d.woCompare( u ) ); - ASSERT_TRUE( ll.woCompare( u ) == i.woCompare( u ) ); - - BSONObj k = BSON( "x" << 1 ); - ASSERT_TRUE( ll.woCompare( u , k ) == d.woCompare( u , k ) ); - ASSERT_TRUE( ll.woCompare( u , k ) == i.woCompare( u , k ) ); - - ASSERT_TRUE( u.woCompare( ll ) == u.woCompare( d ) ); - ASSERT_TRUE( u.woCompare( ll ) == u.woCompare( i ) ); - ASSERT_TRUE( u.woCompare( ll , k ) == u.woCompare( d , k ) ); - ASSERT_TRUE( u.woCompare( ll , k ) == u.woCompare( d , k ) ); - - ASSERT_TRUE( i.woCompare( n ) == d.woCompare( n ) ); - - ASSERT_TRUE( ll.woCompare( n ) == d.woCompare( n ) ); - ASSERT_TRUE( ll.woCompare( n ) == i.woCompare( n ) ); - ASSERT_TRUE( ll.woCompare( n , k ) == d.woCompare( n , k ) ); - ASSERT_TRUE( ll.woCompare( n , k ) == i.woCompare( n , k ) ); - - ASSERT_TRUE( n.woCompare( ll ) == n.woCompare( d ) ); - ASSERT_TRUE( n.woCompare( ll ) == n.woCompare( i ) ); - ASSERT_TRUE( n.woCompare( ll , k ) == n.woCompare( d , k ) ); - ASSERT_TRUE( n.woCompare( ll , k ) == n.woCompare( d , k ) ); + { + BSONObjBuilder b; + b.append("x", std::numeric_limits<int>::max()); + l = b.obj(); } - TEST(BSONObjCompare, NumericBounds) { - BSONObj l, r; - { - BSONObjBuilder b; - b.append("x", std::numeric_limits<long long>::max()); - l = b.obj(); - } - { - BSONObjBuilder b; - b.append("x", std::numeric_limits<double>::max()); - r = b.obj(); - } - - ASSERT_LT(l.woCompare(r), 0); - ASSERT_GT(r.woCompare(l), 0); + ASSERT_LT(l.woCompare(r), 0); + ASSERT_GT(r.woCompare(l), 0); +} - { - BSONObjBuilder b; - b.append("x", std::numeric_limits<int>::max()); - l = b.obj(); - } - - ASSERT_LT(l.woCompare(r), 0); - ASSERT_GT(r.woCompare(l), 0); +TEST(Looping, Cpp11Basic) { + int count = 0; + for (BSONElement e : BSON("a" << 1 << "a" << 2 << "a" << 3)) { + ASSERT_EQUALS(e.fieldNameStringData(), "a"); + count += e.Int(); } - TEST(Looping, Cpp11Basic) { - int count = 0; - for (BSONElement e : BSON("a" << 1 << "a" << 2 << "a" << 3)) { - ASSERT_EQUALS( e.fieldNameStringData() , "a" ); - count += e.Int(); - } + ASSERT_EQUALS(count, 1 + 2 + 3); +} - ASSERT_EQUALS( count , 1 + 2 + 3 ); +TEST(Looping, Cpp11Auto) { + int count = 0; + for (auto e : BSON("a" << 1 << "a" << 2 << "a" << 3)) { + ASSERT_EQUALS(e.fieldNameStringData(), "a"); + count += e.Int(); } - TEST(Looping, Cpp11Auto) { - int count = 0; - for (auto e : BSON("a" << 1 << "a" << 2 << "a" << 3)) { - ASSERT_EQUALS( e.fieldNameStringData() , "a" ); - count += e.Int(); - } - - ASSERT_EQUALS( count , 1 + 2 + 3 ); - } + ASSERT_EQUALS(count, 1 + 2 + 3); +} -} // unnamed namespace +} // unnamed namespace diff --git a/src/mongo/bson/bson_validate.cpp b/src/mongo/bson/bson_validate.cpp index 428447d989a..1e3727be65c 100644 --- a/src/mongo/bson/bson_validate.cpp +++ b/src/mongo/bson/bson_validate.cpp @@ -38,349 +38,341 @@ namespace mongo { - namespace { - - /** - * Creates a status with InvalidBSON code and adds information about _id if available. - * WARNING: only pass in a non-EOO idElem if it has been fully validated already! - */ - Status makeError(std::string baseMsg, BSONElement idElem) { - if (idElem.eoo()) { - baseMsg += " in object with unknown _id"; - } - else { - baseMsg += " in object with " + idElem.toString(/*field name=*/true, /*full=*/true); - } - return Status(ErrorCodes::InvalidBSON, baseMsg); +namespace { + +/** + * Creates a status with InvalidBSON code and adds information about _id if available. + * WARNING: only pass in a non-EOO idElem if it has been fully validated already! + */ +Status makeError(std::string baseMsg, BSONElement idElem) { + if (idElem.eoo()) { + baseMsg += " in object with unknown _id"; + } else { + baseMsg += " in object with " + idElem.toString(/*field name=*/true, /*full=*/true); + } + return Status(ErrorCodes::InvalidBSON, baseMsg); +} + +class Buffer { +public: + Buffer(const char* buffer, uint64_t maxLength) + : _buffer(buffer), _position(0), _maxLength(maxLength) {} + + template <typename N> + bool readNumber(N* out) { + if ((_position + sizeof(N)) > _maxLength) + return false; + if (out) { + *out = ConstDataView(_buffer).read<LittleEndian<N>>(_position); } + _position += sizeof(N); + return true; + } - class Buffer { - public: - Buffer( const char* buffer, uint64_t maxLength ) - : _buffer( buffer ), _position( 0 ), _maxLength( maxLength ) { - } + Status readCString(StringData* out) { + const void* x = memchr(_buffer + _position, 0, _maxLength - _position); + if (!x) + return makeError("no end of c-string", _idElem); + uint64_t len = static_cast<uint64_t>(static_cast<const char*>(x) - (_buffer + _position)); - template<typename N> - bool readNumber( N* out ) { - if ( ( _position + sizeof(N) ) > _maxLength ) - return false; - if ( out ) { - *out = ConstDataView(_buffer).read<LittleEndian<N>>(_position); - } - _position += sizeof(N); - return true; - } + StringData data(_buffer + _position, len); + _position += len + 1; - Status readCString( StringData* out ) { - const void* x = memchr( _buffer + _position, 0, _maxLength - _position ); - if ( !x ) - return makeError("no end of c-string", _idElem); - uint64_t len = static_cast<uint64_t>( static_cast<const char*>(x) - ( _buffer + _position ) ); + if (out) { + *out = data; + } + return Status::OK(); + } - StringData data( _buffer + _position, len ); - _position += len + 1; + Status readUTF8String(StringData* out) { + int sz; + if (!readNumber<int>(&sz)) + return makeError("invalid bson", _idElem); - if ( out ) { - *out = data; - } - return Status::OK(); - } + if (sz <= 0) { + // must have NULL at the very least + return makeError("invalid bson", _idElem); + } - Status readUTF8String( StringData* out ) { - int sz; - if ( !readNumber<int>( &sz ) ) - return makeError("invalid bson", _idElem); + if (out) { + *out = StringData(_buffer + _position, sz); + } - if ( sz <= 0 ) { - // must have NULL at the very least - return makeError("invalid bson", _idElem); - } + if (!skip(sz - 1)) + return makeError("invalid bson", _idElem); - if ( out ) { - *out = StringData( _buffer + _position, sz ); - } + char c; + if (!readNumber<char>(&c)) + return makeError("invalid bson", _idElem); - if ( !skip( sz - 1 ) ) - return makeError("invalid bson", _idElem); + if (c != 0) + return makeError("not null terminated string", _idElem); - char c; - if ( !readNumber<char>( &c ) ) - return makeError("invalid bson", _idElem); + return Status::OK(); + } - if ( c != 0 ) - return makeError("not null terminated string", _idElem); + bool skip(uint64_t sz) { + _position += sz; + return _position < _maxLength; + } - return Status::OK(); - } + uint64_t position() const { + return _position; + } - bool skip( uint64_t sz ) { - _position += sz; - return _position < _maxLength; - } + const char* getBasePtr() const { + return _buffer; + } - uint64_t position() const { - return _position; - } + /** + * WARNING: only pass in a non-EOO idElem if it has been fully validated already! + */ + void setIdElem(BSONElement idElem) { + _idElem = idElem; + } - const char* getBasePtr() const { - return _buffer; - } +private: + const char* _buffer; + uint64_t _position; + uint64_t _maxLength; + BSONElement _idElem; +}; + +struct ValidationState { + enum State { BeginObj = 1, WithinObj, EndObj, BeginCodeWScope, EndCodeWScope, Done }; +}; + +class ValidationObjectFrame { +public: + int startPosition() const { + return _startPosition & ~(1 << 31); + } + bool isCodeWithScope() const { + return _startPosition & (1 << 31); + } - /** - * WARNING: only pass in a non-EOO idElem if it has been fully validated already! - */ - void setIdElem(BSONElement idElem) { - _idElem = idElem; - } + void setStartPosition(int pos) { + _startPosition = (_startPosition & (1 << 31)) | (pos & ~(1 << 31)); + } + void setIsCodeWithScope(bool isCodeWithScope) { + if (isCodeWithScope) { + _startPosition |= 1 << 31; + } else { + _startPosition &= ~(1 << 31); + } + } - private: - const char* _buffer; - uint64_t _position; - uint64_t _maxLength; - BSONElement _idElem; - }; - - struct ValidationState { - enum State { - BeginObj = 1, - WithinObj, - EndObj, - BeginCodeWScope, - EndCodeWScope, - Done - }; - }; - - class ValidationObjectFrame { - public: - int startPosition() const { return _startPosition & ~(1 << 31); } - bool isCodeWithScope() const { return _startPosition & (1 << 31); } - - void setStartPosition(int pos) { - _startPosition = (_startPosition & (1 << 31)) | (pos & ~(1 << 31)); - } - void setIsCodeWithScope(bool isCodeWithScope) { - if (isCodeWithScope) { - _startPosition |= 1 << 31; - } - else { - _startPosition &= ~(1 << 31); - } - } + int expectedSize; - int expectedSize; - private: - int _startPosition; - }; - - /** - * WARNING: only pass in a non-EOO idElem if it has been fully validated already! - */ - Status validateElementInfo(Buffer* buffer, - ValidationState::State* nextState, - BSONElement idElem) { - Status status = Status::OK(); - - signed char type; - if ( !buffer->readNumber<signed char>(&type) ) - return makeError("invalid bson", idElem); +private: + int _startPosition; +}; - if ( type == EOO ) { - *nextState = ValidationState::EndObj; - return Status::OK(); - } +/** + * WARNING: only pass in a non-EOO idElem if it has been fully validated already! + */ +Status validateElementInfo(Buffer* buffer, ValidationState::State* nextState, BSONElement idElem) { + Status status = Status::OK(); - StringData name; - status = buffer->readCString( &name ); - if ( !status.isOK() ) - return status; + signed char type; + if (!buffer->readNumber<signed char>(&type)) + return makeError("invalid bson", idElem); - switch ( type ) { - case MinKey: - case MaxKey: - case jstNULL: - case Undefined: - return Status::OK(); - - case jstOID: - if ( !buffer->skip( OID::kOIDSize ) ) - return makeError("invalid bson", idElem); - return Status::OK(); - - case NumberInt: - if ( !buffer->skip( sizeof(int32_t) ) ) - return makeError("invalid bson", idElem); - return Status::OK(); - - case Bool: - if ( !buffer->skip( sizeof(int8_t) ) ) - return makeError("invalid bson", idElem); - return Status::OK(); - - - case NumberDouble: - case NumberLong: - case bsonTimestamp: - case Date: - if ( !buffer->skip( sizeof(int64_t) ) ) - return makeError("invalid bson", idElem); - return Status::OK(); - - case DBRef: - status = buffer->readUTF8String( NULL ); - if ( !status.isOK() ) - return status; - buffer->skip( OID::kOIDSize ); - return Status::OK(); + if (type == EOO) { + *nextState = ValidationState::EndObj; + return Status::OK(); + } - case RegEx: - status = buffer->readCString( NULL ); - if ( !status.isOK() ) - return status; - status = buffer->readCString( NULL ); - if ( !status.isOK() ) - return status; + StringData name; + status = buffer->readCString(&name); + if (!status.isOK()) + return status; - return Status::OK(); + switch (type) { + case MinKey: + case MaxKey: + case jstNULL: + case Undefined: + return Status::OK(); - case Code: - case Symbol: - case String: - status = buffer->readUTF8String( NULL ); - if ( !status.isOK() ) - return status; - return Status::OK(); - - case BinData: { - int sz; - if ( !buffer->readNumber<int>( &sz ) ) - return makeError("invalid bson", idElem); - if ( sz < 0 || sz == std::numeric_limits<int>::max() ) - return makeError("invalid size in bson", idElem); - if ( !buffer->skip( 1 + sz ) ) - return makeError("invalid bson", idElem); - return Status::OK(); - } - case CodeWScope: - *nextState = ValidationState::BeginCodeWScope; - return Status::OK(); - case Object: - case Array: - *nextState = ValidationState::BeginObj; - return Status::OK(); - - default: - return makeError("invalid bson type", idElem); - } - } + case jstOID: + if (!buffer->skip(OID::kOIDSize)) + return makeError("invalid bson", idElem); + return Status::OK(); - Status validateBSONIterative(Buffer* buffer) { - std::deque<ValidationObjectFrame> frames; - ValidationObjectFrame* curr = NULL; - ValidationState::State state = ValidationState::BeginObj; + case NumberInt: + if (!buffer->skip(sizeof(int32_t))) + return makeError("invalid bson", idElem); + return Status::OK(); - uint64_t idElemStartPos = 0; // will become idElem once validated - BSONElement idElem; + case Bool: + if (!buffer->skip(sizeof(int8_t))) + return makeError("invalid bson", idElem); + return Status::OK(); - while (state != ValidationState::Done) { - switch (state) { - case ValidationState::BeginObj: - frames.push_back(ValidationObjectFrame()); - curr = &frames.back(); - curr->setStartPosition(buffer->position()); - curr->setIsCodeWithScope(false); - if (!buffer->readNumber<int>(&curr->expectedSize)) { - return makeError("bson size is larger than buffer size", idElem); - } - state = ValidationState::WithinObj; - // fall through - case ValidationState::WithinObj: { - const bool atTopLevel = frames.size() == 1; - // check if we've finished validating idElem and are at start of next element. - if (atTopLevel && idElemStartPos) { - idElem = BSONElement(buffer->getBasePtr() + idElemStartPos); - buffer->setIdElem(idElem); - idElemStartPos = 0; - } - const uint64_t elemStartPos = buffer->position(); - ValidationState::State nextState = state; - Status status = validateElementInfo(buffer, &nextState, idElem); - if (!status.isOK()) - return status; - - // we've already validated that fieldname is safe to access as long as we aren't - // at the end of the object, since EOO doesn't have a fieldname. - if (nextState != ValidationState::EndObj && idElem.eoo() && atTopLevel) { - if (strcmp(buffer->getBasePtr() + elemStartPos + 1/*type*/, "_id") == 0) { - idElemStartPos = elemStartPos; - } - } + case NumberDouble: + case NumberLong: + case bsonTimestamp: + case Date: + if (!buffer->skip(sizeof(int64_t))) + return makeError("invalid bson", idElem); + return Status::OK(); - state = nextState; - break; + case DBRef: + status = buffer->readUTF8String(NULL); + if (!status.isOK()) + return status; + buffer->skip(OID::kOIDSize); + return Status::OK(); + + case RegEx: + status = buffer->readCString(NULL); + if (!status.isOK()) + return status; + status = buffer->readCString(NULL); + if (!status.isOK()) + return status; + + return Status::OK(); + + case Code: + case Symbol: + case String: + status = buffer->readUTF8String(NULL); + if (!status.isOK()) + return status; + return Status::OK(); + + case BinData: { + int sz; + if (!buffer->readNumber<int>(&sz)) + return makeError("invalid bson", idElem); + if (sz < 0 || sz == std::numeric_limits<int>::max()) + return makeError("invalid size in bson", idElem); + if (!buffer->skip(1 + sz)) + return makeError("invalid bson", idElem); + return Status::OK(); + } + case CodeWScope: + *nextState = ValidationState::BeginCodeWScope; + return Status::OK(); + case Object: + case Array: + *nextState = ValidationState::BeginObj; + return Status::OK(); + + default: + return makeError("invalid bson type", idElem); + } +} + +Status validateBSONIterative(Buffer* buffer) { + std::deque<ValidationObjectFrame> frames; + ValidationObjectFrame* curr = NULL; + ValidationState::State state = ValidationState::BeginObj; + + uint64_t idElemStartPos = 0; // will become idElem once validated + BSONElement idElem; + + while (state != ValidationState::Done) { + switch (state) { + case ValidationState::BeginObj: + frames.push_back(ValidationObjectFrame()); + curr = &frames.back(); + curr->setStartPosition(buffer->position()); + curr->setIsCodeWithScope(false); + if (!buffer->readNumber<int>(&curr->expectedSize)) { + return makeError("bson size is larger than buffer size", idElem); } - case ValidationState::EndObj: { - int actualLength = buffer->position() - curr->startPosition(); - if ( actualLength != curr->expectedSize ) { - return makeError("bson length doesn't match what we found", idElem); - } - frames.pop_back(); - if (frames.empty()) { - state = ValidationState::Done; - } - else { - curr = &frames.back(); - if (curr->isCodeWithScope()) - state = ValidationState::EndCodeWScope; - else - state = ValidationState::WithinObj; + state = ValidationState::WithinObj; + // fall through + case ValidationState::WithinObj: { + const bool atTopLevel = frames.size() == 1; + // check if we've finished validating idElem and are at start of next element. + if (atTopLevel && idElemStartPos) { + idElem = BSONElement(buffer->getBasePtr() + idElemStartPos); + buffer->setIdElem(idElem); + idElemStartPos = 0; + } + + const uint64_t elemStartPos = buffer->position(); + ValidationState::State nextState = state; + Status status = validateElementInfo(buffer, &nextState, idElem); + if (!status.isOK()) + return status; + + // we've already validated that fieldname is safe to access as long as we aren't + // at the end of the object, since EOO doesn't have a fieldname. + if (nextState != ValidationState::EndObj && idElem.eoo() && atTopLevel) { + if (strcmp(buffer->getBasePtr() + elemStartPos + 1 /*type*/, "_id") == 0) { + idElemStartPos = elemStartPos; } - break; } - case ValidationState::BeginCodeWScope: { - frames.push_back(ValidationObjectFrame()); - curr = &frames.back(); - curr->setStartPosition(buffer->position()); - curr->setIsCodeWithScope(true); - if ( !buffer->readNumber<int>( &curr->expectedSize ) ) - return makeError("invalid bson CodeWScope size", idElem); - Status status = buffer->readUTF8String( NULL ); - if ( !status.isOK() ) - return status; - state = ValidationState::BeginObj; - break; + + state = nextState; + break; + } + case ValidationState::EndObj: { + int actualLength = buffer->position() - curr->startPosition(); + if (actualLength != curr->expectedSize) { + return makeError("bson length doesn't match what we found", idElem); } - case ValidationState::EndCodeWScope: { - int actualLength = buffer->position() - curr->startPosition(); - if ( actualLength != curr->expectedSize ) { - return makeError("bson length for CodeWScope doesn't match what we found", - idElem); - } - frames.pop_back(); - if (frames.empty()) - return makeError("unnested CodeWScope", idElem); + frames.pop_back(); + if (frames.empty()) { + state = ValidationState::Done; + } else { curr = &frames.back(); - state = ValidationState::WithinObj; - break; + if (curr->isCodeWithScope()) + state = ValidationState::EndCodeWScope; + else + state = ValidationState::WithinObj; } - case ValidationState::Done: - break; + break; + } + case ValidationState::BeginCodeWScope: { + frames.push_back(ValidationObjectFrame()); + curr = &frames.back(); + curr->setStartPosition(buffer->position()); + curr->setIsCodeWithScope(true); + if (!buffer->readNumber<int>(&curr->expectedSize)) + return makeError("invalid bson CodeWScope size", idElem); + Status status = buffer->readUTF8String(NULL); + if (!status.isOK()) + return status; + state = ValidationState::BeginObj; + break; + } + case ValidationState::EndCodeWScope: { + int actualLength = buffer->position() - curr->startPosition(); + if (actualLength != curr->expectedSize) { + return makeError("bson length for CodeWScope doesn't match what we found", + idElem); } + frames.pop_back(); + if (frames.empty()) + return makeError("unnested CodeWScope", idElem); + curr = &frames.back(); + state = ValidationState::WithinObj; + break; } - - return Status::OK(); + case ValidationState::Done: + break; } + } - } // namespace + return Status::OK(); +} - Status validateBSON( const char* originalBuffer, uint64_t maxLength ) { - if ( maxLength < 5 ) { - return Status( ErrorCodes::InvalidBSON, "bson data has to be at least 5 bytes" ); - } +} // namespace - Buffer buf( originalBuffer, maxLength ); - return validateBSONIterative( &buf ); +Status validateBSON(const char* originalBuffer, uint64_t maxLength) { + if (maxLength < 5) { + return Status(ErrorCodes::InvalidBSON, "bson data has to be at least 5 bytes"); } + Buffer buf(originalBuffer, maxLength); + return validateBSONIterative(&buf); +} + } // namespace mongo diff --git a/src/mongo/bson/bson_validate.h b/src/mongo/bson/bson_validate.h index f9b6d557da1..1a55a09e6b6 100644 --- a/src/mongo/bson/bson_validate.h +++ b/src/mongo/bson/bson_validate.h @@ -34,14 +34,14 @@ #include "mongo/platform/cstdint.h" namespace mongo { - class BSONObj; - class Status; +class BSONObj; +class Status; - /** - * @param buf - bson data - * @param maxLength - maxLength of buffer - * this is NOT the bson size, but how far we know the buffer is valid - */ - Status validateBSON( const char* buf, uint64_t maxLength ); +/** + * @param buf - bson data + * @param maxLength - maxLength of buffer + * this is NOT the bson size, but how far we know the buffer is valid + */ +Status validateBSON(const char* buf, uint64_t maxLength); } // namespace mongo diff --git a/src/mongo/bson/bson_validate_test.cpp b/src/mongo/bson/bson_validate_test.cpp index b3965763861..715cddd18c2 100644 --- a/src/mongo/bson/bson_validate_test.cpp +++ b/src/mongo/bson/bson_validate_test.cpp @@ -37,274 +37,277 @@ namespace { - using namespace mongo; - using std::unique_ptr; - using std::endl; - - void appendInvalidStringElement(const char* fieldName, BufBuilder* bb) { - // like a BSONObj string, but without a NUL terminator. - bb->appendChar(String); - bb->appendStr(fieldName, /*withNUL*/true); - bb->appendNum(4); - bb->appendStr("asdf", /*withNUL*/false); - } +using namespace mongo; +using std::unique_ptr; +using std::endl; + +void appendInvalidStringElement(const char* fieldName, BufBuilder* bb) { + // like a BSONObj string, but without a NUL terminator. + bb->appendChar(String); + bb->appendStr(fieldName, /*withNUL*/ true); + bb->appendNum(4); + bb->appendStr("asdf", /*withNUL*/ false); +} - TEST(BSONValidate, Basic) { - BSONObj x; - ASSERT_TRUE( x.valid() ); +TEST(BSONValidate, Basic) { + BSONObj x; + ASSERT_TRUE(x.valid()); - x = BSON( "x" << 1 ); - ASSERT_TRUE( x.valid() ); - } + x = BSON("x" << 1); + ASSERT_TRUE(x.valid()); +} - TEST(BSONValidate, RandomData) { - PseudoRandom r(17); +TEST(BSONValidate, RandomData) { + PseudoRandom r(17); - int numValid = 0; - int numToRun = 1000; - long long jsonSize = 0; + int numValid = 0; + int numToRun = 1000; + long long jsonSize = 0; - for ( int i=0; i<numToRun; i++ ) { - int size = 1234; + for (int i = 0; i < numToRun; i++) { + int size = 1234; - char* x = new char[size]; - DataView(x).write(tagLittleEndian(size)); + char* x = new char[size]; + DataView(x).write(tagLittleEndian(size)); - for ( int i=4; i<size; i++ ) { - x[i] = r.nextInt32( 255 ); - } + for (int i = 4; i < size; i++) { + x[i] = r.nextInt32(255); + } - x[size-1] = 0; + x[size - 1] = 0; - BSONObj o( x ); + BSONObj o(x); - ASSERT_EQUALS( size, o.objsize() ); + ASSERT_EQUALS(size, o.objsize()); - if ( o.valid() ) { - numValid++; - jsonSize += o.jsonString().size(); - ASSERT_OK( validateBSON( o.objdata(), o.objsize() ) ); - } - else { - ASSERT_NOT_OK( validateBSON( o.objdata(), o.objsize() ) ); - } - - delete[] x; + if (o.valid()) { + numValid++; + jsonSize += o.jsonString().size(); + ASSERT_OK(validateBSON(o.objdata(), o.objsize())); + } else { + ASSERT_NOT_OK(validateBSON(o.objdata(), o.objsize())); } - log() << "RandomData: didn't crash valid/total: " << numValid << "/" << numToRun - << " (want few valid ones)" - << " jsonSize: " << jsonSize << endl; + delete[] x; } - TEST(BSONValidate, MuckingData1) { - - BSONObj theObject; + log() << "RandomData: didn't crash valid/total: " << numValid << "/" << numToRun + << " (want few valid ones)" + << " jsonSize: " << jsonSize << endl; +} - { - BSONObjBuilder b; - b.append( "name" , "eliot was here" ); - b.append( "yippee" , "asd" ); - BSONArrayBuilder a( b.subarrayStart( "arr" ) ); - for ( int i=0; i<100; i++ ) { - a.append( BSON( "x" << i << "who" << "me" << "asd" << "asd" ) ); - } - a.done(); - b.done(); +TEST(BSONValidate, MuckingData1) { + BSONObj theObject; - theObject = b.obj(); + { + BSONObjBuilder b; + b.append("name", "eliot was here"); + b.append("yippee", "asd"); + BSONArrayBuilder a(b.subarrayStart("arr")); + for (int i = 0; i < 100; i++) { + a.append(BSON("x" << i << "who" + << "me" + << "asd" + << "asd")); } + a.done(); + b.done(); - int numValid = 0; - int numToRun = 1000; - long long jsonSize = 0; + theObject = b.obj(); + } - for ( int i=4; i<theObject.objsize()-1; i++ ) { - BSONObj mine = theObject.copy(); + int numValid = 0; + int numToRun = 1000; + long long jsonSize = 0; - char* data = const_cast<char*>(mine.objdata()); + for (int i = 4; i < theObject.objsize() - 1; i++) { + BSONObj mine = theObject.copy(); - data[ i ] = 200; + char* data = const_cast<char*>(mine.objdata()); - numToRun++; - if ( mine.valid() ) { - numValid++; - jsonSize += mine.jsonString().size(); - ASSERT_OK( validateBSON( mine.objdata(), mine.objsize() ) ); - } - else { - ASSERT_NOT_OK( validateBSON( mine.objdata(), mine.objsize() ) ); - } + data[i] = 200; + numToRun++; + if (mine.valid()) { + numValid++; + jsonSize += mine.jsonString().size(); + ASSERT_OK(validateBSON(mine.objdata(), mine.objsize())); + } else { + ASSERT_NOT_OK(validateBSON(mine.objdata(), mine.objsize())); } - - log() << "MuckingData1: didn't crash valid/total: " << numValid << "/" << numToRun - << " (want few valid ones) " - << " jsonSize: " << jsonSize << endl; } - TEST( BSONValidate, Fuzz ) { - int64_t seed = time( 0 ); - log() << "BSONValidate Fuzz random seed: " << seed << endl; - PseudoRandom randomSource( seed ); - - BSONObj original = BSON( "one" << 3 << - "two" << 5 << - "three" << BSONObj() << - "four" << BSON( "five" << BSON( "six" << 11 ) ) << - "seven" << BSON_ARRAY( "a" << "bb" << "ccc" << 5 ) << - "eight" << BSONDBRef( "rrr", OID( "01234567890123456789aaaa" ) ) << - "_id" << OID( "deadbeefdeadbeefdeadbeef" ) << - "nine" << BSONBinData( "\x69\xb7", 2, BinDataGeneral ) << - "ten" << Date_t::fromMillisSinceEpoch( 44 ) << - "eleven" << BSONRegEx( "foooooo", "i" ) ); - - int32_t fuzzFrequencies[] = { 2, 10, 20, 100, 1000 }; - for( size_t i = 0; i < sizeof( fuzzFrequencies ) / sizeof( int32_t ); ++i ) { - int32_t fuzzFrequency = fuzzFrequencies[ i ]; - - // Copy the 'original' BSONObj to 'buffer'. - unique_ptr<char[]> buffer( new char[ original.objsize() ] ); - memcpy( buffer.get(), original.objdata(), original.objsize() ); - - // Randomly flip bits in 'buffer', with probability determined by 'fuzzFrequency'. The - // first four bytes, representing the size of the object, are excluded from bit - // flipping. - for( int32_t byteIdx = 4; byteIdx < original.objsize(); ++byteIdx ) { - for( int32_t bitIdx = 0; bitIdx < 8; ++bitIdx ) { - if ( randomSource.nextInt32( fuzzFrequency ) == 0 ) { - reinterpret_cast<unsigned char&>( buffer[ byteIdx ] ) ^= ( 1U << bitIdx ); - } + log() << "MuckingData1: didn't crash valid/total: " << numValid << "/" << numToRun + << " (want few valid ones) " + << " jsonSize: " << jsonSize << endl; +} + +TEST(BSONValidate, Fuzz) { + int64_t seed = time(0); + log() << "BSONValidate Fuzz random seed: " << seed << endl; + PseudoRandom randomSource(seed); + + BSONObj original = + BSON("one" << 3 << "two" << 5 << "three" << BSONObj() << "four" + << BSON("five" << BSON("six" << 11)) << "seven" << BSON_ARRAY("a" + << "bb" + << "ccc" << 5) + << "eight" << BSONDBRef("rrr", OID("01234567890123456789aaaa")) << "_id" + << OID("deadbeefdeadbeefdeadbeef") << "nine" + << BSONBinData("\x69\xb7", 2, BinDataGeneral) << "ten" + << Date_t::fromMillisSinceEpoch(44) << "eleven" << BSONRegEx("foooooo", "i")); + + int32_t fuzzFrequencies[] = {2, 10, 20, 100, 1000}; + for (size_t i = 0; i < sizeof(fuzzFrequencies) / sizeof(int32_t); ++i) { + int32_t fuzzFrequency = fuzzFrequencies[i]; + + // Copy the 'original' BSONObj to 'buffer'. + unique_ptr<char[]> buffer(new char[original.objsize()]); + memcpy(buffer.get(), original.objdata(), original.objsize()); + + // Randomly flip bits in 'buffer', with probability determined by 'fuzzFrequency'. The + // first four bytes, representing the size of the object, are excluded from bit + // flipping. + for (int32_t byteIdx = 4; byteIdx < original.objsize(); ++byteIdx) { + for (int32_t bitIdx = 0; bitIdx < 8; ++bitIdx) { + if (randomSource.nextInt32(fuzzFrequency) == 0) { + reinterpret_cast<unsigned char&>(buffer[byteIdx]) ^= (1U << bitIdx); } } - BSONObj fuzzed( buffer.get() ); - - // Check that the two validation implementations agree (and neither crashes). - ASSERT_EQUALS( fuzzed.valid(), - validateBSON( fuzzed.objdata(), fuzzed.objsize() ).isOK() ); } - } - - TEST( BSONValidateFast, Empty ) { - BSONObj x; - ASSERT_OK( validateBSON( x.objdata(), x.objsize() ) ); - } + BSONObj fuzzed(buffer.get()); - TEST( BSONValidateFast, RegEx ) { - BSONObjBuilder b; - b.appendRegex( "foo", "i" ); - BSONObj x = b.obj(); - ASSERT_OK( validateBSON( x.objdata(), x.objsize() ) ); + // Check that the two validation implementations agree (and neither crashes). + ASSERT_EQUALS(fuzzed.valid(), validateBSON(fuzzed.objdata(), fuzzed.objsize()).isOK()); } +} - TEST(BSONValidateFast, Simple0 ) { - BSONObj x; - ASSERT_OK( validateBSON( x.objdata(), x.objsize() ) ); - - x = BSON( "foo" << 17 << "bar" << "eliot" ); - ASSERT_OK( validateBSON( x.objdata(), x.objsize() ) ); +TEST(BSONValidateFast, Empty) { + BSONObj x; + ASSERT_OK(validateBSON(x.objdata(), x.objsize())); +} - } +TEST(BSONValidateFast, RegEx) { + BSONObjBuilder b; + b.appendRegex("foo", "i"); + BSONObj x = b.obj(); + ASSERT_OK(validateBSON(x.objdata(), x.objsize())); +} - TEST(BSONValidateFast, Simple2 ) { - char buf[64]; - for ( int i=1; i<=JSTypeMax; i++ ) { - BSONObjBuilder b; - sprintf( buf, "foo%d", i ); - b.appendMinForType( buf, i ); - sprintf( buf, "bar%d", i ); - b.appendMaxForType( buf, i ); - BSONObj x = b.obj(); - ASSERT_OK( validateBSON( x.objdata(), x.objsize() ) ); - } - } +TEST(BSONValidateFast, Simple0) { + BSONObj x; + ASSERT_OK(validateBSON(x.objdata(), x.objsize())); + x = BSON("foo" << 17 << "bar" + << "eliot"); + ASSERT_OK(validateBSON(x.objdata(), x.objsize())); +} - TEST(BSONValidateFast, Simple3 ) { +TEST(BSONValidateFast, Simple2) { + char buf[64]; + for (int i = 1; i <= JSTypeMax; i++) { BSONObjBuilder b; - char buf[64]; - for ( int i=1; i<=JSTypeMax; i++ ) { - sprintf( buf, "foo%d", i ); - b.appendMinForType( buf, i ); - sprintf( buf, "bar%d", i ); - b.appendMaxForType( buf, i ); - } + sprintf(buf, "foo%d", i); + b.appendMinForType(buf, i); + sprintf(buf, "bar%d", i); + b.appendMaxForType(buf, i); BSONObj x = b.obj(); - ASSERT_OK( validateBSON( x.objdata(), x.objsize() ) ); - } - - TEST(BSONValidateFast, NestedObject) { - BSONObj x = BSON( "a" << 1 << "b" << BSON("c" << 2 << "d" << BSONArrayBuilder().obj() << "e" << BSON_ARRAY("1" << 2 << 3))); ASSERT_OK(validateBSON(x.objdata(), x.objsize())); - ASSERT_NOT_OK(validateBSON(x.objdata(), x.objsize() / 2)); } +} - TEST(BSONValidateFast, ErrorWithId) { - BufBuilder bb; - BSONObjBuilder ob(bb); - ob.append("_id", 1); - appendInvalidStringElement("not_id", &bb); - const BSONObj x = ob.done(); - const Status status = validateBSON(x.objdata(), x.objsize()); - ASSERT_NOT_OK(status); - ASSERT_EQUALS(status.reason(), "not null terminated string in object with _id: 1"); - } - TEST(BSONValidateFast, ErrorBeforeId) { - BufBuilder bb; - BSONObjBuilder ob(bb); - appendInvalidStringElement("not_id", &bb); - ob.append("_id", 1); - const BSONObj x = ob.done(); - const Status status = validateBSON(x.objdata(), x.objsize()); - ASSERT_NOT_OK(status); - ASSERT_EQUALS(status.reason(), "not null terminated string in object with unknown _id"); +TEST(BSONValidateFast, Simple3) { + BSONObjBuilder b; + char buf[64]; + for (int i = 1; i <= JSTypeMax; i++) { + sprintf(buf, "foo%d", i); + b.appendMinForType(buf, i); + sprintf(buf, "bar%d", i); + b.appendMaxForType(buf, i); } + BSONObj x = b.obj(); + ASSERT_OK(validateBSON(x.objdata(), x.objsize())); +} - TEST(BSONValidateFast, ErrorNoId) { - BufBuilder bb; - BSONObjBuilder ob(bb); - appendInvalidStringElement("not_id", &bb); - const BSONObj x = ob.done(); - const Status status = validateBSON(x.objdata(), x.objsize()); - ASSERT_NOT_OK(status); - ASSERT_EQUALS(status.reason(), "not null terminated string in object with unknown _id"); - } +TEST(BSONValidateFast, NestedObject) { + BSONObj x = BSON("a" << 1 << "b" << BSON("c" << 2 << "d" << BSONArrayBuilder().obj() << "e" + << BSON_ARRAY("1" << 2 << 3))); + ASSERT_OK(validateBSON(x.objdata(), x.objsize())); + ASSERT_NOT_OK(validateBSON(x.objdata(), x.objsize() / 2)); +} - TEST(BSONValidateFast, ErrorIsInId) { - BufBuilder bb; - BSONObjBuilder ob(bb); - appendInvalidStringElement("_id", &bb); - const BSONObj x = ob.done(); - const Status status = validateBSON(x.objdata(), x.objsize()); - ASSERT_NOT_OK(status); - ASSERT_EQUALS(status.reason(), "not null terminated string in object with unknown _id"); - } +TEST(BSONValidateFast, ErrorWithId) { + BufBuilder bb; + BSONObjBuilder ob(bb); + ob.append("_id", 1); + appendInvalidStringElement("not_id", &bb); + const BSONObj x = ob.done(); + const Status status = validateBSON(x.objdata(), x.objsize()); + ASSERT_NOT_OK(status); + ASSERT_EQUALS(status.reason(), "not null terminated string in object with _id: 1"); +} - TEST(BSONValidateFast, NonTopLevelId) { - BufBuilder bb; - BSONObjBuilder ob(bb); - ob.append("not_id1", BSON("_id" << "not the real _id")); - appendInvalidStringElement("not_id2", &bb); - const BSONObj x = ob.done(); - const Status status = validateBSON(x.objdata(), x.objsize()); - ASSERT_NOT_OK(status); - ASSERT_EQUALS(status.reason(), "not null terminated string in object with unknown _id"); - } +TEST(BSONValidateFast, ErrorBeforeId) { + BufBuilder bb; + BSONObjBuilder ob(bb); + appendInvalidStringElement("not_id", &bb); + ob.append("_id", 1); + const BSONObj x = ob.done(); + const Status status = validateBSON(x.objdata(), x.objsize()); + ASSERT_NOT_OK(status); + ASSERT_EQUALS(status.reason(), "not null terminated string in object with unknown _id"); +} - TEST(BSONValidateFast, StringHasSomething) { - BufBuilder bb; - BSONObjBuilder ob(bb); - bb.appendChar(String); - bb.appendStr("x", /*withNUL*/true); - bb.appendNum(0); - const BSONObj x = ob.done(); - ASSERT_EQUALS(5 // overhead - + 1 // type - + 2 // name - + 4 // size - , x.objsize()); - ASSERT_NOT_OK(validateBSON(x.objdata(), x.objsize())); - } +TEST(BSONValidateFast, ErrorNoId) { + BufBuilder bb; + BSONObjBuilder ob(bb); + appendInvalidStringElement("not_id", &bb); + const BSONObj x = ob.done(); + const Status status = validateBSON(x.objdata(), x.objsize()); + ASSERT_NOT_OK(status); + ASSERT_EQUALS(status.reason(), "not null terminated string in object with unknown _id"); +} + +TEST(BSONValidateFast, ErrorIsInId) { + BufBuilder bb; + BSONObjBuilder ob(bb); + appendInvalidStringElement("_id", &bb); + const BSONObj x = ob.done(); + const Status status = validateBSON(x.objdata(), x.objsize()); + ASSERT_NOT_OK(status); + ASSERT_EQUALS(status.reason(), "not null terminated string in object with unknown _id"); +} +TEST(BSONValidateFast, NonTopLevelId) { + BufBuilder bb; + BSONObjBuilder ob(bb); + ob.append("not_id1", + BSON("_id" + << "not the real _id")); + appendInvalidStringElement("not_id2", &bb); + const BSONObj x = ob.done(); + const Status status = validateBSON(x.objdata(), x.objsize()); + ASSERT_NOT_OK(status); + ASSERT_EQUALS(status.reason(), "not null terminated string in object with unknown _id"); +} + +TEST(BSONValidateFast, StringHasSomething) { + BufBuilder bb; + BSONObjBuilder ob(bb); + bb.appendChar(String); + bb.appendStr("x", /*withNUL*/ true); + bb.appendNum(0); + const BSONObj x = ob.done(); + ASSERT_EQUALS(5 // overhead + + + 1 // type + + + 2 // name + + + 4 // size + , + x.objsize()); + ASSERT_NOT_OK(validateBSON(x.objdata(), x.objsize())); +} } diff --git a/src/mongo/bson/bsonelement.cpp b/src/mongo/bson/bsonelement.cpp index 6948135e1e2..799da76b11d 100644 --- a/src/mongo/bson/bsonelement.cpp +++ b/src/mongo/bson/bsonelement.cpp @@ -43,99 +43,94 @@ #include "mongo/util/mongoutils/str.h" namespace mongo { - namespace str = mongoutils::str; +namespace str = mongoutils::str; - using std::dec; - using std::hex; - using std::string; +using std::dec; +using std::hex; +using std::string; - string BSONElement::jsonString( JsonStringFormat format, bool includeFieldNames, int pretty ) const { - std::stringstream s; - if ( includeFieldNames ) - s << '"' << escape( fieldName() ) << "\" : "; - switch ( type() ) { +string BSONElement::jsonString(JsonStringFormat format, bool includeFieldNames, int pretty) const { + std::stringstream s; + if (includeFieldNames) + s << '"' << escape(fieldName()) << "\" : "; + switch (type()) { case mongo::String: case Symbol: - s << '"' << escape( string(valuestr(), valuestrsize()-1) ) << '"'; + s << '"' << escape(string(valuestr(), valuestrsize() - 1)) << '"'; break; case NumberLong: if (format == TenGen) { s << "NumberLong(" << _numberLong() << ")"; - } - else { + } else { s << "{ \"$numberLong\" : \"" << _numberLong() << "\" }"; } break; case NumberInt: - if(format == JS) { + if (format == JS) { s << "NumberInt(" << _numberInt() << ")"; break; } case NumberDouble: - if ( number() >= -std::numeric_limits< double >::max() && - number() <= std::numeric_limits< double >::max() ) { - s.precision( 16 ); + if (number() >= -std::numeric_limits<double>::max() && + number() <= std::numeric_limits<double>::max()) { + s.precision(16); s << number(); } // This is not valid JSON, but according to RFC-4627, "Numeric values that cannot be // represented as sequences of digits (such as Infinity and NaN) are not permitted." so // we are accepting the fact that if we have such values we cannot output valid JSON. - else if ( std::isnan(number()) ) { + else if (std::isnan(number())) { s << "NaN"; - } - else if ( std::isinf(number()) ) { - s << ( number() > 0 ? "Infinity" : "-Infinity"); - } - else { + } else if (std::isinf(number())) { + s << (number() > 0 ? "Infinity" : "-Infinity"); + } else { StringBuilder ss; ss << "Number " << number() << " cannot be represented in JSON"; string message = ss.str(); - massert( 10311 , message.c_str(), false ); + massert(10311, message.c_str(), false); } break; case mongo::Bool: - s << ( boolean() ? "true" : "false" ); + s << (boolean() ? "true" : "false"); break; case jstNULL: s << "null"; break; case Undefined: - if ( format == Strict ) { + if (format == Strict) { s << "{ \"$undefined\" : true }"; - } - else { + } else { s << "undefined"; } break; case Object: - s << embeddedObject().jsonString( format, pretty ); + s << embeddedObject().jsonString(format, pretty); break; case mongo::Array: { - if ( embeddedObject().isEmpty() ) { + if (embeddedObject().isEmpty()) { s << "[]"; break; } s << "[ "; - BSONObjIterator i( embeddedObject() ); + BSONObjIterator i(embeddedObject()); BSONElement e = i.next(); - if ( !e.eoo() ) { + if (!e.eoo()) { int count = 0; - while ( 1 ) { - if( pretty ) { + while (1) { + if (pretty) { s << '\n'; - for( int x = 0; x < pretty; x++ ) + for (int x = 0; x < pretty; x++) s << " "; } if (strtol(e.fieldName(), 0, 10) > count) { s << "undefined"; - } - else { - s << e.jsonString( format, false, pretty?pretty+1:0 ); + } else { + s << e.jsonString(format, false, pretty ? pretty + 1 : 0); e = i.next(); } count++; - if ( e.eoo() ) + if (e.eoo()) break; s << ", "; } @@ -144,45 +139,43 @@ namespace mongo { break; } case DBRef: { - if ( format == TenGen ) + if (format == TenGen) s << "Dbref( "; else s << "{ \"$ref\" : "; s << '"' << valuestr() << "\", "; - if ( format != TenGen ) + if (format != TenGen) s << "\"$id\" : "; s << '"' << mongo::OID::from(valuestr() + valuestrsize()) << "\" "; - if ( format == TenGen ) + if (format == TenGen) s << ')'; else s << '}'; break; } case jstOID: - if ( format == TenGen ) { + if (format == TenGen) { s << "ObjectId( "; - } - else { + } else { s << "{ \"$oid\" : "; } s << '"' << __oid() << '"'; - if ( format == TenGen ) { + if (format == TenGen) { s << " )"; - } - else { + } else { s << " }"; } break; case BinData: { - ConstDataCursor reader( value() ); + ConstDataCursor reader(value()); const int len = reader.readAndAdvance<LittleEndian<int>>(); BinDataType type = static_cast<BinDataType>(reader.readAndAdvance<uint8_t>()); s << "{ \"$binary\" : \""; - base64::encode( s , reader.view() , len ); + base64::encode(s, reader.view(), len); s << "\", \"$type\" : \"" << hex; - s.width( 2 ); - s.fill( '0' ); + s.width(2); + s.fill('0'); s << type << dec; s << "\" }"; break; @@ -199,13 +192,11 @@ namespace mongo { // Date_t::millis is negative (before the epoch). if (d.isFormattable()) { s << "\"" << dateToISOStringLocal(date()) << "\""; - } - else { + } else { s << "{ \"$numberLong\" : \"" << d.toMillisSinceEpoch() << "\" }"; } s << " }"; - } - else { + } else { s << "Date( "; if (pretty) { Date_t d = date(); @@ -217,35 +208,32 @@ namespace mongo { // large, and the case where Date_t::millis is negative (before the epoch). if (d.isFormattable()) { s << "\"" << dateToISOStringLocal(date()) << "\""; - } - else { + } else { // FIXME: This is not parseable by the shell, since it may not fit in a // float s << d.toMillisSinceEpoch(); } - } - else { + } else { s << date().asInt64(); } s << " )"; } break; case RegEx: - if ( format == Strict ) { - s << "{ \"$regex\" : \"" << escape( regex() ); + if (format == Strict) { + s << "{ \"$regex\" : \"" << escape(regex()); s << "\", \"$options\" : \"" << regexFlags() << "\" }"; - } - else { - s << "/" << escape( regex() , true ) << "/"; + } else { + s << "/" << escape(regex(), true) << "/"; // FIXME Worry about alpha order? - for ( const char *f = regexFlags(); *f; ++f ) { - switch ( *f ) { - case 'g': - case 'i': - case 'm': - s << *f; - default: - break; + for (const char* f = regexFlags(); *f; ++f) { + switch (*f) { + case 'g': + case 'i': + case 'm': + s << *f; + default: + break; } } } @@ -253,7 +241,7 @@ namespace mongo { case CodeWScope: { BSONObj scope = codeWScopeObject(); - if ( ! scope.isEmpty() ) { + if (!scope.isEmpty()) { s << "{ \"$code\" : \"" << escape(_asCode()) << "\" , " << "\"$scope\" : " << scope.jsonString() << " }"; break; @@ -265,12 +253,10 @@ namespace mongo { break; case bsonTimestamp: - if ( format == TenGen ) { - s << "Timestamp( " - << durationCount<Seconds>(timestampTime().toDurationSinceEpoch()) + if (format == TenGen) { + s << "Timestamp( " << durationCount<Seconds>(timestampTime().toDurationSinceEpoch()) << ", " << timestampInc() << " )"; - } - else { + } else { s << "{ \"$timestamp\" : { \"t\" : " << durationCount<Seconds>(timestampTime().toDurationSinceEpoch()) << ", \"i\" : " << timestampInc() << " } }"; @@ -290,169 +276,173 @@ namespace mongo { ss << "Cannot create a properly formatted JSON string with " << "element: " << toString() << " of type: " << type(); string message = ss.str(); - massert( 10312 , message.c_str(), false ); - } - return s.str(); + massert(10312, message.c_str(), false); } - - int BSONElement::getGtLtOp( int def ) const { - const char *fn = fieldName(); - if ( fn[0] == '$' && fn[1] ) { - if ( fn[2] == 't' ) { - if ( fn[1] == 'g' ) { - if ( fn[3] == 0 ) return BSONObj::GT; - else if ( fn[3] == 'e' && fn[4] == 0 ) return BSONObj::GTE; - } - else if ( fn[1] == 'l' ) { - if ( fn[3] == 0 ) return BSONObj::LT; - else if ( fn[3] == 'e' && fn[4] == 0 ) return BSONObj::LTE; - } - } - else if ( fn[1] == 'n' && fn[2] == 'e' ) { - if ( fn[3] == 0 ) - return BSONObj::NE; - if ( fn[3] == 'a' && fn[4] == 'r') // matches anything with $near prefix - return BSONObj::opNEAR; + return s.str(); +} + +int BSONElement::getGtLtOp(int def) const { + const char* fn = fieldName(); + if (fn[0] == '$' && fn[1]) { + if (fn[2] == 't') { + if (fn[1] == 'g') { + if (fn[3] == 0) + return BSONObj::GT; + else if (fn[3] == 'e' && fn[4] == 0) + return BSONObj::GTE; + } else if (fn[1] == 'l') { + if (fn[3] == 0) + return BSONObj::LT; + else if (fn[3] == 'e' && fn[4] == 0) + return BSONObj::LTE; } - else if ( fn[1] == 'm' ) { - if ( fn[2] == 'o' && fn[3] == 'd' && fn[4] == 0 ) - return BSONObj::opMOD; - if ( fn[2] == 'a' && fn[3] == 'x' && fn[4] == 'D' && fn[5] == 'i' && fn[6] == 's' && fn[7] == 't' && fn[8] == 'a' && fn[9] == 'n' && fn[10] == 'c' && fn[11] == 'e' && fn[12] == 0 ) - return BSONObj::opMAX_DISTANCE; - } - else if ( fn[1] == 't' && fn[2] == 'y' && fn[3] == 'p' && fn[4] == 'e' && fn[5] == 0 ) - return BSONObj::opTYPE; - else if ( fn[1] == 'i' && fn[2] == 'n' && fn[3] == 0) { - return BSONObj::opIN; - } else if ( fn[1] == 'n' && fn[2] == 'i' && fn[3] == 'n' && fn[4] == 0 ) - return BSONObj::NIN; - else if ( fn[1] == 'a' && fn[2] == 'l' && fn[3] == 'l' && fn[4] == 0 ) - return BSONObj::opALL; - else if ( fn[1] == 's' && fn[2] == 'i' && fn[3] == 'z' && fn[4] == 'e' && fn[5] == 0 ) - return BSONObj::opSIZE; - else if ( fn[1] == 'e' ) { - if ( fn[2] == 'x' && fn[3] == 'i' && fn[4] == 's' && fn[5] == 't' && fn[6] == 's' && fn[7] == 0 ) - return BSONObj::opEXISTS; - if ( fn[2] == 'l' && fn[3] == 'e' && fn[4] == 'm' && fn[5] == 'M' && fn[6] == 'a' && fn[7] == 't' && fn[8] == 'c' && fn[9] == 'h' && fn[10] == 0 ) - return BSONObj::opELEM_MATCH; - } - else if ( fn[1] == 'r' && fn[2] == 'e' && fn[3] == 'g' && fn[4] == 'e' && fn[5] == 'x' && fn[6] == 0 ) - return BSONObj::opREGEX; - else if ( fn[1] == 'o' && fn[2] == 'p' && fn[3] == 't' && fn[4] == 'i' && fn[5] == 'o' && fn[6] == 'n' && fn[7] == 's' && fn[8] == 0 ) - return BSONObj::opOPTIONS; - else if ( fn[1] == 'w' && fn[2] == 'i' && fn[3] == 't' && fn[4] == 'h' && fn[5] == 'i' && fn[6] == 'n' && fn[7] == 0 ) - return BSONObj::opWITHIN; - else if (str::equals(fn + 1, "geoIntersects")) - return BSONObj::opGEO_INTERSECTS; - else if (str::equals(fn + 1, "geoNear")) + } else if (fn[1] == 'n' && fn[2] == 'e') { + if (fn[3] == 0) + return BSONObj::NE; + if (fn[3] == 'a' && fn[4] == 'r') // matches anything with $near prefix return BSONObj::opNEAR; - else if (str::equals(fn + 1, "geoWithin")) - return BSONObj::opWITHIN; - } - return def; + } else if (fn[1] == 'm') { + if (fn[2] == 'o' && fn[3] == 'd' && fn[4] == 0) + return BSONObj::opMOD; + if (fn[2] == 'a' && fn[3] == 'x' && fn[4] == 'D' && fn[5] == 'i' && fn[6] == 's' && + fn[7] == 't' && fn[8] == 'a' && fn[9] == 'n' && fn[10] == 'c' && fn[11] == 'e' && + fn[12] == 0) + return BSONObj::opMAX_DISTANCE; + } else if (fn[1] == 't' && fn[2] == 'y' && fn[3] == 'p' && fn[4] == 'e' && fn[5] == 0) + return BSONObj::opTYPE; + else if (fn[1] == 'i' && fn[2] == 'n' && fn[3] == 0) { + return BSONObj::opIN; + } else if (fn[1] == 'n' && fn[2] == 'i' && fn[3] == 'n' && fn[4] == 0) + return BSONObj::NIN; + else if (fn[1] == 'a' && fn[2] == 'l' && fn[3] == 'l' && fn[4] == 0) + return BSONObj::opALL; + else if (fn[1] == 's' && fn[2] == 'i' && fn[3] == 'z' && fn[4] == 'e' && fn[5] == 0) + return BSONObj::opSIZE; + else if (fn[1] == 'e') { + if (fn[2] == 'x' && fn[3] == 'i' && fn[4] == 's' && fn[5] == 't' && fn[6] == 's' && + fn[7] == 0) + return BSONObj::opEXISTS; + if (fn[2] == 'l' && fn[3] == 'e' && fn[4] == 'm' && fn[5] == 'M' && fn[6] == 'a' && + fn[7] == 't' && fn[8] == 'c' && fn[9] == 'h' && fn[10] == 0) + return BSONObj::opELEM_MATCH; + } else if (fn[1] == 'r' && fn[2] == 'e' && fn[3] == 'g' && fn[4] == 'e' && fn[5] == 'x' && + fn[6] == 0) + return BSONObj::opREGEX; + else if (fn[1] == 'o' && fn[2] == 'p' && fn[3] == 't' && fn[4] == 'i' && fn[5] == 'o' && + fn[6] == 'n' && fn[7] == 's' && fn[8] == 0) + return BSONObj::opOPTIONS; + else if (fn[1] == 'w' && fn[2] == 'i' && fn[3] == 't' && fn[4] == 'h' && fn[5] == 'i' && + fn[6] == 'n' && fn[7] == 0) + return BSONObj::opWITHIN; + else if (str::equals(fn + 1, "geoIntersects")) + return BSONObj::opGEO_INTERSECTS; + else if (str::equals(fn + 1, "geoNear")) + return BSONObj::opNEAR; + else if (str::equals(fn + 1, "geoWithin")) + return BSONObj::opWITHIN; } + return def; +} - /** transform a BSON array into a vector of BSONElements. - we match array # positions with their vector position, and ignore - any fields with non-numeric field names. - */ - std::vector<BSONElement> BSONElement::Array() const { - chk(mongo::Array); - std::vector<BSONElement> v; - BSONObjIterator i(Obj()); - while( i.more() ) { - BSONElement e = i.next(); - const char *f = e.fieldName(); - - unsigned u; - Status status = parseNumberFromString( f, &u ); - if ( status.isOK() ) { - verify( u < 1000000 ); - if( u >= v.size() ) - v.resize(u+1); - v[u] = e; - } - else { - // ignore? - } - } - return v; - } - - /* wo = "well ordered" - note: (mongodb related) : this can only change in behavior when index version # changes +/** transform a BSON array into a vector of BSONElements. + we match array # positions with their vector position, and ignore + any fields with non-numeric field names. */ - int BSONElement::woCompare( const BSONElement &e, - bool considerFieldName ) const { - int lt = (int) canonicalType(); - int rt = (int) e.canonicalType(); - int x = lt - rt; - if( x != 0 && (!isNumber() || !e.isNumber()) ) - return x; - if ( considerFieldName ) { - x = strcmp(fieldName(), e.fieldName()); - if ( x != 0 ) - return x; +std::vector<BSONElement> BSONElement::Array() const { + chk(mongo::Array); + std::vector<BSONElement> v; + BSONObjIterator i(Obj()); + while (i.more()) { + BSONElement e = i.next(); + const char* f = e.fieldName(); + + unsigned u; + Status status = parseNumberFromString(f, &u); + if (status.isOK()) { + verify(u < 1000000); + if (u >= v.size()) + v.resize(u + 1); + v[u] = e; + } else { + // ignore? } - x = compareElementValues(*this, e); + } + return v; +} + +/* wo = "well ordered" + note: (mongodb related) : this can only change in behavior when index version # changes +*/ +int BSONElement::woCompare(const BSONElement& e, bool considerFieldName) const { + int lt = (int)canonicalType(); + int rt = (int)e.canonicalType(); + int x = lt - rt; + if (x != 0 && (!isNumber() || !e.isNumber())) return x; + if (considerFieldName) { + x = strcmp(fieldName(), e.fieldName()); + if (x != 0) + return x; } + x = compareElementValues(*this, e); + return x; +} - BSONObj BSONElement::embeddedObjectUserCheck() const { - if ( MONGO_likely(isABSONObj()) ) - return BSONObj(value()); - std::stringstream ss; - ss << "invalid parameter: expected an object (" << fieldName() << ")"; - uasserted( 10065 , ss.str() ); - return BSONObj(); // never reachable - } - - BSONObj BSONElement::embeddedObject() const { - verify( isABSONObj() ); +BSONObj BSONElement::embeddedObjectUserCheck() const { + if (MONGO_likely(isABSONObj())) return BSONObj(value()); - } - - BSONObj BSONElement::codeWScopeObject() const { - verify( type() == CodeWScope ); - int strSizeWNull = ConstDataView(value() + 4).read<LittleEndian<int>>(); - return BSONObj( value() + 4 + 4 + strSizeWNull ); - } - - // wrap this element up as a singleton object. - BSONObj BSONElement::wrap() const { - BSONObjBuilder b(size()+6); - b.append(*this); - return b.obj(); - } - - BSONObj BSONElement::wrap( StringData newName ) const { - BSONObjBuilder b(size() + 6 + newName.size()); - b.appendAs(*this,newName); - return b.obj(); - } - - void BSONElement::Val(BSONObj& v) const { - v = Obj(); - } - - BSONObj BSONElement::Obj() const { - return embeddedObjectUserCheck(); - } - - BSONElement BSONElement::operator[] (const std::string& field) const { - BSONObj o = Obj(); - return o[field]; - } - - int BSONElement::size( int maxLen ) const { - if ( totalSize >= 0 ) - return totalSize; + std::stringstream ss; + ss << "invalid parameter: expected an object (" << fieldName() << ")"; + uasserted(10065, ss.str()); + return BSONObj(); // never reachable +} + +BSONObj BSONElement::embeddedObject() const { + verify(isABSONObj()); + return BSONObj(value()); +} + +BSONObj BSONElement::codeWScopeObject() const { + verify(type() == CodeWScope); + int strSizeWNull = ConstDataView(value() + 4).read<LittleEndian<int>>(); + return BSONObj(value() + 4 + 4 + strSizeWNull); +} + +// wrap this element up as a singleton object. +BSONObj BSONElement::wrap() const { + BSONObjBuilder b(size() + 6); + b.append(*this); + return b.obj(); +} + +BSONObj BSONElement::wrap(StringData newName) const { + BSONObjBuilder b(size() + 6 + newName.size()); + b.appendAs(*this, newName); + return b.obj(); +} + +void BSONElement::Val(BSONObj& v) const { + v = Obj(); +} + +BSONObj BSONElement::Obj() const { + return embeddedObjectUserCheck(); +} + +BSONElement BSONElement::operator[](const std::string& field) const { + BSONObj o = Obj(); + return o[field]; +} + +int BSONElement::size(int maxLen) const { + if (totalSize >= 0) + return totalSize; - int remain = maxLen - fieldNameSize() - 1; + int remain = maxLen - fieldNameSize() - 1; - int x = 0; - switch ( type() ) { + int x = 0; + switch (type()) { case EOO: case Undefined: case jstNULL: @@ -477,62 +467,66 @@ namespace mongo { case Symbol: case Code: case mongo::String: - massert( 10313 , "Insufficient bytes to calculate element size", maxLen == -1 || remain > 3 ); + massert( + 10313, "Insufficient bytes to calculate element size", maxLen == -1 || remain > 3); x = valuestrsize() + 4; break; case CodeWScope: - massert( 10314 , "Insufficient bytes to calculate element size", maxLen == -1 || remain > 3 ); + massert( + 10314, "Insufficient bytes to calculate element size", maxLen == -1 || remain > 3); x = objsize(); break; case DBRef: - massert( 10315 , "Insufficient bytes to calculate element size", maxLen == -1 || remain > 3 ); + massert( + 10315, "Insufficient bytes to calculate element size", maxLen == -1 || remain > 3); x = valuestrsize() + 4 + 12; break; case Object: case mongo::Array: - massert( 10316 , "Insufficient bytes to calculate element size", maxLen == -1 || remain > 3 ); + massert( + 10316, "Insufficient bytes to calculate element size", maxLen == -1 || remain > 3); x = objsize(); break; case BinData: - massert( 10317 , "Insufficient bytes to calculate element size", maxLen == -1 || remain > 3 ); - x = valuestrsize() + 4 + 1/*subtype*/; + massert( + 10317, "Insufficient bytes to calculate element size", maxLen == -1 || remain > 3); + x = valuestrsize() + 4 + 1 /*subtype*/; break; case RegEx: { - const char *p = value(); - size_t len1 = ( maxLen == -1 ) ? strlen( p ) : strnlen( p, remain ); - massert( 10318 , "Invalid regex string", maxLen == -1 || len1 < size_t(remain) ); + const char* p = value(); + size_t len1 = (maxLen == -1) ? strlen(p) : strnlen(p, remain); + massert(10318, "Invalid regex string", maxLen == -1 || len1 < size_t(remain)); p = p + len1 + 1; size_t len2; - if( maxLen == -1 ) - len2 = strlen( p ); + if (maxLen == -1) + len2 = strlen(p); else { size_t x = remain - len1 - 1; - verify( x <= 0x7fffffff ); - len2 = strnlen( p, x ); - massert( 10319 , "Invalid regex options string", len2 < x ); + verify(x <= 0x7fffffff); + len2 = strnlen(p, x); + massert(10319, "Invalid regex options string", len2 < x); } - x = (int) (len1 + 1 + len2 + 1); - } - break; + x = (int)(len1 + 1 + len2 + 1); + } break; default: { StringBuilder ss; - ss << "BSONElement: bad type " << (int) type(); + ss << "BSONElement: bad type " << (int)type(); std::string msg = ss.str(); - massert( 13655 , msg.c_str(),false); - } + massert(13655, msg.c_str(), false); } - totalSize = x + fieldNameSize() + 1; // BSONType - - return totalSize; } + totalSize = x + fieldNameSize() + 1; // BSONType - int BSONElement::size() const { - if ( totalSize >= 0 ) - return totalSize; + return totalSize; +} - int x = 0; - switch ( type() ) { +int BSONElement::size() const { + if (totalSize >= 0) + return totalSize; + + int x = 0; + switch (type()) { case EOO: case Undefined: case jstNULL: @@ -568,54 +562,50 @@ namespace mongo { x = objsize(); break; case BinData: - x = valuestrsize() + 4 + 1/*subtype*/; - break; - case RegEx: - { - const char *p = value(); - size_t len1 = strlen(p); - p = p + len1 + 1; - size_t len2; - len2 = strlen( p ); - x = (int) (len1 + 1 + len2 + 1); - } + x = valuestrsize() + 4 + 1 /*subtype*/; break; - default: - { - StringBuilder ss; - ss << "BSONElement: bad type " << (int) type(); - std::string msg = ss.str(); - massert(10320 , msg.c_str(),false); - } + case RegEx: { + const char* p = value(); + size_t len1 = strlen(p); + p = p + len1 + 1; + size_t len2; + len2 = strlen(p); + x = (int)(len1 + 1 + len2 + 1); + } break; + default: { + StringBuilder ss; + ss << "BSONElement: bad type " << (int)type(); + std::string msg = ss.str(); + massert(10320, msg.c_str(), false); } - totalSize = x + fieldNameSize() + 1; // BSONType - - return totalSize; } - - std::string BSONElement::toString( bool includeFieldName, bool full ) const { - StringBuilder s; - toString(s, includeFieldName, full); - return s.str(); - } - - void BSONElement::toString( StringBuilder& s, bool includeFieldName, bool full, int depth ) const { - - if ( depth > BSONObj::maxToStringRecursionDepth ) { - // check if we want the full/complete string - if ( full ) { - StringBuilder s; - s << "Reached maximum recursion depth of "; - s << BSONObj::maxToStringRecursionDepth; - uassert(16150, s.str(), full != true); - } - s << "..."; - return; + totalSize = x + fieldNameSize() + 1; // BSONType + + return totalSize; +} + +std::string BSONElement::toString(bool includeFieldName, bool full) const { + StringBuilder s; + toString(s, includeFieldName, full); + return s.str(); +} + +void BSONElement::toString(StringBuilder& s, bool includeFieldName, bool full, int depth) const { + if (depth > BSONObj::maxToStringRecursionDepth) { + // check if we want the full/complete string + if (full) { + StringBuilder s; + s << "Reached maximum recursion depth of "; + s << BSONObj::maxToStringRecursionDepth; + uassert(16150, s.str(), full != true); } + s << "..."; + return; + } - if ( includeFieldName && type() != EOO ) - s << fieldName() << ": "; - switch ( type() ) { + if (includeFieldName && type() != EOO) + s << fieldName() << ": "; + switch (type()) { case EOO: s << "EOO"; break; @@ -624,12 +614,12 @@ namespace mongo { break; case RegEx: { s << "/" << regex() << '/'; - const char *p = regexFlags(); - if ( p ) s << p; - } - break; + const char* p = regexFlags(); + if (p) + s << p; + } break; case NumberDouble: - s.appendDoubleNice( number() ); + s.appendDoubleNice(number()); break; case NumberLong: s << _numberLong(); @@ -638,13 +628,13 @@ namespace mongo { s << _numberInt(); break; case mongo::Bool: - s << ( boolean() ? "true" : "false" ); + s << (boolean() ? "true" : "false"); break; case Object: - embeddedObject().toString(s, false, full, depth+1); + embeddedObject().toString(s, false, full, depth + 1); break; case mongo::Array: - embeddedObject().toString(s, true, full, depth+1); + embeddedObject().toString(s, true, full, depth + 1); break; case Undefined: s << "undefined"; @@ -659,27 +649,25 @@ namespace mongo { s << "MinKey"; break; case CodeWScope: - s << "CodeWScope( " - << codeWScopeCode() << ", " << codeWScopeObject().toString(false, full) << ")"; + s << "CodeWScope( " << codeWScopeCode() << ", " + << codeWScopeObject().toString(false, full) << ")"; break; case Code: - if ( !full && valuestrsize() > 80 ) { + if (!full && valuestrsize() > 80) { s.write(valuestr(), 70); s << "..."; - } - else { - s.write(valuestr(), valuestrsize()-1); + } else { + s.write(valuestr(), valuestrsize() - 1); } break; case Symbol: case mongo::String: s << '"'; - if ( !full && valuestrsize() > 160 ) { + if (!full && valuestrsize() > 160) { s.write(valuestr(), 150); s << "...\""; - } - else { - s.write(valuestr(), valuestrsize()-1); + } else { + s.write(valuestr(), valuestrsize() - 1); s << '"'; } break; @@ -695,11 +683,10 @@ namespace mongo { s << "BinData(" << binDataType() << ", "; { int len; - const char *data = binDataClean(len); - if ( !full && len > 80 ) { + const char* data = binDataClean(len); + if (!full && len > 80) { s << toHex(data, 70) << "...)"; - } - else { + } else { s << toHex(data, len) << ")"; } } @@ -710,89 +697,96 @@ namespace mongo { default: s << "?type=" << type(); break; - } } +} - std::string BSONElement::_asCode() const { - switch( type() ) { +std::string BSONElement::_asCode() const { + switch (type()) { case mongo::String: case Code: - return std::string(valuestr(), valuestrsize()-1); + return std::string(valuestr(), valuestrsize() - 1); case CodeWScope: return std::string(codeWScopeCode(), ConstDataView(valuestr()).read<LittleEndian<int>>() - 1); default: log() << "can't convert type: " << (int)(type()) << " to code" << std::endl; - } - uassert( 10062 , "not code" , 0 ); - return ""; - } - - std::ostream& operator<<( std::ostream &s, const BSONElement &e ) { - return s << e.toString(); - } - - StringBuilder& operator<<( StringBuilder &s, const BSONElement &e ) { - e.toString( s ); - return s; } - - template<> bool BSONElement::coerce<std::string>( std::string* out ) const { - if ( type() != mongo::String ) + uassert(10062, "not code", 0); + return ""; +} + +std::ostream& operator<<(std::ostream& s, const BSONElement& e) { + return s << e.toString(); +} + +StringBuilder& operator<<(StringBuilder& s, const BSONElement& e) { + e.toString(s); + return s; +} + +template <> +bool BSONElement::coerce<std::string>(std::string* out) const { + if (type() != mongo::String) + return false; + *out = String(); + return true; +} + +template <> +bool BSONElement::coerce<int>(int* out) const { + if (!isNumber()) + return false; + *out = numberInt(); + return true; +} + +template <> +bool BSONElement::coerce<long long>(long long* out) const { + if (!isNumber()) + return false; + *out = numberLong(); + return true; +} + +template <> +bool BSONElement::coerce<double>(double* out) const { + if (!isNumber()) + return false; + *out = numberDouble(); + return true; +} + +template <> +bool BSONElement::coerce<bool>(bool* out) const { + *out = trueValue(); + return true; +} + +template <> +bool BSONElement::coerce<std::vector<std::string>>(std::vector<std::string>* out) const { + if (type() != mongo::Array) + return false; + return Obj().coerceVector<std::string>(out); +} + +template <typename T> +bool BSONObj::coerceVector(std::vector<T>* out) const { + BSONObjIterator i(*this); + while (i.more()) { + BSONElement e = i.next(); + T t; + if (!e.coerce<T>(&t)) return false; - *out = String(); - return true; + out->push_back(t); } - - template<> bool BSONElement::coerce<int>( int* out ) const { - if ( !isNumber() ) - return false; - *out = numberInt(); - return true; - } - - template<> bool BSONElement::coerce<long long>( long long* out ) const { - if ( !isNumber() ) - return false; - *out = numberLong(); - return true; - } - - template<> bool BSONElement::coerce<double>( double* out ) const { - if ( !isNumber() ) - return false; - *out = numberDouble(); - return true; - } - - template<> bool BSONElement::coerce<bool>( bool* out ) const { - *out = trueValue(); - return true; - } - - template<> bool BSONElement::coerce< std::vector<std::string> >( std::vector<std::string>* out ) const { - if ( type() != mongo::Array ) - return false; - return Obj().coerceVector<std::string>( out ); - } - - template<typename T> bool BSONObj::coerceVector( std::vector<T>* out ) const { - BSONObjIterator i( *this ); - while ( i.more() ) { - BSONElement e = i.next(); - T t; - if ( ! e.coerce<T>( &t ) ) - return false; - out->push_back( t ); - } - return true; - } - - // used by jsonString() - std::string escape( const std::string& s , bool escape_slash) { - StringBuilder ret; - for ( std::string::const_iterator i = s.begin(); i != s.end(); ++i ) { - switch ( *i ) { + return true; +} + +// used by jsonString() +std::string escape(const std::string& s, bool escape_slash) { + StringBuilder ret; + for (std::string::const_iterator i = s.begin(); i != s.end(); ++i) { + switch (*i) { case '"': ret << "\\\""; break; @@ -818,39 +812,39 @@ namespace mongo { ret << "\\t"; break; default: - if ( *i >= 0 && *i <= 0x1f ) { - //TODO: these should be utf16 code-units not bytes + if (*i >= 0 && *i <= 0x1f) { + // TODO: these should be utf16 code-units not bytes char c = *i; ret << "\\u00" << toHexLower(&c, 1); - } - else { + } else { ret << *i; } - } } - return ret.str(); } + return ret.str(); +} - /** - * l and r must be same canonicalType when called. - */ - int compareElementValues(const BSONElement& l, const BSONElement& r) { - int f; +/** + * l and r must be same canonicalType when called. + */ +int compareElementValues(const BSONElement& l, const BSONElement& r) { + int f; - switch ( l.type() ) { + switch (l.type()) { case EOO: - case Undefined: // EOO and Undefined are same canonicalType + case Undefined: // EOO and Undefined are same canonicalType case jstNULL: case MaxKey: case MinKey: f = l.canonicalType() - r.canonicalType(); - if ( f<0 ) return -1; - return f==0 ? 0 : 1; + if (f < 0) + return -1; + return f == 0 ? 0 : 1; case Bool: return *l.value() - *r.value(); case bsonTimestamp: // unsigned compare for timestamps - note they are not really dates but (ordinal + time_t) - if ( l.date() < r.date() ) + if (l.date() < r.date()) return -1; return l.date() == r.date() ? 0 : 1; case Date: @@ -858,7 +852,7 @@ namespace mongo { { const Date_t a = l.Date(); const Date_t b = r.Date(); - if( a < b ) + if (a < b) return -1; return a == b ? 0 : 1; } @@ -867,28 +861,40 @@ namespace mongo { // All types can precisely represent all NumberInts, so it is safe to simply convert to // whatever rhs's type is. switch (r.type()) { - case NumberInt: return compareInts(l._numberInt(), r._numberInt()); - case NumberLong: return compareLongs(l._numberInt(), r._numberLong()); - case NumberDouble: return compareDoubles(l._numberInt(), r._numberDouble()); - default: invariant(false); + case NumberInt: + return compareInts(l._numberInt(), r._numberInt()); + case NumberLong: + return compareLongs(l._numberInt(), r._numberLong()); + case NumberDouble: + return compareDoubles(l._numberInt(), r._numberDouble()); + default: + invariant(false); } } case NumberLong: { switch (r.type()) { - case NumberLong: return compareLongs(l._numberLong(), r._numberLong()); - case NumberInt: return compareLongs(l._numberLong(), r._numberInt()); - case NumberDouble: return compareLongToDouble(l._numberLong(), r._numberDouble()); - default: invariant(false); + case NumberLong: + return compareLongs(l._numberLong(), r._numberLong()); + case NumberInt: + return compareLongs(l._numberLong(), r._numberInt()); + case NumberDouble: + return compareLongToDouble(l._numberLong(), r._numberDouble()); + default: + invariant(false); } } case NumberDouble: { switch (r.type()) { - case NumberDouble: return compareDoubles(l._numberDouble(), r._numberDouble()); - case NumberInt: return compareDoubles(l._numberDouble(), r._numberInt()); - case NumberLong: return compareDoubleToLong(l._numberDouble(), r._numberLong()); - default: invariant(false); + case NumberDouble: + return compareDoubles(l._numberDouble(), r._numberDouble()); + case NumberInt: + return compareDoubles(l._numberDouble(), r._numberInt()); + case NumberLong: + return compareDoubleToLong(l._numberDouble(), r._numberLong()); + default: + invariant(false); } } @@ -904,57 +910,60 @@ namespace mongo { int rsz = r.valuestrsize(); int common = std::min(lsz, rsz); int res = memcmp(l.valuestr(), r.valuestr(), common); - if( res ) + if (res) return res; // longer std::string is the greater one - return lsz-rsz; + return lsz - rsz; } case Object: case Array: - return l.embeddedObject().woCompare( r.embeddedObject() ); + return l.embeddedObject().woCompare(r.embeddedObject()); case DBRef: { int lsz = l.valuesize(); int rsz = r.valuesize(); - if ( lsz - rsz != 0 ) return lsz - rsz; + if (lsz - rsz != 0) + return lsz - rsz; return memcmp(l.value(), r.value(), lsz); } case BinData: { - int lsz = l.objsize(); // our bin data size in bytes, not including the subtype byte + int lsz = l.objsize(); // our bin data size in bytes, not including the subtype byte int rsz = r.objsize(); - if ( lsz - rsz != 0 ) return lsz - rsz; - return memcmp(l.value()+4, r.value()+4, lsz+1 /*+1 for subtype byte*/); + if (lsz - rsz != 0) + return lsz - rsz; + return memcmp(l.value() + 4, r.value() + 4, lsz + 1 /*+1 for subtype byte*/); } case RegEx: { int c = strcmp(l.regex(), r.regex()); - if ( c ) + if (c) return c; return strcmp(l.regexFlags(), r.regexFlags()); } - case CodeWScope : { - int cmp = StringData(l.codeWScopeCode(), l.codeWScopeCodeLen() - 1).compare( - StringData(r.codeWScopeCode(), r.codeWScopeCodeLen() - 1)); - if (cmp) return cmp; + case CodeWScope: { + int cmp = StringData(l.codeWScopeCode(), l.codeWScopeCodeLen() - 1) + .compare(StringData(r.codeWScopeCode(), r.codeWScopeCodeLen() - 1)); + if (cmp) + return cmp; return l.codeWScopeObject().woCompare(r.codeWScopeObject()); } default: - verify( false); - } - return -1; + verify(false); } - - size_t BSONElement::Hasher::operator()(const BSONElement& elem) const { - size_t hash = 0; + return -1; +} - boost::hash_combine(hash, elem.canonicalType()); +size_t BSONElement::Hasher::operator()(const BSONElement& elem) const { + size_t hash = 0; - const StringData fieldName = elem.fieldNameStringData(); - if (!fieldName.empty()) { - boost::hash_combine(hash, StringData::Hasher()(fieldName)); - } + boost::hash_combine(hash, elem.canonicalType()); + + const StringData fieldName = elem.fieldNameStringData(); + if (!fieldName.empty()) { + boost::hash_combine(hash, StringData::Hasher()(fieldName)); + } - switch (elem.type()) { - // Order of types is the same as in compareElementValues(). + switch (elem.type()) { + // Order of types is the same as in compareElementValues(). case mongo::EOO: case mongo::Undefined: @@ -986,8 +995,7 @@ namespace mongo { const double dbl = elem.numberDouble(); if (std::isnan(dbl)) { boost::hash_combine(hash, std::numeric_limits<double>::quiet_NaN()); - } - else { + } else { boost::hash_combine(hash, dbl); } break; @@ -1011,8 +1019,8 @@ namespace mongo { case mongo::DBRef: case mongo::BinData: // All bytes of the value are required to be identical. - boost::hash_combine(hash, StringData::Hasher()(StringData(elem.value(), - elem.valuesize()))); + boost::hash_combine(hash, + StringData::Hasher()(StringData(elem.value(), elem.valuesize()))); break; case mongo::RegEx: @@ -1021,14 +1029,14 @@ namespace mongo { break; case mongo::CodeWScope: { - boost::hash_combine(hash, StringData::Hasher()( - StringData(elem.codeWScopeCode(), - elem.codeWScopeCodeLen()))); + boost::hash_combine( + hash, + StringData::Hasher()(StringData(elem.codeWScopeCode(), elem.codeWScopeCodeLen()))); boost::hash_combine(hash, BSONObj::Hasher()(elem.codeWScopeObject())); break; } - } - return hash; } + return hash; +} -} // namespace mongo +} // namespace mongo diff --git a/src/mongo/bson/bsonelement.h b/src/mongo/bson/bsonelement.h index 25d9e895b49..9ee5513ddf9 100644 --- a/src/mongo/bson/bsonelement.h +++ b/src/mongo/bson/bsonelement.h @@ -30,7 +30,7 @@ #pragma once #include <cmath> -#include <string.h> // strlen +#include <string.h> // strlen #include <string> #include <vector> @@ -42,530 +42,590 @@ #include "mongo/platform/cstdint.h" namespace mongo { - class BSONObj; - class BSONElement; - class BSONObjBuilder; - class Timestamp; +class BSONObj; +class BSONElement; +class BSONObjBuilder; +class Timestamp; + +typedef BSONElement be; +typedef BSONObj bo; +typedef BSONObjBuilder bob; + +/* l and r MUST have same type when called: check that first. */ +int compareElementValues(const BSONElement& l, const BSONElement& r); + + +/** BSONElement represents an "element" in a BSONObj. So for the object { a : 3, b : "abc" }, + 'a : 3' is the first element (key+value). + + The BSONElement object points into the BSONObj's data. Thus the BSONObj must stay in scope + for the life of the BSONElement. + + internals: + <type><fieldName ><value> + -------- size() ------------ + -fieldNameSize- + value() + type() +*/ +class BSONElement { +public: + /** These functions, which start with a capital letter, throw a MsgAssertionException if the + element is not of the required type. Example: + + std::string foo = obj["foo"].String(); // std::exception if not a std::string type or DNE + */ + std::string String() const { + return chk(mongo::String).str(); + } + const StringData checkAndGetStringData() const { + return chk(mongo::String).valueStringData(); + } + Date_t Date() const { + return chk(mongo::Date).date(); + } + double Number() const { + return chk(isNumber()).number(); + } + double Double() const { + return chk(NumberDouble)._numberDouble(); + } + long long Long() const { + return chk(NumberLong)._numberLong(); + } + int Int() const { + return chk(NumberInt)._numberInt(); + } + bool Bool() const { + return chk(mongo::Bool).boolean(); + } + std::vector<BSONElement> Array() const; // see implementation for detailed comments + mongo::OID OID() const { + return chk(jstOID).__oid(); + } + void Null() const { + chk(isNull()); + } // throw MsgAssertionException if not null + void OK() const { + chk(ok()); + } // throw MsgAssertionException if element DNE + + /** @return the embedded object associated with this field. + Note the returned object is a reference to within the parent bson object. If that + object is out of scope, this pointer will no longer be valid. Call getOwned() on the + returned BSONObj if you need your own copy. + throws UserException if the element is not of type object. + */ + BSONObj Obj() const; - typedef BSONElement be; - typedef BSONObj bo; - typedef BSONObjBuilder bob; + /** populate v with the value of the element. If type does not match, throw exception. + useful in templates -- see also BSONObj::Vals(). + */ + void Val(Date_t& v) const { + v = Date(); + } + void Val(long long& v) const { + v = Long(); + } + void Val(bool& v) const { + v = Bool(); + } + void Val(BSONObj& v) const; + void Val(mongo::OID& v) const { + v = OID(); + } + void Val(int& v) const { + v = Int(); + } + void Val(double& v) const { + v = Double(); + } + void Val(std::string& v) const { + v = String(); + } - /* l and r MUST have same type when called: check that first. */ - int compareElementValues(const BSONElement& l, const BSONElement& r); + /** Use ok() to check if a value is assigned: + if( myObj["foo"].ok() ) ... + */ + bool ok() const { + return !eoo(); + } + /** + * True if this element has a value (ie not EOO). + * + * Makes it easier to check for a field's existence and use it: + * if (auto elem = myObj["foo"]) { + * // Use elem + * } + * else { + * // default behavior + * } + */ + explicit operator bool() const { + return ok(); + } - /** BSONElement represents an "element" in a BSONObj. So for the object { a : 3, b : "abc" }, - 'a : 3' is the first element (key+value). + std::string toString(bool includeFieldName = true, bool full = false) const; + void toString(StringBuilder& s, + bool includeFieldName = true, + bool full = false, + int depth = 0) const; + std::string jsonString(JsonStringFormat format, + bool includeFieldNames = true, + int pretty = 0) const; + operator std::string() const { + return toString(); + } - The BSONElement object points into the BSONObj's data. Thus the BSONObj must stay in scope - for the life of the BSONElement. + /** Returns the type of the element */ + BSONType type() const { + const signed char typeByte = ConstDataView(data).read<signed char>(); + return static_cast<BSONType>(typeByte); + } - internals: - <type><fieldName ><value> - -------- size() ------------ - -fieldNameSize- - value() - type() + /** retrieve a field within this element + throws exception if *this is not an embedded object */ - class BSONElement { - public: - /** These functions, which start with a capital letter, throw a MsgAssertionException if the - element is not of the required type. Example: - - std::string foo = obj["foo"].String(); // std::exception if not a std::string type or DNE - */ - std::string String() const { return chk(mongo::String).str(); } - const StringData checkAndGetStringData() const { - return chk(mongo::String).valueStringData(); - } - Date_t Date() const { return chk(mongo::Date).date(); } - double Number() const { return chk(isNumber()).number(); } - double Double() const { return chk(NumberDouble)._numberDouble(); } - long long Long() const { return chk(NumberLong)._numberLong(); } - int Int() const { return chk(NumberInt)._numberInt(); } - bool Bool() const { return chk(mongo::Bool).boolean(); } - std::vector<BSONElement> Array() const; // see implementation for detailed comments - mongo::OID OID() const { return chk(jstOID).__oid(); } - void Null() const { chk(isNull()); } // throw MsgAssertionException if not null - void OK() const { chk(ok()); } // throw MsgAssertionException if element DNE - - /** @return the embedded object associated with this field. - Note the returned object is a reference to within the parent bson object. If that - object is out of scope, this pointer will no longer be valid. Call getOwned() on the - returned BSONObj if you need your own copy. - throws UserException if the element is not of type object. - */ - BSONObj Obj() const; - - /** populate v with the value of the element. If type does not match, throw exception. - useful in templates -- see also BSONObj::Vals(). - */ - void Val(Date_t& v) const { v = Date(); } - void Val(long long& v) const { v = Long(); } - void Val(bool& v) const { v = Bool(); } - void Val(BSONObj& v) const; - void Val(mongo::OID& v) const { v = OID(); } - void Val(int& v) const { v = Int(); } - void Val(double& v) const { v = Double(); } - void Val(std::string& v) const { v = String(); } - - /** Use ok() to check if a value is assigned: - if( myObj["foo"].ok() ) ... - */ - bool ok() const { return !eoo(); } - - /** - * True if this element has a value (ie not EOO). - * - * Makes it easier to check for a field's existence and use it: - * if (auto elem = myObj["foo"]) { - * // Use elem - * } - * else { - * // default behavior - * } - */ - explicit operator bool() const { return ok(); } - - std::string toString( bool includeFieldName = true, bool full=false) const; - void toString(StringBuilder& s, bool includeFieldName = true, bool full=false, int depth=0) const; - std::string jsonString( JsonStringFormat format, bool includeFieldNames = true, int pretty = 0 ) const; - operator std::string() const { return toString(); } - - /** Returns the type of the element */ - BSONType type() const { - const signed char typeByte = ConstDataView(data).read<signed char>(); - return static_cast<BSONType>(typeByte); - } + BSONElement operator[](const std::string& field) const; - /** retrieve a field within this element - throws exception if *this is not an embedded object - */ - BSONElement operator[] (const std::string& field) const; - - /** See canonicalizeBSONType in bsontypes.h */ - int canonicalType() const { return canonicalizeBSONType(type()); } - - /** Indicates if it is the end-of-object element, which is present at the end of - every BSON object. - */ - bool eoo() const { return type() == EOO; } - - /** Size of the element. - @param maxLen If maxLen is specified, don't scan more than maxLen bytes to calculate size. - */ - int size( int maxLen ) const; - int size() const; - - /** Wrap this element up as a singleton object. */ - BSONObj wrap() const; - - /** Wrap this element up as a singleton object with a new name. */ - BSONObj wrap( StringData newName) const; - - /** field name of the element. e.g., for - name : "Joe" - "name" is the fieldname - */ - const char * fieldName() const { - if ( eoo() ) return ""; // no fieldname for it. - return data + 1; - } + /** See canonicalizeBSONType in bsontypes.h */ + int canonicalType() const { + return canonicalizeBSONType(type()); + } - /** - * NOTE: size includes the NULL terminator. - */ - int fieldNameSize() const { - if ( fieldNameSize_ == -1 ) - fieldNameSize_ = (int)strlen( fieldName() ) + 1; - return fieldNameSize_; - } + /** Indicates if it is the end-of-object element, which is present at the end of + every BSON object. + */ + bool eoo() const { + return type() == EOO; + } - const StringData fieldNameStringData() const { - return StringData(fieldName(), eoo() ? 0 : fieldNameSize() - 1); - } + /** Size of the element. + @param maxLen If maxLen is specified, don't scan more than maxLen bytes to calculate size. + */ + int size(int maxLen) const; + int size() const; - /** raw data of the element's value (so be careful). */ - const char * value() const { - return (data + fieldNameSize() + 1); - } - /** size in bytes of the element's value (when applicable). */ - int valuesize() const { - return size() - fieldNameSize() - 1; - } + /** Wrap this element up as a singleton object. */ + BSONObj wrap() const; - bool isBoolean() const { return type() == mongo::Bool; } + /** Wrap this element up as a singleton object with a new name. */ + BSONObj wrap(StringData newName) const; - /** @return value of a boolean element. - You must assure element is a boolean before - calling. */ - bool boolean() const { - return *value() ? true : false; - } + /** field name of the element. e.g., for + name : "Joe" + "name" is the fieldname + */ + const char* fieldName() const { + if (eoo()) + return ""; // no fieldname for it. + return data + 1; + } - bool booleanSafe() const { return isBoolean() && boolean(); } + /** + * NOTE: size includes the NULL terminator. + */ + int fieldNameSize() const { + if (fieldNameSize_ == -1) + fieldNameSize_ = (int)strlen(fieldName()) + 1; + return fieldNameSize_; + } - /** Retrieve a java style date value from the element. - Ensure element is of type Date before calling. - @see Bool(), trueValue() - */ - Date_t date() const { - return Date_t::fromMillisSinceEpoch( - ConstDataView(value()).read<LittleEndian<long long>>()); - } + const StringData fieldNameStringData() const { + return StringData(fieldName(), eoo() ? 0 : fieldNameSize() - 1); + } - /** Convert the value to boolean, regardless of its type, in a javascript-like fashion - (i.e., treats zero and null and eoo as false). - */ - bool trueValue() const; + /** raw data of the element's value (so be careful). */ + const char* value() const { + return (data + fieldNameSize() + 1); + } + /** size in bytes of the element's value (when applicable). */ + int valuesize() const { + return size() - fieldNameSize() - 1; + } - /** True if number, string, bool, date, OID */ - bool isSimpleType() const; + bool isBoolean() const { + return type() == mongo::Bool; + } - /** True if element is of a numeric type. */ - bool isNumber() const; + /** @return value of a boolean element. + You must assure element is a boolean before + calling. */ + bool boolean() const { + return *value() ? true : false; + } - /** Return double value for this field. MUST be NumberDouble type. */ - double _numberDouble() const { - return ConstDataView(value()).read<LittleEndian<double>>(); - } + bool booleanSafe() const { + return isBoolean() && boolean(); + } - /** Return int value for this field. MUST be NumberInt type. */ - int _numberInt() const { - return ConstDataView(value()).read<LittleEndian<int>>(); - } + /** Retrieve a java style date value from the element. + Ensure element is of type Date before calling. + @see Bool(), trueValue() + */ + Date_t date() const { + return Date_t::fromMillisSinceEpoch(ConstDataView(value()).read<LittleEndian<long long>>()); + } - /** Return long long value for this field. MUST be NumberLong type. */ - long long _numberLong() const { - return ConstDataView(value()).read<LittleEndian<long long>>(); - } + /** Convert the value to boolean, regardless of its type, in a javascript-like fashion + (i.e., treats zero and null and eoo as false). + */ + bool trueValue() const; - /** Retrieve int value for the element safely. Zero returned if not a number. */ - int numberInt() const; - /** Retrieve long value for the element safely. Zero returned if not a number. - * Behavior is not defined for double values that are NaNs, or too large/small - * to be represented by long longs */ - long long numberLong() const; - - /** Like numberLong() but with well-defined behavior for doubles that - * are NaNs, or too large/small to be represented as long longs. - * NaNs -> 0 - * very large doubles -> LLONG_MAX - * very small doubles -> LLONG_MIN */ - long long safeNumberLong() const; - - /** Retrieve the numeric value of the element. If not of a numeric type, returns 0. - Note: casts to double, data loss may occur with large (>52 bit) NumberLong values. - */ - double numberDouble() const; - /** Retrieve the numeric value of the element. If not of a numeric type, returns 0. - Note: casts to double, data loss may occur with large (>52 bit) NumberLong values. - */ - double number() const { return numberDouble(); } - - /** Retrieve the object ID stored in the object. - You must ensure the element is of type jstOID first. */ - mongo::OID __oid() const { - return OID::from(value()); - } + /** True if number, string, bool, date, OID */ + bool isSimpleType() const; - /** True if element is null. */ - bool isNull() const { - return type() == jstNULL; - } + /** True if element is of a numeric type. */ + bool isNumber() const; - /** Size (length) of a std::string element. - You must assure of type std::string first. - @return std::string size including terminating null - */ - int valuestrsize() const { - return ConstDataView(value()).read<LittleEndian<int>>(); - } + /** Return double value for this field. MUST be NumberDouble type. */ + double _numberDouble() const { + return ConstDataView(value()).read<LittleEndian<double>>(); + } - // for objects the size *includes* the size of the size field - size_t objsize() const { - return ConstDataView(value()).read<LittleEndian<uint32_t>>(); - } + /** Return int value for this field. MUST be NumberInt type. */ + int _numberInt() const { + return ConstDataView(value()).read<LittleEndian<int>>(); + } - /** Get a string's value. Also gives you start of the real data for an embedded object. - You must assure data is of an appropriate type first -- see also valuestrsafe(). - */ - const char * valuestr() const { - return value() + 4; - } + /** Return long long value for this field. MUST be NumberLong type. */ + long long _numberLong() const { + return ConstDataView(value()).read<LittleEndian<long long>>(); + } - /** Get the std::string value of the element. If not a std::string returns "". */ - const char *valuestrsafe() const { - return type() == mongo::String ? valuestr() : ""; - } - /** Get the std::string value of the element. If not a std::string returns "". */ - std::string str() const { - return type() == mongo::String ? std::string(valuestr(), valuestrsize()-1) : std::string(); - } + /** Retrieve int value for the element safely. Zero returned if not a number. */ + int numberInt() const; + /** Retrieve long value for the element safely. Zero returned if not a number. + * Behavior is not defined for double values that are NaNs, or too large/small + * to be represented by long longs */ + long long numberLong() const; - /** - * Returns a StringData pointing into this element's data. Does not validate that the - * element is actually of type String. - */ - const StringData valueStringData() const { - return StringData(valuestr(), valuestrsize() - 1); - } + /** Like numberLong() but with well-defined behavior for doubles that + * are NaNs, or too large/small to be represented as long longs. + * NaNs -> 0 + * very large doubles -> LLONG_MAX + * very small doubles -> LLONG_MIN */ + long long safeNumberLong() const; - /** Get javascript code of a CodeWScope data element. */ - const char * codeWScopeCode() const { - massert( 16177 , "not codeWScope" , type() == CodeWScope ); - return value() + 4 + 4; //two ints precede code (see BSON spec) - } + /** Retrieve the numeric value of the element. If not of a numeric type, returns 0. + Note: casts to double, data loss may occur with large (>52 bit) NumberLong values. + */ + double numberDouble() const; + /** Retrieve the numeric value of the element. If not of a numeric type, returns 0. + Note: casts to double, data loss may occur with large (>52 bit) NumberLong values. + */ + double number() const { + return numberDouble(); + } - /** Get length of the code part of the CodeWScope object - * This INCLUDES the null char at the end */ - int codeWScopeCodeLen() const { - massert( 16178 , "not codeWScope" , type() == CodeWScope ); - return ConstDataView(value() + 4).read<LittleEndian<int>>(); - } + /** Retrieve the object ID stored in the object. + You must ensure the element is of type jstOID first. */ + mongo::OID __oid() const { + return OID::from(value()); + } - /** Get the scope SavedContext of a CodeWScope data element. - * - * This function is DEPRECATED, since it can error if there are - * null chars in the codeWScopeCode. However, some existing indexes - * may be based on an incorrect ordering derived from this function, - * so it may still need to be used in certain cases. - * */ - const char * codeWScopeScopeDataUnsafe() const { - //This can error if there are null chars in the codeWScopeCode - return codeWScopeCode() + strlen( codeWScopeCode() ) + 1; - } + /** True if element is null. */ + bool isNull() const { + return type() == jstNULL; + } - /* Get the scope SavedContext of a CodeWScope data element. - * - * This is the corrected version of codeWScopeScopeDataUnsafe(), - * but note that existing uses might rely on the behavior of - * that function so be careful in choosing which version to use. - */ - const char * codeWScopeScopeData() const { - return codeWScopeCode() + codeWScopeCodeLen(); - } + /** Size (length) of a std::string element. + You must assure of type std::string first. + @return std::string size including terminating null + */ + int valuestrsize() const { + return ConstDataView(value()).read<LittleEndian<int>>(); + } - /** Get the embedded object this element holds. */ - BSONObj embeddedObject() const; + // for objects the size *includes* the size of the size field + size_t objsize() const { + return ConstDataView(value()).read<LittleEndian<uint32_t>>(); + } - /* uasserts if not an object */ - BSONObj embeddedObjectUserCheck() const; + /** Get a string's value. Also gives you start of the real data for an embedded object. + You must assure data is of an appropriate type first -- see also valuestrsafe(). + */ + const char* valuestr() const { + return value() + 4; + } - BSONObj codeWScopeObject() const; + /** Get the std::string value of the element. If not a std::string returns "". */ + const char* valuestrsafe() const { + return type() == mongo::String ? valuestr() : ""; + } + /** Get the std::string value of the element. If not a std::string returns "". */ + std::string str() const { + return type() == mongo::String ? std::string(valuestr(), valuestrsize() - 1) + : std::string(); + } - /** Get raw binary data. Element must be of type BinData. Doesn't handle type 2 specially */ - const char *binData(int& len) const { - // BinData: <int len> <byte subtype> <byte[len] data> - verify( type() == BinData ); - len = valuestrsize(); - return value() + 5; - } - /** Get binary data. Element must be of type BinData. Handles type 2 */ - const char *binDataClean(int& len) const { - // BinData: <int len> <byte subtype> <byte[len] data> - if (binDataType() != ByteArrayDeprecated) { - return binData(len); - } - else { - // Skip extra size - len = valuestrsize() - 4; - return value() + 5 + 4; - } - } + /** + * Returns a StringData pointing into this element's data. Does not validate that the + * element is actually of type String. + */ + const StringData valueStringData() const { + return StringData(valuestr(), valuestrsize() - 1); + } - BinDataType binDataType() const { - // BinData: <int len> <byte subtype> <byte[len] data> - verify( type() == BinData ); - unsigned char c = (value() + 4)[0]; - return (BinDataType)c; - } + /** Get javascript code of a CodeWScope data element. */ + const char* codeWScopeCode() const { + massert(16177, "not codeWScope", type() == CodeWScope); + return value() + 4 + 4; // two ints precede code (see BSON spec) + } - /** Retrieve the regex std::string for a Regex element */ - const char *regex() const { - verify(type() == RegEx); - return value(); - } + /** Get length of the code part of the CodeWScope object + * This INCLUDES the null char at the end */ + int codeWScopeCodeLen() const { + massert(16178, "not codeWScope", type() == CodeWScope); + return ConstDataView(value() + 4).read<LittleEndian<int>>(); + } - /** Retrieve the regex flags (options) for a Regex element */ - const char *regexFlags() const { - const char *p = regex(); - return p + strlen(p) + 1; - } + /** Get the scope SavedContext of a CodeWScope data element. + * + * This function is DEPRECATED, since it can error if there are + * null chars in the codeWScopeCode. However, some existing indexes + * may be based on an incorrect ordering derived from this function, + * so it may still need to be used in certain cases. + * */ + const char* codeWScopeScopeDataUnsafe() const { + // This can error if there are null chars in the codeWScopeCode + return codeWScopeCode() + strlen(codeWScopeCode()) + 1; + } - /** like operator== but doesn't check the fieldname, - just the value. - */ - bool valuesEqual(const BSONElement& r) const { - return woCompare( r , false ) == 0; - } + /* Get the scope SavedContext of a CodeWScope data element. + * + * This is the corrected version of codeWScopeScopeDataUnsafe(), + * but note that existing uses might rely on the behavior of + * that function so be careful in choosing which version to use. + */ + const char* codeWScopeScopeData() const { + return codeWScopeCode() + codeWScopeCodeLen(); + } - /** Returns true if elements are equal. */ - bool operator==(const BSONElement& r) const { - return woCompare( r , true ) == 0; + /** Get the embedded object this element holds. */ + BSONObj embeddedObject() const; + + /* uasserts if not an object */ + BSONObj embeddedObjectUserCheck() const; + + BSONObj codeWScopeObject() const; + + /** Get raw binary data. Element must be of type BinData. Doesn't handle type 2 specially */ + const char* binData(int& len) const { + // BinData: <int len> <byte subtype> <byte[len] data> + verify(type() == BinData); + len = valuestrsize(); + return value() + 5; + } + /** Get binary data. Element must be of type BinData. Handles type 2 */ + const char* binDataClean(int& len) const { + // BinData: <int len> <byte subtype> <byte[len] data> + if (binDataType() != ByteArrayDeprecated) { + return binData(len); + } else { + // Skip extra size + len = valuestrsize() - 4; + return value() + 5 + 4; } - /** Returns true if elements are unequal. */ - bool operator!=(const BSONElement& r) const { return !operator==(r); } - - /** Well ordered comparison. - @return <0: l<r. 0:l==r. >0:l>r - order by type, field name, and field value. - If considerFieldName is true, pay attention to the field name. - */ - int woCompare( const BSONElement &e, bool considerFieldName = true ) const; - - /** - * Functor compatible with std::hash for std::unordered_{map,set} - * Warning: The hash function is subject to change. Do not use in cases where hashes need - * to be consistent across versions. - */ - struct Hasher { - size_t operator() (const BSONElement& elem) const; - }; - - const char * rawdata() const { return data; } - - /** 0 == Equality, just not defined yet */ - int getGtLtOp( int def = 0 ) const; - - /** Constructs an empty element */ - BSONElement(); - - /** True if this element may contain subobjects. */ - bool mayEncapsulate() const { - switch ( type() ) { + } + + BinDataType binDataType() const { + // BinData: <int len> <byte subtype> <byte[len] data> + verify(type() == BinData); + unsigned char c = (value() + 4)[0]; + return (BinDataType)c; + } + + /** Retrieve the regex std::string for a Regex element */ + const char* regex() const { + verify(type() == RegEx); + return value(); + } + + /** Retrieve the regex flags (options) for a Regex element */ + const char* regexFlags() const { + const char* p = regex(); + return p + strlen(p) + 1; + } + + /** like operator== but doesn't check the fieldname, + just the value. + */ + bool valuesEqual(const BSONElement& r) const { + return woCompare(r, false) == 0; + } + + /** Returns true if elements are equal. */ + bool operator==(const BSONElement& r) const { + return woCompare(r, true) == 0; + } + /** Returns true if elements are unequal. */ + bool operator!=(const BSONElement& r) const { + return !operator==(r); + } + + /** Well ordered comparison. + @return <0: l<r. 0:l==r. >0:l>r + order by type, field name, and field value. + If considerFieldName is true, pay attention to the field name. + */ + int woCompare(const BSONElement& e, bool considerFieldName = true) const; + + /** + * Functor compatible with std::hash for std::unordered_{map,set} + * Warning: The hash function is subject to change. Do not use in cases where hashes need + * to be consistent across versions. + */ + struct Hasher { + size_t operator()(const BSONElement& elem) const; + }; + + const char* rawdata() const { + return data; + } + + /** 0 == Equality, just not defined yet */ + int getGtLtOp(int def = 0) const; + + /** Constructs an empty element */ + BSONElement(); + + /** True if this element may contain subobjects. */ + bool mayEncapsulate() const { + switch (type()) { case Object: case mongo::Array: case CodeWScope: return true; default: return false; - } } + } - /** True if this element can be a BSONObj */ - bool isABSONObj() const { - switch( type() ) { + /** True if this element can be a BSONObj */ + bool isABSONObj() const { + switch (type()) { case Object: case mongo::Array: return true; default: return false; - } - } - - Timestamp timestamp() const { - if( type() == mongo::Date || type() == bsonTimestamp ) { - return Timestamp( - ConstDataView(value()).read<LittleEndian<unsigned long long>>().value); - } - return Timestamp(); } + } - Date_t timestampTime() const { - unsigned long long t = ConstDataView(value() + 4).read<LittleEndian<unsigned int>>(); - return Date_t::fromMillisSinceEpoch(t * 1000); - } - unsigned int timestampInc() const { - return ConstDataView(value()).read<LittleEndian<unsigned int>>(); + Timestamp timestamp() const { + if (type() == mongo::Date || type() == bsonTimestamp) { + return Timestamp(ConstDataView(value()).read<LittleEndian<unsigned long long>>().value); } + return Timestamp(); + } - unsigned long long timestampValue() const { - return ConstDataView(value()).read<LittleEndian<unsigned long long>>(); - } + Date_t timestampTime() const { + unsigned long long t = ConstDataView(value() + 4).read<LittleEndian<unsigned int>>(); + return Date_t::fromMillisSinceEpoch(t * 1000); + } + unsigned int timestampInc() const { + return ConstDataView(value()).read<LittleEndian<unsigned int>>(); + } - const char * dbrefNS() const { - uassert( 10063 , "not a dbref" , type() == DBRef ); - return value() + 4; - } + unsigned long long timestampValue() const { + return ConstDataView(value()).read<LittleEndian<unsigned long long>>(); + } - const mongo::OID dbrefOID() const { - uassert( 10064 , "not a dbref" , type() == DBRef ); - const char * start = value(); - start += 4 + ConstDataView(start).read<LittleEndian<int>>(); - return mongo::OID::from(start); - } + const char* dbrefNS() const { + uassert(10063, "not a dbref", type() == DBRef); + return value() + 4; + } - /** this does not use fieldName in the comparison, just the value */ - bool operator<( const BSONElement& other ) const { - int x = (int)canonicalType() - (int)other.canonicalType(); - if ( x < 0 ) return true; - else if ( x > 0 ) return false; - return compareElementValues(*this,other) < 0; - } + const mongo::OID dbrefOID() const { + uassert(10064, "not a dbref", type() == DBRef); + const char* start = value(); + start += 4 + ConstDataView(start).read<LittleEndian<int>>(); + return mongo::OID::from(start); + } - // @param maxLen don't scan more than maxLen bytes - explicit BSONElement(const char *d, int maxLen) : data(d) { - if ( eoo() ) { - totalSize = 1; - fieldNameSize_ = 0; - } - else { - totalSize = -1; - fieldNameSize_ = -1; - if ( maxLen != -1 ) { - size_t size = strnlen( fieldName(), maxLen - 1 ); - uassert( 10333 , "Invalid field name", size < size_t(maxLen - 1) ); - fieldNameSize_ = size + 1; - } - } - } + /** this does not use fieldName in the comparison, just the value */ + bool operator<(const BSONElement& other) const { + int x = (int)canonicalType() - (int)other.canonicalType(); + if (x < 0) + return true; + else if (x > 0) + return false; + return compareElementValues(*this, other) < 0; + } - explicit BSONElement(const char *d) : data(d) { - fieldNameSize_ = -1; + // @param maxLen don't scan more than maxLen bytes + explicit BSONElement(const char* d, int maxLen) : data(d) { + if (eoo()) { + totalSize = 1; + fieldNameSize_ = 0; + } else { totalSize = -1; - if ( eoo() ) { - fieldNameSize_ = 0; - totalSize = 1; + fieldNameSize_ = -1; + if (maxLen != -1) { + size_t size = strnlen(fieldName(), maxLen - 1); + uassert(10333, "Invalid field name", size < size_t(maxLen - 1)); + fieldNameSize_ = size + 1; } } + } - struct FieldNameSizeTag {}; // For disambiguation with ctor taking 'maxLen' above. - - /** Construct a BSONElement where you already know the length of the name. The value - * passed here includes the null terminator. The data pointed to by 'd' must not - * represent an EOO. You may pass -1 to indicate that you don't actually know the - * size. - */ - BSONElement(const char* d, int fieldNameSize, FieldNameSizeTag) - : data(d) - , fieldNameSize_(fieldNameSize) // internal size includes null terminator - , totalSize(-1) { + explicit BSONElement(const char* d) : data(d) { + fieldNameSize_ = -1; + totalSize = -1; + if (eoo()) { + fieldNameSize_ = 0; + totalSize = 1; } + } - std::string _asCode() const; - - template<typename T> bool coerce( T* out ) const; - - private: - const char *data; - mutable int fieldNameSize_; // cached value - - mutable int totalSize; /* caches the computed size */ - - friend class BSONObjIterator; - friend class BSONObj; - const BSONElement& chk(int t) const { - if ( t != type() ) { - StringBuilder ss; - if( eoo() ) - ss << "field not found, expected type " << t; - else - ss << "wrong type for field (" << fieldName() << ") " << type() << " != " << t; - msgasserted(13111, ss.str() ); - } - return *this; - } - const BSONElement& chk(bool expr) const { - massert(13118, "unexpected or missing type value in BSON object", expr); - return *this; - } - }; + struct FieldNameSizeTag {}; // For disambiguation with ctor taking 'maxLen' above. + + /** Construct a BSONElement where you already know the length of the name. The value + * passed here includes the null terminator. The data pointed to by 'd' must not + * represent an EOO. You may pass -1 to indicate that you don't actually know the + * size. + */ + BSONElement(const char* d, int fieldNameSize, FieldNameSizeTag) + : data(d), + fieldNameSize_(fieldNameSize) // internal size includes null terminator + , + totalSize(-1) {} + + std::string _asCode() const; + + template <typename T> + bool coerce(T* out) const; + +private: + const char* data; + mutable int fieldNameSize_; // cached value + + mutable int totalSize; /* caches the computed size */ + + friend class BSONObjIterator; + friend class BSONObj; + const BSONElement& chk(int t) const { + if (t != type()) { + StringBuilder ss; + if (eoo()) + ss << "field not found, expected type " << t; + else + ss << "wrong type for field (" << fieldName() << ") " << type() << " != " << t; + msgasserted(13111, ss.str()); + } + return *this; + } + const BSONElement& chk(bool expr) const { + massert(13118, "unexpected or missing type value in BSON object", expr); + return *this; + } +}; - inline bool BSONElement::trueValue() const { - // NOTE Behavior changes must be replicated in Value::coerceToBool(). - switch( type() ) { +inline bool BSONElement::trueValue() const { + // NOTE Behavior changes must be replicated in Value::coerceToBool(). + switch (type()) { case NumberLong: return _numberLong() != 0; case NumberDouble: @@ -581,24 +641,24 @@ namespace mongo { default: ; - } - return true; } + return true; +} - /** @return true if element is of a numeric type. */ - inline bool BSONElement::isNumber() const { - switch( type() ) { +/** @return true if element is of a numeric type. */ +inline bool BSONElement::isNumber() const { + switch (type()) { case NumberLong: case NumberDouble: case NumberInt: return true; default: return false; - } } +} - inline bool BSONElement::isSimpleType() const { - switch( type() ) { +inline bool BSONElement::isSimpleType() const { + switch (type()) { case NumberLong: case NumberDouble: case NumberInt: @@ -609,11 +669,11 @@ namespace mongo { return true; default: return false; - } } +} - inline double BSONElement::numberDouble() const { - switch( type() ) { +inline double BSONElement::numberDouble() const { + switch (type()) { case NumberDouble: return _numberDouble(); case NumberInt: @@ -622,69 +682,68 @@ namespace mongo { return _numberLong(); default: return 0; - } } +} - /** Retrieve int value for the element safely. Zero returned if not a number. Converted to int if another numeric type. */ - inline int BSONElement::numberInt() const { - switch( type() ) { +/** Retrieve int value for the element safely. Zero returned if not a number. Converted to int if another numeric type. */ +inline int BSONElement::numberInt() const { + switch (type()) { case NumberDouble: - return (int) _numberDouble(); + return (int)_numberDouble(); case NumberInt: return _numberInt(); case NumberLong: - return (int) _numberLong(); + return (int)_numberLong(); default: return 0; - } } +} - /** Retrieve long value for the element safely. Zero returned if not a number. */ - inline long long BSONElement::numberLong() const { - switch( type() ) { +/** Retrieve long value for the element safely. Zero returned if not a number. */ +inline long long BSONElement::numberLong() const { + switch (type()) { case NumberDouble: - return (long long) _numberDouble(); + return (long long)_numberDouble(); case NumberInt: return _numberInt(); case NumberLong: return _numberLong(); default: return 0; - } } +} - /** Like numberLong() but with well-defined behavior for doubles that - * are NaNs, or too large/small to be represented as long longs. - * NaNs -> 0 - * very large doubles -> LLONG_MAX - * very small doubles -> LLONG_MIN */ - inline long long BSONElement::safeNumberLong() const { - double d; - switch( type() ) { +/** Like numberLong() but with well-defined behavior for doubles that + * are NaNs, or too large/small to be represented as long longs. + * NaNs -> 0 + * very large doubles -> LLONG_MAX + * very small doubles -> LLONG_MIN */ +inline long long BSONElement::safeNumberLong() const { + double d; + switch (type()) { case NumberDouble: d = numberDouble(); - if ( std::isnan( d ) ){ + if (std::isnan(d)) { return 0; } - if ( d > (double) std::numeric_limits<long long>::max() ){ + if (d > (double)std::numeric_limits<long long>::max()) { return std::numeric_limits<long long>::max(); } - if ( d < std::numeric_limits<long long>::min() ){ + if (d < std::numeric_limits<long long>::min()) { return std::numeric_limits<long long>::min(); } default: return numberLong(); - } - } - - inline BSONElement::BSONElement() { - static const char kEooElement[] = ""; - data = kEooElement; - fieldNameSize_ = 0; - totalSize = 1; } +} - // TODO(SERVER-14596): move to a better place; take a StringData. - std::string escape( const std::string& s , bool escape_slash=false); +inline BSONElement::BSONElement() { + static const char kEooElement[] = ""; + data = kEooElement; + fieldNameSize_ = 0; + totalSize = 1; +} +// TODO(SERVER-14596): move to a better place; take a StringData. +std::string escape(const std::string& s, bool escape_slash = false); } diff --git a/src/mongo/bson/bsonmisc.cpp b/src/mongo/bson/bsonmisc.cpp index 9e7be1b2353..b106121b590 100644 --- a/src/mongo/bson/bsonmisc.cpp +++ b/src/mongo/bson/bsonmisc.cpp @@ -31,87 +31,87 @@ namespace mongo { - int getGtLtOp(const BSONElement& e) { - if ( e.type() != Object ) - return BSONObj::Equality; +int getGtLtOp(const BSONElement& e) { + if (e.type() != Object) + return BSONObj::Equality; - BSONElement fe = e.embeddedObject().firstElement(); - return fe.getGtLtOp(); - } + BSONElement fe = e.embeddedObject().firstElement(); + return fe.getGtLtOp(); +} - bool fieldsMatch(const BSONObj& lhs, const BSONObj& rhs) { - BSONObjIterator l(lhs); - BSONObjIterator r(rhs); +bool fieldsMatch(const BSONObj& lhs, const BSONObj& rhs) { + BSONObjIterator l(lhs); + BSONObjIterator r(rhs); - while (l.more() && r.more()){ - if (strcmp(l.next().fieldName(), r.next().fieldName())) { - return false; - } + while (l.more() && r.more()) { + if (strcmp(l.next().fieldName(), r.next().fieldName())) { + return false; } - - return !(l.more() || r.more()); // false if lhs and rhs have diff nFields() - } - - Labeler::Label GT( "$gt" ); - Labeler::Label GTE( "$gte" ); - Labeler::Label LT( "$lt" ); - Labeler::Label LTE( "$lte" ); - Labeler::Label NE( "$ne" ); - Labeler::Label NIN( "$nin" ); - Labeler::Label BSIZE( "$size" ); - - GENOIDLabeler GENOID; - DateNowLabeler DATENOW; - NullLabeler BSONNULL; - UndefinedLabeler BSONUndefined; - - MinKeyLabeler MINKEY; - MaxKeyLabeler MAXKEY; - - BSONObjBuilderValueStream::BSONObjBuilderValueStream( BSONObjBuilder * builder ) { - _builder = builder; - } - - BSONObjBuilder& BSONObjBuilderValueStream::operator<<( const BSONElement& e ) { - _builder->appendAs( e , _fieldName ); - _fieldName = StringData(); - return *_builder; } - BufBuilder& BSONObjBuilderValueStream::subobjStart() { - StringData tmp = _fieldName; - _fieldName = StringData(); - return _builder->subobjStart(tmp); + return !(l.more() || r.more()); // false if lhs and rhs have diff nFields() +} + +Labeler::Label GT("$gt"); +Labeler::Label GTE("$gte"); +Labeler::Label LT("$lt"); +Labeler::Label LTE("$lte"); +Labeler::Label NE("$ne"); +Labeler::Label NIN("$nin"); +Labeler::Label BSIZE("$size"); + +GENOIDLabeler GENOID; +DateNowLabeler DATENOW; +NullLabeler BSONNULL; +UndefinedLabeler BSONUndefined; + +MinKeyLabeler MINKEY; +MaxKeyLabeler MAXKEY; + +BSONObjBuilderValueStream::BSONObjBuilderValueStream(BSONObjBuilder* builder) { + _builder = builder; +} + +BSONObjBuilder& BSONObjBuilderValueStream::operator<<(const BSONElement& e) { + _builder->appendAs(e, _fieldName); + _fieldName = StringData(); + return *_builder; +} + +BufBuilder& BSONObjBuilderValueStream::subobjStart() { + StringData tmp = _fieldName; + _fieldName = StringData(); + return _builder->subobjStart(tmp); +} + +BufBuilder& BSONObjBuilderValueStream::subarrayStart() { + StringData tmp = _fieldName; + _fieldName = StringData(); + return _builder->subarrayStart(tmp); +} + +Labeler BSONObjBuilderValueStream::operator<<(const Labeler::Label& l) { + return Labeler(l, this); +} + +void BSONObjBuilderValueStream::endField(StringData nextFieldName) { + if (haveSubobj()) { + verify(_fieldName.rawData()); + _builder->append(_fieldName, subobj()->done()); + _subobj.reset(); } + _fieldName = nextFieldName; +} - BufBuilder& BSONObjBuilderValueStream::subarrayStart() { - StringData tmp = _fieldName; - _fieldName = StringData(); - return _builder->subarrayStart(tmp); - } - - Labeler BSONObjBuilderValueStream::operator<<( const Labeler::Label &l ) { - return Labeler( l, this ); - } +BSONObjBuilder* BSONObjBuilderValueStream::subobj() { + if (!haveSubobj()) + _subobj.reset(new BSONObjBuilder()); + return _subobj.get(); +} - void BSONObjBuilderValueStream::endField( StringData nextFieldName ) { - if ( haveSubobj() ) { - verify( _fieldName.rawData() ); - _builder->append( _fieldName, subobj()->done() ); - _subobj.reset(); - } - _fieldName = nextFieldName; - } - - BSONObjBuilder *BSONObjBuilderValueStream::subobj() { - if ( !haveSubobj() ) - _subobj.reset( new BSONObjBuilder() ); - return _subobj.get(); - } - - BSONObjBuilder& Labeler::operator<<( const BSONElement& e ) { - s_->subobj()->appendAs( e, l_.l_ ); - return *s_->_builder; - } +BSONObjBuilder& Labeler::operator<<(const BSONElement& e) { + s_->subobj()->appendAs(e, l_.l_); + return *s_->_builder; +} -} // namespace mongo +} // namespace mongo diff --git a/src/mongo/bson/bsonmisc.h b/src/mongo/bson/bsonmisc.h index 3c9e5259161..baa7abad5e7 100644 --- a/src/mongo/bson/bsonmisc.h +++ b/src/mongo/bson/bsonmisc.h @@ -34,254 +34,271 @@ namespace mongo { - int getGtLtOp(const BSONElement& e); +int getGtLtOp(const BSONElement& e); - struct BSONElementCmpWithoutField { - bool operator()( const BSONElement &l, const BSONElement &r ) const { - return l.woCompare( r, false ) < 0; - } - }; - - class BSONObjCmp { - public: - BSONObjCmp( const BSONObj &order = BSONObj() ) : _order( order ) {} - bool operator()( const BSONObj &l, const BSONObj &r ) const { - return l.woCompare( r, _order ) < 0; - } - BSONObj order() const { return _order; } - private: - BSONObj _order; - }; - - typedef std::set<BSONObj,BSONObjCmp> BSONObjSet; - - enum FieldCompareResult { - LEFT_SUBFIELD = -2, - LEFT_BEFORE = -1, - SAME = 0, - RIGHT_BEFORE = 1 , - RIGHT_SUBFIELD = 2 - }; - - /** Use BSON macro to build a BSONObj from a stream - - e.g., - BSON( "name" << "joe" << "age" << 33 ) - - with auto-generated object id: - BSON( GENOID << "name" << "joe" << "age" << 33 ) - - The labels GT, GTE, LT, LTE, NE can be helpful for stream-oriented construction - of a BSONObj, particularly when assembling a Query. For example, - BSON( "a" << GT << 23.4 << NE << 30 << "b" << 2 ) produces the object - { a: { \$gt: 23.4, \$ne: 30 }, b: 2 }. - */ -#define BSON(x) (( ::mongo::BSONObjBuilder(64) << x ).obj()) +struct BSONElementCmpWithoutField { + bool operator()(const BSONElement& l, const BSONElement& r) const { + return l.woCompare(r, false) < 0; + } +}; - /** Use BSON_ARRAY macro like BSON macro, but without keys +class BSONObjCmp { +public: + BSONObjCmp(const BSONObj& order = BSONObj()) : _order(order) {} + bool operator()(const BSONObj& l, const BSONObj& r) const { + return l.woCompare(r, _order) < 0; + } + BSONObj order() const { + return _order; + } - BSONArray arr = BSON_ARRAY( "hello" << 1 << BSON( "foo" << BSON_ARRAY( "bar" << "baz" << "qux" ) ) ); +private: + BSONObj _order; +}; - */ -#define BSON_ARRAY(x) (( ::mongo::BSONArrayBuilder() << x ).arr()) +typedef std::set<BSONObj, BSONObjCmp> BSONObjSet; - /* Utility class to auto assign object IDs. - Example: - std::cout << BSON( GENOID << "z" << 3 ); // { _id : ..., z : 3 } - */ - struct GENOIDLabeler { }; - extern GENOIDLabeler GENOID; +enum FieldCompareResult { + LEFT_SUBFIELD = -2, + LEFT_BEFORE = -1, + SAME = 0, + RIGHT_BEFORE = 1, + RIGHT_SUBFIELD = 2 +}; - /* Utility class to add a Date element with the current time - Example: - std::cout << BSON( "created" << DATENOW ); // { created : "2009-10-09 11:41:42" } - */ - struct DateNowLabeler { }; - extern DateNowLabeler DATENOW; +/** Use BSON macro to build a BSONObj from a stream - /* Utility class to assign a NULL value to a given attribute - Example: - std::cout << BSON( "a" << BSONNULL ); // { a : null } - */ - struct NullLabeler { }; - extern NullLabeler BSONNULL; + e.g., + BSON( "name" << "joe" << "age" << 33 ) - /* Utility class to assign an Undefined value to a given attribute - Example: - std::cout << BSON( "a" << BSONUndefined ); // { a : undefined } - */ - struct UndefinedLabeler { }; - extern UndefinedLabeler BSONUndefined; + with auto-generated object id: + BSON( GENOID << "name" << "joe" << "age" << 33 ) - /* Utility class to add the minKey (minus infinity) to a given attribute - Example: - std::cout << BSON( "a" << MINKEY ); // { "a" : { "$minKey" : 1 } } - */ - struct MinKeyLabeler { }; - extern MinKeyLabeler MINKEY; - struct MaxKeyLabeler { }; - extern MaxKeyLabeler MAXKEY; - - // Utility class to implement GT, GTE, etc as described above. - class Labeler { - public: - struct Label { - explicit Label( const char *l ) : l_( l ) {} - const char *l_; - }; - Labeler( const Label &l, BSONObjBuilderValueStream *s ) : l_( l ), s_( s ) {} - template<class T> - BSONObjBuilder& operator<<( T value ); - - /* the value of the element e is appended i.e. for - "age" << GT << someElement - one gets - { age : { $gt : someElement's value } } - */ - BSONObjBuilder& operator<<( const BSONElement& e ); - private: - const Label &l_; - BSONObjBuilderValueStream *s_; - }; + The labels GT, GTE, LT, LTE, NE can be helpful for stream-oriented construction + of a BSONObj, particularly when assembling a Query. For example, + BSON( "a" << GT << 23.4 << NE << 30 << "b" << 2 ) produces the object + { a: { \$gt: 23.4, \$ne: 30 }, b: 2 }. +*/ +#define BSON(x) ((::mongo::BSONObjBuilder(64) << x).obj()) - // Utility class to allow adding a std::string to BSON as a Symbol - struct BSONSymbol { - explicit BSONSymbol(StringData sym) :symbol(sym) {} - StringData symbol; - }; - - // Utility class to allow adding a std::string to BSON as Code - struct BSONCode { - explicit BSONCode(StringData str) :code(str) {} - StringData code; - }; +/** Use BSON_ARRAY macro like BSON macro, but without keys - // Utility class to allow adding CodeWScope to BSON - struct BSONCodeWScope { - explicit BSONCodeWScope(StringData str, const BSONObj& obj) :code(str), scope(obj) {} - StringData code; - BSONObj scope; - }; - - // Utility class to allow adding a RegEx to BSON - struct BSONRegEx { - explicit BSONRegEx(StringData pat, StringData f="") :pattern(pat), flags(f) {} - StringData pattern; - StringData flags; - }; + BSONArray arr = BSON_ARRAY( "hello" << 1 << BSON( "foo" << BSON_ARRAY( "bar" << "baz" << "qux" ) ) ); - // Utility class to allow adding binary data to BSON - struct BSONBinData { - BSONBinData(const void* d, int l, BinDataType t) :data(d), length(l), type(t) {} - const void* data; - int length; - BinDataType type; - }; - - // Utility class to allow adding deprecated DBRef type to BSON - struct BSONDBRef { - BSONDBRef(StringData nameSpace, const OID& o) :ns(nameSpace), oid(o) {} - StringData ns; - OID oid; + */ +#define BSON_ARRAY(x) ((::mongo::BSONArrayBuilder() << x).arr()) + +/* Utility class to auto assign object IDs. + Example: + std::cout << BSON( GENOID << "z" << 3 ); // { _id : ..., z : 3 } +*/ +struct GENOIDLabeler {}; +extern GENOIDLabeler GENOID; + +/* Utility class to add a Date element with the current time + Example: + std::cout << BSON( "created" << DATENOW ); // { created : "2009-10-09 11:41:42" } +*/ +struct DateNowLabeler {}; +extern DateNowLabeler DATENOW; + +/* Utility class to assign a NULL value to a given attribute + Example: + std::cout << BSON( "a" << BSONNULL ); // { a : null } +*/ +struct NullLabeler {}; +extern NullLabeler BSONNULL; + +/* Utility class to assign an Undefined value to a given attribute + Example: + std::cout << BSON( "a" << BSONUndefined ); // { a : undefined } +*/ +struct UndefinedLabeler {}; +extern UndefinedLabeler BSONUndefined; + +/* Utility class to add the minKey (minus infinity) to a given attribute + Example: + std::cout << BSON( "a" << MINKEY ); // { "a" : { "$minKey" : 1 } } +*/ +struct MinKeyLabeler {}; +extern MinKeyLabeler MINKEY; +struct MaxKeyLabeler {}; +extern MaxKeyLabeler MAXKEY; + +// Utility class to implement GT, GTE, etc as described above. +class Labeler { +public: + struct Label { + explicit Label(const char* l) : l_(l) {} + const char* l_; }; + Labeler(const Label& l, BSONObjBuilderValueStream* s) : l_(l), s_(s) {} + template <class T> + BSONObjBuilder& operator<<(T value); + + /* the value of the element e is appended i.e. for + "age" << GT << someElement + one gets + { age : { $gt : someElement's value } } + */ + BSONObjBuilder& operator<<(const BSONElement& e); + +private: + const Label& l_; + BSONObjBuilderValueStream* s_; +}; + +// Utility class to allow adding a std::string to BSON as a Symbol +struct BSONSymbol { + explicit BSONSymbol(StringData sym) : symbol(sym) {} + StringData symbol; +}; + +// Utility class to allow adding a std::string to BSON as Code +struct BSONCode { + explicit BSONCode(StringData str) : code(str) {} + StringData code; +}; + +// Utility class to allow adding CodeWScope to BSON +struct BSONCodeWScope { + explicit BSONCodeWScope(StringData str, const BSONObj& obj) : code(str), scope(obj) {} + StringData code; + BSONObj scope; +}; + +// Utility class to allow adding a RegEx to BSON +struct BSONRegEx { + explicit BSONRegEx(StringData pat, StringData f = "") : pattern(pat), flags(f) {} + StringData pattern; + StringData flags; +}; + +// Utility class to allow adding binary data to BSON +struct BSONBinData { + BSONBinData(const void* d, int l, BinDataType t) : data(d), length(l), type(t) {} + const void* data; + int length; + BinDataType type; +}; + +// Utility class to allow adding deprecated DBRef type to BSON +struct BSONDBRef { + BSONDBRef(StringData nameSpace, const OID& o) : ns(nameSpace), oid(o) {} + StringData ns; + OID oid; +}; + +extern Labeler::Label GT; +extern Labeler::Label GTE; +extern Labeler::Label LT; +extern Labeler::Label LTE; +extern Labeler::Label NE; +extern Labeler::Label NIN; +extern Labeler::Label BSIZE; + + +// $or helper: OR(BSON("x" << GT << 7), BSON("y" << LT << 6)); +// becomes : {$or: [{x: {$gt: 7}}, {y: {$lt: 6}}]} +inline BSONObj OR(const BSONObj& a, const BSONObj& b); +inline BSONObj OR(const BSONObj& a, const BSONObj& b, const BSONObj& c); +inline BSONObj OR(const BSONObj& a, const BSONObj& b, const BSONObj& c, const BSONObj& d); +inline BSONObj OR( + const BSONObj& a, const BSONObj& b, const BSONObj& c, const BSONObj& d, const BSONObj& e); +inline BSONObj OR(const BSONObj& a, + const BSONObj& b, + const BSONObj& c, + const BSONObj& d, + const BSONObj& e, + const BSONObj& f); +// definitions in bsonobjbuilder.h b/c of incomplete types + +// Utility class to implement BSON( key << val ) as described above. +class BSONObjBuilderValueStream { + MONGO_DISALLOW_COPYING(BSONObjBuilderValueStream); + +public: + friend class Labeler; + BSONObjBuilderValueStream(BSONObjBuilder* builder); + + BSONObjBuilder& operator<<(const BSONElement& e); + + template <class T> + BSONObjBuilder& operator<<(T value); + + BSONObjBuilder& operator<<(const DateNowLabeler& id); + + BSONObjBuilder& operator<<(const NullLabeler& id); + BSONObjBuilder& operator<<(const UndefinedLabeler& id); + + BSONObjBuilder& operator<<(const MinKeyLabeler& id); + BSONObjBuilder& operator<<(const MaxKeyLabeler& id); + + Labeler operator<<(const Labeler::Label& l); + + void endField(StringData nextFieldName = StringData()); + bool subobjStarted() const { + return _fieldName != 0; + } + + // The following methods provide API compatibility with BSONArrayBuilder + BufBuilder& subobjStart(); + BufBuilder& subarrayStart(); + + // This method should only be called from inside of implementations of + // BSONObjBuilder& operator<<(BSONObjBuilderValueStream&, SOME_TYPE) + // to provide the return value. + BSONObjBuilder& builder() { + return *_builder; + } + +private: + StringData _fieldName; + BSONObjBuilder* _builder; + + bool haveSubobj() const { + return _subobj.get() != 0; + } + BSONObjBuilder* subobj(); + std::unique_ptr<BSONObjBuilder> _subobj; +}; + +/** + used in conjuction with BSONObjBuilder, allows for proper buffer size to prevent crazy memory usage + */ +class BSONSizeTracker { +public: + BSONSizeTracker() { + _pos = 0; + for (int i = 0; i < SIZE; i++) + _sizes[i] = 512; // this is the default, so just be consistent + } - extern Labeler::Label GT; - extern Labeler::Label GTE; - extern Labeler::Label LT; - extern Labeler::Label LTE; - extern Labeler::Label NE; - extern Labeler::Label NIN; - extern Labeler::Label BSIZE; - - - // $or helper: OR(BSON("x" << GT << 7), BSON("y" << LT << 6)); - // becomes : {$or: [{x: {$gt: 7}}, {y: {$lt: 6}}]} - inline BSONObj OR(const BSONObj& a, const BSONObj& b); - inline BSONObj OR(const BSONObj& a, const BSONObj& b, const BSONObj& c); - inline BSONObj OR(const BSONObj& a, const BSONObj& b, const BSONObj& c, const BSONObj& d); - inline BSONObj OR(const BSONObj& a, const BSONObj& b, const BSONObj& c, const BSONObj& d, const BSONObj& e); - inline BSONObj OR(const BSONObj& a, const BSONObj& b, const BSONObj& c, const BSONObj& d, const BSONObj& e, const BSONObj& f); - // definitions in bsonobjbuilder.h b/c of incomplete types - - // Utility class to implement BSON( key << val ) as described above. - class BSONObjBuilderValueStream { - MONGO_DISALLOW_COPYING(BSONObjBuilderValueStream); - public: - friend class Labeler; - BSONObjBuilderValueStream( BSONObjBuilder * builder ); - - BSONObjBuilder& operator<<( const BSONElement& e ); - - template<class T> - BSONObjBuilder& operator<<( T value ); - - BSONObjBuilder& operator<<(const DateNowLabeler& id); - - BSONObjBuilder& operator<<(const NullLabeler& id); - BSONObjBuilder& operator<<(const UndefinedLabeler& id); - - BSONObjBuilder& operator<<(const MinKeyLabeler& id); - BSONObjBuilder& operator<<(const MaxKeyLabeler& id); - - Labeler operator<<( const Labeler::Label &l ); - - void endField( StringData nextFieldName = StringData() ); - bool subobjStarted() const { return _fieldName != 0; } - - // The following methods provide API compatibility with BSONArrayBuilder - BufBuilder& subobjStart(); - BufBuilder& subarrayStart(); - - // This method should only be called from inside of implementations of - // BSONObjBuilder& operator<<(BSONObjBuilderValueStream&, SOME_TYPE) - // to provide the return value. - BSONObjBuilder& builder() { return *_builder; } - private: - StringData _fieldName; - BSONObjBuilder * _builder; + ~BSONSizeTracker() {} - bool haveSubobj() const { return _subobj.get() != 0; } - BSONObjBuilder *subobj(); - std::unique_ptr< BSONObjBuilder > _subobj; - }; + void got(int size) { + _sizes[_pos] = size; + _pos = (_pos + 1) % SIZE; // thread safe at least on certain compilers + } /** - used in conjuction with BSONObjBuilder, allows for proper buffer size to prevent crazy memory usage + * right now choosing largest size */ - class BSONSizeTracker { - public: - BSONSizeTracker() { - _pos = 0; - for ( int i=0; i<SIZE; i++ ) - _sizes[i] = 512; // this is the default, so just be consistent - } - - ~BSONSizeTracker() { - } - - void got( int size ) { - _sizes[_pos] = size; - _pos = (_pos + 1) % SIZE; // thread safe at least on certain compilers + int getSize() const { + int x = 16; // sane min + for (int i = 0; i < SIZE; i++) { + if (_sizes[i] > x) + x = _sizes[i]; } + return x; + } - /** - * right now choosing largest size - */ - int getSize() const { - int x = 16; // sane min - for ( int i=0; i<SIZE; i++ ) { - if ( _sizes[i] > x ) - x = _sizes[i]; - } - return x; - } - - private: - enum { SIZE = 10 }; - int _pos; - int _sizes[SIZE]; - }; +private: + enum { SIZE = 10 }; + int _pos; + int _sizes[SIZE]; +}; - // considers order - bool fieldsMatch(const BSONObj& lhs, const BSONObj& rhs); +// considers order +bool fieldsMatch(const BSONObj& lhs, const BSONObj& rhs); } diff --git a/src/mongo/bson/bsonobj.cpp b/src/mongo/bson/bsonobj.cpp index 3374e5de1dc..0c9f8f01636 100644 --- a/src/mongo/bson/bsonobj.cpp +++ b/src/mongo/bson/bsonobj.cpp @@ -43,879 +43,861 @@ #include "mongo/util/stringutils.h" namespace mongo { - using namespace std; - /* BSONObj ------------------------------------------------------------*/ - - // deep (full) equality - bool BSONObj::equal(const BSONObj &rhs) const { - BSONObjIterator i(*this); - BSONObjIterator j(rhs); - BSONElement l,r; - do { - // so far, equal... - l = i.next(); - r = j.next(); - if ( l.eoo() ) - return r.eoo(); - } while( l == r ); - return false; - } - - void BSONObj::_assertInvalid() const { - StringBuilder ss; - int os = objsize(); - ss << "BSONObj size: " << os << " (0x" << integerToHex( os ) << ") is invalid. " - << "Size must be between 0 and " << BSONObjMaxInternalSize - << "(" << ( BSONObjMaxInternalSize/(1024*1024) ) << "MB)"; - try { - BSONElement e = firstElement(); - ss << " First element: " << e.toString(); - } - catch ( ... ) { } - massert( 10334 , ss.str() , 0 ); - } - - BSONObj BSONObj::copy() const { - char* storage = static_cast<char*>(mongoMalloc(sizeof(Holder) + objsize())); - memcpy(storage + sizeof(Holder), objdata(), objsize()); - return BSONObj::takeOwnership(storage); - } - - BSONObj BSONObj::getOwned() const { - if ( isOwned() ) - return *this; - return copy(); - } - - string BSONObj::jsonString( JsonStringFormat format, int pretty, bool isArray ) const { - - if ( isEmpty() ) return isArray ? "[]" : "{}"; - - StringBuilder s; - s << (isArray ? "[ " : "{ "); - BSONObjIterator i(*this); - BSONElement e = i.next(); - if ( !e.eoo() ) - while ( 1 ) { - s << e.jsonString( format, !isArray, pretty?pretty+1:0 ); - e = i.next(); - if ( e.eoo() ) - break; - s << ","; - if ( pretty ) { - s << '\n'; - for( int x = 0; x < pretty; x++ ) - s << " "; - } - else { - s << " "; - } +using namespace std; +/* BSONObj ------------------------------------------------------------*/ + +// deep (full) equality +bool BSONObj::equal(const BSONObj& rhs) const { + BSONObjIterator i(*this); + BSONObjIterator j(rhs); + BSONElement l, r; + do { + // so far, equal... + l = i.next(); + r = j.next(); + if (l.eoo()) + return r.eoo(); + } while (l == r); + return false; +} + +void BSONObj::_assertInvalid() const { + StringBuilder ss; + int os = objsize(); + ss << "BSONObj size: " << os << " (0x" << integerToHex(os) << ") is invalid. " + << "Size must be between 0 and " << BSONObjMaxInternalSize << "(" + << (BSONObjMaxInternalSize / (1024 * 1024)) << "MB)"; + try { + BSONElement e = firstElement(); + ss << " First element: " << e.toString(); + } catch (...) { + } + massert(10334, ss.str(), 0); +} + +BSONObj BSONObj::copy() const { + char* storage = static_cast<char*>(mongoMalloc(sizeof(Holder) + objsize())); + memcpy(storage + sizeof(Holder), objdata(), objsize()); + return BSONObj::takeOwnership(storage); +} + +BSONObj BSONObj::getOwned() const { + if (isOwned()) + return *this; + return copy(); +} + +string BSONObj::jsonString(JsonStringFormat format, int pretty, bool isArray) const { + if (isEmpty()) + return isArray ? "[]" : "{}"; + + StringBuilder s; + s << (isArray ? "[ " : "{ "); + BSONObjIterator i(*this); + BSONElement e = i.next(); + if (!e.eoo()) + while (1) { + s << e.jsonString(format, !isArray, pretty ? pretty + 1 : 0); + e = i.next(); + if (e.eoo()) + break; + s << ","; + if (pretty) { + s << '\n'; + for (int x = 0; x < pretty; x++) + s << " "; + } else { + s << " "; } - s << (isArray ? " ]" : " }"); - return s.str(); - } - - bool BSONObj::valid() const { - return validateBSON( objdata(), objsize() ).isOK(); - } - - int BSONObj::woCompare(const BSONObj& r, const Ordering &o, bool considerFieldName) const { - if ( isEmpty() ) - return r.isEmpty() ? 0 : -1; - if ( r.isEmpty() ) + } + s << (isArray ? " ]" : " }"); + return s.str(); +} + +bool BSONObj::valid() const { + return validateBSON(objdata(), objsize()).isOK(); +} + +int BSONObj::woCompare(const BSONObj& r, const Ordering& o, bool considerFieldName) const { + if (isEmpty()) + return r.isEmpty() ? 0 : -1; + if (r.isEmpty()) + return 1; + + BSONObjIterator i(*this); + BSONObjIterator j(r); + unsigned mask = 1; + while (1) { + // so far, equal... + + BSONElement l = i.next(); + BSONElement r = j.next(); + if (l.eoo()) + return r.eoo() ? 0 : -1; + if (r.eoo()) return 1; - BSONObjIterator i(*this); - BSONObjIterator j(r); - unsigned mask = 1; - while ( 1 ) { - // so far, equal... - - BSONElement l = i.next(); - BSONElement r = j.next(); - if ( l.eoo() ) - return r.eoo() ? 0 : -1; - if ( r.eoo() ) - return 1; - - int x; - { - x = l.woCompare( r, considerFieldName ); - if( o.descending(mask) ) - x = -x; - } - if ( x != 0 ) - return x; - mask <<= 1; + int x; + { + x = l.woCompare(r, considerFieldName); + if (o.descending(mask)) + x = -x; } - return -1; - } - - /* well ordered compare */ - int BSONObj::woCompare(const BSONObj &r, const BSONObj &idxKey, - bool considerFieldName) const { - if ( isEmpty() ) - return r.isEmpty() ? 0 : -1; - if ( r.isEmpty() ) + if (x != 0) + return x; + mask <<= 1; + } + return -1; +} + +/* well ordered compare */ +int BSONObj::woCompare(const BSONObj& r, const BSONObj& idxKey, bool considerFieldName) const { + if (isEmpty()) + return r.isEmpty() ? 0 : -1; + if (r.isEmpty()) + return 1; + + bool ordered = !idxKey.isEmpty(); + + BSONObjIterator i(*this); + BSONObjIterator j(r); + BSONObjIterator k(idxKey); + while (1) { + // so far, equal... + + BSONElement l = i.next(); + BSONElement r = j.next(); + BSONElement o; + if (ordered) + o = k.next(); + if (l.eoo()) + return r.eoo() ? 0 : -1; + if (r.eoo()) return 1; - bool ordered = !idxKey.isEmpty(); - - BSONObjIterator i(*this); - BSONObjIterator j(r); - BSONObjIterator k(idxKey); - while ( 1 ) { - // so far, equal... - - BSONElement l = i.next(); - BSONElement r = j.next(); - BSONElement o; - if ( ordered ) - o = k.next(); - if ( l.eoo() ) - return r.eoo() ? 0 : -1; - if ( r.eoo() ) - return 1; - - int x; - /* + int x; + /* if( ordered && o.type() == String && strcmp(o.valuestr(), "ascii-proto") == 0 && l.type() == String && r.type() == String ) { // note: no negative support yet, as this is just sort of a POC x = _stricmp(l.valuestr(), r.valuestr()); } else*/ { - x = l.woCompare( r, considerFieldName ); - if ( ordered && o.number() < 0 ) - x = -x; - } - if ( x != 0 ) - return x; - } - return -1; - } - - BSONObj staticNull = fromjson( "{'':null}" ); - BSONObj makeUndefined() { - BSONObjBuilder b; - b.appendUndefined( "" ); - return b.obj(); - } - BSONObj staticUndefined = makeUndefined(); - - /* well ordered compare */ - int BSONObj::woSortOrder(const BSONObj& other, const BSONObj& sortKey , bool useDotted ) const { - if ( isEmpty() ) - return other.isEmpty() ? 0 : -1; - if ( other.isEmpty() ) - return 1; - - uassert( 10060 , "woSortOrder needs a non-empty sortKey" , ! sortKey.isEmpty() ); - - BSONObjIterator i(sortKey); - while ( 1 ) { - BSONElement f = i.next(); - if ( f.eoo() ) - return 0; - - BSONElement l = useDotted ? getFieldDotted( f.fieldName() ) : getField( f.fieldName() ); - if ( l.eoo() ) - l = staticNull.firstElement(); - BSONElement r = useDotted ? other.getFieldDotted( f.fieldName() ) : other.getField( f.fieldName() ); - if ( r.eoo() ) - r = staticNull.firstElement(); - - int x = l.woCompare( r, false ); - if ( f.number() < 0 ) + x = l.woCompare(r, considerFieldName); + if (ordered && o.number() < 0) x = -x; - if ( x != 0 ) - return x; - } - return -1; - } - - size_t BSONObj::Hasher::operator()(const BSONObj& obj) const { - size_t hash = 0; - BSONForEach(elem, obj) { - boost::hash_combine(hash, BSONElement::Hasher()(elem)); } - return hash; - } - - bool BSONObj::isPrefixOf( const BSONObj& otherObj ) const { - BSONObjIterator a( *this ); - BSONObjIterator b( otherObj ); - - while ( a.more() && b.more() ) { - BSONElement x = a.next(); - BSONElement y = b.next(); - if ( x != y ) - return false; - } - - return ! a.more(); - } - - bool BSONObj::isFieldNamePrefixOf( const BSONObj& otherObj ) const { - BSONObjIterator a( *this ); - BSONObjIterator b( otherObj ); - - while ( a.more() && b.more() ) { - BSONElement x = a.next(); - BSONElement y = b.next(); - if ( ! str::equals( x.fieldName() , y.fieldName() ) ) { - return false; - } - } - - return ! a.more(); - } - - template <typename BSONElementColl> - void _getFieldsDotted( const BSONObj* obj, StringData name, BSONElementColl &ret, bool expandLastArray ) { - BSONElement e = obj->getField( name ); - - if ( e.eoo() ) { - size_t idx = name.find( '.' ); - if ( idx != string::npos ) { - StringData left = name.substr( 0, idx ); - StringData next = name.substr( idx + 1, name.size() ); - - BSONElement e = obj->getField( left ); - - if (e.type() == Object) { - e.embeddedObject().getFieldsDotted(next, ret, expandLastArray ); + if (x != 0) + return x; + } + return -1; +} + +BSONObj staticNull = fromjson("{'':null}"); +BSONObj makeUndefined() { + BSONObjBuilder b; + b.appendUndefined(""); + return b.obj(); +} +BSONObj staticUndefined = makeUndefined(); + +/* well ordered compare */ +int BSONObj::woSortOrder(const BSONObj& other, const BSONObj& sortKey, bool useDotted) const { + if (isEmpty()) + return other.isEmpty() ? 0 : -1; + if (other.isEmpty()) + return 1; + + uassert(10060, "woSortOrder needs a non-empty sortKey", !sortKey.isEmpty()); + + BSONObjIterator i(sortKey); + while (1) { + BSONElement f = i.next(); + if (f.eoo()) + return 0; + + BSONElement l = useDotted ? getFieldDotted(f.fieldName()) : getField(f.fieldName()); + if (l.eoo()) + l = staticNull.firstElement(); + BSONElement r = + useDotted ? other.getFieldDotted(f.fieldName()) : other.getField(f.fieldName()); + if (r.eoo()) + r = staticNull.firstElement(); + + int x = l.woCompare(r, false); + if (f.number() < 0) + x = -x; + if (x != 0) + return x; + } + return -1; +} + +size_t BSONObj::Hasher::operator()(const BSONObj& obj) const { + size_t hash = 0; + BSONForEach(elem, obj) { + boost::hash_combine(hash, BSONElement::Hasher()(elem)); + } + return hash; +} + +bool BSONObj::isPrefixOf(const BSONObj& otherObj) const { + BSONObjIterator a(*this); + BSONObjIterator b(otherObj); + + while (a.more() && b.more()) { + BSONElement x = a.next(); + BSONElement y = b.next(); + if (x != y) + return false; + } + + return !a.more(); +} + +bool BSONObj::isFieldNamePrefixOf(const BSONObj& otherObj) const { + BSONObjIterator a(*this); + BSONObjIterator b(otherObj); + + while (a.more() && b.more()) { + BSONElement x = a.next(); + BSONElement y = b.next(); + if (!str::equals(x.fieldName(), y.fieldName())) { + return false; + } + } + + return !a.more(); +} + +template <typename BSONElementColl> +void _getFieldsDotted(const BSONObj* obj, + StringData name, + BSONElementColl& ret, + bool expandLastArray) { + BSONElement e = obj->getField(name); + + if (e.eoo()) { + size_t idx = name.find('.'); + if (idx != string::npos) { + StringData left = name.substr(0, idx); + StringData next = name.substr(idx + 1, name.size()); + + BSONElement e = obj->getField(left); + + if (e.type() == Object) { + e.embeddedObject().getFieldsDotted(next, ret, expandLastArray); + } else if (e.type() == Array) { + bool allDigits = false; + if (next.size() > 0 && isdigit(next[0])) { + unsigned temp = 1; + while (temp < next.size() && isdigit(next[temp])) + temp++; + allDigits = temp == next.size() || next[temp] == '.'; } - else if (e.type() == Array) { - bool allDigits = false; - if ( next.size() > 0 && isdigit( next[0] ) ) { - unsigned temp = 1; - while ( temp < next.size() && isdigit( next[temp] ) ) - temp++; - allDigits = temp == next.size() || next[temp] == '.'; - } - if (allDigits) { - e.embeddedObject().getFieldsDotted(next, ret, expandLastArray ); - } - else { - BSONObjIterator i(e.embeddedObject()); - while ( i.more() ) { - BSONElement e2 = i.next(); - if (e2.type() == Object || e2.type() == Array) - e2.embeddedObject().getFieldsDotted(next, ret, expandLastArray ); - } + if (allDigits) { + e.embeddedObject().getFieldsDotted(next, ret, expandLastArray); + } else { + BSONObjIterator i(e.embeddedObject()); + while (i.more()) { + BSONElement e2 = i.next(); + if (e2.type() == Object || e2.type() == Array) + e2.embeddedObject().getFieldsDotted(next, ret, expandLastArray); } } - else { - // do nothing: no match - } - } - } - else { - if (e.type() == Array && expandLastArray) { - BSONObjIterator i(e.embeddedObject()); - while ( i.more() ) - ret.insert(i.next()); - } - else { - ret.insert(e); + } else { + // do nothing: no match } } - } - - void BSONObj::getFieldsDotted(StringData name, BSONElementSet &ret, bool expandLastArray ) const { - _getFieldsDotted( this, name, ret, expandLastArray ); - } - void BSONObj::getFieldsDotted(StringData name, BSONElementMSet &ret, bool expandLastArray ) const { - _getFieldsDotted( this, name, ret, expandLastArray ); - } - - BSONElement eooElement; - - BSONElement BSONObj::getFieldDottedOrArray(const char *&name) const { - const char *p = strchr(name, '.'); - - BSONElement sub; - - if ( p ) { - sub = getField( string(name, p-name) ); - name = p + 1; - } - else { - sub = getField( name ); - name = name + strlen(name); - } - - if ( sub.eoo() ) - return eooElement; - else if ( sub.type() == Array || name[0] == '\0' ) - return sub; - else if ( sub.type() == Object ) - return sub.embeddedObject().getFieldDottedOrArray( name ); - else - return eooElement; - } - - BSONObj BSONObj::extractFieldsUnDotted(const BSONObj& pattern) const { - BSONObjBuilder b; - BSONObjIterator i(pattern); - while ( i.moreWithEOO() ) { - BSONElement e = i.next(); - if ( e.eoo() ) - break; - BSONElement x = getField(e.fieldName()); - if ( !x.eoo() ) - b.appendAs(x, ""); - } - return b.obj(); - } - - BSONObj BSONObj::extractFields(const BSONObj& pattern , bool fillWithNull ) const { - BSONObjBuilder b(32); // scanandorder.h can make a zillion of these, so we start the allocation very small - BSONObjIterator i(pattern); - while ( i.moreWithEOO() ) { - BSONElement e = i.next(); - if ( e.eoo() ) - break; - BSONElement x = getFieldDotted(e.fieldName()); - if ( ! x.eoo() ) - b.appendAs( x, e.fieldName() ); - else if ( fillWithNull ) - b.appendNull( e.fieldName() ); - } - return b.obj(); - } - - BSONObj BSONObj::filterFieldsUndotted( const BSONObj &filter, bool inFilter ) const { - BSONObjBuilder b; - BSONObjIterator i( *this ); - while( i.moreWithEOO() ) { - BSONElement e = i.next(); - if ( e.eoo() ) + } else { + if (e.type() == Array && expandLastArray) { + BSONObjIterator i(e.embeddedObject()); + while (i.more()) + ret.insert(i.next()); + } else { + ret.insert(e); + } + } +} + +void BSONObj::getFieldsDotted(StringData name, BSONElementSet& ret, bool expandLastArray) const { + _getFieldsDotted(this, name, ret, expandLastArray); +} +void BSONObj::getFieldsDotted(StringData name, BSONElementMSet& ret, bool expandLastArray) const { + _getFieldsDotted(this, name, ret, expandLastArray); +} + +BSONElement eooElement; + +BSONElement BSONObj::getFieldDottedOrArray(const char*& name) const { + const char* p = strchr(name, '.'); + + BSONElement sub; + + if (p) { + sub = getField(string(name, p - name)); + name = p + 1; + } else { + sub = getField(name); + name = name + strlen(name); + } + + if (sub.eoo()) + return eooElement; + else if (sub.type() == Array || name[0] == '\0') + return sub; + else if (sub.type() == Object) + return sub.embeddedObject().getFieldDottedOrArray(name); + else + return eooElement; +} + +BSONObj BSONObj::extractFieldsUnDotted(const BSONObj& pattern) const { + BSONObjBuilder b; + BSONObjIterator i(pattern); + while (i.moreWithEOO()) { + BSONElement e = i.next(); + if (e.eoo()) + break; + BSONElement x = getField(e.fieldName()); + if (!x.eoo()) + b.appendAs(x, ""); + } + return b.obj(); +} + +BSONObj BSONObj::extractFields(const BSONObj& pattern, bool fillWithNull) const { + BSONObjBuilder b( + 32); // scanandorder.h can make a zillion of these, so we start the allocation very small + BSONObjIterator i(pattern); + while (i.moreWithEOO()) { + BSONElement e = i.next(); + if (e.eoo()) + break; + BSONElement x = getFieldDotted(e.fieldName()); + if (!x.eoo()) + b.appendAs(x, e.fieldName()); + else if (fillWithNull) + b.appendNull(e.fieldName()); + } + return b.obj(); +} + +BSONObj BSONObj::filterFieldsUndotted(const BSONObj& filter, bool inFilter) const { + BSONObjBuilder b; + BSONObjIterator i(*this); + while (i.moreWithEOO()) { + BSONElement e = i.next(); + if (e.eoo()) + break; + BSONElement x = filter.getField(e.fieldName()); + if ((x.eoo() && !inFilter) || (!x.eoo() && inFilter)) + b.append(e); + } + return b.obj(); +} + +BSONElement BSONObj::getFieldUsingIndexNames(StringData fieldName, const BSONObj& indexKey) const { + BSONObjIterator i(indexKey); + int j = 0; + while (i.moreWithEOO()) { + BSONElement f = i.next(); + if (f.eoo()) + return BSONElement(); + if (f.fieldName() == fieldName) + break; + ++j; + } + BSONObjIterator k(*this); + while (k.moreWithEOO()) { + BSONElement g = k.next(); + if (g.eoo()) + return BSONElement(); + if (j == 0) { + return g; + } + --j; + } + return BSONElement(); +} + +/* grab names of all the fields in this object */ +int BSONObj::getFieldNames(set<string>& fields) const { + int n = 0; + BSONObjIterator i(*this); + while (i.moreWithEOO()) { + BSONElement e = i.next(); + if (e.eoo()) + break; + fields.insert(e.fieldName()); + n++; + } + return n; +} + +/* note: addFields always adds _id even if not specified + returns n added not counting _id unless requested. +*/ +int BSONObj::addFields(BSONObj& from, set<string>& fields) { + verify(isEmpty() && !isOwned()); /* partial implementation for now... */ + + BSONObjBuilder b; + + int N = fields.size(); + int n = 0; + BSONObjIterator i(from); + bool gotId = false; + while (i.moreWithEOO()) { + BSONElement e = i.next(); + const char* fname = e.fieldName(); + if (fields.count(fname)) { + b.append(e); + ++n; + gotId = gotId || strcmp(fname, "_id") == 0; + if (n == N && gotId) break; - BSONElement x = filter.getField( e.fieldName() ); - if ( ( x.eoo() && !inFilter ) || - ( !x.eoo() && inFilter ) ) - b.append( e ); - } - return b.obj(); - } - - BSONElement BSONObj::getFieldUsingIndexNames(StringData fieldName, - const BSONObj &indexKey) const { - BSONObjIterator i( indexKey ); - int j = 0; - while( i.moreWithEOO() ) { - BSONElement f = i.next(); - if ( f.eoo() ) - return BSONElement(); - if ( f.fieldName() == fieldName ) + } else if (strcmp(fname, "_id") == 0) { + b.append(e); + gotId = true; + if (n == N && gotId) break; - ++j; - } - BSONObjIterator k( *this ); - while( k.moreWithEOO() ) { - BSONElement g = k.next(); - if ( g.eoo() ) - return BSONElement(); - if ( j == 0 ) { - return g; - } - --j; } - return BSONElement(); } - /* grab names of all the fields in this object */ - int BSONObj::getFieldNames(set<string>& fields) const { - int n = 0; - BSONObjIterator i(*this); - while ( i.moreWithEOO() ) { - BSONElement e = i.next(); - if ( e.eoo() ) - break; - fields.insert(e.fieldName()); - n++; - } - return n; + if (n) { + *this = b.obj(); } - /* note: addFields always adds _id even if not specified - returns n added not counting _id unless requested. - */ - int BSONObj::addFields(BSONObj& from, set<string>& fields) { - verify( isEmpty() && !isOwned() ); /* partial implementation for now... */ + return n; +} - BSONObjBuilder b; - - int N = fields.size(); - int n = 0; - BSONObjIterator i(from); - bool gotId = false; - while ( i.moreWithEOO() ) { - BSONElement e = i.next(); - const char *fname = e.fieldName(); - if ( fields.count(fname) ) { - b.append(e); - ++n; - gotId = gotId || strcmp(fname, "_id")==0; - if ( n == N && gotId ) - break; - } - else if ( strcmp(fname, "_id")==0 ) { - b.append(e); - gotId = true; - if ( n == N && gotId ) - break; - } - } - - if ( n ) { - *this = b.obj(); - } +bool BSONObj::couldBeArray() const { + BSONObjIterator i(*this); + int index = 0; + while (i.moreWithEOO()) { + BSONElement e = i.next(); + if (e.eoo()) + break; - return n; + // TODO: If actually important, may be able to do int->char* much faster + if (strcmp(e.fieldName(), ((string)(str::stream() << index)).c_str()) != 0) + return false; + index++; } + return true; +} - bool BSONObj::couldBeArray() const { - BSONObjIterator i( *this ); - int index = 0; - while( i.moreWithEOO() ){ - BSONElement e = i.next(); - if( e.eoo() ) break; - - // TODO: If actually important, may be able to do int->char* much faster - if( strcmp( e.fieldName(), ((string)( str::stream() << index )).c_str() ) != 0 ) - return false; - index++; - } - return true; - } - - BSONObj BSONObj::clientReadable() const { - BSONObjBuilder b; - BSONObjIterator i( *this ); - while( i.moreWithEOO() ) { - BSONElement e = i.next(); - if ( e.eoo() ) - break; - switch( e.type() ) { +BSONObj BSONObj::clientReadable() const { + BSONObjBuilder b; + BSONObjIterator i(*this); + while (i.moreWithEOO()) { + BSONElement e = i.next(); + if (e.eoo()) + break; + switch (e.type()) { case MinKey: { BSONObjBuilder m; - m.append( "$minElement", 1 ); - b.append( e.fieldName(), m.done() ); + m.append("$minElement", 1); + b.append(e.fieldName(), m.done()); break; } case MaxKey: { BSONObjBuilder m; - m.append( "$maxElement", 1 ); - b.append( e.fieldName(), m.done() ); + m.append("$maxElement", 1); + b.append(e.fieldName(), m.done()); break; } default: - b.append( e ); - } + b.append(e); } - return b.obj(); } + return b.obj(); +} - BSONObj BSONObj::replaceFieldNames( const BSONObj &names ) const { - BSONObjBuilder b; - BSONObjIterator i( *this ); - BSONObjIterator j( names ); - BSONElement f = j.moreWithEOO() ? j.next() : BSONObj().firstElement(); - while( i.moreWithEOO() ) { - BSONElement e = i.next(); - if ( e.eoo() ) - break; - if ( !f.eoo() ) { - b.appendAs( e, f.fieldName() ); - f = j.next(); - } - else { - b.append( e ); - } +BSONObj BSONObj::replaceFieldNames(const BSONObj& names) const { + BSONObjBuilder b; + BSONObjIterator i(*this); + BSONObjIterator j(names); + BSONElement f = j.moreWithEOO() ? j.next() : BSONObj().firstElement(); + while (i.moreWithEOO()) { + BSONElement e = i.next(); + if (e.eoo()) + break; + if (!f.eoo()) { + b.appendAs(e, f.fieldName()); + f = j.next(); + } else { + b.append(e); } - return b.obj(); } + return b.obj(); +} - Status BSONObj::_okForStorage(bool root, bool deep) const { - BSONObjIterator i( *this ); - - // The first field is special in the case of a DBRef where the first field must be $ref - bool first = true; - while ( i.more() ) { - BSONElement e = i.next(); - const char* name = e.fieldName(); - - // Cannot start with "$", unless dbref which must start with ($ref, $id) - if (str::startsWith(name, '$')) { - if ( first && - // $ref is a collection name and must be a String - str::equals(name, "$ref") && e.type() == String && - str::equals(i.next().fieldName(), "$id") ) { - - first = false; - // keep inspecting fields for optional "$db" - e = i.next(); - name = e.fieldName(); // "" if eoo() +Status BSONObj::_okForStorage(bool root, bool deep) const { + BSONObjIterator i(*this); - // optional $db field must be a String - if (str::equals(name, "$db") && e.type() == String) { - continue; //this element is fine, so continue on to siblings (if any more) - } + // The first field is special in the case of a DBRef where the first field must be $ref + bool first = true; + while (i.more()) { + BSONElement e = i.next(); + const char* name = e.fieldName(); + + // Cannot start with "$", unless dbref which must start with ($ref, $id) + if (str::startsWith(name, '$')) { + if (first && + // $ref is a collection name and must be a String + str::equals(name, "$ref") && + e.type() == String && str::equals(i.next().fieldName(), "$id")) { + first = false; + // keep inspecting fields for optional "$db" + e = i.next(); + name = e.fieldName(); // "" if eoo() - // Can't start with a "$", all other checks are done below (outside if blocks) - if (str::startsWith(name, '$')) { - return Status(ErrorCodes::DollarPrefixedFieldName, - str::stream() << name << " is not valid for storage."); - } + // optional $db field must be a String + if (str::equals(name, "$db") && e.type() == String) { + continue; // this element is fine, so continue on to siblings (if any more) } - else { - // not an okay, $ prefixed field name. + + // Can't start with a "$", all other checks are done below (outside if blocks) + if (str::startsWith(name, '$')) { return Status(ErrorCodes::DollarPrefixedFieldName, str::stream() << name << " is not valid for storage."); } - } - - // Do not allow "." in the field name - if (strchr(name, '.')) { - return Status(ErrorCodes::DottedFieldName, + } else { + // not an okay, $ prefixed field name. + return Status(ErrorCodes::DollarPrefixedFieldName, str::stream() << name << " is not valid for storage."); } + } - // (SERVER-9502) Do not allow storing an _id field with a RegEx type or - // Array type in a root document - if (root && (e.type() == RegEx || e.type() == Array || e.type() == Undefined) - && str::equals(name,"_id")) { - return Status(ErrorCodes::InvalidIdField, - str::stream() << name - << " is not valid for storage because it is of type " - << typeName(e.type())); - } + // Do not allow "." in the field name + if (strchr(name, '.')) { + return Status(ErrorCodes::DottedFieldName, + str::stream() << name << " is not valid for storage."); + } + + // (SERVER-9502) Do not allow storing an _id field with a RegEx type or + // Array type in a root document + if (root && (e.type() == RegEx || e.type() == Array || e.type() == Undefined) && + str::equals(name, "_id")) { + return Status(ErrorCodes::InvalidIdField, + str::stream() << name + << " is not valid for storage because it is of type " + << typeName(e.type())); + } - if ( deep && e.mayEncapsulate() ) { - switch ( e.type() ) { + if (deep && e.mayEncapsulate()) { + switch (e.type()) { case Object: - case Array: - { - Status s = e.embeddedObject()._okForStorage(false, true); - // TODO: combine field names for better error messages - if ( ! s.isOK() ) - return s; - } - break; - case CodeWScope: - { - Status s = e.codeWScopeObject()._okForStorage(false, true); - // TODO: combine field names for better error messages - if ( ! s.isOK() ) - return s; - } - break; + case Array: { + Status s = e.embeddedObject()._okForStorage(false, true); + // TODO: combine field names for better error messages + if (!s.isOK()) + return s; + } break; + case CodeWScope: { + Status s = e.codeWScopeObject()._okForStorage(false, true); + // TODO: combine field names for better error messages + if (!s.isOK()) + return s; + } break; default: - uassert( 12579, "unhandled cases in BSONObj okForStorage" , 0 ); - } + uassert(12579, "unhandled cases in BSONObj okForStorage", 0); } - - // After we have processed one field, we are no longer on the first field - first = false; - } - return Status::OK(); - } - - void BSONObj::dump() const { - LogstreamBuilder builder = log(); - builder << hex; - const char *p = objdata(); - for ( int i = 0; i < objsize(); i++ ) { - builder << i << '\t' << ( 0xff & ( (unsigned) *p ) ); - if ( *p >= 'A' && *p <= 'z' ) - builder << '\t' << *p; - builder << endl; - p++; } + + // After we have processed one field, we are no longer on the first field + first = false; } + return Status::OK(); +} - void BSONObj::getFields(unsigned n, const char **fieldNames, BSONElement *fields) const { - BSONObjIterator i(*this); - while ( i.more() ) { - BSONElement e = i.next(); - const char *p = e.fieldName(); - for( unsigned i = 0; i < n; i++ ) { - if( strcmp(p, fieldNames[i]) == 0 ) { - fields[i] = e; - break; - } - } - } +void BSONObj::dump() const { + LogstreamBuilder builder = log(); + builder << hex; + const char* p = objdata(); + for (int i = 0; i < objsize(); i++) { + builder << i << '\t' << (0xff & ((unsigned)*p)); + if (*p >= 'A' && *p <= 'z') + builder << '\t' << *p; + builder << endl; + p++; } +} - BSONElement BSONObj::getField(StringData name) const { - BSONObjIterator i(*this); - while ( i.more() ) { - BSONElement e = i.next(); - // We know that e has a cached field length since BSONObjIterator::next internally - // called BSONElement::size on the BSONElement that it returned, so it is more - // efficient to re-use that information by obtaining the field name as a - // StringData, which will be pre-populated with the cached length. - if ( name == e.fieldNameStringData() ) - return e; +void BSONObj::getFields(unsigned n, const char** fieldNames, BSONElement* fields) const { + BSONObjIterator i(*this); + while (i.more()) { + BSONElement e = i.next(); + const char* p = e.fieldName(); + for (unsigned i = 0; i < n; i++) { + if (strcmp(p, fieldNames[i]) == 0) { + fields[i] = e; + break; + } } - return BSONElement(); } +} - int BSONObj::getIntField(StringData name) const { - BSONElement e = getField(name); - return e.isNumber() ? (int) e.number() : std::numeric_limits< int >::min(); +BSONElement BSONObj::getField(StringData name) const { + BSONObjIterator i(*this); + while (i.more()) { + BSONElement e = i.next(); + // We know that e has a cached field length since BSONObjIterator::next internally + // called BSONElement::size on the BSONElement that it returned, so it is more + // efficient to re-use that information by obtaining the field name as a + // StringData, which will be pre-populated with the cached length. + if (name == e.fieldNameStringData()) + return e; + } + return BSONElement(); +} + +int BSONObj::getIntField(StringData name) const { + BSONElement e = getField(name); + return e.isNumber() ? (int)e.number() : std::numeric_limits<int>::min(); +} + +bool BSONObj::getBoolField(StringData name) const { + BSONElement e = getField(name); + return e.type() == Bool ? e.boolean() : false; +} + +const char* BSONObj::getStringField(StringData name) const { + BSONElement e = getField(name); + return e.type() == String ? e.valuestr() : ""; +} + +bool BSONObj::getObjectID(BSONElement& e) const { + BSONElement f = getField("_id"); + if (!f.eoo()) { + e = f; + return true; } + return false; +} - bool BSONObj::getBoolField(StringData name) const { - BSONElement e = getField(name); - return e.type() == Bool ? e.boolean() : false; +BSONObj BSONObj::removeField(StringData name) const { + BSONObjBuilder b; + BSONObjIterator i(*this); + while (i.more()) { + BSONElement e = i.next(); + const char* fname = e.fieldName(); + if (name != fname) + b.append(e); + } + return b.obj(); +} + +std::string BSONObj::hexDump() const { + std::stringstream ss; + const char* d = objdata(); + int size = objsize(); + for (int i = 0; i < size; ++i) { + ss.width(2); + ss.fill('0'); + ss << std::hex << (unsigned)(unsigned char)(d[i]) << std::dec; + if ((d[i] >= '0' && d[i] <= '9') || (d[i] >= 'A' && d[i] <= 'z')) + ss << '\'' << d[i] << '\''; + if (i != size - 1) + ss << ' '; + } + return ss.str(); +} + + +void BSONObj::elems(std::vector<BSONElement>& v) const { + BSONObjIterator i(*this); + while (i.more()) + v.push_back(i.next()); +} + +void BSONObj::elems(std::list<BSONElement>& v) const { + BSONObjIterator i(*this); + while (i.more()) + v.push_back(i.next()); +} + +/* return has eoo() true if no match + supports "." notation to reach into embedded objects +*/ +BSONElement BSONObj::getFieldDotted(StringData name) const { + BSONElement e = getField(name); + if (e.eoo()) { + size_t dot_offset = name.find('.'); + if (dot_offset != std::string::npos) { + StringData left = name.substr(0, dot_offset); + StringData right = name.substr(dot_offset + 1); + BSONObj sub = getObjectField(left); + return sub.isEmpty() ? BSONElement() : sub.getFieldDotted(right); + } + } + + return e; +} + +BSONObj BSONObj::getObjectField(StringData name) const { + BSONElement e = getField(name); + BSONType t = e.type(); + return t == Object || t == Array ? e.embeddedObject() : BSONObj(); +} + +int BSONObj::nFields() const { + int n = 0; + BSONObjIterator i(*this); + while (i.moreWithEOO()) { + BSONElement e = i.next(); + if (e.eoo()) + break; + n++; + } + return n; +} + +std::string BSONObj::toString(bool isArray, bool full) const { + if (isEmpty()) + return (isArray ? "[]" : "{}"); + StringBuilder s; + toString(s, isArray, full); + return s.str(); +} +void BSONObj::toString(StringBuilder& s, bool isArray, bool full, int depth) const { + if (isEmpty()) { + s << (isArray ? "[]" : "{}"); + return; + } + + s << (isArray ? "[ " : "{ "); + BSONObjIterator i(*this); + bool first = true; + while (1) { + massert(10327, "Object does not end with EOO", i.moreWithEOO()); + BSONElement e = i.next(true); + massert(10328, "Invalid element size", e.size() > 0); + massert(10329, "Element too large", e.size() < (1 << 30)); + int offset = (int)(e.rawdata() - this->objdata()); + massert(10330, "Element extends past end of object", e.size() + offset <= this->objsize()); + bool end = (e.size() + offset == this->objsize()); + if (e.eoo()) { + massert(10331, "EOO Before end of object", end); + break; + } + if (first) + first = false; + else + s << ", "; + e.toString(s, !isArray, full, depth); } + s << (isArray ? " ]" : " }"); +} - const char * BSONObj::getStringField(StringData name) const { - BSONElement e = getField(name); - return e.type() == String ? e.valuestr() : ""; - } +Status DataType::Handler<BSONObj>::load( + BSONObj* bson, const char* ptr, size_t length, size_t* advanced, std::ptrdiff_t debug_offset) { + auto len = ConstDataRange(ptr, ptr + length).read<LittleEndian<uint32_t>>(); - bool BSONObj::getObjectID(BSONElement& e) const { - BSONElement f = getField("_id"); - if( !f.eoo() ) { - e = f; - return true; - } - return false; + if (!len.isOK()) { + mongoutils::str::stream ss; + ss << "buffer size too small to read length at offset: " << debug_offset; + return Status(ErrorCodes::InvalidBSON, ss); } - BSONObj BSONObj::removeField(StringData name) const { - BSONObjBuilder b; - BSONObjIterator i(*this); - while ( i.more() ) { - BSONElement e = i.next(); - const char *fname = e.fieldName(); - if ( name != fname ) - b.append(e); - } - return b.obj(); - } - - std::string BSONObj::hexDump() const { - std::stringstream ss; - const char *d = objdata(); - int size = objsize(); - for( int i = 0; i < size; ++i ) { - ss.width( 2 ); - ss.fill( '0' ); - ss << std::hex << (unsigned)(unsigned char)( d[ i ] ) << std::dec; - if ( ( d[ i ] >= '0' && d[ i ] <= '9' ) || ( d[ i ] >= 'A' && d[ i ] <= 'z' ) ) - ss << '\'' << d[ i ] << '\''; - if ( i != size - 1 ) - ss << ' '; - } - return ss.str(); + if (len.getValue() > length) { + mongoutils::str::stream ss; + ss << "length (" << len.getValue() << ") greater than buffer size (" << length + << ") at offset: " << debug_offset; + return Status(ErrorCodes::InvalidBSON, ss); } - - void BSONObj::elems(std::vector<BSONElement> &v) const { - BSONObjIterator i(*this); - while( i.more() ) - v.push_back(i.next()); + if (len.getValue() < BSONObj::kMinBSONLength) { + mongoutils::str::stream ss; + ss << "Invalid bson length (" << len.getValue() << ") at offset: " << debug_offset; + return Status(ErrorCodes::InvalidBSON, ss); } - void BSONObj::elems(std::list<BSONElement> &v) const { - BSONObjIterator i(*this); - while( i.more() ) - v.push_back(i.next()); - } + try { + BSONObj temp(ptr); - /* return has eoo() true if no match - supports "." notation to reach into embedded objects - */ - BSONElement BSONObj::getFieldDotted(StringData name) const { - BSONElement e = getField(name); - if (e.eoo()) { - size_t dot_offset = name.find('.'); - if (dot_offset != std::string::npos) { - StringData left = name.substr(0, dot_offset); - StringData right = name.substr(dot_offset + 1); - BSONObj sub = getObjectField(left); - return sub.isEmpty() ? BSONElement() : sub.getFieldDotted(right); - } + if (bson) { + *bson = std::move(temp); } + } catch (...) { + auto status = exceptionToStatus(); + mongoutils::str::stream ss; + ss << status.reason() << " at offset: " << debug_offset; - return e; + return Status(status.code(), ss); } - BSONObj BSONObj::getObjectField(StringData name) const { - BSONElement e = getField(name); - BSONType t = e.type(); - return t == Object || t == Array ? e.embeddedObject() : BSONObj(); + if (advanced) { + *advanced = len.getValue(); } - int BSONObj::nFields() const { - int n = 0; - BSONObjIterator i(*this); - while ( i.moreWithEOO() ) { - BSONElement e = i.next(); - if ( e.eoo() ) - break; - n++; - } - return n; - } + return Status::OK(); +} - std::string BSONObj::toString( bool isArray, bool full ) const { - if ( isEmpty() ) return (isArray ? "[]" : "{}"); - StringBuilder s; - toString(s, isArray, full); - return s.str(); +Status DataType::Handler<BSONObj>::store( + const BSONObj& bson, char* ptr, size_t length, size_t* advanced, std::ptrdiff_t debug_offset) { + if (bson.objsize() > static_cast<int>(length)) { + mongoutils::str::stream ss; + ss << "buffer too small to write bson of size (" << bson.objsize() + << ") at offset: " << debug_offset; + return Status(ErrorCodes::Overflow, ss); } - void BSONObj::toString( StringBuilder& s, bool isArray, bool full, int depth ) const { - if ( isEmpty() ) { - s << (isArray ? "[]" : "{}"); - return; - } - s << ( isArray ? "[ " : "{ " ); - BSONObjIterator i(*this); - bool first = true; - while ( 1 ) { - massert( 10327 , "Object does not end with EOO", i.moreWithEOO() ); - BSONElement e = i.next( true ); - massert( 10328 , "Invalid element size", e.size() > 0 ); - massert( 10329 , "Element too large", e.size() < ( 1 << 30 ) ); - int offset = (int) (e.rawdata() - this->objdata()); - massert( 10330 , "Element extends past end of object", - e.size() + offset <= this->objsize() ); - bool end = ( e.size() + offset == this->objsize() ); - if ( e.eoo() ) { - massert( 10331 , "EOO Before end of object", end ); - break; - } - if ( first ) - first = false; - else - s << ", "; - e.toString( s, !isArray, full, depth ); - } - s << ( isArray ? " ]" : " }" ); + if (ptr) { + std::memcpy(ptr, bson.objdata(), bson.objsize()); } - Status DataType::Handler<BSONObj>::load(BSONObj* bson, const char *ptr, size_t length, - size_t *advanced, std::ptrdiff_t debug_offset) { - auto len = ConstDataRange(ptr, ptr + length).read<LittleEndian<uint32_t>>(); - - if (!len.isOK()) { - mongoutils::str::stream ss; - ss << "buffer size too small to read length at offset: " << debug_offset; - return Status(ErrorCodes::InvalidBSON, ss); - } - - if (len.getValue() > length) { - mongoutils::str::stream ss; - ss << "length (" << len.getValue() << ") greater than buffer size (" - << length << ") at offset: " << debug_offset; - return Status(ErrorCodes::InvalidBSON, ss); - } - - if (len.getValue() < BSONObj::kMinBSONLength) { - mongoutils::str::stream ss; - ss << "Invalid bson length (" << len.getValue() << ") at offset: " - << debug_offset; - return Status(ErrorCodes::InvalidBSON, ss); - } - - try { - BSONObj temp(ptr); - - if (bson) { - *bson = std::move(temp); - } - } - catch (...) { - auto status = exceptionToStatus(); - mongoutils::str::stream ss; - ss << status.reason() << " at offset: " << debug_offset; - - return Status(status.code(), ss); - } - - if (advanced) { - *advanced = len.getValue(); - } - - return Status::OK(); + if (advanced) { + *advanced = bson.objsize(); } - Status DataType::Handler<BSONObj>::store(const BSONObj& bson, char *ptr, size_t length, - size_t *advanced, std::ptrdiff_t debug_offset) { - if (bson.objsize() > static_cast<int>(length)) { - mongoutils::str::stream ss; - ss << "buffer too small to write bson of size (" << bson.objsize() - << ") at offset: " << debug_offset; - return Status(ErrorCodes::Overflow, ss); - } - - if (ptr) { - std::memcpy(ptr, bson.objdata(), bson.objsize()); - } + return Status::OK(); +} - if (advanced) { - *advanced = bson.objsize(); - } +std::ostream& operator<<(std::ostream& s, const BSONObj& o) { + return s << o.toString(); +} - return Status::OK(); - } +StringBuilder& operator<<(StringBuilder& s, const BSONObj& o) { + o.toString(s); + return s; +} - std::ostream& operator<<( std::ostream &s, const BSONObj &o ) { - return s << o.toString(); - } - - StringBuilder& operator<<( StringBuilder &s, const BSONObj &o ) { - o.toString( s ); - return s; - } +/** Compare two bson elements, provided as const char *'s, by field name. */ +class BSONIteratorSorted::ElementFieldCmp { +public: + ElementFieldCmp(bool isArray); + bool operator()(const char* s1, const char* s2) const; - /** Compare two bson elements, provided as const char *'s, by field name. */ - class BSONIteratorSorted::ElementFieldCmp { - public: - ElementFieldCmp( bool isArray ); - bool operator()( const char *s1, const char *s2 ) const; - private: - LexNumCmp _cmp; - }; +private: + LexNumCmp _cmp; +}; - BSONIteratorSorted::ElementFieldCmp::ElementFieldCmp( bool isArray ) : - _cmp( !isArray ) { - } +BSONIteratorSorted::ElementFieldCmp::ElementFieldCmp(bool isArray) : _cmp(!isArray) {} - bool BSONIteratorSorted::ElementFieldCmp::operator()( const char *s1, const char *s2 ) - const { - // Skip the type byte and compare field names. - return _cmp( s1 + 1, s2 + 1 ); - } +bool BSONIteratorSorted::ElementFieldCmp::operator()(const char* s1, const char* s2) const { + // Skip the type byte and compare field names. + return _cmp(s1 + 1, s2 + 1); +} - BSONIteratorSorted::BSONIteratorSorted( const BSONObj &o, const ElementFieldCmp &cmp ) - : _nfields(o.nFields()), _fields(new const char*[_nfields]) { - int x = 0; - BSONObjIterator i( o ); - while ( i.more() ) { - _fields[x++] = i.next().rawdata(); - verify( _fields[x-1] ); - } - verify( x == _nfields ); - std::sort( _fields.get() , _fields.get() + _nfields , cmp ); - _cur = 0; +BSONIteratorSorted::BSONIteratorSorted(const BSONObj& o, const ElementFieldCmp& cmp) + : _nfields(o.nFields()), _fields(new const char* [_nfields]) { + int x = 0; + BSONObjIterator i(o); + while (i.more()) { + _fields[x++] = i.next().rawdata(); + verify(_fields[x - 1]); } + verify(x == _nfields); + std::sort(_fields.get(), _fields.get() + _nfields, cmp); + _cur = 0; +} - BSONObjIteratorSorted::BSONObjIteratorSorted( const BSONObj &object ) : - BSONIteratorSorted( object, ElementFieldCmp( false ) ) { - } +BSONObjIteratorSorted::BSONObjIteratorSorted(const BSONObj& object) + : BSONIteratorSorted(object, ElementFieldCmp(false)) {} - BSONArrayIteratorSorted::BSONArrayIteratorSorted( const BSONArray &array ) : - BSONIteratorSorted( array, ElementFieldCmp( true ) ) { - } +BSONArrayIteratorSorted::BSONArrayIteratorSorted(const BSONArray& array) + : BSONIteratorSorted(array, ElementFieldCmp(true)) {} -} // namespace mongo +} // namespace mongo diff --git a/src/mongo/bson/bsonobj.h b/src/mongo/bson/bsonobj.h index 32411f21118..269278bef92 100644 --- a/src/mongo/bson/bsonobj.h +++ b/src/mongo/bson/bsonobj.h @@ -49,717 +49,750 @@ namespace mongo { - typedef std::set< BSONElement, BSONElementCmpWithoutField > BSONElementSet; - typedef std::multiset< BSONElement, BSONElementCmpWithoutField > BSONElementMSet; +typedef std::set<BSONElement, BSONElementCmpWithoutField> BSONElementSet; +typedef std::multiset<BSONElement, BSONElementCmpWithoutField> BSONElementMSet; + +/** + C++ representation of a "BSON" object -- that is, an extended JSON-style + object in a binary representation. + + See bsonspec.org. + + Note that BSONObj's have a smart pointer capability built in -- so you can + pass them around by value. The reference counts used to implement this + do not use locking, so copying and destroying BSONObj's are not thread-safe + operations. + + BSON object format: + + code + <unsigned totalSize> {<byte BSONType><cstring FieldName><Data>}* EOO + + totalSize includes itself. + + Data: + Bool: <byte> + EOO: nothing follows + Undefined: nothing follows + OID: an OID object + NumberDouble: <double> + NumberInt: <int32> + String: <unsigned32 strsizewithnull><cstring> + Date: <8bytes> + Regex: <cstring regex><cstring options> + Object: a nested object, leading with its entire size, which terminates with EOO. + Array: same as object + DBRef: <strlen> <cstring ns> <oid> + DBRef: a database reference: basically a collection name plus an Object ID + BinData: <int len> <byte subtype> <byte[len] data> + Code: a function (not a closure): same format as String. + Symbol: a language symbol (say a python symbol). same format as String. + Code With Scope: <total size><String><Object> + \endcode + */ +class BSONObj { +public: + static const char kMinBSONLength = 5; + + /** Construct an empty BSONObj -- that is, {}. */ + BSONObj() { + // Little endian ordering here, but that is ok regardless as BSON is spec'd to be + // little endian external to the system. (i.e. the rest of the implementation of + // bson, not this part, fails to support big endian) + static const char kEmptyObjectPrototype[] = {/*size*/ kMinBSONLength, 0, 0, 0, /*eoo*/ 0}; + + _objdata = kEmptyObjectPrototype; + } - /** - C++ representation of a "BSON" object -- that is, an extended JSON-style - object in a binary representation. - - See bsonspec.org. - - Note that BSONObj's have a smart pointer capability built in -- so you can - pass them around by value. The reference counts used to implement this - do not use locking, so copying and destroying BSONObj's are not thread-safe - operations. - - BSON object format: - - code - <unsigned totalSize> {<byte BSONType><cstring FieldName><Data>}* EOO - - totalSize includes itself. - - Data: - Bool: <byte> - EOO: nothing follows - Undefined: nothing follows - OID: an OID object - NumberDouble: <double> - NumberInt: <int32> - String: <unsigned32 strsizewithnull><cstring> - Date: <8bytes> - Regex: <cstring regex><cstring options> - Object: a nested object, leading with its entire size, which terminates with EOO. - Array: same as object - DBRef: <strlen> <cstring ns> <oid> - DBRef: a database reference: basically a collection name plus an Object ID - BinData: <int len> <byte subtype> <byte[len] data> - Code: a function (not a closure): same format as String. - Symbol: a language symbol (say a python symbol). same format as String. - Code With Scope: <total size><String><Object> - \endcode + /** Construct a BSONObj from data in the proper format. + * Use this constructor when something else owns bsonData's buffer + */ + explicit BSONObj(const char* bsonData) { + init(bsonData); + } + + explicit BSONObj(SharedBuffer ownedBuffer) + : _objdata(ownedBuffer.get() ? ownedBuffer.get() : BSONObj().objdata()), + _ownedBuffer(std::move(ownedBuffer)) {} + + /** Move construct a BSONObj */ + BSONObj(BSONObj&& other) + : _objdata(std::move(other._objdata)), _ownedBuffer(std::move(other._ownedBuffer)) { + other._objdata = BSONObj()._objdata; // To return to an empty state. + dassert(!other.isOwned()); + } + + // The explicit move constructor above will inhibit generation of the copy ctor, so + // explicitly request the default implementation. + + /** Copy construct a BSONObj. */ + BSONObj(const BSONObj&) = default; + + /** Provide assignment semantics. We use the value taking form so that we can use copy + * and swap, and consume both lvalue and rvalue references. */ - class BSONObj { - public: + BSONObj& operator=(BSONObj otherCopy) { + this->swap(otherCopy); + return *this; + } - static const char kMinBSONLength = 5; + /** Swap this BSONObj with 'other' */ + void swap(BSONObj& other) { + using std::swap; + swap(_objdata, other._objdata); + swap(_ownedBuffer, other._ownedBuffer); + } - /** Construct an empty BSONObj -- that is, {}. */ - BSONObj() { - // Little endian ordering here, but that is ok regardless as BSON is spec'd to be - // little endian external to the system. (i.e. the rest of the implementation of - // bson, not this part, fails to support big endian) - static const char kEmptyObjectPrototype[] = - { /*size*/kMinBSONLength, 0, 0, 0, /*eoo*/0 }; + /** + A BSONObj can use a buffer it "owns" or one it does not. - _objdata = kEmptyObjectPrototype; - } + OWNED CASE + If the BSONObj owns the buffer, the buffer can be shared among several BSONObj's (by assignment). + In this case the buffer is basically implemented as a shared_ptr. + Since BSONObj's are typically immutable, this works well. - /** Construct a BSONObj from data in the proper format. - * Use this constructor when something else owns bsonData's buffer - */ - explicit BSONObj(const char *bsonData) { - init(bsonData); - } + UNOWNED CASE + A BSONObj can also point to BSON data in some other data structure it does not "own" or free later. + For example, in a memory mapped file. In this case, it is important the original data stays in + scope for as long as the BSONObj is in use. If you think the original data may go out of scope, + call BSONObj::getOwned() to promote your BSONObj to having its own copy. - explicit BSONObj(SharedBuffer ownedBuffer) - : _objdata(ownedBuffer.get() ? ownedBuffer.get() : BSONObj().objdata()) - , _ownedBuffer(std::move(ownedBuffer)) { - } + On a BSONObj assignment, if the source is unowned, both the source and dest will have unowned + pointers to the original buffer after the assignment. - /** Move construct a BSONObj */ - BSONObj(BSONObj&& other) - : _objdata(std::move(other._objdata)) - , _ownedBuffer(std::move(other._ownedBuffer)) { - other._objdata = BSONObj()._objdata; // To return to an empty state. - dassert(!other.isOwned()); - } + If you are not sure about ownership but need the buffer to last as long as the BSONObj, call + getOwned(). getOwned() is a no-op if the buffer is already owned. If not already owned, a malloc + and memcpy will result. - // The explicit move constructor above will inhibit generation of the copy ctor, so - // explicitly request the default implementation. + Most ways to create BSONObj's create 'owned' variants. Unowned versions can be created with: + (1) specifying true for the ifree parameter in the constructor + (2) calling BSONObjBuilder::done(). Use BSONObjBuilder::obj() to get an owned copy + (3) retrieving a subobject retrieves an unowned pointer into the parent BSON object - /** Copy construct a BSONObj. */ - BSONObj(const BSONObj&) = default; + @return true if this is in owned mode + */ + bool isOwned() const { + return _ownedBuffer.get() != 0; + } - /** Provide assignment semantics. We use the value taking form so that we can use copy - * and swap, and consume both lvalue and rvalue references. - */ - BSONObj& operator=(BSONObj otherCopy) { - this->swap(otherCopy); - return *this; - } + /** assure the data buffer is under the control of this BSONObj and not a remote buffer + @see isOwned() + */ + BSONObj getOwned() const; - /** Swap this BSONObj with 'other' */ - void swap(BSONObj& other) { - using std::swap; - swap(_objdata, other._objdata); - swap(_ownedBuffer, other._ownedBuffer); - } + /** @return a new full (and owned) copy of the object. */ + BSONObj copy() const; - /** - A BSONObj can use a buffer it "owns" or one it does not. - - OWNED CASE - If the BSONObj owns the buffer, the buffer can be shared among several BSONObj's (by assignment). - In this case the buffer is basically implemented as a shared_ptr. - Since BSONObj's are typically immutable, this works well. - - UNOWNED CASE - A BSONObj can also point to BSON data in some other data structure it does not "own" or free later. - For example, in a memory mapped file. In this case, it is important the original data stays in - scope for as long as the BSONObj is in use. If you think the original data may go out of scope, - call BSONObj::getOwned() to promote your BSONObj to having its own copy. - - On a BSONObj assignment, if the source is unowned, both the source and dest will have unowned - pointers to the original buffer after the assignment. - - If you are not sure about ownership but need the buffer to last as long as the BSONObj, call - getOwned(). getOwned() is a no-op if the buffer is already owned. If not already owned, a malloc - and memcpy will result. - - Most ways to create BSONObj's create 'owned' variants. Unowned versions can be created with: - (1) specifying true for the ifree parameter in the constructor - (2) calling BSONObjBuilder::done(). Use BSONObjBuilder::obj() to get an owned copy - (3) retrieving a subobject retrieves an unowned pointer into the parent BSON object - - @return true if this is in owned mode - */ - bool isOwned() const { return _ownedBuffer.get() != 0; } - - /** assure the data buffer is under the control of this BSONObj and not a remote buffer - @see isOwned() - */ - BSONObj getOwned() const; - - /** @return a new full (and owned) copy of the object. */ - BSONObj copy() const; - - /** Readable representation of a BSON object in an extended JSON-style notation. - This is an abbreviated representation which might be used for logging. - */ - enum { maxToStringRecursionDepth = 100 }; - - std::string toString( bool isArray = false, bool full=false ) const; - void toString( StringBuilder& s, bool isArray = false, bool full=false, int depth=0 ) const; - - /** Properly formatted JSON string. - @param pretty if true we try to add some lf's and indentation - */ - std::string jsonString( - JsonStringFormat format = Strict, - int pretty = 0, - bool isArray = false - ) const; - - /** note: addFields always adds _id even if not specified */ - int addFields(BSONObj& from, std::set<std::string>& fields); /* returns n added */ - - /** remove specified field and return a new object with the remaining fields. - slowish as builds a full new object - */ - BSONObj removeField(StringData name) const; - - /** returns # of top level fields in the object - note: iterates to count the fields - */ - int nFields() const; - - /** adds the field names to the fields set. does NOT clear it (appends). */ - int getFieldNames(std::set<std::string>& fields) const; - - /** @return the specified element. element.eoo() will be true if not found. - @param name field to find. supports dot (".") notation to reach into embedded objects. - for example "x.y" means "in the nested object in field x, retrieve field y" - */ - BSONElement getFieldDotted(StringData name) const; - - /** Like getFieldDotted(), but expands arrays and returns all matching objects. - * Turning off expandLastArray allows you to retrieve nested array objects instead of - * their contents. - */ - void getFieldsDotted(StringData name, BSONElementSet &ret, bool expandLastArray = true ) const; - void getFieldsDotted(StringData name, BSONElementMSet &ret, bool expandLastArray = true ) const; - - /** Like getFieldDotted(), but returns first array encountered while traversing the - dotted fields of name. The name variable is updated to represent field - names with respect to the returned element. */ - BSONElement getFieldDottedOrArray(const char *&name) const; - - /** Get the field of the specified name. eoo() is true on the returned - element if not found. - */ - BSONElement getField(StringData name) const; - - /** Get several fields at once. This is faster than separate getField() calls as the size of - elements iterated can then be calculated only once each. - @param n number of fieldNames, and number of elements in the fields array - @param fields if a field is found its element is stored in its corresponding position in this array. - if not found the array element is unchanged. - */ - void getFields(unsigned n, const char **fieldNames, BSONElement *fields) const; - - /** Get the field of the specified name. eoo() is true on the returned - element if not found. - */ - BSONElement operator[] (StringData field) const { - return getField(field); - } + /** Readable representation of a BSON object in an extended JSON-style notation. + This is an abbreviated representation which might be used for logging. + */ + enum { maxToStringRecursionDepth = 100 }; - BSONElement operator[] (int field) const { - StringBuilder ss; - ss << field; - std::string s = ss.str(); - return getField(s.c_str()); - } + std::string toString(bool isArray = false, bool full = false) const; + void toString(StringBuilder& s, bool isArray = false, bool full = false, int depth = 0) const; - /** @return true if field exists */ - bool hasField( StringData name ) const { return !getField(name).eoo(); } - /** @return true if field exists */ - bool hasElement(StringData name) const { return hasField(name); } - - /** @return "" if DNE or wrong type */ - const char * getStringField(StringData name) const; - - /** @return subobject of the given name */ - BSONObj getObjectField(StringData name) const; - - /** @return INT_MIN if not present - does some type conversions */ - int getIntField(StringData name) const; - - /** @return false if not present - @see BSONElement::trueValue() - */ - bool getBoolField(StringData name) const; - - /** @param pattern a BSON obj indicating a set of (un-dotted) field - * names. Element values are ignored. - * @return a BSON obj constructed by taking the elements of this obj - * that correspond to the fields in pattern. Field names of the - * returned object are replaced with the empty string. If field in - * pattern is missing, it is omitted from the returned object. - * - * Example: if this = {a : 4 , b : 5 , c : 6}) - * this.extractFieldsUnDotted({a : 1 , c : 1}) -> {"" : 4 , "" : 6 } - * this.extractFieldsUnDotted({b : "blah"}) -> {"" : 5} - * - */ - BSONObj extractFieldsUnDotted(const BSONObj& pattern) const; - - /** extract items from object which match a pattern object. - e.g., if pattern is { x : 1, y : 1 }, builds an object with - x and y elements of this object, if they are present. - returns elements with original field names - */ - BSONObj extractFields(const BSONObj &pattern , bool fillWithNull=false) const; - - BSONObj filterFieldsUndotted(const BSONObj &filter, bool inFilter) const; - - BSONElement getFieldUsingIndexNames(StringData fieldName, - const BSONObj &indexKey) const; - - /** arrays are bson objects with numeric and increasing field names - @return true if field names are numeric and increasing - */ - bool couldBeArray() const; - - /** @return the raw data of the object */ - const char *objdata() const { - return _objdata; - } + /** Properly formatted JSON string. + @param pretty if true we try to add some lf's and indentation + */ + std::string jsonString(JsonStringFormat format = Strict, + int pretty = 0, + bool isArray = false) const; - /** @return total size of the BSON object in bytes */ - int objsize() const { - return ConstDataView(objdata()).read<LittleEndian<int>>(); - } + /** note: addFields always adds _id even if not specified */ + int addFields(BSONObj& from, std::set<std::string>& fields); /* returns n added */ - /** performs a cursory check on the object's size only. */ - bool isValid() const { - int x = objsize(); - return x > 0 && x <= BSONObjMaxInternalSize; - } + /** remove specified field and return a new object with the remaining fields. + slowish as builds a full new object + */ + BSONObj removeField(StringData name) const; - /** @return ok if it can be stored as a valid embedded doc. - * Not valid if any field name: - * - contains a "." - * - starts with "$" - * -- unless it is a dbref ($ref/$id/[$db]/...) - */ - inline bool okForStorage() const { - return _okForStorage(false, true).isOK(); - } + /** returns # of top level fields in the object + note: iterates to count the fields + */ + int nFields() const; - /** Same as above with the following extra restrictions - * Not valid if: - * - "_id" field is a - * -- Regex - * -- Array - */ - inline bool okForStorageAsRoot() const { - return _okForStorage(true, true).isOK(); - } + /** adds the field names to the fields set. does NOT clear it (appends). */ + int getFieldNames(std::set<std::string>& fields) const; - /** - * Validates that this can be stored as an embedded document - * See details above in okForStorage - * - * If 'deep' is true then validation is done to children - * - * If not valid a user readable status message is returned. - */ - inline Status storageValidEmbedded(const bool deep = true) const { - return _okForStorage(false, deep); - } + /** @return the specified element. element.eoo() will be true if not found. + @param name field to find. supports dot (".") notation to reach into embedded objects. + for example "x.y" means "in the nested object in field x, retrieve field y" + */ + BSONElement getFieldDotted(StringData name) const; - /** - * Validates that this can be stored as a document (in a collection) - * See details above in okForStorageAsRoot - * - * If 'deep' is true then validation is done to children - * - * If not valid a user readable status message is returned. - */ - inline Status storageValid(const bool deep = true) const { - return _okForStorage(true, deep); - } + /** Like getFieldDotted(), but expands arrays and returns all matching objects. + * Turning off expandLastArray allows you to retrieve nested array objects instead of + * their contents. + */ + void getFieldsDotted(StringData name, BSONElementSet& ret, bool expandLastArray = true) const; + void getFieldsDotted(StringData name, BSONElementMSet& ret, bool expandLastArray = true) const; - /** @return true if object is empty -- i.e., {} */ - bool isEmpty() const { return objsize() <= kMinBSONLength; } - - void dump() const; - - /** Alternative output format */ - std::string hexDump() const; - - /**wo='well ordered'. fields must be in same order in each object. - Ordering is with respect to the signs of the elements - and allows ascending / descending key mixing. - @return <0 if l<r. 0 if l==r. >0 if l>r - */ - int woCompare(const BSONObj& r, const Ordering &o, - bool considerFieldName=true) const; - - /**wo='well ordered'. fields must be in same order in each object. - Ordering is with respect to the signs of the elements - and allows ascending / descending key mixing. - @return <0 if l<r. 0 if l==r. >0 if l>r - */ - int woCompare(const BSONObj& r, const BSONObj &ordering = BSONObj(), - bool considerFieldName=true) const; - - bool operator<( const BSONObj& other ) const { return woCompare( other ) < 0; } - bool operator<=( const BSONObj& other ) const { return woCompare( other ) <= 0; } - bool operator>( const BSONObj& other ) const { return woCompare( other ) > 0; } - bool operator>=( const BSONObj& other ) const { return woCompare( other ) >= 0; } - - /** - * @param useDotted whether to treat sort key fields as possibly dotted and expand into them - */ - int woSortOrder( const BSONObj& r , const BSONObj& sortKey , bool useDotted=false ) const; - - bool equal(const BSONObj& r) const; - - /** - * Functor compatible with std::hash for std::unordered_{map,set} - * Warning: The hash function is subject to change. Do not use in cases where hashes need - * to be consistent across versions. - */ - struct Hasher { - size_t operator() (const BSONObj& obj) const; - }; - - /** - * @param otherObj - * @return true if 'this' is a prefix of otherObj- in other words if - * otherObj contains the same field names and field vals in the same - * order as 'this', plus optionally some additional elements. - */ - bool isPrefixOf( const BSONObj& otherObj ) const; - - /** - * @param otherObj - * @return returns true if the list of field names in 'this' is a prefix - * of the list of field names in otherObj. Similar to 'isPrefixOf', - * but ignores the field values and only looks at field names. - */ - bool isFieldNamePrefixOf( const BSONObj& otherObj ) const; - - /** This is "shallow equality" -- ints and doubles won't match. for a - deep equality test use woCompare (which is slower). - */ - bool binaryEqual(const BSONObj& r) const { - int os = objsize(); - if ( os == r.objsize() ) { - return (os == 0 || memcmp(objdata(),r.objdata(),os)==0); - } - return false; - } + /** Like getFieldDotted(), but returns first array encountered while traversing the + dotted fields of name. The name variable is updated to represent field + names with respect to the returned element. */ + BSONElement getFieldDottedOrArray(const char*& name) const; - /** @return first field of the object */ - BSONElement firstElement() const { return BSONElement(objdata() + 4); } + /** Get the field of the specified name. eoo() is true on the returned + element if not found. + */ + BSONElement getField(StringData name) const; - /** faster than firstElement().fieldName() - for the first element we can easily find the fieldname without - computing the element size. - */ - const char * firstElementFieldName() const { - const char *p = objdata() + 4; - return *p == EOO ? "" : p+1; - } + /** Get several fields at once. This is faster than separate getField() calls as the size of + elements iterated can then be calculated only once each. + @param n number of fieldNames, and number of elements in the fields array + @param fields if a field is found its element is stored in its corresponding position in this array. + if not found the array element is unchanged. + */ + void getFields(unsigned n, const char** fieldNames, BSONElement* fields) const; - BSONType firstElementType() const { - const char *p = objdata() + 4; - return (BSONType) *p; - } + /** Get the field of the specified name. eoo() is true on the returned + element if not found. + */ + BSONElement operator[](StringData field) const { + return getField(field); + } - /** Get the _id field from the object. For good performance drivers should - assure that _id is the first element of the object; however, correct operation - is assured regardless. - @return true if found - */ - bool getObjectID(BSONElement& e) const; - - // Return a version of this object where top level elements of types - // that are not part of the bson wire protocol are replaced with - // std::string identifier equivalents. - // TODO Support conversion of element types other than min and max. - BSONObj clientReadable() const; - - /** Return new object with the field names replaced by those in the - passed object. */ - BSONObj replaceFieldNames( const BSONObj &obj ) const; - - /** true unless corrupt */ - bool valid() const; - - bool operator==( const BSONObj& other ) const { return equal( other ); } - bool operator!=(const BSONObj& other) const { return !operator==( other); } - - enum MatchType { - Equality = 0, - LT = 0x1, - LTE = 0x3, - GTE = 0x6, - GT = 0x4, - opIN = 0x8, // { x : { $in : [1,2,3] } } - NE = 0x9, - opSIZE = 0x0A, - opALL = 0x0B, - NIN = 0x0C, - opEXISTS = 0x0D, - opMOD = 0x0E, - opTYPE = 0x0F, - opREGEX = 0x10, - opOPTIONS = 0x11, - opELEM_MATCH = 0x12, - opNEAR = 0x13, - opWITHIN = 0x14, - opMAX_DISTANCE = 0x15, - opGEO_INTERSECTS = 0x16, - }; - - /** add all elements of the object to the specified vector */ - void elems(std::vector<BSONElement> &) const; - /** add all elements of the object to the specified list */ - void elems(std::list<BSONElement> &) const; - - friend class BSONObjIterator; - typedef BSONObjIterator iterator; - - /** - * These enable range-based for loops over BSONObjs: - * - * for (BSONElement elem : BSON("a" << 1 << "b" << 2)) { - * ... // Do something with elem - * } - */ - BSONObjIterator begin() const; - BSONObjIterator end() const; - - void appendSelfToBufBuilder(BufBuilder& b) const { - verify( objsize() ); - b.appendBuf(objdata(), objsize()); - } + BSONElement operator[](int field) const { + StringBuilder ss; + ss << field; + std::string s = ss.str(); + return getField(s.c_str()); + } - template<typename T> bool coerceVector( std::vector<T>* out ) const; + /** @return true if field exists */ + bool hasField(StringData name) const { + return !getField(name).eoo(); + } + /** @return true if field exists */ + bool hasElement(StringData name) const { + return hasField(name); + } - typedef SharedBuffer::Holder Holder; + /** @return "" if DNE or wrong type */ + const char* getStringField(StringData name) const; - /** Given a pointer to a region of un-owned memory containing BSON data, prefixed by - * sufficient space for a BSONObj::Holder object, return a BSONObj that owns the - * memory. - * - * This class will call free(holderPrefixedData), so it must have been allocated in a way - * that makes that valid. - */ - static BSONObj takeOwnership(char* holderPrefixedData) { - return BSONObj(SharedBuffer::takeOwnership(holderPrefixedData)); - } + /** @return subobject of the given name */ + BSONObj getObjectField(StringData name) const; - /// members for Sorter - struct SorterDeserializeSettings {}; // unused - void serializeForSorter(BufBuilder& buf) const { buf.appendBuf(objdata(), objsize()); } - static BSONObj deserializeForSorter(BufReader& buf, const SorterDeserializeSettings&) { - const int size = buf.peek<int>(); - const void* ptr = buf.skip(size); - return BSONObj(static_cast<const char*>(ptr)); - } - int memUsageForSorter() const { - // TODO consider ownedness? - return sizeof(BSONObj) + objsize(); - } + /** @return INT_MIN if not present - does some type conversions */ + int getIntField(StringData name) const; - private: - void _assertInvalid() const; + /** @return false if not present + @see BSONElement::trueValue() + */ + bool getBoolField(StringData name) const; + + /** @param pattern a BSON obj indicating a set of (un-dotted) field + * names. Element values are ignored. + * @return a BSON obj constructed by taking the elements of this obj + * that correspond to the fields in pattern. Field names of the + * returned object are replaced with the empty string. If field in + * pattern is missing, it is omitted from the returned object. + * + * Example: if this = {a : 4 , b : 5 , c : 6}) + * this.extractFieldsUnDotted({a : 1 , c : 1}) -> {"" : 4 , "" : 6 } + * this.extractFieldsUnDotted({b : "blah"}) -> {"" : 5} + * + */ + BSONObj extractFieldsUnDotted(const BSONObj& pattern) const; - void init(const char *data) { - _objdata = data; - if ( !isValid() ) - _assertInvalid(); - } + /** extract items from object which match a pattern object. + e.g., if pattern is { x : 1, y : 1 }, builds an object with + x and y elements of this object, if they are present. + returns elements with original field names + */ + BSONObj extractFields(const BSONObj& pattern, bool fillWithNull = false) const; - /** - * Validate if the element is okay to be stored in a collection, maybe as the root element - * - * If 'root' is true then checks against _id are made. - * If 'deep' is false then do not traverse through children - */ - Status _okForStorage(bool root, bool deep) const; + BSONObj filterFieldsUndotted(const BSONObj& filter, bool inFilter) const; - const char* _objdata; - SharedBuffer _ownedBuffer; - }; + BSONElement getFieldUsingIndexNames(StringData fieldName, const BSONObj& indexKey) const; - std::ostream& operator<<( std::ostream &s, const BSONObj &o ); - std::ostream& operator<<( std::ostream &s, const BSONElement &e ); + /** arrays are bson objects with numeric and increasing field names + @return true if field names are numeric and increasing + */ + bool couldBeArray() const; - StringBuilder& operator<<( StringBuilder &s, const BSONObj &o ); - StringBuilder& operator<<( StringBuilder &s, const BSONElement &e ); + /** @return the raw data of the object */ + const char* objdata() const { + return _objdata; + } - inline void swap(BSONObj& l, BSONObj& r) { - l.swap(r); + /** @return total size of the BSON object in bytes */ + int objsize() const { + return ConstDataView(objdata()).read<LittleEndian<int>>(); } - struct BSONArray : BSONObj { - // Don't add anything other than forwarding constructors!!! - BSONArray(): BSONObj() {} - explicit BSONArray(const BSONObj& obj): BSONObj(obj) {} - }; + /** performs a cursory check on the object's size only. */ + bool isValid() const { + int x = objsize(); + return x > 0 && x <= BSONObjMaxInternalSize; + } - /** iterator for a BSONObj + /** @return ok if it can be stored as a valid embedded doc. + * Not valid if any field name: + * - contains a "." + * - starts with "$" + * -- unless it is a dbref ($ref/$id/[$db]/...) + */ + inline bool okForStorage() const { + return _okForStorage(false, true).isOK(); + } - Note each BSONObj ends with an EOO element: so you will get more() on an empty - object, although next().eoo() will be true. + /** Same as above with the following extra restrictions + * Not valid if: + * - "_id" field is a + * -- Regex + * -- Array + */ + inline bool okForStorageAsRoot() const { + return _okForStorage(true, true).isOK(); + } - The BSONObj must stay in scope for the duration of the iterator's execution. + /** + * Validates that this can be stored as an embedded document + * See details above in okForStorage + * + * If 'deep' is true then validation is done to children + * + * If not valid a user readable status message is returned. + */ + inline Status storageValidEmbedded(const bool deep = true) const { + return _okForStorage(false, deep); + } - todo: Finish making this an STL-compatible iterator. - Need iterator_catagory et al (maybe inherit from std::iterator). - Need operator-> - operator* should return a const reference not a value. - */ - class BSONObjIterator { - public: - /** Create an iterator for a BSON object. - */ - explicit BSONObjIterator(const BSONObj& jso) { - int sz = jso.objsize(); - if ( MONGO_unlikely(sz == 0) ) { - _pos = _theend = 0; - return; - } - _pos = jso.objdata() + 4; - _theend = jso.objdata() + sz - 1; - } + /** + * Validates that this can be stored as a document (in a collection) + * See details above in okForStorageAsRoot + * + * If 'deep' is true then validation is done to children + * + * If not valid a user readable status message is returned. + */ + inline Status storageValid(const bool deep = true) const { + return _okForStorage(true, deep); + } - BSONObjIterator( const char * start , const char * end ) { - _pos = start + 4; - _theend = end - 1; - } + /** @return true if object is empty -- i.e., {} */ + bool isEmpty() const { + return objsize() <= kMinBSONLength; + } - static BSONObjIterator endOf(const BSONObj& obj) { - BSONObjIterator end(obj); - end._pos = end._theend; - return end; - } + void dump() const; - /** @return true if more elements exist to be enumerated. */ - bool more() { return _pos < _theend; } + /** Alternative output format */ + std::string hexDump() const; - /** @return true if more elements exist to be enumerated INCLUDING the EOO element which is always at the end. */ - bool moreWithEOO() { return _pos <= _theend; } + /**wo='well ordered'. fields must be in same order in each object. + Ordering is with respect to the signs of the elements + and allows ascending / descending key mixing. + @return <0 if l<r. 0 if l==r. >0 if l>r + */ + int woCompare(const BSONObj& r, const Ordering& o, bool considerFieldName = true) const; - /** @return the next element in the object. For the final element, element.eoo() will be true. */ - BSONElement next( bool checkEnd ) { - verify( _pos <= _theend ); + /**wo='well ordered'. fields must be in same order in each object. + Ordering is with respect to the signs of the elements + and allows ascending / descending key mixing. + @return <0 if l<r. 0 if l==r. >0 if l>r + */ + int woCompare(const BSONObj& r, + const BSONObj& ordering = BSONObj(), + bool considerFieldName = true) const; - int maxLen = -1; - if ( checkEnd ) { - maxLen = _theend + 1 - _pos; - verify( maxLen > 0 ); - } + bool operator<(const BSONObj& other) const { + return woCompare(other) < 0; + } + bool operator<=(const BSONObj& other) const { + return woCompare(other) <= 0; + } + bool operator>(const BSONObj& other) const { + return woCompare(other) > 0; + } + bool operator>=(const BSONObj& other) const { + return woCompare(other) >= 0; + } - BSONElement e( _pos, maxLen ); - int esize = e.size( maxLen ); - massert( 16446, "BSONElement has bad size", esize > 0 ); - _pos += esize; + /** + * @param useDotted whether to treat sort key fields as possibly dotted and expand into them + */ + int woSortOrder(const BSONObj& r, const BSONObj& sortKey, bool useDotted = false) const; - return e; - } + bool equal(const BSONObj& r) const; - BSONElement next() { - verify( _pos <= _theend ); - BSONElement e(_pos); - _pos += e.size(); - return e; - } + /** + * Functor compatible with std::hash for std::unordered_{map,set} + * Warning: The hash function is subject to change. Do not use in cases where hashes need + * to be consistent across versions. + */ + struct Hasher { + size_t operator()(const BSONObj& obj) const; + }; - /** pre-increment */ - BSONObjIterator& operator++() { - next(); - return *this; - } + /** + * @param otherObj + * @return true if 'this' is a prefix of otherObj- in other words if + * otherObj contains the same field names and field vals in the same + * order as 'this', plus optionally some additional elements. + */ + bool isPrefixOf(const BSONObj& otherObj) const; - /** post-increment */ - BSONObjIterator operator++(int) { - BSONObjIterator oldPos = *this; - next(); - return oldPos; - } + /** + * @param otherObj + * @return returns true if the list of field names in 'this' is a prefix + * of the list of field names in otherObj. Similar to 'isPrefixOf', + * but ignores the field values and only looks at field names. + */ + bool isFieldNamePrefixOf(const BSONObj& otherObj) const; - BSONElement operator*() { - verify( _pos <= _theend ); - return BSONElement(_pos); + /** This is "shallow equality" -- ints and doubles won't match. for a + deep equality test use woCompare (which is slower). + */ + bool binaryEqual(const BSONObj& r) const { + int os = objsize(); + if (os == r.objsize()) { + return (os == 0 || memcmp(objdata(), r.objdata(), os) == 0); } + return false; + } - bool operator==(const BSONObjIterator& other) { - dassert(_theend == other._theend); - return _pos == other._pos; - } + /** @return first field of the object */ + BSONElement firstElement() const { + return BSONElement(objdata() + 4); + } - bool operator!=(const BSONObjIterator& other) { - return !(*this == other); - } + /** faster than firstElement().fieldName() - for the first element we can easily find the fieldname without + computing the element size. + */ + const char* firstElementFieldName() const { + const char* p = objdata() + 4; + return *p == EOO ? "" : p + 1; + } - private: - const char* _pos; - const char* _theend; - }; + BSONType firstElementType() const { + const char* p = objdata() + 4; + return (BSONType)*p; + } - /** Base class implementing ordered iteration through BSONElements. */ - class BSONIteratorSorted { - MONGO_DISALLOW_COPYING(BSONIteratorSorted); - public: - ~BSONIteratorSorted() { - verify( _fields ); - } + /** Get the _id field from the object. For good performance drivers should + assure that _id is the first element of the object; however, correct operation + is assured regardless. + @return true if found + */ + bool getObjectID(BSONElement& e) const; - bool more() { - return _cur < _nfields; - } + // Return a version of this object where top level elements of types + // that are not part of the bson wire protocol are replaced with + // std::string identifier equivalents. + // TODO Support conversion of element types other than min and max. + BSONObj clientReadable() const; - BSONElement next() { - verify( _fields ); - if ( _cur < _nfields ) - return BSONElement( _fields[_cur++] ); - return BSONElement(); - } + /** Return new object with the field names replaced by those in the + passed object. */ + BSONObj replaceFieldNames(const BSONObj& obj) const; - protected: - class ElementFieldCmp; - BSONIteratorSorted( const BSONObj &o, const ElementFieldCmp &cmp ); + /** true unless corrupt */ + bool valid() const; - private: - const int _nfields; - const std::unique_ptr<const char *[]> _fields; - int _cur; - }; + bool operator==(const BSONObj& other) const { + return equal(other); + } + bool operator!=(const BSONObj& other) const { + return !operator==(other); + } - /** Provides iteration of a BSONObj's BSONElements in lexical field order. */ - class BSONObjIteratorSorted : public BSONIteratorSorted { - public: - BSONObjIteratorSorted( const BSONObj &object ); + enum MatchType { + Equality = 0, + LT = 0x1, + LTE = 0x3, + GTE = 0x6, + GT = 0x4, + opIN = 0x8, // { x : { $in : [1,2,3] } } + NE = 0x9, + opSIZE = 0x0A, + opALL = 0x0B, + NIN = 0x0C, + opEXISTS = 0x0D, + opMOD = 0x0E, + opTYPE = 0x0F, + opREGEX = 0x10, + opOPTIONS = 0x11, + opELEM_MATCH = 0x12, + opNEAR = 0x13, + opWITHIN = 0x14, + opMAX_DISTANCE = 0x15, + opGEO_INTERSECTS = 0x16, }; + /** add all elements of the object to the specified vector */ + void elems(std::vector<BSONElement>&) const; + /** add all elements of the object to the specified list */ + void elems(std::list<BSONElement>&) const; + + friend class BSONObjIterator; + typedef BSONObjIterator iterator; + /** - * Provides iteration of a BSONArray's BSONElements in numeric field order. - * The elements of a bson array should always be numerically ordered by field name, but this - * implementation re-sorts them anyway. + * These enable range-based for loops over BSONObjs: + * + * for (BSONElement elem : BSON("a" << 1 << "b" << 2)) { + * ... // Do something with elem + * } */ - class BSONArrayIteratorSorted : public BSONIteratorSorted { - public: - BSONArrayIteratorSorted( const BSONArray &array ); - }; + BSONObjIterator begin() const; + BSONObjIterator end() const; + + void appendSelfToBufBuilder(BufBuilder& b) const { + verify(objsize()); + b.appendBuf(objdata(), objsize()); + } + + template <typename T> + bool coerceVector(std::vector<T>* out) const; + + typedef SharedBuffer::Holder Holder; + + /** Given a pointer to a region of un-owned memory containing BSON data, prefixed by + * sufficient space for a BSONObj::Holder object, return a BSONObj that owns the + * memory. + * + * This class will call free(holderPrefixedData), so it must have been allocated in a way + * that makes that valid. + */ + static BSONObj takeOwnership(char* holderPrefixedData) { + return BSONObj(SharedBuffer::takeOwnership(holderPrefixedData)); + } + + /// members for Sorter + struct SorterDeserializeSettings {}; // unused + void serializeForSorter(BufBuilder& buf) const { + buf.appendBuf(objdata(), objsize()); + } + static BSONObj deserializeForSorter(BufReader& buf, const SorterDeserializeSettings&) { + const int size = buf.peek<int>(); + const void* ptr = buf.skip(size); + return BSONObj(static_cast<const char*>(ptr)); + } + int memUsageForSorter() const { + // TODO consider ownedness? + return sizeof(BSONObj) + objsize(); + } + +private: + void _assertInvalid() const; - inline BSONObjIterator BSONObj::begin() const { return BSONObjIterator(*this); } - inline BSONObjIterator BSONObj::end() const { return BSONObjIterator::endOf(*this); } + void init(const char* data) { + _objdata = data; + if (!isValid()) + _assertInvalid(); + } /** - * Similar to BOOST_FOREACH + * Validate if the element is okay to be stored in a collection, maybe as the root element * - * DEPRECATED: Use range-based for loops now. + * If 'root' is true then checks against _id are made. + * If 'deep' is false then do not traverse through children */ -#define BSONForEach(elemName, obj) for (BSONElement elemName : (obj)) + Status _okForStorage(bool root, bool deep) const; + + const char* _objdata; + SharedBuffer _ownedBuffer; +}; + +std::ostream& operator<<(std::ostream& s, const BSONObj& o); +std::ostream& operator<<(std::ostream& s, const BSONElement& e); + +StringBuilder& operator<<(StringBuilder& s, const BSONObj& o); +StringBuilder& operator<<(StringBuilder& s, const BSONElement& e); + +inline void swap(BSONObj& l, BSONObj& r) { + l.swap(r); +} + +struct BSONArray : BSONObj { + // Don't add anything other than forwarding constructors!!! + BSONArray() : BSONObj() {} + explicit BSONArray(const BSONObj& obj) : BSONObj(obj) {} +}; + +/** iterator for a BSONObj - template <> - struct DataType::Handler<BSONObj> { - static Status load(BSONObj* bson, const char *ptr, size_t length, size_t *advanced, - std::ptrdiff_t debug_offset); + Note each BSONObj ends with an EOO element: so you will get more() on an empty + object, although next().eoo() will be true. - static Status store(const BSONObj& bson, char *ptr, size_t length, - size_t *advanced, std::ptrdiff_t debug_offset); + The BSONObj must stay in scope for the duration of the iterator's execution. - static BSONObj defaultConstruct() { - return BSONObj(); + todo: Finish making this an STL-compatible iterator. + Need iterator_catagory et al (maybe inherit from std::iterator). + Need operator-> + operator* should return a const reference not a value. +*/ +class BSONObjIterator { +public: + /** Create an iterator for a BSON object. + */ + explicit BSONObjIterator(const BSONObj& jso) { + int sz = jso.objsize(); + if (MONGO_unlikely(sz == 0)) { + _pos = _theend = 0; + return; + } + _pos = jso.objdata() + 4; + _theend = jso.objdata() + sz - 1; + } + + BSONObjIterator(const char* start, const char* end) { + _pos = start + 4; + _theend = end - 1; + } + + static BSONObjIterator endOf(const BSONObj& obj) { + BSONObjIterator end(obj); + end._pos = end._theend; + return end; + } + + /** @return true if more elements exist to be enumerated. */ + bool more() { + return _pos < _theend; + } + + /** @return true if more elements exist to be enumerated INCLUDING the EOO element which is always at the end. */ + bool moreWithEOO() { + return _pos <= _theend; + } + + /** @return the next element in the object. For the final element, element.eoo() will be true. */ + BSONElement next(bool checkEnd) { + verify(_pos <= _theend); + + int maxLen = -1; + if (checkEnd) { + maxLen = _theend + 1 - _pos; + verify(maxLen > 0); } - }; + + BSONElement e(_pos, maxLen); + int esize = e.size(maxLen); + massert(16446, "BSONElement has bad size", esize > 0); + _pos += esize; + + return e; + } + + BSONElement next() { + verify(_pos <= _theend); + BSONElement e(_pos); + _pos += e.size(); + return e; + } + + /** pre-increment */ + BSONObjIterator& operator++() { + next(); + return *this; + } + + /** post-increment */ + BSONObjIterator operator++(int) { + BSONObjIterator oldPos = *this; + next(); + return oldPos; + } + + BSONElement operator*() { + verify(_pos <= _theend); + return BSONElement(_pos); + } + + bool operator==(const BSONObjIterator& other) { + dassert(_theend == other._theend); + return _pos == other._pos; + } + + bool operator!=(const BSONObjIterator& other) { + return !(*this == other); + } + +private: + const char* _pos; + const char* _theend; +}; + +/** Base class implementing ordered iteration through BSONElements. */ +class BSONIteratorSorted { + MONGO_DISALLOW_COPYING(BSONIteratorSorted); + +public: + ~BSONIteratorSorted() { + verify(_fields); + } + + bool more() { + return _cur < _nfields; + } + + BSONElement next() { + verify(_fields); + if (_cur < _nfields) + return BSONElement(_fields[_cur++]); + return BSONElement(); + } + +protected: + class ElementFieldCmp; + BSONIteratorSorted(const BSONObj& o, const ElementFieldCmp& cmp); + +private: + const int _nfields; + const std::unique_ptr<const char* []> _fields; + int _cur; +}; + +/** Provides iteration of a BSONObj's BSONElements in lexical field order. */ +class BSONObjIteratorSorted : public BSONIteratorSorted { +public: + BSONObjIteratorSorted(const BSONObj& object); +}; + +/** + * Provides iteration of a BSONArray's BSONElements in numeric field order. + * The elements of a bson array should always be numerically ordered by field name, but this + * implementation re-sorts them anyway. + */ +class BSONArrayIteratorSorted : public BSONIteratorSorted { +public: + BSONArrayIteratorSorted(const BSONArray& array); +}; + +inline BSONObjIterator BSONObj::begin() const { + return BSONObjIterator(*this); +} +inline BSONObjIterator BSONObj::end() const { + return BSONObjIterator::endOf(*this); +} + +/** + * Similar to BOOST_FOREACH + * + * DEPRECATED: Use range-based for loops now. + */ +#define BSONForEach(elemName, obj) for (BSONElement elemName : (obj)) + +template <> +struct DataType::Handler<BSONObj> { + static Status load(BSONObj* bson, + const char* ptr, + size_t length, + size_t* advanced, + std::ptrdiff_t debug_offset); + + static Status store(const BSONObj& bson, + char* ptr, + size_t length, + size_t* advanced, + std::ptrdiff_t debug_offset); + + static BSONObj defaultConstruct() { + return BSONObj(); + } +}; } diff --git a/src/mongo/bson/bsonobjbuilder.cpp b/src/mongo/bson/bsonobjbuilder.cpp index a9878b28adc..7a5b04a3fa5 100644 --- a/src/mongo/bson/bsonobjbuilder.cpp +++ b/src/mongo/bson/bsonobjbuilder.cpp @@ -38,243 +38,265 @@ namespace mongo { - using std::string; +using std::string; - void BSONObjBuilder::appendMinForType( StringData fieldName , int t ) { - switch ( t ) { - +void BSONObjBuilder::appendMinForType(StringData fieldName, int t) { + switch (t) { // Shared canonical types case NumberInt: case NumberDouble: case NumberLong: - append( fieldName , - std::numeric_limits<double>::max() ); return; + append(fieldName, -std::numeric_limits<double>::max()); + return; case Symbol: case String: - append( fieldName , "" ); return; - case Date: + append(fieldName, ""); + return; + case Date: // min varies with V0 and V1 indexes, so we go one type lower. appendBool(fieldName, true); - //appendDate( fieldName , numeric_limits<long long>::min() ); + // appendDate( fieldName , numeric_limits<long long>::min() ); return; case bsonTimestamp: - appendTimestamp( fieldName , 0 ); return; - case Undefined: // shared with EOO - appendUndefined( fieldName ); return; - + appendTimestamp(fieldName, 0); + return; + case Undefined: // shared with EOO + appendUndefined(fieldName); + return; + // Separate canonical types case MinKey: - appendMinKey( fieldName ); return; + appendMinKey(fieldName); + return; case MaxKey: - appendMaxKey( fieldName ); return; + appendMaxKey(fieldName); + return; case jstOID: { OID o; - appendOID( fieldName , &o); + appendOID(fieldName, &o); return; } case Bool: - appendBool( fieldName , false); return; + appendBool(fieldName, false); + return; case jstNULL: - appendNull( fieldName ); return; + appendNull(fieldName); + return; case Object: - append( fieldName , BSONObj() ); return; + append(fieldName, BSONObj()); + return; case Array: - appendArray( fieldName , BSONObj() ); return; + appendArray(fieldName, BSONObj()); + return; case BinData: - appendBinData( fieldName , 0 , BinDataGeneral , (const char *) 0 ); return; + appendBinData(fieldName, 0, BinDataGeneral, (const char*)0); + return; case RegEx: - appendRegex( fieldName , "" ); return; + appendRegex(fieldName, ""); + return; case DBRef: { OID o; - appendDBRef( fieldName , "" , o ); + appendDBRef(fieldName, "", o); return; } case Code: - appendCode( fieldName , "" ); return; + appendCode(fieldName, ""); + return; case CodeWScope: - appendCodeWScope( fieldName , "" , BSONObj() ); return; - }; - log() << "type not supported for appendMinElementForType: " << t; - uassert( 10061 , "type not supported for appendMinElementForType" , false ); - } + appendCodeWScope(fieldName, "", BSONObj()); + return; + }; + log() << "type not supported for appendMinElementForType: " << t; + uassert(10061, "type not supported for appendMinElementForType", false); +} - void BSONObjBuilder::appendMaxForType( StringData fieldName , int t ) { - switch ( t ) { - +void BSONObjBuilder::appendMaxForType(StringData fieldName, int t) { + switch (t) { // Shared canonical types case NumberInt: case NumberDouble: case NumberLong: - append( fieldName , std::numeric_limits<double>::max() ); return; + append(fieldName, std::numeric_limits<double>::max()); + return; case Symbol: case String: - appendMinForType( fieldName, Object ); return; + appendMinForType(fieldName, Object); + return; case Date: appendDate(fieldName, Date_t::fromMillisSinceEpoch(std::numeric_limits<long long>::max())); return; case bsonTimestamp: - append( fieldName , Timestamp::max() ); return; - case Undefined: // shared with EOO - appendUndefined( fieldName ); return; + append(fieldName, Timestamp::max()); + return; + case Undefined: // shared with EOO + appendUndefined(fieldName); + return; // Separate canonical types case MinKey: - appendMinKey( fieldName ); return; + appendMinKey(fieldName); + return; case MaxKey: - appendMaxKey( fieldName ); return; + appendMaxKey(fieldName); + return; case jstOID: { OID o = OID::max(); - appendOID( fieldName , &o); + appendOID(fieldName, &o); return; } case Bool: - appendBool( fieldName , true ); return; + appendBool(fieldName, true); + return; case jstNULL: - appendNull( fieldName ); return; + appendNull(fieldName); + return; case Object: - appendMinForType( fieldName, Array ); return; + appendMinForType(fieldName, Array); + return; case Array: - appendMinForType( fieldName, BinData ); return; + appendMinForType(fieldName, BinData); + return; case BinData: - appendMinForType( fieldName, jstOID ); return; + appendMinForType(fieldName, jstOID); + return; case RegEx: - appendMinForType( fieldName, DBRef ); return; + appendMinForType(fieldName, DBRef); + return; case DBRef: - appendMinForType( fieldName, Code ); return; + appendMinForType(fieldName, Code); + return; case Code: - appendMinForType( fieldName, CodeWScope ); return; + appendMinForType(fieldName, CodeWScope); + return; case CodeWScope: // This upper bound may change if a new bson type is added. - appendMinForType( fieldName , MaxKey ); return; - } - log() << "type not supported for appendMaxElementForType: " << t; - uassert( 14853 , "type not supported for appendMaxElementForType" , false ); + appendMinForType(fieldName, MaxKey); + return; } + log() << "type not supported for appendMaxElementForType: " << t; + uassert(14853, "type not supported for appendMaxElementForType", false); +} - bool BSONObjBuilder::appendAsNumber( StringData fieldName , const string& data ) { - if ( data.size() == 0 || data == "-" || data == ".") - return false; - - unsigned int pos=0; - if ( data[0] == '-' ) - pos++; - - bool hasDec = false; - - for ( ; pos<data.size(); pos++ ) { - if ( isdigit(data[pos]) ) - continue; +bool BSONObjBuilder::appendAsNumber(StringData fieldName, const string& data) { + if (data.size() == 0 || data == "-" || data == ".") + return false; - if ( data[pos] == '.' ) { - if ( hasDec ) - return false; - hasDec = true; - continue; - } + unsigned int pos = 0; + if (data[0] == '-') + pos++; - return false; - } + bool hasDec = false; - if ( hasDec ) { - double d = atof( data.c_str() ); - append( fieldName , d ); - return true; - } + for (; pos < data.size(); pos++) { + if (isdigit(data[pos])) + continue; - if ( data.size() < 8 ) { - append( fieldName , atoi( data.c_str() ) ); - return true; + if (data[pos] == '.') { + if (hasDec) + return false; + hasDec = true; + continue; } - try { - long long num = boost::lexical_cast<long long>( data ); - append( fieldName , num ); - return true; - } - catch(boost::bad_lexical_cast &) { - return false; - } + return false; } - BSONObjBuilder& BSONObjBuilder::appendDate(StringData fieldName, Date_t dt) { - _b.appendNum((char) Date); - _b.appendStr(fieldName); - _b.appendNum(dt.toMillisSinceEpoch()); - return *this; + if (hasDec) { + double d = atof(data.c_str()); + append(fieldName, d); + return true; } - /* add all the fields from the object specified to this object */ - BSONObjBuilder& BSONObjBuilder::appendElements(BSONObj x) { - if (!x.isEmpty()) - _b.appendBuf( - x.objdata() + 4, // skip over leading length - x.objsize() - 5); // ignore leading length and trailing \0 - return *this; + if (data.size() < 8) { + append(fieldName, atoi(data.c_str())); + return true; } - /* add all the fields from the object specified to this object if they don't exist */ - BSONObjBuilder& BSONObjBuilder::appendElementsUnique(BSONObj x) { - std::set<std::string> have; - { - BSONObjIterator i = iterator(); - while ( i.more() ) - have.insert( i.next().fieldName() ); - } - - BSONObjIterator it(x); - while ( it.more() ) { - BSONElement e = it.next(); - if ( have.count( e.fieldName() ) ) - continue; - append(e); - } - return *this; + try { + long long num = boost::lexical_cast<long long>(data); + append(fieldName, num); + return true; + } catch (boost::bad_lexical_cast&) { + return false; } +} - void BSONObjBuilder::appendKeys( const BSONObj& keyPattern , const BSONObj& values ) { - BSONObjIterator i(keyPattern); - BSONObjIterator j(values); +BSONObjBuilder& BSONObjBuilder::appendDate(StringData fieldName, Date_t dt) { + _b.appendNum((char)Date); + _b.appendStr(fieldName); + _b.appendNum(dt.toMillisSinceEpoch()); + return *this; +} - while ( i.more() && j.more() ) { - appendAs( j.next() , i.next().fieldName() ); - } +/* add all the fields from the object specified to this object */ +BSONObjBuilder& BSONObjBuilder::appendElements(BSONObj x) { + if (!x.isEmpty()) + _b.appendBuf(x.objdata() + 4, // skip over leading length + x.objsize() - 5); // ignore leading length and trailing \0 + return *this; +} - verify( ! i.more() ); - verify( ! j.more() ); +/* add all the fields from the object specified to this object if they don't exist */ +BSONObjBuilder& BSONObjBuilder::appendElementsUnique(BSONObj x) { + std::set<std::string> have; + { + BSONObjIterator i = iterator(); + while (i.more()) + have.insert(i.next().fieldName()); } - BSONObjIterator BSONObjBuilder::iterator() const { - const char * s = _b.buf() + _offset; - const char * e = _b.buf() + _b.len(); - return BSONObjIterator( s , e ); + BSONObjIterator it(x); + while (it.more()) { + BSONElement e = it.next(); + if (have.count(e.fieldName())) + continue; + append(e); } + return *this; +} - bool BSONObjBuilder::hasField( StringData name ) const { - BSONObjIterator i = iterator(); - while ( i.more() ) - if ( name == i.next().fieldName() ) - return true; - return false; +void BSONObjBuilder::appendKeys(const BSONObj& keyPattern, const BSONObj& values) { + BSONObjIterator i(keyPattern); + BSONObjIterator j(values); + + while (i.more() && j.more()) { + appendAs(j.next(), i.next().fieldName()); } - const string BSONObjBuilder::numStrs[] = { - "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", - "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", - "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", - "30", "31", "32", "33", "34", "35", "36", "37", "38", "39", - "40", "41", "42", "43", "44", "45", "46", "47", "48", "49", - "50", "51", "52", "53", "54", "55", "56", "57", "58", "59", - "60", "61", "62", "63", "64", "65", "66", "67", "68", "69", - "70", "71", "72", "73", "74", "75", "76", "77", "78", "79", - "80", "81", "82", "83", "84", "85", "86", "87", "88", "89", - "90", "91", "92", "93", "94", "95", "96", "97", "98", "99", - }; + verify(!i.more()); + verify(!j.more()); +} + +BSONObjIterator BSONObjBuilder::iterator() const { + const char* s = _b.buf() + _offset; + const char* e = _b.buf() + _b.len(); + return BSONObjIterator(s, e); +} + +bool BSONObjBuilder::hasField(StringData name) const { + BSONObjIterator i = iterator(); + while (i.more()) + if (name == i.next().fieldName()) + return true; + return false; +} + +const string BSONObjBuilder::numStrs[] = { + "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", + "15", "16", "17", "18", "19", "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", + "30", "31", "32", "33", "34", "35", "36", "37", "38", "39", "40", "41", "42", "43", "44", + "45", "46", "47", "48", "49", "50", "51", "52", "53", "54", "55", "56", "57", "58", "59", + "60", "61", "62", "63", "64", "65", "66", "67", "68", "69", "70", "71", "72", "73", "74", + "75", "76", "77", "78", "79", "80", "81", "82", "83", "84", "85", "86", "87", "88", "89", + "90", "91", "92", "93", "94", "95", "96", "97", "98", "99", +}; - // This is to ensure that BSONObjBuilder doesn't try to use numStrs before the strings have - // been constructed I've tested just making numStrs a char[][], but the overhead of - // constructing the strings each time was too high numStrsReady will be 0 until after - // numStrs is initialized because it is a static variable - bool BSONObjBuilder::numStrsReady = (numStrs[0].size() > 0); +// This is to ensure that BSONObjBuilder doesn't try to use numStrs before the strings have +// been constructed I've tested just making numStrs a char[][], but the overhead of +// constructing the strings each time was too high numStrsReady will be 0 until after +// numStrs is initialized because it is a static variable +bool BSONObjBuilder::numStrsReady = (numStrs[0].size() > 0); -} // namespace mongo +} // namespace mongo diff --git a/src/mongo/bson/bsonobjbuilder.h b/src/mongo/bson/bsonobjbuilder.h index 4ca65d75693..76ed3f60ec8 100644 --- a/src/mongo/bson/bsonobjbuilder.h +++ b/src/mongo/bson/bsonobjbuilder.h @@ -50,918 +50,959 @@ namespace mongo { #if defined(_WIN32) // warning: 'this' : used in base member initializer list -#pragma warning( disable : 4355 ) +#pragma warning(disable : 4355) #endif - /** Utility for creating a BSONObj. - See also the BSON() and BSON_ARRAY() macros. - */ - class BSONObjBuilder { - MONGO_DISALLOW_COPYING(BSONObjBuilder); - public: - /** @param initsize this is just a hint as to the final size of the object */ - BSONObjBuilder(int initsize=512) - : _b(_buf) - , _buf(sizeof(BSONObj::Holder) + initsize) - , _offset(sizeof(BSONObj::Holder)) - , _s(this) - , _tracker(0) - , _doneCalled(false) { - // Skip over space for a holder object at the beginning of the buffer, followed by - // space for the object length. The length is filled in by _done. - _b.skip(sizeof(BSONObj::Holder)); - _b.skip(sizeof(int)); - - // Reserve space for the EOO byte. This means _done() can't fail. - _b.reserveBytes(1); - } +/** Utility for creating a BSONObj. + See also the BSON() and BSON_ARRAY() macros. +*/ +class BSONObjBuilder { + MONGO_DISALLOW_COPYING(BSONObjBuilder); + +public: + /** @param initsize this is just a hint as to the final size of the object */ + BSONObjBuilder(int initsize = 512) + : _b(_buf), + _buf(sizeof(BSONObj::Holder) + initsize), + _offset(sizeof(BSONObj::Holder)), + _s(this), + _tracker(0), + _doneCalled(false) { + // Skip over space for a holder object at the beginning of the buffer, followed by + // space for the object length. The length is filled in by _done. + _b.skip(sizeof(BSONObj::Holder)); + _b.skip(sizeof(int)); + + // Reserve space for the EOO byte. This means _done() can't fail. + _b.reserveBytes(1); + } - /** @param baseBuilder construct a BSONObjBuilder using an existing BufBuilder - * This is for more efficient adding of subobjects/arrays. See docs for subobjStart for example. - */ - BSONObjBuilder(BufBuilder &baseBuilder) - : _b(baseBuilder) - , _buf(0) - , _offset(baseBuilder.len()) - , _s(this) - , _tracker(0) - , _doneCalled(false) { - // Skip over space for the object length, which is filled in by _done. We don't need a - // holder since we are a sub-builder, and some parent builder has already made the - // reservation. - _b.skip(sizeof(int)); - - // Reserve space for the EOO byte. This means _done() can't fail. - _b.reserveBytes(1); - } + /** @param baseBuilder construct a BSONObjBuilder using an existing BufBuilder + * This is for more efficient adding of subobjects/arrays. See docs for subobjStart for example. + */ + BSONObjBuilder(BufBuilder& baseBuilder) + : _b(baseBuilder), + _buf(0), + _offset(baseBuilder.len()), + _s(this), + _tracker(0), + _doneCalled(false) { + // Skip over space for the object length, which is filled in by _done. We don't need a + // holder since we are a sub-builder, and some parent builder has already made the + // reservation. + _b.skip(sizeof(int)); + + // Reserve space for the EOO byte. This means _done() can't fail. + _b.reserveBytes(1); + } - BSONObjBuilder( const BSONSizeTracker & tracker ) - : _b(_buf) - , _buf(sizeof(BSONObj::Holder) + tracker.getSize()) - , _offset(sizeof(BSONObj::Holder)) - , _s(this) - , _tracker(const_cast<BSONSizeTracker*>(&tracker)) - , _doneCalled(false) { - // See the comments in the first constructor for details. - _b.skip(sizeof(BSONObj::Holder)); - _b.skip(sizeof(int)); - - // Reserve space for the EOO byte. This means _done() can't fail. - _b.reserveBytes(1); - } + BSONObjBuilder(const BSONSizeTracker& tracker) + : _b(_buf), + _buf(sizeof(BSONObj::Holder) + tracker.getSize()), + _offset(sizeof(BSONObj::Holder)), + _s(this), + _tracker(const_cast<BSONSizeTracker*>(&tracker)), + _doneCalled(false) { + // See the comments in the first constructor for details. + _b.skip(sizeof(BSONObj::Holder)); + _b.skip(sizeof(int)); + + // Reserve space for the EOO byte. This means _done() can't fail. + _b.reserveBytes(1); + } - ~BSONObjBuilder() { - // If 'done' has not already been called, and we have a reference to an owning - // BufBuilder but do not own it ourselves, then we must call _done to write in the - // length. Otherwise, we own this memory and its lifetime ends with us, therefore - // we can elide the write. - if ( !_doneCalled && _b.buf() && _buf.getSize() == 0 ) { - _done(); - } + ~BSONObjBuilder() { + // If 'done' has not already been called, and we have a reference to an owning + // BufBuilder but do not own it ourselves, then we must call _done to write in the + // length. Otherwise, we own this memory and its lifetime ends with us, therefore + // we can elide the write. + if (!_doneCalled && _b.buf() && _buf.getSize() == 0) { + _done(); } + } - /** add all the fields from the object specified to this object */ - BSONObjBuilder& appendElements(BSONObj x); - - /** add all the fields from the object specified to this object if they don't exist already */ - BSONObjBuilder& appendElementsUnique( BSONObj x ); - - /** append element to the object we are building */ - BSONObjBuilder& append( const BSONElement& e) { - verify( !e.eoo() ); // do not append eoo, that would corrupt us. the builder auto appends when done() is called. - _b.appendBuf((void*) e.rawdata(), e.size()); - return *this; - } + /** add all the fields from the object specified to this object */ + BSONObjBuilder& appendElements(BSONObj x); - /** append an element but with a new name */ - BSONObjBuilder& appendAs(const BSONElement& e, StringData fieldName) { - verify( !e.eoo() ); // do not append eoo, that would corrupt us. the builder auto appends when done() is called. - _b.appendNum((char) e.type()); - _b.appendStr(fieldName); - _b.appendBuf((void *) e.value(), e.valuesize()); - return *this; - } + /** add all the fields from the object specified to this object if they don't exist already */ + BSONObjBuilder& appendElementsUnique(BSONObj x); - /** add a subobject as a member */ - BSONObjBuilder& append(StringData fieldName, BSONObj subObj) { - _b.appendNum((char) Object); - _b.appendStr(fieldName); - _b.appendBuf((void *) subObj.objdata(), subObj.objsize()); - return *this; - } + /** append element to the object we are building */ + BSONObjBuilder& append(const BSONElement& e) { + verify( + !e.eoo()); // do not append eoo, that would corrupt us. the builder auto appends when done() is called. + _b.appendBuf((void*)e.rawdata(), e.size()); + return *this; + } - /** add a subobject as a member */ - BSONObjBuilder& appendObject(StringData fieldName, const char * objdata , int size = 0 ) { - verify( objdata ); - if ( size == 0 ) { - size = ConstDataView(objdata).read<LittleEndian<int>>(); - } + /** append an element but with a new name */ + BSONObjBuilder& appendAs(const BSONElement& e, StringData fieldName) { + verify( + !e.eoo()); // do not append eoo, that would corrupt us. the builder auto appends when done() is called. + _b.appendNum((char)e.type()); + _b.appendStr(fieldName); + _b.appendBuf((void*)e.value(), e.valuesize()); + return *this; + } - verify( size > 4 && size < 100000000 ); + /** add a subobject as a member */ + BSONObjBuilder& append(StringData fieldName, BSONObj subObj) { + _b.appendNum((char)Object); + _b.appendStr(fieldName); + _b.appendBuf((void*)subObj.objdata(), subObj.objsize()); + return *this; + } - _b.appendNum((char) Object); - _b.appendStr(fieldName); - _b.appendBuf((void*)objdata, size ); - return *this; + /** add a subobject as a member */ + BSONObjBuilder& appendObject(StringData fieldName, const char* objdata, int size = 0) { + verify(objdata); + if (size == 0) { + size = ConstDataView(objdata).read<LittleEndian<int>>(); } - /** add header for a new subobject and return bufbuilder for writing to - * the subobject's body - * - * example: - * - * BSONObjBuilder b; - * BSONObjBuilder sub (b.subobjStart("fieldName")); - * // use sub - * sub.done() - * // use b and convert to object - */ - BufBuilder &subobjStart(StringData fieldName) { - _b.appendNum((char) Object); - _b.appendStr(fieldName); - return _b; - } + verify(size > 4 && size < 100000000); - /** add a subobject as a member with type Array. Thus arr object should have "0", "1", ... - style fields in it. - */ - BSONObjBuilder& appendArray(StringData fieldName, const BSONObj &subObj) { - _b.appendNum((char) Array); - _b.appendStr(fieldName); - _b.appendBuf((void *) subObj.objdata(), subObj.objsize()); - return *this; - } - BSONObjBuilder& append(StringData fieldName, BSONArray arr) { - return appendArray(fieldName, arr); - } + _b.appendNum((char)Object); + _b.appendStr(fieldName); + _b.appendBuf((void*)objdata, size); + return *this; + } - /** add header for a new subarray and return bufbuilder for writing to - the subarray's body */ - BufBuilder &subarrayStart(StringData fieldName) { - _b.appendNum((char) Array); - _b.appendStr(fieldName); - return _b; - } + /** add header for a new subobject and return bufbuilder for writing to + * the subobject's body + * + * example: + * + * BSONObjBuilder b; + * BSONObjBuilder sub (b.subobjStart("fieldName")); + * // use sub + * sub.done() + * // use b and convert to object + */ + BufBuilder& subobjStart(StringData fieldName) { + _b.appendNum((char)Object); + _b.appendStr(fieldName); + return _b; + } - /** Append a boolean element */ - BSONObjBuilder& appendBool(StringData fieldName, int val) { - _b.appendNum((char) Bool); - _b.appendStr(fieldName); - _b.appendNum((char) (val?1:0)); - return *this; - } + /** add a subobject as a member with type Array. Thus arr object should have "0", "1", ... + style fields in it. + */ + BSONObjBuilder& appendArray(StringData fieldName, const BSONObj& subObj) { + _b.appendNum((char)Array); + _b.appendStr(fieldName); + _b.appendBuf((void*)subObj.objdata(), subObj.objsize()); + return *this; + } + BSONObjBuilder& append(StringData fieldName, BSONArray arr) { + return appendArray(fieldName, arr); + } - /** Append a boolean element */ - BSONObjBuilder& append(StringData fieldName, bool val) { - _b.appendNum((char) Bool); - _b.appendStr(fieldName); - _b.appendNum((char) (val?1:0)); - return *this; - } + /** add header for a new subarray and return bufbuilder for writing to + the subarray's body */ + BufBuilder& subarrayStart(StringData fieldName) { + _b.appendNum((char)Array); + _b.appendStr(fieldName); + return _b; + } - /** Append a 32 bit integer element */ - BSONObjBuilder& append(StringData fieldName, int n) { - _b.appendNum((char) NumberInt); - _b.appendStr(fieldName); - _b.appendNum(n); - return *this; - } + /** Append a boolean element */ + BSONObjBuilder& appendBool(StringData fieldName, int val) { + _b.appendNum((char)Bool); + _b.appendStr(fieldName); + _b.appendNum((char)(val ? 1 : 0)); + return *this; + } - /** Append a 32 bit unsigned element - cast to a signed int. */ - BSONObjBuilder& append(StringData fieldName, unsigned n) { - return append(fieldName, (int) n); - } + /** Append a boolean element */ + BSONObjBuilder& append(StringData fieldName, bool val) { + _b.appendNum((char)Bool); + _b.appendStr(fieldName); + _b.appendNum((char)(val ? 1 : 0)); + return *this; + } - /** Append a NumberLong */ - BSONObjBuilder& append(StringData fieldName, long long n) { - _b.appendNum((char) NumberLong); - _b.appendStr(fieldName); - _b.appendNum(n); - return *this; - } + /** Append a 32 bit integer element */ + BSONObjBuilder& append(StringData fieldName, int n) { + _b.appendNum((char)NumberInt); + _b.appendStr(fieldName); + _b.appendNum(n); + return *this; + } - /** appends a number. if n < max(int)/2 then uses int, otherwise long long */ - BSONObjBuilder& appendIntOrLL( StringData fieldName , long long n ) { - // extra () to avoid max macro on windows - static const long long maxInt = (std::numeric_limits<int>::max)() / 2; - static const long long minInt = -maxInt; - if ( minInt < n && n < maxInt ) { - append( fieldName , static_cast<int>( n ) ); - } - else { - append( fieldName , n ); - } - return *this; - } + /** Append a 32 bit unsigned element - cast to a signed int. */ + BSONObjBuilder& append(StringData fieldName, unsigned n) { + return append(fieldName, (int)n); + } - /** - * appendNumber is a series of method for appending the smallest sensible type - * mostly for JS - */ - BSONObjBuilder& appendNumber( StringData fieldName , int n ) { - return append( fieldName , n ); - } + /** Append a NumberLong */ + BSONObjBuilder& append(StringData fieldName, long long n) { + _b.appendNum((char)NumberLong); + _b.appendStr(fieldName); + _b.appendNum(n); + return *this; + } - BSONObjBuilder& appendNumber( StringData fieldName , double d ) { - return append( fieldName , d ); + /** appends a number. if n < max(int)/2 then uses int, otherwise long long */ + BSONObjBuilder& appendIntOrLL(StringData fieldName, long long n) { + // extra () to avoid max macro on windows + static const long long maxInt = (std::numeric_limits<int>::max)() / 2; + static const long long minInt = -maxInt; + if (minInt < n && n < maxInt) { + append(fieldName, static_cast<int>(n)); + } else { + append(fieldName, n); } + return *this; + } - BSONObjBuilder& appendNumber( StringData fieldName , size_t n ) { - static const size_t maxInt = ( 1 << 30 ); + /** + * appendNumber is a series of method for appending the smallest sensible type + * mostly for JS + */ + BSONObjBuilder& appendNumber(StringData fieldName, int n) { + return append(fieldName, n); + } - if ( n < maxInt ) - append( fieldName, static_cast<int>( n ) ); - else - append( fieldName, static_cast<long long>( n ) ); - return *this; - } + BSONObjBuilder& appendNumber(StringData fieldName, double d) { + return append(fieldName, d); + } - BSONObjBuilder& appendNumber( StringData fieldName, long long llNumber ) { - static const long long maxInt = ( 1LL << 30 ); - static const long long minInt = -maxInt; - static const long long maxDouble = ( 1LL << 40 ); - static const long long minDouble = -maxDouble; - - if ( minInt < llNumber && llNumber < maxInt ) { - append( fieldName, static_cast<int>( llNumber ) ); - } - else if ( minDouble < llNumber && llNumber < maxDouble ) { - append( fieldName, static_cast<double>( llNumber ) ); - } - else { - append( fieldName, llNumber ); - } - - return *this; - } + BSONObjBuilder& appendNumber(StringData fieldName, size_t n) { + static const size_t maxInt = (1 << 30); - /** Append a double element */ - BSONObjBuilder& append(StringData fieldName, double n) { - _b.appendNum((char) NumberDouble); - _b.appendStr(fieldName); - _b.appendNum(n); - return *this; - } + if (n < maxInt) + append(fieldName, static_cast<int>(n)); + else + append(fieldName, static_cast<long long>(n)); + return *this; + } - /** tries to append the data as a number - * @return true if the data was able to be converted to a number - */ - bool appendAsNumber( StringData fieldName , const std::string& data ); - - /** Append a BSON Object ID (OID type). - @deprecated Generally, it is preferred to use the append append(name, oid) - method for this. - */ - BSONObjBuilder& appendOID(StringData fieldName, OID *oid = 0 , bool generateIfBlank = false ) { - _b.appendNum((char) jstOID); - _b.appendStr(fieldName); - if ( oid ) - _b.appendBuf( oid->view().view(), OID::kOIDSize ); - else { - OID tmp; - if ( generateIfBlank ) - tmp.init(); - else - tmp.clear(); - _b.appendBuf( tmp.view().view(), OID::kOIDSize ); - } - return *this; - } + BSONObjBuilder& appendNumber(StringData fieldName, long long llNumber) { + static const long long maxInt = (1LL << 30); + static const long long minInt = -maxInt; + static const long long maxDouble = (1LL << 40); + static const long long minDouble = -maxDouble; - /** - Append a BSON Object ID. - @param fieldName Field name, e.g., "_id". - @returns the builder object - */ - BSONObjBuilder& append( StringData fieldName, OID oid ) { - _b.appendNum((char) jstOID); - _b.appendStr(fieldName); - _b.appendBuf( oid.view().view(), OID::kOIDSize ); - return *this; + if (minInt < llNumber && llNumber < maxInt) { + append(fieldName, static_cast<int>(llNumber)); + } else if (minDouble < llNumber && llNumber < maxDouble) { + append(fieldName, static_cast<double>(llNumber)); + } else { + append(fieldName, llNumber); } - /** - Generate and assign an object id for the _id field. - _id should be the first element in the object for good performance. - */ - BSONObjBuilder& genOID() { - return append("_id", OID::gen()); - } + return *this; + } - /** Append a time_t date. - @param dt a C-style 32 bit date value, that is - the number of seconds since January 1, 1970, 00:00:00 GMT - */ - BSONObjBuilder& appendTimeT(StringData fieldName, time_t dt) { - _b.appendNum((char) Date); - _b.appendStr(fieldName); - _b.appendNum(static_cast<unsigned long long>(dt) * 1000); - return *this; - } - /** Append a date. - @param dt a Java-style 64 bit date value, that is - the number of milliseconds since January 1, 1970, 00:00:00 GMT - */ - BSONObjBuilder& appendDate(StringData fieldName, Date_t dt); - BSONObjBuilder& append(StringData fieldName, Date_t dt) { - return appendDate(fieldName, dt); - } + /** Append a double element */ + BSONObjBuilder& append(StringData fieldName, double n) { + _b.appendNum((char)NumberDouble); + _b.appendStr(fieldName); + _b.appendNum(n); + return *this; + } - /** Append a regular expression value - @param regex the regular expression pattern - @param regex options such as "i" or "g" - */ - BSONObjBuilder& appendRegex(StringData fieldName, StringData regex, StringData options = "") { - _b.appendNum((char) RegEx); - _b.appendStr(fieldName); - _b.appendStr(regex); - _b.appendStr(options); - return *this; - } + /** tries to append the data as a number + * @return true if the data was able to be converted to a number + */ + bool appendAsNumber(StringData fieldName, const std::string& data); - BSONObjBuilder& append(StringData fieldName, const BSONRegEx& regex) { - return appendRegex(fieldName, regex.pattern, regex.flags); + /** Append a BSON Object ID (OID type). + @deprecated Generally, it is preferred to use the append append(name, oid) + method for this. + */ + BSONObjBuilder& appendOID(StringData fieldName, OID* oid = 0, bool generateIfBlank = false) { + _b.appendNum((char)jstOID); + _b.appendStr(fieldName); + if (oid) + _b.appendBuf(oid->view().view(), OID::kOIDSize); + else { + OID tmp; + if (generateIfBlank) + tmp.init(); + else + tmp.clear(); + _b.appendBuf(tmp.view().view(), OID::kOIDSize); } + return *this; + } - BSONObjBuilder& appendCode(StringData fieldName, StringData code) { - _b.appendNum((char) Code); - _b.appendStr(fieldName); - _b.appendNum((int) code.size()+1); - _b.appendStr(code); - return *this; - } + /** + Append a BSON Object ID. + @param fieldName Field name, e.g., "_id". + @returns the builder object + */ + BSONObjBuilder& append(StringData fieldName, OID oid) { + _b.appendNum((char)jstOID); + _b.appendStr(fieldName); + _b.appendBuf(oid.view().view(), OID::kOIDSize); + return *this; + } - BSONObjBuilder& append(StringData fieldName, const BSONCode& code) { - return appendCode(fieldName, code.code); - } + /** + Generate and assign an object id for the _id field. + _id should be the first element in the object for good performance. + */ + BSONObjBuilder& genOID() { + return append("_id", OID::gen()); + } - /** Append a std::string element. - @param sz size includes terminating null character */ - BSONObjBuilder& append(StringData fieldName, const char *str, int sz) { - _b.appendNum((char) String); - _b.appendStr(fieldName); - _b.appendNum((int)sz); - _b.appendBuf(str, sz); - return *this; - } - /** Append a std::string element */ - BSONObjBuilder& append(StringData fieldName, const char *str) { - return append(fieldName, str, (int) strlen(str)+1); - } - /** Append a std::string element */ - BSONObjBuilder& append(StringData fieldName, const std::string& str) { - return append(fieldName, str.c_str(), (int) str.size()+1); - } - /** Append a std::string element */ - BSONObjBuilder& append(StringData fieldName, StringData str) { - _b.appendNum((char) String); - _b.appendStr(fieldName); - _b.appendNum((int)str.size()+1); - _b.appendStr(str, true); - return *this; - } + /** Append a time_t date. + @param dt a C-style 32 bit date value, that is + the number of seconds since January 1, 1970, 00:00:00 GMT + */ + BSONObjBuilder& appendTimeT(StringData fieldName, time_t dt) { + _b.appendNum((char)Date); + _b.appendStr(fieldName); + _b.appendNum(static_cast<unsigned long long>(dt) * 1000); + return *this; + } + /** Append a date. + @param dt a Java-style 64 bit date value, that is + the number of milliseconds since January 1, 1970, 00:00:00 GMT + */ + BSONObjBuilder& appendDate(StringData fieldName, Date_t dt); + BSONObjBuilder& append(StringData fieldName, Date_t dt) { + return appendDate(fieldName, dt); + } - BSONObjBuilder& appendSymbol(StringData fieldName, StringData symbol) { - _b.appendNum((char) Symbol); - _b.appendStr(fieldName); - _b.appendNum((int) symbol.size()+1); - _b.appendStr(symbol); - return *this; - } + /** Append a regular expression value + @param regex the regular expression pattern + @param regex options such as "i" or "g" + */ + BSONObjBuilder& appendRegex(StringData fieldName, StringData regex, StringData options = "") { + _b.appendNum((char)RegEx); + _b.appendStr(fieldName); + _b.appendStr(regex); + _b.appendStr(options); + return *this; + } - BSONObjBuilder& append(StringData fieldName, const BSONSymbol& symbol) { - return appendSymbol(fieldName, symbol.symbol); - } + BSONObjBuilder& append(StringData fieldName, const BSONRegEx& regex) { + return appendRegex(fieldName, regex.pattern, regex.flags); + } - /** Implements builder interface but no-op in ObjBuilder */ - void appendNull() { - msgasserted(16234, "Invalid call to appendNull in BSONObj Builder."); - } + BSONObjBuilder& appendCode(StringData fieldName, StringData code) { + _b.appendNum((char)Code); + _b.appendStr(fieldName); + _b.appendNum((int)code.size() + 1); + _b.appendStr(code); + return *this; + } - /** Append a Null element to the object */ - BSONObjBuilder& appendNull( StringData fieldName ) { - _b.appendNum( (char) jstNULL ); - _b.appendStr( fieldName ); - return *this; - } + BSONObjBuilder& append(StringData fieldName, const BSONCode& code) { + return appendCode(fieldName, code.code); + } - // Append an element that is less than all other keys. - BSONObjBuilder& appendMinKey( StringData fieldName ) { - _b.appendNum( (char) MinKey ); - _b.appendStr( fieldName ); - return *this; - } - // Append an element that is greater than all other keys. - BSONObjBuilder& appendMaxKey( StringData fieldName ) { - _b.appendNum( (char) MaxKey ); - _b.appendStr( fieldName ); - return *this; - } + /** Append a std::string element. + @param sz size includes terminating null character */ + BSONObjBuilder& append(StringData fieldName, const char* str, int sz) { + _b.appendNum((char)String); + _b.appendStr(fieldName); + _b.appendNum((int)sz); + _b.appendBuf(str, sz); + return *this; + } + /** Append a std::string element */ + BSONObjBuilder& append(StringData fieldName, const char* str) { + return append(fieldName, str, (int)strlen(str) + 1); + } + /** Append a std::string element */ + BSONObjBuilder& append(StringData fieldName, const std::string& str) { + return append(fieldName, str.c_str(), (int)str.size() + 1); + } + /** Append a std::string element */ + BSONObjBuilder& append(StringData fieldName, StringData str) { + _b.appendNum((char)String); + _b.appendStr(fieldName); + _b.appendNum((int)str.size() + 1); + _b.appendStr(str, true); + return *this; + } - // Append a Timestamp field -- will be updated to next server Timestamp - BSONObjBuilder& appendTimestamp( StringData fieldName ); - - BSONObjBuilder& appendTimestamp( StringData fieldName , unsigned long long val ); - - /** - * To store a Timestamp in BSON, use this function. - * This captures both the secs and inc fields. - */ - BSONObjBuilder& append(StringData fieldName, Timestamp timestamp); - - /* - Append an element of the deprecated DBRef type. - @deprecated - */ - BSONObjBuilder& appendDBRef( StringData fieldName, StringData ns, const OID &oid ) { - _b.appendNum( (char) DBRef ); - _b.appendStr( fieldName ); - _b.appendNum( (int) ns.size() + 1 ); - _b.appendStr( ns ); - _b.appendBuf( oid.view().view(), OID::kOIDSize ); - return *this; - } + BSONObjBuilder& appendSymbol(StringData fieldName, StringData symbol) { + _b.appendNum((char)Symbol); + _b.appendStr(fieldName); + _b.appendNum((int)symbol.size() + 1); + _b.appendStr(symbol); + return *this; + } - BSONObjBuilder& append(StringData fieldName, const BSONDBRef& dbref) { - return appendDBRef(fieldName, dbref.ns, dbref.oid); - } + BSONObjBuilder& append(StringData fieldName, const BSONSymbol& symbol) { + return appendSymbol(fieldName, symbol.symbol); + } - /** Append a binary data element - @param fieldName name of the field - @param len length of the binary data in bytes - @param subtype subtype information for the data. @see enum BinDataType in bsontypes.h. - Use BinDataGeneral if you don't care about the type. - @param data the byte array - */ - BSONObjBuilder& appendBinData( StringData fieldName, int len, BinDataType type, const void *data ) { - _b.appendNum( (char) BinData ); - _b.appendStr( fieldName ); - _b.appendNum( len ); - _b.appendNum( (char) type ); - _b.appendBuf( data, len ); - return *this; - } + /** Implements builder interface but no-op in ObjBuilder */ + void appendNull() { + msgasserted(16234, "Invalid call to appendNull in BSONObj Builder."); + } - BSONObjBuilder& append(StringData fieldName, const BSONBinData& bd) { - return appendBinData(fieldName, bd.length, bd.type, bd.data); - } + /** Append a Null element to the object */ + BSONObjBuilder& appendNull(StringData fieldName) { + _b.appendNum((char)jstNULL); + _b.appendStr(fieldName); + return *this; + } - /** - Subtype 2 is deprecated. - Append a BSON bindata bytearray element. - @param data a byte array - @param len the length of data - */ - BSONObjBuilder& appendBinDataArrayDeprecated( const char * fieldName , const void * data , int len ) { - _b.appendNum( (char) BinData ); - _b.appendStr( fieldName ); - _b.appendNum( len + 4 ); - _b.appendNum( (char)0x2 ); - _b.appendNum( len ); - _b.appendBuf( data, len ); - return *this; - } + // Append an element that is less than all other keys. + BSONObjBuilder& appendMinKey(StringData fieldName) { + _b.appendNum((char)MinKey); + _b.appendStr(fieldName); + return *this; + } + // Append an element that is greater than all other keys. + BSONObjBuilder& appendMaxKey(StringData fieldName) { + _b.appendNum((char)MaxKey); + _b.appendStr(fieldName); + return *this; + } - /** Append to the BSON object a field of type CodeWScope. This is a javascript code - fragment accompanied by some scope that goes with it. - */ - BSONObjBuilder& appendCodeWScope( StringData fieldName, StringData code, const BSONObj &scope ) { - _b.appendNum( (char) CodeWScope ); - _b.appendStr( fieldName ); - _b.appendNum( ( int )( 4 + 4 + code.size() + 1 + scope.objsize() ) ); - _b.appendNum( ( int ) code.size() + 1 ); - _b.appendStr( code ); - _b.appendBuf( ( void * )scope.objdata(), scope.objsize() ); - return *this; - } + // Append a Timestamp field -- will be updated to next server Timestamp + BSONObjBuilder& appendTimestamp(StringData fieldName); - BSONObjBuilder& append(StringData fieldName, const BSONCodeWScope& cws) { - return appendCodeWScope(fieldName, cws.code, cws.scope); - } + BSONObjBuilder& appendTimestamp(StringData fieldName, unsigned long long val); - void appendUndefined( StringData fieldName ) { - _b.appendNum( (char) Undefined ); - _b.appendStr( fieldName ); - } + /** + * To store a Timestamp in BSON, use this function. + * This captures both the secs and inc fields. + */ + BSONObjBuilder& append(StringData fieldName, Timestamp timestamp); - /* helper function -- see Query::where() for primary way to do this. */ - void appendWhere( StringData code, const BSONObj &scope ) { - appendCodeWScope( "$where" , code , scope ); - } + /* + Append an element of the deprecated DBRef type. + @deprecated + */ + BSONObjBuilder& appendDBRef(StringData fieldName, StringData ns, const OID& oid) { + _b.appendNum((char)DBRef); + _b.appendStr(fieldName); + _b.appendNum((int)ns.size() + 1); + _b.appendStr(ns); + _b.appendBuf(oid.view().view(), OID::kOIDSize); + return *this; + } - /** - these are the min/max when comparing, not strict min/max elements for a given type - */ - void appendMinForType( StringData fieldName , int type ); - void appendMaxForType( StringData fieldName , int type ); - - /** Append an array of values. */ - template < class T > - BSONObjBuilder& append( StringData fieldName, const std::vector< T >& vals ); - - template < class T > - BSONObjBuilder& append( StringData fieldName, const std::list< T >& vals ); - - /** Append a set of values. */ - template < class T > - BSONObjBuilder& append( StringData fieldName, const std::set< T >& vals ); - - /** - * Append a map of values as a sub-object. - * Note: the keys of the map should be StringData-compatible (i.e. strings). - */ - template < class K, class T > - BSONObjBuilder& append( StringData fieldName, const std::map< K, T >& vals ); - - /** - * destructive - * The returned BSONObj will free the buffer when it is finished. - * @return owned BSONObj - */ - BSONObj obj() { - massert( 10335 , "builder does not own memory", owned() ); - doneFast(); - char* buf = _b.buf(); - decouple(); - return BSONObj::takeOwnership(buf); - } + BSONObjBuilder& append(StringData fieldName, const BSONDBRef& dbref) { + return appendDBRef(fieldName, dbref.ns, dbref.oid); + } - /** Fetch the object we have built. - BSONObjBuilder still frees the object when the builder goes out of - scope -- very important to keep in mind. Use obj() if you - would like the BSONObj to last longer than the builder. - */ - BSONObj done() { - return BSONObj(_done()); - } + /** Append a binary data element + @param fieldName name of the field + @param len length of the binary data in bytes + @param subtype subtype information for the data. @see enum BinDataType in bsontypes.h. + Use BinDataGeneral if you don't care about the type. + @param data the byte array + */ + BSONObjBuilder& appendBinData(StringData fieldName, + int len, + BinDataType type, + const void* data) { + _b.appendNum((char)BinData); + _b.appendStr(fieldName); + _b.appendNum(len); + _b.appendNum((char)type); + _b.appendBuf(data, len); + return *this; + } - // Like 'done' above, but does not construct a BSONObj to return to the caller. - void doneFast() { - (void)_done(); - } + BSONObjBuilder& append(StringData fieldName, const BSONBinData& bd) { + return appendBinData(fieldName, bd.length, bd.type, bd.data); + } - /** Peek at what is in the builder, but leave the builder ready for more appends. - The returned object is only valid until the next modification or destruction of the builder. - Intended use case: append a field if not already there. - */ - BSONObj asTempObj() { - BSONObj temp(_done()); - _b.setlen(_b.len()-1); //next append should overwrite the EOO - _b.reserveBytes(1); // Rereserve room for the real EOO - _doneCalled = false; - return temp; - } + /** + Subtype 2 is deprecated. + Append a BSON bindata bytearray element. + @param data a byte array + @param len the length of data + */ + BSONObjBuilder& appendBinDataArrayDeprecated(const char* fieldName, const void* data, int len) { + _b.appendNum((char)BinData); + _b.appendStr(fieldName); + _b.appendNum(len + 4); + _b.appendNum((char)0x2); + _b.appendNum(len); + _b.appendBuf(data, len); + return *this; + } - /** Make it look as if "done" has been called, so that our destructor is a no-op. Do - * this if you know that you don't care about the contents of the builder you are - * destroying. - * - * Note that it is invalid to call any method other than the destructor after invoking - * this method. - */ - void abandon() { - _doneCalled = true; - } + /** Append to the BSON object a field of type CodeWScope. This is a javascript code + fragment accompanied by some scope that goes with it. + */ + BSONObjBuilder& appendCodeWScope(StringData fieldName, StringData code, const BSONObj& scope) { + _b.appendNum((char)CodeWScope); + _b.appendStr(fieldName); + _b.appendNum((int)(4 + 4 + code.size() + 1 + scope.objsize())); + _b.appendNum((int)code.size() + 1); + _b.appendStr(code); + _b.appendBuf((void*)scope.objdata(), scope.objsize()); + return *this; + } - void decouple() { - _b.decouple(); // post done() call version. be sure jsobj frees... - } + BSONObjBuilder& append(StringData fieldName, const BSONCodeWScope& cws) { + return appendCodeWScope(fieldName, cws.code, cws.scope); + } - void appendKeys( const BSONObj& keyPattern , const BSONObj& values ); + void appendUndefined(StringData fieldName) { + _b.appendNum((char)Undefined); + _b.appendStr(fieldName); + } - static std::string numStr( int i ) { - if (i>=0 && i<100 && numStrsReady) - return numStrs[i]; - StringBuilder o; - o << i; - return o.str(); - } + /* helper function -- see Query::where() for primary way to do this. */ + void appendWhere(StringData code, const BSONObj& scope) { + appendCodeWScope("$where", code, scope); + } - /** Stream oriented way to add field names and values. */ - BSONObjBuilderValueStream &operator<<( StringData name ) { - _s.endField( name ); - return _s; - } + /** + these are the min/max when comparing, not strict min/max elements for a given type + */ + void appendMinForType(StringData fieldName, int type); + void appendMaxForType(StringData fieldName, int type); + + /** Append an array of values. */ + template <class T> + BSONObjBuilder& append(StringData fieldName, const std::vector<T>& vals); + + template <class T> + BSONObjBuilder& append(StringData fieldName, const std::list<T>& vals); + + /** Append a set of values. */ + template <class T> + BSONObjBuilder& append(StringData fieldName, const std::set<T>& vals); + + /** + * Append a map of values as a sub-object. + * Note: the keys of the map should be StringData-compatible (i.e. strings). + */ + template <class K, class T> + BSONObjBuilder& append(StringData fieldName, const std::map<K, T>& vals); + + /** + * destructive + * The returned BSONObj will free the buffer when it is finished. + * @return owned BSONObj + */ + BSONObj obj() { + massert(10335, "builder does not own memory", owned()); + doneFast(); + char* buf = _b.buf(); + decouple(); + return BSONObj::takeOwnership(buf); + } - /** Stream oriented way to add field names and values. */ - BSONObjBuilder& operator<<( GENOIDLabeler ) { return genOID(); } + /** Fetch the object we have built. + BSONObjBuilder still frees the object when the builder goes out of + scope -- very important to keep in mind. Use obj() if you + would like the BSONObj to last longer than the builder. + */ + BSONObj done() { + return BSONObj(_done()); + } - Labeler operator<<( const Labeler::Label &l ) { - massert( 10336 , "No subobject started", _s.subobjStarted() ); - return _s << l; - } + // Like 'done' above, but does not construct a BSONObj to return to the caller. + void doneFast() { + (void)_done(); + } - template<typename T> - BSONObjBuilderValueStream& operator<<( const BSONField<T>& f ) { - _s.endField( f.name() ); - return _s; - } + /** Peek at what is in the builder, but leave the builder ready for more appends. + The returned object is only valid until the next modification or destruction of the builder. + Intended use case: append a field if not already there. + */ + BSONObj asTempObj() { + BSONObj temp(_done()); + _b.setlen(_b.len() - 1); // next append should overwrite the EOO + _b.reserveBytes(1); // Rereserve room for the real EOO + _doneCalled = false; + return temp; + } - template<typename T> - BSONObjBuilder& operator<<( const BSONFieldValue<T>& v ) { - append( v.name(), v.value() ); - return *this; - } + /** Make it look as if "done" has been called, so that our destructor is a no-op. Do + * this if you know that you don't care about the contents of the builder you are + * destroying. + * + * Note that it is invalid to call any method other than the destructor after invoking + * this method. + */ + void abandon() { + _doneCalled = true; + } - BSONObjBuilder& operator<<( const BSONElement& e ){ - append( e ); - return *this; - } + void decouple() { + _b.decouple(); // post done() call version. be sure jsobj frees... + } - bool isArray() const { - return false; - } + void appendKeys(const BSONObj& keyPattern, const BSONObj& values); - /** @return true if we are using our own bufbuilder, and not an alternate that was given to us in our constructor */ - bool owned() const { return &_b == &_buf; } + static std::string numStr(int i) { + if (i >= 0 && i < 100 && numStrsReady) + return numStrs[i]; + StringBuilder o; + o << i; + return o.str(); + } - BSONObjIterator iterator() const ; + /** Stream oriented way to add field names and values. */ + BSONObjBuilderValueStream& operator<<(StringData name) { + _s.endField(name); + return _s; + } - bool hasField( StringData name ) const ; + /** Stream oriented way to add field names and values. */ + BSONObjBuilder& operator<<(GENOIDLabeler) { + return genOID(); + } - int len() const { return _b.len(); } + Labeler operator<<(const Labeler::Label& l) { + massert(10336, "No subobject started", _s.subobjStarted()); + return _s << l; + } - BufBuilder& bb() { return _b; } + template <typename T> + BSONObjBuilderValueStream& operator<<(const BSONField<T>& f) { + _s.endField(f.name()); + return _s; + } - private: - char* _done() { - if ( _doneCalled ) - return _b.buf() + _offset; + template <typename T> + BSONObjBuilder& operator<<(const BSONFieldValue<T>& v) { + append(v.name(), v.value()); + return *this; + } - _doneCalled = true; + BSONObjBuilder& operator<<(const BSONElement& e) { + append(e); + return *this; + } - // TODO remove this or find some way to prevent it from failing. Since this is intended - // for use with BSON() literal queries, it is less likely to result in oversized BSON. - _s.endField(); + bool isArray() const { + return false; + } - _b.claimReservedBytes(1); // Prevents adding EOO from failing. - _b.appendNum((char) EOO); + /** @return true if we are using our own bufbuilder, and not an alternate that was given to us in our constructor */ + bool owned() const { + return &_b == &_buf; + } - char *data = _b.buf() + _offset; - int size = _b.len() - _offset; - DataView(data).write(tagLittleEndian(size)); - if ( _tracker ) - _tracker->got( size ); - return data; - } + BSONObjIterator iterator() const; - BufBuilder &_b; - BufBuilder _buf; - int _offset; - BSONObjBuilderValueStream _s; - BSONSizeTracker * _tracker; - bool _doneCalled; - - static const std::string numStrs[100]; // cache of 0 to 99 inclusive - static bool numStrsReady; // for static init safety - }; - - class BSONArrayBuilder { - MONGO_DISALLOW_COPYING(BSONArrayBuilder); - public: - BSONArrayBuilder() : _i(0), _b() {} - BSONArrayBuilder( BufBuilder &_b ) : _i(0), _b(_b) {} - BSONArrayBuilder( int initialSize ) : _i(0), _b(initialSize) {} - - template <typename T> - BSONArrayBuilder& append(const T& x) { - _b.append(num(), x); - return *this; - } + bool hasField(StringData name) const; - BSONArrayBuilder& append(const BSONElement& e) { - _b.appendAs(e, num()); - return *this; - } + int len() const { + return _b.len(); + } - BSONArrayBuilder& operator<<(const BSONElement& e) { - return append(e); - } + BufBuilder& bb() { + return _b; + } - template <typename T> - BSONArrayBuilder& operator<<(const T& x) { - _b << num().c_str() << x; - return *this; - } +private: + char* _done() { + if (_doneCalled) + return _b.buf() + _offset; - void appendNull() { - _b.appendNull(num()); - } + _doneCalled = true; - void appendUndefined() { - _b.appendUndefined(num()); - } + // TODO remove this or find some way to prevent it from failing. Since this is intended + // for use with BSON() literal queries, it is less likely to result in oversized BSON. + _s.endField(); - /** - * destructive - ownership moves to returned BSONArray - * @return owned BSONArray - */ - BSONArray arr() { return BSONArray(_b.obj()); } - BSONObj obj() { return _b.obj(); } + _b.claimReservedBytes(1); // Prevents adding EOO from failing. + _b.appendNum((char)EOO); - BSONObj done() { return _b.done(); } + char* data = _b.buf() + _offset; + int size = _b.len() - _offset; + DataView(data).write(tagLittleEndian(size)); + if (_tracker) + _tracker->got(size); + return data; + } - void doneFast() { _b.doneFast(); } + BufBuilder& _b; + BufBuilder _buf; + int _offset; + BSONObjBuilderValueStream _s; + BSONSizeTracker* _tracker; + bool _doneCalled; - template < class T > - BSONArrayBuilder& append( const std::list< T >& vals ); + static const std::string numStrs[100]; // cache of 0 to 99 inclusive + static bool numStrsReady; // for static init safety +}; - template < class T > - BSONArrayBuilder& append( const std::set< T >& vals ); +class BSONArrayBuilder { + MONGO_DISALLOW_COPYING(BSONArrayBuilder); - // These two just use next position - BufBuilder &subobjStart() { return _b.subobjStart( num() ); } - BufBuilder &subarrayStart() { return _b.subarrayStart( num() ); } +public: + BSONArrayBuilder() : _i(0), _b() {} + BSONArrayBuilder(BufBuilder& _b) : _i(0), _b(_b) {} + BSONArrayBuilder(int initialSize) : _i(0), _b(initialSize) {} - BSONArrayBuilder& appendRegex(StringData regex, StringData options = "") { - _b.appendRegex(num(), regex, options); - return *this; - } + template <typename T> + BSONArrayBuilder& append(const T& x) { + _b.append(num(), x); + return *this; + } - BSONArrayBuilder& appendBinData(int len, BinDataType type, const void* data) { - _b.appendBinData(num(), len, type, data); - return *this; - } + BSONArrayBuilder& append(const BSONElement& e) { + _b.appendAs(e, num()); + return *this; + } - BSONArrayBuilder& appendCode(StringData code) { - _b.appendCode(num(), code); - return *this; - } + BSONArrayBuilder& operator<<(const BSONElement& e) { + return append(e); + } - BSONArrayBuilder& appendCodeWScope(StringData code, const BSONObj& scope) { - _b.appendCodeWScope(num(), code, scope); - return *this; - } + template <typename T> + BSONArrayBuilder& operator<<(const T& x) { + _b << num().c_str() << x; + return *this; + } - BSONArrayBuilder& appendTimeT(time_t dt) { - _b.appendTimeT(num(), dt); - return *this; - } + void appendNull() { + _b.appendNull(num()); + } - BSONArrayBuilder& appendDate(Date_t dt) { - _b.appendDate(num(), dt); - return *this; - } + void appendUndefined() { + _b.appendUndefined(num()); + } - BSONArrayBuilder& appendBool(bool val) { - _b.appendBool(num(), val); - return *this; - } + /** + * destructive - ownership moves to returned BSONArray + * @return owned BSONArray + */ + BSONArray arr() { + return BSONArray(_b.obj()); + } + BSONObj obj() { + return _b.obj(); + } - BSONArrayBuilder& appendTimestamp(unsigned long long ts) { - _b.appendTimestamp(num(), ts); - return *this; - } + BSONObj done() { + return _b.done(); + } - bool isArray() const { - return true; - } + void doneFast() { + _b.doneFast(); + } - int len() const { return _b.len(); } - int arrSize() const { return _i; } + template <class T> + BSONArrayBuilder& append(const std::list<T>& vals); - BufBuilder& bb() { return _b.bb(); } + template <class T> + BSONArrayBuilder& append(const std::set<T>& vals); - private: + // These two just use next position + BufBuilder& subobjStart() { + return _b.subobjStart(num()); + } + BufBuilder& subarrayStart() { + return _b.subarrayStart(num()); + } - std::string num() { return _b.numStr(_i++); } - int _i; - BSONObjBuilder _b; - }; + BSONArrayBuilder& appendRegex(StringData regex, StringData options = "") { + _b.appendRegex(num(), regex, options); + return *this; + } - template < class T > - inline BSONObjBuilder& BSONObjBuilder::append( StringData fieldName, const std::vector< T >& vals ) { - BSONObjBuilder arrBuilder; - for ( unsigned int i = 0; i < vals.size(); ++i ) - arrBuilder.append( numStr( i ), vals[ i ] ); - appendArray( fieldName, arrBuilder.done() ); + BSONArrayBuilder& appendBinData(int len, BinDataType type, const void* data) { + _b.appendBinData(num(), len, type, data); return *this; } - template < class L > - inline BSONObjBuilder& _appendIt( BSONObjBuilder& _this, StringData fieldName, const L& vals ) { - BSONObjBuilder arrBuilder; - int n = 0; - for( typename L::const_iterator i = vals.begin(); i != vals.end(); i++ ) - arrBuilder.append( BSONObjBuilder::numStr(n++), *i ); - _this.appendArray( fieldName, arrBuilder.done() ); - return _this; + BSONArrayBuilder& appendCode(StringData code) { + _b.appendCode(num(), code); + return *this; } - template < class T > - inline BSONObjBuilder& BSONObjBuilder::append( StringData fieldName, const std::list< T >& vals ) { - return _appendIt< std::list< T > >( *this, fieldName, vals ); + BSONArrayBuilder& appendCodeWScope(StringData code, const BSONObj& scope) { + _b.appendCodeWScope(num(), code, scope); + return *this; } - template < class T > - inline BSONObjBuilder& BSONObjBuilder::append( StringData fieldName, const std::set< T >& vals ) { - return _appendIt< std::set< T > >( *this, fieldName, vals ); + BSONArrayBuilder& appendTimeT(time_t dt) { + _b.appendTimeT(num(), dt); + return *this; } - template < class K, class T > - inline BSONObjBuilder& BSONObjBuilder::append( StringData fieldName, const std::map< K, T >& vals ) { - BSONObjBuilder bob; - for( typename std::map<K,T>::const_iterator i = vals.begin(); i != vals.end(); ++i ){ - bob.append(i->first, i->second); - } - append(fieldName, bob.obj()); + BSONArrayBuilder& appendDate(Date_t dt) { + _b.appendDate(num(), dt); return *this; } + BSONArrayBuilder& appendBool(bool val) { + _b.appendBool(num(), val); + return *this; + } - template < class L > - inline BSONArrayBuilder& _appendArrayIt( BSONArrayBuilder& _this, const L& vals ) { - for( typename L::const_iterator i = vals.begin(); i != vals.end(); i++ ) - _this.append( *i ); - return _this; + BSONArrayBuilder& appendTimestamp(unsigned long long ts) { + _b.appendTimestamp(num(), ts); + return *this; } - template < class T > - inline BSONArrayBuilder& BSONArrayBuilder::append( const std::list< T >& vals ) { - return _appendArrayIt< std::list< T > >( *this, vals ); + bool isArray() const { + return true; } - template < class T > - inline BSONArrayBuilder& BSONArrayBuilder::append( const std::set< T >& vals ) { - return _appendArrayIt< std::set< T > >( *this, vals ); + int len() const { + return _b.len(); } - - template <typename T> - inline BSONFieldValue<BSONObj> BSONField<T>::query(const char * q, const T& t) const { - BSONObjBuilder b; - b.append( q , t ); - return BSONFieldValue<BSONObj>( _name , b.obj() ); + int arrSize() const { + return _i; } - - // $or helper: OR(BSON("x" << GT << 7), BSON("y" << LT 6)); - inline BSONObj OR(const BSONObj& a, const BSONObj& b) - { return BSON( "$or" << BSON_ARRAY(a << b) ); } - inline BSONObj OR(const BSONObj& a, const BSONObj& b, const BSONObj& c) - { return BSON( "$or" << BSON_ARRAY(a << b << c) ); } - inline BSONObj OR(const BSONObj& a, const BSONObj& b, const BSONObj& c, const BSONObj& d) - { return BSON( "$or" << BSON_ARRAY(a << b << c << d) ); } - inline BSONObj OR(const BSONObj& a, const BSONObj& b, const BSONObj& c, const BSONObj& d, const BSONObj& e) - { return BSON( "$or" << BSON_ARRAY(a << b << c << d << e) ); } - inline BSONObj OR(const BSONObj& a, const BSONObj& b, const BSONObj& c, const BSONObj& d, const BSONObj& e, const BSONObj& f) - { return BSON( "$or" << BSON_ARRAY(a << b << c << d << e << f) ); } - - inline BSONObjBuilder& BSONObjBuilderValueStream::operator<<(const DateNowLabeler& id) { - _builder->appendDate(_fieldName, jsTime()); - _fieldName = StringData(); - return *_builder; + BufBuilder& bb() { + return _b.bb(); } - inline BSONObjBuilder& BSONObjBuilderValueStream::operator<<(const NullLabeler& id) { - _builder->appendNull(_fieldName); - _fieldName = StringData(); - return *_builder; +private: + std::string num() { + return _b.numStr(_i++); } + int _i; + BSONObjBuilder _b; +}; + +template <class T> +inline BSONObjBuilder& BSONObjBuilder::append(StringData fieldName, const std::vector<T>& vals) { + BSONObjBuilder arrBuilder; + for (unsigned int i = 0; i < vals.size(); ++i) + arrBuilder.append(numStr(i), vals[i]); + appendArray(fieldName, arrBuilder.done()); + return *this; +} - inline BSONObjBuilder& BSONObjBuilderValueStream::operator<<(const UndefinedLabeler& id) { - _builder->appendUndefined(_fieldName); - _fieldName = StringData(); - return *_builder; - } +template <class L> +inline BSONObjBuilder& _appendIt(BSONObjBuilder& _this, StringData fieldName, const L& vals) { + BSONObjBuilder arrBuilder; + int n = 0; + for (typename L::const_iterator i = vals.begin(); i != vals.end(); i++) + arrBuilder.append(BSONObjBuilder::numStr(n++), *i); + _this.appendArray(fieldName, arrBuilder.done()); + return _this; +} +template <class T> +inline BSONObjBuilder& BSONObjBuilder::append(StringData fieldName, const std::list<T>& vals) { + return _appendIt<std::list<T>>(*this, fieldName, vals); +} - inline BSONObjBuilder& BSONObjBuilderValueStream::operator<<(const MinKeyLabeler& id) { - _builder->appendMinKey(_fieldName); - _fieldName = StringData(); - return *_builder; - } +template <class T> +inline BSONObjBuilder& BSONObjBuilder::append(StringData fieldName, const std::set<T>& vals) { + return _appendIt<std::set<T>>(*this, fieldName, vals); +} - inline BSONObjBuilder& BSONObjBuilderValueStream::operator<<(const MaxKeyLabeler& id) { - _builder->appendMaxKey(_fieldName); - _fieldName = StringData(); - return *_builder; +template <class K, class T> +inline BSONObjBuilder& BSONObjBuilder::append(StringData fieldName, const std::map<K, T>& vals) { + BSONObjBuilder bob; + for (typename std::map<K, T>::const_iterator i = vals.begin(); i != vals.end(); ++i) { + bob.append(i->first, i->second); } + append(fieldName, bob.obj()); + return *this; +} - template<class T> inline - BSONObjBuilder& BSONObjBuilderValueStream::operator<<( T value ) { - _builder->append(_fieldName, value); - _fieldName = StringData(); - return *_builder; - } - template<class T> - BSONObjBuilder& Labeler::operator<<( T value ) { - s_->subobj()->append( l_.l_, value ); - return *s_->_builder; - } +template <class L> +inline BSONArrayBuilder& _appendArrayIt(BSONArrayBuilder& _this, const L& vals) { + for (typename L::const_iterator i = vals.begin(); i != vals.end(); i++) + _this.append(*i); + return _this; +} - inline BSONObjBuilder& BSONObjBuilder::append(StringData fieldName, Timestamp optime) { - optime.append(_b, fieldName); - return *this; - } +template <class T> +inline BSONArrayBuilder& BSONArrayBuilder::append(const std::list<T>& vals) { + return _appendArrayIt<std::list<T>>(*this, vals); +} - inline BSONObjBuilder& BSONObjBuilder::appendTimestamp( StringData fieldName ) { - return append(fieldName, Timestamp()); - } +template <class T> +inline BSONArrayBuilder& BSONArrayBuilder::append(const std::set<T>& vals) { + return _appendArrayIt<std::set<T>>(*this, vals); +} - inline BSONObjBuilder& BSONObjBuilder::appendTimestamp( StringData fieldName, - unsigned long long val ) { - return append(fieldName, Timestamp(val)); - } +template <typename T> +inline BSONFieldValue<BSONObj> BSONField<T>::query(const char* q, const T& t) const { + BSONObjBuilder b; + b.append(q, t); + return BSONFieldValue<BSONObj>(_name, b.obj()); +} + + +// $or helper: OR(BSON("x" << GT << 7), BSON("y" << LT 6)); +inline BSONObj OR(const BSONObj& a, const BSONObj& b) { + return BSON("$or" << BSON_ARRAY(a << b)); +} +inline BSONObj OR(const BSONObj& a, const BSONObj& b, const BSONObj& c) { + return BSON("$or" << BSON_ARRAY(a << b << c)); +} +inline BSONObj OR(const BSONObj& a, const BSONObj& b, const BSONObj& c, const BSONObj& d) { + return BSON("$or" << BSON_ARRAY(a << b << c << d)); +} +inline BSONObj OR( + const BSONObj& a, const BSONObj& b, const BSONObj& c, const BSONObj& d, const BSONObj& e) { + return BSON("$or" << BSON_ARRAY(a << b << c << d << e)); +} +inline BSONObj OR(const BSONObj& a, + const BSONObj& b, + const BSONObj& c, + const BSONObj& d, + const BSONObj& e, + const BSONObj& f) { + return BSON("$or" << BSON_ARRAY(a << b << c << d << e << f)); +} + +inline BSONObjBuilder& BSONObjBuilderValueStream::operator<<(const DateNowLabeler& id) { + _builder->appendDate(_fieldName, jsTime()); + _fieldName = StringData(); + return *_builder; +} + +inline BSONObjBuilder& BSONObjBuilderValueStream::operator<<(const NullLabeler& id) { + _builder->appendNull(_fieldName); + _fieldName = StringData(); + return *_builder; +} +inline BSONObjBuilder& BSONObjBuilderValueStream::operator<<(const UndefinedLabeler& id) { + _builder->appendUndefined(_fieldName); + _fieldName = StringData(); + return *_builder; +} + + +inline BSONObjBuilder& BSONObjBuilderValueStream::operator<<(const MinKeyLabeler& id) { + _builder->appendMinKey(_fieldName); + _fieldName = StringData(); + return *_builder; +} + +inline BSONObjBuilder& BSONObjBuilderValueStream::operator<<(const MaxKeyLabeler& id) { + _builder->appendMaxKey(_fieldName); + _fieldName = StringData(); + return *_builder; +} + +template <class T> +inline BSONObjBuilder& BSONObjBuilderValueStream::operator<<(T value) { + _builder->append(_fieldName, value); + _fieldName = StringData(); + return *_builder; +} + +template <class T> +BSONObjBuilder& Labeler::operator<<(T value) { + s_->subobj()->append(l_.l_, value); + return *s_->_builder; +} + +inline BSONObjBuilder& BSONObjBuilder::append(StringData fieldName, Timestamp optime) { + optime.append(_b, fieldName); + return *this; +} + +inline BSONObjBuilder& BSONObjBuilder::appendTimestamp(StringData fieldName) { + return append(fieldName, Timestamp()); +} + +inline BSONObjBuilder& BSONObjBuilder::appendTimestamp(StringData fieldName, + unsigned long long val) { + return append(fieldName, Timestamp(val)); +} } diff --git a/src/mongo/bson/bsonobjbuilder_test.cpp b/src/mongo/bson/bsonobjbuilder_test.cpp index d18fb900537..0f87d966fd8 100644 --- a/src/mongo/bson/bsonobjbuilder_test.cpp +++ b/src/mongo/bson/bsonobjbuilder_test.cpp @@ -38,226 +38,230 @@ namespace { - using std::string; - using std::stringstream; - using mongo::BSONElement; - using mongo::BSONObj; - using mongo::BSONObjBuilder; - using mongo::BSONType; +using std::string; +using std::stringstream; +using mongo::BSONElement; +using mongo::BSONObj; +using mongo::BSONObjBuilder; +using mongo::BSONType; - const long long maxEncodableInt = (1 << 30) - 1; - const long long minEncodableInt = -maxEncodableInt; +const long long maxEncodableInt = (1 << 30) - 1; +const long long minEncodableInt = -maxEncodableInt; - const long long maxInt = (std::numeric_limits<int>::max)(); - const long long minInt = (std::numeric_limits<int>::min)(); +const long long maxInt = (std::numeric_limits<int>::max)(); +const long long minInt = (std::numeric_limits<int>::min)(); - const long long maxEncodableDouble = (1LL << 40) - 1; - const long long minEncodableDouble = -maxEncodableDouble; +const long long maxEncodableDouble = (1LL << 40) - 1; +const long long minEncodableDouble = -maxEncodableDouble; - const long long maxDouble = (1LL << std::numeric_limits<double>::digits) - 1; - const long long minDouble = -maxDouble; +const long long maxDouble = (1LL << std::numeric_limits<double>::digits) - 1; +const long long minDouble = -maxDouble; - const long long maxLongLong = (std::numeric_limits<long long>::max)(); - const long long minLongLong = (std::numeric_limits<long long>::min)(); +const long long maxLongLong = (std::numeric_limits<long long>::max)(); +const long long minLongLong = (std::numeric_limits<long long>::min)(); - template<typename T> void assertBSONTypeEquals(BSONType actual, BSONType expected, T value, int i) { - if (expected != actual) { - stringstream ss; - ss << "incorrect type in bson object for " << (i+1) << "-th test value " << value - << ". actual: " << mongo::typeName(actual) - << "; expected: " << mongo::typeName(expected); - const string msg = ss.str(); - FAIL(msg); - } +template <typename T> +void assertBSONTypeEquals(BSONType actual, BSONType expected, T value, int i) { + if (expected != actual) { + stringstream ss; + ss << "incorrect type in bson object for " << (i + 1) << "-th test value " << value + << ". actual: " << mongo::typeName(actual) + << "; expected: " << mongo::typeName(expected); + const string msg = ss.str(); + FAIL(msg); } +} - /** - * current conversion ranges in append(unsigned n) - * dbl/int max/min in comments refer to max/min encodable constants - * 0 <= n <= uint_max -----> int - */ +/** + * current conversion ranges in append(unsigned n) + * dbl/int max/min in comments refer to max/min encodable constants + * 0 <= n <= uint_max -----> int + */ - TEST(BSONObjBuilderTest, AppendUnsignedInt) { - struct { unsigned int v; BSONType t; } data[] = { - { 0, mongo::NumberInt }, - { 100, mongo::NumberInt }, - { maxEncodableInt, mongo::NumberInt }, - { maxEncodableInt + 1, mongo::NumberInt }, - { static_cast<unsigned int>(maxInt), mongo::NumberInt }, - { static_cast<unsigned int>(maxInt) + 1U, mongo::NumberInt }, - { (std::numeric_limits<unsigned int>::max)(), mongo::NumberInt }, - { 0, mongo::Undefined } - }; - for (int i=0; data[i].t != mongo::Undefined; i++) { - unsigned int v = data[i].v; - BSONObjBuilder b; - b.append("a", v); - BSONObj o = b.obj(); - ASSERT_EQUALS(o.nFields(), 1); - BSONElement e = o.getField("a"); - unsigned int n = e.numberLong(); - ASSERT_EQUALS(n, v); - assertBSONTypeEquals(e.type(), data[i].t, v, i); - } +TEST(BSONObjBuilderTest, AppendUnsignedInt) { + struct { + unsigned int v; + BSONType t; + } data[] = {{0, mongo::NumberInt}, + {100, mongo::NumberInt}, + {maxEncodableInt, mongo::NumberInt}, + {maxEncodableInt + 1, mongo::NumberInt}, + {static_cast<unsigned int>(maxInt), mongo::NumberInt}, + {static_cast<unsigned int>(maxInt) + 1U, mongo::NumberInt}, + {(std::numeric_limits<unsigned int>::max)(), mongo::NumberInt}, + {0, mongo::Undefined}}; + for (int i = 0; data[i].t != mongo::Undefined; i++) { + unsigned int v = data[i].v; + BSONObjBuilder b; + b.append("a", v); + BSONObj o = b.obj(); + ASSERT_EQUALS(o.nFields(), 1); + BSONElement e = o.getField("a"); + unsigned int n = e.numberLong(); + ASSERT_EQUALS(n, v); + assertBSONTypeEquals(e.type(), data[i].t, v, i); } +} - /** - * current conversion ranges in appendIntOrLL(long long n) - * dbl/int max/min in comments refer to max/min encodable constants - * n < dbl_min -----> long long - * dbl_min <= n < int_min -----> double - * int_min <= n <= int_max -----> int - * int_max < n <= dbl_max -----> double - * dbl_max < n -----> long long - */ - - TEST(BSONObjBuilderTest, AppendIntOrLL) { - struct { long long v; BSONType t; } data[] = { - { 0, mongo::NumberInt }, - { -100, mongo::NumberInt }, - { 100, mongo::NumberInt }, - { -(maxInt / 2 - 1), mongo::NumberInt }, - { maxInt / 2 - 1, mongo::NumberInt }, - { -(maxInt / 2), mongo::NumberLong }, - { maxInt / 2, mongo::NumberLong }, - { minEncodableInt, mongo::NumberLong }, - { maxEncodableInt, mongo::NumberLong }, - { minEncodableInt - 1, mongo::NumberLong }, - { maxEncodableInt + 1, mongo::NumberLong }, - { minInt, mongo::NumberLong }, - { maxInt, mongo::NumberLong }, - { minInt - 1, mongo::NumberLong }, - { maxInt + 1, mongo::NumberLong }, - { minLongLong, mongo::NumberLong }, - { maxLongLong, mongo::NumberLong }, - { 0, mongo::Undefined } - }; - for (int i=0; data[i].t != mongo::Undefined; i++) { - long long v = data[i].v; - BSONObjBuilder b; - b.appendIntOrLL("a", v); - BSONObj o = b.obj(); - ASSERT_EQUALS(o.nFields(), 1); - BSONElement e = o.getField("a"); - long long n = e.numberLong(); - ASSERT_EQUALS(n, v); - assertBSONTypeEquals(e.type(), data[i].t, v, i); - } - } +/** + * current conversion ranges in appendIntOrLL(long long n) + * dbl/int max/min in comments refer to max/min encodable constants + * n < dbl_min -----> long long + * dbl_min <= n < int_min -----> double + * int_min <= n <= int_max -----> int + * int_max < n <= dbl_max -----> double + * dbl_max < n -----> long long + */ - /** - * current conversion ranges in appendNumber(size_t n) - * dbl/int max/min in comments refer to max/min encodable constants - * 0 <= n <= int_max -----> int - * int_max < n -----> long long - */ - - TEST(BSONObjBuilderTest, AppendNumberSizeT) { - struct { size_t v; BSONType t; } data[] = { - { 0, mongo::NumberInt }, - { 100, mongo::NumberInt }, - { maxEncodableInt, mongo::NumberInt }, - { maxEncodableInt + 1, mongo::NumberLong }, - { size_t(maxInt), mongo::NumberLong }, - { size_t(maxInt) + 1U, mongo::NumberLong }, - { (std::numeric_limits<size_t>::max)(), mongo::NumberLong }, - { 0, mongo::Undefined } - }; - for (int i=0; data[i].t != mongo::Undefined; i++) { - size_t v = data[i].v; - BSONObjBuilder b; - b.appendNumber("a", v); - BSONObj o = b.obj(); - ASSERT_EQUALS(o.nFields(), 1); - BSONElement e = o.getField("a"); - size_t n = e.numberLong(); - ASSERT_EQUALS(n, v); - assertBSONTypeEquals(e.type(), data[i].t, v, i); - } +TEST(BSONObjBuilderTest, AppendIntOrLL) { + struct { + long long v; + BSONType t; + } data[] = {{0, mongo::NumberInt}, + {-100, mongo::NumberInt}, + {100, mongo::NumberInt}, + {-(maxInt / 2 - 1), mongo::NumberInt}, + {maxInt / 2 - 1, mongo::NumberInt}, + {-(maxInt / 2), mongo::NumberLong}, + {maxInt / 2, mongo::NumberLong}, + {minEncodableInt, mongo::NumberLong}, + {maxEncodableInt, mongo::NumberLong}, + {minEncodableInt - 1, mongo::NumberLong}, + {maxEncodableInt + 1, mongo::NumberLong}, + {minInt, mongo::NumberLong}, + {maxInt, mongo::NumberLong}, + {minInt - 1, mongo::NumberLong}, + {maxInt + 1, mongo::NumberLong}, + {minLongLong, mongo::NumberLong}, + {maxLongLong, mongo::NumberLong}, + {0, mongo::Undefined}}; + for (int i = 0; data[i].t != mongo::Undefined; i++) { + long long v = data[i].v; + BSONObjBuilder b; + b.appendIntOrLL("a", v); + BSONObj o = b.obj(); + ASSERT_EQUALS(o.nFields(), 1); + BSONElement e = o.getField("a"); + long long n = e.numberLong(); + ASSERT_EQUALS(n, v); + assertBSONTypeEquals(e.type(), data[i].t, v, i); } +} - /** - * current conversion ranges in appendNumber(long long n) - * dbl/int max/min in comments refer to max/min encodable constants - * n < dbl_min -----> long long - * dbl_min <= n < int_min -----> double - * int_min <= n <= int_max -----> int - * int_max < n <= dbl_max -----> double - * dbl_max < n -----> long long - */ - - TEST(BSONObjBuilderTest, AppendNumberLongLong) { - struct { long long v; BSONType t; } data[] = { - { 0, mongo::NumberInt }, - { -100, mongo::NumberInt }, - { 100, mongo::NumberInt }, - { minEncodableInt, mongo::NumberInt }, - { maxEncodableInt, mongo::NumberInt }, - { minEncodableInt - 1, mongo::NumberDouble }, - { maxEncodableInt + 1, mongo::NumberDouble }, - { minInt, mongo::NumberDouble }, - { maxInt, mongo::NumberDouble }, - { minInt - 1, mongo::NumberDouble }, - { maxInt + 1, mongo::NumberDouble }, - { minEncodableDouble, mongo::NumberDouble }, - { maxEncodableDouble, mongo::NumberDouble }, - { minEncodableDouble - 1, mongo::NumberLong }, - { maxEncodableDouble + 1, mongo::NumberLong }, - { minDouble, mongo::NumberLong }, - { maxDouble, mongo::NumberLong }, - { minDouble - 1, mongo::NumberLong }, - { maxDouble + 1, mongo::NumberLong }, - { minLongLong, mongo::NumberLong }, - { maxLongLong, mongo::NumberLong }, - { 0, mongo::Undefined } - }; - for (int i=0; data[i].t != mongo::Undefined; i++) { - long long v = data[i].v; - BSONObjBuilder b; - b.appendNumber("a", v); - BSONObj o = b.obj(); - ASSERT_EQUALS(o.nFields(), 1); - BSONElement e = o.getField("a"); - if (data[i].t != mongo::NumberDouble) { - long long n = e.numberLong(); - ASSERT_EQUALS(n, v); - } - else { - double n = e.numberDouble(); - ASSERT_APPROX_EQUAL(n, static_cast<double>(v), 0.001); - } - assertBSONTypeEquals(e.type(), data[i].t, v, i); - } - } +/** + * current conversion ranges in appendNumber(size_t n) + * dbl/int max/min in comments refer to max/min encodable constants + * 0 <= n <= int_max -----> int + * int_max < n -----> long long + */ - TEST(BSONObjBuilderTest, StreamLongLongMin) { - BSONObj o = BSON("a" << std::numeric_limits<long long>::min()); +TEST(BSONObjBuilderTest, AppendNumberSizeT) { + struct { + size_t v; + BSONType t; + } data[] = {{0, mongo::NumberInt}, + {100, mongo::NumberInt}, + {maxEncodableInt, mongo::NumberInt}, + {maxEncodableInt + 1, mongo::NumberLong}, + {size_t(maxInt), mongo::NumberLong}, + {size_t(maxInt) + 1U, mongo::NumberLong}, + {(std::numeric_limits<size_t>::max)(), mongo::NumberLong}, + {0, mongo::Undefined}}; + for (int i = 0; data[i].t != mongo::Undefined; i++) { + size_t v = data[i].v; + BSONObjBuilder b; + b.appendNumber("a", v); + BSONObj o = b.obj(); ASSERT_EQUALS(o.nFields(), 1); BSONElement e = o.getField("a"); - long long n = e.numberLong(); - ASSERT_EQUALS(n, std::numeric_limits<long long>::min()); + size_t n = e.numberLong(); + ASSERT_EQUALS(n, v); + assertBSONTypeEquals(e.type(), data[i].t, v, i); } +} - TEST(BSONObjBuilderTest, AppendNumberLongLongMinCompareObject) { +/** + * current conversion ranges in appendNumber(long long n) + * dbl/int max/min in comments refer to max/min encodable constants + * n < dbl_min -----> long long + * dbl_min <= n < int_min -----> double + * int_min <= n <= int_max -----> int + * int_max < n <= dbl_max -----> double + * dbl_max < n -----> long long + */ + +TEST(BSONObjBuilderTest, AppendNumberLongLong) { + struct { + long long v; + BSONType t; + } data[] = {{0, mongo::NumberInt}, + {-100, mongo::NumberInt}, + {100, mongo::NumberInt}, + {minEncodableInt, mongo::NumberInt}, + {maxEncodableInt, mongo::NumberInt}, + {minEncodableInt - 1, mongo::NumberDouble}, + {maxEncodableInt + 1, mongo::NumberDouble}, + {minInt, mongo::NumberDouble}, + {maxInt, mongo::NumberDouble}, + {minInt - 1, mongo::NumberDouble}, + {maxInt + 1, mongo::NumberDouble}, + {minEncodableDouble, mongo::NumberDouble}, + {maxEncodableDouble, mongo::NumberDouble}, + {minEncodableDouble - 1, mongo::NumberLong}, + {maxEncodableDouble + 1, mongo::NumberLong}, + {minDouble, mongo::NumberLong}, + {maxDouble, mongo::NumberLong}, + {minDouble - 1, mongo::NumberLong}, + {maxDouble + 1, mongo::NumberLong}, + {minLongLong, mongo::NumberLong}, + {maxLongLong, mongo::NumberLong}, + {0, mongo::Undefined}}; + for (int i = 0; data[i].t != mongo::Undefined; i++) { + long long v = data[i].v; BSONObjBuilder b; - b.appendNumber("a", std::numeric_limits<long long>::min()); - BSONObj o1 = b.obj(); - - BSONObj o2 = BSON("a" << std::numeric_limits<long long>::min()); - - ASSERT_EQUALS(o1, o2); + b.appendNumber("a", v); + BSONObj o = b.obj(); + ASSERT_EQUALS(o.nFields(), 1); + BSONElement e = o.getField("a"); + if (data[i].t != mongo::NumberDouble) { + long long n = e.numberLong(); + ASSERT_EQUALS(n, v); + } else { + double n = e.numberDouble(); + ASSERT_APPROX_EQUAL(n, static_cast<double>(v), 0.001); + } + assertBSONTypeEquals(e.type(), data[i].t, v, i); } +} - TEST(BSONObjBuilderTest, AppendMaxTimestampConversion) { - BSONObjBuilder b; - b.appendMaxForType("a", mongo::bsonTimestamp); - BSONObj o1 = b.obj(); +TEST(BSONObjBuilderTest, StreamLongLongMin) { + BSONObj o = BSON("a" << std::numeric_limits<long long>::min()); + ASSERT_EQUALS(o.nFields(), 1); + BSONElement e = o.getField("a"); + long long n = e.numberLong(); + ASSERT_EQUALS(n, std::numeric_limits<long long>::min()); +} - BSONElement e = o1.getField("a"); - ASSERT_FALSE(e.eoo()); +TEST(BSONObjBuilderTest, AppendNumberLongLongMinCompareObject) { + BSONObjBuilder b; + b.appendNumber("a", std::numeric_limits<long long>::min()); + BSONObj o1 = b.obj(); - mongo::Timestamp timestamp = e.timestamp(); - ASSERT_FALSE(timestamp.isNull()); - } + BSONObj o2 = BSON("a" << std::numeric_limits<long long>::min()); + + ASSERT_EQUALS(o1, o2); +} + +TEST(BSONObjBuilderTest, AppendMaxTimestampConversion) { + BSONObjBuilder b; + b.appendMaxForType("a", mongo::bsonTimestamp); + BSONObj o1 = b.obj(); + + BSONElement e = o1.getField("a"); + ASSERT_FALSE(e.eoo()); + + mongo::Timestamp timestamp = e.timestamp(); + ASSERT_FALSE(timestamp.isNull()); +} -} // unnamed namespace +} // unnamed namespace diff --git a/src/mongo/bson/bsontypes.cpp b/src/mongo/bson/bsontypes.cpp index 5fd15364d1d..2d12f311bef 100644 --- a/src/mongo/bson/bsontypes.cpp +++ b/src/mongo/bson/bsontypes.cpp @@ -33,72 +33,94 @@ namespace mongo { #pragma pack(1) - struct MaxKeyData { - MaxKeyData() { - totsize=7; - maxkey=MaxKey; - name=0; - eoo=EOO; - } - int totsize; - char maxkey; - char name; - char eoo; - } maxkeydata; - BSONObj maxKey((const char *) &maxkeydata); +struct MaxKeyData { + MaxKeyData() { + totsize = 7; + maxkey = MaxKey; + name = 0; + eoo = EOO; + } + int totsize; + char maxkey; + char name; + char eoo; +} maxkeydata; +BSONObj maxKey((const char*)&maxkeydata); - struct MinKeyData { - MinKeyData() { - totsize=7; - minkey=MinKey; - name=0; - eoo=EOO; +struct MinKeyData { + MinKeyData() { + totsize = 7; + minkey = MinKey; + name = 0; + eoo = EOO; + } + int totsize; + char minkey; + char name; + char eoo; +} minkeydata; +BSONObj minKey((const char*)&minkeydata); + +/* + struct JSObj0 { + JSObj0() { + totsize = 5; + eoo = EOO; } int totsize; - char minkey; - char name; char eoo; - } minkeydata; - BSONObj minKey((const char *) &minkeydata); - - /* - struct JSObj0 { - JSObj0() { - totsize = 5; - eoo = EOO; - } - int totsize; - char eoo; - } js0; - */ + } js0; +*/ #pragma pack() - /* take a BSONType and return the name of that type as a char* */ - const char* typeName (BSONType type) { - switch (type) { - case MinKey: return "MinKey"; - case EOO: return "EOO"; - case NumberDouble: return "NumberDouble"; - case String: return "String"; - case Object: return "Object"; - case Array: return "Array"; - case BinData: return "BinaryData"; - case Undefined: return "Undefined"; - case jstOID: return "OID"; - case Bool: return "Bool"; - case Date: return "Date"; - case jstNULL: return "NULL"; - case RegEx: return "RegEx"; - case DBRef: return "DBRef"; - case Code: return "Code"; - case Symbol: return "Symbol"; - case CodeWScope: return "CodeWScope"; - case NumberInt: return "NumberInt32"; - case bsonTimestamp: return "Timestamp"; - case NumberLong: return "NumberLong64"; - // JSTypeMax doesn't make sense to turn into a string; overlaps with highest-valued type - case MaxKey: return "MaxKey"; - default: return "Invalid"; - } +/* take a BSONType and return the name of that type as a char* */ +const char* typeName(BSONType type) { + switch (type) { + case MinKey: + return "MinKey"; + case EOO: + return "EOO"; + case NumberDouble: + return "NumberDouble"; + case String: + return "String"; + case Object: + return "Object"; + case Array: + return "Array"; + case BinData: + return "BinaryData"; + case Undefined: + return "Undefined"; + case jstOID: + return "OID"; + case Bool: + return "Bool"; + case Date: + return "Date"; + case jstNULL: + return "NULL"; + case RegEx: + return "RegEx"; + case DBRef: + return "DBRef"; + case Code: + return "Code"; + case Symbol: + return "Symbol"; + case CodeWScope: + return "CodeWScope"; + case NumberInt: + return "NumberInt32"; + case bsonTimestamp: + return "Timestamp"; + case NumberLong: + return "NumberLong64"; + // JSTypeMax doesn't make sense to turn into a string; overlaps with highest-valued type + case MaxKey: + return "MaxKey"; + default: + return "Invalid"; } -} // namespace mongo +} +} // namespace mongo diff --git a/src/mongo/bson/bsontypes.h b/src/mongo/bson/bsontypes.h index 3b7e98fee42..4877dcbbbe5 100644 --- a/src/mongo/bson/bsontypes.h +++ b/src/mongo/bson/bsontypes.h @@ -33,96 +33,96 @@ namespace mongo { - class BSONArrayBuilder; - class BSONElement; - class BSONObj; - class BSONObjBuilder; - class BSONObjBuilderValueStream; - class BSONObjIterator; - class Ordering; - struct BSONArray; // empty subclass of BSONObj useful for overloading - struct BSONElementCmpWithoutField; +class BSONArrayBuilder; +class BSONElement; +class BSONObj; +class BSONObjBuilder; +class BSONObjBuilderValueStream; +class BSONObjIterator; +class Ordering; +struct BSONArray; // empty subclass of BSONObj useful for overloading +struct BSONElementCmpWithoutField; - extern BSONObj maxKey; - extern BSONObj minKey; +extern BSONObj maxKey; +extern BSONObj minKey; - /** - the complete list of valid BSON types - see also bsonspec.org - */ - enum BSONType { - /** smaller than all other types */ - MinKey=-1, - /** end of object */ - EOO=0, - /** double precision floating point value */ - NumberDouble=1, - /** character string, stored in utf8 */ - String=2, - /** an embedded object */ - Object=3, - /** an embedded array */ - Array=4, - /** binary data */ - BinData=5, - /** Undefined type */ - Undefined=6, - /** ObjectId */ - jstOID=7, - /** boolean type */ - Bool=8, - /** date type */ - Date=9, - /** null type */ - jstNULL=10, - /** regular expression, a pattern with options */ - RegEx=11, - /** deprecated / will be redesigned */ - DBRef=12, - /** deprecated / use CodeWScope */ - Code=13, - /** a programming language (e.g., Python) symbol */ - Symbol=14, - /** javascript code that can execute on the database server, with SavedContext */ - CodeWScope=15, - /** 32 bit signed integer */ - NumberInt = 16, - /** Two 32 bit signed integers */ - bsonTimestamp = 17, - /** 64 bit integer */ - NumberLong = 18, - /** max type that is not MaxKey */ - JSTypeMax=18, - /** larger than all other types */ - MaxKey=127 - }; +/** + the complete list of valid BSON types + see also bsonspec.org +*/ +enum BSONType { + /** smaller than all other types */ + MinKey = -1, + /** end of object */ + EOO = 0, + /** double precision floating point value */ + NumberDouble = 1, + /** character string, stored in utf8 */ + String = 2, + /** an embedded object */ + Object = 3, + /** an embedded array */ + Array = 4, + /** binary data */ + BinData = 5, + /** Undefined type */ + Undefined = 6, + /** ObjectId */ + jstOID = 7, + /** boolean type */ + Bool = 8, + /** date type */ + Date = 9, + /** null type */ + jstNULL = 10, + /** regular expression, a pattern with options */ + RegEx = 11, + /** deprecated / will be redesigned */ + DBRef = 12, + /** deprecated / use CodeWScope */ + Code = 13, + /** a programming language (e.g., Python) symbol */ + Symbol = 14, + /** javascript code that can execute on the database server, with SavedContext */ + CodeWScope = 15, + /** 32 bit signed integer */ + NumberInt = 16, + /** Two 32 bit signed integers */ + bsonTimestamp = 17, + /** 64 bit integer */ + NumberLong = 18, + /** max type that is not MaxKey */ + JSTypeMax = 18, + /** larger than all other types */ + MaxKey = 127 +}; - /** - * returns the name of the argument's type - */ - const char* typeName (BSONType type); +/** + * returns the name of the argument's type + */ +const char* typeName(BSONType type); - /* subtypes of BinData. - bdtCustom and above are ones that the JS compiler understands, but are - opaque to the database. - */ - enum BinDataType { - BinDataGeneral=0, - Function=1, - ByteArrayDeprecated=2, /* use BinGeneral instead */ - bdtUUID = 3, /* deprecated */ - newUUID=4, /* language-independent UUID format across all drivers */ - MD5Type=5, - bdtCustom=128 - }; +/* subtypes of BinData. + bdtCustom and above are ones that the JS compiler understands, but are + opaque to the database. +*/ +enum BinDataType { + BinDataGeneral = 0, + Function = 1, + ByteArrayDeprecated = 2, /* use BinGeneral instead */ + bdtUUID = 3, /* deprecated */ + newUUID = 4, /* language-independent UUID format across all drivers */ + MD5Type = 5, + bdtCustom = 128 +}; - /** Returns a number for where a given type falls in the sort order. - * Elements with the same return value should be compared for value equality. - * The return value is not a BSONType and should not be treated as one. - * Note: if the order changes, indexes have to be re-built or than can be corruption - */ - inline int canonicalizeBSONType(BSONType type) { - switch (type) { +/** Returns a number for where a given type falls in the sort order. + * Elements with the same return value should be compared for value equality. + * The return value is not a BSONType and should not be treated as one. + * Note: if the order changes, indexes have to be re-built or than can be corruption + */ +inline int canonicalizeBSONType(BSONType type) { + switch (type) { case MinKey: case MaxKey: return type; @@ -163,6 +163,6 @@ namespace mongo { default: verify(0); return -1; - } } } +} diff --git a/src/mongo/bson/json.cpp b/src/mongo/bson/json.cpp index 961033570da..5e74b9d9a16 100644 --- a/src/mongo/bson/json.cpp +++ b/src/mongo/bson/json.cpp @@ -42,13 +42,14 @@ namespace mongo { - using std::unique_ptr; - using std::ostringstream; - using std::string; +using std::unique_ptr; +using std::ostringstream; +using std::string; #if 0 -#define MONGO_JSON_DEBUG(message) log() << "JSON DEBUG @ " << __FILE__\ - << ":" << __LINE__ << " " << __FUNCTION__ << ": " << message << endl; +#define MONGO_JSON_DEBUG(message) \ + log() << "JSON DEBUG @ " << __FILE__ << ":" << __LINE__ << " " << __FUNCTION__ << ": " \ + << message << endl; #else #define MONGO_JSON_DEBUG(message) #endif @@ -58,613 +59,370 @@ namespace mongo { #define CONTROL "\a\b\f\n\r\t\v" #define JOPTIONS "gims" - // Size hints given to char vectors - enum { - ID_RESERVE_SIZE = 64, - PAT_RESERVE_SIZE = 4096, - OPT_RESERVE_SIZE = 64, - FIELD_RESERVE_SIZE = 4096, - STRINGVAL_RESERVE_SIZE = 4096, - BINDATA_RESERVE_SIZE = 4096, - BINDATATYPE_RESERVE_SIZE = 4096, - NS_RESERVE_SIZE = 64, - DB_RESERVE_SIZE = 64, - NUMBERLONG_RESERVE_SIZE = 64, - DATE_RESERVE_SIZE = 64 - }; - - static const char* LBRACE = "{", - *RBRACE = "}", - *LBRACKET = "[", - *RBRACKET = "]", - *LPAREN = "(", - *RPAREN = ")", - *COLON = ":", - *COMMA = ",", - *FORWARDSLASH = "/", - *SINGLEQUOTE = "'", - *DOUBLEQUOTE = "\""; - - JParse::JParse(StringData str) - : _buf(str.rawData()) - , _input(_buf) - , _input_end(_input + str.size()) - {} - - Status JParse::parseError(StringData msg) { - std::ostringstream ossmsg; - ossmsg << msg; - ossmsg << ": offset:"; - ossmsg << offset(); - ossmsg << " of:"; - ossmsg << _buf; - return Status(ErrorCodes::FailedToParse, ossmsg.str()); - } - - Status JParse::value(StringData fieldName, BSONObjBuilder& builder) { - MONGO_JSON_DEBUG("fieldName: " << fieldName); - if (peekToken(LBRACE)) { - Status ret = object(fieldName, builder); - if (ret != Status::OK()) { - return ret; - } - } - else if (peekToken(LBRACKET)) { - Status ret = array(fieldName, builder); - if (ret != Status::OK()) { - return ret; - } - } - else if (readToken("new")) { - Status ret = constructor(fieldName, builder); - if (ret != Status::OK()) { - return ret; - } - } - else if (readToken("Date")) { - Status ret = date(fieldName, builder); - if (ret != Status::OK()) { - return ret; - } - } - else if (readToken("Timestamp")) { - Status ret = timestamp(fieldName, builder); - if (ret != Status::OK()) { - return ret; - } - } - else if (readToken("ObjectId")) { - Status ret = objectId(fieldName, builder); - if (ret != Status::OK()) { - return ret; - } - } - else if (readToken("NumberLong")) { - Status ret = numberLong(fieldName, builder); - if (ret != Status::OK()) { - return ret; - } - } - else if (readToken("NumberInt")) { - Status ret = numberInt(fieldName, builder); - if (ret != Status::OK()) { - return ret; - } +// Size hints given to char vectors +enum { + ID_RESERVE_SIZE = 64, + PAT_RESERVE_SIZE = 4096, + OPT_RESERVE_SIZE = 64, + FIELD_RESERVE_SIZE = 4096, + STRINGVAL_RESERVE_SIZE = 4096, + BINDATA_RESERVE_SIZE = 4096, + BINDATATYPE_RESERVE_SIZE = 4096, + NS_RESERVE_SIZE = 64, + DB_RESERVE_SIZE = 64, + NUMBERLONG_RESERVE_SIZE = 64, + DATE_RESERVE_SIZE = 64 +}; + +static const char* LBRACE = "{", * RBRACE = "}", * LBRACKET = "[", * RBRACKET = "]", * LPAREN = "(", + * RPAREN = ")", * COLON = ":", * COMMA = ",", * FORWARDSLASH = "/", + * SINGLEQUOTE = "'", * DOUBLEQUOTE = "\""; + +JParse::JParse(StringData str) + : _buf(str.rawData()), _input(_buf), _input_end(_input + str.size()) {} + +Status JParse::parseError(StringData msg) { + std::ostringstream ossmsg; + ossmsg << msg; + ossmsg << ": offset:"; + ossmsg << offset(); + ossmsg << " of:"; + ossmsg << _buf; + return Status(ErrorCodes::FailedToParse, ossmsg.str()); +} + +Status JParse::value(StringData fieldName, BSONObjBuilder& builder) { + MONGO_JSON_DEBUG("fieldName: " << fieldName); + if (peekToken(LBRACE)) { + Status ret = object(fieldName, builder); + if (ret != Status::OK()) { + return ret; } - else if (readToken("Dbref") || readToken("DBRef")) { - Status ret = dbRef(fieldName, builder); - if (ret != Status::OK()) { - return ret; - } + } else if (peekToken(LBRACKET)) { + Status ret = array(fieldName, builder); + if (ret != Status::OK()) { + return ret; } - else if (peekToken(FORWARDSLASH)) { - Status ret = regex(fieldName, builder); - if (ret != Status::OK()) { - return ret; - } + } else if (readToken("new")) { + Status ret = constructor(fieldName, builder); + if (ret != Status::OK()) { + return ret; } - else if (peekToken(DOUBLEQUOTE) || peekToken(SINGLEQUOTE)) { - std::string valueString; - valueString.reserve(STRINGVAL_RESERVE_SIZE); - Status ret = quotedString(&valueString); - if (ret != Status::OK()) { - return ret; - } - builder.append(fieldName, valueString); + } else if (readToken("Date")) { + Status ret = date(fieldName, builder); + if (ret != Status::OK()) { + return ret; } - else if (readToken("true")) { - builder.append(fieldName, true); + } else if (readToken("Timestamp")) { + Status ret = timestamp(fieldName, builder); + if (ret != Status::OK()) { + return ret; } - else if (readToken("false")) { - builder.append(fieldName, false); + } else if (readToken("ObjectId")) { + Status ret = objectId(fieldName, builder); + if (ret != Status::OK()) { + return ret; } - else if (readToken("null")) { - builder.appendNull(fieldName); + } else if (readToken("NumberLong")) { + Status ret = numberLong(fieldName, builder); + if (ret != Status::OK()) { + return ret; } - else if (readToken("undefined")) { - builder.appendUndefined(fieldName); + } else if (readToken("NumberInt")) { + Status ret = numberInt(fieldName, builder); + if (ret != Status::OK()) { + return ret; } - else if (readToken("NaN")) { - builder.append(fieldName, std::numeric_limits<double>::quiet_NaN()); + } else if (readToken("Dbref") || readToken("DBRef")) { + Status ret = dbRef(fieldName, builder); + if (ret != Status::OK()) { + return ret; } - else if (readToken("Infinity")) { - builder.append(fieldName, std::numeric_limits<double>::infinity()); + } else if (peekToken(FORWARDSLASH)) { + Status ret = regex(fieldName, builder); + if (ret != Status::OK()) { + return ret; } - else if (readToken("-Infinity")) { - builder.append(fieldName, -std::numeric_limits<double>::infinity()); + } else if (peekToken(DOUBLEQUOTE) || peekToken(SINGLEQUOTE)) { + std::string valueString; + valueString.reserve(STRINGVAL_RESERVE_SIZE); + Status ret = quotedString(&valueString); + if (ret != Status::OK()) { + return ret; } - else { - Status ret = number(fieldName, builder); - if (ret != Status::OK()) { - return ret; - } + builder.append(fieldName, valueString); + } else if (readToken("true")) { + builder.append(fieldName, true); + } else if (readToken("false")) { + builder.append(fieldName, false); + } else if (readToken("null")) { + builder.appendNull(fieldName); + } else if (readToken("undefined")) { + builder.appendUndefined(fieldName); + } else if (readToken("NaN")) { + builder.append(fieldName, std::numeric_limits<double>::quiet_NaN()); + } else if (readToken("Infinity")) { + builder.append(fieldName, std::numeric_limits<double>::infinity()); + } else if (readToken("-Infinity")) { + builder.append(fieldName, -std::numeric_limits<double>::infinity()); + } else { + Status ret = number(fieldName, builder); + if (ret != Status::OK()) { + return ret; } - return Status::OK(); } + return Status::OK(); +} - Status JParse::parse(BSONObjBuilder& builder) { - return isArray() ? array("UNUSED", builder, false) : object("UNUSED", builder, false); +Status JParse::parse(BSONObjBuilder& builder) { + return isArray() ? array("UNUSED", builder, false) : object("UNUSED", builder, false); +} + +Status JParse::object(StringData fieldName, BSONObjBuilder& builder, bool subObject) { + MONGO_JSON_DEBUG("fieldName: " << fieldName); + if (!readToken(LBRACE)) { + return parseError("Expecting '{'"); } - Status JParse::object(StringData fieldName, BSONObjBuilder& builder, bool subObject) { - MONGO_JSON_DEBUG("fieldName: " << fieldName); - if (!readToken(LBRACE)) { - return parseError("Expecting '{'"); + // Empty object + if (readToken(RBRACE)) { + if (subObject) { + BSONObjBuilder empty(builder.subobjStart(fieldName)); + empty.done(); } + return Status::OK(); + } - // Empty object - if (readToken(RBRACE)) { - if (subObject) { - BSONObjBuilder empty(builder.subobjStart(fieldName)); - empty.done(); - } - return Status::OK(); - } + // Special object + std::string firstField; + firstField.reserve(FIELD_RESERVE_SIZE); + Status ret = field(&firstField); + if (ret != Status::OK()) { + return ret; + } - // Special object - std::string firstField; - firstField.reserve(FIELD_RESERVE_SIZE); - Status ret = field(&firstField); + if (firstField == "$oid") { + if (!subObject) { + return parseError("Reserved field name in base object: $oid"); + } + Status ret = objectIdObject(fieldName, builder); if (ret != Status::OK()) { return ret; } - - if (firstField == "$oid") { - if (!subObject) { - return parseError("Reserved field name in base object: $oid"); - } - Status ret = objectIdObject(fieldName, builder); - if (ret != Status::OK()) { - return ret; - } - } - else if (firstField == "$binary") { - if (!subObject) { - return parseError("Reserved field name in base object: $binary"); - } - Status ret = binaryObject(fieldName, builder); - if (ret != Status::OK()) { - return ret; - } + } else if (firstField == "$binary") { + if (!subObject) { + return parseError("Reserved field name in base object: $binary"); } - else if (firstField == "$date") { - if (!subObject) { - return parseError("Reserved field name in base object: $date"); - } - Status ret = dateObject(fieldName, builder); - if (ret != Status::OK()) { - return ret; - } + Status ret = binaryObject(fieldName, builder); + if (ret != Status::OK()) { + return ret; } - else if (firstField == "$timestamp") { - if (!subObject) { - return parseError("Reserved field name in base object: $timestamp"); - } - Status ret = timestampObject(fieldName, builder); - if (ret != Status::OK()) { - return ret; - } + } else if (firstField == "$date") { + if (!subObject) { + return parseError("Reserved field name in base object: $date"); } - else if (firstField == "$regex") { - if (!subObject) { - return parseError("Reserved field name in base object: $regex"); - } - Status ret = regexObject(fieldName, builder); - if (ret != Status::OK()) { - return ret; - } - } - else if (firstField == "$ref") { - if (!subObject) { - return parseError("Reserved field name in base object: $ref"); - } - Status ret = dbRefObject(fieldName, builder); - if (ret != Status::OK()) { - return ret; - } + Status ret = dateObject(fieldName, builder); + if (ret != Status::OK()) { + return ret; } - else if (firstField == "$undefined") { - if (!subObject) { - return parseError("Reserved field name in base object: $undefined"); - } - Status ret = undefinedObject(fieldName, builder); - if (ret != Status::OK()) { - return ret; - } + } else if (firstField == "$timestamp") { + if (!subObject) { + return parseError("Reserved field name in base object: $timestamp"); } - else if (firstField == "$numberLong") { - if (!subObject) { - return parseError("Reserved field name in base object: $numberLong"); - } - Status ret = numberLongObject(fieldName, builder); - if (ret != Status::OK()) { - return ret; - } + Status ret = timestampObject(fieldName, builder); + if (ret != Status::OK()) { + return ret; } - else if (firstField == "$minKey") { - if (!subObject) { - return parseError("Reserved field name in base object: $minKey"); - } - Status ret = minKeyObject(fieldName, builder); - if (ret != Status::OK()) { - return ret; - } + } else if (firstField == "$regex") { + if (!subObject) { + return parseError("Reserved field name in base object: $regex"); } - else if (firstField == "$maxKey") { - if (!subObject) { - return parseError("Reserved field name in base object: $maxKey"); - } - Status ret = maxKeyObject(fieldName, builder); - if (ret != Status::OK()) { - return ret; - } + Status ret = regexObject(fieldName, builder); + if (ret != Status::OK()) { + return ret; } - else { // firstField != <reserved field name> - // Normal object - - // Only create a sub builder if this is not the base object - BSONObjBuilder* objBuilder = &builder; - unique_ptr<BSONObjBuilder> subObjBuilder; - if (subObject) { - subObjBuilder.reset(new BSONObjBuilder(builder.subobjStart(fieldName))); - objBuilder = subObjBuilder.get(); - } - - if (!readToken(COLON)) { - return parseError("Expecting ':'"); - } - Status valueRet = value(firstField, *objBuilder); - if (valueRet != Status::OK()) { - return valueRet; - } - while (readToken(COMMA)) { - std::string fieldName; - fieldName.reserve(FIELD_RESERVE_SIZE); - Status fieldRet = field(&fieldName); - if (fieldRet != Status::OK()) { - return fieldRet; - } - if (!readToken(COLON)) { - return parseError("Expecting ':'"); - } - Status valueRet = value(fieldName, *objBuilder); - if (valueRet != Status::OK()) { - return valueRet; - } - } + } else if (firstField == "$ref") { + if (!subObject) { + return parseError("Reserved field name in base object: $ref"); } - if (!readToken(RBRACE)) { - return parseError("Expecting '}' or ','"); + Status ret = dbRefObject(fieldName, builder); + if (ret != Status::OK()) { + return ret; } - return Status::OK(); - } - - Status JParse::objectIdObject(StringData fieldName, BSONObjBuilder& builder) { - if (!readToken(COLON)) { - return parseError("Expected ':'"); + } else if (firstField == "$undefined") { + if (!subObject) { + return parseError("Reserved field name in base object: $undefined"); } - std::string id; - id.reserve(ID_RESERVE_SIZE); - Status ret = quotedString(&id); + Status ret = undefinedObject(fieldName, builder); if (ret != Status::OK()) { return ret; } - if (id.size() != 24) { - return parseError("Expecting 24 hex digits: " + id); - } - if (!isHexString(id)) { - return parseError("Expecting hex digits: " + id); + } else if (firstField == "$numberLong") { + if (!subObject) { + return parseError("Reserved field name in base object: $numberLong"); } - builder.append(fieldName, OID(id)); - return Status::OK(); - } - - Status JParse::binaryObject(StringData fieldName, BSONObjBuilder& builder) { - if (!readToken(COLON)) { - return parseError("Expected ':'"); + Status ret = numberLongObject(fieldName, builder); + if (ret != Status::OK()) { + return ret; } - std::string binDataString; - binDataString.reserve(BINDATA_RESERVE_SIZE); - Status dataRet = quotedString(&binDataString); - if (dataRet != Status::OK()) { - return dataRet; + } else if (firstField == "$minKey") { + if (!subObject) { + return parseError("Reserved field name in base object: $minKey"); } - if (binDataString.size() % 4 != 0) { - return parseError("Invalid length base64 encoded string"); + Status ret = minKeyObject(fieldName, builder); + if (ret != Status::OK()) { + return ret; } - if (!isBase64String(binDataString)) { - return parseError("Invalid character in base64 encoded string"); + } else if (firstField == "$maxKey") { + if (!subObject) { + return parseError("Reserved field name in base object: $maxKey"); } - const std::string& binData = base64::decode(binDataString); - if (!readToken(COMMA)) { - return parseError("Expected ','"); + Status ret = maxKeyObject(fieldName, builder); + if (ret != Status::OK()) { + return ret; } + } else { // firstField != <reserved field name> + // Normal object - if (!readField("$type")) { - return parseError("Expected second field name: \"$type\", in \"$binary\" object"); - } - if (!readToken(COLON)) { - return parseError("Expected ':'"); - } - std::string binDataType; - binDataType.reserve(BINDATATYPE_RESERVE_SIZE); - Status typeRet = quotedString(&binDataType); - if (typeRet != Status::OK()) { - return typeRet; - } - if ((binDataType.size() != 2) || !isHexString(binDataType)) { - return parseError("Argument of $type in $bindata object must be a hex string representation of a single byte"); + // Only create a sub builder if this is not the base object + BSONObjBuilder* objBuilder = &builder; + unique_ptr<BSONObjBuilder> subObjBuilder; + if (subObject) { + subObjBuilder.reset(new BSONObjBuilder(builder.subobjStart(fieldName))); + objBuilder = subObjBuilder.get(); } - builder.appendBinData( fieldName, binData.length(), - BinDataType(fromHex(binDataType)), - binData.data()); - return Status::OK(); - } - Status JParse::dateObject(StringData fieldName, BSONObjBuilder& builder) { if (!readToken(COLON)) { - return parseError("Expected ':'"); + return parseError("Expecting ':'"); } - errno = 0; - char* endptr; - Date_t date; - - if (peekToken(DOUBLEQUOTE)) { - std::string dateString; - dateString.reserve(DATE_RESERVE_SIZE); - Status ret = quotedString(&dateString); - if (!ret.isOK()) { - return ret; - } - StatusWith<Date_t> dateRet = dateFromISOString(dateString); - if (!dateRet.isOK()) { - return dateRet.getStatus(); - } - date = dateRet.getValue(); + Status valueRet = value(firstField, *objBuilder); + if (valueRet != Status::OK()) { + return valueRet; } - else if (readToken(LBRACE)) { + while (readToken(COMMA)) { std::string fieldName; fieldName.reserve(FIELD_RESERVE_SIZE); - Status ret = field(&fieldName); - if (ret != Status::OK()) { - return ret; - } - if (fieldName != "$numberLong") { - return parseError("Expected field name: $numberLong for $date value object"); + Status fieldRet = field(&fieldName); + if (fieldRet != Status::OK()) { + return fieldRet; } if (!readToken(COLON)) { return parseError("Expecting ':'"); } - - // The number must be a quoted string, since large long numbers could overflow a double - // and thus may not be valid JSON - std::string numberLongString; - numberLongString.reserve(NUMBERLONG_RESERVE_SIZE); - ret = quotedString(&numberLongString); - if (!ret.isOK()) { - return ret; - } - - long long numberLong; - ret = parseNumberFromString(numberLongString, &numberLong); - if (!ret.isOK()) { - return ret; - } - date = Date_t::fromMillisSinceEpoch(numberLong); - } - else { - // SERVER-11920: We should use parseNumberFromString here, but that function requires - // that we know ahead of time where the number ends, which is not currently the case. - date = Date_t::fromMillisSinceEpoch(strtoll(_input, &endptr, 10)); - if (_input == endptr) { - return parseError("Date expecting integer milliseconds"); - } - if (errno == ERANGE) { - /* Need to handle this because jsonString outputs the value of Date_t as unsigned. - * See SERVER-8330 and SERVER-8573 */ - errno = 0; - // SERVER-11920: We should use parseNumberFromString here, but that function - // requires that we know ahead of time where the number ends, which is not currently - // the case. - date = Date_t::fromMillisSinceEpoch( - static_cast<long long>(strtoull(_input, &endptr, 10))); - if (errno == ERANGE) { - return parseError("Date milliseconds overflow"); - } + Status valueRet = value(fieldName, *objBuilder); + if (valueRet != Status::OK()) { + return valueRet; } - _input = endptr; } - builder.appendDate(fieldName, date); - return Status::OK(); } - - Status JParse::timestampObject(StringData fieldName, BSONObjBuilder& builder) { - if (!readToken(COLON)) { - return parseError("Expecting ':'"); - } - if (!readToken(LBRACE)) { - return parseError("Expecting '{' to start \"$timestamp\" object"); - } - - if (!readField("t")) { - return parseError("Expected field name \"t\" in \"$timestamp\" sub object"); - } - if (!readToken(COLON)) { - return parseError("Expecting ':'"); - } - if (readToken("-")) { - return parseError("Negative seconds in \"$timestamp\""); - } - errno = 0; - char* endptr; - // SERVER-11920: We should use parseNumberFromString here, but that function requires that - // we know ahead of time where the number ends, which is not currently the case. - uint32_t seconds = strtoul(_input, &endptr, 10); - if (errno == ERANGE) { - return parseError("Timestamp seconds overflow"); - } - if (_input == endptr) { - return parseError("Expecting unsigned integer seconds in \"$timestamp\""); - } - _input = endptr; - if (!readToken(COMMA)) { - return parseError("Expecting ','"); - } - - if (!readField("i")) { - return parseError("Expected field name \"i\" in \"$timestamp\" sub object"); - } - if (!readToken(COLON)) { - return parseError("Expecting ':'"); - } - if (readToken("-")) { - return parseError("Negative increment in \"$timestamp\""); - } - errno = 0; - // SERVER-11920: We should use parseNumberFromString here, but that function requires that - // we know ahead of time where the number ends, which is not currently the case. - uint32_t count = strtoul(_input, &endptr, 10); - if (errno == ERANGE) { - return parseError("Timestamp increment overflow"); - } - if (_input == endptr) { - return parseError("Expecting unsigned integer increment in \"$timestamp\""); - } - _input = endptr; - - if (!readToken(RBRACE)) { - return parseError("Expecting '}'"); - } - builder.append(fieldName, Timestamp(seconds, count)); - return Status::OK(); + if (!readToken(RBRACE)) { + return parseError("Expecting '}' or ','"); } + return Status::OK(); +} - Status JParse::regexObject(StringData fieldName, BSONObjBuilder& builder) { - if (!readToken(COLON)) { - return parseError("Expecting ':'"); - } - std::string pat; - pat.reserve(PAT_RESERVE_SIZE); - Status patRet = quotedString(&pat); - if (patRet != Status::OK()) { - return patRet; - } - if (readToken(COMMA)) { - if (!readField("$options")) { - return parseError("Expected field name: \"$options\" in \"$regex\" object"); - } - if (!readToken(COLON)) { - return parseError("Expecting ':'"); - } - std::string opt; - opt.reserve(OPT_RESERVE_SIZE); - Status optRet = quotedString(&opt); - if (optRet != Status::OK()) { - return optRet; - } - Status optCheckRet = regexOptCheck(opt); - if (optCheckRet != Status::OK()) { - return optCheckRet; - } - builder.appendRegex(fieldName, pat, opt); - } - else { - builder.appendRegex(fieldName, pat, ""); - } - return Status::OK(); +Status JParse::objectIdObject(StringData fieldName, BSONObjBuilder& builder) { + if (!readToken(COLON)) { + return parseError("Expected ':'"); } + std::string id; + id.reserve(ID_RESERVE_SIZE); + Status ret = quotedString(&id); + if (ret != Status::OK()) { + return ret; + } + if (id.size() != 24) { + return parseError("Expecting 24 hex digits: " + id); + } + if (!isHexString(id)) { + return parseError("Expecting hex digits: " + id); + } + builder.append(fieldName, OID(id)); + return Status::OK(); +} - Status JParse::dbRefObject(StringData fieldName, BSONObjBuilder& builder) { - - BSONObjBuilder subBuilder(builder.subobjStart(fieldName)); +Status JParse::binaryObject(StringData fieldName, BSONObjBuilder& builder) { + if (!readToken(COLON)) { + return parseError("Expected ':'"); + } + std::string binDataString; + binDataString.reserve(BINDATA_RESERVE_SIZE); + Status dataRet = quotedString(&binDataString); + if (dataRet != Status::OK()) { + return dataRet; + } + if (binDataString.size() % 4 != 0) { + return parseError("Invalid length base64 encoded string"); + } + if (!isBase64String(binDataString)) { + return parseError("Invalid character in base64 encoded string"); + } + const std::string& binData = base64::decode(binDataString); + if (!readToken(COMMA)) { + return parseError("Expected ','"); + } - if (!readToken(COLON)) { - return parseError("DBRef: Expecting ':'"); - } - std::string ns; - ns.reserve(NS_RESERVE_SIZE); - Status ret = quotedString(&ns); - if (ret != Status::OK()) { + if (!readField("$type")) { + return parseError("Expected second field name: \"$type\", in \"$binary\" object"); + } + if (!readToken(COLON)) { + return parseError("Expected ':'"); + } + std::string binDataType; + binDataType.reserve(BINDATATYPE_RESERVE_SIZE); + Status typeRet = quotedString(&binDataType); + if (typeRet != Status::OK()) { + return typeRet; + } + if ((binDataType.size() != 2) || !isHexString(binDataType)) { + return parseError( + "Argument of $type in $bindata object must be a hex string representation of a single " + "byte"); + } + builder.appendBinData( + fieldName, binData.length(), BinDataType(fromHex(binDataType)), binData.data()); + return Status::OK(); +} + +Status JParse::dateObject(StringData fieldName, BSONObjBuilder& builder) { + if (!readToken(COLON)) { + return parseError("Expected ':'"); + } + errno = 0; + char* endptr; + Date_t date; + + if (peekToken(DOUBLEQUOTE)) { + std::string dateString; + dateString.reserve(DATE_RESERVE_SIZE); + Status ret = quotedString(&dateString); + if (!ret.isOK()) { return ret; } - subBuilder.append("$ref", ns); - - if (!readToken(COMMA)) { - return parseError("DBRef: Expecting ','"); - } - - if (!readField("$id")) { - return parseError("DBRef: Expected field name: \"$id\" in \"$ref\" object"); + StatusWith<Date_t> dateRet = dateFromISOString(dateString); + if (!dateRet.isOK()) { + return dateRet.getStatus(); } - if (!readToken(COLON)) { - return parseError("DBRef: Expecting ':'"); - } - Status valueRet = value("$id", subBuilder); - if (valueRet != Status::OK()) { - return valueRet; - } - - if (readToken(COMMA)) { - if (!readField("$db")) { - return parseError("DBRef: Expected field name: \"$db\" in \"$ref\" object"); - } - if (!readToken(COLON)) { - return parseError("DBRef: Expecting ':'"); - } - std::string db; - db.reserve(DB_RESERVE_SIZE); - ret = quotedString(&db); - if (ret != Status::OK()) { - return ret; - } - subBuilder.append("$db", db); - } - - subBuilder.done(); - return Status::OK(); - } - - Status JParse::undefinedObject(StringData fieldName, BSONObjBuilder& builder) { - if (!readToken(COLON)) { - return parseError("Expecting ':'"); + date = dateRet.getValue(); + } else if (readToken(LBRACE)) { + std::string fieldName; + fieldName.reserve(FIELD_RESERVE_SIZE); + Status ret = field(&fieldName); + if (ret != Status::OK()) { + return ret; } - if (!readToken("true")) { - return parseError("Reserved field \"$undefined\" requires value of true"); + if (fieldName != "$numberLong") { + return parseError("Expected field name: $numberLong for $date value object"); } - builder.appendUndefined(fieldName); - return Status::OK(); - } - - Status JParse::numberLongObject(StringData fieldName, BSONObjBuilder& builder) { if (!readToken(COLON)) { return parseError("Expecting ':'"); } - // The number must be a quoted string, since large long numbers could overflow a double and - // thus may not be valid JSON + // The number must be a quoted string, since large long numbers could overflow a double + // and thus may not be valid JSON std::string numberLongString; numberLongString.reserve(NUMBERLONG_RESERVE_SIZE); - Status ret = quotedString(&numberLongString); + ret = quotedString(&numberLongString); if (!ret.isOK()) { return ret; } @@ -674,86 +432,11 @@ namespace mongo { if (!ret.isOK()) { return ret; } - - builder.appendNumber(fieldName, numberLong); - return Status::OK(); - } - - Status JParse::minKeyObject(StringData fieldName, BSONObjBuilder& builder) { - if (!readToken(COLON)) { - return parseError("Expecting ':'"); - } - if (!readToken("1")) { - return parseError("Reserved field \"$minKey\" requires value of 1"); - } - builder.appendMinKey(fieldName); - return Status::OK(); - } - - Status JParse::maxKeyObject(StringData fieldName, BSONObjBuilder& builder) { - if (!readToken(COLON)) { - return parseError("Expecting ':'"); - } - if (!readToken("1")) { - return parseError("Reserved field \"$maxKey\" requires value of 1"); - } - builder.appendMaxKey(fieldName); - return Status::OK(); - } - - Status JParse::array(StringData fieldName, BSONObjBuilder& builder, bool subObject) { - MONGO_JSON_DEBUG("fieldName: " << fieldName); - uint32_t index(0); - if (!readToken(LBRACKET)) { - return parseError("Expecting '['"); - } - - BSONObjBuilder* arrayBuilder = &builder; - unique_ptr<BSONObjBuilder> subObjBuilder; - if (subObject) { - subObjBuilder.reset(new BSONObjBuilder(builder.subarrayStart(fieldName))); - arrayBuilder = subObjBuilder.get(); - } - - if (!peekToken(RBRACKET)) { - do { - Status ret = value(builder.numStr(index), *arrayBuilder); - if (ret != Status::OK()) { - return ret; - } - index++; - } while (readToken(COMMA)); - } - arrayBuilder->done(); - if (!readToken(RBRACKET)) { - return parseError("Expecting ']' or ','"); - } - return Status::OK(); - } - - /* NOTE: this could be easily modified to allow "new" before other - * constructors, but for now it only allows "new" before Date(). - * Also note that unlike the interactive shell "Date(x)" and "new Date(x)" - * have the same behavior. XXX: this may not be desired. */ - Status JParse::constructor(StringData fieldName, BSONObjBuilder& builder) { - if (readToken("Date")) { - date(fieldName, builder); - } - else { - return parseError("\"new\" keyword not followed by Date constructor"); - } - return Status::OK(); - } - - Status JParse::date(StringData fieldName, BSONObjBuilder& builder) { - if (!readToken(LPAREN)) { - return parseError("Expecting '('"); - } - errno = 0; - char* endptr; - // SERVER-11920: We should use parseNumberFromString here, but that function requires that - // we know ahead of time where the number ends, which is not currently the case. - Date_t date = Date_t::fromMillisSinceEpoch(strtoll(_input, &endptr, 10)); + date = Date_t::fromMillisSinceEpoch(numberLong); + } else { + // SERVER-11920: We should use parseNumberFromString here, but that function requires + // that we know ahead of time where the number ends, which is not currently the case. + date = Date_t::fromMillisSinceEpoch(strtoll(_input, &endptr, 10)); if (_input == endptr) { return parseError("Date expecting integer milliseconds"); } @@ -761,192 +444,102 @@ namespace mongo { /* Need to handle this because jsonString outputs the value of Date_t as unsigned. * See SERVER-8330 and SERVER-8573 */ errno = 0; - // SERVER-11920: We should use parseNumberFromString here, but that function requires - // that we know ahead of time where the number ends, which is not currently the case. - date = Date_t::fromMillisSinceEpoch( - static_cast<long long>(strtoull(_input, &endptr, 10))); + // SERVER-11920: We should use parseNumberFromString here, but that function + // requires that we know ahead of time where the number ends, which is not currently + // the case. + date = + Date_t::fromMillisSinceEpoch(static_cast<long long>(strtoull(_input, &endptr, 10))); if (errno == ERANGE) { return parseError("Date milliseconds overflow"); } } _input = endptr; - if (!readToken(RPAREN)) { - return parseError("Expecting ')'"); - } - builder.appendDate(fieldName, date); - return Status::OK(); } + builder.appendDate(fieldName, date); + return Status::OK(); +} - Status JParse::timestamp(StringData fieldName, BSONObjBuilder& builder) { - if (!readToken(LPAREN)) { - return parseError("Expecting '('"); - } - if (readToken("-")) { - return parseError("Negative seconds in \"$timestamp\""); - } - errno = 0; - char* endptr; - // SERVER-11920: We should use parseNumberFromString here, but that function requires that - // we know ahead of time where the number ends, which is not currently the case. - uint32_t seconds = strtoul(_input, &endptr, 10); - if (errno == ERANGE) { - return parseError("Timestamp seconds overflow"); - } - if (_input == endptr) { - return parseError("Expecting unsigned integer seconds in \"$timestamp\""); - } - _input = endptr; - if (!readToken(COMMA)) { - return parseError("Expecting ','"); - } - if (readToken("-")) { - return parseError("Negative seconds in \"$timestamp\""); - } - errno = 0; - // SERVER-11920: We should use parseNumberFromString here, but that function requires that - // we know ahead of time where the number ends, which is not currently the case. - uint32_t count = strtoul(_input, &endptr, 10); - if (errno == ERANGE) { - return parseError("Timestamp increment overflow"); - } - if (_input == endptr) { - return parseError("Expecting unsigned integer increment in \"$timestamp\""); - } - _input = endptr; - if (!readToken(RPAREN)) { - return parseError("Expecting ')'"); - } - builder.append(fieldName, Timestamp(seconds, count)); - return Status::OK(); +Status JParse::timestampObject(StringData fieldName, BSONObjBuilder& builder) { + if (!readToken(COLON)) { + return parseError("Expecting ':'"); } - - Status JParse::objectId(StringData fieldName, BSONObjBuilder& builder) { - if (!readToken(LPAREN)) { - return parseError("Expecting '('"); - } - std::string id; - id.reserve(ID_RESERVE_SIZE); - Status ret = quotedString(&id); - if (ret != Status::OK()) { - return ret; - } - if (!readToken(RPAREN)) { - return parseError("Expecting ')'"); - } - if (id.size() != 24) { - return parseError("Expecting 24 hex digits: " + id); - } - if (!isHexString(id)) { - return parseError("Expecting hex digits: " + id); - } - builder.append(fieldName, OID(id)); - return Status::OK(); + if (!readToken(LBRACE)) { + return parseError("Expecting '{' to start \"$timestamp\" object"); } - Status JParse::numberLong(StringData fieldName, BSONObjBuilder& builder) { - if (!readToken(LPAREN)) { - return parseError("Expecting '('"); - } - errno = 0; - char* endptr; - // SERVER-11920: We should use parseNumberFromString here, but that function requires that - // we know ahead of time where the number ends, which is not currently the case. - int64_t val = strtoll(_input, &endptr, 10); - if (errno == ERANGE) { - return parseError("NumberLong out of range"); - } - if (_input == endptr) { - return parseError("Expecting number in NumberLong"); - } - _input = endptr; - if (!readToken(RPAREN)) { - return parseError("Expecting ')'"); - } - builder.appendNumber(fieldName, static_cast<long long int>(val)); - return Status::OK(); + if (!readField("t")) { + return parseError("Expected field name \"t\" in \"$timestamp\" sub object"); } - - Status JParse::numberInt(StringData fieldName, BSONObjBuilder& builder) { - if (!readToken(LPAREN)) { - return parseError("Expecting '('"); - } - errno = 0; - char* endptr; - // SERVER-11920: We should use parseNumberFromString here, but that function requires that - // we know ahead of time where the number ends, which is not currently the case. - int32_t val = strtol(_input, &endptr, 10); - if (errno == ERANGE) { - return parseError("NumberInt out of range"); - } - if (_input == endptr) { - return parseError("Expecting unsigned number in NumberInt"); - } - _input = endptr; - if (!readToken(RPAREN)) { - return parseError("Expecting ')'"); - } - builder.appendNumber(fieldName, static_cast<int>(val)); - return Status::OK(); + if (!readToken(COLON)) { + return parseError("Expecting ':'"); + } + if (readToken("-")) { + return parseError("Negative seconds in \"$timestamp\""); + } + errno = 0; + char* endptr; + // SERVER-11920: We should use parseNumberFromString here, but that function requires that + // we know ahead of time where the number ends, which is not currently the case. + uint32_t seconds = strtoul(_input, &endptr, 10); + if (errno == ERANGE) { + return parseError("Timestamp seconds overflow"); + } + if (_input == endptr) { + return parseError("Expecting unsigned integer seconds in \"$timestamp\""); + } + _input = endptr; + if (!readToken(COMMA)) { + return parseError("Expecting ','"); } + if (!readField("i")) { + return parseError("Expected field name \"i\" in \"$timestamp\" sub object"); + } + if (!readToken(COLON)) { + return parseError("Expecting ':'"); + } + if (readToken("-")) { + return parseError("Negative increment in \"$timestamp\""); + } + errno = 0; + // SERVER-11920: We should use parseNumberFromString here, but that function requires that + // we know ahead of time where the number ends, which is not currently the case. + uint32_t count = strtoul(_input, &endptr, 10); + if (errno == ERANGE) { + return parseError("Timestamp increment overflow"); + } + if (_input == endptr) { + return parseError("Expecting unsigned integer increment in \"$timestamp\""); + } + _input = endptr; - Status JParse::dbRef(StringData fieldName, BSONObjBuilder& builder) { - BSONObjBuilder subBuilder(builder.subobjStart(fieldName)); - - if (!readToken(LPAREN)) { - return parseError("Expecting '('"); - } - std::string ns; - ns.reserve(NS_RESERVE_SIZE); - Status refRet = quotedString(&ns); - if (refRet != Status::OK()) { - return refRet; - } - subBuilder.append("$ref", ns); - - if (!readToken(COMMA)) { - return parseError("Expecting ','"); - } - - Status valueRet = value("$id", subBuilder); - if (valueRet != Status::OK()) { - return valueRet; - } - - if (readToken(COMMA)) { - std::string db; - db.reserve(DB_RESERVE_SIZE); - Status dbRet = quotedString(&db); - if (dbRet != Status::OK()) { - return dbRet; - } - subBuilder.append("$db", db); - } - - if (!readToken(RPAREN)) { - return parseError("Expecting ')'"); - } - - subBuilder.done(); - return Status::OK(); + if (!readToken(RBRACE)) { + return parseError("Expecting '}'"); } + builder.append(fieldName, Timestamp(seconds, count)); + return Status::OK(); +} - Status JParse::regex(StringData fieldName, BSONObjBuilder& builder) { - if (!readToken(FORWARDSLASH)) { - return parseError("Expecting '/'"); - } - std::string pat; - pat.reserve(PAT_RESERVE_SIZE); - Status patRet = regexPat(&pat); - if (patRet != Status::OK()) { - return patRet; +Status JParse::regexObject(StringData fieldName, BSONObjBuilder& builder) { + if (!readToken(COLON)) { + return parseError("Expecting ':'"); + } + std::string pat; + pat.reserve(PAT_RESERVE_SIZE); + Status patRet = quotedString(&pat); + if (patRet != Status::OK()) { + return patRet; + } + if (readToken(COMMA)) { + if (!readField("$options")) { + return parseError("Expected field name: \"$options\" in \"$regex\" object"); } - if (!readToken(FORWARDSLASH)) { - return parseError("Expecting '/'"); + if (!readToken(COLON)) { + return parseError("Expecting ':'"); } std::string opt; opt.reserve(OPT_RESERVE_SIZE); - Status optRet = regexOpt(&opt); + Status optRet = quotedString(&opt); if (optRet != Status::OK()) { return optRet; } @@ -955,351 +548,732 @@ namespace mongo { return optCheckRet; } builder.appendRegex(fieldName, pat, opt); - return Status::OK(); + } else { + builder.appendRegex(fieldName, pat, ""); } + return Status::OK(); +} - Status JParse::regexPat(std::string* result) { - MONGO_JSON_DEBUG(""); - return chars(result, "/"); - } +Status JParse::dbRefObject(StringData fieldName, BSONObjBuilder& builder) { + BSONObjBuilder subBuilder(builder.subobjStart(fieldName)); - Status JParse::regexOpt(std::string* result) { - MONGO_JSON_DEBUG(""); - return chars(result, "", JOPTIONS); + if (!readToken(COLON)) { + return parseError("DBRef: Expecting ':'"); } + std::string ns; + ns.reserve(NS_RESERVE_SIZE); + Status ret = quotedString(&ns); + if (ret != Status::OK()) { + return ret; + } + subBuilder.append("$ref", ns); - Status JParse::regexOptCheck(StringData opt) { - MONGO_JSON_DEBUG("opt: " << opt); - std::size_t i; - for (i = 0; i < opt.size(); i++) { - if (!match(opt[i], JOPTIONS)) { - return parseError(string("Bad regex option: ") + opt[i]); - } - } - return Status::OK(); + if (!readToken(COMMA)) { + return parseError("DBRef: Expecting ','"); } - Status JParse::number(StringData fieldName, BSONObjBuilder& builder) { - char* endptrll; - char* endptrd; - long long retll; - double retd; + if (!readField("$id")) { + return parseError("DBRef: Expected field name: \"$id\" in \"$ref\" object"); + } + if (!readToken(COLON)) { + return parseError("DBRef: Expecting ':'"); + } + Status valueRet = value("$id", subBuilder); + if (valueRet != Status::OK()) { + return valueRet; + } - // reset errno to make sure that we are getting it from strtod - errno = 0; - // SERVER-11920: We should use parseNumberFromString here, but that function requires that - // we know ahead of time where the number ends, which is not currently the case. - retd = strtod(_input, &endptrd); - // if pointer does not move, we found no digits - if (_input == endptrd) { - return parseError("Bad characters in value"); + if (readToken(COMMA)) { + if (!readField("$db")) { + return parseError("DBRef: Expected field name: \"$db\" in \"$ref\" object"); } - if (errno == ERANGE) { - return parseError("Value cannot fit in double"); + if (!readToken(COLON)) { + return parseError("DBRef: Expecting ':'"); } - // reset errno to make sure that we are getting it from strtoll - errno = 0; - // SERVER-11920: We should use parseNumberFromString here, but that function requires that - // we know ahead of time where the number ends, which is not currently the case. - retll = strtoll(_input, &endptrll, 10); - if (endptrll < endptrd || errno == ERANGE) { - // The number either had characters only meaningful for a double or - // could not fit in a 64 bit int - MONGO_JSON_DEBUG("Type: double"); - builder.append(fieldName, retd); - } - else if (retll == static_cast<int>(retll)) { - // The number can fit in a 32 bit int - MONGO_JSON_DEBUG("Type: 32 bit int"); - builder.append(fieldName, static_cast<int>(retll)); - } - else { - // The number can fit in a 64 bit int - MONGO_JSON_DEBUG("Type: 64 bit int"); - builder.append(fieldName, retll); - } - _input = endptrd; - if (_input >= _input_end) { - return parseError("Trailing number at end of input"); + std::string db; + db.reserve(DB_RESERVE_SIZE); + ret = quotedString(&db); + if (ret != Status::OK()) { + return ret; } - return Status::OK(); + subBuilder.append("$db", db); } - Status JParse::field(std::string* result) { - MONGO_JSON_DEBUG(""); - if (peekToken(DOUBLEQUOTE) || peekToken(SINGLEQUOTE)) { - // Quoted key - // TODO: make sure quoted field names cannot contain null characters - return quotedString(result); - } - else { - // Unquoted key - // 'isspace()' takes an 'int' (signed), so (default signed) 'char's get sign-extended - // and therefore 'corrupted' unless we force them to be unsigned ... 0x80 becomes - // 0xffffff80 as seen by isspace when sign-extended ... we want it to be 0x00000080 - while (_input < _input_end && - isspace(*reinterpret_cast<const unsigned char*>(_input))) { - ++_input; - } - if (_input >= _input_end) { - return parseError("Field name expected"); - } - if (!match(*_input, ALPHA "_$")) { - return parseError("First character in field must be [A-Za-z$_]"); - } - return chars(result, "", ALPHA DIGIT "_$"); - } + subBuilder.done(); + return Status::OK(); +} + +Status JParse::undefinedObject(StringData fieldName, BSONObjBuilder& builder) { + if (!readToken(COLON)) { + return parseError("Expecting ':'"); } + if (!readToken("true")) { + return parseError("Reserved field \"$undefined\" requires value of true"); + } + builder.appendUndefined(fieldName); + return Status::OK(); +} - Status JParse::quotedString(std::string* result) { - MONGO_JSON_DEBUG(""); - if (readToken(DOUBLEQUOTE)) { - Status ret = chars(result, "\""); - if (ret != Status::OK()) { - return ret; - } - if (!readToken(DOUBLEQUOTE)) { - return parseError("Expecting '\"'"); - } - } - else if (readToken(SINGLEQUOTE)) { - Status ret = chars(result, "'"); +Status JParse::numberLongObject(StringData fieldName, BSONObjBuilder& builder) { + if (!readToken(COLON)) { + return parseError("Expecting ':'"); + } + + // The number must be a quoted string, since large long numbers could overflow a double and + // thus may not be valid JSON + std::string numberLongString; + numberLongString.reserve(NUMBERLONG_RESERVE_SIZE); + Status ret = quotedString(&numberLongString); + if (!ret.isOK()) { + return ret; + } + + long long numberLong; + ret = parseNumberFromString(numberLongString, &numberLong); + if (!ret.isOK()) { + return ret; + } + + builder.appendNumber(fieldName, numberLong); + return Status::OK(); +} + +Status JParse::minKeyObject(StringData fieldName, BSONObjBuilder& builder) { + if (!readToken(COLON)) { + return parseError("Expecting ':'"); + } + if (!readToken("1")) { + return parseError("Reserved field \"$minKey\" requires value of 1"); + } + builder.appendMinKey(fieldName); + return Status::OK(); +} + +Status JParse::maxKeyObject(StringData fieldName, BSONObjBuilder& builder) { + if (!readToken(COLON)) { + return parseError("Expecting ':'"); + } + if (!readToken("1")) { + return parseError("Reserved field \"$maxKey\" requires value of 1"); + } + builder.appendMaxKey(fieldName); + return Status::OK(); +} + +Status JParse::array(StringData fieldName, BSONObjBuilder& builder, bool subObject) { + MONGO_JSON_DEBUG("fieldName: " << fieldName); + uint32_t index(0); + if (!readToken(LBRACKET)) { + return parseError("Expecting '['"); + } + + BSONObjBuilder* arrayBuilder = &builder; + unique_ptr<BSONObjBuilder> subObjBuilder; + if (subObject) { + subObjBuilder.reset(new BSONObjBuilder(builder.subarrayStart(fieldName))); + arrayBuilder = subObjBuilder.get(); + } + + if (!peekToken(RBRACKET)) { + do { + Status ret = value(builder.numStr(index), *arrayBuilder); if (ret != Status::OK()) { return ret; } - if (!readToken(SINGLEQUOTE)) { - return parseError("Expecting '''"); - } - } - else { - return parseError("Expecting quoted string"); - } - return Status::OK(); + index++; + } while (readToken(COMMA)); + } + arrayBuilder->done(); + if (!readToken(RBRACKET)) { + return parseError("Expecting ']' or ','"); } + return Status::OK(); +} + +/* NOTE: this could be easily modified to allow "new" before other + * constructors, but for now it only allows "new" before Date(). + * Also note that unlike the interactive shell "Date(x)" and "new Date(x)" + * have the same behavior. XXX: this may not be desired. */ +Status JParse::constructor(StringData fieldName, BSONObjBuilder& builder) { + if (readToken("Date")) { + date(fieldName, builder); + } else { + return parseError("\"new\" keyword not followed by Date constructor"); + } + return Status::OK(); +} - /* - * terminalSet are characters that signal end of string (e.g.) [ :\0] - * allowedSet are the characters that are allowed, if this is set - */ - Status JParse::chars(std::string* result, const char* terminalSet, - const char* allowedSet) { - MONGO_JSON_DEBUG("terminalSet: " << terminalSet); - if (_input >= _input_end) { - return parseError("Unexpected end of input"); - } - const char* q = _input; - while (q < _input_end && !match(*q, terminalSet)) { - MONGO_JSON_DEBUG("q: " << q); - if (allowedSet != NULL) { - if (!match(*q, allowedSet)) { - _input = q; - return Status::OK(); - } - } - if (0x00 <= *q && *q <= 0x1F) { - return parseError("Invalid control character"); - } - if (*q == '\\' && q + 1 < _input_end) { - switch (*(++q)) { - // Escape characters allowed by the JSON spec - case '"': result->push_back('"'); break; - case '\'': result->push_back('\''); break; - case '\\': result->push_back('\\'); break; - case '/': result->push_back('/'); break; - case 'b': result->push_back('\b'); break; - case 'f': result->push_back('\f'); break; - case 'n': result->push_back('\n'); break; - case 'r': result->push_back('\r'); break; - case 't': result->push_back('\t'); break; - case 'u': { //expect 4 hexdigits - // TODO: handle UTF-16 surrogate characters - ++q; - if (q + 4 >= _input_end) { - return parseError("Expecting 4 hex digits"); - } - if (!isHexString(StringData(q, 4))) { - return parseError("Expecting 4 hex digits"); - } - unsigned char first = fromHex(q); - unsigned char second = fromHex(q += 2); - const std::string& utf8str = encodeUTF8(first, second); - for (unsigned int i = 0; i < utf8str.size(); i++) { - result->push_back(utf8str[i]); - } - ++q; - break; - } - // Vertical tab character. Not in JSON spec but allowed in - // our implementation according to test suite. - case 'v': result->push_back('\v'); break; - // Escape characters we explicity disallow - case 'x': return parseError("Hex escape not supported"); - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': return parseError("Octal escape not supported"); - // By default pass on the unescaped character - default: result->push_back(*q); break; - // TODO: check for escaped control characters - } - ++q; - } - else { - result->push_back(*q++); - } - } - if (q < _input_end) { - _input = q; - return Status::OK(); +Status JParse::date(StringData fieldName, BSONObjBuilder& builder) { + if (!readToken(LPAREN)) { + return parseError("Expecting '('"); + } + errno = 0; + char* endptr; + // SERVER-11920: We should use parseNumberFromString here, but that function requires that + // we know ahead of time where the number ends, which is not currently the case. + Date_t date = Date_t::fromMillisSinceEpoch(strtoll(_input, &endptr, 10)); + if (_input == endptr) { + return parseError("Date expecting integer milliseconds"); + } + if (errno == ERANGE) { + /* Need to handle this because jsonString outputs the value of Date_t as unsigned. + * See SERVER-8330 and SERVER-8573 */ + errno = 0; + // SERVER-11920: We should use parseNumberFromString here, but that function requires + // that we know ahead of time where the number ends, which is not currently the case. + date = Date_t::fromMillisSinceEpoch(static_cast<long long>(strtoull(_input, &endptr, 10))); + if (errno == ERANGE) { + return parseError("Date milliseconds overflow"); } - return parseError("Unexpected end of input"); } + _input = endptr; + if (!readToken(RPAREN)) { + return parseError("Expecting ')'"); + } + builder.appendDate(fieldName, date); + return Status::OK(); +} - std::string JParse::encodeUTF8(unsigned char first, unsigned char second) const { - std::ostringstream oss; - if (first == 0 && second < 0x80) { - oss << second; - } - else if (first < 0x08) { - oss << char( 0xc0 | (first << 2 | second >> 6) ); - oss << char( 0x80 | (~0xc0 & second) ); - } - else { - oss << char( 0xe0 | (first >> 4) ); - oss << char( 0x80 | (~0xc0 & (first << 2 | second >> 6) ) ); - oss << char( 0x80 | (~0xc0 & second) ); - } - return oss.str(); +Status JParse::timestamp(StringData fieldName, BSONObjBuilder& builder) { + if (!readToken(LPAREN)) { + return parseError("Expecting '('"); } + if (readToken("-")) { + return parseError("Negative seconds in \"$timestamp\""); + } + errno = 0; + char* endptr; + // SERVER-11920: We should use parseNumberFromString here, but that function requires that + // we know ahead of time where the number ends, which is not currently the case. + uint32_t seconds = strtoul(_input, &endptr, 10); + if (errno == ERANGE) { + return parseError("Timestamp seconds overflow"); + } + if (_input == endptr) { + return parseError("Expecting unsigned integer seconds in \"$timestamp\""); + } + _input = endptr; + if (!readToken(COMMA)) { + return parseError("Expecting ','"); + } + if (readToken("-")) { + return parseError("Negative seconds in \"$timestamp\""); + } + errno = 0; + // SERVER-11920: We should use parseNumberFromString here, but that function requires that + // we know ahead of time where the number ends, which is not currently the case. + uint32_t count = strtoul(_input, &endptr, 10); + if (errno == ERANGE) { + return parseError("Timestamp increment overflow"); + } + if (_input == endptr) { + return parseError("Expecting unsigned integer increment in \"$timestamp\""); + } + _input = endptr; + if (!readToken(RPAREN)) { + return parseError("Expecting ')'"); + } + builder.append(fieldName, Timestamp(seconds, count)); + return Status::OK(); +} - inline bool JParse::peekToken(const char* token) { - return readTokenImpl(token, false); +Status JParse::objectId(StringData fieldName, BSONObjBuilder& builder) { + if (!readToken(LPAREN)) { + return parseError("Expecting '('"); + } + std::string id; + id.reserve(ID_RESERVE_SIZE); + Status ret = quotedString(&id); + if (ret != Status::OK()) { + return ret; } + if (!readToken(RPAREN)) { + return parseError("Expecting ')'"); + } + if (id.size() != 24) { + return parseError("Expecting 24 hex digits: " + id); + } + if (!isHexString(id)) { + return parseError("Expecting hex digits: " + id); + } + builder.append(fieldName, OID(id)); + return Status::OK(); +} - inline bool JParse::readToken(const char* token) { - return readTokenImpl(token, true); +Status JParse::numberLong(StringData fieldName, BSONObjBuilder& builder) { + if (!readToken(LPAREN)) { + return parseError("Expecting '('"); + } + errno = 0; + char* endptr; + // SERVER-11920: We should use parseNumberFromString here, but that function requires that + // we know ahead of time where the number ends, which is not currently the case. + int64_t val = strtoll(_input, &endptr, 10); + if (errno == ERANGE) { + return parseError("NumberLong out of range"); + } + if (_input == endptr) { + return parseError("Expecting number in NumberLong"); } + _input = endptr; + if (!readToken(RPAREN)) { + return parseError("Expecting ')'"); + } + builder.appendNumber(fieldName, static_cast<long long int>(val)); + return Status::OK(); +} - bool JParse::readTokenImpl(const char* token, bool advance) { - MONGO_JSON_DEBUG("token: " << token); - const char* check = _input; - if (token == NULL) { - return false; +Status JParse::numberInt(StringData fieldName, BSONObjBuilder& builder) { + if (!readToken(LPAREN)) { + return parseError("Expecting '('"); + } + errno = 0; + char* endptr; + // SERVER-11920: We should use parseNumberFromString here, but that function requires that + // we know ahead of time where the number ends, which is not currently the case. + int32_t val = strtol(_input, &endptr, 10); + if (errno == ERANGE) { + return parseError("NumberInt out of range"); + } + if (_input == endptr) { + return parseError("Expecting unsigned number in NumberInt"); + } + _input = endptr; + if (!readToken(RPAREN)) { + return parseError("Expecting ')'"); + } + builder.appendNumber(fieldName, static_cast<int>(val)); + return Status::OK(); +} + + +Status JParse::dbRef(StringData fieldName, BSONObjBuilder& builder) { + BSONObjBuilder subBuilder(builder.subobjStart(fieldName)); + + if (!readToken(LPAREN)) { + return parseError("Expecting '('"); + } + std::string ns; + ns.reserve(NS_RESERVE_SIZE); + Status refRet = quotedString(&ns); + if (refRet != Status::OK()) { + return refRet; + } + subBuilder.append("$ref", ns); + + if (!readToken(COMMA)) { + return parseError("Expecting ','"); + } + + Status valueRet = value("$id", subBuilder); + if (valueRet != Status::OK()) { + return valueRet; + } + + if (readToken(COMMA)) { + std::string db; + db.reserve(DB_RESERVE_SIZE); + Status dbRet = quotedString(&db); + if (dbRet != Status::OK()) { + return dbRet; + } + subBuilder.append("$db", db); + } + + if (!readToken(RPAREN)) { + return parseError("Expecting ')'"); + } + + subBuilder.done(); + return Status::OK(); +} + +Status JParse::regex(StringData fieldName, BSONObjBuilder& builder) { + if (!readToken(FORWARDSLASH)) { + return parseError("Expecting '/'"); + } + std::string pat; + pat.reserve(PAT_RESERVE_SIZE); + Status patRet = regexPat(&pat); + if (patRet != Status::OK()) { + return patRet; + } + if (!readToken(FORWARDSLASH)) { + return parseError("Expecting '/'"); + } + std::string opt; + opt.reserve(OPT_RESERVE_SIZE); + Status optRet = regexOpt(&opt); + if (optRet != Status::OK()) { + return optRet; + } + Status optCheckRet = regexOptCheck(opt); + if (optCheckRet != Status::OK()) { + return optCheckRet; + } + builder.appendRegex(fieldName, pat, opt); + return Status::OK(); +} + +Status JParse::regexPat(std::string* result) { + MONGO_JSON_DEBUG(""); + return chars(result, "/"); +} + +Status JParse::regexOpt(std::string* result) { + MONGO_JSON_DEBUG(""); + return chars(result, "", JOPTIONS); +} + +Status JParse::regexOptCheck(StringData opt) { + MONGO_JSON_DEBUG("opt: " << opt); + std::size_t i; + for (i = 0; i < opt.size(); i++) { + if (!match(opt[i], JOPTIONS)) { + return parseError(string("Bad regex option: ") + opt[i]); } + } + return Status::OK(); +} + +Status JParse::number(StringData fieldName, BSONObjBuilder& builder) { + char* endptrll; + char* endptrd; + long long retll; + double retd; + + // reset errno to make sure that we are getting it from strtod + errno = 0; + // SERVER-11920: We should use parseNumberFromString here, but that function requires that + // we know ahead of time where the number ends, which is not currently the case. + retd = strtod(_input, &endptrd); + // if pointer does not move, we found no digits + if (_input == endptrd) { + return parseError("Bad characters in value"); + } + if (errno == ERANGE) { + return parseError("Value cannot fit in double"); + } + // reset errno to make sure that we are getting it from strtoll + errno = 0; + // SERVER-11920: We should use parseNumberFromString here, but that function requires that + // we know ahead of time where the number ends, which is not currently the case. + retll = strtoll(_input, &endptrll, 10); + if (endptrll < endptrd || errno == ERANGE) { + // The number either had characters only meaningful for a double or + // could not fit in a 64 bit int + MONGO_JSON_DEBUG("Type: double"); + builder.append(fieldName, retd); + } else if (retll == static_cast<int>(retll)) { + // The number can fit in a 32 bit int + MONGO_JSON_DEBUG("Type: 32 bit int"); + builder.append(fieldName, static_cast<int>(retll)); + } else { + // The number can fit in a 64 bit int + MONGO_JSON_DEBUG("Type: 64 bit int"); + builder.append(fieldName, retll); + } + _input = endptrd; + if (_input >= _input_end) { + return parseError("Trailing number at end of input"); + } + return Status::OK(); +} + +Status JParse::field(std::string* result) { + MONGO_JSON_DEBUG(""); + if (peekToken(DOUBLEQUOTE) || peekToken(SINGLEQUOTE)) { + // Quoted key + // TODO: make sure quoted field names cannot contain null characters + return quotedString(result); + } else { + // Unquoted key // 'isspace()' takes an 'int' (signed), so (default signed) 'char's get sign-extended // and therefore 'corrupted' unless we force them to be unsigned ... 0x80 becomes // 0xffffff80 as seen by isspace when sign-extended ... we want it to be 0x00000080 - while (check < _input_end && isspace(*reinterpret_cast<const unsigned char*>(check))) { - ++check; + while (_input < _input_end && isspace(*reinterpret_cast<const unsigned char*>(_input))) { + ++_input; } - while (*token != '\0') { - if (check >= _input_end) { - return false; - } - if (*token++ != *check++) { - return false; - } + if (_input >= _input_end) { + return parseError("Field name expected"); } - if (advance) { _input = check; } - return true; + if (!match(*_input, ALPHA "_$")) { + return parseError("First character in field must be [A-Za-z$_]"); + } + return chars(result, "", ALPHA DIGIT "_$"); } +} - bool JParse::readField(StringData expectedField) { - MONGO_JSON_DEBUG("expectedField: " << expectedField); - std::string nextField; - nextField.reserve(FIELD_RESERVE_SIZE); - Status ret = field(&nextField); +Status JParse::quotedString(std::string* result) { + MONGO_JSON_DEBUG(""); + if (readToken(DOUBLEQUOTE)) { + Status ret = chars(result, "\""); if (ret != Status::OK()) { - return false; + return ret; } - if (expectedField != nextField) { - return false; + if (!readToken(DOUBLEQUOTE)) { + return parseError("Expecting '\"'"); } - return true; - } - - inline bool JParse::match(char matchChar, const char* matchSet) const { - if (matchSet == NULL) { - return true; + } else if (readToken(SINGLEQUOTE)) { + Status ret = chars(result, "'"); + if (ret != Status::OK()) { + return ret; } - if (*matchSet == '\0') { - return false; + if (!readToken(SINGLEQUOTE)) { + return parseError("Expecting '''"); } - return (strchr(matchSet, matchChar) != NULL); + } else { + return parseError("Expecting quoted string"); } + return Status::OK(); +} - bool JParse::isHexString(StringData str) const { - MONGO_JSON_DEBUG("str: " << str); - std::size_t i; - for (i = 0; i < str.size(); i++) { - if (!isxdigit(str[i])) { - return false; - } - } - return true; +/* + * terminalSet are characters that signal end of string (e.g.) [ :\0] + * allowedSet are the characters that are allowed, if this is set + */ +Status JParse::chars(std::string* result, const char* terminalSet, const char* allowedSet) { + MONGO_JSON_DEBUG("terminalSet: " << terminalSet); + if (_input >= _input_end) { + return parseError("Unexpected end of input"); } - - bool JParse::isBase64String(StringData str) const { - MONGO_JSON_DEBUG("str: " << str); - std::size_t i; - for (i = 0; i < str.size(); i++) { - if (!match(str[i], base64::chars)) { - return false; + const char* q = _input; + while (q < _input_end && !match(*q, terminalSet)) { + MONGO_JSON_DEBUG("q: " << q); + if (allowedSet != NULL) { + if (!match(*q, allowedSet)) { + _input = q; + return Status::OK(); + } + } + if (0x00 <= *q && *q <= 0x1F) { + return parseError("Invalid control character"); + } + if (*q == '\\' && q + 1 < _input_end) { + switch (*(++q)) { + // Escape characters allowed by the JSON spec + case '"': + result->push_back('"'); + break; + case '\'': + result->push_back('\''); + break; + case '\\': + result->push_back('\\'); + break; + case '/': + result->push_back('/'); + break; + case 'b': + result->push_back('\b'); + break; + case 'f': + result->push_back('\f'); + break; + case 'n': + result->push_back('\n'); + break; + case 'r': + result->push_back('\r'); + break; + case 't': + result->push_back('\t'); + break; + case 'u': { // expect 4 hexdigits + // TODO: handle UTF-16 surrogate characters + ++q; + if (q + 4 >= _input_end) { + return parseError("Expecting 4 hex digits"); + } + if (!isHexString(StringData(q, 4))) { + return parseError("Expecting 4 hex digits"); + } + unsigned char first = fromHex(q); + unsigned char second = fromHex(q += 2); + const std::string& utf8str = encodeUTF8(first, second); + for (unsigned int i = 0; i < utf8str.size(); i++) { + result->push_back(utf8str[i]); + } + ++q; + break; + } + // Vertical tab character. Not in JSON spec but allowed in + // our implementation according to test suite. + case 'v': + result->push_back('\v'); + break; + // Escape characters we explicity disallow + case 'x': + return parseError("Hex escape not supported"); + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + return parseError("Octal escape not supported"); + // By default pass on the unescaped character + default: + result->push_back(*q); + break; + // TODO: check for escaped control characters } + ++q; + } else { + result->push_back(*q++); } - return true; } - - bool JParse::isArray() { - return peekToken(LBRACKET); + if (q < _input_end) { + _input = q; + return Status::OK(); } - - BSONObj fromjson(const char* jsonString, int* len) { - MONGO_JSON_DEBUG("jsonString: " << jsonString); - if (jsonString[0] == '\0') { - if (len) *len = 0; - return BSONObj(); - } - JParse jparse(jsonString); - BSONObjBuilder builder; - Status ret = Status::OK(); - try { - ret = jparse.parse(builder); + return parseError("Unexpected end of input"); +} + +std::string JParse::encodeUTF8(unsigned char first, unsigned char second) const { + std::ostringstream oss; + if (first == 0 && second < 0x80) { + oss << second; + } else if (first < 0x08) { + oss << char(0xc0 | (first << 2 | second >> 6)); + oss << char(0x80 | (~0xc0 & second)); + } else { + oss << char(0xe0 | (first >> 4)); + oss << char(0x80 | (~0xc0 & (first << 2 | second >> 6))); + oss << char(0x80 | (~0xc0 & second)); + } + return oss.str(); +} + +inline bool JParse::peekToken(const char* token) { + return readTokenImpl(token, false); +} + +inline bool JParse::readToken(const char* token) { + return readTokenImpl(token, true); +} + +bool JParse::readTokenImpl(const char* token, bool advance) { + MONGO_JSON_DEBUG("token: " << token); + const char* check = _input; + if (token == NULL) { + return false; + } + // 'isspace()' takes an 'int' (signed), so (default signed) 'char's get sign-extended + // and therefore 'corrupted' unless we force them to be unsigned ... 0x80 becomes + // 0xffffff80 as seen by isspace when sign-extended ... we want it to be 0x00000080 + while (check < _input_end && isspace(*reinterpret_cast<const unsigned char*>(check))) { + ++check; + } + while (*token != '\0') { + if (check >= _input_end) { + return false; } - catch(std::exception& e) { - std::ostringstream message; - message << "caught exception from within JSON parser: " << e.what(); - throw MsgAssertionException(17031, message.str()); + if (*token++ != *check++) { + return false; } + } + if (advance) { + _input = check; + } + return true; +} + +bool JParse::readField(StringData expectedField) { + MONGO_JSON_DEBUG("expectedField: " << expectedField); + std::string nextField; + nextField.reserve(FIELD_RESERVE_SIZE); + Status ret = field(&nextField); + if (ret != Status::OK()) { + return false; + } + if (expectedField != nextField) { + return false; + } + return true; +} - if (ret != Status::OK()) { - ostringstream message; - message << "code " << ret.code() << ": " << ret.codeString() << ": " << ret.reason(); - throw MsgAssertionException(16619, message.str()); +inline bool JParse::match(char matchChar, const char* matchSet) const { + if (matchSet == NULL) { + return true; + } + if (*matchSet == '\0') { + return false; + } + return (strchr(matchSet, matchChar) != NULL); +} + +bool JParse::isHexString(StringData str) const { + MONGO_JSON_DEBUG("str: " << str); + std::size_t i; + for (i = 0; i < str.size(); i++) { + if (!isxdigit(str[i])) { + return false; } - if (len) *len = jparse.offset(); - return builder.obj(); } - - BSONObj fromjson(const std::string& str) { - return fromjson( str.c_str() ); + return true; +} + +bool JParse::isBase64String(StringData str) const { + MONGO_JSON_DEBUG("str: " << str); + std::size_t i; + for (i = 0; i < str.size(); i++) { + if (!match(str[i], base64::chars)) { + return false; + } } - - std::string tojson(const BSONObj& obj, JsonStringFormat format, bool pretty) { - return obj.jsonString(format, pretty); + return true; +} + +bool JParse::isArray() { + return peekToken(LBRACKET); +} + +BSONObj fromjson(const char* jsonString, int* len) { + MONGO_JSON_DEBUG("jsonString: " << jsonString); + if (jsonString[0] == '\0') { + if (len) + *len = 0; + return BSONObj(); } - - std::string tojson(const BSONArray& arr, JsonStringFormat format, bool pretty) { - return arr.jsonString(format, pretty, true); + JParse jparse(jsonString); + BSONObjBuilder builder; + Status ret = Status::OK(); + try { + ret = jparse.parse(builder); + } catch (std::exception& e) { + std::ostringstream message; + message << "caught exception from within JSON parser: " << e.what(); + throw MsgAssertionException(17031, message.str()); } - bool isArray(StringData str) { - JParse parser(str); - return parser.isArray(); + if (ret != Status::OK()) { + ostringstream message; + message << "code " << ret.code() << ": " << ret.codeString() << ": " << ret.reason(); + throw MsgAssertionException(16619, message.str()); } + if (len) + *len = jparse.offset(); + return builder.obj(); +} + +BSONObj fromjson(const std::string& str) { + return fromjson(str.c_str()); +} + +std::string tojson(const BSONObj& obj, JsonStringFormat format, bool pretty) { + return obj.jsonString(format, pretty); +} + +std::string tojson(const BSONArray& arr, JsonStringFormat format, bool pretty) { + return arr.jsonString(format, pretty, true); +} + +bool isArray(StringData str) { + JParse parser(str); + return parser.isArray(); +} -} /* namespace mongo */ +} /* namespace mongo */ diff --git a/src/mongo/bson/json.h b/src/mongo/bson/json.h index 34564765242..7a22d1d2186 100644 --- a/src/mongo/bson/json.h +++ b/src/mongo/bson/json.h @@ -35,454 +35,449 @@ namespace mongo { - /** - * Create a BSONObj from a JSON <http://www.json.org>, - * <http://www.ietf.org/rfc/rfc4627.txt> string. In addition to the JSON - * extensions extensions described here - * <http://dochub.mongodb.org/core/mongodbextendedjson>, this function - * accepts unquoted field names and allows single quotes to optionally be - * used when specifying field names and std::string values instead of double - * quotes. JSON unicode escape sequences (of the form \uXXXX) are - * converted to utf8. +/** + * Create a BSONObj from a JSON <http://www.json.org>, + * <http://www.ietf.org/rfc/rfc4627.txt> string. In addition to the JSON + * extensions extensions described here + * <http://dochub.mongodb.org/core/mongodbextendedjson>, this function + * accepts unquoted field names and allows single quotes to optionally be + * used when specifying field names and std::string values instead of double + * quotes. JSON unicode escape sequences (of the form \uXXXX) are + * converted to utf8. + * + * @throws MsgAssertionException if parsing fails. The message included with + * this assertion includes the character offset where parsing failed. + */ +BSONObj fromjson(const std::string& str); + +/** @param len will be size of JSON object in text chars. */ +BSONObj fromjson(const char* str, int* len = NULL); + +/** + * Tests whether the JSON string is an Array. + * + * Useful for assigning the result of fromjson to the right object type. Either: + * BSONObj + * BSONArray + * + * @example Using the method to select the proper type. + * If this method returns true, the user could store the result of fromjson + * inside a BSONArray, rather than a BSONObj, in order to have it print as an + * array when passed to tojson. + * + * @param obj The JSON string to test. + */ +bool isArray(StringData str); + +/** + * Convert a BSONArray to a JSON string. + * + * @param arr The BSON Array. + * @param format The JSON format (JS, TenGen, Strict). + * @param pretty Enables pretty output. + */ +std::string tojson(const BSONArray& arr, JsonStringFormat format = Strict, bool pretty = false); + +/** + * Convert a BSONObj to a JSON string. + * + * @param obj The BSON Object. + * @param format The JSON format (JS, TenGen, Strict). + * @param pretty Enables pretty output. + */ +std::string tojson(const BSONObj& obj, JsonStringFormat format = Strict, bool pretty = false); + +/** + * Parser class. A BSONObj is constructed incrementally by passing a + * BSONObjBuilder to the recursive parsing methods. The grammar for the + * element parsed is described before each function. + */ +class JParse { +public: + explicit JParse(StringData str); + + /* + * Notation: All-uppercase symbols denote non-terminals; all other + * symbols are literals. + */ + + /* + * VALUE : + * STRING + * | NUMBER + * | NUMBERINT + * | NUMBERLONG + * | OBJECT + * | ARRAY * - * @throws MsgAssertionException if parsing fails. The message included with - * this assertion includes the character offset where parsing failed. + * | true + * | false + * | null + * | undefined + * + * | NaN + * | Infinity + * | -Infinity + * + * | DATE + * | TIMESTAMP + * | REGEX + * | OBJECTID + * | DBREF + * + * | new CONSTRUCTOR + */ +private: + Status value(StringData fieldName, BSONObjBuilder&); + + /* + * OBJECT : + * {} + * | { MEMBERS } + * | SPECIALOBJECT + * + * MEMBERS : + * PAIR + * | PAIR , MEMBERS + * + * PAIR : + * FIELD : VALUE + * + * SPECIALOBJECT : + * OIDOBJECT + * | BINARYOBJECT + * | DATEOBJECT + * | TIMESTAMPOBJECT + * | REGEXOBJECT + * | REFOBJECT + * | UNDEFINEDOBJECT + * | NUMBERLONGOBJECT + * | MINKEYOBJECT + * | MAXKEYOBJECT + * + */ +public: + Status object(StringData fieldName, BSONObjBuilder&, bool subObj = true); + Status parse(BSONObjBuilder& builder); + bool isArray(); + +private: + /* The following functions are called with the '{' and the first + * field already parsed since they are both implied given the + * context. */ + /* + * OIDOBJECT : + * { FIELD("$oid") : <24 character hex std::string> } */ - BSONObj fromjson(const std::string& str); + Status objectIdObject(StringData fieldName, BSONObjBuilder&); - /** @param len will be size of JSON object in text chars. */ - BSONObj fromjson(const char* str, int* len=NULL); + /* + * BINARYOBJECT : + * { FIELD("$binary") : <base64 representation of a binary std::string>, + * FIELD("$type") : <hexadecimal representation of a single byte + * indicating the data type> } + */ + Status binaryObject(StringData fieldName, BSONObjBuilder&); - /** - * Tests whether the JSON string is an Array. + /* + * DATEOBJECT : + * { FIELD("$date") : <64 bit signed integer for milliseconds since epoch> } + */ + Status dateObject(StringData fieldName, BSONObjBuilder&); + + /* + * TIMESTAMPOBJECT : + * { FIELD("$timestamp") : { + * FIELD("t") : <32 bit unsigned integer for seconds since epoch>, + * FIELD("i") : <32 bit unsigned integer for the increment> } } + */ + Status timestampObject(StringData fieldName, BSONObjBuilder&); + + /* + * NOTE: the rules for the body of the regex are different here, + * since it is quoted instead of surrounded by slashes. + * REGEXOBJECT : + * { FIELD("$regex") : <string representing body of regex> } + * | { FIELD("$regex") : <string representing body of regex>, + * FIELD("$options") : <string representing regex options> } + */ + Status regexObject(StringData fieldName, BSONObjBuilder&); + + /* + * REFOBJECT : + * { FIELD("$ref") : <string representing collection name>, + * FIELD("$id") : <24 character hex std::string> } + * | { FIELD("$ref") : std::string , FIELD("$id") : OBJECTID } + * | { FIELD("$ref") : std::string , FIELD("$id") : OIDOBJECT } + */ + Status dbRefObject(StringData fieldName, BSONObjBuilder&); + + /* + * UNDEFINEDOBJECT : + * { FIELD("$undefined") : true } + */ + Status undefinedObject(StringData fieldName, BSONObjBuilder&); + + /* + * NUMBERLONGOBJECT : + * { FIELD("$numberLong") : "<number>" } + */ + Status numberLongObject(StringData fieldName, BSONObjBuilder&); + + /* + * MINKEYOBJECT : + * { FIELD("$minKey") : 1 } + */ + Status minKeyObject(StringData fieldName, BSONObjBuilder& builder); + + /* + * MAXKEYOBJECT : + * { FIELD("$maxKey") : 1 } + */ + Status maxKeyObject(StringData fieldName, BSONObjBuilder& builder); + + /* + * ARRAY : + * [] + * | [ ELEMENTS ] + * + * ELEMENTS : + * VALUE + * | VALUE , ELEMENTS + */ + Status array(StringData fieldName, BSONObjBuilder&, bool subObj = true); + + /* + * NOTE: Currently only Date can be preceded by the "new" keyword + * CONSTRUCTOR : + * DATE + */ + Status constructor(StringData fieldName, BSONObjBuilder&); + + /* The following functions only parse the body of the constructor + * between the parentheses, not including the constructor name */ + /* + * DATE : + * Date( <64 bit signed integer for milliseconds since epoch> ) + */ + Status date(StringData fieldName, BSONObjBuilder&); + + /* + * TIMESTAMP : + * Timestamp( <32 bit unsigned integer for seconds since epoch>, + * <32 bit unsigned integer for the increment> ) + */ + Status timestamp(StringData fieldName, BSONObjBuilder&); + + /* + * OBJECTID : + * ObjectId( <24 character hex std::string> ) + */ + Status objectId(StringData fieldName, BSONObjBuilder&); + + /* + * NUMBERLONG : + * NumberLong( <number> ) + */ + Status numberLong(StringData fieldName, BSONObjBuilder&); + + /* + * NUMBERINT : + * NumberInt( <number> ) + */ + Status numberInt(StringData fieldName, BSONObjBuilder&); + + /* + * DBREF : + * Dbref( <namespace std::string> , <24 character hex std::string> ) + */ + Status dbRef(StringData fieldName, BSONObjBuilder&); + + /* + * REGEX : + * / REGEXCHARS / REGEXOPTIONS + * + * REGEXCHARS : + * REGEXCHAR + * | REGEXCHAR REGEXCHARS * - * Useful for assigning the result of fromjson to the right object type. Either: - * BSONObj - * BSONArray + * REGEXCHAR : + * any-Unicode-character-except-/-or-\-or-CONTROLCHAR + * | \" + * | \' + * | \\ + * | \/ + * | \b + * | \f + * | \n + * | \r + * | \t + * | \v + * | \u HEXDIGIT HEXDIGIT HEXDIGIT HEXDIGIT + * | \any-Unicode-character-except-x-or-[0-7] * - * @example Using the method to select the proper type. - * If this method returns true, the user could store the result of fromjson - * inside a BSONArray, rather than a BSONObj, in order to have it print as an - * array when passed to tojson. + * REGEXOPTIONS : + * REGEXOPTION + * | REGEXOPTION REGEXOPTIONS * - * @param obj The JSON string to test. + * REGEXOPTION : + * g | i | m | s */ - bool isArray(StringData str); + Status regex(StringData fieldName, BSONObjBuilder&); + Status regexPat(std::string* result); + Status regexOpt(std::string* result); + Status regexOptCheck(StringData opt); - /** - * Convert a BSONArray to a JSON string. + /* + * NUMBER : * - * @param arr The BSON Array. - * @param format The JSON format (JS, TenGen, Strict). - * @param pretty Enables pretty output. + * NOTE: Number parsing is based on standard library functions, not + * necessarily on the JSON numeric grammar. + * + * Number as value - strtoll and strtod + * Date - strtoll + * Timestamp - strtoul for both timestamp and increment and '-' + * before a number explicity disallowed */ - std::string tojson( - const BSONArray& arr, - JsonStringFormat format = Strict, - bool pretty = false - ); + Status number(StringData fieldName, BSONObjBuilder&); - /** - * Convert a BSONObj to a JSON string. + /* + * FIELD : + * STRING + * | [a-zA-Z$_] FIELDCHARS + * + * FIELDCHARS : + * [a-zA-Z0-9$_] + * | [a-zA-Z0-9$_] FIELDCHARS + */ + Status field(std::string* result); + + /* + * std::string : + * " " + * | ' ' + * | " CHARS " + * | ' CHARS ' + */ + Status quotedString(std::string* result); + + /* + * CHARS : + * CHAR + * | CHAR CHARS + * + * Note: " or ' may be allowed depending on whether the std::string is + * double or single quoted + * + * CHAR : + * any-Unicode-character-except-"-or-'-or-\-or-CONTROLCHAR + * | \" + * | \' + * | \\ + * | \/ + * | \b + * | \f + * | \n + * | \r + * | \t + * | \v + * | \u HEXDIGIT HEXDIGIT HEXDIGIT HEXDIGIT + * | \any-Unicode-character-except-x-or-[0-9] + * + * HEXDIGIT : [0..9a..fA..F] * - * @param obj The BSON Object. - * @param format The JSON format (JS, TenGen, Strict). - * @param pretty Enables pretty output. + * per http://www.ietf.org/rfc/rfc4627.txt, control characters are + * (U+0000 through U+001F). U+007F is not mentioned as a control + * character. + * CONTROLCHAR : [0x00..0x1F] + * + * If there is not an error, result will contain a null terminated + * string, but there is no guarantee that it will not contain other + * null characters. + */ + Status chars(std::string* result, const char* terminalSet, const char* allowedSet = NULL); + + /** + * Converts the two byte Unicode code point to its UTF8 character + * encoding representation. This function returns a std::string because + * UTF8 encodings for code points from 0x0000 to 0xFFFF can range + * from one to three characters. */ - std::string tojson( - const BSONObj& obj, - JsonStringFormat format = Strict, - bool pretty = false - ); + std::string encodeUTF8(unsigned char first, unsigned char second) const; /** - * Parser class. A BSONObj is constructed incrementally by passing a - * BSONObjBuilder to the recursive parsing methods. The grammar for the - * element parsed is described before each function. - */ - class JParse { - public: - explicit JParse(StringData str); - - /* - * Notation: All-uppercase symbols denote non-terminals; all other - * symbols are literals. - */ - - /* - * VALUE : - * STRING - * | NUMBER - * | NUMBERINT - * | NUMBERLONG - * | OBJECT - * | ARRAY - * - * | true - * | false - * | null - * | undefined - * - * | NaN - * | Infinity - * | -Infinity - * - * | DATE - * | TIMESTAMP - * | REGEX - * | OBJECTID - * | DBREF - * - * | new CONSTRUCTOR - */ - private: - Status value(StringData fieldName, BSONObjBuilder&); - - /* - * OBJECT : - * {} - * | { MEMBERS } - * | SPECIALOBJECT - * - * MEMBERS : - * PAIR - * | PAIR , MEMBERS - * - * PAIR : - * FIELD : VALUE - * - * SPECIALOBJECT : - * OIDOBJECT - * | BINARYOBJECT - * | DATEOBJECT - * | TIMESTAMPOBJECT - * | REGEXOBJECT - * | REFOBJECT - * | UNDEFINEDOBJECT - * | NUMBERLONGOBJECT - * | MINKEYOBJECT - * | MAXKEYOBJECT - * - */ - public: - Status object(StringData fieldName, BSONObjBuilder&, bool subObj=true); - Status parse(BSONObjBuilder& builder); - bool isArray(); - - private: - /* The following functions are called with the '{' and the first - * field already parsed since they are both implied given the - * context. */ - /* - * OIDOBJECT : - * { FIELD("$oid") : <24 character hex std::string> } - */ - Status objectIdObject(StringData fieldName, BSONObjBuilder&); - - /* - * BINARYOBJECT : - * { FIELD("$binary") : <base64 representation of a binary std::string>, - * FIELD("$type") : <hexadecimal representation of a single byte - * indicating the data type> } - */ - Status binaryObject(StringData fieldName, BSONObjBuilder&); - - /* - * DATEOBJECT : - * { FIELD("$date") : <64 bit signed integer for milliseconds since epoch> } - */ - Status dateObject(StringData fieldName, BSONObjBuilder&); - - /* - * TIMESTAMPOBJECT : - * { FIELD("$timestamp") : { - * FIELD("t") : <32 bit unsigned integer for seconds since epoch>, - * FIELD("i") : <32 bit unsigned integer for the increment> } } - */ - Status timestampObject(StringData fieldName, BSONObjBuilder&); - - /* - * NOTE: the rules for the body of the regex are different here, - * since it is quoted instead of surrounded by slashes. - * REGEXOBJECT : - * { FIELD("$regex") : <string representing body of regex> } - * | { FIELD("$regex") : <string representing body of regex>, - * FIELD("$options") : <string representing regex options> } - */ - Status regexObject(StringData fieldName, BSONObjBuilder&); - - /* - * REFOBJECT : - * { FIELD("$ref") : <string representing collection name>, - * FIELD("$id") : <24 character hex std::string> } - * | { FIELD("$ref") : std::string , FIELD("$id") : OBJECTID } - * | { FIELD("$ref") : std::string , FIELD("$id") : OIDOBJECT } - */ - Status dbRefObject(StringData fieldName, BSONObjBuilder&); - - /* - * UNDEFINEDOBJECT : - * { FIELD("$undefined") : true } - */ - Status undefinedObject(StringData fieldName, BSONObjBuilder&); - - /* - * NUMBERLONGOBJECT : - * { FIELD("$numberLong") : "<number>" } - */ - Status numberLongObject(StringData fieldName, BSONObjBuilder&); - - /* - * MINKEYOBJECT : - * { FIELD("$minKey") : 1 } - */ - Status minKeyObject(StringData fieldName, BSONObjBuilder& builder); - - /* - * MAXKEYOBJECT : - * { FIELD("$maxKey") : 1 } - */ - Status maxKeyObject(StringData fieldName, BSONObjBuilder& builder); - - /* - * ARRAY : - * [] - * | [ ELEMENTS ] - * - * ELEMENTS : - * VALUE - * | VALUE , ELEMENTS - */ - Status array(StringData fieldName, BSONObjBuilder&, bool subObj=true); - - /* - * NOTE: Currently only Date can be preceded by the "new" keyword - * CONSTRUCTOR : - * DATE - */ - Status constructor(StringData fieldName, BSONObjBuilder&); - - /* The following functions only parse the body of the constructor - * between the parentheses, not including the constructor name */ - /* - * DATE : - * Date( <64 bit signed integer for milliseconds since epoch> ) - */ - Status date(StringData fieldName, BSONObjBuilder&); - - /* - * TIMESTAMP : - * Timestamp( <32 bit unsigned integer for seconds since epoch>, - * <32 bit unsigned integer for the increment> ) - */ - Status timestamp(StringData fieldName, BSONObjBuilder&); - - /* - * OBJECTID : - * ObjectId( <24 character hex std::string> ) - */ - Status objectId(StringData fieldName, BSONObjBuilder&); - - /* - * NUMBERLONG : - * NumberLong( <number> ) - */ - Status numberLong(StringData fieldName, BSONObjBuilder&); - - /* - * NUMBERINT : - * NumberInt( <number> ) - */ - Status numberInt(StringData fieldName, BSONObjBuilder&); - - /* - * DBREF : - * Dbref( <namespace std::string> , <24 character hex std::string> ) - */ - Status dbRef(StringData fieldName, BSONObjBuilder&); - - /* - * REGEX : - * / REGEXCHARS / REGEXOPTIONS - * - * REGEXCHARS : - * REGEXCHAR - * | REGEXCHAR REGEXCHARS - * - * REGEXCHAR : - * any-Unicode-character-except-/-or-\-or-CONTROLCHAR - * | \" - * | \' - * | \\ - * | \/ - * | \b - * | \f - * | \n - * | \r - * | \t - * | \v - * | \u HEXDIGIT HEXDIGIT HEXDIGIT HEXDIGIT - * | \any-Unicode-character-except-x-or-[0-7] - * - * REGEXOPTIONS : - * REGEXOPTION - * | REGEXOPTION REGEXOPTIONS - * - * REGEXOPTION : - * g | i | m | s - */ - Status regex(StringData fieldName, BSONObjBuilder&); - Status regexPat(std::string* result); - Status regexOpt(std::string* result); - Status regexOptCheck(StringData opt); - - /* - * NUMBER : - * - * NOTE: Number parsing is based on standard library functions, not - * necessarily on the JSON numeric grammar. - * - * Number as value - strtoll and strtod - * Date - strtoll - * Timestamp - strtoul for both timestamp and increment and '-' - * before a number explicity disallowed - */ - Status number(StringData fieldName, BSONObjBuilder&); - - /* - * FIELD : - * STRING - * | [a-zA-Z$_] FIELDCHARS - * - * FIELDCHARS : - * [a-zA-Z0-9$_] - * | [a-zA-Z0-9$_] FIELDCHARS - */ - Status field(std::string* result); - - /* - * std::string : - * " " - * | ' ' - * | " CHARS " - * | ' CHARS ' - */ - Status quotedString(std::string* result); - - /* - * CHARS : - * CHAR - * | CHAR CHARS - * - * Note: " or ' may be allowed depending on whether the std::string is - * double or single quoted - * - * CHAR : - * any-Unicode-character-except-"-or-'-or-\-or-CONTROLCHAR - * | \" - * | \' - * | \\ - * | \/ - * | \b - * | \f - * | \n - * | \r - * | \t - * | \v - * | \u HEXDIGIT HEXDIGIT HEXDIGIT HEXDIGIT - * | \any-Unicode-character-except-x-or-[0-9] - * - * HEXDIGIT : [0..9a..fA..F] - * - * per http://www.ietf.org/rfc/rfc4627.txt, control characters are - * (U+0000 through U+001F). U+007F is not mentioned as a control - * character. - * CONTROLCHAR : [0x00..0x1F] - * - * If there is not an error, result will contain a null terminated - * string, but there is no guarantee that it will not contain other - * null characters. - */ - Status chars(std::string* result, const char* terminalSet, const char* allowedSet=NULL); - - /** - * Converts the two byte Unicode code point to its UTF8 character - * encoding representation. This function returns a std::string because - * UTF8 encodings for code points from 0x0000 to 0xFFFF can range - * from one to three characters. - */ - std::string encodeUTF8(unsigned char first, unsigned char second) const; - - /** - * @return true if the given token matches the next non whitespace - * sequence in our buffer, and false if the token doesn't match or - * we reach the end of our buffer. Do not update the pointer to our - * buffer (same as calling readTokenImpl with advance=false). - */ - inline bool peekToken(const char* token); - - /** - * @return true if the given token matches the next non whitespace - * sequence in our buffer, and false if the token doesn't match or - * we reach the end of our buffer. Updates the pointer to our - * buffer (same as calling readTokenImpl with advance=true). - */ - inline bool readToken(const char* token); - - /** - * @return true if the given token matches the next non whitespace - * sequence in our buffer, and false if the token doesn't match or - * we reach the end of our buffer. Do not update the pointer to our - * buffer if advance is false. - */ - bool readTokenImpl(const char* token, bool advance=true); - - /** - * @return true if the next field in our stream matches field. - * Handles single quoted, double quoted, and unquoted field names - */ - bool readField(StringData field); - - /** - * @return true if matchChar is in matchSet - * @return true if matchSet is NULL and false if it is an empty string - */ - bool match(char matchChar, const char* matchSet) const; - - /** - * @return true if every character in the std::string is a hex digit - */ - bool isHexString(StringData) const; - - /** - * @return true if every character in the std::string is a valid base64 - * character - */ - bool isBase64String(StringData) const; - - /** - * @return FailedToParse status with the given message and some - * additional context information - */ - Status parseError(StringData msg); - public: - inline int offset() { return (_input - _buf); } - - private: - /* - * _buf - start of our input buffer - * _input - cursor we advance in our input buffer - * _input_end - sentinel for the end of our input buffer - * - * _buf is the null terminated buffer containing the JSON std::string we - * are parsing. _input_end points to the null byte at the end of - * the buffer. strtoll, strtol, and strtod will access the null - * byte at the end of the buffer because they are assuming a c-style - * string. - */ - const char* const _buf; - const char* _input; - const char* const _input_end; - }; - -} // namespace mongo + * @return true if the given token matches the next non whitespace + * sequence in our buffer, and false if the token doesn't match or + * we reach the end of our buffer. Do not update the pointer to our + * buffer (same as calling readTokenImpl with advance=false). + */ + inline bool peekToken(const char* token); + + /** + * @return true if the given token matches the next non whitespace + * sequence in our buffer, and false if the token doesn't match or + * we reach the end of our buffer. Updates the pointer to our + * buffer (same as calling readTokenImpl with advance=true). + */ + inline bool readToken(const char* token); + + /** + * @return true if the given token matches the next non whitespace + * sequence in our buffer, and false if the token doesn't match or + * we reach the end of our buffer. Do not update the pointer to our + * buffer if advance is false. + */ + bool readTokenImpl(const char* token, bool advance = true); + + /** + * @return true if the next field in our stream matches field. + * Handles single quoted, double quoted, and unquoted field names + */ + bool readField(StringData field); + + /** + * @return true if matchChar is in matchSet + * @return true if matchSet is NULL and false if it is an empty string + */ + bool match(char matchChar, const char* matchSet) const; + + /** + * @return true if every character in the std::string is a hex digit + */ + bool isHexString(StringData) const; + + /** + * @return true if every character in the std::string is a valid base64 + * character + */ + bool isBase64String(StringData) const; + + /** + * @return FailedToParse status with the given message and some + * additional context information + */ + Status parseError(StringData msg); + +public: + inline int offset() { + return (_input - _buf); + } + +private: + /* + * _buf - start of our input buffer + * _input - cursor we advance in our input buffer + * _input_end - sentinel for the end of our input buffer + * + * _buf is the null terminated buffer containing the JSON std::string we + * are parsing. _input_end points to the null byte at the end of + * the buffer. strtoll, strtol, and strtod will access the null + * byte at the end of the buffer because they are assuming a c-style + * string. + */ + const char* const _buf; + const char* _input; + const char* const _input_end; +}; + +} // namespace mongo diff --git a/src/mongo/bson/mutable/algorithm.h b/src/mongo/bson/mutable/algorithm.h index 8646e429a0b..66cec29956a 100644 --- a/src/mongo/bson/mutable/algorithm.h +++ b/src/mongo/bson/mutable/algorithm.h @@ -38,264 +38,255 @@ namespace mongo { namespace mutablebson { - /** For an overview of mutable BSON, please see the file document.h in this directory. - * - * This file defines, in analogy with <algorithm>, a collection of useful algorithms for - * use with mutable BSON classes. In particular, algorithms for searching, sorting, - * indexed access, and counting are included. - */ - - /** 'findElement' searches rightward among the sibiling Elements of 'first', returning an - * Element representing the first item matching the predicate 'predicate'. If no Element - * matches, then the 'ok' method on the returned Element will return false. - */ - template<typename ElementType, typename Predicate> - inline ElementType findElement(ElementType first, Predicate predicate) { - while (first.ok() && !predicate(first)) - first = first.rightSibling(); - return first; - } - - /** A predicate for findElement that matches on the field name of Elements. */ - struct FieldNameEquals { - // The lifetime of this object must be a subset of the lifetime of 'fieldName'. - explicit FieldNameEquals(StringData fieldName) - : fieldName(fieldName) {} +/** For an overview of mutable BSON, please see the file document.h in this directory. + * + * This file defines, in analogy with <algorithm>, a collection of useful algorithms for + * use with mutable BSON classes. In particular, algorithms for searching, sorting, + * indexed access, and counting are included. + */ - bool operator()(const ConstElement& element) const { - return (fieldName == element.getFieldName()); - } +/** 'findElement' searches rightward among the sibiling Elements of 'first', returning an + * Element representing the first item matching the predicate 'predicate'. If no Element + * matches, then the 'ok' method on the returned Element will return false. + */ +template <typename ElementType, typename Predicate> +inline ElementType findElement(ElementType first, Predicate predicate) { + while (first.ok() && !predicate(first)) + first = first.rightSibling(); + return first; +} + +/** A predicate for findElement that matches on the field name of Elements. */ +struct FieldNameEquals { + // The lifetime of this object must be a subset of the lifetime of 'fieldName'. + explicit FieldNameEquals(StringData fieldName) : fieldName(fieldName) {} + + bool operator()(const ConstElement& element) const { + return (fieldName == element.getFieldName()); + } - StringData fieldName; - }; + StringData fieldName; +}; - /** An overload of findElement that delegates to the special implementation - * Element::findElementNamed to reduce traffic across the Element API. - */ - template<typename ElementType> - inline ElementType findElement(ElementType first, FieldNameEquals predicate) { - return first.ok() ? first.findElementNamed(predicate.fieldName) : first; - } +/** An overload of findElement that delegates to the special implementation + * Element::findElementNamed to reduce traffic across the Element API. + */ +template <typename ElementType> +inline ElementType findElement(ElementType first, FieldNameEquals predicate) { + return first.ok() ? first.findElementNamed(predicate.fieldName) : first; +} + +/** A convenience wrapper around findElement<ElementType, FieldNameEquals>. */ +template <typename ElementType> +inline ElementType findElementNamed(ElementType first, StringData fieldName) { + return findElement(first, FieldNameEquals(fieldName)); +} + +/** Finds the first child under 'parent' that matches the given predicate. If no such child + * Element is found, the returned Element's 'ok' method will return false. + */ +template <typename ElementType, typename Predicate> +inline ElementType findFirstChild(ElementType parent, Predicate predicate) { + return findElement(parent.leftchild(), predicate); +} - /** A convenience wrapper around findElement<ElementType, FieldNameEquals>. */ - template<typename ElementType> - inline ElementType findElementNamed(ElementType first, StringData fieldName) { - return findElement(first, FieldNameEquals(fieldName)); - } +/** An overload of findFirstChild that delegates to the special implementation + * Element::findFirstChildNamed to reduce traffic across the Element API. + */ +template <typename ElementType> +inline ElementType findFirstChild(ElementType parent, FieldNameEquals predicate) { + return parent.ok() ? parent.findFirstChildNamed(predicate.fieldName) : parent; +} - /** Finds the first child under 'parent' that matches the given predicate. If no such child - * Element is found, the returned Element's 'ok' method will return false. - */ - template<typename ElementType, typename Predicate> - inline ElementType findFirstChild(ElementType parent, Predicate predicate) { - return findElement(parent.leftchild(), predicate); +/** Finds the first child under 'parent' that matches the given field name. If no such child + * Element is found, the returned Element's 'ok' method will return false. + */ +template <typename ElementType> +inline ElementType findFirstChildNamed(ElementType parent, StringData fieldName) { + return findFirstChild(parent, FieldNameEquals(fieldName)); +} + +/** A less-than ordering for Elements that compares based on the Element field names. */ +class FieldNameLessThan { + // TODO: This should possibly derive from std::binary_function. +public: + inline bool operator()(const ConstElement& left, const ConstElement& right) const { + return left.getFieldName() < right.getFieldName(); } +}; - /** An overload of findFirstChild that delegates to the special implementation - * Element::findFirstChildNamed to reduce traffic across the Element API. - */ - template<typename ElementType> - inline ElementType findFirstChild(ElementType parent, FieldNameEquals predicate) { - return parent.ok() ? parent.findFirstChildNamed(predicate.fieldName) : parent; +/** Sort any children of Element 'parent' by way of Comparator 'comp', which should provide + * an operator() that takes two const Element&'s and implements a strict weak ordering. + */ +template <typename Comparator> +void sortChildren(Element parent, Comparator comp) { + // TODO: The following works, but obviously is not ideal. + + // First, build a vector of the children. + std::vector<Element> children; + Element current = parent.leftChild(); + while (current.ok()) { + children.push_back(current); + current = current.rightSibling(); } - /** Finds the first child under 'parent' that matches the given field name. If no such child - * Element is found, the returned Element's 'ok' method will return false. - */ - template<typename ElementType> - inline ElementType findFirstChildNamed(ElementType parent, StringData fieldName) { - return findFirstChild(parent, FieldNameEquals(fieldName)); + // Then, sort the child vector with our comparator. + std::sort(children.begin(), children.end(), comp); + + // Finally, reorder the children of parent according to the ordering established in + // 'children'. + std::vector<Element>::iterator where = children.begin(); + const std::vector<Element>::iterator end = children.end(); + for (; where != end; ++where) { + // Detach from its current location. + where->remove(); + // Make it the new rightmost element. + parent.pushBack(*where); } +} - /** A less-than ordering for Elements that compares based on the Element field names. */ - class FieldNameLessThan { - // TODO: This should possibly derive from std::binary_function. - public: - inline bool operator()(const ConstElement& left, const ConstElement& right) const { - return left.getFieldName() < right.getFieldName(); - } - }; - - /** Sort any children of Element 'parent' by way of Comparator 'comp', which should provide - * an operator() that takes two const Element&'s and implements a strict weak ordering. - */ - template<typename Comparator> - void sortChildren(Element parent, Comparator comp) { - // TODO: The following works, but obviously is not ideal. - - // First, build a vector of the children. - std::vector<Element> children; - Element current = parent.leftChild(); - while (current.ok()) { - children.push_back(current); - current = current.rightSibling(); - } - - // Then, sort the child vector with our comparator. - std::sort(children.begin(), children.end(), comp); - - // Finally, reorder the children of parent according to the ordering established in - // 'children'. - std::vector<Element>::iterator where = children.begin(); - const std::vector<Element>::iterator end = children.end(); - for( ; where != end; ++where ) { - // Detach from its current location. - where->remove(); - // Make it the new rightmost element. - parent.pushBack(*where); - } - } - - /** Remove any consecutive children that compare as identical according to 'comp'. The - * children must be sorted (see sortChildren, above), and the equality comparator here - * must be compatible with the comparator used for the sort. - */ - template<typename EqualityComparator> - void deduplicateChildren(Element parent, EqualityComparator equal) { - Element current = parent.leftChild(); - while (current.ok()) { - Element next = current.rightSibling(); - if (next.ok() && equal(current, next)) { - next.remove(); - } else { - current = next; - } +/** Remove any consecutive children that compare as identical according to 'comp'. The + * children must be sorted (see sortChildren, above), and the equality comparator here + * must be compatible with the comparator used for the sort. + */ +template <typename EqualityComparator> +void deduplicateChildren(Element parent, EqualityComparator equal) { + Element current = parent.leftChild(); + while (current.ok()) { + Element next = current.rightSibling(); + if (next.ok() && equal(current, next)) { + next.remove(); + } else { + current = next; } } +} - /** A less-than ordering for Elements that compares based on woCompare */ - class woLess { - // TODO: This should possibly derive from std::binary_function. - public: - woLess(bool considerFieldName = true) - : _considerFieldName(considerFieldName) { - } +/** A less-than ordering for Elements that compares based on woCompare */ +class woLess { + // TODO: This should possibly derive from std::binary_function. +public: + woLess(bool considerFieldName = true) : _considerFieldName(considerFieldName) {} - inline bool operator()(const ConstElement& left, const ConstElement& right) const { - return left.compareWithElement(right, _considerFieldName) < 0; - } - private: - const bool _considerFieldName; - }; - - /** A greater-than ordering for Elements that compares based on woCompare */ - class woGreater { - // TODO: This should possibly derive from std::binary_function. - public: - woGreater(bool considerFieldName = true) - : _considerFieldName(considerFieldName) { - } + inline bool operator()(const ConstElement& left, const ConstElement& right) const { + return left.compareWithElement(right, _considerFieldName) < 0; + } - inline bool operator()(const ConstElement& left, const ConstElement& right) const { - return left.compareWithElement(right, _considerFieldName) > 0; - } - private: - const bool _considerFieldName; - }; - - /** An equality predicate for elements that compares based on woCompare */ - class woEqual { - // TODO: This should possibly derive from std::binary_function. - public: - woEqual(bool considerFieldName = true) - : _considerFieldName(considerFieldName) { - } +private: + const bool _considerFieldName; +}; - inline bool operator()(const ConstElement& left, const ConstElement& right) const { - return left.compareWithElement(right, _considerFieldName) == 0; - } - private: - const bool _considerFieldName; - }; - - /** An equality predicate for elements that compares based on woCompare */ - class woEqualTo { - // TODO: This should possibly derive from std::binary_function. - public: - woEqualTo(const ConstElement& value, bool considerFieldName = true) - : _value(value) - , _considerFieldName(considerFieldName) { - } +/** A greater-than ordering for Elements that compares based on woCompare */ +class woGreater { + // TODO: This should possibly derive from std::binary_function. +public: + woGreater(bool considerFieldName = true) : _considerFieldName(considerFieldName) {} - inline bool operator()(const ConstElement& elt) const { - return _value.compareWithElement(elt, _considerFieldName) == 0; - } - private: - const ConstElement& _value; - const bool _considerFieldName; - }; - - // NOTE: Originally, these truly were algorithms, in that they executed the loop over a - // generic ElementType. However, these operations were later made intrinsic to - // Element/Document for performance reasons. These functions hare here for backward - // compatibility, and just delegate to the appropriate Element or ConstElement method of - // the same name. - - /** Return the element that is 'n' Elements to the left in the sibling chain of 'element'. */ - template<typename ElementType> - ElementType getNthLeftSibling(ElementType element, std::size_t n) { - return element.leftSibling(n); + inline bool operator()(const ConstElement& left, const ConstElement& right) const { + return left.compareWithElement(right, _considerFieldName) > 0; } - /** Return the element that is 'n' Elements to the right in the sibling chain of 'element'. */ - template<typename ElementType> - ElementType getNthRightSibling(ElementType element, std::size_t n) { - return element.rightSibling(n); - } +private: + const bool _considerFieldName; +}; - /** Move 'n' Elements left or right in the sibling chain of 'element' */ - template<typename ElementType> - ElementType getNthSibling(ElementType element, int n) { - return (n < 0) ? - getNthLeftSibling(element, -n) : - getNthRightSibling(element, n); - } +/** An equality predicate for elements that compares based on woCompare */ +class woEqual { + // TODO: This should possibly derive from std::binary_function. +public: + woEqual(bool considerFieldName = true) : _considerFieldName(considerFieldName) {} - /** Get the child that is 'n' Elements to the right of 'element's left child. */ - template<typename ElementType> - ElementType getNthChild(ElementType element, std::size_t n) { - return element.findNthChild(n); + inline bool operator()(const ConstElement& left, const ConstElement& right) const { + return left.compareWithElement(right, _considerFieldName) == 0; } - /** Returns the number of valid siblings to the left of 'element'. */ - template<typename ElementType> - std::size_t countSiblingsLeft(ElementType element) { - return element.countSiblingsLeft(); - } +private: + const bool _considerFieldName; +}; - /** Returns the number of valid siblings to the right of 'element'. */ - template<typename ElementType> - std::size_t countSiblingsRight(ElementType element) { - return element.countSiblingsRight(); - } +/** An equality predicate for elements that compares based on woCompare */ +class woEqualTo { + // TODO: This should possibly derive from std::binary_function. +public: + woEqualTo(const ConstElement& value, bool considerFieldName = true) + : _value(value), _considerFieldName(considerFieldName) {} - /** Return the number of children of 'element'. */ - template<typename ElementType> - std::size_t countChildren(ElementType element) { - return element.countChildren(); + inline bool operator()(const ConstElement& elt) const { + return _value.compareWithElement(elt, _considerFieldName) == 0; } - /** Return the full (path) name of this element separating each name with the delim string. */ - template<typename ElementType> - std::string getFullName(ElementType element, char delim = '.') { - std::vector<StringData> names; - ElementType curr = element; - while(curr.ok() && curr.parent().ok()) { - names.push_back(curr.getFieldName()); - curr = curr.parent(); - } +private: + const ConstElement& _value; + const bool _considerFieldName; +}; + +// NOTE: Originally, these truly were algorithms, in that they executed the loop over a +// generic ElementType. However, these operations were later made intrinsic to +// Element/Document for performance reasons. These functions hare here for backward +// compatibility, and just delegate to the appropriate Element or ConstElement method of +// the same name. + +/** Return the element that is 'n' Elements to the left in the sibling chain of 'element'. */ +template <typename ElementType> +ElementType getNthLeftSibling(ElementType element, std::size_t n) { + return element.leftSibling(n); +} + +/** Return the element that is 'n' Elements to the right in the sibling chain of 'element'. */ +template <typename ElementType> +ElementType getNthRightSibling(ElementType element, std::size_t n) { + return element.rightSibling(n); +} + +/** Move 'n' Elements left or right in the sibling chain of 'element' */ +template <typename ElementType> +ElementType getNthSibling(ElementType element, int n) { + return (n < 0) ? getNthLeftSibling(element, -n) : getNthRightSibling(element, n); +} + +/** Get the child that is 'n' Elements to the right of 'element's left child. */ +template <typename ElementType> +ElementType getNthChild(ElementType element, std::size_t n) { + return element.findNthChild(n); +} + +/** Returns the number of valid siblings to the left of 'element'. */ +template <typename ElementType> +std::size_t countSiblingsLeft(ElementType element) { + return element.countSiblingsLeft(); +} + +/** Returns the number of valid siblings to the right of 'element'. */ +template <typename ElementType> +std::size_t countSiblingsRight(ElementType element) { + return element.countSiblingsRight(); +} + +/** Return the number of children of 'element'. */ +template <typename ElementType> +std::size_t countChildren(ElementType element) { + return element.countChildren(); +} + +/** Return the full (path) name of this element separating each name with the delim string. */ +template <typename ElementType> +std::string getFullName(ElementType element, char delim = '.') { + std::vector<StringData> names; + ElementType curr = element; + while (curr.ok() && curr.parent().ok()) { + names.push_back(curr.getFieldName()); + curr = curr.parent(); + } - mongoutils::str::stream name; - bool first = true; - for(std::vector<StringData>::reverse_iterator it = names.rbegin(); - it != names.rend(); - ++it) { - if (!first) - name << delim; - name << *it; - first = false; - } - return name; + mongoutils::str::stream name; + bool first = true; + for (std::vector<StringData>::reverse_iterator it = names.rbegin(); it != names.rend(); ++it) { + if (!first) + name << delim; + name << *it; + first = false; } -} // namespace mutablebson -} // namespace mongo + return name; +} +} // namespace mutablebson +} // namespace mongo diff --git a/src/mongo/bson/mutable/const_element-inl.h b/src/mongo/bson/mutable/const_element-inl.h index af059fd37a2..562a47c16a0 100644 --- a/src/mongo/bson/mutable/const_element-inl.h +++ b/src/mongo/bson/mutable/const_element-inl.h @@ -30,212 +30,210 @@ namespace mongo { namespace mutablebson { - inline ConstElement::ConstElement(const Element& basis) - : _basis(basis) {} +inline ConstElement::ConstElement(const Element& basis) : _basis(basis) {} - inline ConstElement ConstElement::leftChild() const { - return _basis.leftChild(); - } +inline ConstElement ConstElement::leftChild() const { + return _basis.leftChild(); +} - inline ConstElement ConstElement::rightChild() const { - return _basis.rightChild(); - } +inline ConstElement ConstElement::rightChild() const { + return _basis.rightChild(); +} - inline bool ConstElement::hasChildren() const { - return _basis.hasChildren(); - } +inline bool ConstElement::hasChildren() const { + return _basis.hasChildren(); +} - inline ConstElement ConstElement::leftSibling(size_t distance) const { - return _basis.leftSibling(distance); - } +inline ConstElement ConstElement::leftSibling(size_t distance) const { + return _basis.leftSibling(distance); +} - inline ConstElement ConstElement::rightSibling(size_t distance) const { - return _basis.rightSibling(distance); - } +inline ConstElement ConstElement::rightSibling(size_t distance) const { + return _basis.rightSibling(distance); +} - inline ConstElement ConstElement::parent() const { - return _basis.parent(); - } +inline ConstElement ConstElement::parent() const { + return _basis.parent(); +} - inline ConstElement ConstElement::findNthChild(size_t n) const { - return _basis.findNthChild(n); - } +inline ConstElement ConstElement::findNthChild(size_t n) const { + return _basis.findNthChild(n); +} - inline ConstElement ConstElement::operator[](size_t n) const { - return _basis[n]; - } +inline ConstElement ConstElement::operator[](size_t n) const { + return _basis[n]; +} - inline ConstElement ConstElement::findFirstChildNamed(StringData name) const { - return _basis.findFirstChildNamed(name); - } +inline ConstElement ConstElement::findFirstChildNamed(StringData name) const { + return _basis.findFirstChildNamed(name); +} - inline ConstElement ConstElement::operator[](StringData name) const { - return _basis[name]; - } +inline ConstElement ConstElement::operator[](StringData name) const { + return _basis[name]; +} - inline ConstElement ConstElement::findElementNamed(StringData name) const { - return _basis.findElementNamed(name); - } +inline ConstElement ConstElement::findElementNamed(StringData name) const { + return _basis.findElementNamed(name); +} - inline size_t ConstElement::countSiblingsLeft() const { - return _basis.countSiblingsLeft(); - } +inline size_t ConstElement::countSiblingsLeft() const { + return _basis.countSiblingsLeft(); +} - inline size_t ConstElement::countSiblingsRight() const { - return _basis.countSiblingsRight(); - } +inline size_t ConstElement::countSiblingsRight() const { + return _basis.countSiblingsRight(); +} - inline size_t ConstElement::countChildren() const { - return _basis.countChildren(); - } +inline size_t ConstElement::countChildren() const { + return _basis.countChildren(); +} - inline bool ConstElement::hasValue() const { - return _basis.hasValue(); - } +inline bool ConstElement::hasValue() const { + return _basis.hasValue(); +} - inline const BSONElement ConstElement::getValue() const { - return _basis.getValue(); - } +inline const BSONElement ConstElement::getValue() const { + return _basis.getValue(); +} - inline double ConstElement::getValueDouble() const { - return _basis.getValueDouble(); - } +inline double ConstElement::getValueDouble() const { + return _basis.getValueDouble(); +} - inline StringData ConstElement::getValueString() const { - return _basis.getValueString(); - } +inline StringData ConstElement::getValueString() const { + return _basis.getValueString(); +} - inline BSONObj ConstElement::getValueObject() const { - return _basis.getValueObject(); - } +inline BSONObj ConstElement::getValueObject() const { + return _basis.getValueObject(); +} - inline BSONArray ConstElement::getValueArray() const { - return _basis.getValueArray(); - } +inline BSONArray ConstElement::getValueArray() const { + return _basis.getValueArray(); +} - inline bool ConstElement::isValueUndefined() const { - return _basis.isValueUndefined(); - } +inline bool ConstElement::isValueUndefined() const { + return _basis.isValueUndefined(); +} - inline OID ConstElement::getValueOID() const { - return _basis.getValueOID(); - } +inline OID ConstElement::getValueOID() const { + return _basis.getValueOID(); +} - inline bool ConstElement::getValueBool() const { - return _basis.getValueBool(); - } +inline bool ConstElement::getValueBool() const { + return _basis.getValueBool(); +} - inline Date_t ConstElement::getValueDate() const { - return _basis.getValueDate(); - } +inline Date_t ConstElement::getValueDate() const { + return _basis.getValueDate(); +} - inline bool ConstElement::isValueNull() const { - return _basis.isValueNull(); - } +inline bool ConstElement::isValueNull() const { + return _basis.isValueNull(); +} - inline StringData ConstElement::getValueSymbol() const { - return _basis.getValueSymbol(); - } +inline StringData ConstElement::getValueSymbol() const { + return _basis.getValueSymbol(); +} - inline int32_t ConstElement::getValueInt() const { - return _basis.getValueInt(); - } +inline int32_t ConstElement::getValueInt() const { + return _basis.getValueInt(); +} - inline Timestamp ConstElement::getValueTimestamp() const { - return _basis.getValueTimestamp(); - } +inline Timestamp ConstElement::getValueTimestamp() const { + return _basis.getValueTimestamp(); +} - inline int64_t ConstElement::getValueLong() const { - return _basis.getValueLong(); - } +inline int64_t ConstElement::getValueLong() const { + return _basis.getValueLong(); +} - inline bool ConstElement::isValueMinKey() const { - return _basis.isValueMinKey(); - } +inline bool ConstElement::isValueMinKey() const { + return _basis.isValueMinKey(); +} - inline bool ConstElement::isValueMaxKey() const { - return _basis.isValueMaxKey(); - } +inline bool ConstElement::isValueMaxKey() const { + return _basis.isValueMaxKey(); +} - inline SafeNum ConstElement::getValueSafeNum() const { - return _basis.getValueSafeNum(); - } +inline SafeNum ConstElement::getValueSafeNum() const { + return _basis.getValueSafeNum(); +} - inline int ConstElement::compareWithElement(const ConstElement& other, - bool considerFieldName) const { - return _basis.compareWithElement(other, considerFieldName); - } - - inline int ConstElement::compareWithBSONElement(const BSONElement& other, - bool considerFieldName) const { - return _basis.compareWithBSONElement(other, considerFieldName); - } +inline int ConstElement::compareWithElement(const ConstElement& other, + bool considerFieldName) const { + return _basis.compareWithElement(other, considerFieldName); +} - inline int ConstElement::compareWithBSONObj(const BSONObj& other, +inline int ConstElement::compareWithBSONElement(const BSONElement& other, bool considerFieldName) const { - return _basis.compareWithBSONObj(other, considerFieldName); - } + return _basis.compareWithBSONElement(other, considerFieldName); +} + +inline int ConstElement::compareWithBSONObj(const BSONObj& other, bool considerFieldName) const { + return _basis.compareWithBSONObj(other, considerFieldName); +} - inline void ConstElement::writeTo(BSONObjBuilder* builder) const { - return _basis.writeTo(builder); - } +inline void ConstElement::writeTo(BSONObjBuilder* builder) const { + return _basis.writeTo(builder); +} - inline void ConstElement::writeArrayTo(BSONArrayBuilder* builder) const { - return _basis.writeArrayTo(builder); - } +inline void ConstElement::writeArrayTo(BSONArrayBuilder* builder) const { + return _basis.writeArrayTo(builder); +} - inline bool ConstElement::ok() const { - return _basis.ok(); - } +inline bool ConstElement::ok() const { + return _basis.ok(); +} - inline const Document& ConstElement::getDocument() const { - return _basis.getDocument(); - } +inline const Document& ConstElement::getDocument() const { + return _basis.getDocument(); +} - inline BSONType ConstElement::getType() const { - return _basis.getType(); - } +inline BSONType ConstElement::getType() const { + return _basis.getType(); +} - inline bool ConstElement::isType(BSONType type) const { - return _basis.isType(type); - } +inline bool ConstElement::isType(BSONType type) const { + return _basis.isType(type); +} - inline StringData ConstElement::getFieldName() const { - return _basis.getFieldName(); - } +inline StringData ConstElement::getFieldName() const { + return _basis.getFieldName(); +} - inline Element::RepIdx ConstElement::getIdx() const { - return _basis.getIdx(); - } +inline Element::RepIdx ConstElement::getIdx() const { + return _basis.getIdx(); +} - inline std::string ConstElement::toString() const { - return _basis.toString(); - } +inline std::string ConstElement::toString() const { + return _basis.toString(); +} - inline bool operator==(const ConstElement& l, const ConstElement& r) { - return l._basis == r._basis; - } +inline bool operator==(const ConstElement& l, const ConstElement& r) { + return l._basis == r._basis; +} - inline bool operator!=(const ConstElement& l, const ConstElement& r) { - return !(l == r); - } +inline bool operator!=(const ConstElement& l, const ConstElement& r) { + return !(l == r); +} - inline bool operator==(const Element& l, const ConstElement& r) { - return ConstElement(l) == r; - } +inline bool operator==(const Element& l, const ConstElement& r) { + return ConstElement(l) == r; +} - inline bool operator!=(const Element& l, const ConstElement& r) { - return !(l == r); - } +inline bool operator!=(const Element& l, const ConstElement& r) { + return !(l == r); +} - inline bool operator==(const ConstElement& l, const Element& r) { - return l == ConstElement(r); - } +inline bool operator==(const ConstElement& l, const Element& r) { + return l == ConstElement(r); +} - inline bool operator!=(const ConstElement& l, const Element& r) { - return !(l == r); - } +inline bool operator!=(const ConstElement& l, const Element& r) { + return !(l == r); +} -} // namespace mutablebson -} // namespace mongo +} // namespace mutablebson +} // namespace mongo diff --git a/src/mongo/bson/mutable/const_element.h b/src/mongo/bson/mutable/const_element.h index c1c5d2f4da7..3c6b71d593a 100644 --- a/src/mongo/bson/mutable/const_element.h +++ b/src/mongo/bson/mutable/const_element.h @@ -32,106 +32,102 @@ namespace mongo { namespace mutablebson { - /** For an overview of mutable BSON, please see the file document.h in this directory. */ - - /** ConstElement recapitulates all of the const methods of Element, but cannot be converted - * to an Element. This makes it safe to return as a value from a constant Document, since - * none of Element's non-const methods may be called on ConstElement or any values it - * yields. If you think of Element like an STL 'iterator', then ConstElement is a - * 'const_iterator'. - * - * For details on the API methods of ConstElement, please see the method comments for the - * analagous Element methods in the file element.h (in this same directory). - * - * All calls on ConstElement are simply forwarded to the underlying Element. - */ - class ConstElement { - public: - - // This one argument constructor is intentionally not explicit, since we want to be - // able to pass Elements to functions taking ConstElements without complaint. - inline ConstElement(const Element& basis); - - inline ConstElement leftChild() const; - inline ConstElement rightChild() const; - inline bool hasChildren() const; - inline ConstElement leftSibling(size_t distance = 1) const; - inline ConstElement rightSibling(size_t distance = 1) const; - inline ConstElement parent() const; - inline ConstElement findNthChild(size_t n) const; - inline ConstElement operator[](size_t n) const; - inline ConstElement findFirstChildNamed(StringData name) const; - inline ConstElement operator[](StringData n) const; - inline ConstElement findElementNamed(StringData name) const; - - inline size_t countSiblingsLeft() const; - inline size_t countSiblingsRight() const; - inline size_t countChildren() const; - - inline bool hasValue() const; - inline const BSONElement getValue() const; - - inline double getValueDouble() const; - inline StringData getValueString() const; - inline BSONObj getValueObject() const; - inline BSONArray getValueArray() const; - inline bool isValueUndefined() const; - inline OID getValueOID() const; - inline bool getValueBool() const; - inline Date_t getValueDate() const; - inline bool isValueNull() const; - inline StringData getValueSymbol() const; - inline int32_t getValueInt() const; - inline Timestamp getValueTimestamp() const; - inline int64_t getValueLong() const; - inline bool isValueMinKey() const; - inline bool isValueMaxKey() const; - inline SafeNum getValueSafeNum() const; - - inline int compareWithElement(const ConstElement& other, - bool considerFieldName = true) const; - - inline int compareWithBSONElement(const BSONElement& other, - bool considerFieldName = true) const; +/** For an overview of mutable BSON, please see the file document.h in this directory. */ - inline int compareWithBSONObj(const BSONObj& other, +/** ConstElement recapitulates all of the const methods of Element, but cannot be converted + * to an Element. This makes it safe to return as a value from a constant Document, since + * none of Element's non-const methods may be called on ConstElement or any values it + * yields. If you think of Element like an STL 'iterator', then ConstElement is a + * 'const_iterator'. + * + * For details on the API methods of ConstElement, please see the method comments for the + * analagous Element methods in the file element.h (in this same directory). + * + * All calls on ConstElement are simply forwarded to the underlying Element. + */ +class ConstElement { +public: + // This one argument constructor is intentionally not explicit, since we want to be + // able to pass Elements to functions taking ConstElements without complaint. + inline ConstElement(const Element& basis); + + inline ConstElement leftChild() const; + inline ConstElement rightChild() const; + inline bool hasChildren() const; + inline ConstElement leftSibling(size_t distance = 1) const; + inline ConstElement rightSibling(size_t distance = 1) const; + inline ConstElement parent() const; + inline ConstElement findNthChild(size_t n) const; + inline ConstElement operator[](size_t n) const; + inline ConstElement findFirstChildNamed(StringData name) const; + inline ConstElement operator[](StringData n) const; + inline ConstElement findElementNamed(StringData name) const; + + inline size_t countSiblingsLeft() const; + inline size_t countSiblingsRight() const; + inline size_t countChildren() const; + + inline bool hasValue() const; + inline const BSONElement getValue() const; + + inline double getValueDouble() const; + inline StringData getValueString() const; + inline BSONObj getValueObject() const; + inline BSONArray getValueArray() const; + inline bool isValueUndefined() const; + inline OID getValueOID() const; + inline bool getValueBool() const; + inline Date_t getValueDate() const; + inline bool isValueNull() const; + inline StringData getValueSymbol() const; + inline int32_t getValueInt() const; + inline Timestamp getValueTimestamp() const; + inline int64_t getValueLong() const; + inline bool isValueMinKey() const; + inline bool isValueMaxKey() const; + inline SafeNum getValueSafeNum() const; + + inline int compareWithElement(const ConstElement& other, bool considerFieldName = true) const; + + inline int compareWithBSONElement(const BSONElement& other, bool considerFieldName = true) const; - inline void writeTo(BSONObjBuilder* builder) const; - inline void writeArrayTo(BSONArrayBuilder* builder) const; + inline int compareWithBSONObj(const BSONObj& other, bool considerFieldName = true) const; + + inline void writeTo(BSONObjBuilder* builder) const; + inline void writeArrayTo(BSONArrayBuilder* builder) const; - inline bool ok() const; - inline const Document& getDocument() const; - inline BSONType getType() const; - inline bool isType(BSONType type) const; - inline StringData getFieldName() const; - inline Element::RepIdx getIdx() const; + inline bool ok() const; + inline const Document& getDocument() const; + inline BSONType getType() const; + inline bool isType(BSONType type) const; + inline StringData getFieldName() const; + inline Element::RepIdx getIdx() const; - inline std::string toString() const; + inline std::string toString() const; - friend bool operator==(const ConstElement&, const ConstElement&); + friend bool operator==(const ConstElement&, const ConstElement&); - private: - friend class Document; +private: + friend class Document; - template<typename Builder> - inline void writeElement(Builder* builder, const StringData* fieldName = NULL) const; + template <typename Builder> + inline void writeElement(Builder* builder, const StringData* fieldName = NULL) const; - Element _basis; - }; + Element _basis; +}; - /** See notes for operator==(const Element&, const Element&). The multiple variants listed - * here enable cross type comparisons between Elements and ConstElements. - */ - inline bool operator==(const ConstElement& l, const ConstElement& r); - inline bool operator!=(const ConstElement& l, const ConstElement& r); - inline bool operator==(const Element& l, const ConstElement& r); - inline bool operator!=(const Element& l, const ConstElement& r); - inline bool operator==(const ConstElement& l, const Element& r); - inline bool operator!=(const ConstElement& l, const Element& r); +/** See notes for operator==(const Element&, const Element&). The multiple variants listed + * here enable cross type comparisons between Elements and ConstElements. + */ +inline bool operator==(const ConstElement& l, const ConstElement& r); +inline bool operator!=(const ConstElement& l, const ConstElement& r); +inline bool operator==(const Element& l, const ConstElement& r); +inline bool operator!=(const Element& l, const ConstElement& r); +inline bool operator==(const ConstElement& l, const Element& r); +inline bool operator!=(const ConstElement& l, const Element& r); -} // namespace mutablebson -} // namespace mongo +} // namespace mutablebson +} // namespace mongo #include "mongo/bson/mutable/const_element-inl.h" - diff --git a/src/mongo/bson/mutable/damage_vector.h b/src/mongo/bson/mutable/damage_vector.h index c8098331d47..1072b4f7ec4 100644 --- a/src/mongo/bson/mutable/damage_vector.h +++ b/src/mongo/bson/mutable/damage_vector.h @@ -34,24 +34,24 @@ namespace mongo { namespace mutablebson { - // A damage event represents a change of size 'size' byte at starting at offset - // 'target_offset' in some target buffer, with the replacement data being 'size' bytes of - // data from the 'source' offset. The base addresses against which these offsets are to be - // applied are not captured here. - struct DamageEvent { - typedef uint32_t OffsetSizeType; +// A damage event represents a change of size 'size' byte at starting at offset +// 'target_offset' in some target buffer, with the replacement data being 'size' bytes of +// data from the 'source' offset. The base addresses against which these offsets are to be +// applied are not captured here. +struct DamageEvent { + typedef uint32_t OffsetSizeType; - // Offset of source data (in some buffer held elsewhere). - OffsetSizeType sourceOffset; + // Offset of source data (in some buffer held elsewhere). + OffsetSizeType sourceOffset; - // Offset of target data (in some buffer held elsewhere). - OffsetSizeType targetOffset; + // Offset of target data (in some buffer held elsewhere). + OffsetSizeType targetOffset; - // Size of the damage region. - size_t size; - }; + // Size of the damage region. + size_t size; +}; - typedef std::vector<DamageEvent> DamageVector; +typedef std::vector<DamageEvent> DamageVector; -} // namespace mutablebson -} // namespace mongo +} // namespace mutablebson +} // namespace mongo diff --git a/src/mongo/bson/mutable/document-inl.h b/src/mongo/bson/mutable/document-inl.h index d38868ffd8e..3307567536e 100644 --- a/src/mongo/bson/mutable/document-inl.h +++ b/src/mongo/bson/mutable/document-inl.h @@ -30,50 +30,50 @@ namespace mongo { namespace mutablebson { - inline int Document::compareWith(const Document& other, bool considerFieldName) const { - // We cheat and use Element::compareWithElement since we know that 'other' is a - // Document and has a 'hidden' fieldname that is always indentical across all Document - // instances. - return root().compareWithElement(other.root(), considerFieldName); - } +inline int Document::compareWith(const Document& other, bool considerFieldName) const { + // We cheat and use Element::compareWithElement since we know that 'other' is a + // Document and has a 'hidden' fieldname that is always indentical across all Document + // instances. + return root().compareWithElement(other.root(), considerFieldName); +} - inline int Document::compareWithBSONObj(const BSONObj& other, bool considerFieldName) const { - return root().compareWithBSONObj(other, considerFieldName); - } +inline int Document::compareWithBSONObj(const BSONObj& other, bool considerFieldName) const { + return root().compareWithBSONObj(other, considerFieldName); +} - inline void Document::writeTo(BSONObjBuilder* builder) const { - return root().writeTo(builder); - } +inline void Document::writeTo(BSONObjBuilder* builder) const { + return root().writeTo(builder); +} - inline BSONObj Document::getObject() const { - BSONObjBuilder builder; - writeTo(&builder); - return builder.obj(); - } +inline BSONObj Document::getObject() const { + BSONObjBuilder builder; + writeTo(&builder); + return builder.obj(); +} - inline Element Document::root() { - return _root; - } +inline Element Document::root() { + return _root; +} - inline ConstElement Document::root() const { - return _root; - } +inline ConstElement Document::root() const { + return _root; +} - inline Element Document::end() { - return Element(this, Element::kInvalidRepIdx); - } +inline Element Document::end() { + return Element(this, Element::kInvalidRepIdx); +} - inline ConstElement Document::end() const { - return const_cast<Document*>(this)->end(); - } +inline ConstElement Document::end() const { + return const_cast<Document*>(this)->end(); +} - inline std::string Document::toString() const { - return getObject().toString(); - } +inline std::string Document::toString() const { + return getObject().toString(); +} - inline bool Document::isInPlaceModeEnabled() const { - return getCurrentInPlaceMode() == kInPlaceEnabled; - } +inline bool Document::isInPlaceModeEnabled() const { + return getCurrentInPlaceMode() == kInPlaceEnabled; +} -} // namespace mutablebson -} // namespace mongo +} // namespace mutablebson +} // namespace mongo diff --git a/src/mongo/bson/mutable/document.cpp b/src/mongo/bson/mutable/document.cpp index ddff3e694f2..c03b4d197f3 100644 --- a/src/mongo/bson/mutable/document.cpp +++ b/src/mongo/bson/mutable/document.cpp @@ -42,2528 +42,2475 @@ namespace mongo { namespace mutablebson { - /** Mutable BSON Implementation Overview - * - * If you haven't read it already, please read the 'Mutable BSON Overview' comment in - * document.h before reading further. - * - * In the following discussion, the capitalized terms 'Element' and 'Document' refer to - * the classes of the same name. At times, it is also necessary to refer to abstract - * 'elements' or 'documents', in the sense of bsonspec.org. These latter uses are - * non-capitalized. In the BSON specification, there are two 'classes' of - * elements. 'Primitive' or 'leaf' elements are those elements which do not contain other - * elements. In practice, all BSON types except 'Array' and 'Object' are primitives. The - * CodeWScope type is an exception, but one that we sidestep by considering its BSONObj - * payload to be opaque. - * - * A mutable BSON Document and its component Elements are implemented in terms of four - * data structures. These structures are owned by a Document::Impl object. Each Document - * owns a unique Document::Impl, which owns the relevant data structures and provides - * accessors, mutators, and helper methods related to those data structures. Understanding - * these data structures is critical for understanding how the system as a whole operates. - * - * - The 'Elements Vector': This is a std::vector<ElementRep>, where 'ElementRep' is a - * structure type defined below that contains the detailed information about an entity - * in the Document (e.g. an Object, or an Array, or a NumberLong, etc.). The 'Element' - * and 'ConstElement' objects contain a pointer to a Document (which allows us to reach - * the Document::Impl for the Document), and an index into the Elements Vector in the - * Document::Impl. These two pieces of information make it possible for us to obtain the - * ElementRep associated with a given Element. Note that the Elements Vector is append - * only: ElementReps are never removed from it, even if the cooresponding Element is - * removed from the Document. By never removing ElementReps, and by using indexes into - * the Elements Vector, we can ensure that Elements are never invalidated. Note that - * every Document comes with an automatically provided 'root' element of mongo::Object - * type. The ElementRep for the root is always in the first slot (index zero) of the - * Elements Vector. - * - * - The 'Leaf Builder': This is a standard BSONObjBuilder. When a request is made to the - * Document to add new data to the Document via one of the Document::makeElement[TYPE] - * calls, the element is constructed by invoking the appropriate method on the Leaf - * Builder, forwarding the arguments provided to the call on Document. This results in a - * contiguous region of memory which encodes this element, capturing its field name, its - * type, and the bytes that encode its value, in the same way it normally does when - * using BSONObjBuilder. We then build an ElementRep that indexes into the BufBuilder - * behind the BSONObjBuilder (more on how this happens below, in the section on the - * 'Objects Vector'), then insert that new ElementRep into the ElementsVector, and - * finally return an Element that dereferences to the new ElementRep. Subsequently, - * requests for the type, fieldname or value bytes via the Element are satisfied by - * obtaining the contiguous memory region for the element, which may be used to - * construct a BSONElement over that memory region. - * - * - The 'Objects Vector': This is a std::vector<BSONObj>. Any BSONObj object that - * provides values for parts of the Document is stored in the Objects Vector. For - * instance, in 'Example 2' from document.h, the Document we construct wraps an existing - * BSONObj, which is passed in to the Document constructor. That BSONObj would be stored - * in the Objects Vector. The data content of the BSONObj is not copied, but the BSONObj - * is copied, so the if the BSONObj is counted, we will up its refcount. In any event - * the lifetime of the BSONObj must exceed our lifetime by some mechanism. ElementReps - * that represent the component elements of the BSONObj store the index of their - * supporting BSONObj into the 'objIdx' field of ElementRep. Later, when Elements - * referring to those ElementReps are asked for properties like the field name or type - * of the Element, the underlying memory region in the appropriate BSONObj may be - * examined to provide the relevant data. - * - * - The 'Field Name Heap': For some elements, particularly those in the Leaf Builder or - * those embedded in a BSONObj in the Objects Vector, we can easily obtain the field - * name by reading it from the encoded BSON. However, some elements are not so - * fortunate. Newly created elements of mongo::Array or mongo::Object type, for - * instance, don't have a memory region that provides values. In such cases, the field - * name is stored in the field name heap, which is simply std::vector<char>, where the - * field names are null-byte-delimited. ElementsReps for such elements store an offset - * into the Field Name Heap, and when asked for their field name simply return a pointer - * to the string data the offset identifies. This exploits the fact that in BSON, valid - * field names are null terinated and do not contain embedded null bytes. - * - * - The 'root' Element. Each Document contains a well known Element, which always refers - * to a pre-constructed ElementRep at offset zero in the Elements Vector. This is an - * Object element, and it is considered as the root of the document tree. It is possible - * for ElementReps to exist in the Document data structures, but not be in a child - * relationship to the root Element. Newly created Elements, for instance, are in this - * sort of 'detached' state until they are attched to another element. Only Element's - * that are children of the root element are traversed when calling top level - * serialization or comparision operations on Document. - * - * When you construct a Document that obtains its values from an underlying BSONObj, the - * entire BSONObj is not 'unpacked' into ElementReps at Document construction - * time. Instead, as you ask for Elements with the Element navigation API, the Elements - * for children and siblings are created on demand. Subobjects which are never visited - * will never have ElementReps constructed for them. Similarly, when writing a Document - * back out to a builder, regions of memory that provide values for the Document and which - * have not been modified will be block copied, instead of being recursively explored and - * written. - * - * To see how these data structures interoperate, we will walk through an example. You may - * want to read the comments for ElementRep before tackling the example, since we will - * refer to the internal state of ElementRep here. The example code used here exists as a - * unit test in mutable_bson_test.cpp as (Documentation, Example3). - * - * - * Legend: - * oi : objIdx - * +/- : bitfield state (s: serialized, a: array) - * x : invalid/empty rep idx - * ? : opaque rep idx - * ls/rs: left/right sibling - * lc/rc: left/right child - * p : parent - - static const char inJson[] = - "{" - " 'xs': { 'x' : 'x', 'X' : 'X' }," - " 'ys': { 'y' : 'y' }" - "}"; - mongo::BSONObj inObj = mongo::fromjson(inJson); - mmb::Document doc(inObj); - - * _elements - * oi flags offset ls rs lc rc p - * +-----------------------------------------------------------------------------+ - * 0 | 1 | s:- | ... | off 0 into _fieldNames | x | x | ? | ? | x | - * +-----------------------------------------------------------------------------+ - * - * _objects - * +-----------------------------------------------------------------------------+ - * | BSONObj for _leafBuilder | BSONObj for inObj | | - * +-----------------------------------------------------------------------------+ - * - * _fieldNames - * +-----------------------------------------------------------------------------+ - * | \0 | - * +-----------------------------------------------------------------------------+ - * - * _leafBuf - * +-----------------------------------------------------------------------------+ - * | {} | - * +-----------------------------------------------------------------------------+ - - - mmb::Element root = doc.root(); - mmb::Element xs = root.leftChild(); - - * _elements - * oi flags offset ls rs lc rc p - * +-----------------------------------------------------------------------------+ - * 0 | 1 | s:- | ... | off 0 into _fieldNames | x | x | 1 | ? | x | * - * 1 | 1 | s:+ | ... | off of 'xs' into _objects[1] | x | ? | ? | ? | 0 | * - * +-----------------------------------------------------------------------------+ - * - * _objects - * +-----------------------------------------------------------------------------+ - * | BSONObj for _leafBuilder | BSONObj for inObj | | - * +-----------------------------------------------------------------------------+ - * - * _fieldNames - * +-----------------------------------------------------------------------------+ - * | \0 | - * +-----------------------------------------------------------------------------+ - * - * _leafBuf - * +-----------------------------------------------------------------------------+ - * | {} | - * +-----------------------------------------------------------------------------+ - - - mmb::Element ys = xs.rightSibling(); - - * _elements - * oi flags offset ls rs lc rc p - * +-----------------------------------------------------------------------------+ - * 0 | 1 | s:- | ... | off 0 into _fieldNames | x | x | 1 | ? | x | - * 1 | 1 | s:+ | ... | off of 'xs' into _objects[1] | x | 2 | ? | ? | 0 | * - * 2 | 1 | s:+ | ... | off of 'ys' into _objects[1] | 1 | ? | ? | ? | 0 | * - * +-----------------------------------------------------------------------------+ - * - * _objects - * +-----------------------------------------------------------------------------+ - * | BSONObj for _leafBuilder | BSONObj for inObj | | - * +-----------------------------------------------------------------------------+ - * - * _fieldNames - * +-----------------------------------------------------------------------------+ - * | \0 | - * +-----------------------------------------------------------------------------+ - * - * _leafBuf - * +-----------------------------------------------------------------------------+ - * | {} | - * +-----------------------------------------------------------------------------+ - - - mmb::Element dne = ys.rightSibling(); - - * _elements - * oi flags offset ls rs lc rc p - * +-----------------------------------------------------------------------------+ - * 0 | 1 | s:- | ... | off 0 into _fieldNames | x | x | 1 | 2 | x | * - * 1 | 1 | s:+ | ... | off of 'xs' into _objects[1] | x | 2 | ? | ? | 0 | - * 2 | 1 | s:+ | ... | off of 'ys' into _objects[1] | 1 | x | ? | ? | 0 | * - * +-----------------------------------------------------------------------------+ - * - * _objects - * +-----------------------------------------------------------------------------+ - * | BSONObj for _leafBuilder | BSONObj for inObj | | - * +-----------------------------------------------------------------------------+ - * - * _fieldNames - * +-----------------------------------------------------------------------------+ - * | \0 | - * +-----------------------------------------------------------------------------+ - * - * _leafBuf - * +-----------------------------------------------------------------------------+ - * | {} | - * +-----------------------------------------------------------------------------+ - - - mmb::Element ycaps = doc.makeElementString("Y", "Y"); - - * _elements - * oi flags offset ls rs lc rc p - * +-----------------------------------------------------------------------------+ - * 0 | 1 | s:- | ... | off 0 into _fieldNames | x | x | 1 | 2 | x | - * 1 | 1 | s:+ | ... | off of 'xs' into _objects[1] | x | 2 | ? | ? | 0 | - * 2 | 1 | s:+ | ... | off of 'ys' into _objects[1] | 1 | x | ? | ? | 0 | - * 3 | 0 | s:+ | ... | off of 'Y' into _objects[0] | x | x | x | x | x | * - * +-----------------------------------------------------------------------------+ - * - * _objects - * +-----------------------------------------------------------------------------+ - * | BSONObj for _leafBuilder | BSONObj for inObj | | - * +-----------------------------------------------------------------------------+ - * - * _fieldNames - * +-----------------------------------------------------------------------------+ - * | \0 | - * +-----------------------------------------------------------------------------+ - * - * _leafBuf - * +-----------------------------------------------------------------------------+ - * | { "Y" : "Y" } | * - * +-----------------------------------------------------------------------------+ - - - ys.pushBack(ycaps); - - * _elements - * oi flags offset ls rs lc rc p - * +-----------------------------------------------------------------------------+ - * 0 | 1 | s:- | ... | off 0 into _fieldNames | x | x | 1 | 2 | x | - * 1 | 1 | s:+ | ... | off of 'xs' into _objects[1] | x | 2 | ? | ? | 0 | - * 2 | 1 | s:- | ... | off of 'ys' into _objects[1] | 1 | x | 4 | 3 | 0 | * - * 3 | 0 | s:+ | ... | off of 'Y' into _objects[0] | 4 | x | x | x | 2 | * - * 4 | 1 | s:+ | ... | off of 'ys.y' into _objects[1] | x | 3 | x | x | 2 | * - * +-----------------------------------------------------------------------------+ - * - * _objects - * +-----------------------------------------------------------------------------+ - * | BSONObj for _leafBuilder | BSONObj for inObj | | - * +-----------------------------------------------------------------------------+ - * - * _fieldNames - * +-----------------------------------------------------------------------------+ - * | \0 | - * +-----------------------------------------------------------------------------+ - * - * _leafBuf - * +-----------------------------------------------------------------------------+ - * | { "Y" : "Y" } | - * +-----------------------------------------------------------------------------+ - - - mmb::Element pun = doc.makeElementArray("why"); - - * _elements - * oi flags offset ls rs lc rc p - * +-----------------------------------------------------------------------------+ - * 0 | 1 | s:- | ... | off 0 into _fieldNames | x | x | 1 | 2 | x | - * 1 | 1 | s:+ | ... | off of 'xs' into _objects[1] | x | 2 | ? | ? | 0 | - * 2 | 1 | s:- | ... | off of 'ys' into _objects[1] | 1 | x | 4 | 3 | 0 | - * 3 | 0 | s:+ | ... | off of 'Y' into _objects[0] | 4 | x | x | x | 2 | - * 4 | 1 | s:+ | ... | off of 'ys.y' into _objects[1] | x | 3 | x | x | 2 | - * 5 | -1 | s:- | a:+ | ... | off of 'why' into _fieldNames | x | x | x | x | x | * - * +-----------------------------------------------------------------------------+ - * - * _objects - * +-----------------------------------------------------------------------------+ - * | BSONObj for _leafBuilder | BSONObj for inObj | | - * +-----------------------------------------------------------------------------+ - * - * _fieldNames - * +-----------------------------------------------------------------------------+ - * | \0why\0 | * - * +-----------------------------------------------------------------------------+ - * - * _leafBuf - * +-----------------------------------------------------------------------------+ - * | { "Y" : "Y" } | - * +-----------------------------------------------------------------------------+ - - - ys.pushBack(pun); - - * _elements - * oi flags offset ls rs lc rc p - * +-----------------------------------------------------------------------------+ - * 0 | 1 | s:- | ... | off 0 into _fieldNames | x | x | 1 | 2 | x | - * 1 | 1 | s:+ | ... | off of 'xs' into _objects[1] | x | 2 | ? | ? | 0 | - * 2 | 1 | s:- | ... | off of 'ys' into _objects[1] | 1 | x | 4 | 5 | 0 | * - * 3 | 0 | s:+ | ... | off of 'Y' into _objects[0] | 4 | 5 | x | x | 2 | * - * 4 | 1 | s:+ | ... | off of 'ys.y' into _objects[1] | x | 3 | x | x | 2 | - * 5 | -1 | s:- | a:+ | ... | off of 'why' into _fieldNames | 3 | x | x | x | 2 | * - * +-----------------------------------------------------------------------------+ - * - * _objects - * +-----------------------------------------------------------------------------+ - * | BSONObj for _leafBuilder | BSONObj for inObj | | - * +-----------------------------------------------------------------------------+ - * - * _fieldNames - * +-----------------------------------------------------------------------------+ - * | \0why\0 | - * +-----------------------------------------------------------------------------+ - * - * _leafBuf - * +-----------------------------------------------------------------------------+ - * | { "Y" : "Y" } | - * +-----------------------------------------------------------------------------+ - - - pun.appendString("na", "not"); - - * _elements - * oi flags offset ls rs lc rc p - * +-----------------------------------------------------------------------------+ - * 0 | 1 | s:- | ... | off 0 into _fieldNames | x | x | 1 | 2 | x | - * 1 | 1 | s:+ | ... | off of 'xs' into _objects[1] | x | 2 | ? | ? | 0 | - * 2 | 1 | s:- | ... | off of 'ys' into _objects[1] | 1 | x | 4 | 5 | 0 | - * 3 | 0 | s:+ | ... | off of 'Y' into _objects[0] | 4 | 5 | x | x | 2 | - * 4 | 1 | s:+ | ... | off of 'ys.y' into _objects[1] | x | 3 | x | x | 2 | - * 5 | -1 | s:- | a:+ | ... | off of 'why' into _fieldNames | 3 | x | 6 | 6 | 2 | * - * 6 | 0 | s:+ | ... | off of 'na' into _objects[0] | x | x | x | x | 5 | * - * +-----------------------------------------------------------------------------+ - * - * _objects - * +-----------------------------------------------------------------------------+ - * | BSONObj for _leafBuilder | BSONObj for inObj | | - * +-----------------------------------------------------------------------------+ - * - * _fieldNames - * +-----------------------------------------------------------------------------+ - * | \0why\0 | - * +-----------------------------------------------------------------------------+ - * - * _leafBuf - * +-----------------------------------------------------------------------------+ - * | { "Y" : "Y", "na" : "not" } | * - * +-----------------------------------------------------------------------------+ - * - */ - -// Work around http://gcc.gnu.org/bugzilla/show_bug.cgi?id=29365. Note that the selection of -// minor version 4 is somewhat arbitrary. It does appear that the fix for this was backported -// to earlier versions. This is a conservative choice that we can revisit later. We need the -// __clang__ here because Clang claims to be gcc of some version. -#if defined(__clang__) || !defined(__GNUC__) || (__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 4) - namespace { -#endif +/** Mutable BSON Implementation Overview + * + * If you haven't read it already, please read the 'Mutable BSON Overview' comment in + * document.h before reading further. + * + * In the following discussion, the capitalized terms 'Element' and 'Document' refer to + * the classes of the same name. At times, it is also necessary to refer to abstract + * 'elements' or 'documents', in the sense of bsonspec.org. These latter uses are + * non-capitalized. In the BSON specification, there are two 'classes' of + * elements. 'Primitive' or 'leaf' elements are those elements which do not contain other + * elements. In practice, all BSON types except 'Array' and 'Object' are primitives. The + * CodeWScope type is an exception, but one that we sidestep by considering its BSONObj + * payload to be opaque. + * + * A mutable BSON Document and its component Elements are implemented in terms of four + * data structures. These structures are owned by a Document::Impl object. Each Document + * owns a unique Document::Impl, which owns the relevant data structures and provides + * accessors, mutators, and helper methods related to those data structures. Understanding + * these data structures is critical for understanding how the system as a whole operates. + * + * - The 'Elements Vector': This is a std::vector<ElementRep>, where 'ElementRep' is a + * structure type defined below that contains the detailed information about an entity + * in the Document (e.g. an Object, or an Array, or a NumberLong, etc.). The 'Element' + * and 'ConstElement' objects contain a pointer to a Document (which allows us to reach + * the Document::Impl for the Document), and an index into the Elements Vector in the + * Document::Impl. These two pieces of information make it possible for us to obtain the + * ElementRep associated with a given Element. Note that the Elements Vector is append + * only: ElementReps are never removed from it, even if the cooresponding Element is + * removed from the Document. By never removing ElementReps, and by using indexes into + * the Elements Vector, we can ensure that Elements are never invalidated. Note that + * every Document comes with an automatically provided 'root' element of mongo::Object + * type. The ElementRep for the root is always in the first slot (index zero) of the + * Elements Vector. + * + * - The 'Leaf Builder': This is a standard BSONObjBuilder. When a request is made to the + * Document to add new data to the Document via one of the Document::makeElement[TYPE] + * calls, the element is constructed by invoking the appropriate method on the Leaf + * Builder, forwarding the arguments provided to the call on Document. This results in a + * contiguous region of memory which encodes this element, capturing its field name, its + * type, and the bytes that encode its value, in the same way it normally does when + * using BSONObjBuilder. We then build an ElementRep that indexes into the BufBuilder + * behind the BSONObjBuilder (more on how this happens below, in the section on the + * 'Objects Vector'), then insert that new ElementRep into the ElementsVector, and + * finally return an Element that dereferences to the new ElementRep. Subsequently, + * requests for the type, fieldname or value bytes via the Element are satisfied by + * obtaining the contiguous memory region for the element, which may be used to + * construct a BSONElement over that memory region. + * + * - The 'Objects Vector': This is a std::vector<BSONObj>. Any BSONObj object that + * provides values for parts of the Document is stored in the Objects Vector. For + * instance, in 'Example 2' from document.h, the Document we construct wraps an existing + * BSONObj, which is passed in to the Document constructor. That BSONObj would be stored + * in the Objects Vector. The data content of the BSONObj is not copied, but the BSONObj + * is copied, so the if the BSONObj is counted, we will up its refcount. In any event + * the lifetime of the BSONObj must exceed our lifetime by some mechanism. ElementReps + * that represent the component elements of the BSONObj store the index of their + * supporting BSONObj into the 'objIdx' field of ElementRep. Later, when Elements + * referring to those ElementReps are asked for properties like the field name or type + * of the Element, the underlying memory region in the appropriate BSONObj may be + * examined to provide the relevant data. + * + * - The 'Field Name Heap': For some elements, particularly those in the Leaf Builder or + * those embedded in a BSONObj in the Objects Vector, we can easily obtain the field + * name by reading it from the encoded BSON. However, some elements are not so + * fortunate. Newly created elements of mongo::Array or mongo::Object type, for + * instance, don't have a memory region that provides values. In such cases, the field + * name is stored in the field name heap, which is simply std::vector<char>, where the + * field names are null-byte-delimited. ElementsReps for such elements store an offset + * into the Field Name Heap, and when asked for their field name simply return a pointer + * to the string data the offset identifies. This exploits the fact that in BSON, valid + * field names are null terinated and do not contain embedded null bytes. + * + * - The 'root' Element. Each Document contains a well known Element, which always refers + * to a pre-constructed ElementRep at offset zero in the Elements Vector. This is an + * Object element, and it is considered as the root of the document tree. It is possible + * for ElementReps to exist in the Document data structures, but not be in a child + * relationship to the root Element. Newly created Elements, for instance, are in this + * sort of 'detached' state until they are attched to another element. Only Element's + * that are children of the root element are traversed when calling top level + * serialization or comparision operations on Document. + * + * When you construct a Document that obtains its values from an underlying BSONObj, the + * entire BSONObj is not 'unpacked' into ElementReps at Document construction + * time. Instead, as you ask for Elements with the Element navigation API, the Elements + * for children and siblings are created on demand. Subobjects which are never visited + * will never have ElementReps constructed for them. Similarly, when writing a Document + * back out to a builder, regions of memory that provide values for the Document and which + * have not been modified will be block copied, instead of being recursively explored and + * written. + * + * To see how these data structures interoperate, we will walk through an example. You may + * want to read the comments for ElementRep before tackling the example, since we will + * refer to the internal state of ElementRep here. The example code used here exists as a + * unit test in mutable_bson_test.cpp as (Documentation, Example3). + * + * + * Legend: + * oi : objIdx + * +/- : bitfield state (s: serialized, a: array) + * x : invalid/empty rep idx + * ? : opaque rep idx + * ls/rs: left/right sibling + * lc/rc: left/right child + * p : parent + + static const char inJson[] = + "{" + " 'xs': { 'x' : 'x', 'X' : 'X' }," + " 'ys': { 'y' : 'y' }" + "}"; + mongo::BSONObj inObj = mongo::fromjson(inJson); + mmb::Document doc(inObj); + + * _elements + * oi flags offset ls rs lc rc p + * +-----------------------------------------------------------------------------+ + * 0 | 1 | s:- | ... | off 0 into _fieldNames | x | x | ? | ? | x | + * +-----------------------------------------------------------------------------+ + * + * _objects + * +-----------------------------------------------------------------------------+ + * | BSONObj for _leafBuilder | BSONObj for inObj | | + * +-----------------------------------------------------------------------------+ + * + * _fieldNames + * +-----------------------------------------------------------------------------+ + * | \0 | + * +-----------------------------------------------------------------------------+ + * + * _leafBuf + * +-----------------------------------------------------------------------------+ + * | {} | + * +-----------------------------------------------------------------------------+ - // The designated field name for the root element. - const char kRootFieldName[] = ""; - // How many reps do we cache before we spill to heap. Use a power of two. For debug - // builds we make this very small so it is less likely to mask vector invalidation - // logic errors. We don't make it zero so that we do execute the fastRep code paths. - const size_t kFastReps = kDebugBuild ? 2 : 128; + mmb::Element root = doc.root(); + mmb::Element xs = root.leftChild(); - // An ElementRep contains the information necessary to locate the data for an Element, - // and the topology information for how the Element is related to other Elements in the - // document. -#pragma pack(push, 1) - struct ElementRep { - - // The index of the BSONObj that provides the value for this Element. For nodes - // where serialized is 'false', this value may be kInvalidObjIdx to indicate that - // the Element does not have a supporting BSONObj. - typedef uint16_t ObjIdx; - ObjIdx objIdx; - - // This bit is true if this ElementRep identifies a completely serialized - // BSONElement (i.e. a region of memory with a bson type byte, a fieldname, and an - // encoded value). Changes to children of a serialized element will cause it to be - // marked as unserialized. - uint16_t serialized: 1; - - // For object like Elements where we cannot determine the type of the object by - // looking a region of memory, the 'array' bit allows us to determine whether we - // are an object or an array. - uint16_t array: 1; - - // Reserved for future use. - uint16_t reserved: 14; - - // This word either gives the offset into the BSONObj associated with this - // ElementRep where this serialized BSON element may be located, or the offset into - // the _fieldNames member of the Document where the field name for this BSON - // element may be located. - uint32_t offset; - - // The indexes of our left and right siblings in the Document. - struct { - Element::RepIdx left; - Element::RepIdx right; - } sibling; - - // The indexes of our left and right chidren in the Document. - struct { - Element::RepIdx left; - Element::RepIdx right; - } child; - - // The index of our parent in the Document. - Element::RepIdx parent; - - // The cached field name size of this element, or -1 if unknown. - int32_t fieldNameSize; - }; -#pragma pack(pop) + * _elements + * oi flags offset ls rs lc rc p + * +-----------------------------------------------------------------------------+ + * 0 | 1 | s:- | ... | off 0 into _fieldNames | x | x | 1 | ? | x | * + * 1 | 1 | s:+ | ... | off of 'xs' into _objects[1] | x | ? | ? | ? | 0 | * + * +-----------------------------------------------------------------------------+ + * + * _objects + * +-----------------------------------------------------------------------------+ + * | BSONObj for _leafBuilder | BSONObj for inObj | | + * +-----------------------------------------------------------------------------+ + * + * _fieldNames + * +-----------------------------------------------------------------------------+ + * | \0 | + * +-----------------------------------------------------------------------------+ + * + * _leafBuf + * +-----------------------------------------------------------------------------+ + * | {} | + * +-----------------------------------------------------------------------------+ - BOOST_STATIC_ASSERT(sizeof(ElementRep) == 32); - // We want ElementRep to be a POD so Document::Impl can grow the std::vector with - // memmove. - // - // TODO: C++11 static_assert(std::is_pod<ElementRep>::value); - - // The ElementRep for the root element is always zero. - const Element::RepIdx kRootRepIdx = Element::RepIdx(0); - - // This is the object index for elements in the leaf heap. - const ElementRep::ObjIdx kLeafObjIdx = ElementRep::ObjIdx(0); - - // This is the sentinel value to indicate that we have no supporting BSONObj. - const ElementRep::ObjIdx kInvalidObjIdx = ElementRep::ObjIdx(-1); - - // This is the highest valid object index that does not overlap sentinel values. - const ElementRep::ObjIdx kMaxObjIdx = ElementRep::ObjIdx(-2); - - // Returns the offset of 'elt' within 'object' as a uint32_t. The element must be part - // of the object or the behavior is undefined. - uint32_t getElementOffset(const BSONObj& object, const BSONElement& elt) { - dassert(!elt.eoo()); - const char* const objRaw = object.objdata(); - const char* const eltRaw = elt.rawdata(); - dassert(objRaw < eltRaw); - dassert(eltRaw < objRaw + object.objsize()); - dassert(eltRaw + elt.size() <= objRaw + object.objsize()); - const ptrdiff_t offset = eltRaw - objRaw; - // BSON documents express their size as an int32_t so we should always be able to - // express the offset as a uint32_t. - verify(offset > 0); - verify(offset <= std::numeric_limits<int32_t>::max()); - return offset; - } + mmb::Element ys = xs.rightSibling(); - // Returns true if this ElementRep is 'detached' from all other elements and can be - // added as a child, which helps ensure that we maintain a tree rather than a graph - // when adding new elements to the tree. The root element is never considered to be - // attachable. - bool canAttach(const Element::RepIdx id, const ElementRep& rep) { - return - (id != kRootRepIdx) && - (rep.sibling.left == Element::kInvalidRepIdx) && - (rep.sibling.right == Element::kInvalidRepIdx) && - (rep.parent == Element::kInvalidRepIdx); - } + * _elements + * oi flags offset ls rs lc rc p + * +-----------------------------------------------------------------------------+ + * 0 | 1 | s:- | ... | off 0 into _fieldNames | x | x | 1 | ? | x | + * 1 | 1 | s:+ | ... | off of 'xs' into _objects[1] | x | 2 | ? | ? | 0 | * + * 2 | 1 | s:+ | ... | off of 'ys' into _objects[1] | 1 | ? | ? | ? | 0 | * + * +-----------------------------------------------------------------------------+ + * + * _objects + * +-----------------------------------------------------------------------------+ + * | BSONObj for _leafBuilder | BSONObj for inObj | | + * +-----------------------------------------------------------------------------+ + * + * _fieldNames + * +-----------------------------------------------------------------------------+ + * | \0 | + * +-----------------------------------------------------------------------------+ + * + * _leafBuf + * +-----------------------------------------------------------------------------+ + * | {} | + * +-----------------------------------------------------------------------------+ - // Returns a Status describing why 'canAttach' returned false. This function should not - // be inlined since it just makes the callers larger for no real gain. - NOINLINE_DECL Status getAttachmentError(const ElementRep& rep); - Status getAttachmentError(const ElementRep& rep) { - if (rep.sibling.left != Element::kInvalidRepIdx) - return Status(ErrorCodes::IllegalOperation, "dangling left sibling"); - if (rep.sibling.right != Element::kInvalidRepIdx) - return Status(ErrorCodes::IllegalOperation, "dangling right sibling"); - if (rep.parent != Element::kInvalidRepIdx) - return Status(ErrorCodes::IllegalOperation, "dangling parent"); - return Status(ErrorCodes::IllegalOperation, "cannot add the root as a child"); - } + mmb::Element dne = ys.rightSibling(); - // Enable paranoid mode to force a reallocation on mutation of the princple data - // structures in Document::Impl. This is really slow, but can be very helpful if you - // suspect an invalidation logic error and want to find it with valgrind. Paranoid mode - // only works in debug mode; it is ignored in release builds. - const bool paranoid = false; + * _elements + * oi flags offset ls rs lc rc p + * +-----------------------------------------------------------------------------+ + * 0 | 1 | s:- | ... | off 0 into _fieldNames | x | x | 1 | 2 | x | * + * 1 | 1 | s:+ | ... | off of 'xs' into _objects[1] | x | 2 | ? | ? | 0 | + * 2 | 1 | s:+ | ... | off of 'ys' into _objects[1] | 1 | x | ? | ? | 0 | * + * +-----------------------------------------------------------------------------+ + * + * _objects + * +-----------------------------------------------------------------------------+ + * | BSONObj for _leafBuilder | BSONObj for inObj | | + * +-----------------------------------------------------------------------------+ + * + * _fieldNames + * +-----------------------------------------------------------------------------+ + * | \0 | + * +-----------------------------------------------------------------------------+ + * + * _leafBuf + * +-----------------------------------------------------------------------------+ + * | {} | + * +-----------------------------------------------------------------------------+ + + + mmb::Element ycaps = doc.makeElementString("Y", "Y"); + + * _elements + * oi flags offset ls rs lc rc p + * +-----------------------------------------------------------------------------+ + * 0 | 1 | s:- | ... | off 0 into _fieldNames | x | x | 1 | 2 | x | + * 1 | 1 | s:+ | ... | off of 'xs' into _objects[1] | x | 2 | ? | ? | 0 | + * 2 | 1 | s:+ | ... | off of 'ys' into _objects[1] | 1 | x | ? | ? | 0 | + * 3 | 0 | s:+ | ... | off of 'Y' into _objects[0] | x | x | x | x | x | * + * +-----------------------------------------------------------------------------+ + * + * _objects + * +-----------------------------------------------------------------------------+ + * | BSONObj for _leafBuilder | BSONObj for inObj | | + * +-----------------------------------------------------------------------------+ + * + * _fieldNames + * +-----------------------------------------------------------------------------+ + * | \0 | + * +-----------------------------------------------------------------------------+ + * + * _leafBuf + * +-----------------------------------------------------------------------------+ + * | { "Y" : "Y" } | * + * +-----------------------------------------------------------------------------+ + + + ys.pushBack(ycaps); + + * _elements + * oi flags offset ls rs lc rc p + * +-----------------------------------------------------------------------------+ + * 0 | 1 | s:- | ... | off 0 into _fieldNames | x | x | 1 | 2 | x | + * 1 | 1 | s:+ | ... | off of 'xs' into _objects[1] | x | 2 | ? | ? | 0 | + * 2 | 1 | s:- | ... | off of 'ys' into _objects[1] | 1 | x | 4 | 3 | 0 | * + * 3 | 0 | s:+ | ... | off of 'Y' into _objects[0] | 4 | x | x | x | 2 | * + * 4 | 1 | s:+ | ... | off of 'ys.y' into _objects[1] | x | 3 | x | x | 2 | * + * +-----------------------------------------------------------------------------+ + * + * _objects + * +-----------------------------------------------------------------------------+ + * | BSONObj for _leafBuilder | BSONObj for inObj | | + * +-----------------------------------------------------------------------------+ + * + * _fieldNames + * +-----------------------------------------------------------------------------+ + * | \0 | + * +-----------------------------------------------------------------------------+ + * + * _leafBuf + * +-----------------------------------------------------------------------------+ + * | { "Y" : "Y" } | + * +-----------------------------------------------------------------------------+ + + + mmb::Element pun = doc.makeElementArray("why"); + + * _elements + * oi flags offset ls rs lc rc p + * +-----------------------------------------------------------------------------+ + * 0 | 1 | s:- | ... | off 0 into _fieldNames | x | x | 1 | 2 | x | + * 1 | 1 | s:+ | ... | off of 'xs' into _objects[1] | x | 2 | ? | ? | 0 | + * 2 | 1 | s:- | ... | off of 'ys' into _objects[1] | 1 | x | 4 | 3 | 0 | + * 3 | 0 | s:+ | ... | off of 'Y' into _objects[0] | 4 | x | x | x | 2 | + * 4 | 1 | s:+ | ... | off of 'ys.y' into _objects[1] | x | 3 | x | x | 2 | + * 5 | -1 | s:- | a:+ | ... | off of 'why' into _fieldNames | x | x | x | x | x | * + * +-----------------------------------------------------------------------------+ + * + * _objects + * +-----------------------------------------------------------------------------+ + * | BSONObj for _leafBuilder | BSONObj for inObj | | + * +-----------------------------------------------------------------------------+ + * + * _fieldNames + * +-----------------------------------------------------------------------------+ + * | \0why\0 | * + * +-----------------------------------------------------------------------------+ + * + * _leafBuf + * +-----------------------------------------------------------------------------+ + * | { "Y" : "Y" } | + * +-----------------------------------------------------------------------------+ + + + ys.pushBack(pun); + + * _elements + * oi flags offset ls rs lc rc p + * +-----------------------------------------------------------------------------+ + * 0 | 1 | s:- | ... | off 0 into _fieldNames | x | x | 1 | 2 | x | + * 1 | 1 | s:+ | ... | off of 'xs' into _objects[1] | x | 2 | ? | ? | 0 | + * 2 | 1 | s:- | ... | off of 'ys' into _objects[1] | 1 | x | 4 | 5 | 0 | * + * 3 | 0 | s:+ | ... | off of 'Y' into _objects[0] | 4 | 5 | x | x | 2 | * + * 4 | 1 | s:+ | ... | off of 'ys.y' into _objects[1] | x | 3 | x | x | 2 | + * 5 | -1 | s:- | a:+ | ... | off of 'why' into _fieldNames | 3 | x | x | x | 2 | * + * +-----------------------------------------------------------------------------+ + * + * _objects + * +-----------------------------------------------------------------------------+ + * | BSONObj for _leafBuilder | BSONObj for inObj | | + * +-----------------------------------------------------------------------------+ + * + * _fieldNames + * +-----------------------------------------------------------------------------+ + * | \0why\0 | + * +-----------------------------------------------------------------------------+ + * + * _leafBuf + * +-----------------------------------------------------------------------------+ + * | { "Y" : "Y" } | + * +-----------------------------------------------------------------------------+ + + + pun.appendString("na", "not"); + + * _elements + * oi flags offset ls rs lc rc p + * +-----------------------------------------------------------------------------+ + * 0 | 1 | s:- | ... | off 0 into _fieldNames | x | x | 1 | 2 | x | + * 1 | 1 | s:+ | ... | off of 'xs' into _objects[1] | x | 2 | ? | ? | 0 | + * 2 | 1 | s:- | ... | off of 'ys' into _objects[1] | 1 | x | 4 | 5 | 0 | + * 3 | 0 | s:+ | ... | off of 'Y' into _objects[0] | 4 | 5 | x | x | 2 | + * 4 | 1 | s:+ | ... | off of 'ys.y' into _objects[1] | x | 3 | x | x | 2 | + * 5 | -1 | s:- | a:+ | ... | off of 'why' into _fieldNames | 3 | x | 6 | 6 | 2 | * + * 6 | 0 | s:+ | ... | off of 'na' into _objects[0] | x | x | x | x | 5 | * + * +-----------------------------------------------------------------------------+ + * + * _objects + * +-----------------------------------------------------------------------------+ + * | BSONObj for _leafBuilder | BSONObj for inObj | | + * +-----------------------------------------------------------------------------+ + * + * _fieldNames + * +-----------------------------------------------------------------------------+ + * | \0why\0 | + * +-----------------------------------------------------------------------------+ + * + * _leafBuf + * +-----------------------------------------------------------------------------+ + * | { "Y" : "Y", "na" : "not" } | * + * +-----------------------------------------------------------------------------+ + * + */ -#if defined(__clang__) || !defined(__GNUC__) || (__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 4) - } // namespace +// Work around http://gcc.gnu.org/bugzilla/show_bug.cgi?id=29365. Note that the selection of +// minor version 4 is somewhat arbitrary. It does appear that the fix for this was backported +// to earlier versions. This is a conservative choice that we can revisit later. We need the +// __clang__ here because Clang claims to be gcc of some version. +#if defined(__clang__) || !defined(__GNUC__) || (__GNUC__ > 4) || \ + (__GNUC__ == 4 && __GNUC_MINOR__ >= 4) +namespace { #endif - /** Document::Impl holds the Document state. Please see the file comment above for details - * on the fields of Impl and how they are used to realize the implementation of mutable - * BSON. Impl provides various utility methods to insert, lookup, and interrogate the - * Elements, BSONObj objects, field names, and builders associated with the Document. - * - * TODO: At some point, we could remove the firewall and inline the members of Impl into - * Document. - */ - class Document::Impl { - MONGO_DISALLOW_COPYING(Impl); - - public: - Impl(Document::InPlaceMode inPlaceMode) - : _numElements(0) - , _slowElements() - , _objects() - , _fieldNames() - , _leafBuf() - , _leafBuilder(_leafBuf) - , _fieldNameScratch() - , _damages() - , _inPlaceMode(inPlaceMode) { - - // We always have a BSONObj for the leaves, and we often have - // one for our base document, so reserve 2. - _objects.reserve(2); - - // We always have at least one byte for the root field name, and we would like - // to be able to hold a few short field names without reallocation. - _fieldNames.reserve(8); - - // We need an object at _objects[0] so that we can access leaf elements we - // construct with the leaf builder in the same way we access elements serialized in - // other BSONObjs. So we call asTempObj on the builder and store the result in slot - // 0. - dassert(_objects.size() == kLeafObjIdx); - _objects.push_back(_leafBuilder.asTempObj()); - dassert(_leafBuf.len() != 0); - } - - ~Impl() { - _leafBuilder.abandon(); - } - - void reset(Document::InPlaceMode inPlaceMode) { - // Clear out the state in the vectors. - _slowElements.clear(); - _numElements = 0; - - _objects.clear(); - _fieldNames.clear(); - - // There is no way to reset the state of a BSONObjBuilder, so we need to call its - // dtor, reset the underlying buf, and re-invoke the constructor in-place. - _leafBuilder.abandon(); - _leafBuilder.~BSONObjBuilder(); - _leafBuf.reset(); - new (&_leafBuilder) BSONObjBuilder(_leafBuf); +// The designated field name for the root element. +const char kRootFieldName[] = ""; - _fieldNameScratch.clear(); - _damages.clear(); - _inPlaceMode = inPlaceMode; - - // Ensure that we start in the same state as the ctor would leave us in. - _objects.push_back(_leafBuilder.asTempObj()); - } - - // Obtain the ElementRep for the given rep id. - ElementRep& getElementRep(Element::RepIdx id) { - return const_cast<ElementRep&>(const_cast<const Impl*>(this)->getElementRep(id)); - } +// How many reps do we cache before we spill to heap. Use a power of two. For debug +// builds we make this very small so it is less likely to mask vector invalidation +// logic errors. We don't make it zero so that we do execute the fastRep code paths. +const size_t kFastReps = kDebugBuild ? 2 : 128; - // Obtain the ElementRep for the given rep id. - const ElementRep& getElementRep(Element::RepIdx id) const { - dassert(id < _numElements); - if (id < kFastReps) - return _fastElements[id]; - else - return _slowElements[id - kFastReps]; - } - - // Construct and return a new default initialized ElementRep. The RepIdx identifying - // the new rep is returned in the out parameter. - ElementRep& makeNewRep(Element::RepIdx* newIdx) { - - const ElementRep defaultRep = { - kInvalidObjIdx, - false, false, 0, - 0, - { Element::kInvalidRepIdx, Element::kInvalidRepIdx }, - { Element::kInvalidRepIdx, Element::kInvalidRepIdx }, - Element::kInvalidRepIdx, - -1 - }; - - const Element::RepIdx id = *newIdx = _numElements++; - - if (id < kFastReps) { - return _fastElements[id] = defaultRep; - } - else { - verify(id <= Element::kMaxRepIdx); - - if (kDebugBuild && paranoid) { - // Force all reps to new addresses to help catch invalid rep usage. - std::vector<ElementRep> newSlowElements(_slowElements); - _slowElements.swap(newSlowElements); - } - - return *_slowElements.insert(_slowElements.end(), defaultRep); - } - } - - // Insert a new ElementRep for a leaf element at the given offset and return its ID. - Element::RepIdx insertLeafElement(int offset, int fieldNameSize = -1) { - // BufBuilder hands back sizes in 'int's. - Element::RepIdx inserted; - ElementRep& rep = makeNewRep(&inserted); - - rep.fieldNameSize = fieldNameSize; - rep.objIdx = kLeafObjIdx; - rep.serialized = true; - dassert(offset >= 0); - // TODO: Is this a legitimate possibility? - dassert(static_cast<unsigned int>(offset) < std::numeric_limits<uint32_t>::max()); - rep.offset = offset; - _objects[kLeafObjIdx] = _leafBuilder.asTempObj(); - return inserted; - } - - // Obtain the object builder for the leaves. - BSONObjBuilder& leafBuilder() { - return _leafBuilder; - } - - // Obtain the BSONObj for the given object id. - BSONObj& getObject(ElementRep::ObjIdx objIdx) { - dassert(objIdx < _objects.size()); - return _objects[objIdx]; - } - - // Obtain the BSONObj for the given object id. - const BSONObj& getObject(ElementRep::ObjIdx objIdx) const { - dassert(objIdx < _objects.size()); - return _objects[objIdx]; - } - - // Insert the given BSONObj and return an ID for it. - ElementRep::ObjIdx insertObject(const BSONObj& newObj) { - const size_t objIdx = _objects.size(); - verify(objIdx <= kMaxObjIdx); - _objects.push_back(newObj); - if (kDebugBuild && paranoid) { - // Force reallocation to catch use after invalidation. - std::vector<BSONObj> new_objects(_objects); - _objects.swap(new_objects); - } - return objIdx; - } - - // Given a RepIdx, return the BSONElement that it represents. - BSONElement getSerializedElement(const ElementRep& rep) const { - const BSONObj& object = getObject(rep.objIdx); - return BSONElement( - object.objdata() + rep.offset, - rep.fieldNameSize, - BSONElement::FieldNameSizeTag()); - } - - // A helper method that either inserts the field name into the field name heap and - // updates element. - void insertFieldName(ElementRep& rep, StringData fieldName) { - dassert(!rep.serialized); - rep.offset = insertFieldName(fieldName); - } - - // Retrieve the fieldName, given a rep. - StringData getFieldName(const ElementRep& rep) const { - // The root element has no field name. - if (&rep == &getElementRep(kRootRepIdx)) - return StringData(); - - if (rep.serialized || (rep.objIdx != kInvalidObjIdx)) - return getSerializedElement(rep).fieldNameStringData(); - - return getFieldName(rep.offset); - } - - StringData getFieldNameForNewElement(const ElementRep& rep) { - StringData result = getFieldName(rep); - if (rep.objIdx == kLeafObjIdx) { - _fieldNameScratch.assign(result.rawData(), result.size()); - result = StringData(_fieldNameScratch); - } - return result; - } - - // Retrieve the type, given a rep. - BSONType getType(const ElementRep& rep) const { - // The root element is always an Object. - if (&rep == &getElementRep(kRootRepIdx)) - return mongo::Object; - - if (rep.serialized || (rep.objIdx != kInvalidObjIdx)) - return getSerializedElement(rep).type(); - - return rep.array ? mongo::Array : mongo::Object; - } - - static bool isLeafType(BSONType type) { - return ((type != mongo::Object) && (type != mongo::Array)); - } - - // Returns true if rep is not an object or array. - bool isLeaf(const ElementRep& rep) const { - return isLeafType(getType(rep)); - } - - bool isLeaf(const BSONElement& elt) const { - return isLeafType(elt.type()); - } - - // Returns true if rep's value can be provided as a BSONElement. - bool hasValue(const ElementRep& rep) const { - // The root element may be marked serialized, but it doesn't have a BSONElement - // representation. - if (&rep == &getElementRep(kRootRepIdx)) - return false; - - return rep.serialized; - } - - // Return the index of the left child of the Element with index 'index', resolving the - // left child to a realized Element if it is currently opaque. This may also cause the - // parent elements child.right entry to be updated. - Element::RepIdx resolveLeftChild(Element::RepIdx index) { - dassert(index != Element::kInvalidRepIdx); - dassert(index != Element::kOpaqueRepIdx); - - // If the left child is anything other than opaque, then we are done here. - ElementRep* rep = &getElementRep(index); - if (rep->child.left != Element::kOpaqueRepIdx) - return rep->child.left; - - // It should be impossible to have an opaque left child and be non-serialized, - dassert(rep->serialized); - BSONElement childElt = ( - hasValue(*rep) ? - getSerializedElement(*rep).embeddedObject() : - getObject(rep->objIdx)).firstElement(); - - if (!childElt.eoo()) { - - // Do this now before other writes so compiler can exploit knowing - // that we are not eoo. - const int32_t fieldNameSize = childElt.fieldNameSize(); - - Element::RepIdx inserted; - ElementRep& newRep = makeNewRep(&inserted); - // Calling makeNewRep invalidates rep since it may cause a reallocation of - // the element vector. After calling insertElement, we reacquire rep. - rep = &getElementRep(index); - - newRep.serialized = true; - newRep.objIdx = rep->objIdx; - newRep.offset = - getElementOffset(getObject(rep->objIdx), childElt); - newRep.parent = index; - newRep.sibling.right = Element::kOpaqueRepIdx; - // If this new object has possible substructure, mark its children as opaque. - if (!isLeaf(childElt)) { - newRep.child.left = Element::kOpaqueRepIdx; - newRep.child.right = Element::kOpaqueRepIdx; - } - newRep.fieldNameSize = fieldNameSize; - rep->child.left = inserted; - } else { - rep->child.left = Element::kInvalidRepIdx; - rep->child.right = Element::kInvalidRepIdx; - } - - dassert(rep->child.left != Element::kOpaqueRepIdx); - return rep->child.left; - } - - // Return the index of the right child of the Element with index 'index', resolving any - // opaque nodes. Note that this may require resolving all of the right siblings of the - // left child. - Element::RepIdx resolveRightChild(Element::RepIdx index) { - dassert(index != Element::kInvalidRepIdx); - dassert(index != Element::kOpaqueRepIdx); - - Element::RepIdx current = getElementRep(index).child.right; - if (current == Element::kOpaqueRepIdx) { - current = resolveLeftChild(index); - while (current != Element::kInvalidRepIdx) { - Element::RepIdx next = resolveRightSibling(current); - if (next == Element::kInvalidRepIdx) - break; - current = next; - } - - // The resolveRightSibling calls should have eventually updated this nodes right - // child pointer to point to the node we are about to return. - dassert(getElementRep(index).child.right == current); - } - - return current; - } - - // Return the index of the right sibling of the Element with index 'index', resolving - // the right sibling to a realized Element if it is currently opaque. - Element::RepIdx resolveRightSibling(Element::RepIdx index) { - dassert(index != Element::kInvalidRepIdx); - dassert(index != Element::kOpaqueRepIdx); - - // If the right sibling is anything other than opaque, then we are done here. - ElementRep* rep = &getElementRep(index); - if (rep->sibling.right != Element::kOpaqueRepIdx) - return rep->sibling.right; - - BSONElement elt = getSerializedElement(*rep); - BSONElement rightElt(elt.rawdata() + elt.size()); - - if (!rightElt.eoo()) { - - // Do this now before other writes so compiler can exploit knowing - // that we are not eoo. - const int32_t fieldNameSize = rightElt.fieldNameSize(); - - Element::RepIdx inserted; - ElementRep& newRep = makeNewRep(&inserted); - // Calling makeNewRep invalidates rep since it may cause a reallocation of - // the element vector. After calling insertElement, we reacquire rep. - rep = &getElementRep(index); - - newRep.serialized = true; - newRep.objIdx = rep->objIdx; - newRep.offset = - getElementOffset(getObject(rep->objIdx), rightElt); - newRep.parent = rep->parent; - newRep.sibling.left = index; - newRep.sibling.right = Element::kOpaqueRepIdx; - // If this new object has possible substructure, mark its children as opaque. - if (!isLeaf(rightElt)) { - newRep.child.left = Element::kOpaqueRepIdx; - newRep.child.right = Element::kOpaqueRepIdx; - } - newRep.fieldNameSize = fieldNameSize; - rep->sibling.right = inserted; - } else { - rep->sibling.right = Element::kInvalidRepIdx; - // If we have found the end of this object, then our (necessarily existing) - // parent's necessarily opaque right child is now determined to be us. - dassert(rep->parent <= Element::kMaxRepIdx); - ElementRep& parentRep = getElementRep(rep->parent); - dassert(parentRep.child.right == Element::kOpaqueRepIdx); - parentRep.child.right = index; - } - - dassert(rep->sibling.right != Element::kOpaqueRepIdx); - return rep->sibling.right; - } - - // Find the ElementRep at index 'index', and mark it and all of its currently - // serialized parents as non-serialized. - void deserialize(Element::RepIdx index) { - while (index != Element::kInvalidRepIdx) { - ElementRep& rep = getElementRep(index); - // It does not make sense for leaf Elements to become deserialized, and - // requests to do so indicate a bug in the implementation of the library. - dassert(!isLeaf(rep)); - if (!rep.serialized) - break; - rep.serialized = false; - index = rep.parent; - } - } - - inline bool doesNotAlias(StringData s) const { - // StringData may come from either the field name heap or the leaf builder. - return doesNotAliasLeafBuilder(s) && !inFieldNameHeap(s.rawData()); - } - - inline bool doesNotAliasLeafBuilder(StringData s) const { - return !inLeafBuilder(s.rawData()); - } - - inline bool doesNotAlias(const BSONElement& e) const { - // A BSONElement could alias the leaf builder. - return !inLeafBuilder(e.rawdata()); - } - - inline bool doesNotAlias(const BSONObj& o) const { - // A BSONObj could alias the leaf buildr. - return !inLeafBuilder(o.objdata()); - } - - // Returns true if 'data' points within the leaf BufBuilder. - inline bool inLeafBuilder(const char* data) const { - // TODO: Write up something documenting that the following is technically UB due - // to illegality of comparing pointers to different aggregates for ordering. Also, - // do we need to do anything to prevent the optimizer from compiling this out on - // that basis? I've seen clang do that. We may need to declare these volatile. On - // the other hand, these should only be being called under a dassert, so the - // optimizer is maybe not in play, and the UB is unlikely to be a problem in - // practice. - const char* const start = _leafBuf.buf(); - const char* const end = start + _leafBuf.len(); - return (data >= start) && (data < end); - } - - // Returns true if 'data' points within the field name heap. - inline bool inFieldNameHeap(const char* data) const { - if (_fieldNames.empty()) - return false; - const char* const start = &_fieldNames.front(); - const char* const end = &_fieldNames.back(); - return (data >= start) && (data < end); - } - - void reserveDamageEvents(size_t expectedEvents) { - _damages.reserve(expectedEvents); - } - - bool getInPlaceUpdates(DamageVector* damages, const char** source, size_t* size) { - - // If some operations were not in-place, set source to NULL and return false to - // inform upstream that we are not returning in-place result data. - if (_inPlaceMode == Document::kInPlaceDisabled) { - damages->clear(); - *source = NULL; - if (size) - *size = 0; - return false; - } - - // Set up the source and source size out parameters. - *source = _objects[0].objdata(); - if (size) - *size = _objects[0].objsize(); - - // Swap our damage event queue with upstream, and reset ours to an empty vector. In - // princple, we can do another round of in-place updates. - damages->swap(_damages); - _damages.clear(); - - return true; - } - - void disableInPlaceUpdates() { - _inPlaceMode = Document::kInPlaceDisabled; - } - - Document::InPlaceMode getCurrentInPlaceMode() const { - return _inPlaceMode; - } - - bool isInPlaceModeEnabled() const { - return getCurrentInPlaceMode() == Document::kInPlaceEnabled; - } - - void recordDamageEvent(DamageEvent::OffsetSizeType targetOffset, - DamageEvent::OffsetSizeType sourceOffset, - size_t size) { - _damages.push_back(DamageEvent()); - _damages.back().targetOffset = targetOffset; - _damages.back().sourceOffset = sourceOffset; - _damages.back().size = size; - if (kDebugBuild && paranoid) { - // Force damage events to new addresses to catch invalidation errors. - DamageVector new_damages(_damages); - _damages.swap(new_damages); - } - } - - // Check all preconditions on doing an in-place update, except for size match. - bool canUpdateInPlace(const ElementRep& sourceRep, const ElementRep& targetRep) { - - // NOTE: CodeWScope might arguably be excluded since it has substructure, but - // mutable doesn't permit navigation into its document, so we can handle it. - - // We can only do an in-place update to an element that is serialized and is not in - // the leaf heap. - // - // TODO: In the future, we can replace values in the leaf heap if they are of the - // same size as the origin was. For now, we don't support that. - if (!hasValue(targetRep) || (targetRep.objIdx == kLeafObjIdx)) - return false; +// An ElementRep contains the information necessary to locate the data for an Element, +// and the topology information for how the Element is related to other Elements in the +// document. +#pragma pack(push, 1) +struct ElementRep { + // The index of the BSONObj that provides the value for this Element. For nodes + // where serialized is 'false', this value may be kInvalidObjIdx to indicate that + // the Element does not have a supporting BSONObj. + typedef uint16_t ObjIdx; + ObjIdx objIdx; + + // This bit is true if this ElementRep identifies a completely serialized + // BSONElement (i.e. a region of memory with a bson type byte, a fieldname, and an + // encoded value). Changes to children of a serialized element will cause it to be + // marked as unserialized. + uint16_t serialized : 1; + + // For object like Elements where we cannot determine the type of the object by + // looking a region of memory, the 'array' bit allows us to determine whether we + // are an object or an array. + uint16_t array : 1; + + // Reserved for future use. + uint16_t reserved : 14; + + // This word either gives the offset into the BSONObj associated with this + // ElementRep where this serialized BSON element may be located, or the offset into + // the _fieldNames member of the Document where the field name for this BSON + // element may be located. + uint32_t offset; + + // The indexes of our left and right siblings in the Document. + struct { + Element::RepIdx left; + Element::RepIdx right; + } sibling; + + // The indexes of our left and right chidren in the Document. + struct { + Element::RepIdx left; + Element::RepIdx right; + } child; + + // The index of our parent in the Document. + Element::RepIdx parent; + + // The cached field name size of this element, or -1 if unknown. + int32_t fieldNameSize; +}; +#pragma pack(pop) - // sourceRep should be newly created, so it must have a value representation. - dassert(hasValue(sourceRep)); - - // For a target that has substructure, we only permit in-place updates if there - // cannot be ElementReps that reference data within the target. We don't need to - // worry about ElementReps for source, since it is newly created. The only way - // there can be ElementReps referring into substructure is if the Element has - // non-empty non-opaque child references. - if (!isLeaf(targetRep)) { - if (((targetRep.child.left != Element::kOpaqueRepIdx) && - (targetRep.child.left != Element::kInvalidRepIdx)) || - ((targetRep.child.right != Element::kOpaqueRepIdx) && - (targetRep.child.right != Element::kInvalidRepIdx))) - return false; - } +BOOST_STATIC_ASSERT(sizeof(ElementRep) == 32); + +// We want ElementRep to be a POD so Document::Impl can grow the std::vector with +// memmove. +// +// TODO: C++11 static_assert(std::is_pod<ElementRep>::value); + +// The ElementRep for the root element is always zero. +const Element::RepIdx kRootRepIdx = Element::RepIdx(0); + +// This is the object index for elements in the leaf heap. +const ElementRep::ObjIdx kLeafObjIdx = ElementRep::ObjIdx(0); + +// This is the sentinel value to indicate that we have no supporting BSONObj. +const ElementRep::ObjIdx kInvalidObjIdx = ElementRep::ObjIdx(-1); + +// This is the highest valid object index that does not overlap sentinel values. +const ElementRep::ObjIdx kMaxObjIdx = ElementRep::ObjIdx(-2); + +// Returns the offset of 'elt' within 'object' as a uint32_t. The element must be part +// of the object or the behavior is undefined. +uint32_t getElementOffset(const BSONObj& object, const BSONElement& elt) { + dassert(!elt.eoo()); + const char* const objRaw = object.objdata(); + const char* const eltRaw = elt.rawdata(); + dassert(objRaw < eltRaw); + dassert(eltRaw < objRaw + object.objsize()); + dassert(eltRaw + elt.size() <= objRaw + object.objsize()); + const ptrdiff_t offset = eltRaw - objRaw; + // BSON documents express their size as an int32_t so we should always be able to + // express the offset as a uint32_t. + verify(offset > 0); + verify(offset <= std::numeric_limits<int32_t>::max()); + return offset; +} + +// Returns true if this ElementRep is 'detached' from all other elements and can be +// added as a child, which helps ensure that we maintain a tree rather than a graph +// when adding new elements to the tree. The root element is never considered to be +// attachable. +bool canAttach(const Element::RepIdx id, const ElementRep& rep) { + return (id != kRootRepIdx) && (rep.sibling.left == Element::kInvalidRepIdx) && + (rep.sibling.right == Element::kInvalidRepIdx) && (rep.parent == Element::kInvalidRepIdx); +} + +// Returns a Status describing why 'canAttach' returned false. This function should not +// be inlined since it just makes the callers larger for no real gain. +NOINLINE_DECL Status getAttachmentError(const ElementRep& rep); +Status getAttachmentError(const ElementRep& rep) { + if (rep.sibling.left != Element::kInvalidRepIdx) + return Status(ErrorCodes::IllegalOperation, "dangling left sibling"); + if (rep.sibling.right != Element::kInvalidRepIdx) + return Status(ErrorCodes::IllegalOperation, "dangling right sibling"); + if (rep.parent != Element::kInvalidRepIdx) + return Status(ErrorCodes::IllegalOperation, "dangling parent"); + return Status(ErrorCodes::IllegalOperation, "cannot add the root as a child"); +} + + +// Enable paranoid mode to force a reallocation on mutation of the princple data +// structures in Document::Impl. This is really slow, but can be very helpful if you +// suspect an invalidation logic error and want to find it with valgrind. Paranoid mode +// only works in debug mode; it is ignored in release builds. +const bool paranoid = false; + +#if defined(__clang__) || !defined(__GNUC__) || (__GNUC__ > 4) || \ + (__GNUC__ == 4 && __GNUC_MINOR__ >= 4) +} // namespace +#endif - return true; - } +/** Document::Impl holds the Document state. Please see the file comment above for details + * on the fields of Impl and how they are used to realize the implementation of mutable + * BSON. Impl provides various utility methods to insert, lookup, and interrogate the + * Elements, BSONObj objects, field names, and builders associated with the Document. + * + * TODO: At some point, we could remove the firewall and inline the members of Impl into + * Document. + */ +class Document::Impl { + MONGO_DISALLOW_COPYING(Impl); + +public: + Impl(Document::InPlaceMode inPlaceMode) + : _numElements(0), + _slowElements(), + _objects(), + _fieldNames(), + _leafBuf(), + _leafBuilder(_leafBuf), + _fieldNameScratch(), + _damages(), + _inPlaceMode(inPlaceMode) { + // We always have a BSONObj for the leaves, and we often have + // one for our base document, so reserve 2. + _objects.reserve(2); + + // We always have at least one byte for the root field name, and we would like + // to be able to hold a few short field names without reallocation. + _fieldNames.reserve(8); + + // We need an object at _objects[0] so that we can access leaf elements we + // construct with the leaf builder in the same way we access elements serialized in + // other BSONObjs. So we call asTempObj on the builder and store the result in slot + // 0. + dassert(_objects.size() == kLeafObjIdx); + _objects.push_back(_leafBuilder.asTempObj()); + dassert(_leafBuf.len() != 0); + } + + ~Impl() { + _leafBuilder.abandon(); + } + + void reset(Document::InPlaceMode inPlaceMode) { + // Clear out the state in the vectors. + _slowElements.clear(); + _numElements = 0; + + _objects.clear(); + _fieldNames.clear(); + + // There is no way to reset the state of a BSONObjBuilder, so we need to call its + // dtor, reset the underlying buf, and re-invoke the constructor in-place. + _leafBuilder.abandon(); + _leafBuilder.~BSONObjBuilder(); + _leafBuf.reset(); + new (&_leafBuilder) BSONObjBuilder(_leafBuf); + + _fieldNameScratch.clear(); + _damages.clear(); + _inPlaceMode = inPlaceMode; + + // Ensure that we start in the same state as the ctor would leave us in. + _objects.push_back(_leafBuilder.asTempObj()); + } + + // Obtain the ElementRep for the given rep id. + ElementRep& getElementRep(Element::RepIdx id) { + return const_cast<ElementRep&>(const_cast<const Impl*>(this)->getElementRep(id)); + } + + // Obtain the ElementRep for the given rep id. + const ElementRep& getElementRep(Element::RepIdx id) const { + dassert(id < _numElements); + if (id < kFastReps) + return _fastElements[id]; + else + return _slowElements[id - kFastReps]; + } + + // Construct and return a new default initialized ElementRep. The RepIdx identifying + // the new rep is returned in the out parameter. + ElementRep& makeNewRep(Element::RepIdx* newIdx) { + const ElementRep defaultRep = {kInvalidObjIdx, + false, + false, + 0, + 0, + {Element::kInvalidRepIdx, Element::kInvalidRepIdx}, + {Element::kInvalidRepIdx, Element::kInvalidRepIdx}, + Element::kInvalidRepIdx, + -1}; + + const Element::RepIdx id = *newIdx = _numElements++; + + if (id < kFastReps) { + return _fastElements[id] = defaultRep; + } else { + verify(id <= Element::kMaxRepIdx); - template<typename Builder> - void writeElement(Element::RepIdx repIdx, Builder* builder, - const StringData* fieldName = NULL) const; - - template<typename Builder> - void writeChildren(Element::RepIdx repIdx, Builder* builder) const; - - private: - - // Insert the given field name into the field name heap, and return an ID for this - // field name. - int32_t insertFieldName(StringData fieldName) { - const uint32_t id = _fieldNames.size(); - if (!fieldName.empty()) - _fieldNames.insert( - _fieldNames.end(), - fieldName.rawData(), - fieldName.rawData() + fieldName.size()); - _fieldNames.push_back('\0'); if (kDebugBuild && paranoid) { - // Force names to new addresses to catch invalidation errors. - std::vector<char> new_fieldNames(_fieldNames); - _fieldNames.swap(new_fieldNames); + // Force all reps to new addresses to help catch invalid rep usage. + std::vector<ElementRep> newSlowElements(_slowElements); + _slowElements.swap(newSlowElements); } - return id; - } - - // Retrieve the field name with the given id. - StringData getFieldName(uint32_t fieldNameId) const { - dassert(fieldNameId < _fieldNames.size()); - return &_fieldNames[fieldNameId]; - } - - size_t _numElements; - ElementRep _fastElements[kFastReps]; - std::vector<ElementRep> _slowElements; - - std::vector<BSONObj> _objects; - std::vector<char> _fieldNames; - - // We own a BufBuilder to avoid BSONObjBuilder's ref-count mechanism which would throw - // off our offset calculations. - BufBuilder _leafBuf; - BSONObjBuilder _leafBuilder; - - // Sometimes, we need a temporary storage area for a fieldName, because the source of - // the fieldName is in the same buffer that we want to write to, potentially - // reallocating it. In such cases, we temporarily store the value here, rather than - // creating and destroying a string and its buffer each time. - std::string _fieldNameScratch; - - // Queue of damage events and status bit for whether in-place updates are possible. - DamageVector _damages; - Document::InPlaceMode _inPlaceMode; - }; - - Status Element::addSiblingLeft(Element e) { - verify(ok()); - verify(e.ok()); - verify(_doc == e._doc); - - Document::Impl& impl = getDocument().getImpl(); - ElementRep& newRep = impl.getElementRep(e._repIdx); - - // check that new element roots a clean subtree. - if (!canAttach(e._repIdx, newRep)) - return getAttachmentError(newRep); - - ElementRep& thisRep = impl.getElementRep(_repIdx); - - dassert(thisRep.parent != kOpaqueRepIdx); - if (thisRep.parent == kInvalidRepIdx) - return Status( - ErrorCodes::IllegalOperation, - "Attempt to add a sibling to an element without a parent"); - - ElementRep& parentRep = impl.getElementRep(thisRep.parent); - dassert(!impl.isLeaf(parentRep)); - - impl.disableInPlaceUpdates(); - - // The new element shares our parent. - newRep.parent = thisRep.parent; - - // We are the new element's right sibling. - newRep.sibling.right = _repIdx; - - // The new element's left sibling is our left sibling. - newRep.sibling.left = thisRep.sibling.left; - // If the new element has a left sibling after the adjustments above, then that left - // sibling must be updated to have the new element as its right sibling. - if (newRep.sibling.left != kInvalidRepIdx) - impl.getElementRep(thisRep.sibling.left).sibling.right = e._repIdx; - - // The new element becomes our left sibling. - thisRep.sibling.left = e._repIdx; - - // If we were our parent's left child, then we no longer are. Make the new right - // sibling the right child. - if (parentRep.child.left == _repIdx) - parentRep.child.left = e._repIdx; - - impl.deserialize(thisRep.parent); - - return Status::OK(); - } - - Status Element::addSiblingRight(Element e) { - verify(ok()); - verify(e.ok()); - verify(_doc == e._doc); - - Document::Impl& impl = getDocument().getImpl(); - ElementRep* newRep = &impl.getElementRep(e._repIdx); - - // check that new element roots a clean subtree. - if (!canAttach(e._repIdx, *newRep)) - return getAttachmentError(*newRep); - - ElementRep* thisRep = &impl.getElementRep(_repIdx); - - dassert(thisRep->parent != kOpaqueRepIdx); - if (thisRep->parent == kInvalidRepIdx) - return Status( - ErrorCodes::IllegalOperation, - "Attempt to add a sibling to an element without a parent"); - - ElementRep* parentRep = &impl.getElementRep(thisRep->parent); - dassert(!impl.isLeaf(*parentRep)); - - impl.disableInPlaceUpdates(); - - // If our current right sibling is opaque it needs to be resolved. This will invalidate - // our reps so we need to reacquire them. - Element::RepIdx rightSiblingIdx = thisRep->sibling.right; - if (rightSiblingIdx == kOpaqueRepIdx) { - rightSiblingIdx = impl.resolveRightSibling(_repIdx); - dassert(rightSiblingIdx != kOpaqueRepIdx); - newRep = &impl.getElementRep(e._repIdx); - thisRep = &impl.getElementRep(_repIdx); - parentRep = &impl.getElementRep(thisRep->parent); + return *_slowElements.insert(_slowElements.end(), defaultRep); } - - // The new element shares our parent. - newRep->parent = thisRep->parent; - - // We are the new element's left sibling. - newRep->sibling.left = _repIdx; - - // The new element right sibling is our right sibling. - newRep->sibling.right = rightSiblingIdx; - - // The new element becomes our right sibling. - thisRep->sibling.right = e._repIdx; - - // If the new element has a right sibling after the adjustments above, then that right - // sibling must be updated to have the new element as its left sibling. - if (newRep->sibling.right != kInvalidRepIdx) - impl.getElementRep(rightSiblingIdx).sibling.left = e._repIdx; - - // If we were our parent's right child, then we no longer are. Make the new right - // sibling the right child. - if (parentRep->child.right == _repIdx) - parentRep->child.right = e._repIdx; - - impl.deserialize(thisRep->parent); - - return Status::OK(); - } - - Status Element::remove() { - verify(ok()); - Document::Impl& impl = getDocument().getImpl(); - - // We need to realize any opaque right sibling, because we are going to need to set its - // left sibling. Do this before acquiring thisRep since otherwise we would potentially - // invalidate it. - impl.resolveRightSibling(_repIdx); - - ElementRep& thisRep = impl.getElementRep(_repIdx); - - if (thisRep.parent == kInvalidRepIdx) - return Status(ErrorCodes::IllegalOperation, "trying to remove a parentless element"); - impl.disableInPlaceUpdates(); - - // If our right sibling is not the end of the object, then set its left sibling to be - // our left sibling. - if (thisRep.sibling.right != kInvalidRepIdx) - impl.getElementRep(thisRep.sibling.right).sibling.left = thisRep.sibling.left; - - // Similarly, if our left sibling is not the beginning of the obejct, then set its - // right sibling to be our right sibling. - if (thisRep.sibling.left != kInvalidRepIdx) { - ElementRep& leftRep = impl.getElementRep(thisRep.sibling.left); - leftRep.sibling.right = thisRep.sibling.right; - } - - // If this element was our parent's right child, then our left sibling is the new right - // child. - ElementRep& parentRep = impl.getElementRep(thisRep.parent); - if (parentRep.child.right == _repIdx) - parentRep.child.right = thisRep.sibling.left; - - // Similarly, if this element was our parent's left child, then our right sibling is - // the new left child. - if (parentRep.child.left == _repIdx) - parentRep.child.left = thisRep.sibling.right; - - impl.deserialize(thisRep.parent); - - // The Element becomes detached. - thisRep.parent = kInvalidRepIdx; - thisRep.sibling.left = kInvalidRepIdx; - thisRep.sibling.right = kInvalidRepIdx; - - return Status::OK(); } - Status Element::rename(StringData newName) { - verify(ok()); - Document::Impl& impl = getDocument().getImpl(); - - if (_repIdx == kRootRepIdx) - return Status(ErrorCodes::IllegalOperation, - "Invalid attempt to rename the root element of a document"); - - dassert(impl.doesNotAlias(newName)); - - // TODO: Some rename operations may be possible to do in-place. - impl.disableInPlaceUpdates(); - - // Operations below may invalidate thisRep, so we may need to reacquire it. - ElementRep* thisRep = &impl.getElementRep(_repIdx); - - // For non-leaf serialized elements, we can realize any opaque relatives and then - // convert ourselves to deserialized. - if (thisRep->objIdx != kInvalidObjIdx && !impl.isLeaf(*thisRep)) { - - const bool array = (impl.getType(*thisRep) == mongo::Array); - - // Realize any opaque right sibling or left child now, since otherwise we will lose - // the ability to do so. - impl.resolveLeftChild(_repIdx); - impl.resolveRightSibling(_repIdx); - - // The resolve calls above may have invalidated thisRep, we need to reacquire it. - thisRep = &impl.getElementRep(_repIdx); - - // Set this up as a non-supported deserialized element. We will set the fieldName - // in the else clause in the block below. - impl.deserialize(_repIdx); - - thisRep->array = array; - - // TODO: If we ever want to be able to add to the left or right of an opaque object - // without expanding, this may need to change. - thisRep->objIdx = kInvalidObjIdx; - } - - if (impl.hasValue(*thisRep)) { - // For leaf elements we just create a new Element with the current value and - // replace. Note that the 'setValue' call below will invalidate thisRep. - Element replacement = _doc->makeElementWithNewFieldName(newName, *this); - setValue(replacement._repIdx); - } else { - // The easy case: just update what our field name offset refers to. - impl.insertFieldName(*thisRep, newName); - } + // Insert a new ElementRep for a leaf element at the given offset and return its ID. + Element::RepIdx insertLeafElement(int offset, int fieldNameSize = -1) { + // BufBuilder hands back sizes in 'int's. + Element::RepIdx inserted; + ElementRep& rep = makeNewRep(&inserted); - return Status::OK(); + rep.fieldNameSize = fieldNameSize; + rep.objIdx = kLeafObjIdx; + rep.serialized = true; + dassert(offset >= 0); + // TODO: Is this a legitimate possibility? + dassert(static_cast<unsigned int>(offset) < std::numeric_limits<uint32_t>::max()); + rep.offset = offset; + _objects[kLeafObjIdx] = _leafBuilder.asTempObj(); + return inserted; } - Element Element::leftChild() const { - verify(ok()); - - // Capturing Document::Impl by non-const ref exploits the constness loophole - // created by our Impl so that we can let leftChild be lazily evaluated, even for a - // const Element. - Document::Impl& impl = _doc->getImpl(); - const Element::RepIdx leftChildIdx = impl.resolveLeftChild(_repIdx); - dassert(leftChildIdx != kOpaqueRepIdx); - return Element(_doc, leftChildIdx); + // Obtain the object builder for the leaves. + BSONObjBuilder& leafBuilder() { + return _leafBuilder; } - Element Element::rightChild() const { - verify(ok()); - - // Capturing Document::Impl by non-const ref exploits the constness loophole - // created by our Impl so that we can let leftChild be lazily evaluated, even for a - // const Element. - Document::Impl& impl = _doc->getImpl(); - const Element::RepIdx rightChildIdx = impl.resolveRightChild(_repIdx); - dassert(rightChildIdx != kOpaqueRepIdx); - return Element(_doc, rightChildIdx); + // Obtain the BSONObj for the given object id. + BSONObj& getObject(ElementRep::ObjIdx objIdx) { + dassert(objIdx < _objects.size()); + return _objects[objIdx]; } - bool Element::hasChildren() const { - verify(ok()); - // Capturing Document::Impl by non-const ref exploits the constness loophole - // created by our Impl so that we can let leftChild be lazily evaluated, even for a - // const Element. - Document::Impl& impl = _doc->getImpl(); - return impl.resolveLeftChild(_repIdx) != kInvalidRepIdx; + // Obtain the BSONObj for the given object id. + const BSONObj& getObject(ElementRep::ObjIdx objIdx) const { + dassert(objIdx < _objects.size()); + return _objects[objIdx]; } - Element Element::leftSibling(size_t distance) const { - verify(ok()); - const Document::Impl& impl = getDocument().getImpl(); - Element::RepIdx current = _repIdx; - while ((current != kInvalidRepIdx) && (distance-- != 0)) { - // We are (currently) never left opaque, so don't need to resolve. - current = impl.getElementRep(current).sibling.left; + // Insert the given BSONObj and return an ID for it. + ElementRep::ObjIdx insertObject(const BSONObj& newObj) { + const size_t objIdx = _objects.size(); + verify(objIdx <= kMaxObjIdx); + _objects.push_back(newObj); + if (kDebugBuild && paranoid) { + // Force reallocation to catch use after invalidation. + std::vector<BSONObj> new_objects(_objects); + _objects.swap(new_objects); } - return Element(_doc, current); + return objIdx; } - Element Element::rightSibling(size_t distance) const { - verify(ok()); - - // Capturing Document::Impl by non-const ref exploits the constness loophole - // created by our Impl so that we can let rightSibling be lazily evaluated, even for a - // const Element. - Document::Impl& impl = _doc->getImpl(); - Element::RepIdx current = _repIdx; - while ((current != kInvalidRepIdx) && (distance-- != 0)) - current = impl.resolveRightSibling(current); - return Element(_doc, current); + // Given a RepIdx, return the BSONElement that it represents. + BSONElement getSerializedElement(const ElementRep& rep) const { + const BSONObj& object = getObject(rep.objIdx); + return BSONElement( + object.objdata() + rep.offset, rep.fieldNameSize, BSONElement::FieldNameSizeTag()); } - Element Element::parent() const { - verify(ok()); - const Document::Impl& impl = getDocument().getImpl(); - const Element::RepIdx parentIdx = impl.getElementRep(_repIdx).parent; - dassert(parentIdx != kOpaqueRepIdx); - return Element(_doc, parentIdx); + // A helper method that either inserts the field name into the field name heap and + // updates element. + void insertFieldName(ElementRep& rep, StringData fieldName) { + dassert(!rep.serialized); + rep.offset = insertFieldName(fieldName); } - Element Element::findNthChild(size_t n) const { - verify(ok()); - Document::Impl& impl = _doc->getImpl(); - Element::RepIdx current = _repIdx; - current = impl.resolveLeftChild(current); - while ((current != kInvalidRepIdx) && (n-- != 0)) - current = impl.resolveRightSibling(current); - return Element(_doc, current); - } + // Retrieve the fieldName, given a rep. + StringData getFieldName(const ElementRep& rep) const { + // The root element has no field name. + if (&rep == &getElementRep(kRootRepIdx)) + return StringData(); - Element Element::findFirstChildNamed(StringData name) const { - verify(ok()); - Document::Impl& impl = _doc->getImpl(); - Element::RepIdx current = _repIdx; - current = impl.resolveLeftChild(current); - // TODO: Could DRY this loop with the identical logic in findElementNamed. - while ((current != kInvalidRepIdx) && - (impl.getFieldName(impl.getElementRep(current)) != name)) - current = impl.resolveRightSibling(current); - return Element(_doc, current); - } + if (rep.serialized || (rep.objIdx != kInvalidObjIdx)) + return getSerializedElement(rep).fieldNameStringData(); - Element Element::findElementNamed(StringData name) const { - verify(ok()); - Document::Impl& impl = _doc->getImpl(); - Element::RepIdx current = _repIdx; - while ((current != kInvalidRepIdx) && - (impl.getFieldName(impl.getElementRep(current)) != name)) - current = impl.resolveRightSibling(current); - return Element(_doc, current); + return getFieldName(rep.offset); } - size_t Element::countSiblingsLeft() const { - verify(ok()); - const Document::Impl& impl = getDocument().getImpl(); - Element::RepIdx current = _repIdx; - size_t result = 0; - while (true) { - // We are (currently) never left opaque, so don't need to resolve. - current = impl.getElementRep(current).sibling.left; - if (current == kInvalidRepIdx) - break; - ++result; + StringData getFieldNameForNewElement(const ElementRep& rep) { + StringData result = getFieldName(rep); + if (rep.objIdx == kLeafObjIdx) { + _fieldNameScratch.assign(result.rawData(), result.size()); + result = StringData(_fieldNameScratch); } return result; } - size_t Element::countSiblingsRight() const { - verify(ok()); - Document::Impl& impl = _doc->getImpl(); - Element::RepIdx current = _repIdx; - size_t result = 0; - while (true) { - current = impl.resolveRightSibling(current); - if (current == kInvalidRepIdx) - break; - ++result; - } - return result; - } + // Retrieve the type, given a rep. + BSONType getType(const ElementRep& rep) const { + // The root element is always an Object. + if (&rep == &getElementRep(kRootRepIdx)) + return mongo::Object; - size_t Element::countChildren() const { - verify(ok()); - Document::Impl& impl = _doc->getImpl(); - Element::RepIdx current = _repIdx; - current = impl.resolveLeftChild(current); - size_t result = 0; - while (current != kInvalidRepIdx) { - ++result; - current = impl.resolveRightSibling(current); - } - return result; - } + if (rep.serialized || (rep.objIdx != kInvalidObjIdx)) + return getSerializedElement(rep).type(); - bool Element::hasValue() const { - verify(ok()); - const Document::Impl& impl = getDocument().getImpl(); - const ElementRep& thisRep = impl.getElementRep(_repIdx); - return impl.hasValue(thisRep); + return rep.array ? mongo::Array : mongo::Object; } - bool Element::isNumeric() const { - verify(ok()); - const Document::Impl& impl = getDocument().getImpl(); - const ElementRep& thisRep = impl.getElementRep(_repIdx); - const BSONType type = impl.getType(thisRep); - return ((type == mongo::NumberLong) || - (type == mongo::NumberInt) || - (type == mongo::NumberDouble)); + static bool isLeafType(BSONType type) { + return ((type != mongo::Object) && (type != mongo::Array)); } - bool Element::isIntegral() const { - verify(ok()); - const Document::Impl& impl = getDocument().getImpl(); - const ElementRep& thisRep = impl.getElementRep(_repIdx); - const BSONType type = impl.getType(thisRep); - return ((type == mongo::NumberLong) || - (type == mongo::NumberInt)); + // Returns true if rep is not an object or array. + bool isLeaf(const ElementRep& rep) const { + return isLeafType(getType(rep)); } - const BSONElement Element::getValue() const { - verify(ok()); - const Document::Impl& impl = getDocument().getImpl(); - const ElementRep& thisRep = impl.getElementRep(_repIdx); - if (impl.hasValue(thisRep)) - return impl.getSerializedElement(thisRep); - return BSONElement(); + bool isLeaf(const BSONElement& elt) const { + return isLeafType(elt.type()); } - SafeNum Element::getValueSafeNum() const { - switch (getType()) { - case mongo::NumberInt: - return static_cast<int>(getValueInt()); - case mongo::NumberLong: - return static_cast<long long int>(getValueLong()); - case mongo::NumberDouble: - return getValueDouble(); - default: - return SafeNum(); - } - } - - int Element::compareWithElement(const ConstElement& other, bool considerFieldName) const { - verify(ok()); - verify(other.ok()); - - // Short circuit a tautological compare. - if ((_repIdx == other.getIdx()) && (_doc == &other.getDocument())) - return 0; - - // If either Element can represent its current value as a BSONElement, then we can - // obtain its value and use compareWithBSONElement. If both Elements have a - // representation as a BSONElement, compareWithBSONElement will notice that the first - // argument has a value and delegate to BSONElement::woCompare. + // Returns true if rep's value can be provided as a BSONElement. + bool hasValue(const ElementRep& rep) const { + // The root element may be marked serialized, but it doesn't have a BSONElement + // representation. + if (&rep == &getElementRep(kRootRepIdx)) + return false; - const Document::Impl& impl = getDocument().getImpl(); - const ElementRep& thisRep = impl.getElementRep(_repIdx); - - // Subtle: we must negate the comparison result here because we are reversing the - // argument order in this call. - // - // TODO: Andy has suggested that this may not be legal since woCompare is not reflexive - // in all cases. - if (impl.hasValue(thisRep)) - return -other.compareWithBSONElement( - impl.getSerializedElement(thisRep), considerFieldName); - - const Document::Impl& oimpl = other.getDocument().getImpl(); - const ElementRep& otherRep = oimpl.getElementRep(other.getIdx()); - - if (oimpl.hasValue(otherRep)) - return compareWithBSONElement( - oimpl.getSerializedElement(otherRep), considerFieldName); - - // Leaf elements should always have a value, so we should only be dealing with Objects - // or Arrays here. - dassert(!impl.isLeaf(thisRep)); - dassert(!oimpl.isLeaf(otherRep)); - - // Obtain the canonical types for this Element and the BSONElement, if they are - // different use the difference as the result. Please see BSONElement::woCompare for - // details. We know that thisRep is not a number, so we don't need to check that - // particular case. - const int leftCanonType = canonicalizeBSONType(impl.getType(thisRep)); - const int rightCanonType = canonicalizeBSONType(oimpl.getType(otherRep)); - const int diffCanon = leftCanonType - rightCanonType; - if (diffCanon != 0) - return diffCanon; - - // If we are considering field names, and the field names do not compare as equal, - // return the field name ordering as the element ordering. - if (considerFieldName) { - const int fnamesComp = impl.getFieldName(thisRep).compare(oimpl.getFieldName(otherRep)); - if (fnamesComp != 0) - return fnamesComp; - } - - const bool considerChildFieldNames = - (impl.getType(thisRep) != mongo::Array) && - (oimpl.getType(otherRep) != mongo::Array); - - // We are dealing with either two objects, or two arrays. We need to consider the child - // elements individually. We walk two iterators forward over the children and compare - // them. Length mismatches are handled by checking early for reaching the end of the - // children. - ConstElement thisIter = leftChild(); - ConstElement otherIter = other.leftChild(); - - while (true) { - if (!thisIter.ok()) - return !otherIter.ok() ? 0 : -1; - if (!otherIter.ok()) - return 1; - - const int result = thisIter.compareWithElement(otherIter, considerChildFieldNames); - if (result != 0) - return result; - - thisIter = thisIter.rightSibling(); - otherIter = otherIter.rightSibling(); - } + return rep.serialized; } - int Element::compareWithBSONElement(const BSONElement& other, bool considerFieldName) const { - verify(ok()); - - const Document::Impl& impl = getDocument().getImpl(); - const ElementRep& thisRep = impl.getElementRep(_repIdx); - - // If we have a representation as a BSONElement, we can just use BSONElement::woCompare - // to do the entire comparison. - if (impl.hasValue(thisRep)) - return impl.getSerializedElement(thisRep).woCompare(other, considerFieldName); - - // Leaf elements should always have a value, so we should only be dealing with Objects - // or Arrays here. - dassert(!impl.isLeaf(thisRep)); - - // Obtain the canonical types for this Element and the BSONElement, if they are - // different use the difference as the result. Please see BSONElement::woCompare for - // details. We know that thisRep is not a number, so we don't need to check that - // particular case. - const int leftCanonType = canonicalizeBSONType(impl.getType(thisRep)); - const int rightCanonType = canonicalizeBSONType(other.type()); - const int diffCanon = leftCanonType - rightCanonType; - if (diffCanon != 0) - return diffCanon; - - // If we are considering field names, and the field names do not compare as equal, - // return the field name ordering as the element ordering. - if (considerFieldName) { - const int fnamesComp = impl.getFieldName(thisRep).compare(other.fieldNameStringData()); - if (fnamesComp != 0) - return fnamesComp; - } - - const bool considerChildFieldNames = - (impl.getType(thisRep) != mongo::Array) && - (other.type() != mongo::Array); + // Return the index of the left child of the Element with index 'index', resolving the + // left child to a realized Element if it is currently opaque. This may also cause the + // parent elements child.right entry to be updated. + Element::RepIdx resolveLeftChild(Element::RepIdx index) { + dassert(index != Element::kInvalidRepIdx); + dassert(index != Element::kOpaqueRepIdx); - return compareWithBSONObj(other.Obj(), considerChildFieldNames); - } - - int Element::compareWithBSONObj(const BSONObj& other, bool considerFieldName) const { - verify(ok()); - - const Document::Impl& impl = getDocument().getImpl(); - const ElementRep& thisRep = impl.getElementRep(_repIdx); - verify(!impl.isLeaf(thisRep)); - - // We are dealing with either two objects, or two arrays. We need to consider the child - // elements individually. We walk two iterators forward over the children and compare - // them. Length mismatches are handled by checking early for reaching the end of the - // children. - ConstElement thisIter = leftChild(); - BSONObjIterator otherIter(other); - - while (true) { - const BSONElement otherVal = otherIter.next(); - - if (!thisIter.ok()) - return otherVal.eoo() ? 0 : -1; - if (otherVal.eoo()) - return 1; + // If the left child is anything other than opaque, then we are done here. + ElementRep* rep = &getElementRep(index); + if (rep->child.left != Element::kOpaqueRepIdx) + return rep->child.left; - const int result = thisIter.compareWithBSONElement(otherVal, considerFieldName); - if (result != 0) - return result; + // It should be impossible to have an opaque left child and be non-serialized, + dassert(rep->serialized); + BSONElement childElt = (hasValue(*rep) ? getSerializedElement(*rep).embeddedObject() + : getObject(rep->objIdx)).firstElement(); - thisIter = thisIter.rightSibling(); - } - } + if (!childElt.eoo()) { + // Do this now before other writes so compiler can exploit knowing + // that we are not eoo. + const int32_t fieldNameSize = childElt.fieldNameSize(); - void Element::writeTo(BSONObjBuilder* const builder) const { - verify(ok()); - const Document::Impl& impl = getDocument().getImpl(); - const ElementRep& thisRep = impl.getElementRep(_repIdx); - verify(impl.getType(thisRep) == mongo::Object); - if (thisRep.parent == kInvalidRepIdx && _repIdx == kRootRepIdx) { - // If this is the root element, then we need to handle it differently, since it - // doesn't have a field name and should embed directly, rather than as an object. - impl.writeChildren(_repIdx, builder); + Element::RepIdx inserted; + ElementRep& newRep = makeNewRep(&inserted); + // Calling makeNewRep invalidates rep since it may cause a reallocation of + // the element vector. After calling insertElement, we reacquire rep. + rep = &getElementRep(index); + + newRep.serialized = true; + newRep.objIdx = rep->objIdx; + newRep.offset = getElementOffset(getObject(rep->objIdx), childElt); + newRep.parent = index; + newRep.sibling.right = Element::kOpaqueRepIdx; + // If this new object has possible substructure, mark its children as opaque. + if (!isLeaf(childElt)) { + newRep.child.left = Element::kOpaqueRepIdx; + newRep.child.right = Element::kOpaqueRepIdx; + } + newRep.fieldNameSize = fieldNameSize; + rep->child.left = inserted; } else { - impl.writeElement(_repIdx, builder); + rep->child.left = Element::kInvalidRepIdx; + rep->child.right = Element::kInvalidRepIdx; } - } - void Element::writeArrayTo(BSONArrayBuilder* const builder) const { - verify(ok()); - const Document::Impl& impl = getDocument().getImpl(); - const ElementRep& thisRep = impl.getElementRep(_repIdx); - verify(impl.getType(thisRep) == mongo::Array); - return impl.writeChildren(_repIdx, builder); + dassert(rep->child.left != Element::kOpaqueRepIdx); + return rep->child.left; } - Status Element::setValueDouble(const double value) { - verify(ok()); - Document::Impl& impl = getDocument().getImpl(); - ElementRep thisRep = impl.getElementRep(_repIdx); - const StringData fieldName = impl.getFieldNameForNewElement(thisRep); - Element newValue = getDocument().makeElementDouble(fieldName, value); - return setValue(newValue._repIdx); - } + // Return the index of the right child of the Element with index 'index', resolving any + // opaque nodes. Note that this may require resolving all of the right siblings of the + // left child. + Element::RepIdx resolveRightChild(Element::RepIdx index) { + dassert(index != Element::kInvalidRepIdx); + dassert(index != Element::kOpaqueRepIdx); - Status Element::setValueString(StringData value) { - verify(ok()); - Document::Impl& impl = getDocument().getImpl(); + Element::RepIdx current = getElementRep(index).child.right; + if (current == Element::kOpaqueRepIdx) { + current = resolveLeftChild(index); + while (current != Element::kInvalidRepIdx) { + Element::RepIdx next = resolveRightSibling(current); + if (next == Element::kInvalidRepIdx) + break; + current = next; + } - dassert(impl.doesNotAlias(value)); + // The resolveRightSibling calls should have eventually updated this nodes right + // child pointer to point to the node we are about to return. + dassert(getElementRep(index).child.right == current); + } - const ElementRep& thisRep = impl.getElementRep(_repIdx); - const StringData fieldName = impl.getFieldNameForNewElement(thisRep); - Element newValue = getDocument().makeElementString(fieldName, value); - return setValue(newValue._repIdx); + return current; } - Status Element::setValueObject(const BSONObj& value) { - verify(ok()); - Document::Impl& impl = getDocument().getImpl(); + // Return the index of the right sibling of the Element with index 'index', resolving + // the right sibling to a realized Element if it is currently opaque. + Element::RepIdx resolveRightSibling(Element::RepIdx index) { + dassert(index != Element::kInvalidRepIdx); + dassert(index != Element::kOpaqueRepIdx); - dassert(impl.doesNotAlias(value)); - - const ElementRep& thisRep = impl.getElementRep(_repIdx); - const StringData fieldName = impl.getFieldNameForNewElement(thisRep); - Element newValue = getDocument().makeElementObject(fieldName, value); - return setValue(newValue._repIdx); - } + // If the right sibling is anything other than opaque, then we are done here. + ElementRep* rep = &getElementRep(index); + if (rep->sibling.right != Element::kOpaqueRepIdx) + return rep->sibling.right; - Status Element::setValueArray(const BSONObj& value) { - verify(ok()); - Document::Impl& impl = getDocument().getImpl(); + BSONElement elt = getSerializedElement(*rep); + BSONElement rightElt(elt.rawdata() + elt.size()); - dassert(impl.doesNotAlias(value)); + if (!rightElt.eoo()) { + // Do this now before other writes so compiler can exploit knowing + // that we are not eoo. + const int32_t fieldNameSize = rightElt.fieldNameSize(); - const ElementRep& thisRep = impl.getElementRep(_repIdx); - const StringData fieldName = impl.getFieldNameForNewElement(thisRep); - Element newValue = getDocument().makeElementArray(fieldName, value); - return setValue(newValue._repIdx); + Element::RepIdx inserted; + ElementRep& newRep = makeNewRep(&inserted); + // Calling makeNewRep invalidates rep since it may cause a reallocation of + // the element vector. After calling insertElement, we reacquire rep. + rep = &getElementRep(index); + + newRep.serialized = true; + newRep.objIdx = rep->objIdx; + newRep.offset = getElementOffset(getObject(rep->objIdx), rightElt); + newRep.parent = rep->parent; + newRep.sibling.left = index; + newRep.sibling.right = Element::kOpaqueRepIdx; + // If this new object has possible substructure, mark its children as opaque. + if (!isLeaf(rightElt)) { + newRep.child.left = Element::kOpaqueRepIdx; + newRep.child.right = Element::kOpaqueRepIdx; + } + newRep.fieldNameSize = fieldNameSize; + rep->sibling.right = inserted; + } else { + rep->sibling.right = Element::kInvalidRepIdx; + // If we have found the end of this object, then our (necessarily existing) + // parent's necessarily opaque right child is now determined to be us. + dassert(rep->parent <= Element::kMaxRepIdx); + ElementRep& parentRep = getElementRep(rep->parent); + dassert(parentRep.child.right == Element::kOpaqueRepIdx); + parentRep.child.right = index; + } + + dassert(rep->sibling.right != Element::kOpaqueRepIdx); + return rep->sibling.right; + } + + // Find the ElementRep at index 'index', and mark it and all of its currently + // serialized parents as non-serialized. + void deserialize(Element::RepIdx index) { + while (index != Element::kInvalidRepIdx) { + ElementRep& rep = getElementRep(index); + // It does not make sense for leaf Elements to become deserialized, and + // requests to do so indicate a bug in the implementation of the library. + dassert(!isLeaf(rep)); + if (!rep.serialized) + break; + rep.serialized = false; + index = rep.parent; + } } - Status Element::setValueBinary(const uint32_t len, mongo::BinDataType binType, - const void* const data) { - verify(ok()); - Document::Impl& impl = getDocument().getImpl(); - - // TODO: Alias check for binary data? - - const ElementRep& thisRep = impl.getElementRep(_repIdx); - const StringData fieldName = impl.getFieldNameForNewElement(thisRep); - Element newValue = getDocument().makeElementBinary( - fieldName, len, binType, data); - return setValue(newValue._repIdx); + inline bool doesNotAlias(StringData s) const { + // StringData may come from either the field name heap or the leaf builder. + return doesNotAliasLeafBuilder(s) && !inFieldNameHeap(s.rawData()); } - Status Element::setValueUndefined() { - verify(ok()); - Document::Impl& impl = getDocument().getImpl(); - const ElementRep& thisRep = impl.getElementRep(_repIdx); - const StringData fieldName = impl.getFieldNameForNewElement(thisRep); - Element newValue = getDocument().makeElementUndefined(fieldName); - return setValue(newValue._repIdx); + inline bool doesNotAliasLeafBuilder(StringData s) const { + return !inLeafBuilder(s.rawData()); } - Status Element::setValueOID(const OID value) { - verify(ok()); - Document::Impl& impl = getDocument().getImpl(); - const ElementRep& thisRep = impl.getElementRep(_repIdx); - const StringData fieldName = impl.getFieldNameForNewElement(thisRep); - Element newValue = getDocument().makeElementOID(fieldName, value); - return setValue(newValue._repIdx); + inline bool doesNotAlias(const BSONElement& e) const { + // A BSONElement could alias the leaf builder. + return !inLeafBuilder(e.rawdata()); } - Status Element::setValueBool(const bool value) { - verify(ok()); - Document::Impl& impl = getDocument().getImpl(); - ElementRep thisRep = impl.getElementRep(_repIdx); - const StringData fieldName = impl.getFieldNameForNewElement(thisRep); - Element newValue = getDocument().makeElementBool(fieldName, value); - return setValue(newValue._repIdx); + inline bool doesNotAlias(const BSONObj& o) const { + // A BSONObj could alias the leaf buildr. + return !inLeafBuilder(o.objdata()); } - Status Element::setValueDate(const Date_t value) { - verify(ok()); - Document::Impl& impl = getDocument().getImpl(); - const ElementRep& thisRep = impl.getElementRep(_repIdx); - const StringData fieldName = impl.getFieldNameForNewElement(thisRep); - Element newValue = getDocument().makeElementDate(fieldName, value); - return setValue(newValue._repIdx); + // Returns true if 'data' points within the leaf BufBuilder. + inline bool inLeafBuilder(const char* data) const { + // TODO: Write up something documenting that the following is technically UB due + // to illegality of comparing pointers to different aggregates for ordering. Also, + // do we need to do anything to prevent the optimizer from compiling this out on + // that basis? I've seen clang do that. We may need to declare these volatile. On + // the other hand, these should only be being called under a dassert, so the + // optimizer is maybe not in play, and the UB is unlikely to be a problem in + // practice. + const char* const start = _leafBuf.buf(); + const char* const end = start + _leafBuf.len(); + return (data >= start) && (data < end); } - Status Element::setValueNull() { - verify(ok()); - Document::Impl& impl = getDocument().getImpl(); - const ElementRep& thisRep = impl.getElementRep(_repIdx); - const StringData fieldName = impl.getFieldNameForNewElement(thisRep); - Element newValue = getDocument().makeElementNull(fieldName); - return setValue(newValue._repIdx); + // Returns true if 'data' points within the field name heap. + inline bool inFieldNameHeap(const char* data) const { + if (_fieldNames.empty()) + return false; + const char* const start = &_fieldNames.front(); + const char* const end = &_fieldNames.back(); + return (data >= start) && (data < end); } - Status Element::setValueRegex(StringData re, StringData flags) { - verify(ok()); - Document::Impl& impl = getDocument().getImpl(); - - dassert(impl.doesNotAlias(re)); - dassert(impl.doesNotAlias(flags)); - - const ElementRep& thisRep = impl.getElementRep(_repIdx); - const StringData fieldName = impl.getFieldNameForNewElement(thisRep); - Element newValue = getDocument().makeElementRegex(fieldName, re, flags); - return setValue(newValue._repIdx); + void reserveDamageEvents(size_t expectedEvents) { + _damages.reserve(expectedEvents); } - Status Element::setValueDBRef(StringData ns, const OID oid) { - verify(ok()); - Document::Impl& impl = getDocument().getImpl(); - - dassert(impl.doesNotAlias(ns)); - - const ElementRep& thisRep = impl.getElementRep(_repIdx); - const StringData fieldName = impl.getFieldNameForNewElement(thisRep); - Element newValue = getDocument().makeElementDBRef(fieldName, ns, oid); - return setValue(newValue._repIdx); - } + bool getInPlaceUpdates(DamageVector* damages, const char** source, size_t* size) { + // If some operations were not in-place, set source to NULL and return false to + // inform upstream that we are not returning in-place result data. + if (_inPlaceMode == Document::kInPlaceDisabled) { + damages->clear(); + *source = NULL; + if (size) + *size = 0; + return false; + } - Status Element::setValueCode(StringData value) { - verify(ok()); - Document::Impl& impl = getDocument().getImpl(); + // Set up the source and source size out parameters. + *source = _objects[0].objdata(); + if (size) + *size = _objects[0].objsize(); - dassert(impl.doesNotAlias(value)); + // Swap our damage event queue with upstream, and reset ours to an empty vector. In + // princple, we can do another round of in-place updates. + damages->swap(_damages); + _damages.clear(); - const ElementRep& thisRep = impl.getElementRep(_repIdx); - const StringData fieldName = impl.getFieldNameForNewElement(thisRep); - Element newValue = getDocument().makeElementCode(fieldName, value); - return setValue(newValue._repIdx); + return true; } - Status Element::setValueSymbol(StringData value) { - verify(ok()); - Document::Impl& impl = getDocument().getImpl(); - - dassert(impl.doesNotAlias(value)); - - const ElementRep& thisRep = impl.getElementRep(_repIdx); - const StringData fieldName = impl.getFieldNameForNewElement(thisRep); - Element newValue = getDocument().makeElementSymbol(fieldName, value); - return setValue(newValue._repIdx); + void disableInPlaceUpdates() { + _inPlaceMode = Document::kInPlaceDisabled; } - Status Element::setValueCodeWithScope(StringData code, const BSONObj& scope) { - verify(ok()); - Document::Impl& impl = getDocument().getImpl(); - - dassert(impl.doesNotAlias(code)); - dassert(impl.doesNotAlias(scope)); - - const ElementRep& thisRep = impl.getElementRep(_repIdx); - const StringData fieldName = impl.getFieldNameForNewElement(thisRep); - Element newValue = getDocument().makeElementCodeWithScope( - fieldName, code, scope); - return setValue(newValue._repIdx); + Document::InPlaceMode getCurrentInPlaceMode() const { + return _inPlaceMode; } - Status Element::setValueInt(const int32_t value) { - verify(ok()); - Document::Impl& impl = getDocument().getImpl(); - ElementRep thisRep = impl.getElementRep(_repIdx); - const StringData fieldName = impl.getFieldNameForNewElement(thisRep); - Element newValue = getDocument().makeElementInt(fieldName, value); - return setValue(newValue._repIdx); + bool isInPlaceModeEnabled() const { + return getCurrentInPlaceMode() == Document::kInPlaceEnabled; } - Status Element::setValueTimestamp(const Timestamp value) { - verify(ok()); - Document::Impl& impl = getDocument().getImpl(); - const ElementRep& thisRep = impl.getElementRep(_repIdx); - const StringData fieldName = impl.getFieldNameForNewElement(thisRep); - Element newValue = getDocument().makeElementTimestamp(fieldName, value); - return setValue(newValue._repIdx); + void recordDamageEvent(DamageEvent::OffsetSizeType targetOffset, + DamageEvent::OffsetSizeType sourceOffset, + size_t size) { + _damages.push_back(DamageEvent()); + _damages.back().targetOffset = targetOffset; + _damages.back().sourceOffset = sourceOffset; + _damages.back().size = size; + if (kDebugBuild && paranoid) { + // Force damage events to new addresses to catch invalidation errors. + DamageVector new_damages(_damages); + _damages.swap(new_damages); + } } - Status Element::setValueLong(const int64_t value) { - verify(ok()); - Document::Impl& impl = getDocument().getImpl(); - ElementRep thisRep = impl.getElementRep(_repIdx); - const StringData fieldName = impl.getFieldNameForNewElement(thisRep); - Element newValue = getDocument().makeElementLong(fieldName, value); - return setValue(newValue._repIdx); - } + // Check all preconditions on doing an in-place update, except for size match. + bool canUpdateInPlace(const ElementRep& sourceRep, const ElementRep& targetRep) { + // NOTE: CodeWScope might arguably be excluded since it has substructure, but + // mutable doesn't permit navigation into its document, so we can handle it. - Status Element::setValueMinKey() { - verify(ok()); - Document::Impl& impl = getDocument().getImpl(); - const ElementRep& thisRep = impl.getElementRep(_repIdx); - const StringData fieldName = impl.getFieldNameForNewElement(thisRep); - Element newValue = getDocument().makeElementMinKey(fieldName); - return setValue(newValue._repIdx); - } + // We can only do an in-place update to an element that is serialized and is not in + // the leaf heap. + // + // TODO: In the future, we can replace values in the leaf heap if they are of the + // same size as the origin was. For now, we don't support that. + if (!hasValue(targetRep) || (targetRep.objIdx == kLeafObjIdx)) + return false; + + // sourceRep should be newly created, so it must have a value representation. + dassert(hasValue(sourceRep)); + + // For a target that has substructure, we only permit in-place updates if there + // cannot be ElementReps that reference data within the target. We don't need to + // worry about ElementReps for source, since it is newly created. The only way + // there can be ElementReps referring into substructure is if the Element has + // non-empty non-opaque child references. + if (!isLeaf(targetRep)) { + if (((targetRep.child.left != Element::kOpaqueRepIdx) && + (targetRep.child.left != Element::kInvalidRepIdx)) || + ((targetRep.child.right != Element::kOpaqueRepIdx) && + (targetRep.child.right != Element::kInvalidRepIdx))) + return false; + } - Status Element::setValueMaxKey() { - verify(ok()); - Document::Impl& impl = getDocument().getImpl(); - const ElementRep& thisRep = impl.getElementRep(_repIdx); - const StringData fieldName = impl.getFieldNameForNewElement(thisRep); - Element newValue = getDocument().makeElementMaxKey(fieldName); - return setValue(newValue._repIdx); + return true; } - Status Element::setValueBSONElement(const BSONElement& value) { - verify(ok()); - - if (value.type() == mongo::EOO) - return Status(ErrorCodes::IllegalOperation, "Can't set Element value to EOO"); - - Document::Impl& impl = getDocument().getImpl(); + template <typename Builder> + void writeElement(Element::RepIdx repIdx, + Builder* builder, + const StringData* fieldName = NULL) const; - dassert(impl.doesNotAlias(value)); - - ElementRep thisRep = impl.getElementRep(_repIdx); - const StringData fieldName = impl.getFieldNameForNewElement(thisRep); - Element newValue = getDocument().makeElementWithNewFieldName(fieldName, value); - return setValue(newValue._repIdx); - } + template <typename Builder> + void writeChildren(Element::RepIdx repIdx, Builder* builder) const; - Status Element::setValueSafeNum(const SafeNum value) { - verify(ok()); - switch (value.type()) { - case mongo::NumberInt: - return setValueInt(value._value.int32Val); - case mongo::NumberLong: - return setValueLong(value._value.int64Val); - case mongo::NumberDouble: - return setValueDouble(value._value.doubleVal); - default: - return Status( - ErrorCodes::UnsupportedFormat, - "Don't know how to handle unexpected SafeNum type"); +private: + // Insert the given field name into the field name heap, and return an ID for this + // field name. + int32_t insertFieldName(StringData fieldName) { + const uint32_t id = _fieldNames.size(); + if (!fieldName.empty()) + _fieldNames.insert( + _fieldNames.end(), fieldName.rawData(), fieldName.rawData() + fieldName.size()); + _fieldNames.push_back('\0'); + if (kDebugBuild && paranoid) { + // Force names to new addresses to catch invalidation errors. + std::vector<char> new_fieldNames(_fieldNames); + _fieldNames.swap(new_fieldNames); } + return id; } - BSONType Element::getType() const { - verify(ok()); - const Document::Impl& impl = getDocument().getImpl(); - const ElementRep& thisRep = impl.getElementRep(_repIdx); - return impl.getType(thisRep); + // Retrieve the field name with the given id. + StringData getFieldName(uint32_t fieldNameId) const { + dassert(fieldNameId < _fieldNames.size()); + return &_fieldNames[fieldNameId]; } - StringData Element::getFieldName() const { - verify(ok()); - const Document::Impl& impl = getDocument().getImpl(); - const ElementRep& thisRep = impl.getElementRep(_repIdx); - return impl.getFieldName(thisRep); - } + size_t _numElements; + ElementRep _fastElements[kFastReps]; + std::vector<ElementRep> _slowElements; - Status Element::addChild(Element e, bool front) { - // No need to verify(ok()) since we are only called from methods that have done so. - dassert(ok()); - - verify(e.ok()); - verify(_doc == e._doc); - - Document::Impl& impl = getDocument().getImpl(); - ElementRep& newRep = impl.getElementRep(e._repIdx); - - // check that new element roots a clean subtree. - if (!canAttach(e._repIdx, newRep)) - return getAttachmentError(newRep); - - // Check that this element is eligible for children. - ElementRep& thisRep = impl.getElementRep(_repIdx); - if (impl.isLeaf(thisRep)) - return Status( - ErrorCodes::IllegalOperation, - "Attempt to add a child element to a non-object element"); - - impl.disableInPlaceUpdates(); - - // TODO: In both of the following cases, we call two public API methods each. We can - // probably do better by writing this explicitly here and drying it with the public - // addSiblingLeft and addSiblingRight implementations. - if (front) { - // TODO: It is cheap to get the left child. However, it still means creating a rep - // for it. Can we do better? - Element lc = leftChild(); - if (lc.ok()) - return lc.addSiblingLeft(e); - } else { - // TODO: It is expensive to get the right child, since we have to build reps for - // all of the opaque children. But in principle, we don't really need them. Could - // we potentially add this element as a right child, leaving its left sibling - // opaque? We would at minimum need to update leftSibling, which currently assumes - // that your left sibling is never opaque. But adding new Elements to the end is a - // quite common operation, so it would be nice if we could do this efficiently. - Element rc = rightChild(); - if (rc.ok()) - return rc.addSiblingRight(e); - } + std::vector<BSONObj> _objects; + std::vector<char> _fieldNames; - // It must be the case that we have no children, so the new element becomes both the - // right and left child of this node. - dassert((thisRep.child.left == kInvalidRepIdx) && (thisRep.child.right == kInvalidRepIdx)); - thisRep.child.left = thisRep.child.right = e._repIdx; - newRep.parent = _repIdx; - impl.deserialize(_repIdx); - return Status::OK(); - } + // We own a BufBuilder to avoid BSONObjBuilder's ref-count mechanism which would throw + // off our offset calculations. + BufBuilder _leafBuf; + BSONObjBuilder _leafBuilder; - Status Element::setValue(const Element::RepIdx newValueIdx) { - // No need to verify(ok()) since we are only called from methods that have done so. - dassert(ok()); + // Sometimes, we need a temporary storage area for a fieldName, because the source of + // the fieldName is in the same buffer that we want to write to, potentially + // reallocating it. In such cases, we temporarily store the value here, rather than + // creating and destroying a string and its buffer each time. + std::string _fieldNameScratch; - if (_repIdx == kRootRepIdx) - return Status(ErrorCodes::IllegalOperation, "Cannot call setValue on the root object"); + // Queue of damage events and status bit for whether in-place updates are possible. + DamageVector _damages; + Document::InPlaceMode _inPlaceMode; +}; - Document::Impl& impl = getDocument().getImpl(); +Status Element::addSiblingLeft(Element e) { + verify(ok()); + verify(e.ok()); + verify(_doc == e._doc); - // Establish our right sibling in case it is opaque. Otherwise, we would lose the - // ability to do so after the modifications below. It is important that this occur - // before we acquire thisRep and valueRep since otherwise we would potentially - // invalidate them. - impl.resolveRightSibling(_repIdx); + Document::Impl& impl = getDocument().getImpl(); + ElementRep& newRep = impl.getElementRep(e._repIdx); - ElementRep& thisRep = impl.getElementRep(_repIdx); - ElementRep& valueRep = impl.getElementRep(newValueIdx); + // check that new element roots a clean subtree. + if (!canAttach(e._repIdx, newRep)) + return getAttachmentError(newRep); - if (impl.isInPlaceModeEnabled() && impl.canUpdateInPlace(valueRep, thisRep)) { + ElementRep& thisRep = impl.getElementRep(_repIdx); - // Get the BSONElement representations of the existing and new value, so we can - // check if they are size compatible. - BSONElement thisElt = impl.getSerializedElement(thisRep); - BSONElement valueElt = impl.getSerializedElement(valueRep); + dassert(thisRep.parent != kOpaqueRepIdx); + if (thisRep.parent == kInvalidRepIdx) + return Status(ErrorCodes::IllegalOperation, + "Attempt to add a sibling to an element without a parent"); - if (thisElt.size() == valueElt.size()) { + ElementRep& parentRep = impl.getElementRep(thisRep.parent); + dassert(!impl.isLeaf(parentRep)); - // The old and new elements are size compatible. Compute the base offsets - // of each BSONElement in the object in which it resides. We use these to - // calculate the source and target offsets in the damage entries we are - // going to write. + impl.disableInPlaceUpdates(); - const DamageEvent::OffsetSizeType targetBaseOffset = - getElementOffset(impl.getObject(thisRep.objIdx), thisElt); + // The new element shares our parent. + newRep.parent = thisRep.parent; - const DamageEvent::OffsetSizeType sourceBaseOffset = - getElementOffset(impl.getObject(valueRep.objIdx), valueElt); + // We are the new element's right sibling. + newRep.sibling.right = _repIdx; - // If this is a type change, record a damage event for the new type. - if (thisElt.type() != valueElt.type()) { - impl.recordDamageEvent(targetBaseOffset, sourceBaseOffset, 1); - } + // The new element's left sibling is our left sibling. + newRep.sibling.left = thisRep.sibling.left; - dassert(thisElt.fieldNameSize() == valueElt.fieldNameSize()); - dassert(thisElt.valuesize() == valueElt.valuesize()); + // If the new element has a left sibling after the adjustments above, then that left + // sibling must be updated to have the new element as its right sibling. + if (newRep.sibling.left != kInvalidRepIdx) + impl.getElementRep(thisRep.sibling.left).sibling.right = e._repIdx; - // Record a damage event for the new value data. - impl.recordDamageEvent( - targetBaseOffset + thisElt.fieldNameSize() + 1, - sourceBaseOffset + thisElt.fieldNameSize() + 1, - thisElt.valuesize()); - } else { + // The new element becomes our left sibling. + thisRep.sibling.left = e._repIdx; - // We couldn't do it in place, so disable future in-place updates. - impl.disableInPlaceUpdates(); + // If we were our parent's left child, then we no longer are. Make the new right + // sibling the right child. + if (parentRep.child.left == _repIdx) + parentRep.child.left = e._repIdx; - } - } - - // If we are not rootish, then wire in the new value among our relations. - if (thisRep.parent != kInvalidRepIdx) { - valueRep.parent = thisRep.parent; - valueRep.sibling.left = thisRep.sibling.left; - valueRep.sibling.right = thisRep.sibling.right; - } - - // Copy the rep for value to our slot so that our repIdx is unmodified. - thisRep = valueRep; + impl.deserialize(thisRep.parent); - // Be nice and clear out the source rep to make debugging easier. - valueRep = ElementRep(); + return Status::OK(); +} - impl.deserialize(thisRep.parent); - return Status::OK(); - } +Status Element::addSiblingRight(Element e) { + verify(ok()); + verify(e.ok()); + verify(_doc == e._doc); + Document::Impl& impl = getDocument().getImpl(); + ElementRep* newRep = &impl.getElementRep(e._repIdx); - namespace { - - // A helper for Element::writeElement below. For cases where we are building inside an - // array, we want to ignore field names. So the specialization for BSONArrayBuilder ignores - // the third parameter. - template<typename Builder> - struct SubBuilder; - - template<> - struct SubBuilder<BSONObjBuilder> { - SubBuilder(BSONObjBuilder* builder, BSONType type, StringData fieldName) - : buffer( - (type == mongo::Array) ? - builder->subarrayStart(fieldName) : - builder->subobjStart(fieldName)) {} - BufBuilder& buffer; - }; - - template<> - struct SubBuilder<BSONArrayBuilder> { - SubBuilder(BSONArrayBuilder* builder, BSONType type, StringData) - : buffer( - (type == mongo::Array) ? - builder->subarrayStart() : - builder->subobjStart()) {} - BufBuilder& buffer; - }; - - static void appendElement(BSONObjBuilder* builder, - const BSONElement& element, - const StringData* fieldName) { - if (fieldName) - builder->appendAs(element, *fieldName); - else - builder->append(element); - } + // check that new element roots a clean subtree. + if (!canAttach(e._repIdx, *newRep)) + return getAttachmentError(*newRep); - // BSONArrayBuilder should not be appending elements with a fieldName - static void appendElement(BSONArrayBuilder* builder, - const BSONElement& element, - const StringData* fieldName) { - invariant(!fieldName); - builder->append(element); - } + ElementRep* thisRep = &impl.getElementRep(_repIdx); - } // namespace + dassert(thisRep->parent != kOpaqueRepIdx); + if (thisRep->parent == kInvalidRepIdx) + return Status(ErrorCodes::IllegalOperation, + "Attempt to add a sibling to an element without a parent"); - template<typename Builder> - void Document::Impl::writeElement(Element::RepIdx repIdx, Builder* builder, - const StringData* fieldName) const { + ElementRep* parentRep = &impl.getElementRep(thisRep->parent); + dassert(!impl.isLeaf(*parentRep)); - const ElementRep& rep = getElementRep(repIdx); + impl.disableInPlaceUpdates(); - if (hasValue(rep)) { - appendElement(builder, getSerializedElement(rep), fieldName); - } else { - const BSONType type = getType(rep); - const StringData subName = fieldName ? *fieldName : getFieldName(rep); - SubBuilder<Builder> subBuilder(builder, type, subName); - - // Otherwise, this is a 'dirty leaf', which is impossible. - dassert((type == mongo::Array) || (type == mongo::Object)); - - if (type == mongo::Array) { - BSONArrayBuilder child_builder(subBuilder.buffer); - writeChildren(repIdx, &child_builder); - child_builder.doneFast(); - } else { - BSONObjBuilder child_builder(subBuilder.buffer); - writeChildren(repIdx, &child_builder); - child_builder.doneFast(); - } - } + // If our current right sibling is opaque it needs to be resolved. This will invalidate + // our reps so we need to reacquire them. + Element::RepIdx rightSiblingIdx = thisRep->sibling.right; + if (rightSiblingIdx == kOpaqueRepIdx) { + rightSiblingIdx = impl.resolveRightSibling(_repIdx); + dassert(rightSiblingIdx != kOpaqueRepIdx); + newRep = &impl.getElementRep(e._repIdx); + thisRep = &impl.getElementRep(_repIdx); + parentRep = &impl.getElementRep(thisRep->parent); } - template<typename Builder> - void Document::Impl::writeChildren(Element::RepIdx repIdx, Builder* builder) const { - - // TODO: In theory, I think we can walk rightwards building a write region from all - // serialized embedded children that share an obj id and form a contiguous memory - // region. For arrays we would need to know something about how many elements we wrote - // that way so that the indexes would come out right. - // - // However, that involves walking the memory twice: once to build the copy region, and - // another time to actually copy it. It is unclear if this is better than just walking - // it once with the recursive solution. - - const ElementRep& rep = getElementRep(repIdx); - - // OK, need to resolve left if we haven't done that yet. - Element::RepIdx current = rep.child.left; - if (current == Element::kOpaqueRepIdx) - current = const_cast<Impl*>(this)->resolveLeftChild(repIdx); - - // We need to write the element, and then walk rightwards. - while (current != Element::kInvalidRepIdx) { - writeElement(current, builder); - - // If we have an opaque region to the right, and we are not in an array, then we - // can bulk copy from the end of the element we just wrote to the end of our - // parent. - const ElementRep& currentRep = getElementRep(current); + // The new element shares our parent. + newRep->parent = thisRep->parent; - if (currentRep.sibling.right == Element::kOpaqueRepIdx) { + // We are the new element's left sibling. + newRep->sibling.left = _repIdx; - // Obtain the current parent, so we can see if we can bulk copy the right - // siblings. - const ElementRep& parentRep = getElementRep(currentRep.parent); + // The new element right sibling is our right sibling. + newRep->sibling.right = rightSiblingIdx; - // Bulk copying right only works on objects - if ((getType(parentRep) == mongo::Object) && - (currentRep.objIdx != kInvalidObjIdx) && - (currentRep.objIdx == parentRep.objIdx)) { + // The new element becomes our right sibling. + thisRep->sibling.right = e._repIdx; - BSONElement currentElt = getSerializedElement(currentRep); - const uint32_t currentSize = currentElt.size(); + // If the new element has a right sibling after the adjustments above, then that right + // sibling must be updated to have the new element as its left sibling. + if (newRep->sibling.right != kInvalidRepIdx) + impl.getElementRep(rightSiblingIdx).sibling.left = e._repIdx; - const BSONObj parentObj = (currentRep.parent == kRootRepIdx) ? - getObject(parentRep.objIdx) : - getSerializedElement(parentRep).Obj(); - const uint32_t parentSize = parentObj.objsize(); + // If we were our parent's right child, then we no longer are. Make the new right + // sibling the right child. + if (parentRep->child.right == _repIdx) + parentRep->child.right = e._repIdx; - const uint32_t currentEltOffset = getElementOffset(parentObj, currentElt); - const uint32_t nextEltOffset = currentEltOffset + currentSize; + impl.deserialize(thisRep->parent); - const char* copyBegin = parentObj.objdata() + nextEltOffset; - const uint32_t copyBytes = parentSize - nextEltOffset; + return Status::OK(); +} - // The -1 is because we don't want to copy in the terminal EOO. - builder->bb().appendBuf(copyBegin, copyBytes - 1); +Status Element::remove() { + verify(ok()); + Document::Impl& impl = getDocument().getImpl(); - // We are done with all children. - break; - } - - // We couldn't bulk copy, and our right sibling is opaque. We need to - // resolve. Note that the call to resolve may invalidate 'currentRep', so - // rather than falling through and acquiring the index by examining currentRep, - // update it with the return value of resolveRightSibling and restart the loop. - current = const_cast<Impl*>(this)->resolveRightSibling(current); - continue; - } + // We need to realize any opaque right sibling, because we are going to need to set its + // left sibling. Do this before acquiring thisRep since otherwise we would potentially + // invalidate it. + impl.resolveRightSibling(_repIdx); - current = currentRep.sibling.right; - } - } + ElementRep& thisRep = impl.getElementRep(_repIdx); - Document::Document() - : _impl(new Impl(Document::kInPlaceDisabled)) - , _root(makeRootElement()) { - dassert(_root._repIdx == kRootRepIdx); - } + if (thisRep.parent == kInvalidRepIdx) + return Status(ErrorCodes::IllegalOperation, "trying to remove a parentless element"); + impl.disableInPlaceUpdates(); - Document::Document(const BSONObj& value, InPlaceMode inPlaceMode) - : _impl(new Impl(inPlaceMode)) - , _root(makeRootElement(value)) { - dassert(_root._repIdx == kRootRepIdx); - } + // If our right sibling is not the end of the object, then set its left sibling to be + // our left sibling. + if (thisRep.sibling.right != kInvalidRepIdx) + impl.getElementRep(thisRep.sibling.right).sibling.left = thisRep.sibling.left; - void Document::reset() { - _impl->reset(Document::kInPlaceDisabled); - MONGO_COMPILER_VARIABLE_UNUSED const Element newRoot = makeRootElement(); - dassert(newRoot._repIdx == _root._repIdx); - dassert(_root._repIdx == kRootRepIdx); + // Similarly, if our left sibling is not the beginning of the obejct, then set its + // right sibling to be our right sibling. + if (thisRep.sibling.left != kInvalidRepIdx) { + ElementRep& leftRep = impl.getElementRep(thisRep.sibling.left); + leftRep.sibling.right = thisRep.sibling.right; } - void Document::reset(const BSONObj& value, InPlaceMode inPlaceMode) { - _impl->reset(inPlaceMode); - MONGO_COMPILER_VARIABLE_UNUSED const Element newRoot = makeRootElement(value); - dassert(newRoot._repIdx == _root._repIdx); - dassert(_root._repIdx == kRootRepIdx); - } + // If this element was our parent's right child, then our left sibling is the new right + // child. + ElementRep& parentRep = impl.getElementRep(thisRep.parent); + if (parentRep.child.right == _repIdx) + parentRep.child.right = thisRep.sibling.left; - Document::~Document() {} + // Similarly, if this element was our parent's left child, then our right sibling is + // the new left child. + if (parentRep.child.left == _repIdx) + parentRep.child.left = thisRep.sibling.right; - void Document::reserveDamageEvents(size_t expectedEvents) { - return getImpl().reserveDamageEvents(expectedEvents); - } + impl.deserialize(thisRep.parent); - bool Document::getInPlaceUpdates(DamageVector* damages, - const char** source, size_t* size) { - return getImpl().getInPlaceUpdates(damages, source, size); - } + // The Element becomes detached. + thisRep.parent = kInvalidRepIdx; + thisRep.sibling.left = kInvalidRepIdx; + thisRep.sibling.right = kInvalidRepIdx; - void Document::disableInPlaceUpdates() { - return getImpl().disableInPlaceUpdates(); - } + return Status::OK(); +} - Document::InPlaceMode Document::getCurrentInPlaceMode() const { - return getImpl().getCurrentInPlaceMode(); - } +Status Element::rename(StringData newName) { + verify(ok()); + Document::Impl& impl = getDocument().getImpl(); - Element Document::makeElementDouble(StringData fieldName, const double value) { - Impl& impl = getImpl(); - dassert(impl.doesNotAlias(fieldName)); + if (_repIdx == kRootRepIdx) + return Status(ErrorCodes::IllegalOperation, + "Invalid attempt to rename the root element of a document"); - BSONObjBuilder& builder = impl.leafBuilder(); - const int leafRef = builder.len(); - builder.append(fieldName, value); - return Element(this, impl.insertLeafElement(leafRef, fieldName.size() + 1)); - } + dassert(impl.doesNotAlias(newName)); - Element Document::makeElementString(StringData fieldName, StringData value) { - Impl& impl = getImpl(); - dassert(impl.doesNotAlias(fieldName)); - dassert(impl.doesNotAlias(value)); + // TODO: Some rename operations may be possible to do in-place. + impl.disableInPlaceUpdates(); - BSONObjBuilder& builder = impl.leafBuilder(); - const int leafRef = builder.len(); - builder.append(fieldName, value); - return Element(this, impl.insertLeafElement(leafRef, fieldName.size() + 1)); - } - - Element Document::makeElementObject(StringData fieldName) { - Impl& impl = getImpl(); - dassert(impl.doesNotAlias(fieldName)); - - Element::RepIdx newEltIdx; - ElementRep& newElt = impl.makeNewRep(&newEltIdx); - impl.insertFieldName(newElt, fieldName); - return Element(this, newEltIdx); - } - - Element Document::makeElementObject(StringData fieldName, const BSONObj& value) { - Impl& impl = getImpl(); - dassert(impl.doesNotAliasLeafBuilder(fieldName)); - dassert(impl.doesNotAlias(value)); + // Operations below may invalidate thisRep, so we may need to reacquire it. + ElementRep* thisRep = &impl.getElementRep(_repIdx); - // Copy the provided values into the leaf builder. - BSONObjBuilder& builder = impl.leafBuilder(); - const int leafRef = builder.len(); - builder.append(fieldName, value); - Element::RepIdx newEltIdx = impl.insertLeafElement(leafRef, fieldName.size() + 1); - ElementRep& newElt = impl.getElementRep(newEltIdx); + // For non-leaf serialized elements, we can realize any opaque relatives and then + // convert ourselves to deserialized. + if (thisRep->objIdx != kInvalidObjIdx && !impl.isLeaf(*thisRep)) { + const bool array = (impl.getType(*thisRep) == mongo::Array); - newElt.child.left = Element::kOpaqueRepIdx; - newElt.child.right = Element::kOpaqueRepIdx; + // Realize any opaque right sibling or left child now, since otherwise we will lose + // the ability to do so. + impl.resolveLeftChild(_repIdx); + impl.resolveRightSibling(_repIdx); - return Element(this, newEltIdx); - } + // The resolve calls above may have invalidated thisRep, we need to reacquire it. + thisRep = &impl.getElementRep(_repIdx); - Element Document::makeElementArray(StringData fieldName) { - Impl& impl = getImpl(); - dassert(impl.doesNotAlias(fieldName)); + // Set this up as a non-supported deserialized element. We will set the fieldName + // in the else clause in the block below. + impl.deserialize(_repIdx); - Element::RepIdx newEltIdx; - ElementRep& newElt = impl.makeNewRep(&newEltIdx); - newElt.array = true; - impl.insertFieldName(newElt, fieldName); - return Element(this, newEltIdx); + thisRep->array = array; + + // TODO: If we ever want to be able to add to the left or right of an opaque object + // without expanding, this may need to change. + thisRep->objIdx = kInvalidObjIdx; + } + + if (impl.hasValue(*thisRep)) { + // For leaf elements we just create a new Element with the current value and + // replace. Note that the 'setValue' call below will invalidate thisRep. + Element replacement = _doc->makeElementWithNewFieldName(newName, *this); + setValue(replacement._repIdx); + } else { + // The easy case: just update what our field name offset refers to. + impl.insertFieldName(*thisRep, newName); + } + + return Status::OK(); +} + +Element Element::leftChild() const { + verify(ok()); + + // Capturing Document::Impl by non-const ref exploits the constness loophole + // created by our Impl so that we can let leftChild be lazily evaluated, even for a + // const Element. + Document::Impl& impl = _doc->getImpl(); + const Element::RepIdx leftChildIdx = impl.resolveLeftChild(_repIdx); + dassert(leftChildIdx != kOpaqueRepIdx); + return Element(_doc, leftChildIdx); +} + +Element Element::rightChild() const { + verify(ok()); + + // Capturing Document::Impl by non-const ref exploits the constness loophole + // created by our Impl so that we can let leftChild be lazily evaluated, even for a + // const Element. + Document::Impl& impl = _doc->getImpl(); + const Element::RepIdx rightChildIdx = impl.resolveRightChild(_repIdx); + dassert(rightChildIdx != kOpaqueRepIdx); + return Element(_doc, rightChildIdx); +} + +bool Element::hasChildren() const { + verify(ok()); + // Capturing Document::Impl by non-const ref exploits the constness loophole + // created by our Impl so that we can let leftChild be lazily evaluated, even for a + // const Element. + Document::Impl& impl = _doc->getImpl(); + return impl.resolveLeftChild(_repIdx) != kInvalidRepIdx; +} + +Element Element::leftSibling(size_t distance) const { + verify(ok()); + const Document::Impl& impl = getDocument().getImpl(); + Element::RepIdx current = _repIdx; + while ((current != kInvalidRepIdx) && (distance-- != 0)) { + // We are (currently) never left opaque, so don't need to resolve. + current = impl.getElementRep(current).sibling.left; + } + return Element(_doc, current); +} + +Element Element::rightSibling(size_t distance) const { + verify(ok()); + + // Capturing Document::Impl by non-const ref exploits the constness loophole + // created by our Impl so that we can let rightSibling be lazily evaluated, even for a + // const Element. + Document::Impl& impl = _doc->getImpl(); + Element::RepIdx current = _repIdx; + while ((current != kInvalidRepIdx) && (distance-- != 0)) + current = impl.resolveRightSibling(current); + return Element(_doc, current); +} + +Element Element::parent() const { + verify(ok()); + const Document::Impl& impl = getDocument().getImpl(); + const Element::RepIdx parentIdx = impl.getElementRep(_repIdx).parent; + dassert(parentIdx != kOpaqueRepIdx); + return Element(_doc, parentIdx); +} + +Element Element::findNthChild(size_t n) const { + verify(ok()); + Document::Impl& impl = _doc->getImpl(); + Element::RepIdx current = _repIdx; + current = impl.resolveLeftChild(current); + while ((current != kInvalidRepIdx) && (n-- != 0)) + current = impl.resolveRightSibling(current); + return Element(_doc, current); +} + +Element Element::findFirstChildNamed(StringData name) const { + verify(ok()); + Document::Impl& impl = _doc->getImpl(); + Element::RepIdx current = _repIdx; + current = impl.resolveLeftChild(current); + // TODO: Could DRY this loop with the identical logic in findElementNamed. + while ((current != kInvalidRepIdx) && (impl.getFieldName(impl.getElementRep(current)) != name)) + current = impl.resolveRightSibling(current); + return Element(_doc, current); +} + +Element Element::findElementNamed(StringData name) const { + verify(ok()); + Document::Impl& impl = _doc->getImpl(); + Element::RepIdx current = _repIdx; + while ((current != kInvalidRepIdx) && (impl.getFieldName(impl.getElementRep(current)) != name)) + current = impl.resolveRightSibling(current); + return Element(_doc, current); +} + +size_t Element::countSiblingsLeft() const { + verify(ok()); + const Document::Impl& impl = getDocument().getImpl(); + Element::RepIdx current = _repIdx; + size_t result = 0; + while (true) { + // We are (currently) never left opaque, so don't need to resolve. + current = impl.getElementRep(current).sibling.left; + if (current == kInvalidRepIdx) + break; + ++result; + } + return result; +} + +size_t Element::countSiblingsRight() const { + verify(ok()); + Document::Impl& impl = _doc->getImpl(); + Element::RepIdx current = _repIdx; + size_t result = 0; + while (true) { + current = impl.resolveRightSibling(current); + if (current == kInvalidRepIdx) + break; + ++result; + } + return result; +} + +size_t Element::countChildren() const { + verify(ok()); + Document::Impl& impl = _doc->getImpl(); + Element::RepIdx current = _repIdx; + current = impl.resolveLeftChild(current); + size_t result = 0; + while (current != kInvalidRepIdx) { + ++result; + current = impl.resolveRightSibling(current); + } + return result; +} + +bool Element::hasValue() const { + verify(ok()); + const Document::Impl& impl = getDocument().getImpl(); + const ElementRep& thisRep = impl.getElementRep(_repIdx); + return impl.hasValue(thisRep); +} + +bool Element::isNumeric() const { + verify(ok()); + const Document::Impl& impl = getDocument().getImpl(); + const ElementRep& thisRep = impl.getElementRep(_repIdx); + const BSONType type = impl.getType(thisRep); + return ((type == mongo::NumberLong) || (type == mongo::NumberInt) || + (type == mongo::NumberDouble)); +} + +bool Element::isIntegral() const { + verify(ok()); + const Document::Impl& impl = getDocument().getImpl(); + const ElementRep& thisRep = impl.getElementRep(_repIdx); + const BSONType type = impl.getType(thisRep); + return ((type == mongo::NumberLong) || (type == mongo::NumberInt)); +} + +const BSONElement Element::getValue() const { + verify(ok()); + const Document::Impl& impl = getDocument().getImpl(); + const ElementRep& thisRep = impl.getElementRep(_repIdx); + if (impl.hasValue(thisRep)) + return impl.getSerializedElement(thisRep); + return BSONElement(); +} + +SafeNum Element::getValueSafeNum() const { + switch (getType()) { + case mongo::NumberInt: + return static_cast<int>(getValueInt()); + case mongo::NumberLong: + return static_cast<long long int>(getValueLong()); + case mongo::NumberDouble: + return getValueDouble(); + default: + return SafeNum(); } +} + +int Element::compareWithElement(const ConstElement& other, bool considerFieldName) const { + verify(ok()); + verify(other.ok()); + + // Short circuit a tautological compare. + if ((_repIdx == other.getIdx()) && (_doc == &other.getDocument())) + return 0; + + // If either Element can represent its current value as a BSONElement, then we can + // obtain its value and use compareWithBSONElement. If both Elements have a + // representation as a BSONElement, compareWithBSONElement will notice that the first + // argument has a value and delegate to BSONElement::woCompare. + + const Document::Impl& impl = getDocument().getImpl(); + const ElementRep& thisRep = impl.getElementRep(_repIdx); + + // Subtle: we must negate the comparison result here because we are reversing the + // argument order in this call. + // + // TODO: Andy has suggested that this may not be legal since woCompare is not reflexive + // in all cases. + if (impl.hasValue(thisRep)) + return -other.compareWithBSONElement(impl.getSerializedElement(thisRep), considerFieldName); + + const Document::Impl& oimpl = other.getDocument().getImpl(); + const ElementRep& otherRep = oimpl.getElementRep(other.getIdx()); + + if (oimpl.hasValue(otherRep)) + return compareWithBSONElement(oimpl.getSerializedElement(otherRep), considerFieldName); + + // Leaf elements should always have a value, so we should only be dealing with Objects + // or Arrays here. + dassert(!impl.isLeaf(thisRep)); + dassert(!oimpl.isLeaf(otherRep)); + + // Obtain the canonical types for this Element and the BSONElement, if they are + // different use the difference as the result. Please see BSONElement::woCompare for + // details. We know that thisRep is not a number, so we don't need to check that + // particular case. + const int leftCanonType = canonicalizeBSONType(impl.getType(thisRep)); + const int rightCanonType = canonicalizeBSONType(oimpl.getType(otherRep)); + const int diffCanon = leftCanonType - rightCanonType; + if (diffCanon != 0) + return diffCanon; + + // If we are considering field names, and the field names do not compare as equal, + // return the field name ordering as the element ordering. + if (considerFieldName) { + const int fnamesComp = impl.getFieldName(thisRep).compare(oimpl.getFieldName(otherRep)); + if (fnamesComp != 0) + return fnamesComp; + } + + const bool considerChildFieldNames = + (impl.getType(thisRep) != mongo::Array) && (oimpl.getType(otherRep) != mongo::Array); + + // We are dealing with either two objects, or two arrays. We need to consider the child + // elements individually. We walk two iterators forward over the children and compare + // them. Length mismatches are handled by checking early for reaching the end of the + // children. + ConstElement thisIter = leftChild(); + ConstElement otherIter = other.leftChild(); + + while (true) { + if (!thisIter.ok()) + return !otherIter.ok() ? 0 : -1; + if (!otherIter.ok()) + return 1; + + const int result = thisIter.compareWithElement(otherIter, considerChildFieldNames); + if (result != 0) + return result; - Element Document::makeElementArray(StringData fieldName, const BSONObj& value) { - Impl& impl = getImpl(); - dassert(impl.doesNotAliasLeafBuilder(fieldName)); - dassert(impl.doesNotAlias(value)); - - // Copy the provided array values into the leaf builder. - BSONObjBuilder& builder = impl.leafBuilder(); - const int leafRef = builder.len(); - builder.appendArray(fieldName, value); - Element::RepIdx newEltIdx = impl.insertLeafElement(leafRef, fieldName.size() + 1); - ElementRep& newElt = impl.getElementRep(newEltIdx); - newElt.child.left = Element::kOpaqueRepIdx; - newElt.child.right = Element::kOpaqueRepIdx; - return Element(this, newEltIdx); + thisIter = thisIter.rightSibling(); + otherIter = otherIter.rightSibling(); } +} - Element Document::makeElementBinary(StringData fieldName, - const uint32_t len, - const mongo::BinDataType binType, - const void* const data) { - Impl& impl = getImpl(); - dassert(impl.doesNotAlias(fieldName)); - // TODO: Alias check 'data'? +int Element::compareWithBSONElement(const BSONElement& other, bool considerFieldName) const { + verify(ok()); - BSONObjBuilder& builder = impl.leafBuilder(); - const int leafRef = builder.len(); - builder.appendBinData(fieldName, len, binType, data); - return Element(this, impl.insertLeafElement(leafRef, fieldName.size() + 1)); - } + const Document::Impl& impl = getDocument().getImpl(); + const ElementRep& thisRep = impl.getElementRep(_repIdx); - Element Document::makeElementUndefined(StringData fieldName) { - Impl& impl = getImpl(); - dassert(impl.doesNotAlias(fieldName)); + // If we have a representation as a BSONElement, we can just use BSONElement::woCompare + // to do the entire comparison. + if (impl.hasValue(thisRep)) + return impl.getSerializedElement(thisRep).woCompare(other, considerFieldName); - BSONObjBuilder& builder = impl.leafBuilder(); - const int leafRef = builder.len(); - builder.appendUndefined(fieldName); - return Element(this, impl.insertLeafElement(leafRef, fieldName.size() + 1)); - } - - Element Document::makeElementNewOID(StringData fieldName) { - OID newOID; - newOID.init(); - return makeElementOID(fieldName, newOID); - } + // Leaf elements should always have a value, so we should only be dealing with Objects + // or Arrays here. + dassert(!impl.isLeaf(thisRep)); - Element Document::makeElementOID(StringData fieldName, const OID value) { - Impl& impl = getImpl(); - dassert(impl.doesNotAlias(fieldName)); + // Obtain the canonical types for this Element and the BSONElement, if they are + // different use the difference as the result. Please see BSONElement::woCompare for + // details. We know that thisRep is not a number, so we don't need to check that + // particular case. + const int leftCanonType = canonicalizeBSONType(impl.getType(thisRep)); + const int rightCanonType = canonicalizeBSONType(other.type()); + const int diffCanon = leftCanonType - rightCanonType; + if (diffCanon != 0) + return diffCanon; - BSONObjBuilder& builder = impl.leafBuilder(); - const int leafRef = builder.len(); - builder.append(fieldName, value); - return Element(this, impl.insertLeafElement(leafRef, fieldName.size() + 1)); + // If we are considering field names, and the field names do not compare as equal, + // return the field name ordering as the element ordering. + if (considerFieldName) { + const int fnamesComp = impl.getFieldName(thisRep).compare(other.fieldNameStringData()); + if (fnamesComp != 0) + return fnamesComp; } - Element Document::makeElementBool(StringData fieldName, const bool value) { - Impl& impl = getImpl(); - dassert(impl.doesNotAlias(fieldName)); + const bool considerChildFieldNames = + (impl.getType(thisRep) != mongo::Array) && (other.type() != mongo::Array); - BSONObjBuilder& builder = impl.leafBuilder(); - const int leafRef = builder.len(); - builder.appendBool(fieldName, value); - return Element(this, impl.insertLeafElement(leafRef, fieldName.size() + 1)); - } + return compareWithBSONObj(other.Obj(), considerChildFieldNames); +} - Element Document::makeElementDate(StringData fieldName, const Date_t value) { - Impl& impl = getImpl(); - dassert(impl.doesNotAlias(fieldName)); +int Element::compareWithBSONObj(const BSONObj& other, bool considerFieldName) const { + verify(ok()); - BSONObjBuilder& builder = impl.leafBuilder(); - const int leafRef = builder.len(); - builder.appendDate(fieldName, value); - return Element(this, impl.insertLeafElement(leafRef, fieldName.size() + 1)); - } + const Document::Impl& impl = getDocument().getImpl(); + const ElementRep& thisRep = impl.getElementRep(_repIdx); + verify(!impl.isLeaf(thisRep)); - Element Document::makeElementNull(StringData fieldName) { - Impl& impl = getImpl(); - dassert(impl.doesNotAlias(fieldName)); + // We are dealing with either two objects, or two arrays. We need to consider the child + // elements individually. We walk two iterators forward over the children and compare + // them. Length mismatches are handled by checking early for reaching the end of the + // children. + ConstElement thisIter = leftChild(); + BSONObjIterator otherIter(other); - BSONObjBuilder& builder = impl.leafBuilder(); - const int leafRef = builder.len(); - builder.appendNull(fieldName); - return Element(this, impl.insertLeafElement(leafRef, fieldName.size() + 1)); - } + while (true) { + const BSONElement otherVal = otherIter.next(); - Element Document::makeElementRegex(StringData fieldName, - StringData re, - StringData flags) { - Impl& impl = getImpl(); - dassert(impl.doesNotAlias(fieldName)); - dassert(impl.doesNotAlias(re)); - dassert(impl.doesNotAlias(flags)); + if (!thisIter.ok()) + return otherVal.eoo() ? 0 : -1; + if (otherVal.eoo()) + return 1; - BSONObjBuilder& builder = impl.leafBuilder(); - const int leafRef = builder.len(); - builder.appendRegex(fieldName, re, flags); - return Element(this, impl.insertLeafElement(leafRef, fieldName.size() + 1)); - } + const int result = thisIter.compareWithBSONElement(otherVal, considerFieldName); + if (result != 0) + return result; - Element Document::makeElementDBRef(StringData fieldName, - StringData ns, const OID value) { - Impl& impl = getImpl(); - dassert(impl.doesNotAlias(fieldName)); - BSONObjBuilder& builder = impl.leafBuilder(); - const int leafRef = builder.len(); - builder.appendDBRef(fieldName, ns, value); - return Element(this, impl.insertLeafElement(leafRef, fieldName.size() + 1)); - } + thisIter = thisIter.rightSibling(); + } +} + +void Element::writeTo(BSONObjBuilder* const builder) const { + verify(ok()); + const Document::Impl& impl = getDocument().getImpl(); + const ElementRep& thisRep = impl.getElementRep(_repIdx); + verify(impl.getType(thisRep) == mongo::Object); + if (thisRep.parent == kInvalidRepIdx && _repIdx == kRootRepIdx) { + // If this is the root element, then we need to handle it differently, since it + // doesn't have a field name and should embed directly, rather than as an object. + impl.writeChildren(_repIdx, builder); + } else { + impl.writeElement(_repIdx, builder); + } +} + +void Element::writeArrayTo(BSONArrayBuilder* const builder) const { + verify(ok()); + const Document::Impl& impl = getDocument().getImpl(); + const ElementRep& thisRep = impl.getElementRep(_repIdx); + verify(impl.getType(thisRep) == mongo::Array); + return impl.writeChildren(_repIdx, builder); +} + +Status Element::setValueDouble(const double value) { + verify(ok()); + Document::Impl& impl = getDocument().getImpl(); + ElementRep thisRep = impl.getElementRep(_repIdx); + const StringData fieldName = impl.getFieldNameForNewElement(thisRep); + Element newValue = getDocument().makeElementDouble(fieldName, value); + return setValue(newValue._repIdx); +} + +Status Element::setValueString(StringData value) { + verify(ok()); + Document::Impl& impl = getDocument().getImpl(); + + dassert(impl.doesNotAlias(value)); + + const ElementRep& thisRep = impl.getElementRep(_repIdx); + const StringData fieldName = impl.getFieldNameForNewElement(thisRep); + Element newValue = getDocument().makeElementString(fieldName, value); + return setValue(newValue._repIdx); +} + +Status Element::setValueObject(const BSONObj& value) { + verify(ok()); + Document::Impl& impl = getDocument().getImpl(); + + dassert(impl.doesNotAlias(value)); + + const ElementRep& thisRep = impl.getElementRep(_repIdx); + const StringData fieldName = impl.getFieldNameForNewElement(thisRep); + Element newValue = getDocument().makeElementObject(fieldName, value); + return setValue(newValue._repIdx); +} + +Status Element::setValueArray(const BSONObj& value) { + verify(ok()); + Document::Impl& impl = getDocument().getImpl(); + + dassert(impl.doesNotAlias(value)); + + const ElementRep& thisRep = impl.getElementRep(_repIdx); + const StringData fieldName = impl.getFieldNameForNewElement(thisRep); + Element newValue = getDocument().makeElementArray(fieldName, value); + return setValue(newValue._repIdx); +} + +Status Element::setValueBinary(const uint32_t len, + mongo::BinDataType binType, + const void* const data) { + verify(ok()); + Document::Impl& impl = getDocument().getImpl(); + + // TODO: Alias check for binary data? + + const ElementRep& thisRep = impl.getElementRep(_repIdx); + const StringData fieldName = impl.getFieldNameForNewElement(thisRep); + Element newValue = getDocument().makeElementBinary(fieldName, len, binType, data); + return setValue(newValue._repIdx); +} + +Status Element::setValueUndefined() { + verify(ok()); + Document::Impl& impl = getDocument().getImpl(); + const ElementRep& thisRep = impl.getElementRep(_repIdx); + const StringData fieldName = impl.getFieldNameForNewElement(thisRep); + Element newValue = getDocument().makeElementUndefined(fieldName); + return setValue(newValue._repIdx); +} + +Status Element::setValueOID(const OID value) { + verify(ok()); + Document::Impl& impl = getDocument().getImpl(); + const ElementRep& thisRep = impl.getElementRep(_repIdx); + const StringData fieldName = impl.getFieldNameForNewElement(thisRep); + Element newValue = getDocument().makeElementOID(fieldName, value); + return setValue(newValue._repIdx); +} + +Status Element::setValueBool(const bool value) { + verify(ok()); + Document::Impl& impl = getDocument().getImpl(); + ElementRep thisRep = impl.getElementRep(_repIdx); + const StringData fieldName = impl.getFieldNameForNewElement(thisRep); + Element newValue = getDocument().makeElementBool(fieldName, value); + return setValue(newValue._repIdx); +} + +Status Element::setValueDate(const Date_t value) { + verify(ok()); + Document::Impl& impl = getDocument().getImpl(); + const ElementRep& thisRep = impl.getElementRep(_repIdx); + const StringData fieldName = impl.getFieldNameForNewElement(thisRep); + Element newValue = getDocument().makeElementDate(fieldName, value); + return setValue(newValue._repIdx); +} + +Status Element::setValueNull() { + verify(ok()); + Document::Impl& impl = getDocument().getImpl(); + const ElementRep& thisRep = impl.getElementRep(_repIdx); + const StringData fieldName = impl.getFieldNameForNewElement(thisRep); + Element newValue = getDocument().makeElementNull(fieldName); + return setValue(newValue._repIdx); +} + +Status Element::setValueRegex(StringData re, StringData flags) { + verify(ok()); + Document::Impl& impl = getDocument().getImpl(); + + dassert(impl.doesNotAlias(re)); + dassert(impl.doesNotAlias(flags)); + + const ElementRep& thisRep = impl.getElementRep(_repIdx); + const StringData fieldName = impl.getFieldNameForNewElement(thisRep); + Element newValue = getDocument().makeElementRegex(fieldName, re, flags); + return setValue(newValue._repIdx); +} + +Status Element::setValueDBRef(StringData ns, const OID oid) { + verify(ok()); + Document::Impl& impl = getDocument().getImpl(); + + dassert(impl.doesNotAlias(ns)); + + const ElementRep& thisRep = impl.getElementRep(_repIdx); + const StringData fieldName = impl.getFieldNameForNewElement(thisRep); + Element newValue = getDocument().makeElementDBRef(fieldName, ns, oid); + return setValue(newValue._repIdx); +} + +Status Element::setValueCode(StringData value) { + verify(ok()); + Document::Impl& impl = getDocument().getImpl(); + + dassert(impl.doesNotAlias(value)); + + const ElementRep& thisRep = impl.getElementRep(_repIdx); + const StringData fieldName = impl.getFieldNameForNewElement(thisRep); + Element newValue = getDocument().makeElementCode(fieldName, value); + return setValue(newValue._repIdx); +} + +Status Element::setValueSymbol(StringData value) { + verify(ok()); + Document::Impl& impl = getDocument().getImpl(); + + dassert(impl.doesNotAlias(value)); + + const ElementRep& thisRep = impl.getElementRep(_repIdx); + const StringData fieldName = impl.getFieldNameForNewElement(thisRep); + Element newValue = getDocument().makeElementSymbol(fieldName, value); + return setValue(newValue._repIdx); +} + +Status Element::setValueCodeWithScope(StringData code, const BSONObj& scope) { + verify(ok()); + Document::Impl& impl = getDocument().getImpl(); + + dassert(impl.doesNotAlias(code)); + dassert(impl.doesNotAlias(scope)); + + const ElementRep& thisRep = impl.getElementRep(_repIdx); + const StringData fieldName = impl.getFieldNameForNewElement(thisRep); + Element newValue = getDocument().makeElementCodeWithScope(fieldName, code, scope); + return setValue(newValue._repIdx); +} + +Status Element::setValueInt(const int32_t value) { + verify(ok()); + Document::Impl& impl = getDocument().getImpl(); + ElementRep thisRep = impl.getElementRep(_repIdx); + const StringData fieldName = impl.getFieldNameForNewElement(thisRep); + Element newValue = getDocument().makeElementInt(fieldName, value); + return setValue(newValue._repIdx); +} + +Status Element::setValueTimestamp(const Timestamp value) { + verify(ok()); + Document::Impl& impl = getDocument().getImpl(); + const ElementRep& thisRep = impl.getElementRep(_repIdx); + const StringData fieldName = impl.getFieldNameForNewElement(thisRep); + Element newValue = getDocument().makeElementTimestamp(fieldName, value); + return setValue(newValue._repIdx); +} + +Status Element::setValueLong(const int64_t value) { + verify(ok()); + Document::Impl& impl = getDocument().getImpl(); + ElementRep thisRep = impl.getElementRep(_repIdx); + const StringData fieldName = impl.getFieldNameForNewElement(thisRep); + Element newValue = getDocument().makeElementLong(fieldName, value); + return setValue(newValue._repIdx); +} + +Status Element::setValueMinKey() { + verify(ok()); + Document::Impl& impl = getDocument().getImpl(); + const ElementRep& thisRep = impl.getElementRep(_repIdx); + const StringData fieldName = impl.getFieldNameForNewElement(thisRep); + Element newValue = getDocument().makeElementMinKey(fieldName); + return setValue(newValue._repIdx); +} + +Status Element::setValueMaxKey() { + verify(ok()); + Document::Impl& impl = getDocument().getImpl(); + const ElementRep& thisRep = impl.getElementRep(_repIdx); + const StringData fieldName = impl.getFieldNameForNewElement(thisRep); + Element newValue = getDocument().makeElementMaxKey(fieldName); + return setValue(newValue._repIdx); +} + +Status Element::setValueBSONElement(const BSONElement& value) { + verify(ok()); + + if (value.type() == mongo::EOO) + return Status(ErrorCodes::IllegalOperation, "Can't set Element value to EOO"); + + Document::Impl& impl = getDocument().getImpl(); + + dassert(impl.doesNotAlias(value)); + + ElementRep thisRep = impl.getElementRep(_repIdx); + const StringData fieldName = impl.getFieldNameForNewElement(thisRep); + Element newValue = getDocument().makeElementWithNewFieldName(fieldName, value); + return setValue(newValue._repIdx); +} + +Status Element::setValueSafeNum(const SafeNum value) { + verify(ok()); + switch (value.type()) { + case mongo::NumberInt: + return setValueInt(value._value.int32Val); + case mongo::NumberLong: + return setValueLong(value._value.int64Val); + case mongo::NumberDouble: + return setValueDouble(value._value.doubleVal); + default: + return Status(ErrorCodes::UnsupportedFormat, + "Don't know how to handle unexpected SafeNum type"); + } +} + +BSONType Element::getType() const { + verify(ok()); + const Document::Impl& impl = getDocument().getImpl(); + const ElementRep& thisRep = impl.getElementRep(_repIdx); + return impl.getType(thisRep); +} + +StringData Element::getFieldName() const { + verify(ok()); + const Document::Impl& impl = getDocument().getImpl(); + const ElementRep& thisRep = impl.getElementRep(_repIdx); + return impl.getFieldName(thisRep); +} + +Status Element::addChild(Element e, bool front) { + // No need to verify(ok()) since we are only called from methods that have done so. + dassert(ok()); + + verify(e.ok()); + verify(_doc == e._doc); + + Document::Impl& impl = getDocument().getImpl(); + ElementRep& newRep = impl.getElementRep(e._repIdx); + + // check that new element roots a clean subtree. + if (!canAttach(e._repIdx, newRep)) + return getAttachmentError(newRep); + + // Check that this element is eligible for children. + ElementRep& thisRep = impl.getElementRep(_repIdx); + if (impl.isLeaf(thisRep)) + return Status(ErrorCodes::IllegalOperation, + "Attempt to add a child element to a non-object element"); + + impl.disableInPlaceUpdates(); + + // TODO: In both of the following cases, we call two public API methods each. We can + // probably do better by writing this explicitly here and drying it with the public + // addSiblingLeft and addSiblingRight implementations. + if (front) { + // TODO: It is cheap to get the left child. However, it still means creating a rep + // for it. Can we do better? + Element lc = leftChild(); + if (lc.ok()) + return lc.addSiblingLeft(e); + } else { + // TODO: It is expensive to get the right child, since we have to build reps for + // all of the opaque children. But in principle, we don't really need them. Could + // we potentially add this element as a right child, leaving its left sibling + // opaque? We would at minimum need to update leftSibling, which currently assumes + // that your left sibling is never opaque. But adding new Elements to the end is a + // quite common operation, so it would be nice if we could do this efficiently. + Element rc = rightChild(); + if (rc.ok()) + return rc.addSiblingRight(e); + } + + // It must be the case that we have no children, so the new element becomes both the + // right and left child of this node. + dassert((thisRep.child.left == kInvalidRepIdx) && (thisRep.child.right == kInvalidRepIdx)); + thisRep.child.left = thisRep.child.right = e._repIdx; + newRep.parent = _repIdx; + impl.deserialize(_repIdx); + return Status::OK(); +} + +Status Element::setValue(const Element::RepIdx newValueIdx) { + // No need to verify(ok()) since we are only called from methods that have done so. + dassert(ok()); + + if (_repIdx == kRootRepIdx) + return Status(ErrorCodes::IllegalOperation, "Cannot call setValue on the root object"); + + Document::Impl& impl = getDocument().getImpl(); + + // Establish our right sibling in case it is opaque. Otherwise, we would lose the + // ability to do so after the modifications below. It is important that this occur + // before we acquire thisRep and valueRep since otherwise we would potentially + // invalidate them. + impl.resolveRightSibling(_repIdx); + + ElementRep& thisRep = impl.getElementRep(_repIdx); + ElementRep& valueRep = impl.getElementRep(newValueIdx); + + if (impl.isInPlaceModeEnabled() && impl.canUpdateInPlace(valueRep, thisRep)) { + // Get the BSONElement representations of the existing and new value, so we can + // check if they are size compatible. + BSONElement thisElt = impl.getSerializedElement(thisRep); + BSONElement valueElt = impl.getSerializedElement(valueRep); + + if (thisElt.size() == valueElt.size()) { + // The old and new elements are size compatible. Compute the base offsets + // of each BSONElement in the object in which it resides. We use these to + // calculate the source and target offsets in the damage entries we are + // going to write. + + const DamageEvent::OffsetSizeType targetBaseOffset = + getElementOffset(impl.getObject(thisRep.objIdx), thisElt); + + const DamageEvent::OffsetSizeType sourceBaseOffset = + getElementOffset(impl.getObject(valueRep.objIdx), valueElt); + + // If this is a type change, record a damage event for the new type. + if (thisElt.type() != valueElt.type()) { + impl.recordDamageEvent(targetBaseOffset, sourceBaseOffset, 1); + } - Element Document::makeElementCode(StringData fieldName, StringData value) { - Impl& impl = getImpl(); - dassert(impl.doesNotAlias(fieldName)); - dassert(impl.doesNotAlias(value)); + dassert(thisElt.fieldNameSize() == valueElt.fieldNameSize()); + dassert(thisElt.valuesize() == valueElt.valuesize()); - BSONObjBuilder& builder = impl.leafBuilder(); - const int leafRef = builder.len(); - builder.appendCode(fieldName, value); - return Element(this, impl.insertLeafElement(leafRef, fieldName.size() + 1)); - } + // Record a damage event for the new value data. + impl.recordDamageEvent(targetBaseOffset + thisElt.fieldNameSize() + 1, + sourceBaseOffset + thisElt.fieldNameSize() + 1, + thisElt.valuesize()); + } else { + // We couldn't do it in place, so disable future in-place updates. + impl.disableInPlaceUpdates(); + } + } + + // If we are not rootish, then wire in the new value among our relations. + if (thisRep.parent != kInvalidRepIdx) { + valueRep.parent = thisRep.parent; + valueRep.sibling.left = thisRep.sibling.left; + valueRep.sibling.right = thisRep.sibling.right; + } + + // Copy the rep for value to our slot so that our repIdx is unmodified. + thisRep = valueRep; + + // Be nice and clear out the source rep to make debugging easier. + valueRep = ElementRep(); + + impl.deserialize(thisRep.parent); + return Status::OK(); +} + + +namespace { + +// A helper for Element::writeElement below. For cases where we are building inside an +// array, we want to ignore field names. So the specialization for BSONArrayBuilder ignores +// the third parameter. +template <typename Builder> +struct SubBuilder; + +template <> +struct SubBuilder<BSONObjBuilder> { + SubBuilder(BSONObjBuilder* builder, BSONType type, StringData fieldName) + : buffer((type == mongo::Array) ? builder->subarrayStart(fieldName) + : builder->subobjStart(fieldName)) {} + BufBuilder& buffer; +}; + +template <> +struct SubBuilder<BSONArrayBuilder> { + SubBuilder(BSONArrayBuilder* builder, BSONType type, StringData) + : buffer((type == mongo::Array) ? builder->subarrayStart() : builder->subobjStart()) {} + BufBuilder& buffer; +}; + +static void appendElement(BSONObjBuilder* builder, + const BSONElement& element, + const StringData* fieldName) { + if (fieldName) + builder->appendAs(element, *fieldName); + else + builder->append(element); +} + +// BSONArrayBuilder should not be appending elements with a fieldName +static void appendElement(BSONArrayBuilder* builder, + const BSONElement& element, + const StringData* fieldName) { + invariant(!fieldName); + builder->append(element); +} + +} // namespace + +template <typename Builder> +void Document::Impl::writeElement(Element::RepIdx repIdx, + Builder* builder, + const StringData* fieldName) const { + const ElementRep& rep = getElementRep(repIdx); + + if (hasValue(rep)) { + appendElement(builder, getSerializedElement(rep), fieldName); + } else { + const BSONType type = getType(rep); + const StringData subName = fieldName ? *fieldName : getFieldName(rep); + SubBuilder<Builder> subBuilder(builder, type, subName); + + // Otherwise, this is a 'dirty leaf', which is impossible. + dassert((type == mongo::Array) || (type == mongo::Object)); + + if (type == mongo::Array) { + BSONArrayBuilder child_builder(subBuilder.buffer); + writeChildren(repIdx, &child_builder); + child_builder.doneFast(); + } else { + BSONObjBuilder child_builder(subBuilder.buffer); + writeChildren(repIdx, &child_builder); + child_builder.doneFast(); + } + } +} + +template <typename Builder> +void Document::Impl::writeChildren(Element::RepIdx repIdx, Builder* builder) const { + // TODO: In theory, I think we can walk rightwards building a write region from all + // serialized embedded children that share an obj id and form a contiguous memory + // region. For arrays we would need to know something about how many elements we wrote + // that way so that the indexes would come out right. + // + // However, that involves walking the memory twice: once to build the copy region, and + // another time to actually copy it. It is unclear if this is better than just walking + // it once with the recursive solution. + + const ElementRep& rep = getElementRep(repIdx); + + // OK, need to resolve left if we haven't done that yet. + Element::RepIdx current = rep.child.left; + if (current == Element::kOpaqueRepIdx) + current = const_cast<Impl*>(this)->resolveLeftChild(repIdx); + + // We need to write the element, and then walk rightwards. + while (current != Element::kInvalidRepIdx) { + writeElement(current, builder); + + // If we have an opaque region to the right, and we are not in an array, then we + // can bulk copy from the end of the element we just wrote to the end of our + // parent. + const ElementRep& currentRep = getElementRep(current); + + if (currentRep.sibling.right == Element::kOpaqueRepIdx) { + // Obtain the current parent, so we can see if we can bulk copy the right + // siblings. + const ElementRep& parentRep = getElementRep(currentRep.parent); + + // Bulk copying right only works on objects + if ((getType(parentRep) == mongo::Object) && (currentRep.objIdx != kInvalidObjIdx) && + (currentRep.objIdx == parentRep.objIdx)) { + BSONElement currentElt = getSerializedElement(currentRep); + const uint32_t currentSize = currentElt.size(); + + const BSONObj parentObj = (currentRep.parent == kRootRepIdx) + ? getObject(parentRep.objIdx) + : getSerializedElement(parentRep).Obj(); + const uint32_t parentSize = parentObj.objsize(); + + const uint32_t currentEltOffset = getElementOffset(parentObj, currentElt); + const uint32_t nextEltOffset = currentEltOffset + currentSize; + + const char* copyBegin = parentObj.objdata() + nextEltOffset; + const uint32_t copyBytes = parentSize - nextEltOffset; + + // The -1 is because we don't want to copy in the terminal EOO. + builder->bb().appendBuf(copyBegin, copyBytes - 1); + + // We are done with all children. + break; + } - Element Document::makeElementSymbol(StringData fieldName, StringData value) { - Impl& impl = getImpl(); - dassert(impl.doesNotAlias(fieldName)); + // We couldn't bulk copy, and our right sibling is opaque. We need to + // resolve. Note that the call to resolve may invalidate 'currentRep', so + // rather than falling through and acquiring the index by examining currentRep, + // update it with the return value of resolveRightSibling and restart the loop. + current = const_cast<Impl*>(this)->resolveRightSibling(current); + continue; + } + + current = currentRep.sibling.right; + } +} + +Document::Document() : _impl(new Impl(Document::kInPlaceDisabled)), _root(makeRootElement()) { + dassert(_root._repIdx == kRootRepIdx); +} + +Document::Document(const BSONObj& value, InPlaceMode inPlaceMode) + : _impl(new Impl(inPlaceMode)), _root(makeRootElement(value)) { + dassert(_root._repIdx == kRootRepIdx); +} + +void Document::reset() { + _impl->reset(Document::kInPlaceDisabled); + MONGO_COMPILER_VARIABLE_UNUSED const Element newRoot = makeRootElement(); + dassert(newRoot._repIdx == _root._repIdx); + dassert(_root._repIdx == kRootRepIdx); +} + +void Document::reset(const BSONObj& value, InPlaceMode inPlaceMode) { + _impl->reset(inPlaceMode); + MONGO_COMPILER_VARIABLE_UNUSED const Element newRoot = makeRootElement(value); + dassert(newRoot._repIdx == _root._repIdx); + dassert(_root._repIdx == kRootRepIdx); +} + +Document::~Document() {} + +void Document::reserveDamageEvents(size_t expectedEvents) { + return getImpl().reserveDamageEvents(expectedEvents); +} + +bool Document::getInPlaceUpdates(DamageVector* damages, const char** source, size_t* size) { + return getImpl().getInPlaceUpdates(damages, source, size); +} + +void Document::disableInPlaceUpdates() { + return getImpl().disableInPlaceUpdates(); +} + +Document::InPlaceMode Document::getCurrentInPlaceMode() const { + return getImpl().getCurrentInPlaceMode(); +} + +Element Document::makeElementDouble(StringData fieldName, const double value) { + Impl& impl = getImpl(); + dassert(impl.doesNotAlias(fieldName)); + + BSONObjBuilder& builder = impl.leafBuilder(); + const int leafRef = builder.len(); + builder.append(fieldName, value); + return Element(this, impl.insertLeafElement(leafRef, fieldName.size() + 1)); +} + +Element Document::makeElementString(StringData fieldName, StringData value) { + Impl& impl = getImpl(); + dassert(impl.doesNotAlias(fieldName)); + dassert(impl.doesNotAlias(value)); + + BSONObjBuilder& builder = impl.leafBuilder(); + const int leafRef = builder.len(); + builder.append(fieldName, value); + return Element(this, impl.insertLeafElement(leafRef, fieldName.size() + 1)); +} + +Element Document::makeElementObject(StringData fieldName) { + Impl& impl = getImpl(); + dassert(impl.doesNotAlias(fieldName)); + + Element::RepIdx newEltIdx; + ElementRep& newElt = impl.makeNewRep(&newEltIdx); + impl.insertFieldName(newElt, fieldName); + return Element(this, newEltIdx); +} + +Element Document::makeElementObject(StringData fieldName, const BSONObj& value) { + Impl& impl = getImpl(); + dassert(impl.doesNotAliasLeafBuilder(fieldName)); + dassert(impl.doesNotAlias(value)); + + // Copy the provided values into the leaf builder. + BSONObjBuilder& builder = impl.leafBuilder(); + const int leafRef = builder.len(); + builder.append(fieldName, value); + Element::RepIdx newEltIdx = impl.insertLeafElement(leafRef, fieldName.size() + 1); + ElementRep& newElt = impl.getElementRep(newEltIdx); + + newElt.child.left = Element::kOpaqueRepIdx; + newElt.child.right = Element::kOpaqueRepIdx; + + return Element(this, newEltIdx); +} + +Element Document::makeElementArray(StringData fieldName) { + Impl& impl = getImpl(); + dassert(impl.doesNotAlias(fieldName)); + + Element::RepIdx newEltIdx; + ElementRep& newElt = impl.makeNewRep(&newEltIdx); + newElt.array = true; + impl.insertFieldName(newElt, fieldName); + return Element(this, newEltIdx); +} + +Element Document::makeElementArray(StringData fieldName, const BSONObj& value) { + Impl& impl = getImpl(); + dassert(impl.doesNotAliasLeafBuilder(fieldName)); + dassert(impl.doesNotAlias(value)); + + // Copy the provided array values into the leaf builder. + BSONObjBuilder& builder = impl.leafBuilder(); + const int leafRef = builder.len(); + builder.appendArray(fieldName, value); + Element::RepIdx newEltIdx = impl.insertLeafElement(leafRef, fieldName.size() + 1); + ElementRep& newElt = impl.getElementRep(newEltIdx); + newElt.child.left = Element::kOpaqueRepIdx; + newElt.child.right = Element::kOpaqueRepIdx; + return Element(this, newEltIdx); +} + +Element Document::makeElementBinary(StringData fieldName, + const uint32_t len, + const mongo::BinDataType binType, + const void* const data) { + Impl& impl = getImpl(); + dassert(impl.doesNotAlias(fieldName)); + // TODO: Alias check 'data'? + + BSONObjBuilder& builder = impl.leafBuilder(); + const int leafRef = builder.len(); + builder.appendBinData(fieldName, len, binType, data); + return Element(this, impl.insertLeafElement(leafRef, fieldName.size() + 1)); +} + +Element Document::makeElementUndefined(StringData fieldName) { + Impl& impl = getImpl(); + dassert(impl.doesNotAlias(fieldName)); + + BSONObjBuilder& builder = impl.leafBuilder(); + const int leafRef = builder.len(); + builder.appendUndefined(fieldName); + return Element(this, impl.insertLeafElement(leafRef, fieldName.size() + 1)); +} + +Element Document::makeElementNewOID(StringData fieldName) { + OID newOID; + newOID.init(); + return makeElementOID(fieldName, newOID); +} + +Element Document::makeElementOID(StringData fieldName, const OID value) { + Impl& impl = getImpl(); + dassert(impl.doesNotAlias(fieldName)); + + BSONObjBuilder& builder = impl.leafBuilder(); + const int leafRef = builder.len(); + builder.append(fieldName, value); + return Element(this, impl.insertLeafElement(leafRef, fieldName.size() + 1)); +} + +Element Document::makeElementBool(StringData fieldName, const bool value) { + Impl& impl = getImpl(); + dassert(impl.doesNotAlias(fieldName)); + + BSONObjBuilder& builder = impl.leafBuilder(); + const int leafRef = builder.len(); + builder.appendBool(fieldName, value); + return Element(this, impl.insertLeafElement(leafRef, fieldName.size() + 1)); +} + +Element Document::makeElementDate(StringData fieldName, const Date_t value) { + Impl& impl = getImpl(); + dassert(impl.doesNotAlias(fieldName)); + + BSONObjBuilder& builder = impl.leafBuilder(); + const int leafRef = builder.len(); + builder.appendDate(fieldName, value); + return Element(this, impl.insertLeafElement(leafRef, fieldName.size() + 1)); +} + +Element Document::makeElementNull(StringData fieldName) { + Impl& impl = getImpl(); + dassert(impl.doesNotAlias(fieldName)); + + BSONObjBuilder& builder = impl.leafBuilder(); + const int leafRef = builder.len(); + builder.appendNull(fieldName); + return Element(this, impl.insertLeafElement(leafRef, fieldName.size() + 1)); +} + +Element Document::makeElementRegex(StringData fieldName, StringData re, StringData flags) { + Impl& impl = getImpl(); + dassert(impl.doesNotAlias(fieldName)); + dassert(impl.doesNotAlias(re)); + dassert(impl.doesNotAlias(flags)); + + BSONObjBuilder& builder = impl.leafBuilder(); + const int leafRef = builder.len(); + builder.appendRegex(fieldName, re, flags); + return Element(this, impl.insertLeafElement(leafRef, fieldName.size() + 1)); +} + +Element Document::makeElementDBRef(StringData fieldName, StringData ns, const OID value) { + Impl& impl = getImpl(); + dassert(impl.doesNotAlias(fieldName)); + BSONObjBuilder& builder = impl.leafBuilder(); + const int leafRef = builder.len(); + builder.appendDBRef(fieldName, ns, value); + return Element(this, impl.insertLeafElement(leafRef, fieldName.size() + 1)); +} + +Element Document::makeElementCode(StringData fieldName, StringData value) { + Impl& impl = getImpl(); + dassert(impl.doesNotAlias(fieldName)); + dassert(impl.doesNotAlias(value)); + + BSONObjBuilder& builder = impl.leafBuilder(); + const int leafRef = builder.len(); + builder.appendCode(fieldName, value); + return Element(this, impl.insertLeafElement(leafRef, fieldName.size() + 1)); +} + +Element Document::makeElementSymbol(StringData fieldName, StringData value) { + Impl& impl = getImpl(); + dassert(impl.doesNotAlias(fieldName)); + dassert(impl.doesNotAlias(value)); + + BSONObjBuilder& builder = impl.leafBuilder(); + const int leafRef = builder.len(); + builder.appendSymbol(fieldName, value); + return Element(this, impl.insertLeafElement(leafRef, fieldName.size() + 1)); +} + +Element Document::makeElementCodeWithScope(StringData fieldName, + StringData code, + const BSONObj& scope) { + Impl& impl = getImpl(); + dassert(impl.doesNotAlias(fieldName)); + dassert(impl.doesNotAlias(code)); + dassert(impl.doesNotAlias(scope)); + + BSONObjBuilder& builder = impl.leafBuilder(); + const int leafRef = builder.len(); + builder.appendCodeWScope(fieldName, code, scope); + return Element(this, impl.insertLeafElement(leafRef, fieldName.size() + 1)); +} + +Element Document::makeElementInt(StringData fieldName, const int32_t value) { + Impl& impl = getImpl(); + dassert(impl.doesNotAlias(fieldName)); + + BSONObjBuilder& builder = impl.leafBuilder(); + const int leafRef = builder.len(); + builder.append(fieldName, value); + return Element(this, impl.insertLeafElement(leafRef, fieldName.size() + 1)); +} + +Element Document::makeElementTimestamp(StringData fieldName, const Timestamp value) { + Impl& impl = getImpl(); + dassert(impl.doesNotAlias(fieldName)); + + BSONObjBuilder& builder = impl.leafBuilder(); + const int leafRef = builder.len(); + builder.append(fieldName, value); + return Element(this, impl.insertLeafElement(leafRef, fieldName.size() + 1)); +} + +Element Document::makeElementLong(StringData fieldName, const int64_t value) { + Impl& impl = getImpl(); + dassert(impl.doesNotAlias(fieldName)); + + BSONObjBuilder& builder = impl.leafBuilder(); + const int leafRef = builder.len(); + builder.append(fieldName, static_cast<long long int>(value)); + return Element(this, impl.insertLeafElement(leafRef, fieldName.size() + 1)); +} + +Element Document::makeElementMinKey(StringData fieldName) { + Impl& impl = getImpl(); + dassert(impl.doesNotAlias(fieldName)); + + BSONObjBuilder& builder = impl.leafBuilder(); + const int leafRef = builder.len(); + builder.appendMinKey(fieldName); + return Element(this, impl.insertLeafElement(leafRef, fieldName.size() + 1)); +} + +Element Document::makeElementMaxKey(StringData fieldName) { + Impl& impl = getImpl(); + dassert(impl.doesNotAlias(fieldName)); + + BSONObjBuilder& builder = impl.leafBuilder(); + const int leafRef = builder.len(); + builder.appendMaxKey(fieldName); + return Element(this, impl.insertLeafElement(leafRef, fieldName.size() + 1)); +} + +Element Document::makeElement(const BSONElement& value) { + Impl& impl = getImpl(); + + // Attempts to create an EOO element are translated to returning an invalid + // Element. For array and object nodes, we flow through the custom + // makeElement{Object|Array} methods, since they have special logic to deal with + // opaqueness. Otherwise, we can just insert via appendAs. + if (value.type() == mongo::EOO) + return end(); + else if (value.type() == mongo::Object) + return makeElementObject(value.fieldNameStringData(), value.Obj()); + else if (value.type() == mongo::Array) + return makeElementArray(value.fieldNameStringData(), value.Obj()); + else { dassert(impl.doesNotAlias(value)); - - BSONObjBuilder& builder = impl.leafBuilder(); - const int leafRef = builder.len(); - builder.appendSymbol(fieldName, value); - return Element(this, impl.insertLeafElement(leafRef, fieldName.size() + 1)); - } - - Element Document::makeElementCodeWithScope(StringData fieldName, - StringData code, const BSONObj& scope) { - Impl& impl = getImpl(); - dassert(impl.doesNotAlias(fieldName)); - dassert(impl.doesNotAlias(code)); - dassert(impl.doesNotAlias(scope)); - BSONObjBuilder& builder = impl.leafBuilder(); const int leafRef = builder.len(); - builder.appendCodeWScope(fieldName, code, scope); - return Element(this, impl.insertLeafElement(leafRef, fieldName.size() + 1)); - } - - Element Document::makeElementInt(StringData fieldName, const int32_t value) { - Impl& impl = getImpl(); - dassert(impl.doesNotAlias(fieldName)); - + builder.append(value); + return Element(this, impl.insertLeafElement(leafRef, value.fieldNameSize())); + } +} + +Element Document::makeElementWithNewFieldName(StringData fieldName, const BSONElement& value) { + Impl& impl = getImpl(); + + // See the above makeElement for notes on these cases. + if (value.type() == mongo::EOO) + return end(); + else if (value.type() == mongo::Object) + return makeElementObject(fieldName, value.Obj()); + else if (value.type() == mongo::Array) + return makeElementArray(fieldName, value.Obj()); + else { + dassert(getImpl().doesNotAliasLeafBuilder(fieldName)); + dassert(getImpl().doesNotAlias(value)); BSONObjBuilder& builder = impl.leafBuilder(); const int leafRef = builder.len(); - builder.append(fieldName, value); + builder.appendAs(value, fieldName); return Element(this, impl.insertLeafElement(leafRef, fieldName.size() + 1)); } +} - Element Document::makeElementTimestamp(StringData fieldName, const Timestamp value) { - Impl& impl = getImpl(); - dassert(impl.doesNotAlias(fieldName)); - - BSONObjBuilder& builder = impl.leafBuilder(); - const int leafRef = builder.len(); - builder.append(fieldName, value); - return Element(this, impl.insertLeafElement(leafRef, fieldName.size() + 1)); - } - - Element Document::makeElementLong(StringData fieldName, const int64_t value) { - Impl& impl = getImpl(); - dassert(impl.doesNotAlias(fieldName)); - - BSONObjBuilder& builder = impl.leafBuilder(); - const int leafRef = builder.len(); - builder.append(fieldName, static_cast<long long int>(value)); - return Element(this, impl.insertLeafElement(leafRef, fieldName.size() + 1)); - } - - Element Document::makeElementMinKey(StringData fieldName) { - Impl& impl = getImpl(); - dassert(impl.doesNotAlias(fieldName)); - - BSONObjBuilder& builder = impl.leafBuilder(); - const int leafRef = builder.len(); - builder.appendMinKey(fieldName); - return Element(this, impl.insertLeafElement(leafRef, fieldName.size() + 1)); - } - - Element Document::makeElementMaxKey(StringData fieldName) { - Impl& impl = getImpl(); - dassert(impl.doesNotAlias(fieldName)); - - BSONObjBuilder& builder = impl.leafBuilder(); - const int leafRef = builder.len(); - builder.appendMaxKey(fieldName); - return Element(this, impl.insertLeafElement(leafRef, fieldName.size() + 1)); - } - - Element Document::makeElement(const BSONElement& value) { - Impl& impl = getImpl(); - - // Attempts to create an EOO element are translated to returning an invalid - // Element. For array and object nodes, we flow through the custom - // makeElement{Object|Array} methods, since they have special logic to deal with - // opaqueness. Otherwise, we can just insert via appendAs. - if (value.type() == mongo::EOO) - return end(); - else if(value.type() == mongo::Object) - return makeElementObject(value.fieldNameStringData(), value.Obj()); - else if(value.type() == mongo::Array) - return makeElementArray(value.fieldNameStringData(), value.Obj()); - else { - dassert(impl.doesNotAlias(value)); - BSONObjBuilder& builder = impl.leafBuilder(); - const int leafRef = builder.len(); - builder.append(value); - return Element(this, impl.insertLeafElement(leafRef, value.fieldNameSize())); - } - } - - Element Document::makeElementWithNewFieldName(StringData fieldName, - const BSONElement& value) { - Impl& impl = getImpl(); +Element Document::makeElementSafeNum(StringData fieldName, SafeNum value) { + dassert(getImpl().doesNotAlias(fieldName)); - // See the above makeElement for notes on these cases. - if (value.type() == mongo::EOO) - return end(); - else if(value.type() == mongo::Object) - return makeElementObject(fieldName, value.Obj()); - else if(value.type() == mongo::Array) - return makeElementArray(fieldName, value.Obj()); - else { - dassert(getImpl().doesNotAliasLeafBuilder(fieldName)); - dassert(getImpl().doesNotAlias(value)); - BSONObjBuilder& builder = impl.leafBuilder(); - const int leafRef = builder.len(); - builder.appendAs(value, fieldName); - return Element(this, impl.insertLeafElement(leafRef, fieldName.size() + 1)); - } - } - - Element Document::makeElementSafeNum(StringData fieldName, SafeNum value) { - - dassert(getImpl().doesNotAlias(fieldName)); - - switch (value.type()) { + switch (value.type()) { case mongo::NumberInt: return makeElementInt(fieldName, value._value.int32Val); case mongo::NumberLong: @@ -2573,93 +2520,89 @@ namespace mutablebson { default: // Return an invalid element to indicate that we failed. return end(); - } } - - Element Document::makeElement(ConstElement element) { - return makeElement(element, NULL); - } - - Element Document::makeElementWithNewFieldName(StringData fieldName, - ConstElement element) { - return makeElement(element, &fieldName); - } - - Element Document::makeRootElement() { - return makeElementObject(StringData(kRootFieldName, StringData::LiteralTag())); - } - - Element Document::makeRootElement(const BSONObj& value) { - Impl& impl = getImpl(); - Element::RepIdx newEltIdx = Element::kInvalidRepIdx; - ElementRep* newElt = &impl.makeNewRep(&newEltIdx); - - // A BSONObj provided for the root Element is stored in _objects rather than being - // copied like all other BSONObjs. - newElt->objIdx = impl.insertObject(value); - impl.insertFieldName(*newElt, kRootFieldName); - - // Strictly, the following is a lie: the root isn't serialized, because it doesn't - // have a contiguous fieldname. However, it is a useful fiction to pretend that it - // is, so we can easily check if we have a 'pristine' document state by checking if - // the root is marked as serialized. - newElt->serialized = true; - - // If the provided value is empty, mark it as having no children, otherwise mark the - // children as opaque. - if (value.isEmpty()) - newElt->child.left = Element::kInvalidRepIdx; - else - newElt->child.left = Element::kOpaqueRepIdx; - newElt->child.right = newElt->child.left; - - return Element(this, newEltIdx); - } - - Element Document::makeElement(ConstElement element, const StringData* fieldName) { - - Impl& impl = getImpl(); - - if (this == &element.getDocument()) { - - // If the Element that we want to build from belongs to this Document, then we have - // to first copy it to the side, and then back in, since otherwise we might be - // attempting both read to and write from the underlying BufBuilder simultaneously, - // which will not work. - BSONObjBuilder builder; - impl.writeElement(element.getIdx(), &builder, fieldName); - BSONObj built = builder.done(); - BSONElement newElement = built.firstElement(); - return makeElement(newElement); - - } else { - - // If the Element belongs to another document, then we can just stream it into our - // builder. We still do need to dassert that the field name doesn't alias us - // somehow. - if (fieldName) { - dassert(impl.doesNotAlias(*fieldName)); - } - BSONObjBuilder& builder = impl.leafBuilder(); - const int leafRef = builder.len(); - - const Impl& oImpl = element.getDocument().getImpl(); - oImpl.writeElement(element.getIdx(), &builder, fieldName); - return Element(this, impl.insertLeafElement(leafRef)); +} + +Element Document::makeElement(ConstElement element) { + return makeElement(element, NULL); +} + +Element Document::makeElementWithNewFieldName(StringData fieldName, ConstElement element) { + return makeElement(element, &fieldName); +} + +Element Document::makeRootElement() { + return makeElementObject(StringData(kRootFieldName, StringData::LiteralTag())); +} + +Element Document::makeRootElement(const BSONObj& value) { + Impl& impl = getImpl(); + Element::RepIdx newEltIdx = Element::kInvalidRepIdx; + ElementRep* newElt = &impl.makeNewRep(&newEltIdx); + + // A BSONObj provided for the root Element is stored in _objects rather than being + // copied like all other BSONObjs. + newElt->objIdx = impl.insertObject(value); + impl.insertFieldName(*newElt, kRootFieldName); + + // Strictly, the following is a lie: the root isn't serialized, because it doesn't + // have a contiguous fieldname. However, it is a useful fiction to pretend that it + // is, so we can easily check if we have a 'pristine' document state by checking if + // the root is marked as serialized. + newElt->serialized = true; + + // If the provided value is empty, mark it as having no children, otherwise mark the + // children as opaque. + if (value.isEmpty()) + newElt->child.left = Element::kInvalidRepIdx; + else + newElt->child.left = Element::kOpaqueRepIdx; + newElt->child.right = newElt->child.left; + + return Element(this, newEltIdx); +} + +Element Document::makeElement(ConstElement element, const StringData* fieldName) { + Impl& impl = getImpl(); + + if (this == &element.getDocument()) { + // If the Element that we want to build from belongs to this Document, then we have + // to first copy it to the side, and then back in, since otherwise we might be + // attempting both read to and write from the underlying BufBuilder simultaneously, + // which will not work. + BSONObjBuilder builder; + impl.writeElement(element.getIdx(), &builder, fieldName); + BSONObj built = builder.done(); + BSONElement newElement = built.firstElement(); + return makeElement(newElement); + + } else { + // If the Element belongs to another document, then we can just stream it into our + // builder. We still do need to dassert that the field name doesn't alias us + // somehow. + if (fieldName) { + dassert(impl.doesNotAlias(*fieldName)); } - } + BSONObjBuilder& builder = impl.leafBuilder(); + const int leafRef = builder.len(); - inline Document::Impl& Document::getImpl() { - // Don't use unique_ptr<Impl>::operator* since it may generate assertions that the - // pointer is non-null, but we already know that to be always and forever true, and - // otherwise the assertion code gets spammed into every method that inlines the call to - // this function. We just dereference the pointer returned from 'get' ourselves. - return *_impl.get(); + const Impl& oImpl = element.getDocument().getImpl(); + oImpl.writeElement(element.getIdx(), &builder, fieldName); + return Element(this, impl.insertLeafElement(leafRef)); } +} - inline const Document::Impl& Document::getImpl() const { - return *_impl.get(); - } +inline Document::Impl& Document::getImpl() { + // Don't use unique_ptr<Impl>::operator* since it may generate assertions that the + // pointer is non-null, but we already know that to be always and forever true, and + // otherwise the assertion code gets spammed into every method that inlines the call to + // this function. We just dereference the pointer returned from 'get' ourselves. + return *_impl.get(); +} + +inline const Document::Impl& Document::getImpl() const { + return *_impl.get(); +} -} // namespace mutablebson -} // namespace mongo +} // namespace mutablebson +} // namespace mongo diff --git a/src/mongo/bson/mutable/document.h b/src/mongo/bson/mutable/document.h index 9655daddfab..30c79f1552b 100644 --- a/src/mongo/bson/mutable/document.h +++ b/src/mongo/bson/mutable/document.h @@ -40,481 +40,476 @@ namespace mongo { namespace mutablebson { - /** Mutable BSON Overview - * - * Mutable BSON provides classes to facilitate the manipulation of existing BSON objects - * or the construction of new BSON objects from scratch in an incremental fashion. The - * operations (including additions, deletions, renamings, type changes and value - * modification) that are to be performed do not need to be known ahead of time, and do - * not need to occur in any particular order. This is in contrast to BSONObjBuilder and - * BSONArrayBuilder which offer only serialization and cannot revise already serialized - * data. If you need to build a BSONObj but you know upfront what you need to build then - * you should use BSONObjBuilder and BSONArrayBuilder directly as they will be faster and - * less resource intensive. - * - * The classes in this library (Document, Element, and ConstElement) present a tree-like - * (or DOM like) interface. Elements are logically equivalent to BSONElements: they carry - * a type, a field name, and a value. Every Element belongs to a Document, which roots the - * tree, and Elements of proper type (mongo::Object or mongo::Array) may have child - * Elements of their own. Given an Element, you may navigate to the Element's parent, to - * its siblings to the left or right of the Element in the tree, and to the leftmost or - * rightmost children of the Element. Note that some Elements may not offer all of these - * relationships: An Element that represents a terminal BSON value (like an integer) will - * not have children (though it may well have siblings). Similarly, an Element that is an - * 'only child' will not have any left or right siblings. Given a Document, you may begin - * navigating by obtaining the root Element of the tree by calling Document::root. See the - * documentation for the Element class for the specific navigation methods that will be - * available from the root Element. - * - * Elements within the Document may be modified in various ways: the value of the Element - * may be changed, the Element may be removed, it may be renamed, and if it is eligible - * for children (i.e. it represents a mongo::Array or mongo::Object) it may have child - * Elements added to it. Once you have completed building or modifying the Document, you - * may write it back out to a BSONObjBuilder by calling Document::writeTo. You may also - * serialize individual Elements within the Document to BSONObjBuilder or BSONArrayBuilder - * objects by calling Element::writeTo or Element::writeArrayTo. - * - * In addition to the above capabilities, there are algorithms provided in 'algorithm.h' - * to help with tasks like searching for Elements that match a predicate or for sorting - * the children of an Object Element. - * - * Example 1: Building up a document from scratch, reworking it, and then serializing it: - - namespace mmb = mongo::mutablebson; - // Create a new document - mmb::Document doc; - // doc contents: '{}' - - // Get the root of the document. - mmb::Element root = doc.root(); - - // Create a new mongo::NumberInt typed Element to represent life, the universe, and - // everything, then push that Element into the root object, making it a child of root. - mmb::Element e0 = doc.makeElementInt("ltuae", 42); - root.pushBack(e0); - // doc contents: '{ ltuae : 42 }' - - // Create a new empty mongo::Object-typed Element named 'magic', and push it back as a - // child of the root, making it a sibling of e0. - mmb::Element e1 = doc.makeElementObject("magic"); - root.pushBack(e1); - // doc contents: '{ ltuae : 42, magic : {} }' - - // Create a new mongo::NumberDouble typed Element to represent Pi, and insert it as child - // of the new object we just created. - mmb::Element e3 = doc.makeElementDouble("pi", 3.14); - e1.pushBack(e3); - // doc contents: '{ ltuae : 42, magic : { pi : 3.14 } }' - - // Create a new mongo::NumberDouble to represent Plancks constant in electrovolt - // micrometers, and add it as a child of the 'magic' object. - mmb::Element e4 = doc.makeElementDouble("hbar", 1.239); - e1.pushBack(e4); - // doc contents: '{ ltuae : 42, magic : { pi : 3.14, hbar : 1.239 } }' - - // Rename the parent element of 'hbar' to be 'constants'. - e4.parent().rename("constants"); - // doc contents: '{ ltuae : 42, constants : { pi : 3.14, hbar : 1.239 } }' - - // Rename 'ltuae' to 'answer' by accessing it as the root objects left child. - doc.root().leftChild().rename("answer"); - // doc contents: '{ answer : 42, constants : { pi : 3.14, hbar : 1.239 } }' - - // Sort the constants by name. - mmb::sortChildren(doc.root().rightChild(), mmb::FieldNameLessThan()); - // doc contents: '{ answer : 42, constants : { hbar : 1.239, pi : 3.14 } }' - - mongo::BSONObjBuilder builder; - doc.writeTo(&builder); - mongo::BSONObj result = builder.obj(); - // result contents: '{ answer : 42, constants : { hbar : 1.239, pi : 3.14 } }' - - * While you can use this library to build Documents from scratch, its real purpose is to - * manipulate existing BSONObjs. A BSONObj may be passed to the Document constructor or to - * Document::make[Object|Array]Element, in which case the Document or Element will reflect - * the values contained within the provided BSONObj. Modifications will not alter the - * underlying BSONObj: they are held off to the side within the Document. However, when - * the Document is subsequently written back out to a BSONObjBuilder, the modifications - * applied to the Document will be reflected in the serialized version. - * - * Example 2: Modifying an existing BSONObj (some error handling removed for length) - - namespace mmb = mongo::mutablebson; - - static const char inJson[] = - "{" - " 'whale': { 'alive': true, 'dv': -9.8, 'height': 50, attrs : [ 'big' ] }," - " 'petunias': { 'alive': true, 'dv': -9.8, 'height': 50 } " - "}"; - mongo::BSONObj obj = mongo::fromjson(inJson); - - // Create a new document representing the BSONObj with the above contents. - mmb::Document doc(obj); - - // The whale hits the planet and dies. - mmb::Element whale = mmb::findFirstChildNamed(doc.root(), "whale"); - // Find the 'dv' field in the whale. - mmb::Element whale_deltav = mmb::findFirstChildNamed(whale, "dv"); - // Set the dv field to zero. - whale_deltav.setValueDouble(0.0); - // Find the 'height' field in the whale. - mmb::Element whale_height = mmb::findFirstChildNamed(whale, "height"); - // Set the height field to zero. - whale_deltav.setValueDouble(0); - // Find the 'alive' field, and set it to false. - mmb::Element whale_alive = mmb::findFirstChildNamed(whale, "alive"); - whale_alive.setValueBool(false); - - // The petunias survive, update its fields much like we did above. - mmb::Element petunias = mmb::findFirstChildNamed(doc.root(), "petunias"); - mmb::Element petunias_deltav = mmb::findFirstChildNamed(petunias, "dv"); - petunias_deltav.setValueDouble(0.0); - mmb::Element petunias_height = mmb::findFirstChildNamed(petunias, "height"); - petunias_deltav.setValueDouble(0); - - // Replace the whale by its wreckage, saving only its attributes: - // Construct a new mongo::Object element for the ex-whale. - mmb::Element ex_whale = doc.makeElementObject("ex-whale"); - doc.root().pushBack(ex_whale); - // Find the attributes of the old 'whale' element. - mmb::Element whale_attrs = mmb::findFirstChildNamed(whale, "attrs"); - // Remove the attributes from the whale (they remain valid, but detached). - whale_attrs.remove(); - // Add the attributes into the ex-whale. - ex_whale.pushBack(whale_attrs); - // Remove the whale object. - whale.remove(); - - // Current state of document: +/** Mutable BSON Overview + * + * Mutable BSON provides classes to facilitate the manipulation of existing BSON objects + * or the construction of new BSON objects from scratch in an incremental fashion. The + * operations (including additions, deletions, renamings, type changes and value + * modification) that are to be performed do not need to be known ahead of time, and do + * not need to occur in any particular order. This is in contrast to BSONObjBuilder and + * BSONArrayBuilder which offer only serialization and cannot revise already serialized + * data. If you need to build a BSONObj but you know upfront what you need to build then + * you should use BSONObjBuilder and BSONArrayBuilder directly as they will be faster and + * less resource intensive. + * + * The classes in this library (Document, Element, and ConstElement) present a tree-like + * (or DOM like) interface. Elements are logically equivalent to BSONElements: they carry + * a type, a field name, and a value. Every Element belongs to a Document, which roots the + * tree, and Elements of proper type (mongo::Object or mongo::Array) may have child + * Elements of their own. Given an Element, you may navigate to the Element's parent, to + * its siblings to the left or right of the Element in the tree, and to the leftmost or + * rightmost children of the Element. Note that some Elements may not offer all of these + * relationships: An Element that represents a terminal BSON value (like an integer) will + * not have children (though it may well have siblings). Similarly, an Element that is an + * 'only child' will not have any left or right siblings. Given a Document, you may begin + * navigating by obtaining the root Element of the tree by calling Document::root. See the + * documentation for the Element class for the specific navigation methods that will be + * available from the root Element. + * + * Elements within the Document may be modified in various ways: the value of the Element + * may be changed, the Element may be removed, it may be renamed, and if it is eligible + * for children (i.e. it represents a mongo::Array or mongo::Object) it may have child + * Elements added to it. Once you have completed building or modifying the Document, you + * may write it back out to a BSONObjBuilder by calling Document::writeTo. You may also + * serialize individual Elements within the Document to BSONObjBuilder or BSONArrayBuilder + * objects by calling Element::writeTo or Element::writeArrayTo. + * + * In addition to the above capabilities, there are algorithms provided in 'algorithm.h' + * to help with tasks like searching for Elements that match a predicate or for sorting + * the children of an Object Element. + * + * Example 1: Building up a document from scratch, reworking it, and then serializing it: + + namespace mmb = mongo::mutablebson; + // Create a new document + mmb::Document doc; + // doc contents: '{}' + + // Get the root of the document. + mmb::Element root = doc.root(); + + // Create a new mongo::NumberInt typed Element to represent life, the universe, and + // everything, then push that Element into the root object, making it a child of root. + mmb::Element e0 = doc.makeElementInt("ltuae", 42); + root.pushBack(e0); + // doc contents: '{ ltuae : 42 }' + + // Create a new empty mongo::Object-typed Element named 'magic', and push it back as a + // child of the root, making it a sibling of e0. + mmb::Element e1 = doc.makeElementObject("magic"); + root.pushBack(e1); + // doc contents: '{ ltuae : 42, magic : {} }' + + // Create a new mongo::NumberDouble typed Element to represent Pi, and insert it as child + // of the new object we just created. + mmb::Element e3 = doc.makeElementDouble("pi", 3.14); + e1.pushBack(e3); + // doc contents: '{ ltuae : 42, magic : { pi : 3.14 } }' + + // Create a new mongo::NumberDouble to represent Plancks constant in electrovolt + // micrometers, and add it as a child of the 'magic' object. + mmb::Element e4 = doc.makeElementDouble("hbar", 1.239); + e1.pushBack(e4); + // doc contents: '{ ltuae : 42, magic : { pi : 3.14, hbar : 1.239 } }' + + // Rename the parent element of 'hbar' to be 'constants'. + e4.parent().rename("constants"); + // doc contents: '{ ltuae : 42, constants : { pi : 3.14, hbar : 1.239 } }' + + // Rename 'ltuae' to 'answer' by accessing it as the root objects left child. + doc.root().leftChild().rename("answer"); + // doc contents: '{ answer : 42, constants : { pi : 3.14, hbar : 1.239 } }' + + // Sort the constants by name. + mmb::sortChildren(doc.root().rightChild(), mmb::FieldNameLessThan()); + // doc contents: '{ answer : 42, constants : { hbar : 1.239, pi : 3.14 } }' + + mongo::BSONObjBuilder builder; + doc.writeTo(&builder); + mongo::BSONObj result = builder.obj(); + // result contents: '{ answer : 42, constants : { hbar : 1.239, pi : 3.14 } }' + + * While you can use this library to build Documents from scratch, its real purpose is to + * manipulate existing BSONObjs. A BSONObj may be passed to the Document constructor or to + * Document::make[Object|Array]Element, in which case the Document or Element will reflect + * the values contained within the provided BSONObj. Modifications will not alter the + * underlying BSONObj: they are held off to the side within the Document. However, when + * the Document is subsequently written back out to a BSONObjBuilder, the modifications + * applied to the Document will be reflected in the serialized version. + * + * Example 2: Modifying an existing BSONObj (some error handling removed for length) + + namespace mmb = mongo::mutablebson; + + static const char inJson[] = "{" - " 'petunias': { 'alive': true, 'dv': 0.0, 'height': 50 }," - " 'ex-whale': { 'attrs': [ 'big' ] } })" + " 'whale': { 'alive': true, 'dv': -9.8, 'height': 50, attrs : [ 'big' ] }," + " 'petunias': { 'alive': true, 'dv': -9.8, 'height': 50 } " "}"; + mongo::BSONObj obj = mongo::fromjson(inJson); + + // Create a new document representing the BSONObj with the above contents. + mmb::Document doc(obj); + + // The whale hits the planet and dies. + mmb::Element whale = mmb::findFirstChildNamed(doc.root(), "whale"); + // Find the 'dv' field in the whale. + mmb::Element whale_deltav = mmb::findFirstChildNamed(whale, "dv"); + // Set the dv field to zero. + whale_deltav.setValueDouble(0.0); + // Find the 'height' field in the whale. + mmb::Element whale_height = mmb::findFirstChildNamed(whale, "height"); + // Set the height field to zero. + whale_deltav.setValueDouble(0); + // Find the 'alive' field, and set it to false. + mmb::Element whale_alive = mmb::findFirstChildNamed(whale, "alive"); + whale_alive.setValueBool(false); + + // The petunias survive, update its fields much like we did above. + mmb::Element petunias = mmb::findFirstChildNamed(doc.root(), "petunias"); + mmb::Element petunias_deltav = mmb::findFirstChildNamed(petunias, "dv"); + petunias_deltav.setValueDouble(0.0); + mmb::Element petunias_height = mmb::findFirstChildNamed(petunias, "height"); + petunias_deltav.setValueDouble(0); + + // Replace the whale by its wreckage, saving only its attributes: + // Construct a new mongo::Object element for the ex-whale. + mmb::Element ex_whale = doc.makeElementObject("ex-whale"); + doc.root().pushBack(ex_whale); + // Find the attributes of the old 'whale' element. + mmb::Element whale_attrs = mmb::findFirstChildNamed(whale, "attrs"); + // Remove the attributes from the whale (they remain valid, but detached). + whale_attrs.remove(); + // Add the attributes into the ex-whale. + ex_whale.pushBack(whale_attrs); + // Remove the whale object. + whale.remove(); + + // Current state of document: + "{" + " 'petunias': { 'alive': true, 'dv': 0.0, 'height': 50 }," + " 'ex-whale': { 'attrs': [ 'big' ] } })" + "}"; + + * Both of the above examples are derived from tests in mutable_bson_test.cpp, see the + * tests Example1 and Example2 if you would like to play with the code. + * + * Additional details on Element and Document are available in their class and member + * comments. + */ - * Both of the above examples are derived from tests in mutable_bson_test.cpp, see the - * tests Example1 and Example2 if you would like to play with the code. - * - * Additional details on Element and Document are available in their class and member - * comments. +/** Document is the entry point into the mutable BSON system. It has a fairly simple + * API. It acts as an owner for the Element resources of the document, provides a + * pre-constructed designated root Object Element, and acts as a factory for new Elements, + * which may then be attached to the root or to other Elements by calling the appropriate + * topology mutation methods in Element. + * + * The default constructor builds an empty Document which you may then extend by creating + * new Elements and manipulating the tree topology. It is also possible to build a + * Document that derives its initial values from a BSONObj. The given BSONObj will not be + * modified, but it also must not be modified elsewhere while Document is using it. Unlike + * all other calls in this library where a BSONObj is passed in, the one argument Document + * constructor *does not copy* the BSONObj's contents, so they must remain valid for the + * duration of Documents lifetime. Document does hold a copy of the BSONObj itself, so it + * will up the refcount if the BSONObj internals are counted. + * + * Newly constructed Elements formed by calls to 'makeElement[Type]' methods are not + * attached to the root of the document. You must explicitly attach them somewhere. If you + * lose the Element value that is returned to you from a 'makeElement' call before you + * attach it to the tree then the value will be unreachable. Elements in a document do not + * outlive the Document. + * + * Document provides a convenience method to serialize all of the Elements in the tree + * that are reachable from the root element to a BSONObjBuilder. In general you should use + * this in preference to root().writeTo() if you mean to write the entire + * Document. Similarly, Document provides wrappers for comparisons that simply delegate to + * comparison operations on the root Element. + * + * A 'const Document' is very limited: you may only write its contents out or obtain a + * ConstElement for the root. ConstElement is much like Element, but does not permit + * mutations. See the class comment for ConstElement for more information. + */ +class Document { + // TODO: In principle there is nothing that prevents implementing a deep copy for + // Document, but for now it is not permitted. + MONGO_DISALLOW_COPYING(Document); + +public: + // + // Lifecycle + // + + /** Construct a new empty document. */ + Document(); + + enum InPlaceMode { + kInPlaceDisabled = 0, + kInPlaceEnabled = 1, + }; + + /** Construct new document for the given BSONObj. The data in 'value' is NOT copied. By + * default, queueing of in-place modifications against the underlying document is + * permitted. To disable this behavior, explicitly pass kInPlaceDisabled. + */ + explicit Document(const BSONObj& value, InPlaceMode inPlaceMode = kInPlaceEnabled); + + /** Abandon all internal state associated with this Document, and return to a state + * semantically equivalent to that yielded by a call to the default constructor. All + * objects associated with the current document state are invalidated (e.g. Elements, + * BSONElements, BSONObj's values, field names, etc.). This method is useful because + * it may (though it is not required to) preserve the memory allocation of the + * internal data structures of Document. If you need to logically create and destroy + * many Documents in serial, it may be faster to reset. */ + void reset(); - /** Document is the entry point into the mutable BSON system. It has a fairly simple - * API. It acts as an owner for the Element resources of the document, provides a - * pre-constructed designated root Object Element, and acts as a factory for new Elements, - * which may then be attached to the root or to other Elements by calling the appropriate - * topology mutation methods in Element. - * - * The default constructor builds an empty Document which you may then extend by creating - * new Elements and manipulating the tree topology. It is also possible to build a - * Document that derives its initial values from a BSONObj. The given BSONObj will not be - * modified, but it also must not be modified elsewhere while Document is using it. Unlike - * all other calls in this library where a BSONObj is passed in, the one argument Document - * constructor *does not copy* the BSONObj's contents, so they must remain valid for the - * duration of Documents lifetime. Document does hold a copy of the BSONObj itself, so it - * will up the refcount if the BSONObj internals are counted. - * - * Newly constructed Elements formed by calls to 'makeElement[Type]' methods are not - * attached to the root of the document. You must explicitly attach them somewhere. If you - * lose the Element value that is returned to you from a 'makeElement' call before you - * attach it to the tree then the value will be unreachable. Elements in a document do not - * outlive the Document. - * - * Document provides a convenience method to serialize all of the Elements in the tree - * that are reachable from the root element to a BSONObjBuilder. In general you should use - * this in preference to root().writeTo() if you mean to write the entire - * Document. Similarly, Document provides wrappers for comparisons that simply delegate to - * comparison operations on the root Element. - * - * A 'const Document' is very limited: you may only write its contents out or obtain a - * ConstElement for the root. ConstElement is much like Element, but does not permit - * mutations. See the class comment for ConstElement for more information. + /** As the no argument 'reset', but returns to a state semantically equivalent to that + * yielded by a call to the two argument constructor with the arguments provided + * here. As with the other 'reset' call, all associated objects are invalidated. */ + void reset(const BSONObj& value, InPlaceMode inPlaceMode = kInPlaceEnabled); + + /** Destroy this document permanently */ + ~Document(); + + + // + // Comparison API + // + + /** Compare this Document to 'other' with the semantics of BSONObj::woCompare. */ + inline int compareWith(const Document& other, bool considerFieldName = true) const; + + /** Compare this Document to 'other' with the semantics of BSONObj::woCompare. */ + inline int compareWithBSONObj(const BSONObj& other, bool considerFieldName = true) const; + + + // + // Serialization API + // + + /** Serialize the Elements reachable from the root Element of this Document to the + * provided builder. */ - class Document { + inline void writeTo(BSONObjBuilder* builder) const; + + /** Serialize the Elements reachable from the root Element of this Document and return + * the result as a BSONObj. + */ + inline BSONObj getObject() const; + + + // + // Element creation API. + // + // Newly created elements are not attached to the tree (effectively, they are + // 'roots'). You must call one of the topology management methods in 'Element' to + // connect the newly created Element to another Element in the Document, possibly the + // Element referenced by Document::root. Elements do not outlive the Document. + // - // TODO: In principle there is nothing that prevents implementing a deep copy for - // Document, but for now it is not permitted. - MONGO_DISALLOW_COPYING(Document); + /** Create a new double Element with the given value and field name. */ + Element makeElementDouble(StringData fieldName, double value); - public: + /** Create a new std::string Element with the given value and field name. */ + Element makeElementString(StringData fieldName, StringData value); - // - // Lifecycle - // + /** Create a new empty object Element with the given field name. */ + Element makeElementObject(StringData fieldName); - /** Construct a new empty document. */ - Document(); + /** Create a new object Element with the given field name. The data in 'value' is + * copied. + */ + Element makeElementObject(StringData fieldName, const BSONObj& value); - enum InPlaceMode { - kInPlaceDisabled = 0, - kInPlaceEnabled = 1, - }; + /** Create a new empty array Element with the given field name. */ + Element makeElementArray(StringData fieldName); - /** Construct new document for the given BSONObj. The data in 'value' is NOT copied. By - * default, queueing of in-place modifications against the underlying document is - * permitted. To disable this behavior, explicitly pass kInPlaceDisabled. - */ - explicit Document(const BSONObj& value, InPlaceMode inPlaceMode = kInPlaceEnabled); + /** Create a new array Element with the given field name. The data in 'value' is + * copied. + */ + Element makeElementArray(StringData fieldName, const BSONObj& value); - /** Abandon all internal state associated with this Document, and return to a state - * semantically equivalent to that yielded by a call to the default constructor. All - * objects associated with the current document state are invalidated (e.g. Elements, - * BSONElements, BSONObj's values, field names, etc.). This method is useful because - * it may (though it is not required to) preserve the memory allocation of the - * internal data structures of Document. If you need to logically create and destroy - * many Documents in serial, it may be faster to reset. - */ - void reset(); + /** Create a new binary Element with the given data and field name. */ + Element makeElementBinary(StringData fieldName, + uint32_t len, + BinDataType binType, + const void* data); - /** As the no argument 'reset', but returns to a state semantically equivalent to that - * yielded by a call to the two argument constructor with the arguments provided - * here. As with the other 'reset' call, all associated objects are invalidated. */ - void reset(const BSONObj& value, InPlaceMode inPlaceMode = kInPlaceEnabled); + /** Create a new undefined Element with the given field name. */ + Element makeElementUndefined(StringData fieldName); - /** Destroy this document permanently */ - ~Document(); + /** Create a new OID + Element with the given field name. */ + Element makeElementNewOID(StringData fieldName); + /** Create a new OID Element with the given value and field name. */ + Element makeElementOID(StringData fieldName, mongo::OID value); - // - // Comparison API - // + /** Create a new bool Element with the given value and field name. */ + Element makeElementBool(StringData fieldName, bool value); - /** Compare this Document to 'other' with the semantics of BSONObj::woCompare. */ - inline int compareWith(const Document& other, bool considerFieldName = true) const; + /** Create a new date Element with the given value and field name. */ + Element makeElementDate(StringData fieldName, Date_t value); - /** Compare this Document to 'other' with the semantics of BSONObj::woCompare. */ - inline int compareWithBSONObj(const BSONObj& other, bool considerFieldName = true) const; + /** Create a new null Element with the given field name. */ + Element makeElementNull(StringData fieldName); + /** Create a new regex Element with the given data and field name. */ + Element makeElementRegex(StringData fieldName, StringData regex, StringData flags); - // - // Serialization API - // + /** Create a new DBRef Element with the given data and field name. */ + Element makeElementDBRef(StringData fieldName, StringData ns, mongo::OID oid); - /** Serialize the Elements reachable from the root Element of this Document to the - * provided builder. - */ - inline void writeTo(BSONObjBuilder* builder) const; + /** Create a new code Element with the given value and field name. */ + Element makeElementCode(StringData fieldName, StringData value); - /** Serialize the Elements reachable from the root Element of this Document and return - * the result as a BSONObj. - */ - inline BSONObj getObject() const; + /** Create a new symbol Element with the given value and field name. */ + Element makeElementSymbol(StringData fieldName, StringData value); + /** Create a new scoped code Element with the given data and field name. */ + Element makeElementCodeWithScope(StringData fieldName, StringData code, const BSONObj& scope); - // - // Element creation API. - // - // Newly created elements are not attached to the tree (effectively, they are - // 'roots'). You must call one of the topology management methods in 'Element' to - // connect the newly created Element to another Element in the Document, possibly the - // Element referenced by Document::root. Elements do not outlive the Document. - // + /** Create a new integer Element with the given value and field name. */ + Element makeElementInt(StringData fieldName, int32_t value); - /** Create a new double Element with the given value and field name. */ - Element makeElementDouble(StringData fieldName, double value); + /** Create a new timestamp Element with the given value and field name. */ + Element makeElementTimestamp(StringData fieldName, Timestamp value); - /** Create a new std::string Element with the given value and field name. */ - Element makeElementString(StringData fieldName, StringData value); + /** Create a new long integer Element with the given value and field name. */ + Element makeElementLong(StringData fieldName, int64_t value); - /** Create a new empty object Element with the given field name. */ - Element makeElementObject(StringData fieldName); + /** Create a new min key Element with the given field name. */ + Element makeElementMinKey(StringData fieldName); - /** Create a new object Element with the given field name. The data in 'value' is - * copied. - */ - Element makeElementObject(StringData fieldName, const BSONObj& value); + /** Create a new max key Element with the given field name. */ + Element makeElementMaxKey(StringData fieldName); - /** Create a new empty array Element with the given field name. */ - Element makeElementArray(StringData fieldName); - /** Create a new array Element with the given field name. The data in 'value' is - * copied. - */ - Element makeElementArray(StringData fieldName, const BSONObj& value); + // + // Element creation methods from variant types + // - /** Create a new binary Element with the given data and field name. */ - Element makeElementBinary( - StringData fieldName, uint32_t len, BinDataType binType, const void* data); + /** Construct a new Element with the same name, type, and value as the provided + * BSONElement. The value is copied. + */ + Element makeElement(const BSONElement& elt); - /** Create a new undefined Element with the given field name. */ - Element makeElementUndefined(StringData fieldName); + /** Construct a new Element with the same type and value as the provided BSONElement, + * but with a new name. The value is copied. + */ + Element makeElementWithNewFieldName(StringData fieldName, const BSONElement& elt); - /** Create a new OID + Element with the given field name. */ - Element makeElementNewOID(StringData fieldName); + /** Create a new element of the appopriate type to hold the given value, with the given + * field name. + */ + Element makeElementSafeNum(StringData fieldName, SafeNum value); - /** Create a new OID Element with the given value and field name. */ - Element makeElementOID(StringData fieldName, mongo::OID value); + /** Construct a new element with the same name, type, and value as the provided mutable + * Element. The data is copied from the given Element. Unlike most methods in this + * class the provided Element may be from a different Document. + */ + Element makeElement(ConstElement elt); - /** Create a new bool Element with the given value and field name. */ - Element makeElementBool(StringData fieldName, bool value); + /** Construct a new Element with the same type and value as the provided mutable + * Element, but with a new field name. The data is copied from the given + * Element. Unlike most methods in this class the provided Element may be from a + * different Document. + */ + Element makeElementWithNewFieldName(StringData fieldName, ConstElement elt); - /** Create a new date Element with the given value and field name. */ - Element makeElementDate(StringData fieldName, Date_t value); + // + // Accessors + // - /** Create a new null Element with the given field name. */ - Element makeElementNull(StringData fieldName); + /** Returns the root element for this document. */ + inline Element root(); - /** Create a new regex Element with the given data and field name. */ - Element makeElementRegex( - StringData fieldName, StringData regex, StringData flags); + /** Returns the root element for this document. */ + inline ConstElement root() const; - /** Create a new DBRef Element with the given data and field name. */ - Element makeElementDBRef( - StringData fieldName, StringData ns, mongo::OID oid); + /** Returns an element that will compare equal to a non-ok element. */ + inline Element end(); - /** Create a new code Element with the given value and field name. */ - Element makeElementCode(StringData fieldName, StringData value); + /** Returns an element that will compare equal to a non-ok element. */ + inline ConstElement end() const; - /** Create a new symbol Element with the given value and field name. */ - Element makeElementSymbol(StringData fieldName, StringData value); + inline std::string toString() const; - /** Create a new scoped code Element with the given data and field name. */ - Element makeElementCodeWithScope( - StringData fieldName, StringData code, const BSONObj& scope); - - /** Create a new integer Element with the given value and field name. */ - Element makeElementInt(StringData fieldName, int32_t value); - - /** Create a new timestamp Element with the given value and field name. */ - Element makeElementTimestamp(StringData fieldName, Timestamp value); - - /** Create a new long integer Element with the given value and field name. */ - Element makeElementLong(StringData fieldName, int64_t value); - - /** Create a new min key Element with the given field name. */ - Element makeElementMinKey(StringData fieldName); - - /** Create a new max key Element with the given field name. */ - Element makeElementMaxKey(StringData fieldName); - - - // - // Element creation methods from variant types - // - - /** Construct a new Element with the same name, type, and value as the provided - * BSONElement. The value is copied. - */ - Element makeElement(const BSONElement& elt); - - /** Construct a new Element with the same type and value as the provided BSONElement, - * but with a new name. The value is copied. - */ - Element makeElementWithNewFieldName(StringData fieldName, const BSONElement& elt); - - /** Create a new element of the appopriate type to hold the given value, with the given - * field name. - */ - Element makeElementSafeNum(StringData fieldName, SafeNum value); - - /** Construct a new element with the same name, type, and value as the provided mutable - * Element. The data is copied from the given Element. Unlike most methods in this - * class the provided Element may be from a different Document. - */ - Element makeElement(ConstElement elt); - - /** Construct a new Element with the same type and value as the provided mutable - * Element, but with a new field name. The data is copied from the given - * Element. Unlike most methods in this class the provided Element may be from a - * different Document. - */ - Element makeElementWithNewFieldName(StringData fieldName, ConstElement elt); - - // - // Accessors - // - - /** Returns the root element for this document. */ - inline Element root(); - - /** Returns the root element for this document. */ - inline ConstElement root() const; - - /** Returns an element that will compare equal to a non-ok element. */ - inline Element end(); - - /** Returns an element that will compare equal to a non-ok element. */ - inline ConstElement end() const; - - inline std::string toString() const; - - // - // In-place API. - // - - /** Ensure that at least 'expectedEvents' damage events can be recorded for in-place - * mutations without reallocation. This call is ignored if damage events are disabled. - */ - void reserveDamageEvents(size_t expectedEvents); - - /** Request a vector of damage events describing in-place updates to this Document. If - * the modifications to this Document were not all able to be achieved in-place, then - * a non-OK Status is returned, and the provided damage vector will be made empty and - * *source set equal to NULL. Otherwise, the provided damage vector is populated, and - * the 'source' argument is set to point to a region from which bytes can be read. The - * 'source' offsets in the damage vector are to be interpreted as offsets within this - * region. If the 'size' parameter is non-null and 'source' is set to a non-NULL - * value, then size will be filled in with the size of the 'source' region to - * facilitate making an owned copy of the source data, in the event that that is - * needed. - * - * The lifetime of the source region should be considered to extend only from the - * return from this call to before the next API call on this Document or any of its - * member Elements. That is almost certainly overly conservative: some read only calls - * are undoubtedly fine. But it is very easy to invalidate 'source' by calling any - * mutating operation, so proceed with due caution. - * - * It is expected, though, that in normal modes of operation obtainin the damage - * vector is one of the last operations performed on a Document before its - * destruction, so this is not so great a restriction. - * - * The destination offsets in the damage events are implicitly offsets into the - * BSONObj used to construct this Document. - */ - bool getInPlaceUpdates(DamageVector* damages, - const char** source, - size_t* size = NULL); - - /** Drop the queue of in-place update damage events, and do not queue new operations - * that would otherwise have been in-place. Use this if you know that in-place updates - * will not continue to be possible and do not want to pay the overhead of - * speculatively queueing them. After calling this method, getInPlaceUpdates will - * return a non-OK Status. It is not possible to re-enable in-place updates once - * disabled. - */ - void disableInPlaceUpdates(); - - /** Returns the current in-place mode for the document. Note that for some documents, - * like those created without any backing BSONObj, this will always return kForbidden, - * since in-place updates make no sense for such an object. In other cases, an object - * which started in kInPlacePermitted mode may transition to kInPlaceForbidden if a - * topology mutating operation is applied. - */ - InPlaceMode getCurrentInPlaceMode() const; - - /** A convenience routine, this returns true if the current in-place mode is - * kInPlaceEnabled, and false otherwise. - */ - inline bool isInPlaceModeEnabled() const; - - private: - friend class Element; - - // For now, the implementation of Document is firewalled. - class Impl; - inline Impl& getImpl(); - inline const Impl& getImpl() const; - - Element makeRootElement(); - Element makeRootElement(const BSONObj& value); - Element makeElement(ConstElement element, const StringData* fieldName); - - const std::unique_ptr<Impl> _impl; - - // The root element of this document. - const Element _root; - }; + // + // In-place API. + // + + /** Ensure that at least 'expectedEvents' damage events can be recorded for in-place + * mutations without reallocation. This call is ignored if damage events are disabled. + */ + void reserveDamageEvents(size_t expectedEvents); + + /** Request a vector of damage events describing in-place updates to this Document. If + * the modifications to this Document were not all able to be achieved in-place, then + * a non-OK Status is returned, and the provided damage vector will be made empty and + * *source set equal to NULL. Otherwise, the provided damage vector is populated, and + * the 'source' argument is set to point to a region from which bytes can be read. The + * 'source' offsets in the damage vector are to be interpreted as offsets within this + * region. If the 'size' parameter is non-null and 'source' is set to a non-NULL + * value, then size will be filled in with the size of the 'source' region to + * facilitate making an owned copy of the source data, in the event that that is + * needed. + * + * The lifetime of the source region should be considered to extend only from the + * return from this call to before the next API call on this Document or any of its + * member Elements. That is almost certainly overly conservative: some read only calls + * are undoubtedly fine. But it is very easy to invalidate 'source' by calling any + * mutating operation, so proceed with due caution. + * + * It is expected, though, that in normal modes of operation obtainin the damage + * vector is one of the last operations performed on a Document before its + * destruction, so this is not so great a restriction. + * + * The destination offsets in the damage events are implicitly offsets into the + * BSONObj used to construct this Document. + */ + bool getInPlaceUpdates(DamageVector* damages, const char** source, size_t* size = NULL); + + /** Drop the queue of in-place update damage events, and do not queue new operations + * that would otherwise have been in-place. Use this if you know that in-place updates + * will not continue to be possible and do not want to pay the overhead of + * speculatively queueing them. After calling this method, getInPlaceUpdates will + * return a non-OK Status. It is not possible to re-enable in-place updates once + * disabled. + */ + void disableInPlaceUpdates(); + + /** Returns the current in-place mode for the document. Note that for some documents, + * like those created without any backing BSONObj, this will always return kForbidden, + * since in-place updates make no sense for such an object. In other cases, an object + * which started in kInPlacePermitted mode may transition to kInPlaceForbidden if a + * topology mutating operation is applied. + */ + InPlaceMode getCurrentInPlaceMode() const; + + /** A convenience routine, this returns true if the current in-place mode is + * kInPlaceEnabled, and false otherwise. + */ + inline bool isInPlaceModeEnabled() const; + +private: + friend class Element; + + // For now, the implementation of Document is firewalled. + class Impl; + inline Impl& getImpl(); + inline const Impl& getImpl() const; + + Element makeRootElement(); + Element makeRootElement(const BSONObj& value); + Element makeElement(ConstElement element, const StringData* fieldName); + + const std::unique_ptr<Impl> _impl; + + // The root element of this document. + const Element _root; +}; -} // namespace mutablebson -} // namespace mongo +} // namespace mutablebson +} // namespace mongo #include "mongo/bson/mutable/document-inl.h" diff --git a/src/mongo/bson/mutable/element-inl.h b/src/mongo/bson/mutable/element-inl.h index 7e9249a5b15..6dd2810e43c 100644 --- a/src/mongo/bson/mutable/element-inl.h +++ b/src/mongo/bson/mutable/element-inl.h @@ -30,127 +30,125 @@ namespace mongo { namespace mutablebson { - inline Element Element::operator[](size_t n) const { - return findNthChild(n); - } - - inline Element Element::operator[](StringData name) const { - return findFirstChildNamed(name); - } - - inline double Element::getValueDouble() const { - dassert(hasValue() && isType(mongo::NumberDouble)); - return getValue()._numberDouble(); - } - - inline StringData Element::getValueString() const { - dassert(hasValue() && isType(mongo::String)); - return getValueStringOrSymbol(); - } - - inline BSONObj Element::getValueObject() const { - dassert(hasValue() && isType(mongo::Object)); - return getValue().Obj(); - } - - inline BSONArray Element::getValueArray() const { - dassert(hasValue() && isType(mongo::Array)); - return BSONArray(getValue().Obj()); - } - - inline bool Element::isValueUndefined() const { - return isType(mongo::Undefined); - } - - inline OID Element::getValueOID() const { - dassert(hasValue() && isType(mongo::jstOID)); - return getValue().__oid(); - } - - inline bool Element::getValueBool() const { - dassert(hasValue() && isType(mongo::Bool)); - return getValue().boolean(); - } - - inline Date_t Element::getValueDate() const { - dassert(hasValue() && isType(mongo::Date)); - return getValue().date(); - } - - inline bool Element::isValueNull() const { - return isType(mongo::jstNULL); - } - - inline StringData Element::getValueSymbol() const { - dassert(hasValue() && isType(mongo::Symbol)); - return getValueStringOrSymbol(); - } - - inline int32_t Element::getValueInt() const { - dassert(hasValue() && isType(mongo::NumberInt)); - return getValue()._numberInt(); - } - - inline Timestamp Element::getValueTimestamp() const { - dassert(hasValue() && isType(mongo::bsonTimestamp)); - return getValue().timestamp(); - } - - inline int64_t Element::getValueLong() const { - dassert(hasValue() && isType(mongo::NumberLong)); - return getValue()._numberLong(); - } - - inline bool Element::isValueMinKey() const { - return isType(mongo::MinKey); - } - - inline bool Element::isValueMaxKey() const { - return isType(mongo::MaxKey); - } - - inline bool Element::ok() const { - dassert(_doc != NULL); - return _repIdx <= kMaxRepIdx; - } - - inline Document& Element::getDocument() { - return *_doc; - } - - inline const Document& Element::getDocument() const { - return *_doc; - } - - inline bool Element::isType(BSONType type) const { - return (getType() == type); - } - - inline Element::RepIdx Element::getIdx() const { - return _repIdx; - } - - inline Element::Element(Document* doc, RepIdx repIdx) - : _doc(doc) - , _repIdx(repIdx) { - dassert(_doc != NULL); - } - - inline StringData Element::getValueStringOrSymbol() const { - const BSONElement value = getValue(); - const char* str = value.valuestr(); - const size_t size = value.valuestrsize() - 1; - return StringData(str, size); - } - - inline bool operator==(const Element& l, const Element& r) { - return (l._doc == r._doc) && (l._repIdx == r._repIdx); - } - - inline bool operator!=(const Element& l, const Element& r) { - return !(l == r); - } - - -} // namespace mutablebson -} // namespace mongo +inline Element Element::operator[](size_t n) const { + return findNthChild(n); +} + +inline Element Element::operator[](StringData name) const { + return findFirstChildNamed(name); +} + +inline double Element::getValueDouble() const { + dassert(hasValue() && isType(mongo::NumberDouble)); + return getValue()._numberDouble(); +} + +inline StringData Element::getValueString() const { + dassert(hasValue() && isType(mongo::String)); + return getValueStringOrSymbol(); +} + +inline BSONObj Element::getValueObject() const { + dassert(hasValue() && isType(mongo::Object)); + return getValue().Obj(); +} + +inline BSONArray Element::getValueArray() const { + dassert(hasValue() && isType(mongo::Array)); + return BSONArray(getValue().Obj()); +} + +inline bool Element::isValueUndefined() const { + return isType(mongo::Undefined); +} + +inline OID Element::getValueOID() const { + dassert(hasValue() && isType(mongo::jstOID)); + return getValue().__oid(); +} + +inline bool Element::getValueBool() const { + dassert(hasValue() && isType(mongo::Bool)); + return getValue().boolean(); +} + +inline Date_t Element::getValueDate() const { + dassert(hasValue() && isType(mongo::Date)); + return getValue().date(); +} + +inline bool Element::isValueNull() const { + return isType(mongo::jstNULL); +} + +inline StringData Element::getValueSymbol() const { + dassert(hasValue() && isType(mongo::Symbol)); + return getValueStringOrSymbol(); +} + +inline int32_t Element::getValueInt() const { + dassert(hasValue() && isType(mongo::NumberInt)); + return getValue()._numberInt(); +} + +inline Timestamp Element::getValueTimestamp() const { + dassert(hasValue() && isType(mongo::bsonTimestamp)); + return getValue().timestamp(); +} + +inline int64_t Element::getValueLong() const { + dassert(hasValue() && isType(mongo::NumberLong)); + return getValue()._numberLong(); +} + +inline bool Element::isValueMinKey() const { + return isType(mongo::MinKey); +} + +inline bool Element::isValueMaxKey() const { + return isType(mongo::MaxKey); +} + +inline bool Element::ok() const { + dassert(_doc != NULL); + return _repIdx <= kMaxRepIdx; +} + +inline Document& Element::getDocument() { + return *_doc; +} + +inline const Document& Element::getDocument() const { + return *_doc; +} + +inline bool Element::isType(BSONType type) const { + return (getType() == type); +} + +inline Element::RepIdx Element::getIdx() const { + return _repIdx; +} + +inline Element::Element(Document* doc, RepIdx repIdx) : _doc(doc), _repIdx(repIdx) { + dassert(_doc != NULL); +} + +inline StringData Element::getValueStringOrSymbol() const { + const BSONElement value = getValue(); + const char* str = value.valuestr(); + const size_t size = value.valuestrsize() - 1; + return StringData(str, size); +} + +inline bool operator==(const Element& l, const Element& r) { + return (l._doc == r._doc) && (l._repIdx == r._repIdx); +} + +inline bool operator!=(const Element& l, const Element& r) { + return !(l == r); +} + + +} // namespace mutablebson +} // namespace mongo diff --git a/src/mongo/bson/mutable/element.cpp b/src/mongo/bson/mutable/element.cpp index 407a27c3df7..4ff1959559f 100644 --- a/src/mongo/bson/mutable/element.cpp +++ b/src/mongo/bson/mutable/element.cpp @@ -35,151 +35,149 @@ namespace mongo { namespace mutablebson { - // Many of the methods of Element are actually implemented in document.cpp, since they need - // access to the firewalled implementation of Document. - - Status Element::pushFront(Element e) { - return addChild(e, true); - } - - Status Element::pushBack(Element e) { - return addChild(e, false); - } - - Status Element::popFront() { - Element left = leftChild(); - if (!left.ok()) - return Status(ErrorCodes::EmptyArrayOperation, "popFront on empty"); - return left.remove(); - } - - Status Element::popBack() { - Element right = rightChild(); - if (!right.ok()) - return Status(ErrorCodes::EmptyArrayOperation, "popBack on empty"); - return right.remove(); - } - - Status Element::appendDouble(StringData fieldName, double value) { - return pushBack(getDocument().makeElementDouble(fieldName, value)); - } - - Status Element::appendString(StringData fieldName, StringData value) { - return pushBack(getDocument().makeElementString(fieldName, value)); - } - - Status Element::appendObject(StringData fieldName, const BSONObj& value) { - return pushBack(getDocument().makeElementObject(fieldName, value)); - } - - Status Element::appendArray(StringData fieldName, const BSONObj& value) { - return pushBack(getDocument().makeElementArray(fieldName, value)); - } - - Status Element::appendBinary(StringData fieldName, - uint32_t len, mongo::BinDataType binType, - const void* data) { - return pushBack(getDocument().makeElementBinary(fieldName, len, binType, data)); - } - - Status Element::appendUndefined(StringData fieldName) { - return pushBack(getDocument().makeElementUndefined(fieldName)); - } - - Status Element::appendOID(StringData fieldName, const OID value) { - return pushBack(getDocument().makeElementOID(fieldName, value)); - } - - Status Element::appendBool(StringData fieldName, bool value) { - return pushBack(getDocument().makeElementBool(fieldName, value)); - } - - Status Element::appendDate(StringData fieldName, Date_t value) { - return pushBack(getDocument().makeElementDate(fieldName, value)); - } - - Status Element::appendNull(StringData fieldName) { - return pushBack(getDocument().makeElementNull(fieldName)); - } - - Status Element::appendRegex(StringData fieldName, - StringData re, StringData flags) { - return pushBack(getDocument().makeElementRegex(fieldName, re, flags)); - } - - Status Element::appendDBRef(StringData fieldName, - StringData ns, const OID oid) { - return pushBack(getDocument().makeElementDBRef(fieldName, ns, oid)); - } - - Status Element::appendCode(StringData fieldName, StringData value) { - return pushBack(getDocument().makeElementCode(fieldName, value)); - } - - Status Element::appendSymbol(StringData fieldName, StringData value) { - return pushBack(getDocument().makeElementSymbol(fieldName, value)); - } - - Status Element::appendCodeWithScope(StringData fieldName, - StringData code, const BSONObj& scope) { - return pushBack(getDocument().makeElementCodeWithScope(fieldName, code, scope)); - } - - Status Element::appendInt(StringData fieldName, int32_t value) { - return pushBack(getDocument().makeElementInt(fieldName, value)); - } - - Status Element::appendTimestamp(StringData fieldName, Timestamp value) { - return pushBack(getDocument().makeElementTimestamp(fieldName, value)); - } - - Status Element::appendLong(StringData fieldName, int64_t value) { - return pushBack(getDocument().makeElementLong(fieldName, value)); - } - - Status Element::appendMinKey(StringData fieldName) { - return pushBack(getDocument().makeElementMinKey(fieldName)); - } - - Status Element::appendMaxKey(StringData fieldName) { - return pushBack(getDocument().makeElementMaxKey(fieldName)); - } - - Status Element::appendElement(const BSONElement& value) { - return pushBack(getDocument().makeElement(value)); - } - - Status Element::appendSafeNum(StringData fieldName, SafeNum value) { - return pushBack(getDocument().makeElementSafeNum(fieldName, value)); - } - - std::string Element::toString() const { - if (!ok()) - return "INVALID-MUTABLE-ELEMENT"; - - if (hasValue()) - return getValue().toString(); - - const BSONType type = getType(); - - // The only types that sometimes don't have a value are Object and Array nodes. - dassert((type == mongo::Object) || (type == mongo::Array)); - - if (type == mongo::Object) { - BSONObjBuilder builder; - writeTo(&builder); - BSONObj obj = builder.obj(); - return obj.firstElement().toString(); - } else { - // It must be an array. - BSONObjBuilder builder; - BSONArrayBuilder arrayBuilder(builder.subarrayStart(getFieldName())); - writeArrayTo(&arrayBuilder); - arrayBuilder.done(); - BSONObj obj = builder.obj(); - return obj.firstElement().toString(); - } - } - -} // namespace mutablebson -} // namespace mongo +// Many of the methods of Element are actually implemented in document.cpp, since they need +// access to the firewalled implementation of Document. + +Status Element::pushFront(Element e) { + return addChild(e, true); +} + +Status Element::pushBack(Element e) { + return addChild(e, false); +} + +Status Element::popFront() { + Element left = leftChild(); + if (!left.ok()) + return Status(ErrorCodes::EmptyArrayOperation, "popFront on empty"); + return left.remove(); +} + +Status Element::popBack() { + Element right = rightChild(); + if (!right.ok()) + return Status(ErrorCodes::EmptyArrayOperation, "popBack on empty"); + return right.remove(); +} + +Status Element::appendDouble(StringData fieldName, double value) { + return pushBack(getDocument().makeElementDouble(fieldName, value)); +} + +Status Element::appendString(StringData fieldName, StringData value) { + return pushBack(getDocument().makeElementString(fieldName, value)); +} + +Status Element::appendObject(StringData fieldName, const BSONObj& value) { + return pushBack(getDocument().makeElementObject(fieldName, value)); +} + +Status Element::appendArray(StringData fieldName, const BSONObj& value) { + return pushBack(getDocument().makeElementArray(fieldName, value)); +} + +Status Element::appendBinary(StringData fieldName, + uint32_t len, + mongo::BinDataType binType, + const void* data) { + return pushBack(getDocument().makeElementBinary(fieldName, len, binType, data)); +} + +Status Element::appendUndefined(StringData fieldName) { + return pushBack(getDocument().makeElementUndefined(fieldName)); +} + +Status Element::appendOID(StringData fieldName, const OID value) { + return pushBack(getDocument().makeElementOID(fieldName, value)); +} + +Status Element::appendBool(StringData fieldName, bool value) { + return pushBack(getDocument().makeElementBool(fieldName, value)); +} + +Status Element::appendDate(StringData fieldName, Date_t value) { + return pushBack(getDocument().makeElementDate(fieldName, value)); +} + +Status Element::appendNull(StringData fieldName) { + return pushBack(getDocument().makeElementNull(fieldName)); +} + +Status Element::appendRegex(StringData fieldName, StringData re, StringData flags) { + return pushBack(getDocument().makeElementRegex(fieldName, re, flags)); +} + +Status Element::appendDBRef(StringData fieldName, StringData ns, const OID oid) { + return pushBack(getDocument().makeElementDBRef(fieldName, ns, oid)); +} + +Status Element::appendCode(StringData fieldName, StringData value) { + return pushBack(getDocument().makeElementCode(fieldName, value)); +} + +Status Element::appendSymbol(StringData fieldName, StringData value) { + return pushBack(getDocument().makeElementSymbol(fieldName, value)); +} + +Status Element::appendCodeWithScope(StringData fieldName, StringData code, const BSONObj& scope) { + return pushBack(getDocument().makeElementCodeWithScope(fieldName, code, scope)); +} + +Status Element::appendInt(StringData fieldName, int32_t value) { + return pushBack(getDocument().makeElementInt(fieldName, value)); +} + +Status Element::appendTimestamp(StringData fieldName, Timestamp value) { + return pushBack(getDocument().makeElementTimestamp(fieldName, value)); +} + +Status Element::appendLong(StringData fieldName, int64_t value) { + return pushBack(getDocument().makeElementLong(fieldName, value)); +} + +Status Element::appendMinKey(StringData fieldName) { + return pushBack(getDocument().makeElementMinKey(fieldName)); +} + +Status Element::appendMaxKey(StringData fieldName) { + return pushBack(getDocument().makeElementMaxKey(fieldName)); +} + +Status Element::appendElement(const BSONElement& value) { + return pushBack(getDocument().makeElement(value)); +} + +Status Element::appendSafeNum(StringData fieldName, SafeNum value) { + return pushBack(getDocument().makeElementSafeNum(fieldName, value)); +} + +std::string Element::toString() const { + if (!ok()) + return "INVALID-MUTABLE-ELEMENT"; + + if (hasValue()) + return getValue().toString(); + + const BSONType type = getType(); + + // The only types that sometimes don't have a value are Object and Array nodes. + dassert((type == mongo::Object) || (type == mongo::Array)); + + if (type == mongo::Object) { + BSONObjBuilder builder; + writeTo(&builder); + BSONObj obj = builder.obj(); + return obj.firstElement().toString(); + } else { + // It must be an array. + BSONObjBuilder builder; + BSONArrayBuilder arrayBuilder(builder.subarrayStart(getFieldName())); + writeArrayTo(&arrayBuilder); + arrayBuilder.done(); + BSONObj obj = builder.obj(); + return obj.firstElement().toString(); + } +} + +} // namespace mutablebson +} // namespace mongo diff --git a/src/mongo/bson/mutable/element.h b/src/mongo/bson/mutable/element.h index f58771f2513..50df2e1b620 100644 --- a/src/mongo/bson/mutable/element.h +++ b/src/mongo/bson/mutable/element.h @@ -36,586 +36,585 @@ namespace mongo { namespace mutablebson { - /** For an overview of mutable BSON, please see the file document.h in this directory. */ - - class ConstElement; - class Document; - - /** Element represents a BSON value or object in a mutable BSON Document. The lifetime of - * an Element is a subset of the Document to which it belongs. Much like a BSONElement, an - * Element has a type, a field name, and (usually) a value. An Element may be used to read - * or modify the value (including changing its type), to navigate to related Elements in - * the Document tree, or for a number of topological changes to the Document - * structure. Element also offers the ability to compare its value to that of other - * Elements, and to serialize its value to a BSONObjBuilder or BSONArrayBuilder. - * - * Elements have reference or iterator like semantics, and are very lightweight. You - * should not worry about the cost of passing an Element by value, copying an Element, or - * similar operations. Such operations do not mean that the logical element in the - * underlying Document is duplicated. Only the reference is duplicated. - * - * The API for Element is broken into several sections: - * - * - Topology mutation: These methods are to either add other Elements to the Document - * tree as siblings or children (when applicable) of the current Element, to remove the - * Element from the tree, or to remove children of the Element (when applicable). - * - * - Navigation: These methods are used to navigate the Document tree by returning other - * Elements in specified relationships to the current Element. In this regard, Elements - * act much like STL iterators that walk over the Document tree. One important - * difference is that Elements are never invalidated, even when 'remove' is called. If - * you have two Elements that alias the same element in the Document tree, modifications - * through one Element will be visible via the other. - * - * - Value access: These methods provide access to the value in the Document tree that the - * current Element represents. All leaf (a.k.a. 'primitive', or non-Object and - * non-Array) like Elements will always be able to provide a value. However, there are - * cases where non-leaf Elements (representing Objects or Arrays) cannot provide a - * value. Therefore, you must always call 'hasValue' to determine if the value is - * available before calling 'getValue'. Similarly, you must determine the type of the - * Element by calling getType() and only call the matching typed getValue. - * - * - Comparison: It is possible to compare one Element with another to determine ordering - * or equality as defined by woCompare. Similarly, it is possible to directly compare an - * Element with a BSONElement. It is legal to compare two Elements which belong to - * different Documents. - * - * - Serialization: Elements may be serialized to BSONObjBuilder or to BSONArrayBuilder - * objects when appropriate. One detail to consider is that writeTo for the root Element - * behaves differently than the others: it does not start a new subobj scope in the - * builder, so all of its children will be added at the current level to the - * builder. The provided builder does not have its 'done' method called automatically. - * - * - Value mutation: You may freely modify the value of an Element, including - * modifications that change the type of the Element and setting the value of the - * Element to the value of another BSONObj. You may also set the value from a SafeNum or - * from a BSONElement. - * - * - Accessors: These provide access to various properties of the Element, like the - * Document to which the Element belongs, the BSON type and field name of the Element, - * etc. One critical accessor is 'ok'. When using the topology API to navigate a - * document, it is possible to request an Element which does not exist, like the parent - * of the root element, or the left child of an integer, or the right sibling of the - * last element in an array. In these cases, the topology API will return an Element for - * which the 'ok' accessor will return 'false', which is roughly analagous to an 'end' - * valued STL iterator. It is illegal to call any method (other than 'ok') on a non-OK - * Element. +/** For an overview of mutable BSON, please see the file document.h in this directory. */ + +class ConstElement; +class Document; + +/** Element represents a BSON value or object in a mutable BSON Document. The lifetime of + * an Element is a subset of the Document to which it belongs. Much like a BSONElement, an + * Element has a type, a field name, and (usually) a value. An Element may be used to read + * or modify the value (including changing its type), to navigate to related Elements in + * the Document tree, or for a number of topological changes to the Document + * structure. Element also offers the ability to compare its value to that of other + * Elements, and to serialize its value to a BSONObjBuilder or BSONArrayBuilder. + * + * Elements have reference or iterator like semantics, and are very lightweight. You + * should not worry about the cost of passing an Element by value, copying an Element, or + * similar operations. Such operations do not mean that the logical element in the + * underlying Document is duplicated. Only the reference is duplicated. + * + * The API for Element is broken into several sections: + * + * - Topology mutation: These methods are to either add other Elements to the Document + * tree as siblings or children (when applicable) of the current Element, to remove the + * Element from the tree, or to remove children of the Element (when applicable). + * + * - Navigation: These methods are used to navigate the Document tree by returning other + * Elements in specified relationships to the current Element. In this regard, Elements + * act much like STL iterators that walk over the Document tree. One important + * difference is that Elements are never invalidated, even when 'remove' is called. If + * you have two Elements that alias the same element in the Document tree, modifications + * through one Element will be visible via the other. + * + * - Value access: These methods provide access to the value in the Document tree that the + * current Element represents. All leaf (a.k.a. 'primitive', or non-Object and + * non-Array) like Elements will always be able to provide a value. However, there are + * cases where non-leaf Elements (representing Objects or Arrays) cannot provide a + * value. Therefore, you must always call 'hasValue' to determine if the value is + * available before calling 'getValue'. Similarly, you must determine the type of the + * Element by calling getType() and only call the matching typed getValue. + * + * - Comparison: It is possible to compare one Element with another to determine ordering + * or equality as defined by woCompare. Similarly, it is possible to directly compare an + * Element with a BSONElement. It is legal to compare two Elements which belong to + * different Documents. + * + * - Serialization: Elements may be serialized to BSONObjBuilder or to BSONArrayBuilder + * objects when appropriate. One detail to consider is that writeTo for the root Element + * behaves differently than the others: it does not start a new subobj scope in the + * builder, so all of its children will be added at the current level to the + * builder. The provided builder does not have its 'done' method called automatically. + * + * - Value mutation: You may freely modify the value of an Element, including + * modifications that change the type of the Element and setting the value of the + * Element to the value of another BSONObj. You may also set the value from a SafeNum or + * from a BSONElement. + * + * - Accessors: These provide access to various properties of the Element, like the + * Document to which the Element belongs, the BSON type and field name of the Element, + * etc. One critical accessor is 'ok'. When using the topology API to navigate a + * document, it is possible to request an Element which does not exist, like the parent + * of the root element, or the left child of an integer, or the right sibling of the + * last element in an array. In these cases, the topology API will return an Element for + * which the 'ok' accessor will return 'false', which is roughly analagous to an 'end' + * valued STL iterator. It is illegal to call any method (other than 'ok') on a non-OK + * Element. + * + * - Streaming API: As a convenience for when you are building Documents from scratch, an + * API is provided that combines the effects of calling makeElement on the Document with + * calling pushBack on the current Element. The effect is to create the element and make + * it the new rightmost child of this Element. Use of this API is discouraged and it may + * be removed. + */ + +class Element { +public: + typedef uint32_t RepIdx; + + // Some special RepIdx values. These are really implementation details, but they are + // here so that we can inline Element::OK, which gets called very frequently, and they + // need to be public so some free functions in document.cpp can use them. You must not + // use these values explicitly. + + // Used to signal an invalid Element. + static const RepIdx kInvalidRepIdx = RepIdx(-1); + + // A rep that points to an unexamined entity + static const RepIdx kOpaqueRepIdx = RepIdx(-2); + + // This is the highest valid rep that does not overlap flag values. + static const RepIdx kMaxRepIdx = RepIdx(-3); + + // + // Topology mutation API. Element arguments must belong to the same Document. + // + + /** Add the provided Element to the left of this Element. The added Element must be + * 'ok', and this Element must have a parent. + */ + Status addSiblingLeft(Element e); + + /** Add the provided Element to the right of this Element. The added Element must be + * 'ok', and this Element must have a parent. + */ + Status addSiblingRight(Element e); + + /** 'Remove' this Element by detaching it from its parent and siblings. The Element + * continues to exist and may be manipulated, but cannot be re-obtained by navigating + * from the root. + */ + Status remove(); + + /** If this Element is empty, add 'e' as the first child. Otherwise, add 'e' as the new + * left child. + */ + Status pushFront(Element e); + + /** If this Element is empty, add 'e' as the first child. Otherwise, add 'e' as the new + * right child. + */ + Status pushBack(Element e); + + /** Remove the leftmost child Element if it exists, otherwise return an error. */ + Status popFront(); + + /** Remove the rightmost child Element if it exists, otherwise return an error. */ + Status popBack(); + + /** Rename this Element to the provided name. */ + Status rename(StringData newName); + + + // + // Navigation API. + // + + /** Returns either this Element's left child, or a non-ok Element if no left child + * exists. + */ + Element leftChild() const; + + /** Returns either this Element's right child, or a non-ok Element if no right child + * exists. Note that obtaining the right child may require realizing all immediate + * child nodes of a document that is being consumed lazily. + */ + Element rightChild() const; + + /** Returns true if this element has children. Always returns false if this Element is + * not an Object or Array. + */ + bool hasChildren() const; + + /** Returns either this Element's sibling 'distance' elements to the left, or a non-ok + * Element if no such left sibling exists. + */ + Element leftSibling(size_t distance = 1) const; + + /** Returns either this Element's sibling 'distance' Elements to the right, or a non-ok + * Element if no such right sibling exists. + */ + Element rightSibling(size_t distance = 1) const; + + /** Returns this Element's parent, or a non-ok Element if this Element has no parent + * (is a root). + */ + Element parent() const; + + /** Returns the nth child, if any, of this Element. If no such element exists, a non-ok + * Element is returned. This is not a constant time operation. This method is also + * available as operator[] taking a size_t for convenience. + */ + Element findNthChild(size_t n) const; + inline Element operator[](size_t n) const; + + /** Returns the first child, if any, of this Element named 'name'. If no such Element + * exists, a non-ok Element is returned. This is not a constant time operation. This + * method is also available as operator[] taking a StringData for convenience. + */ + Element findFirstChildNamed(StringData name) const; + inline Element operator[](StringData name) const; + + /** Returns the first element found named 'name', starting the search at the current + * Element, and walking right. If no such Element exists, a non-ok Element is + * returned. This is not a constant time operation. This implementation is used in the + * specialized implementation of findElement<ElementType, FieldNameEquals>. + */ + Element findElementNamed(StringData name) const; + + // + // Counting API. + // + + /** Returns the number of valid siblings to the left of this Element. */ + size_t countSiblingsLeft() const; + + /** Returns the number of valid siblings to the right of this Element. */ + size_t countSiblingsRight() const; + + /** Return the number of children of this Element. */ + size_t countChildren() const; + + // + // Value access API. + // + // We only provide accessors for BSONElement and for simple types. For more complex + // types like regex you should obtain the BSONElement and use that API to extract the + // components. + // + // Note that the getValueX methods are *unchecked* in release builds: You are + // responsible for calling hasValue() to ensure that this element has a value + // representation, and for calling getType to ensure that the Element is of the proper + // type. + // + // As usual, methods here are in bsonspec type order, please keep them that way. + // + + /** Returns true if 'getValue' can return a valid BSONElement from which a value may be + * extracted. See the notes for 'getValue' to understand the conditions under which an + * Element can provide a BSONElement. + */ + bool hasValue() const; + + /** Returns true if this element is a numeric type (e.g. NumberLong). Currently, the + * only numeric BSON types are NumberLong, NumberInt, and NumberDouble. + */ + bool isNumeric() const; + + /** Returns true if this element is one of the integral numeric types (e.g. NumberLong + * or NumberInt). + */ + bool isIntegral() const; + + /** Get the value of this element if available. Note that not all elements have a + * representation as a BSONElement. For elements that do have a representation, this + * will return it. For elements that do not this method returns an eoo + * BSONElement. All 'value-ish' Elements will have a BSONElement + * representation. 'Tree-ish' Elements may or may not have a BSONElement + * representation. Mutations may cause elements to change whether or not they have a + * value and may invalidate previously returned values. * - * - Streaming API: As a convenience for when you are building Documents from scratch, an - * API is provided that combines the effects of calling makeElement on the Document with - * calling pushBack on the current Element. The effect is to create the element and make - * it the new rightmost child of this Element. Use of this API is discouraged and it may - * be removed. - */ - - class Element { - public: - typedef uint32_t RepIdx; - - // Some special RepIdx values. These are really implementation details, but they are - // here so that we can inline Element::OK, which gets called very frequently, and they - // need to be public so some free functions in document.cpp can use them. You must not - // use these values explicitly. - - // Used to signal an invalid Element. - static const RepIdx kInvalidRepIdx = RepIdx(-1); - - // A rep that points to an unexamined entity - static const RepIdx kOpaqueRepIdx = RepIdx(-2); - - // This is the highest valid rep that does not overlap flag values. - static const RepIdx kMaxRepIdx = RepIdx(-3); - - // - // Topology mutation API. Element arguments must belong to the same Document. - // - - /** Add the provided Element to the left of this Element. The added Element must be - * 'ok', and this Element must have a parent. - */ - Status addSiblingLeft(Element e); - - /** Add the provided Element to the right of this Element. The added Element must be - * 'ok', and this Element must have a parent. - */ - Status addSiblingRight(Element e); - - /** 'Remove' this Element by detaching it from its parent and siblings. The Element - * continues to exist and may be manipulated, but cannot be re-obtained by navigating - * from the root. - */ - Status remove(); - - /** If this Element is empty, add 'e' as the first child. Otherwise, add 'e' as the new - * left child. - */ - Status pushFront(Element e); - - /** If this Element is empty, add 'e' as the first child. Otherwise, add 'e' as the new - * right child. - */ - Status pushBack(Element e); - - /** Remove the leftmost child Element if it exists, otherwise return an error. */ - Status popFront(); - - /** Remove the rightmost child Element if it exists, otherwise return an error. */ - Status popBack(); - - /** Rename this Element to the provided name. */ - Status rename(StringData newName); - - - // - // Navigation API. - // - - /** Returns either this Element's left child, or a non-ok Element if no left child - * exists. - */ - Element leftChild() const; - - /** Returns either this Element's right child, or a non-ok Element if no right child - * exists. Note that obtaining the right child may require realizing all immediate - * child nodes of a document that is being consumed lazily. - */ - Element rightChild() const; - - /** Returns true if this element has children. Always returns false if this Element is - * not an Object or Array. - */ - bool hasChildren() const; - - /** Returns either this Element's sibling 'distance' elements to the left, or a non-ok - * Element if no such left sibling exists. - */ - Element leftSibling(size_t distance = 1) const; - - /** Returns either this Element's sibling 'distance' Elements to the right, or a non-ok - * Element if no such right sibling exists. - */ - Element rightSibling(size_t distance = 1) const; - - /** Returns this Element's parent, or a non-ok Element if this Element has no parent - * (is a root). - */ - Element parent() const; - - /** Returns the nth child, if any, of this Element. If no such element exists, a non-ok - * Element is returned. This is not a constant time operation. This method is also - * available as operator[] taking a size_t for convenience. - */ - Element findNthChild(size_t n) const; - inline Element operator[](size_t n) const; - - /** Returns the first child, if any, of this Element named 'name'. If no such Element - * exists, a non-ok Element is returned. This is not a constant time operation. This - * method is also available as operator[] taking a StringData for convenience. - */ - Element findFirstChildNamed(StringData name) const; - inline Element operator[](StringData name) const; - - /** Returns the first element found named 'name', starting the search at the current - * Element, and walking right. If no such Element exists, a non-ok Element is - * returned. This is not a constant time operation. This implementation is used in the - * specialized implementation of findElement<ElementType, FieldNameEquals>. - */ - Element findElementNamed(StringData name) const; - - // - // Counting API. - // - - /** Returns the number of valid siblings to the left of this Element. */ - size_t countSiblingsLeft() const; - - /** Returns the number of valid siblings to the right of this Element. */ - size_t countSiblingsRight() const; - - /** Return the number of children of this Element. */ - size_t countChildren() const; - - // - // Value access API. - // - // We only provide accessors for BSONElement and for simple types. For more complex - // types like regex you should obtain the BSONElement and use that API to extract the - // components. - // - // Note that the getValueX methods are *unchecked* in release builds: You are - // responsible for calling hasValue() to ensure that this element has a value - // representation, and for calling getType to ensure that the Element is of the proper - // type. - // - // As usual, methods here are in bsonspec type order, please keep them that way. - // - - /** Returns true if 'getValue' can return a valid BSONElement from which a value may be - * extracted. See the notes for 'getValue' to understand the conditions under which an - * Element can provide a BSONElement. - */ - bool hasValue() const; - - /** Returns true if this element is a numeric type (e.g. NumberLong). Currently, the - * only numeric BSON types are NumberLong, NumberInt, and NumberDouble. - */ - bool isNumeric() const; - - /** Returns true if this element is one of the integral numeric types (e.g. NumberLong - * or NumberInt). - */ - bool isIntegral() const; - - /** Get the value of this element if available. Note that not all elements have a - * representation as a BSONElement. For elements that do have a representation, this - * will return it. For elements that do not this method returns an eoo - * BSONElement. All 'value-ish' Elements will have a BSONElement - * representation. 'Tree-ish' Elements may or may not have a BSONElement - * representation. Mutations may cause elements to change whether or not they have a - * value and may invalidate previously returned values. - * - * Please note that a const BSONElement allows retrieval of a non-const - * BSONObj. However, the contents of the BSONElement returned here must be treated as - * const. - */ - const BSONElement getValue() const; - - /** Get the value from a double valued Element. */ - inline double getValueDouble() const; - - /** Get the value from a std::string valued Element. */ - inline StringData getValueString() const; - - /** Get the value from an object valued Element. Note that this may not always be - * possible! - */ - inline BSONObj getValueObject() const; - - /** Get the value from an object valued Element. Note that this may not always be - * possible! - */ - inline BSONArray getValueArray() const; - - /** Returns true if this Element is the undefined type. */ - inline bool isValueUndefined() const; - - /** Get the value from an OID valued Element. */ - inline OID getValueOID() const; - - /** Get the value from a bool valued Element. */ - inline bool getValueBool() const; - - /** Get the value from a date valued Element. */ - inline Date_t getValueDate() const; - - /** Returns true if this Element is the null type. */ - inline bool isValueNull() const; - - /** Get the value from a symbol valued Element. */ - inline StringData getValueSymbol() const; - - /** Get the value from an int valued Element. */ - inline int32_t getValueInt() const; - - /** Get the value from a timestamp valued Element. */ - inline Timestamp getValueTimestamp() const; - - /** Get the value from a long valued Element. */ - inline int64_t getValueLong() const; - - /** Returns true if this Element is the min key type. */ - inline bool isValueMinKey() const; - - /** Returns true if this Element is the max key type. */ - inline bool isValueMaxKey() const; - - /** Returns the numeric value as a SafeNum */ - SafeNum getValueSafeNum() const; - - - // - // Comparision API. - // - - /** Compare this Element with Element 'other'. The two Elements may belong to different - * Documents. You should not call this on the root Element of the Document because the - * root Element does not have a field name. Use compareWithBSONObj to handle that - * case. - * - * Returns -1 if this < other according to BSONElement::woCompare - * Returns 0 if this == other either tautologically, or according to woCompare. - * Returns 1 if this > other according to BSONElement::woCompare - */ - int compareWithElement(const ConstElement& other, bool considerFieldName = true) const; + * Please note that a const BSONElement allows retrieval of a non-const + * BSONObj. However, the contents of the BSONElement returned here must be treated as + * const. + */ + const BSONElement getValue() const; - /** Compare this Element with BSONElement 'other'. You should not call this on the root - * Element of the Document because the root Element does not have a field name. Use - * compareWithBSONObj to handle that case. - * - * Returns -1 if this < other according to BSONElement::woCompare - * Returns 0 if this == other either tautologically, or according to woCompare. - * Returns 1 if this > other according to BSONElement::woCompare - */ - int compareWithBSONElement(const BSONElement& other, bool considerFieldName = true) const; + /** Get the value from a double valued Element. */ + inline double getValueDouble() const; - /** Compare this Element, which must be an Object or an Array, with 'other'. - * - * Returns -1 if this object < other according to BSONElement::woCompare - * Returns 0 if this object == other either tautologically, or according to woCompare. - * Returns 1 if this object > other according to BSONElement::woCompare - */ - int compareWithBSONObj(const BSONObj& other, bool considerFieldName = true) const; + /** Get the value from a std::string valued Element. */ + inline StringData getValueString() const; + /** Get the value from an object valued Element. Note that this may not always be + * possible! + */ + inline BSONObj getValueObject() const; - // - // Serialization API. - // + /** Get the value from an object valued Element. Note that this may not always be + * possible! + */ + inline BSONArray getValueArray() const; - /** Write this Element to the provided object builder. */ - void writeTo(BSONObjBuilder* builder) const; + /** Returns true if this Element is the undefined type. */ + inline bool isValueUndefined() const; - /** Write this Element to the provided array builder. This Element must be of type - * mongo::Array. - */ - void writeArrayTo(BSONArrayBuilder* builder) const; + /** Get the value from an OID valued Element. */ + inline OID getValueOID() const; + /** Get the value from a bool valued Element. */ + inline bool getValueBool() const; - // - // Value mutation API. Please note that the types are ordered according to bsonspec.org - // ordering. Please keep them that way. - // + /** Get the value from a date valued Element. */ + inline Date_t getValueDate() const; - /** Set the value of this Element to the given double. */ - Status setValueDouble(double value); + /** Returns true if this Element is the null type. */ + inline bool isValueNull() const; - /** Set the value of this Element to the given string. */ - Status setValueString(StringData value); + /** Get the value from a symbol valued Element. */ + inline StringData getValueSymbol() const; - /** Set the value of this Element to the given object. The data in 'value' is - * copied. - */ - Status setValueObject(const BSONObj& value); + /** Get the value from an int valued Element. */ + inline int32_t getValueInt() const; - /** Set the value of this Element to the given object. The data in 'value' is - * copied. - */ - Status setValueArray(const BSONObj& value); + /** Get the value from a timestamp valued Element. */ + inline Timestamp getValueTimestamp() const; - /** Set the value of this Element to the given binary data. */ - Status setValueBinary(uint32_t len, mongo::BinDataType binType, const void* data); + /** Get the value from a long valued Element. */ + inline int64_t getValueLong() const; - /** Set the value of this Element to Undefined. */ - Status setValueUndefined(); + /** Returns true if this Element is the min key type. */ + inline bool isValueMinKey() const; - /** Set the value of this Element to the given OID. */ - Status setValueOID(OID value); + /** Returns true if this Element is the max key type. */ + inline bool isValueMaxKey() const; - /** Set the value of this Element to the given boolean. */ - Status setValueBool(bool value); + /** Returns the numeric value as a SafeNum */ + SafeNum getValueSafeNum() const; - /** Set the value of this Element to the given date. */ - Status setValueDate(Date_t value); - /** Set the value of this Element to Null. */ - Status setValueNull(); + // + // Comparision API. + // - /** Set the value of this Element to the given regex parameters. */ - Status setValueRegex(StringData re, StringData flags); + /** Compare this Element with Element 'other'. The two Elements may belong to different + * Documents. You should not call this on the root Element of the Document because the + * root Element does not have a field name. Use compareWithBSONObj to handle that + * case. + * + * Returns -1 if this < other according to BSONElement::woCompare + * Returns 0 if this == other either tautologically, or according to woCompare. + * Returns 1 if this > other according to BSONElement::woCompare + */ + int compareWithElement(const ConstElement& other, bool considerFieldName = true) const; - /** Set the value of this Element to the given db ref parameters. */ - Status setValueDBRef(StringData ns, OID oid); + /** Compare this Element with BSONElement 'other'. You should not call this on the root + * Element of the Document because the root Element does not have a field name. Use + * compareWithBSONObj to handle that case. + * + * Returns -1 if this < other according to BSONElement::woCompare + * Returns 0 if this == other either tautologically, or according to woCompare. + * Returns 1 if this > other according to BSONElement::woCompare + */ + int compareWithBSONElement(const BSONElement& other, bool considerFieldName = true) const; - /** Set the value of this Element to the given code data. */ - Status setValueCode(StringData value); + /** Compare this Element, which must be an Object or an Array, with 'other'. + * + * Returns -1 if this object < other according to BSONElement::woCompare + * Returns 0 if this object == other either tautologically, or according to woCompare. + * Returns 1 if this object > other according to BSONElement::woCompare + */ + int compareWithBSONObj(const BSONObj& other, bool considerFieldName = true) const; - /** Set the value of this Element to the given symbol. */ - Status setValueSymbol(StringData value); - /** Set the value of this Element to the given code and scope data. */ - Status setValueCodeWithScope(StringData code, const BSONObj& scope); + // + // Serialization API. + // - /** Set the value of this Element to the given integer. */ - Status setValueInt(int32_t value); + /** Write this Element to the provided object builder. */ + void writeTo(BSONObjBuilder* builder) const; - /** Set the value of this Element to the given timestamp. */ - Status setValueTimestamp(Timestamp value); + /** Write this Element to the provided array builder. This Element must be of type + * mongo::Array. + */ + void writeArrayTo(BSONArrayBuilder* builder) const; - /** Set the value of this Element to the given long integer */ - Status setValueLong(int64_t value); - /** Set the value of this Element to MinKey. */ - Status setValueMinKey(); + // + // Value mutation API. Please note that the types are ordered according to bsonspec.org + // ordering. Please keep them that way. + // - /** Set the value of this Element to MaxKey. */ - Status setValueMaxKey(); + /** Set the value of this Element to the given double. */ + Status setValueDouble(double value); + /** Set the value of this Element to the given string. */ + Status setValueString(StringData value); - // - // Value mutation API from variant types. - // + /** Set the value of this Element to the given object. The data in 'value' is + * copied. + */ + Status setValueObject(const BSONObj& value); - /** Set the value of this element to equal the value of the provided BSONElement - * 'value'. The name of this Element is not modified. - * - * The contents of value are copied. - */ - Status setValueBSONElement(const BSONElement& value); + /** Set the value of this Element to the given object. The data in 'value' is + * copied. + */ + Status setValueArray(const BSONObj& value); - /** Set the value of this Element to a numeric type appropriate to hold the given - * SafeNum value. - */ - Status setValueSafeNum(const SafeNum value); + /** Set the value of this Element to the given binary data. */ + Status setValueBinary(uint32_t len, mongo::BinDataType binType, const void* data); + /** Set the value of this Element to Undefined. */ + Status setValueUndefined(); - // - // Accessors - // + /** Set the value of this Element to the given OID. */ + Status setValueOID(OID value); - /** Returns true if this Element represents a valid part of the Document. */ - inline bool ok() const; + /** Set the value of this Element to the given boolean. */ + Status setValueBool(bool value); - /** Returns the Document to which this Element belongs. */ - inline Document& getDocument(); + /** Set the value of this Element to the given date. */ + Status setValueDate(Date_t value); - /** Returns the Document to which this Element belongs. */ - inline const Document& getDocument() const; + /** Set the value of this Element to Null. */ + Status setValueNull(); - /** Returns the BSONType of this Element. */ - BSONType getType() const; + /** Set the value of this Element to the given regex parameters. */ + Status setValueRegex(StringData re, StringData flags); - /** Returns true if this Element is of the specified type */ - inline bool isType(BSONType type) const; + /** Set the value of this Element to the given db ref parameters. */ + Status setValueDBRef(StringData ns, OID oid); - /** Returns the field name of this Element. Note that the value returned here is not - * stable across mutations, since the storage for fieldNames may be reallocated. If - * you need a stable version of the fieldName, you must call toString on the returned - * StringData. - */ - StringData getFieldName() const; + /** Set the value of this Element to the given code data. */ + Status setValueCode(StringData value); - /** Returns the opaque ID for this element. This is unlikely to be useful to a caller - * and is mostly for testing. - */ - inline RepIdx getIdx() const; + /** Set the value of this Element to the given symbol. */ + Status setValueSymbol(StringData value); + /** Set the value of this Element to the given code and scope data. */ + Status setValueCodeWithScope(StringData code, const BSONObj& scope); - // - // Stream API - BSONObjBuilder like API, but methods return a Status. These are - // strictly a convenience API. You don't need to use them if you would rather be more - // explicit. - // + /** Set the value of this Element to the given integer. */ + Status setValueInt(int32_t value); - /** Append the provided double value as a new field with the provided name. */ - Status appendDouble(StringData fieldName, double value); + /** Set the value of this Element to the given timestamp. */ + Status setValueTimestamp(Timestamp value); - /** Append the provided std::string value as a new field with the provided name. */ - Status appendString(StringData fieldName, StringData value); + /** Set the value of this Element to the given long integer */ + Status setValueLong(int64_t value); - /** Append the provided object as a new field with the provided name. The data in - * 'value' is copied. - */ - Status appendObject(StringData fieldName, const BSONObj& value); + /** Set the value of this Element to MinKey. */ + Status setValueMinKey(); - /** Append the provided array object as a new field with the provided name. The data in - * value is copied. - */ - Status appendArray(StringData fieldName, const BSONObj& value); + /** Set the value of this Element to MaxKey. */ + Status setValueMaxKey(); - /** Append the provided binary data as a new field with the provided name. */ - Status appendBinary(StringData fieldName, - uint32_t len, mongo::BinDataType binType, const void* data); - /** Append an undefined value as a new field with the provided name. */ - Status appendUndefined(StringData fieldName); + // + // Value mutation API from variant types. + // - /** Append the provided OID as a new field with the provided name. */ - Status appendOID(StringData fieldName, mongo::OID value); + /** Set the value of this element to equal the value of the provided BSONElement + * 'value'. The name of this Element is not modified. + * + * The contents of value are copied. + */ + Status setValueBSONElement(const BSONElement& value); - /** Append the provided bool as a new field with the provided name. */ - Status appendBool(StringData fieldName, bool value); + /** Set the value of this Element to a numeric type appropriate to hold the given + * SafeNum value. + */ + Status setValueSafeNum(const SafeNum value); + + + // + // Accessors + // + + /** Returns true if this Element represents a valid part of the Document. */ + inline bool ok() const; - /** Append the provided date as a new field with the provided name. */ - Status appendDate(StringData fieldName, Date_t value); + /** Returns the Document to which this Element belongs. */ + inline Document& getDocument(); - /** Append a null as a new field with the provided name. */ - Status appendNull(StringData fieldName); + /** Returns the Document to which this Element belongs. */ + inline const Document& getDocument() const; - /** Append the provided regex data as a new field with the provided name. */ - Status appendRegex(StringData fieldName, - StringData re, StringData flags); + /** Returns the BSONType of this Element. */ + BSONType getType() const; - /** Append the provided DBRef data as a new field with the provided name. */ - Status appendDBRef(StringData fieldName, - StringData ns, mongo::OID oid); + /** Returns true if this Element is of the specified type */ + inline bool isType(BSONType type) const; - /** Append the provided code data as a new field with the iven name. */ - Status appendCode(StringData fieldName, StringData value); + /** Returns the field name of this Element. Note that the value returned here is not + * stable across mutations, since the storage for fieldNames may be reallocated. If + * you need a stable version of the fieldName, you must call toString on the returned + * StringData. + */ + StringData getFieldName() const; + + /** Returns the opaque ID for this element. This is unlikely to be useful to a caller + * and is mostly for testing. + */ + inline RepIdx getIdx() const; - /** Append the provided symbol data as a new field with the provided name. */ - Status appendSymbol(StringData fieldName, StringData value); - /** Append the provided code and scope data as a new field with the provided name. */ - Status appendCodeWithScope(StringData fieldName, - StringData code, const BSONObj& scope); + // + // Stream API - BSONObjBuilder like API, but methods return a Status. These are + // strictly a convenience API. You don't need to use them if you would rather be more + // explicit. + // + + /** Append the provided double value as a new field with the provided name. */ + Status appendDouble(StringData fieldName, double value); + + /** Append the provided std::string value as a new field with the provided name. */ + Status appendString(StringData fieldName, StringData value); + + /** Append the provided object as a new field with the provided name. The data in + * 'value' is copied. + */ + Status appendObject(StringData fieldName, const BSONObj& value); + + /** Append the provided array object as a new field with the provided name. The data in + * value is copied. + */ + Status appendArray(StringData fieldName, const BSONObj& value); - /** Append the provided integer as a new field with the provided name. */ - Status appendInt(StringData fieldName, int32_t value); + /** Append the provided binary data as a new field with the provided name. */ + Status appendBinary(StringData fieldName, + uint32_t len, + mongo::BinDataType binType, + const void* data); - /** Append the provided timestamp as a new field with the provided name. */ - Status appendTimestamp(StringData fieldName, Timestamp value); + /** Append an undefined value as a new field with the provided name. */ + Status appendUndefined(StringData fieldName); - /** Append the provided long integer as a new field with the provided name. */ - Status appendLong(StringData fieldName, int64_t value); + /** Append the provided OID as a new field with the provided name. */ + Status appendOID(StringData fieldName, mongo::OID value); - /** Append a max key as a new field with the provided name. */ - Status appendMinKey(StringData fieldName); + /** Append the provided bool as a new field with the provided name. */ + Status appendBool(StringData fieldName, bool value); - /** Append a min key as a new field with the provided name. */ - Status appendMaxKey(StringData fieldName); + /** Append the provided date as a new field with the provided name. */ + Status appendDate(StringData fieldName, Date_t value); - /** Append the given BSONElement. The data in 'value' is copied. */ - Status appendElement(const BSONElement& value); + /** Append a null as a new field with the provided name. */ + Status appendNull(StringData fieldName); - /** Append the provided number as field of the appropriate numeric type with the - * provided name. - */ - Status appendSafeNum(StringData fieldName, SafeNum value); + /** Append the provided regex data as a new field with the provided name. */ + Status appendRegex(StringData fieldName, StringData re, StringData flags); - /** Convert this element to its JSON representation if ok(), - * otherwise return !ok() message */ - std::string toString() const; + /** Append the provided DBRef data as a new field with the provided name. */ + Status appendDBRef(StringData fieldName, StringData ns, mongo::OID oid); - private: - friend class Document; - friend class ConstElement; + /** Append the provided code data as a new field with the iven name. */ + Status appendCode(StringData fieldName, StringData value); - friend bool operator==(const Element&, const Element&); + /** Append the provided symbol data as a new field with the provided name. */ + Status appendSymbol(StringData fieldName, StringData value); - inline Element(Document* doc, RepIdx repIdx); + /** Append the provided code and scope data as a new field with the provided name. */ + Status appendCodeWithScope(StringData fieldName, StringData code, const BSONObj& scope); - Status addChild(Element e, bool front); + /** Append the provided integer as a new field with the provided name. */ + Status appendInt(StringData fieldName, int32_t value); - StringData getValueStringOrSymbol() const; + /** Append the provided timestamp as a new field with the provided name. */ + Status appendTimestamp(StringData fieldName, Timestamp value); - Status setValue(Element::RepIdx newValueIdx); + /** Append the provided long integer as a new field with the provided name. */ + Status appendLong(StringData fieldName, int64_t value); - Document* _doc; - RepIdx _repIdx; - }; + /** Append a max key as a new field with the provided name. */ + Status appendMinKey(StringData fieldName); - /** Element comparison support. Comparison is like STL iterator comparision: equal Elements - * refer to the same underlying data. The equality does *not* mean that the underlying - * values are equivalent. Use the Element::compareWith methods to compare the represented - * data. + /** Append a min key as a new field with the provided name. */ + Status appendMaxKey(StringData fieldName); + + /** Append the given BSONElement. The data in 'value' is copied. */ + Status appendElement(const BSONElement& value); + + /** Append the provided number as field of the appropriate numeric type with the + * provided name. */ + Status appendSafeNum(StringData fieldName, SafeNum value); + + /** Convert this element to its JSON representation if ok(), + * otherwise return !ok() message */ + std::string toString() const; + +private: + friend class Document; + friend class ConstElement; + + friend bool operator==(const Element&, const Element&); + + inline Element(Document* doc, RepIdx repIdx); + + Status addChild(Element e, bool front); + + StringData getValueStringOrSymbol() const; + + Status setValue(Element::RepIdx newValueIdx); + + Document* _doc; + RepIdx _repIdx; +}; + +/** Element comparison support. Comparison is like STL iterator comparision: equal Elements + * refer to the same underlying data. The equality does *not* mean that the underlying + * values are equivalent. Use the Element::compareWith methods to compare the represented + * data. + */ - /** Returns true if l and r refer to the same data, false otherwise. */ - inline bool operator==(const Element& l, const Element& r); +/** Returns true if l and r refer to the same data, false otherwise. */ +inline bool operator==(const Element& l, const Element& r); - /** Returns false if l and r refer to the same data, true otherwise. */ - inline bool operator!=(const Element& l, const Element& r); +/** Returns false if l and r refer to the same data, true otherwise. */ +inline bool operator!=(const Element& l, const Element& r); -} // namespace mutablebson -} // namespace mongo +} // namespace mutablebson +} // namespace mongo #include "mongo/bson/mutable/element-inl.h" diff --git a/src/mongo/bson/mutable/mutable_bson_algo_test.cpp b/src/mongo/bson/mutable/mutable_bson_algo_test.cpp index 382fcf926b5..cdd85576ebf 100644 --- a/src/mongo/bson/mutable/mutable_bson_algo_test.cpp +++ b/src/mongo/bson/mutable/mutable_bson_algo_test.cpp @@ -37,295 +37,296 @@ namespace { - using mongo::Status; - using namespace mongo::mutablebson; - - class DocumentTest : public mongo::unittest::Test { - public: - DocumentTest() - : _doc() {} - - Document& doc() { return _doc; } - - private: - Document _doc; - }; - - TEST_F(DocumentTest, FindInEmptyObject) { - Element leftChild = doc().root().leftChild(); - ASSERT_FALSE(leftChild.ok()); - Element found = findElementNamed(leftChild, "X"); - ASSERT_FALSE(found.ok()); - ASSERT_EQUALS(&leftChild.getDocument(), &found.getDocument()); - ASSERT_EQUALS(leftChild.getIdx(), found.getIdx()); - } - - class OneChildTest : public DocumentTest { - virtual void setUp() { - ASSERT_EQUALS(Status::OK(), doc().root().appendBool("t", true)); - } - }; - - TEST_F(OneChildTest, FindNoMatch) { - Element leftChild = doc().root().leftChild(); - ASSERT_TRUE(leftChild.ok()); - Element found = findElementNamed(leftChild, "f"); - ASSERT_FALSE(found.ok()); - ASSERT_EQUALS(&leftChild.getDocument(), &found.getDocument()); - } - - TEST_F(OneChildTest, FindMatch) { - Element leftChild = doc().root().leftChild(); - ASSERT_TRUE(leftChild.ok()); - Element found = findElementNamed(leftChild, "t"); - ASSERT_TRUE(found.ok()); - ASSERT_EQUALS(&leftChild.getDocument(), &found.getDocument()); - ASSERT_EQUALS(found.getFieldName(), "t"); - found = findElementNamed(found.rightSibling(), "t"); - ASSERT_FALSE(found.ok()); - ASSERT_EQUALS(&leftChild.getDocument(), &found.getDocument()); - } - - class ManyChildrenTest : public DocumentTest { - virtual void setUp() { - ASSERT_EQUALS(Status::OK(), doc().root().appendString("begin", "a")); - ASSERT_EQUALS(Status::OK(), doc().root().appendString("repeated_sparse", "b")); - ASSERT_EQUALS(Status::OK(), doc().root().appendString("repeated_dense", "c")); - ASSERT_EQUALS(Status::OK(), doc().root().appendString("repeated_dense", "d")); - ASSERT_EQUALS(Status::OK(), doc().root().appendString("middle", "e")); - ASSERT_EQUALS(Status::OK(), doc().root().appendString("repeated_sparse", "f")); - ASSERT_EQUALS(Status::OK(), doc().root().appendString("end", "g")); - } - }; - - TEST_F(ManyChildrenTest, FindAtStart) { - static const char kName[] = "begin"; - Element leftChild = doc().root().leftChild(); - ASSERT_TRUE(leftChild.ok()); - Element found = findElementNamed(leftChild, kName); - ASSERT_TRUE(found.ok()); - ASSERT_EQUALS(found.getFieldName(), kName); - ASSERT_EQUALS(&leftChild.getDocument(), &found.getDocument()); - ASSERT_FALSE(findElementNamed(found.rightSibling(), kName).ok()); - } - - TEST_F(ManyChildrenTest, FindInMiddle) { - static const char kName[] = "middle"; - Element leftChild = doc().root().leftChild(); - ASSERT_TRUE(leftChild.ok()); - Element found = findElementNamed(leftChild, kName); - ASSERT_TRUE(found.ok()); - ASSERT_EQUALS(found.getFieldName(), kName); - ASSERT_EQUALS(&leftChild.getDocument(), &found.getDocument()); - ASSERT_FALSE(findElementNamed(found.rightSibling(), kName).ok()); - } - - TEST_F(ManyChildrenTest, FindAtEnd) { - static const char kName[] = "end"; - Element leftChild = doc().root().leftChild(); - ASSERT_TRUE(leftChild.ok()); - Element found = findElementNamed(leftChild, kName); - ASSERT_TRUE(found.ok()); - ASSERT_EQUALS(found.getFieldName(), kName); - ASSERT_EQUALS(&leftChild.getDocument(), &found.getDocument()); - ASSERT_FALSE(findElementNamed(found.rightSibling(), kName).ok()); - } - - TEST_F(ManyChildrenTest, FindRepeatedSparse) { - static const char kName[] = "repeated_sparse"; - Element leftChild = doc().root().leftChild(); - ASSERT_TRUE(leftChild.ok()); - Element first = findElementNamed(leftChild, kName); - ASSERT_TRUE(first.ok()); - ASSERT_EQUALS(first.getFieldName(), kName); - ASSERT_EQUALS(&leftChild.getDocument(), &first.getDocument()); - Element second = findElementNamed(first.rightSibling(), kName); - ASSERT_TRUE(second.ok()); - ASSERT_EQUALS(&first.getDocument(), &second.getDocument()); - ASSERT_NOT_EQUALS(first.getIdx(), second.getIdx()); - Element none = findElementNamed(second.rightSibling(), kName); - ASSERT_FALSE(none.ok()); - } - - TEST_F(ManyChildrenTest, FindRepeatedDense) { - static const char kName[] = "repeated_dense"; - Element leftChild = doc().root().leftChild(); - ASSERT_TRUE(leftChild.ok()); - Element first = findElementNamed(leftChild, kName); - ASSERT_TRUE(first.ok()); - ASSERT_EQUALS(first.getFieldName(), kName); - ASSERT_EQUALS(&leftChild.getDocument(), &first.getDocument()); - Element second = findElementNamed(first.rightSibling(), kName); - ASSERT_TRUE(second.ok()); - ASSERT_EQUALS(&first.getDocument(), &second.getDocument()); - ASSERT_NOT_EQUALS(first.getIdx(), second.getIdx()); - Element none = findElementNamed(second.rightSibling(), kName); - ASSERT_FALSE(none.ok()); - } - - TEST_F(ManyChildrenTest, FindDoesNotSearchWithinChildren) { - static const char kName[] = "in_child"; - Element found_before_add = findElementNamed(doc().root().leftChild(), kName); - ASSERT_FALSE(found_before_add.ok()); - Element subdoc = doc().makeElementObject("child"); - ASSERT_EQUALS(Status::OK(), doc().root().pushBack(subdoc)); - ASSERT_EQUALS(Status::OK(), subdoc.appendBool(kName, true)); - Element found_after_add = findElementNamed(doc().root().leftChild(), kName); - ASSERT_FALSE(found_after_add.ok()); - } - - TEST_F(ManyChildrenTest, getNthSibling) { - const Element leftChild = doc().root().leftChild(); - ASSERT_TRUE(leftChild.ok()); - const Element rightChild = doc().root().rightChild(); - ASSERT_TRUE(rightChild.ok()); - - // Check that moving zero is a no-op - Element zeroAway = getNthSibling(leftChild, 0); - ASSERT_TRUE(zeroAway.ok()); - ASSERT_EQUALS(leftChild, zeroAway); - zeroAway = getNthSibling(rightChild, 0); - ASSERT_TRUE(zeroAway.ok()); - ASSERT_EQUALS(rightChild, zeroAway); - - // Check that moving left of leftmost gets a not-ok element. - Element badLeft = getNthSibling(leftChild, -1); - ASSERT_FALSE(badLeft.ok()); - - // Check that moving right of rightmost gets a non-ok element. - Element badRight = getNthSibling(rightChild, 1); - ASSERT_FALSE(badRight.ok()); - - // Check that the moving one right from leftmost gets us the expected element. - Element target = leftChild.rightSibling(); - ASSERT_TRUE(target.ok()); - Element query = getNthSibling(leftChild, 1); - ASSERT_TRUE(target.ok()); - ASSERT_EQUALS(target, query); - - // And the same from the other side - target = rightChild.leftSibling(); - ASSERT_TRUE(target.ok()); - query = getNthSibling(rightChild, -1); - ASSERT_TRUE(target.ok()); - ASSERT_EQUALS(target, query); - - // Ensure that walking more chidren than we have gets us past the end - const int children = countChildren(doc().root()); - query = getNthSibling(leftChild, children); - ASSERT_FALSE(query.ok()); - query = getNthSibling(rightChild, -children); - ASSERT_FALSE(query.ok()); - - // Ensure that walking all the children in either direction gets - // us to the other right/left child. - query = getNthSibling(leftChild, children - 1); - ASSERT_TRUE(query.ok()); - ASSERT_EQUALS(rightChild, query); - query = getNthSibling(rightChild, -(children - 1)); - ASSERT_TRUE(query.ok()); - ASSERT_EQUALS(leftChild, query); - } - - class CountTest : public DocumentTest { - virtual void setUp() { - Element root = doc().root(); - - ASSERT_OK(root.appendInt("leaf", 0)); - - Element one = doc().makeElementObject("oneChild"); - ASSERT_TRUE(one.ok()); - ASSERT_OK(one.appendInt("one", 1)); - ASSERT_OK(root.pushBack(one)); - - Element threeChildren = doc().makeElementObject("threeChildren"); - ASSERT_TRUE(one.ok()); - ASSERT_OK(threeChildren.appendInt("one", 1)); - ASSERT_OK(threeChildren.appendInt("two", 2)); - ASSERT_OK(threeChildren.appendInt("three", 3)); - ASSERT_OK(root.pushBack(threeChildren)); - } - }; - - TEST_F(CountTest, EmptyDocument) { - // Doesn't use the fixture but belongs in the same group of tests. - Document doc; - ASSERT_EQUALS(countChildren(doc.root()), 0u); - } - - TEST_F(CountTest, EmptyElement) { - Element leaf = findFirstChildNamed(doc().root(), "leaf"); - ASSERT_TRUE(leaf.ok()); - ASSERT_EQUALS(countChildren(leaf), 0u); - } - - TEST_F(CountTest, OneChildElement) { - Element oneChild = findFirstChildNamed(doc().root(), "oneChild"); - ASSERT_TRUE(oneChild.ok()); - ASSERT_EQUALS(countChildren(oneChild), 1u); - } - - TEST_F(CountTest, ManyChildren) { - Element threeChildren = findFirstChildNamed(doc().root(), "threeChildren"); - ASSERT_TRUE(threeChildren.ok()); - ASSERT_EQUALS(countChildren(threeChildren), 3u); - } - - TEST_F(CountTest, CountSiblingsNone) { - ConstElement current = findFirstChildNamed(doc().root(), "oneChild"); - ASSERT_TRUE(current.ok()); - - current = current.leftChild(); - ASSERT_TRUE(current.ok()); - - ASSERT_EQUALS(0U, countSiblingsLeft(current)); - ASSERT_EQUALS(0U, countSiblingsRight(current)); - } - - TEST_F(CountTest, CountSiblingsMany) { - ConstElement current = findFirstChildNamed(doc().root(), "threeChildren"); - ASSERT_TRUE(current.ok()); - - current = current.leftChild(); - ASSERT_TRUE(current.ok()); - - ASSERT_EQUALS(0U, countSiblingsLeft(current)); - ASSERT_EQUALS(2U, countSiblingsRight(current)); - - current = current.rightSibling(); - ASSERT_TRUE(current.ok()); - ASSERT_EQUALS(1U, countSiblingsLeft(current)); - ASSERT_EQUALS(1U, countSiblingsRight(current)); +using mongo::Status; +using namespace mongo::mutablebson; - current = current.rightSibling(); - ASSERT_TRUE(current.ok()); - ASSERT_EQUALS(2U, countSiblingsLeft(current)); - ASSERT_EQUALS(0U, countSiblingsRight(current)); +class DocumentTest : public mongo::unittest::Test { +public: + DocumentTest() : _doc() {} - current = current.rightSibling(); - ASSERT_FALSE(current.ok()); + Document& doc() { + return _doc; } - TEST(DeduplicateTest, ManyDuplicates) { - Document doc(mongo::fromjson("{ x : [ 1, 2, 2, 3, 3, 3, 4, 4, 4 ] }")); - deduplicateChildren(doc.root().leftChild(), woEqual(false)); - ASSERT_TRUE(checkDoc(doc, mongo::fromjson("{x : [ 1, 2, 3, 4 ]}"))); +private: + Document _doc; +}; + +TEST_F(DocumentTest, FindInEmptyObject) { + Element leftChild = doc().root().leftChild(); + ASSERT_FALSE(leftChild.ok()); + Element found = findElementNamed(leftChild, "X"); + ASSERT_FALSE(found.ok()); + ASSERT_EQUALS(&leftChild.getDocument(), &found.getDocument()); + ASSERT_EQUALS(leftChild.getIdx(), found.getIdx()); +} + +class OneChildTest : public DocumentTest { + virtual void setUp() { + ASSERT_EQUALS(Status::OK(), doc().root().appendBool("t", true)); } - - TEST(FullNameTest, RootField) { - Document doc(mongo::fromjson("{ x : 1 }")); - ASSERT_EQUALS("x", getFullName(doc.root().leftChild())); +}; + +TEST_F(OneChildTest, FindNoMatch) { + Element leftChild = doc().root().leftChild(); + ASSERT_TRUE(leftChild.ok()); + Element found = findElementNamed(leftChild, "f"); + ASSERT_FALSE(found.ok()); + ASSERT_EQUALS(&leftChild.getDocument(), &found.getDocument()); +} + +TEST_F(OneChildTest, FindMatch) { + Element leftChild = doc().root().leftChild(); + ASSERT_TRUE(leftChild.ok()); + Element found = findElementNamed(leftChild, "t"); + ASSERT_TRUE(found.ok()); + ASSERT_EQUALS(&leftChild.getDocument(), &found.getDocument()); + ASSERT_EQUALS(found.getFieldName(), "t"); + found = findElementNamed(found.rightSibling(), "t"); + ASSERT_FALSE(found.ok()); + ASSERT_EQUALS(&leftChild.getDocument(), &found.getDocument()); +} + +class ManyChildrenTest : public DocumentTest { + virtual void setUp() { + ASSERT_EQUALS(Status::OK(), doc().root().appendString("begin", "a")); + ASSERT_EQUALS(Status::OK(), doc().root().appendString("repeated_sparse", "b")); + ASSERT_EQUALS(Status::OK(), doc().root().appendString("repeated_dense", "c")); + ASSERT_EQUALS(Status::OK(), doc().root().appendString("repeated_dense", "d")); + ASSERT_EQUALS(Status::OK(), doc().root().appendString("middle", "e")); + ASSERT_EQUALS(Status::OK(), doc().root().appendString("repeated_sparse", "f")); + ASSERT_EQUALS(Status::OK(), doc().root().appendString("end", "g")); } - - TEST(FullNameTest, OneLevel) { - Document doc(mongo::fromjson("{ x : { y: 1 } }")); - ASSERT_EQUALS("x.y", getFullName(doc.root().leftChild().leftChild())); +}; + +TEST_F(ManyChildrenTest, FindAtStart) { + static const char kName[] = "begin"; + Element leftChild = doc().root().leftChild(); + ASSERT_TRUE(leftChild.ok()); + Element found = findElementNamed(leftChild, kName); + ASSERT_TRUE(found.ok()); + ASSERT_EQUALS(found.getFieldName(), kName); + ASSERT_EQUALS(&leftChild.getDocument(), &found.getDocument()); + ASSERT_FALSE(findElementNamed(found.rightSibling(), kName).ok()); +} + +TEST_F(ManyChildrenTest, FindInMiddle) { + static const char kName[] = "middle"; + Element leftChild = doc().root().leftChild(); + ASSERT_TRUE(leftChild.ok()); + Element found = findElementNamed(leftChild, kName); + ASSERT_TRUE(found.ok()); + ASSERT_EQUALS(found.getFieldName(), kName); + ASSERT_EQUALS(&leftChild.getDocument(), &found.getDocument()); + ASSERT_FALSE(findElementNamed(found.rightSibling(), kName).ok()); +} + +TEST_F(ManyChildrenTest, FindAtEnd) { + static const char kName[] = "end"; + Element leftChild = doc().root().leftChild(); + ASSERT_TRUE(leftChild.ok()); + Element found = findElementNamed(leftChild, kName); + ASSERT_TRUE(found.ok()); + ASSERT_EQUALS(found.getFieldName(), kName); + ASSERT_EQUALS(&leftChild.getDocument(), &found.getDocument()); + ASSERT_FALSE(findElementNamed(found.rightSibling(), kName).ok()); +} + +TEST_F(ManyChildrenTest, FindRepeatedSparse) { + static const char kName[] = "repeated_sparse"; + Element leftChild = doc().root().leftChild(); + ASSERT_TRUE(leftChild.ok()); + Element first = findElementNamed(leftChild, kName); + ASSERT_TRUE(first.ok()); + ASSERT_EQUALS(first.getFieldName(), kName); + ASSERT_EQUALS(&leftChild.getDocument(), &first.getDocument()); + Element second = findElementNamed(first.rightSibling(), kName); + ASSERT_TRUE(second.ok()); + ASSERT_EQUALS(&first.getDocument(), &second.getDocument()); + ASSERT_NOT_EQUALS(first.getIdx(), second.getIdx()); + Element none = findElementNamed(second.rightSibling(), kName); + ASSERT_FALSE(none.ok()); +} + +TEST_F(ManyChildrenTest, FindRepeatedDense) { + static const char kName[] = "repeated_dense"; + Element leftChild = doc().root().leftChild(); + ASSERT_TRUE(leftChild.ok()); + Element first = findElementNamed(leftChild, kName); + ASSERT_TRUE(first.ok()); + ASSERT_EQUALS(first.getFieldName(), kName); + ASSERT_EQUALS(&leftChild.getDocument(), &first.getDocument()); + Element second = findElementNamed(first.rightSibling(), kName); + ASSERT_TRUE(second.ok()); + ASSERT_EQUALS(&first.getDocument(), &second.getDocument()); + ASSERT_NOT_EQUALS(first.getIdx(), second.getIdx()); + Element none = findElementNamed(second.rightSibling(), kName); + ASSERT_FALSE(none.ok()); +} + +TEST_F(ManyChildrenTest, FindDoesNotSearchWithinChildren) { + static const char kName[] = "in_child"; + Element found_before_add = findElementNamed(doc().root().leftChild(), kName); + ASSERT_FALSE(found_before_add.ok()); + Element subdoc = doc().makeElementObject("child"); + ASSERT_EQUALS(Status::OK(), doc().root().pushBack(subdoc)); + ASSERT_EQUALS(Status::OK(), subdoc.appendBool(kName, true)); + Element found_after_add = findElementNamed(doc().root().leftChild(), kName); + ASSERT_FALSE(found_after_add.ok()); +} + +TEST_F(ManyChildrenTest, getNthSibling) { + const Element leftChild = doc().root().leftChild(); + ASSERT_TRUE(leftChild.ok()); + const Element rightChild = doc().root().rightChild(); + ASSERT_TRUE(rightChild.ok()); + + // Check that moving zero is a no-op + Element zeroAway = getNthSibling(leftChild, 0); + ASSERT_TRUE(zeroAway.ok()); + ASSERT_EQUALS(leftChild, zeroAway); + zeroAway = getNthSibling(rightChild, 0); + ASSERT_TRUE(zeroAway.ok()); + ASSERT_EQUALS(rightChild, zeroAway); + + // Check that moving left of leftmost gets a not-ok element. + Element badLeft = getNthSibling(leftChild, -1); + ASSERT_FALSE(badLeft.ok()); + + // Check that moving right of rightmost gets a non-ok element. + Element badRight = getNthSibling(rightChild, 1); + ASSERT_FALSE(badRight.ok()); + + // Check that the moving one right from leftmost gets us the expected element. + Element target = leftChild.rightSibling(); + ASSERT_TRUE(target.ok()); + Element query = getNthSibling(leftChild, 1); + ASSERT_TRUE(target.ok()); + ASSERT_EQUALS(target, query); + + // And the same from the other side + target = rightChild.leftSibling(); + ASSERT_TRUE(target.ok()); + query = getNthSibling(rightChild, -1); + ASSERT_TRUE(target.ok()); + ASSERT_EQUALS(target, query); + + // Ensure that walking more chidren than we have gets us past the end + const int children = countChildren(doc().root()); + query = getNthSibling(leftChild, children); + ASSERT_FALSE(query.ok()); + query = getNthSibling(rightChild, -children); + ASSERT_FALSE(query.ok()); + + // Ensure that walking all the children in either direction gets + // us to the other right/left child. + query = getNthSibling(leftChild, children - 1); + ASSERT_TRUE(query.ok()); + ASSERT_EQUALS(rightChild, query); + query = getNthSibling(rightChild, -(children - 1)); + ASSERT_TRUE(query.ok()); + ASSERT_EQUALS(leftChild, query); +} + +class CountTest : public DocumentTest { + virtual void setUp() { + Element root = doc().root(); + + ASSERT_OK(root.appendInt("leaf", 0)); + + Element one = doc().makeElementObject("oneChild"); + ASSERT_TRUE(one.ok()); + ASSERT_OK(one.appendInt("one", 1)); + ASSERT_OK(root.pushBack(one)); + + Element threeChildren = doc().makeElementObject("threeChildren"); + ASSERT_TRUE(one.ok()); + ASSERT_OK(threeChildren.appendInt("one", 1)); + ASSERT_OK(threeChildren.appendInt("two", 2)); + ASSERT_OK(threeChildren.appendInt("three", 3)); + ASSERT_OK(root.pushBack(threeChildren)); } - - TEST(FullNameTest, InsideArray) { - Document doc(mongo::fromjson("{ x : { y: [ 1 , 2 ] } }")); - ASSERT_EQUALS("x.y.1", - getFullName(doc.root().leftChild().leftChild().leftChild().rightSibling())); - } - -} // namespace +}; + +TEST_F(CountTest, EmptyDocument) { + // Doesn't use the fixture but belongs in the same group of tests. + Document doc; + ASSERT_EQUALS(countChildren(doc.root()), 0u); +} + +TEST_F(CountTest, EmptyElement) { + Element leaf = findFirstChildNamed(doc().root(), "leaf"); + ASSERT_TRUE(leaf.ok()); + ASSERT_EQUALS(countChildren(leaf), 0u); +} + +TEST_F(CountTest, OneChildElement) { + Element oneChild = findFirstChildNamed(doc().root(), "oneChild"); + ASSERT_TRUE(oneChild.ok()); + ASSERT_EQUALS(countChildren(oneChild), 1u); +} + +TEST_F(CountTest, ManyChildren) { + Element threeChildren = findFirstChildNamed(doc().root(), "threeChildren"); + ASSERT_TRUE(threeChildren.ok()); + ASSERT_EQUALS(countChildren(threeChildren), 3u); +} + +TEST_F(CountTest, CountSiblingsNone) { + ConstElement current = findFirstChildNamed(doc().root(), "oneChild"); + ASSERT_TRUE(current.ok()); + + current = current.leftChild(); + ASSERT_TRUE(current.ok()); + + ASSERT_EQUALS(0U, countSiblingsLeft(current)); + ASSERT_EQUALS(0U, countSiblingsRight(current)); +} + +TEST_F(CountTest, CountSiblingsMany) { + ConstElement current = findFirstChildNamed(doc().root(), "threeChildren"); + ASSERT_TRUE(current.ok()); + + current = current.leftChild(); + ASSERT_TRUE(current.ok()); + + ASSERT_EQUALS(0U, countSiblingsLeft(current)); + ASSERT_EQUALS(2U, countSiblingsRight(current)); + + current = current.rightSibling(); + ASSERT_TRUE(current.ok()); + ASSERT_EQUALS(1U, countSiblingsLeft(current)); + ASSERT_EQUALS(1U, countSiblingsRight(current)); + + current = current.rightSibling(); + ASSERT_TRUE(current.ok()); + ASSERT_EQUALS(2U, countSiblingsLeft(current)); + ASSERT_EQUALS(0U, countSiblingsRight(current)); + + current = current.rightSibling(); + ASSERT_FALSE(current.ok()); +} + +TEST(DeduplicateTest, ManyDuplicates) { + Document doc(mongo::fromjson("{ x : [ 1, 2, 2, 3, 3, 3, 4, 4, 4 ] }")); + deduplicateChildren(doc.root().leftChild(), woEqual(false)); + ASSERT_TRUE(checkDoc(doc, mongo::fromjson("{x : [ 1, 2, 3, 4 ]}"))); +} + +TEST(FullNameTest, RootField) { + Document doc(mongo::fromjson("{ x : 1 }")); + ASSERT_EQUALS("x", getFullName(doc.root().leftChild())); +} + +TEST(FullNameTest, OneLevel) { + Document doc(mongo::fromjson("{ x : { y: 1 } }")); + ASSERT_EQUALS("x.y", getFullName(doc.root().leftChild().leftChild())); +} + +TEST(FullNameTest, InsideArray) { + Document doc(mongo::fromjson("{ x : { y: [ 1 , 2 ] } }")); + ASSERT_EQUALS("x.y.1", + getFullName(doc.root().leftChild().leftChild().leftChild().rightSibling())); +} + +} // namespace diff --git a/src/mongo/bson/mutable/mutable_bson_test.cpp b/src/mongo/bson/mutable/mutable_bson_test.cpp index a7998173631..f8086da510f 100644 --- a/src/mongo/bson/mutable/mutable_bson_test.cpp +++ b/src/mongo/bson/mutable/mutable_bson_test.cpp @@ -39,2987 +39,2975 @@ namespace { - namespace mmb = mongo::mutablebson; - - TEST(TopologyBuilding, TopDownFromScratch) { - /* - [ e0 ] - / \ - / \ - [ e1 ]..[ e2 ] - / - / - [ e3 ] - / \ - / \ - [ e4 ]..[ e5 ] - */ - - mmb::Document doc; - - mmb::Element e0 = doc.makeElementObject("e0"); - mmb::Element e1 = doc.makeElementObject("e1"); - mmb::Element e2 = doc.makeElementObject("e2"); - mmb::Element e3 = doc.makeElementObject("e3"); - mmb::Element e4 = doc.makeElementObject("e4"); - mmb::Element e5 = doc.makeElementObject("e5"); - - ASSERT_EQUALS(e0.pushBack(e1), mongo::Status::OK()); - ASSERT_EQUALS(e0.pushBack(e2), mongo::Status::OK()); - ASSERT_EQUALS(e2.pushBack(e3), mongo::Status::OK()); - ASSERT_EQUALS(e3.pushBack(e4), mongo::Status::OK()); - ASSERT_EQUALS(e3.pushBack(e5), mongo::Status::OK()); - - ASSERT_EQUALS("e0", e0.getFieldName()); - ASSERT_EQUALS("e1", e0.leftChild().getFieldName()); - ASSERT_EQUALS("e2", e0.rightChild().getFieldName()); - ASSERT_EQUALS("e0", e1.parent().getFieldName()); - ASSERT_EQUALS("e0", e2.parent().getFieldName()); - ASSERT_EQUALS("e2", e1.rightSibling().getFieldName()); - ASSERT_EQUALS("e1", e2.leftSibling().getFieldName()); - ASSERT_EQUALS("e3", e2.leftChild().getFieldName()); - ASSERT_EQUALS("e3", e2.rightChild().getFieldName()); - - ASSERT_EQUALS("e2", e3.parent().getFieldName()); - ASSERT_EQUALS("e4", e3.leftChild().getFieldName()); - ASSERT_EQUALS("e5", e3.rightChild().getFieldName()); - ASSERT_EQUALS("e4", e5.leftSibling().getFieldName()); - ASSERT_EQUALS("e5", e4.rightSibling().getFieldName()); - ASSERT_EQUALS("e3", e4.parent().getFieldName()); - ASSERT_EQUALS("e3", e5.parent().getFieldName()); - } - - TEST(TopologyBuilding, AddSiblingAfter) { - /* - [ e0 ] - / \ - / \ - [ e1 ]..[ e2 ] - / \ - / \ - [ e3 ]..[ e4 ] - */ - - mmb::Document doc; - - mmb::Element e0 = doc.makeElementObject("e0"); - mmb::Element e1 = doc.makeElementObject("e1"); - mmb::Element e2 = doc.makeElementObject("e2"); - mmb::Element e3 = doc.makeElementObject("e3"); - mmb::Element e4 = doc.makeElementObject("e4"); - - ASSERT_EQUALS(e0.pushBack(e1), mongo::Status::OK()); - ASSERT_EQUALS(e0.pushBack(e2), mongo::Status::OK()); - ASSERT_EQUALS(e2.pushBack(e3), mongo::Status::OK()); - ASSERT_EQUALS(e3.addSiblingRight(e4), mongo::Status::OK()); - - ASSERT_EQUALS("e4", e3.rightSibling().getFieldName()); - ASSERT_EQUALS("e3", e4.leftSibling().getFieldName()); - ASSERT_EQUALS("e2", e4.parent().getFieldName()); - ASSERT_EQUALS("e3", e2.leftChild().getFieldName()); - ASSERT_EQUALS("e4", e2.rightChild().getFieldName()); - } - - TEST(TopologyBuilding, AddSiblingBefore) { - /* - [ e2 ] - / \ - / \ - [ e3 ]..[ e4 ] - / | \ - / | \ - [ e7 ]..[ e5 ]..[ e6 ] - */ - - mmb::Document doc; - - mmb::Element e2 = doc.makeElementObject("e2"); - mmb::Element e3 = doc.makeElementObject("e3"); - mmb::Element e4 = doc.makeElementObject("e4"); - mmb::Element e5 = doc.makeElementObject("e5"); - mmb::Element e6 = doc.makeElementObject("e6"); - mmb::Element e7 = doc.makeElementObject("e7"); - - ASSERT_EQUALS(e2.pushBack(e3), mongo::Status::OK()); - ASSERT_EQUALS(e2.pushBack(e4), mongo::Status::OK()); - ASSERT_EQUALS(e3.pushBack(e5), mongo::Status::OK()); - ASSERT_EQUALS(e5.addSiblingRight(e6), mongo::Status::OK()); - ASSERT_EQUALS(e5.addSiblingLeft(e7), mongo::Status::OK()); - - ASSERT_EQUALS("e5", e7.rightSibling().getFieldName()); - ASSERT_EQUALS("e7", e5.leftSibling().getFieldName()); - ASSERT_EQUALS("e6", e5.rightSibling().getFieldName()); - ASSERT_EQUALS("e5", e6.leftSibling().getFieldName()); - - ASSERT_EQUALS("e3", e5.parent().getFieldName()); - ASSERT_EQUALS("e3", e6.parent().getFieldName()); - ASSERT_EQUALS("e3", e7.parent().getFieldName()); - ASSERT_EQUALS("e7", e3.leftChild().getFieldName()); - ASSERT_EQUALS("e6", e3.rightChild().getFieldName()); - } - - TEST(TopologyBuilding, AddSubtreeBottomUp) { - /* - [ e3 ] - / | \ - / | \ - [ e4 ]..[ e5 ]..[ e6 ] - | - | - [ e8 ] +namespace mmb = mongo::mutablebson; + +TEST(TopologyBuilding, TopDownFromScratch) { + /* + [ e0 ] + / \ + / \ + [ e1 ]..[ e2 ] + / + / + [ e3 ] + / \ + / \ + [ e4 ]..[ e5 ] + */ + + mmb::Document doc; + + mmb::Element e0 = doc.makeElementObject("e0"); + mmb::Element e1 = doc.makeElementObject("e1"); + mmb::Element e2 = doc.makeElementObject("e2"); + mmb::Element e3 = doc.makeElementObject("e3"); + mmb::Element e4 = doc.makeElementObject("e4"); + mmb::Element e5 = doc.makeElementObject("e5"); + + ASSERT_EQUALS(e0.pushBack(e1), mongo::Status::OK()); + ASSERT_EQUALS(e0.pushBack(e2), mongo::Status::OK()); + ASSERT_EQUALS(e2.pushBack(e3), mongo::Status::OK()); + ASSERT_EQUALS(e3.pushBack(e4), mongo::Status::OK()); + ASSERT_EQUALS(e3.pushBack(e5), mongo::Status::OK()); + + ASSERT_EQUALS("e0", e0.getFieldName()); + ASSERT_EQUALS("e1", e0.leftChild().getFieldName()); + ASSERT_EQUALS("e2", e0.rightChild().getFieldName()); + ASSERT_EQUALS("e0", e1.parent().getFieldName()); + ASSERT_EQUALS("e0", e2.parent().getFieldName()); + ASSERT_EQUALS("e2", e1.rightSibling().getFieldName()); + ASSERT_EQUALS("e1", e2.leftSibling().getFieldName()); + ASSERT_EQUALS("e3", e2.leftChild().getFieldName()); + ASSERT_EQUALS("e3", e2.rightChild().getFieldName()); + + ASSERT_EQUALS("e2", e3.parent().getFieldName()); + ASSERT_EQUALS("e4", e3.leftChild().getFieldName()); + ASSERT_EQUALS("e5", e3.rightChild().getFieldName()); + ASSERT_EQUALS("e4", e5.leftSibling().getFieldName()); + ASSERT_EQUALS("e5", e4.rightSibling().getFieldName()); + ASSERT_EQUALS("e3", e4.parent().getFieldName()); + ASSERT_EQUALS("e3", e5.parent().getFieldName()); +} + +TEST(TopologyBuilding, AddSiblingAfter) { + /* + [ e0 ] + / \ + / \ + [ e1 ]..[ e2 ] / \ / \ - [ e9 ]..[ e10] - */ - mmb::Document doc; - - mmb::Element e3 = doc.makeElementObject("e3"); - mmb::Element e4 = doc.makeElementObject("e4"); - mmb::Element e5 = doc.makeElementObject("e5"); - mmb::Element e6 = doc.makeElementObject("e6"); - mmb::Element e8 = doc.makeElementObject("e8"); - mmb::Element e9 = doc.makeElementObject("e9"); - mmb::Element e10 = doc.makeElementObject("e10"); - - ASSERT_EQUALS(e3.pushBack(e4), mongo::Status::OK()); - ASSERT_EQUALS(e3.pushBack(e5), mongo::Status::OK()); - ASSERT_EQUALS(e3.pushBack(e6), mongo::Status::OK()); - ASSERT_EQUALS(e8.pushBack(e9), mongo::Status::OK()); - ASSERT_EQUALS(e8.pushBack(e10), mongo::Status::OK()); - ASSERT_EQUALS(e5.pushBack(e8), mongo::Status::OK()); - - ASSERT_EQUALS("e8", e9.parent().getFieldName()); - ASSERT_EQUALS("e8", e10.parent().getFieldName()); - ASSERT_EQUALS("e9", e8.leftChild().getFieldName()); - ASSERT_EQUALS("e10", e8.rightChild().getFieldName()); - ASSERT_EQUALS("e9", e10.leftSibling().getFieldName()); - ASSERT_EQUALS("e10", e9.rightSibling().getFieldName()); - ASSERT_EQUALS("e5", e8.parent().getFieldName()); - ASSERT_EQUALS("e8", e5.leftChild().getFieldName()); - ASSERT_EQUALS("e8", e5.rightChild().getFieldName()); - } - - TEST(TopologyBuilding, RemoveLeafNode) { - /* - [ e0 ] [ e0 ] - / \ => \ - / \ \ - [ e1 ] [ e2 ] [ e2 ] - */ - mmb::Document doc; - - mmb::Element e0 = doc.makeElementObject("e0"); - mmb::Element e1 = doc.makeElementObject("e1"); - mmb::Element e2 = doc.makeElementObject("e2"); - - ASSERT_EQUALS(e0.pushBack(e1), mongo::Status::OK()); - ASSERT_EQUALS(e0.pushBack(e2), mongo::Status::OK()); - ASSERT_EQUALS(e1.remove(), mongo::Status::OK()); - - ASSERT_EQUALS("e2", e0.leftChild().getFieldName()); - ASSERT_EQUALS("e2", e0.rightChild().getFieldName()); - } - - TEST(TopologyBuilding, RemoveSubtree) { - /* - [ e3 ] [ e3 ] - / | \ / \ - / | \ / \ - [ e4 ]..[ e5 ]..[ e6 ] [ e4 ] [ e6 ] - | => - | - [ e8 ] + [ e3 ]..[ e4 ] + */ + + mmb::Document doc; + + mmb::Element e0 = doc.makeElementObject("e0"); + mmb::Element e1 = doc.makeElementObject("e1"); + mmb::Element e2 = doc.makeElementObject("e2"); + mmb::Element e3 = doc.makeElementObject("e3"); + mmb::Element e4 = doc.makeElementObject("e4"); + + ASSERT_EQUALS(e0.pushBack(e1), mongo::Status::OK()); + ASSERT_EQUALS(e0.pushBack(e2), mongo::Status::OK()); + ASSERT_EQUALS(e2.pushBack(e3), mongo::Status::OK()); + ASSERT_EQUALS(e3.addSiblingRight(e4), mongo::Status::OK()); + + ASSERT_EQUALS("e4", e3.rightSibling().getFieldName()); + ASSERT_EQUALS("e3", e4.leftSibling().getFieldName()); + ASSERT_EQUALS("e2", e4.parent().getFieldName()); + ASSERT_EQUALS("e3", e2.leftChild().getFieldName()); + ASSERT_EQUALS("e4", e2.rightChild().getFieldName()); +} + +TEST(TopologyBuilding, AddSiblingBefore) { + /* + [ e2 ] / \ / \ - [ e9 ]..[ e10] - */ - mmb::Document doc; - - mmb::Element e3 = doc.makeElementObject("e3"); - mmb::Element e4 = doc.makeElementObject("e4"); - mmb::Element e5 = doc.makeElementObject("e5"); - mmb::Element e6 = doc.makeElementObject("e6"); - mmb::Element e8 = doc.makeElementObject("e8"); - mmb::Element e9 = doc.makeElementObject("e9"); - mmb::Element e10 = doc.makeElementObject("e10"); - - ASSERT_EQUALS(e3.pushBack(e4), mongo::Status::OK()); - ASSERT_EQUALS(e3.pushBack(e5), mongo::Status::OK()); - ASSERT_EQUALS(e5.addSiblingRight(e6), mongo::Status::OK()); - - ASSERT_EQUALS(e8.pushBack(e9), mongo::Status::OK()); - ASSERT_EQUALS(e8.pushBack(e10), mongo::Status::OK()); - ASSERT_EQUALS(e5.pushBack(e8), mongo::Status::OK()); - ASSERT_EQUALS(e5.remove(), mongo::Status::OK()); - - ASSERT_EQUALS("e3", e4.parent().getFieldName()); - ASSERT_EQUALS("e3", e6.parent().getFieldName()); - ASSERT_EQUALS("e4", e3.leftChild().getFieldName()); - ASSERT_EQUALS("e6", e3.rightChild().getFieldName()); - } - - TEST(TopologyBuilding, RenameNode) { - /* - - [ e0 ] [ f0 ] - / \ => / \ - / \ / \ - [ e1 ]..[ e2 ] [ e1 ]..[ e2 ] - */ - - mmb::Document doc; - - mmb::Element e0 = doc.makeElementObject("e0"); - mmb::Element e1 = doc.makeElementObject("e1"); - mmb::Element e2 = doc.makeElementObject("e2"); - - ASSERT_EQUALS(e0.pushBack(e1), mongo::Status::OK()); - ASSERT_EQUALS(e0.pushBack(e2), mongo::Status::OK()); - ASSERT_EQUALS(e0.rename("f0"), mongo::Status::OK()); - ASSERT_EQUALS("f0", e0.getFieldName()); - } - - TEST(TopologyBuilding, MoveNode) { - /* - [ e0 ] [ e0 ] - / \ / | \ - / \ / | \ - [ e1 ]..[ e2 ] [ e1 ]..[ e2 ]..[ e3 ] - / => / \ - / / \ - [ e3 ] [ e4 ]..[ e5 ] - / \ - / \ - [ e4 ]..[ e5 ] - */ - - mmb::Document doc; - - mmb::Element e0 = doc.makeElementObject("e0"); - mmb::Element e1 = doc.makeElementObject("e1"); - mmb::Element e2 = doc.makeElementObject("e2"); - mmb::Element e3 = doc.makeElementObject("e3"); - mmb::Element e4 = doc.makeElementObject("e4"); - mmb::Element e5 = doc.makeElementObject("e5"); - - ASSERT_EQUALS(e0.pushBack(e1), mongo::Status::OK()); - ASSERT_EQUALS(e0.pushBack(e2), mongo::Status::OK()); - ASSERT_EQUALS(e2.pushBack(e3), mongo::Status::OK()); - ASSERT_EQUALS(e3.pushBack(e4), mongo::Status::OK()); - ASSERT_EQUALS(e3.pushBack(e5), mongo::Status::OK()); - - ASSERT_EQUALS("e0", e0.getFieldName()); - ASSERT_EQUALS("e1", e0.leftChild().getFieldName()); - ASSERT_EQUALS("e2", e0.rightChild().getFieldName()); - ASSERT_EQUALS("e0", e1.parent().getFieldName()); - ASSERT_EQUALS("e0", e2.parent().getFieldName()); - ASSERT_EQUALS("e2", e1.rightSibling().getFieldName()); - ASSERT_EQUALS("e1", e2.leftSibling().getFieldName()); - ASSERT_EQUALS("e3", e2.leftChild().getFieldName()); - ASSERT_EQUALS("e3", e2.rightChild().getFieldName()); - ASSERT_EQUALS("e4", e3.leftChild().getFieldName()); - ASSERT_EQUALS("e5", e3.rightChild().getFieldName()); - ASSERT_EQUALS("e5", e4.rightSibling().getFieldName()); - ASSERT_EQUALS("e4", e5.leftSibling().getFieldName()); - - ASSERT_EQUALS(e3.remove(), mongo::Status::OK()); - ASSERT_EQUALS(e0.pushBack(e3), mongo::Status::OK()); - - ASSERT_EQUALS("e0", e3.parent().getFieldName()); - ASSERT_EQUALS("e1", e0.leftChild().getFieldName()); - ASSERT_EQUALS("e3", e0.rightChild().getFieldName()); - ASSERT_EQUALS("e3", e2.rightSibling().getFieldName()); - ASSERT_EQUALS("e2", e3.leftSibling().getFieldName()); - ASSERT_EQUALS("e4", e3.leftChild().getFieldName()); - ASSERT_EQUALS("e5", e3.rightChild().getFieldName()); - } - - TEST(TopologyBuilding, CantAddAttachedAsLeftSibling) { - mmb::Document doc; - ASSERT_OK(doc.root().appendString("foo", "foo")); - mmb::Element foo = doc.root().rightChild(); - ASSERT_TRUE(foo.ok()); - ASSERT_OK(doc.root().appendString("bar", "bar")); - mmb::Element bar = doc.root().rightChild(); - ASSERT_TRUE(bar.ok()); - ASSERT_NOT_OK(foo.addSiblingLeft(bar)); - } - - TEST(TopologyBuilding, CantAddAttachedAsRightSibling) { - mmb::Document doc; - ASSERT_OK(doc.root().appendString("foo", "foo")); - mmb::Element foo = doc.root().rightChild(); - ASSERT_TRUE(foo.ok()); - ASSERT_OK(doc.root().appendString("bar", "bar")); - mmb::Element bar = doc.root().rightChild(); - ASSERT_TRUE(bar.ok()); - ASSERT_NOT_OK(foo.addSiblingRight(bar)); - } - - TEST(TopologyBuilding, CantAddAttachedAsChild) { - mmb::Document doc; - mmb::Element foo = doc.makeElementObject("foo"); - ASSERT_TRUE(foo.ok()); - ASSERT_OK(doc.root().pushBack(foo)); - ASSERT_OK(doc.root().appendString("bar", "bar")); - mmb::Element bar = doc.root().rightChild(); - ASSERT_TRUE(bar.ok()); - ASSERT_NOT_OK(foo.pushFront(bar)); - ASSERT_NOT_OK(foo.pushBack(bar)); - } - - TEST(TopologyBuilding, CantAddChildrenToNonObject) { - mmb::Document doc; - ASSERT_OK(doc.root().appendString("foo", "bar")); - mmb::Element foo = doc.root().rightChild(); - ASSERT_TRUE(foo.ok()); - mmb::Element bar = doc.makeElementString("bar", "bar"); - ASSERT_TRUE(bar.ok()); - ASSERT_NOT_OK(foo.pushFront(bar)); - ASSERT_NOT_OK(foo.pushBack(bar)); - } - - TEST(TopologyBuilding, CantAddLeftSiblingToDetached) { - mmb::Document doc; - ASSERT_OK(doc.root().appendString("foo", "foo")); - mmb::Element foo = doc.root().rightChild(); - ASSERT_TRUE(foo.ok()); - ASSERT_OK(foo.remove()); - ASSERT_FALSE(foo.parent().ok()); - mmb::Element bar = doc.makeElementString("bar", "bar"); - ASSERT_TRUE(bar.ok()); - ASSERT_NOT_OK(foo.addSiblingLeft(bar)); - } - - TEST(TopologyBuilding, CantAddRightSiblingToDetached) { - mmb::Document doc; - ASSERT_OK(doc.root().appendString("foo", "foo")); - mmb::Element foo = doc.root().rightChild(); - ASSERT_TRUE(foo.ok()); - ASSERT_OK(foo.remove()); - ASSERT_FALSE(foo.parent().ok()); - mmb::Element bar = doc.makeElementString("bar", "bar"); - ASSERT_TRUE(bar.ok()); - ASSERT_NOT_OK(foo.addSiblingRight(bar)); - } - - TEST(TopologyBuilding, AddSiblingLeftIntrusion) { - mmb::Document doc; - ASSERT_OK(doc.root().appendString("first", "first")); - mmb::Element first = doc.root().rightChild(); - ASSERT_TRUE(first.ok()); - ASSERT_OK(doc.root().appendString("last", "last")); - mmb::Element last = doc.root().rightChild(); - ASSERT_TRUE(last.ok()); - - ASSERT_EQUALS(first, last.leftSibling()); - ASSERT_EQUALS(last, first.rightSibling()); - - mmb::Element middle = doc.makeElementString("middle", "middle"); - ASSERT_TRUE(middle.ok()); - ASSERT_OK(last.addSiblingLeft(middle)); - - ASSERT_EQUALS(middle, first.rightSibling()); - ASSERT_EQUALS(middle, last.leftSibling()); - - ASSERT_EQUALS(first, middle.leftSibling()); - ASSERT_EQUALS(last, middle.rightSibling()); - } - - TEST(TopologyBuilding, AddSiblingRightIntrusion) { - mmb::Document doc; - ASSERT_OK(doc.root().appendString("first", "first")); - mmb::Element first = doc.root().rightChild(); - ASSERT_TRUE(first.ok()); - ASSERT_OK(doc.root().appendString("last", "last")); - mmb::Element last = doc.root().rightChild(); - ASSERT_TRUE(last.ok()); - mmb::Element middle = doc.makeElementString("middle", "middle"); - ASSERT_TRUE(middle.ok()); - ASSERT_OK(first.addSiblingRight(middle)); - ASSERT_EQUALS(first, middle.leftSibling()); - ASSERT_EQUALS(last, middle.rightSibling()); - } - - TEST(ArrayAPI, SimpleNumericArray) { - /* - { a : [] } create - { a : [10] } pushBack - { a : [10, 20] } pushBack - { a : [10, 20, 30] } pushBack - { a : [10, 20, 30, 40] } pushBack - { a : [5, 10, 20, 30, 40] } pushFront - { a : [0, 5, 10, 20, 30, 40] } pushFront - { a : [0, 5, 10, 20, 30] } popBack - { a : [5, 10, 20, 30] } popFront - { a : [10, 20, 30] } popFront - { a : [10, 20] } popBack - { a : [20] } popFront - { a : [100] } set - { a : [] } popFront - */ - - mmb::Document doc; - - mmb::Element e1 = doc.makeElementArray("a"); - ASSERT_FALSE(e1[0].ok()); - ASSERT_FALSE(e1[1].ok()); - - mmb::Element e2 = doc.makeElementInt("", 10); - ASSERT_EQUALS(10, e2.getValueInt()); - mmb::Element e3 = doc.makeElementInt("", 20); - ASSERT_EQUALS(20, e3.getValueInt()); - mmb::Element e4 = doc.makeElementInt("", 30); - ASSERT_EQUALS(30, e4.getValueInt()); - mmb::Element e5 = doc.makeElementInt("", 40); - ASSERT_EQUALS(40, e5.getValueInt()); - mmb::Element e6 = doc.makeElementInt("", 5); - ASSERT_EQUALS(5, e6.getValueInt()); - mmb::Element e7 = doc.makeElementInt("", 0); - ASSERT_EQUALS(0, e7.getValueInt()); - - ASSERT_EQUALS(e1.pushBack(e2), mongo::Status::OK()); - ASSERT_EQUALS(e1.pushBack(e3), mongo::Status::OK()); - ASSERT_EQUALS(e1.pushBack(e4), mongo::Status::OK()); - ASSERT_EQUALS(e1.pushBack(e5), mongo::Status::OK()); - ASSERT_EQUALS(e1.pushFront(e6), mongo::Status::OK()); - ASSERT_EQUALS(e1.pushFront(e7), mongo::Status::OK()); - - ASSERT_EQUALS(size_t(6), mmb::countChildren(e1)); - ASSERT_EQUALS(0, e1[0].getValueInt()); - ASSERT_EQUALS(5, e1[1].getValueInt()); - ASSERT_EQUALS(10, e1[2].getValueInt()); - ASSERT_EQUALS(20, e1[3].getValueInt()); - ASSERT_EQUALS(30, e1[4].getValueInt()); - ASSERT_EQUALS(40, e1[5].getValueInt()); - ASSERT_EQUALS(40, e1.rightChild().getValueInt()); - ASSERT_EQUALS(e1.popBack(), mongo::Status::OK()); - - ASSERT_EQUALS(size_t(5), mmb::countChildren(e1)); - ASSERT_EQUALS(0, e1[0].getValueInt()); - ASSERT_EQUALS(5, e1[1].getValueInt()); - ASSERT_EQUALS(10, e1[2].getValueInt()); - ASSERT_EQUALS(20, e1[3].getValueInt()); - ASSERT_EQUALS(30, e1[4].getValueInt()); - ASSERT_EQUALS(0, e1.leftChild().getValueInt()); - ASSERT_EQUALS(e1.popFront(), mongo::Status::OK()); - - ASSERT_EQUALS(size_t(4), mmb::countChildren(e1)); - ASSERT_EQUALS(5, e1[0].getValueInt()); - ASSERT_EQUALS(10, e1[1].getValueInt()); - ASSERT_EQUALS(20, e1[2].getValueInt()); - ASSERT_EQUALS(30, e1[3].getValueInt()); - ASSERT_EQUALS(5, e1.leftChild().getValueInt()); - ASSERT_EQUALS(e1.popFront(), mongo::Status::OK()); - - ASSERT_EQUALS(size_t(3), mmb::countChildren(e1)); - ASSERT_EQUALS(10, e1[0].getValueInt()); - ASSERT_EQUALS(20, e1[1].getValueInt()); - ASSERT_EQUALS(30, e1[2].getValueInt()); - ASSERT_EQUALS(30, e1.rightChild().getValueInt()); - ASSERT_EQUALS(e1.popBack(), mongo::Status::OK()); - - ASSERT_EQUALS(size_t(2), mmb::countChildren(e1)); - ASSERT_EQUALS(10, e1[0].getValueInt()); - ASSERT_EQUALS(20, e1[1].getValueInt()); - ASSERT_EQUALS(10, e1.leftChild().getValueInt()); - ASSERT_EQUALS(e1.popFront(), mongo::Status::OK()); - - ASSERT_EQUALS(size_t(1), mmb::countChildren(e1)); - ASSERT_EQUALS(20, e1[0].getValueInt()); - - ASSERT_EQUALS(e1[0].setValueInt(100), mongo::Status::OK()); - ASSERT_EQUALS(100, e1[0].getValueInt()); - ASSERT_EQUALS(100, e1.leftChild().getValueInt()); - ASSERT_EQUALS(size_t(1), mmb::countChildren(e1)); - ASSERT_EQUALS(e1.popFront(), mongo::Status::OK()); - - ASSERT_EQUALS(size_t(0), mmb::countChildren(e1)); - ASSERT_FALSE(e1[0].ok()); - ASSERT_FALSE(e1[1].ok()); - } - - TEST(Element, setters) { - mmb::Document doc; - - mmb::Element t0 = doc.makeElementNull("t0"); - - t0.setValueBool(true); - ASSERT_EQUALS(mongo::Bool, t0.getType()); - - t0.setValueInt(12345); - ASSERT_EQUALS(mongo::NumberInt, t0.getType()); - - t0.setValueLong(12345LL); - ASSERT_EQUALS(mongo::NumberLong, t0.getType()); - - t0.setValueTimestamp(mongo::Timestamp()); - ASSERT_EQUALS(mongo::bsonTimestamp, t0.getType()); - - t0.setValueDate(mongo::Date_t::fromMillisSinceEpoch(12345LL)); - ASSERT_EQUALS(mongo::Date, t0.getType()); - - t0.setValueDouble(123.45); - ASSERT_EQUALS(mongo::NumberDouble, t0.getType()); - - t0.setValueOID(mongo::OID("47cc67093475061e3d95369d")); - ASSERT_EQUALS(mongo::jstOID, t0.getType()); - - t0.setValueRegex("[a-zA-Z]?", ""); - ASSERT_EQUALS(mongo::RegEx, t0.getType()); - - t0.setValueString("foo bar baz"); - ASSERT_EQUALS(mongo::String, t0.getType()); - } - - TEST(Element, toString) { - mongo::BSONObj obj = mongo::fromjson("{ a : 1, b : [1, 2, 3], c : { x : 'x' } }"); - mmb::Document doc(obj); - - // Deserialize the 'c' but keep its value the same. - mmb::Element c = doc.root().rightChild(); - ASSERT_TRUE(c.ok()); - ASSERT_OK(c.appendString("y", "y")); - ASSERT_OK(c.popBack()); - - // 'a' - mongo::BSONObjIterator iter(obj); - mmb::Element docChild = doc.root().leftChild(); - ASSERT_TRUE(docChild.ok()); - ASSERT_EQUALS(iter.next().toString(), docChild.toString()); - - // 'b' - docChild = docChild.rightSibling(); - ASSERT_TRUE(docChild.ok()); - ASSERT_TRUE(iter.more()); - ASSERT_EQUALS(iter.next().toString(), mmb::ConstElement(docChild).toString()); - - // 'c' - docChild = docChild.rightSibling(); - ASSERT_TRUE(docChild.ok()); - ASSERT_TRUE(iter.more()); - ASSERT_EQUALS(iter.next().toString(), docChild.toString()); - - // eoo - docChild = docChild.rightSibling(); - ASSERT_FALSE(iter.more()); - ASSERT_FALSE(docChild.ok()); - } - - TEST(TimestampType, createElement) { - mmb::Document doc; - - mmb::Element t0 = doc.makeElementTimestamp("t0", mongo::Timestamp()); - ASSERT(mongo::Timestamp() == t0.getValueTimestamp()); - - mmb::Element t1 = doc.makeElementTimestamp("t1", mongo::Timestamp(123, 456)); - ASSERT(mongo::Timestamp(123, 456) == t1.getValueTimestamp()); - } - - TEST(TimestampType, setElement) { - mmb::Document doc; - - mmb::Element t0 = doc.makeElementTimestamp("t0", mongo::Timestamp()); - t0.setValueTimestamp(mongo::Timestamp(123, 456)); - ASSERT(mongo::Timestamp(123, 456) == t0.getValueTimestamp()); - - // Try setting to other types and back to Timestamp - t0.setValueLong(1234567890); - ASSERT_EQUALS(1234567890LL, t0.getValueLong()); - t0.setValueTimestamp(mongo::Timestamp(789, 321)); - ASSERT(mongo::Timestamp(789, 321) == t0.getValueTimestamp()); - - t0.setValueString("foo bar baz"); - ASSERT_EQUALS("foo bar baz", t0.getValueString()); - t0.setValueTimestamp(mongo::Timestamp(9876, 5432)); - ASSERT(mongo::Timestamp(9876, 5432) == t0.getValueTimestamp()); - } - - TEST(TimestampType, appendElement) { - mmb::Document doc; - - mmb::Element t0 = doc.makeElementObject("e0"); - t0.appendTimestamp("a timestamp field", mongo::Timestamp(1352151971, 471)); - - mmb::Element it = - mmb::findFirstChildNamed(t0, "a timestamp field"); - ASSERT_TRUE(it.ok()); - ASSERT(mongo::Timestamp(1352151971, 471) == it.getValueTimestamp()); - } - - TEST(SafeNumType, createElement) { - mmb::Document doc; - - mmb::Element t0 = doc.makeElementSafeNum("t0", mongo::SafeNum(123.456)); - ASSERT_EQUALS(mongo::SafeNum(123.456), t0.getValueSafeNum()); - } - - // Try getting SafeNums from different types. - TEST(SafeNumType, getSafeNum) { - mmb::Document doc; - - mmb::Element t0 = doc.makeElementInt("t0", 1234567890); - ASSERT_EQUALS(1234567890, t0.getValueInt()); - mongo::SafeNum num = t0.getValueSafeNum(); - ASSERT_EQUALS(num, 1234567890); - - t0.setValueLong(1234567890LL); - ASSERT_EQUALS(1234567890LL, t0.getValueLong()); - num = t0.getValueSafeNum(); - ASSERT_EQUALS(num, 1234567890LL); - - t0.setValueDouble(123.456789); - ASSERT_EQUALS(123.456789, t0.getValueDouble()); - num = t0.getValueSafeNum(); - ASSERT_EQUALS(num, 123.456789); - } - - TEST(SafeNumType, setSafeNum) { - mmb::Document doc; - - mmb::Element t0 = doc.makeElementSafeNum("t0", mongo::SafeNum(123456)); - t0.setValueSafeNum(mongo::SafeNum(654321)); - ASSERT_EQUALS(mongo::SafeNum(654321), t0.getValueSafeNum()); - - // Try setting to other types and back to SafeNum - t0.setValueLong(1234567890); - ASSERT_EQUALS(1234567890LL, t0.getValueLong()); - t0.setValueSafeNum(mongo::SafeNum(1234567890)); - ASSERT_EQUALS(mongo::SafeNum(1234567890), t0.getValueSafeNum()); - - t0.setValueString("foo bar baz"); - - mongo::StringData left = "foo bar baz"; - mongo::StringData right = t0.getValueString(); - ASSERT_EQUALS(left, right); - - ASSERT_EQUALS(mongo::StringData("foo bar baz"), t0.getValueString()); - t0.setValueSafeNum(mongo::SafeNum(12345)); - ASSERT_EQUALS(mongo::SafeNum(12345), t0.getValueSafeNum()); - } - - TEST(SafeNumType, appendElement) { - mmb::Document doc; - - mmb::Element t0 = doc.makeElementObject("e0"); - t0.appendSafeNum("a timestamp field", mongo::SafeNum(1352151971LL)); - - mmb::Element it = findFirstChildNamed(t0, "a timestamp field"); - ASSERT_TRUE(it.ok()); - ASSERT_EQUALS(mongo::SafeNum(1352151971LL), it.getValueSafeNum()); - } - - TEST(OIDType, getOidValue) { - mmb::Document doc; - mmb::Element t0 = doc.makeElementObject("e0"); - const mongo::OID generated = mongo::OID::gen(); - t0.appendOID("myOid", generated); - mmb::Element it = findFirstChildNamed(t0, "myOid"); - const mongo::OID recovered = mongo::OID(it.getValueOID()); - ASSERT_EQUALS(generated, recovered); - } - - TEST(OIDType, nullOID) { - mmb::Document doc; - mmb::Element t0 = doc.makeElementObject("e0"); - const mongo::OID withNull("50a9c82263e413ad0028faad"); - t0.appendOID("myOid", withNull); - mmb::Element it = findFirstChildNamed(t0, "myOid"); - const mongo::OID recovered = mongo::OID(it.getValueOID()); - ASSERT_EQUALS(withNull, recovered); - } - - static const char jsonSample[] = - "{_id:ObjectId(\"47cc67093475061e3d95369d\")," - "query:\"kate hudson\"," - "owner:1234567887654321," - "date:\"2011-05-13T14:22:46.777Z\"," - "score:123.456," - "field1:Infinity," - "\"field2\":-Infinity," - "\"field3\":NaN," - "users:[" - "{uname:\"@aaaa\",editid:\"123\",date:1303959350,yes_votes:0,no_votes:0}," - "{uname:\"@bbbb\",editid:\"456\",date:1303959350,yes_votes:0,no_votes:0}," - "{uname:\"@cccc\",editid:\"789\",date:1303959350,yes_votes:0,no_votes:0}]," - "pattern:/match.*this/," - "lastfield:\"last\"}"; - - TEST(Serialization, RoundTrip) { - mongo::BSONObj obj = mongo::fromjson(jsonSample); - mmb::Document doc(obj.copy()); - mongo::BSONObj built = doc.getObject(); - ASSERT_EQUALS(obj, built); - } - - TEST(Documentation, Example1) { - - // Create a new document - mmb::Document doc; - ASSERT_EQUALS(mongo::fromjson("{}"), - doc.getObject()); - - // Get the root of the document. - mmb::Element root = doc.root(); - - // Create a new mongo::NumberInt typed Element to represent life, the universe, and - // everything, then push that Element into the root object, making it a child of root. - mmb::Element e0 = doc.makeElementInt("ltuae", 42); - ASSERT_OK(root.pushBack(e0)); - ASSERT_EQUALS(mongo::fromjson("{ ltuae : 42 }"), - doc.getObject()); - - // Create a new empty mongo::Object-typed Element named 'magic', and push it back as a - // child of the root, making it a sibling of e0. - mmb::Element e1 = doc.makeElementObject("magic"); - ASSERT_OK(root.pushBack(e1)); - ASSERT_EQUALS(mongo::fromjson("{ ltuae : 42, magic : {} }"), - doc.getObject()); - - // Create a new mongo::NumberDouble typed Element to represent Pi, and insert it as child - // of the new object we just created. - mmb::Element e3 = doc.makeElementDouble("pi", 3.14); - ASSERT_OK(e1.pushBack(e3)); - ASSERT_EQUALS(mongo::fromjson("{ ltuae : 42, magic : { pi : 3.14 } }"), - doc.getObject()); - - // Create a new mongo::NumberDouble to represent Plancks constant in electrovolt - // micrometers, and add it as a child of the 'magic' object. - mmb::Element e4 = doc.makeElementDouble("hbar", 1.239); - ASSERT_OK(e1.pushBack(e4)); - ASSERT_EQUALS(mongo::fromjson("{ ltuae : 42, magic : { pi : 3.14, hbar : 1.239 } }"), - doc.getObject()); - - // Rename the parent element of 'hbar' to be 'constants'. - ASSERT_OK(e4.parent().rename("constants")); - ASSERT_EQUALS(mongo::fromjson("{ ltuae : 42, constants : { pi : 3.14, hbar : 1.239 } }"), - doc.getObject()); - - // Rename 'ltuae' to 'answer' by accessing it as the root objects left child. - ASSERT_OK(doc.root().leftChild().rename("answer")); - ASSERT_EQUALS(mongo::fromjson("{ answer : 42, constants : { pi : 3.14, hbar : 1.239 } }"), - doc.getObject()); - - // Sort the constants by name. - mmb::sortChildren(doc.root().rightChild(), mmb::FieldNameLessThan()); - ASSERT_EQUALS(mongo::fromjson("{ answer : 42, constants : { hbar : 1.239, pi : 3.14 } }"), - doc.getObject()); - } - - TEST(Documentation, Example2) { - - static const char inJson[] = - "{" - " 'whale': { 'alive': true, 'dv': -9.8, 'height': 50.0, attrs : [ 'big' ] }," - " 'petunias': { 'alive': true, 'dv': -9.8, 'height': 50.0 } " - "}"; - mongo::BSONObj obj = mongo::fromjson(inJson); - - // Create a new document representing BSONObj with the above contents. - mmb::Document doc(obj); - - // The whale hits the planet and dies. - mmb::Element whale = mmb::findFirstChildNamed(doc.root(), "whale"); - ASSERT_TRUE(whale.ok()); - // Find the 'dv' field in the whale. - mmb::Element whale_deltav = mmb::findFirstChildNamed(whale, "dv"); - ASSERT_TRUE(whale_deltav.ok()); - // Set the dv field to zero. - ASSERT_OK(whale_deltav.setValueDouble(0.0)); - // Find the 'height' field in the whale. - mmb::Element whale_height = mmb::findFirstChildNamed(whale, "height"); - ASSERT_TRUE(whale_height.ok()); - // Set the height field to zero. - ASSERT_OK(whale_height.setValueDouble(0)); - // Find the 'alive' field, and set it to false. - mmb::Element whale_alive = mmb::findFirstChildNamed(whale, "alive"); - ASSERT_TRUE(whale_alive.ok()); - ASSERT_OK(whale_alive.setValueBool(false)); - - // The petunias survive, update its fields much like we did above. - mmb::Element petunias = mmb::findFirstChildNamed(doc.root(), "petunias"); - ASSERT_TRUE(petunias.ok()); - mmb::Element petunias_deltav = mmb::findFirstChildNamed(petunias, "dv"); - ASSERT_TRUE(petunias_deltav.ok()); - ASSERT_OK(petunias_deltav.setValueDouble(0.0)); - mmb::Element petunias_height = mmb::findFirstChildNamed(petunias, "height"); - ASSERT_TRUE(petunias_height.ok()); - ASSERT_OK(petunias_height.setValueDouble(0)); - - // Replace the whale by its wreckage, saving only its attributes: - // Construct a new mongo::Object element for the ex-whale. - mmb::Element ex_whale = doc.makeElementObject("ex-whale"); - ASSERT_OK(doc.root().pushBack(ex_whale)); - // Find the attributes of the old 'whale' element. - mmb::Element whale_attrs = mmb::findFirstChildNamed(whale, "attrs"); - // Remove the attributes from the whale (they remain valid, but detached). - ASSERT_OK(whale_attrs.remove()); - // Insert the attributes into the ex-whale. - ASSERT_OK(ex_whale.pushBack(whale_attrs)); - // Remove the whale object. - ASSERT_OK(whale.remove()); - - static const char outJson[] = - "{" - " 'petunias': { 'alive': true, 'dv': 0.0, 'height': 0 }," - " 'ex-whale': { 'attrs': [ 'big' ] } })" - "}"; - - mongo::BSONObjBuilder builder; - doc.writeTo(&builder); - ASSERT_EQUALS(mongo::fromjson(outJson), doc.getObject()); - } - - namespace { - void apply(mongo::BSONObj* obj, const mmb::DamageVector& damages, const char* source) { - const mmb::DamageVector::const_iterator end = damages.end(); - mmb::DamageVector::const_iterator where = damages.begin(); - char* const target = const_cast<char *>(obj->objdata()); - for ( ; where != end; ++where ) { - std::memcpy( - target + where->targetOffset, - source + where->sourceOffset, - where->size); - } - } - } // namespace - - TEST(Documentation, Example2InPlaceWithDamageVector) { - - static const char inJson[] = - "{" - " 'whale': { 'alive': true, 'dv': -9.8, 'height': 50.0, attrs : [ 'big' ] }," - " 'petunias': { 'alive': true, 'dv': -9.8, 'height': 50.0 } " - "}"; - - // Make the object, and make a copy for reference. - mongo::BSONObj obj = mongo::fromjson(inJson); - const mongo::BSONObj copyOfObj = obj.getOwned(); - ASSERT_EQUALS(obj, copyOfObj); - - // Create a new document representing BSONObj with the above contents. - mmb::Document doc(obj, mmb::Document::kInPlaceEnabled); - ASSERT_EQUALS(obj, doc); - ASSERT_EQUALS(copyOfObj, doc); - - // Enable in-place mutation for this document - ASSERT_EQUALS(mmb::Document::kInPlaceEnabled, doc.getCurrentInPlaceMode()); - - // The whale hits the planet and dies. - mmb::Element whale = mmb::findFirstChildNamed(doc.root(), "whale"); - ASSERT_TRUE(whale.ok()); - // Find the 'dv' field in the whale. - mmb::Element whale_deltav = mmb::findFirstChildNamed(whale, "dv"); - ASSERT_TRUE(whale_deltav.ok()); - // Set the dv field to zero. - ASSERT_OK(whale_deltav.setValueDouble(0.0)); - // Find the 'height' field in the whale. - mmb::Element whale_height = mmb::findFirstChildNamed(whale, "height"); - ASSERT_TRUE(whale_height.ok()); - // Set the height field to zero. - ASSERT_OK(whale_height.setValueDouble(0)); - // Find the 'alive' field, and set it to false. - mmb::Element whale_alive = mmb::findFirstChildNamed(whale, "alive"); - ASSERT_TRUE(whale_alive.ok()); - ASSERT_OK(whale_alive.setValueBool(false)); - - // The petunias survive, update its fields much like we did above. - mmb::Element petunias = mmb::findFirstChildNamed(doc.root(), "petunias"); - ASSERT_TRUE(petunias.ok()); - mmb::Element petunias_deltav = mmb::findFirstChildNamed(petunias, "dv"); - ASSERT_TRUE(petunias_deltav.ok()); - ASSERT_OK(petunias_deltav.setValueDouble(0.0)); - mmb::Element petunias_height = mmb::findFirstChildNamed(petunias, "height"); - ASSERT_TRUE(petunias_height.ok()); - ASSERT_OK(petunias_height.setValueDouble(0)); - - // Demonstrate that while the document has changed, the underlying BSONObj has not yet - // changed. - ASSERT_FALSE(obj == doc); - ASSERT_EQUALS(copyOfObj, obj); - - // Ensure that in-place updates are still enabled. - ASSERT_EQUALS(mmb::Document::kInPlaceEnabled, doc.getCurrentInPlaceMode()); - - // Extract the damage events - mmb::DamageVector damages; - const char* source = NULL; - size_t size = 0; - ASSERT_EQUALS(true, doc.getInPlaceUpdates(&damages, &source, &size)); - ASSERT_NOT_EQUALS(0U, damages.size()); - ASSERT_NOT_EQUALS(static_cast<const char*>(NULL), source); - ASSERT_NOT_EQUALS(0U, size); - - apply(&obj, damages, source); - - static const char outJson[] = - "{" - " 'whale': { 'alive': false, 'dv': 0, 'height': 0, attrs : [ 'big' ] }," - " 'petunias': { 'alive': true, 'dv': 0, 'height': 0 } " - "}"; - mongo::BSONObj outObj = mongo::fromjson(outJson); - - ASSERT_EQUALS(outObj, doc); - - mongo::BSONObjBuilder builder; - doc.writeTo(&builder); - ASSERT_EQUALS(mongo::fromjson(outJson), doc.getObject()); - } - - TEST(Documentation, Example3) { - static const char inJson[] = - "{" - " 'xs': { 'x' : 'x', 'X' : 'X' }," - " 'ys': { 'y' : 'y' }" - "}"; - mongo::BSONObj inObj = mongo::fromjson(inJson); - - mmb::Document doc(inObj); - mmb::Element xs = doc.root().leftChild(); - ASSERT_TRUE(xs.ok()); - mmb::Element ys = xs.rightSibling(); - ASSERT_TRUE(ys.ok()); - mmb::Element dne = ys.rightSibling(); - ASSERT_FALSE(dne.ok()); - mmb::Element ycaps = doc.makeElementString("Y", "Y"); - ASSERT_OK(ys.pushBack(ycaps)); - mmb::Element pun = doc.makeElementArray("why"); - ASSERT_OK(ys.pushBack(pun)); - pun.appendString("na", "not"); - mongo::BSONObj outObj = doc.getObject(); - - static const char outJson[] = - "{" - " 'xs': { 'x' : 'x', 'X' : 'X' }," - " 'ys': { 'y' : 'y', 'Y' : 'Y', 'why' : ['not'] }" - "}"; - ASSERT_EQUALS(mongo::fromjson(outJson), outObj); - } - - TEST(Document, LifecycleConstructDefault) { - // Verify the state of a newly created empty Document. - mmb::Document doc; - ASSERT_TRUE(doc.root().ok()); - ASSERT_TRUE(const_cast<const mmb::Document&>(doc).root().ok()); - ASSERT_TRUE(doc.root().isType(mongo::Object)); - ASSERT_FALSE(doc.root().leftSibling().ok()); - ASSERT_FALSE(doc.root().rightSibling().ok()); - ASSERT_FALSE(doc.root().leftChild().ok()); - ASSERT_FALSE(doc.root().rightChild().ok()); - ASSERT_FALSE(doc.root().parent().ok()); - ASSERT_FALSE(doc.root().hasValue()); - } - - TEST(Document, LifecycleConstructEmptyBSONObj) { - // Verify the state of a newly created empty Document where the construction argument - // is an empty BSONObj. - mongo::BSONObj obj; - mmb::Document doc(obj); - ASSERT_TRUE(doc.root().ok()); - ASSERT_TRUE(const_cast<const mmb::Document&>(doc).root().ok()); - ASSERT_TRUE(doc.root().isType(mongo::Object)); - ASSERT_FALSE(doc.root().leftSibling().ok()); - ASSERT_FALSE(doc.root().rightSibling().ok()); - ASSERT_FALSE(doc.root().leftChild().ok()); - ASSERT_FALSE(doc.root().rightChild().ok()); - ASSERT_FALSE(doc.root().parent().ok()); - ASSERT_FALSE(doc.root().hasValue()); - } - - TEST(Document, LifecycleConstructSimpleBSONObj) { - // Verify the state of a newly created Document where the construction argument is a - // simple (flat) BSONObj. - mongo::BSONObj obj = mongo::fromjson("{ e1: 1, e2: 'hello', e3: false }"); - mmb::Document doc(obj); - - // Check the state of the root. - ASSERT_TRUE(doc.root().ok()); - ASSERT_TRUE(const_cast<const mmb::Document&>(doc).root().ok()); - ASSERT_TRUE(doc.root().isType(mongo::Object)); - ASSERT_FALSE(doc.root().parent().ok()); - ASSERT_FALSE(doc.root().leftSibling().ok()); - ASSERT_FALSE(doc.root().rightSibling().ok()); - ASSERT_FALSE(doc.root().hasValue()); - - mmb::ConstElement e1Child = doc.root().leftChild(); - // Check the connectivity of 'e1'. - ASSERT_TRUE(e1Child.ok()); - ASSERT_EQUALS(doc.root(), e1Child.parent()); - ASSERT_FALSE(e1Child.leftSibling().ok()); - ASSERT_TRUE(e1Child.rightSibling().ok()); - ASSERT_FALSE(e1Child.leftChild().ok()); - ASSERT_FALSE(e1Child.rightChild().ok()); - - // Check the type, name, and value of 'e1'. - ASSERT_TRUE(e1Child.isType(mongo::NumberInt)); - ASSERT_EQUALS("e1", e1Child.getFieldName()); - ASSERT_TRUE(e1Child.hasValue()); - ASSERT_EQUALS(int32_t(1), e1Child.getValueInt()); - - mmb::ConstElement e2Child = e1Child.rightSibling(); - // Check the connectivity of 'e2'. - ASSERT_TRUE(e2Child.ok()); - ASSERT_EQUALS(doc.root(), e2Child.parent()); - ASSERT_TRUE(e2Child.leftSibling().ok()); - ASSERT_TRUE(e2Child.rightSibling().ok()); - ASSERT_EQUALS(e1Child, e2Child.leftSibling()); - ASSERT_FALSE(e2Child.leftChild().ok()); - ASSERT_FALSE(e2Child.rightChild().ok()); - - // Check the type, name and value of 'e2'. - ASSERT_TRUE(e2Child.isType(mongo::String)); - ASSERT_EQUALS("e2", e2Child.getFieldName()); - ASSERT_TRUE(e2Child.hasValue()); - ASSERT_EQUALS("hello", e2Child.getValueString()); - - mmb::ConstElement e3Child = e2Child.rightSibling(); - // Check the connectivity of 'e3'. - ASSERT_TRUE(e3Child.ok()); - ASSERT_EQUALS(doc.root(), e3Child.parent()); - ASSERT_TRUE(e3Child.leftSibling().ok()); - ASSERT_FALSE(e3Child.rightSibling().ok()); - ASSERT_EQUALS(e2Child, e3Child.leftSibling()); - ASSERT_FALSE(e2Child.leftChild().ok()); - ASSERT_FALSE(e2Child.rightChild().ok()); - - // Check the type, name and value of 'e3'. - ASSERT_TRUE(e3Child.isType(mongo::Bool)); - ASSERT_EQUALS("e3", e3Child.getFieldName()); - ASSERT_TRUE(e3Child.hasValue()); - ASSERT_EQUALS(false, e3Child.getValueBool()); - } - - TEST(Document, RenameDeserialization) { - // Regression test for a bug where certain rename operations failed to deserialize up - // the tree correctly, resulting in a lost rename - static const char inJson[] = - "{" - " 'a' : { 'b' : { 'c' : { 'd' : 4 } } }" - "}"; - mongo::BSONObj inObj = mongo::fromjson(inJson); - - mmb::Document doc(inObj); - mmb::Element a = doc.root().leftChild(); - ASSERT_TRUE(a.ok()); - mmb::Element b = a.leftChild(); - ASSERT_TRUE(b.ok()); - mmb::Element c = b.leftChild(); - ASSERT_TRUE(c.ok()); - c.rename("C"); - mongo::BSONObj outObj = doc.getObject(); - static const char outJson[] = - "{" - " 'a' : { 'b' : { 'C' : { 'd' : 4 } } }" - "}"; - ASSERT_EQUALS(mongo::fromjson(outJson), outObj); - } - - TEST(Document, CantRenameRootElement) { - mmb::Document doc; - ASSERT_NOT_OK(doc.root().rename("foo")); - } - - TEST(Document, RemoveElementWithOpaqueRightSibling) { - // Regression test for a bug where removing an element with an opaque right sibling - // would access an invalidated rep. Note that this test may or may not fail depending - // on the details of memory allocation: failures would be clearly visible with - // valgrind, however. - static const char inJson[] = - "{" - " 'a' : 1, 'b' : 2, 'c' : 3" - "}"; - - mongo::BSONObj inObj = mongo::fromjson(inJson); - mmb::Document doc(inObj); - - mmb::Element a = doc.root().leftChild(); - ASSERT_TRUE(a.ok()); - a.remove(); - - static const char outJson[] = - "{" - " 'b' : 2, 'c' : 3" - "}"; - mongo::BSONObj outObj = doc.getObject(); - ASSERT_EQUALS(mongo::fromjson(outJson), outObj); - } - - TEST(Document, AddRightSiblingToElementWithOpaqueRightSibling) { - // Regression test for a bug where adding a right sibling to a node with an opaque - // right sibling would potentially access an invalidated rep. Like the 'remove' test - // above, this may or may not crash, but would be visible under a memory checking tool. - static const char inJson[] = - "{" - " 'a' : 1, 'b' : 2, 'c' : 3" - "}"; - - mongo::BSONObj inObj = mongo::fromjson(inJson); - mmb::Document doc(inObj); - - mmb::Element a = doc.root().leftChild(); - ASSERT_TRUE(a.ok()); - mmb::Element newElt = doc.makeElementString("X", "X"); - ASSERT_OK(a.addSiblingRight(newElt)); - - static const char outJson[] = - "{" - " 'a' : 1, 'X' : 'X', 'b' : 2, 'c' : 3" - "}"; - mongo::BSONObj outObj = doc.getObject(); - ASSERT_EQUALS(mongo::fromjson(outJson), outObj); - } - - TEST(Document, ArrayIndexedAccessFromJson) { - static const char inJson[] = - "{" - " a : 1, b : [{ c : 1 }]" - "}"; - - mongo::BSONObj inObj = mongo::fromjson(inJson); - mmb::Document doc(inObj); - - mmb::Element a = doc.root().leftChild(); - ASSERT_TRUE(a.ok()); - ASSERT_EQUALS("a", a.getFieldName()); - ASSERT_EQUALS(mongo::NumberInt, a.getType()); - - mmb::Element b = a.rightSibling(); - ASSERT_TRUE(b.ok()); - ASSERT_EQUALS("b", b.getFieldName()); - ASSERT_EQUALS(mongo::Array, b.getType()); - - mmb::Element b0 = b[0]; - ASSERT_TRUE(b0.ok()); - ASSERT_EQUALS("0", b0.getFieldName()); - ASSERT_EQUALS(mongo::Object, b0.getType()); - } - - TEST(Document, ArrayIndexedAccessFromManuallyBuilt) { - mmb::Document doc; - mmb::Element root = doc.root(); - ASSERT_TRUE(root.ok()); - { - ASSERT_OK(root.appendInt("a", 1)); - mmb::Element b = doc.makeElementArray("b"); - ASSERT_TRUE(b.ok()); - ASSERT_OK(root.pushBack(b)); - mmb::Element b0 = doc.makeElementObject("ignored"); - ASSERT_TRUE(b0.ok()); - ASSERT_OK(b.pushBack(b0)); - ASSERT_OK(b0.appendInt("c", 1)); - } - - mmb::Element a = doc.root().leftChild(); - ASSERT_TRUE(a.ok()); - ASSERT_EQUALS("a", a.getFieldName()); - ASSERT_EQUALS(mongo::NumberInt, a.getType()); - - mmb::Element b = a.rightSibling(); - ASSERT_TRUE(b.ok()); - ASSERT_EQUALS("b", b.getFieldName()); - ASSERT_EQUALS(mongo::Array, b.getType()); - - mmb::Element b0 = b[0]; - ASSERT_TRUE(b0.ok()); - ASSERT_EQUALS("ignored", b0.getFieldName()); - ASSERT_EQUALS(mongo::Object, b0.getType()); - } - - TEST(Document, EndElement) { - mmb::Document doc; - mmb::Element end = doc.end(); - ASSERT_FALSE(end.ok()); - mmb::Element missing = doc.root().leftChild(); - ASSERT_EQUALS(end, missing); - missing = doc.root().rightChild(); - ASSERT_EQUALS(end, missing); - missing = doc.root().leftSibling(); - ASSERT_EQUALS(end, missing); - missing = doc.root().rightSibling(); - ASSERT_EQUALS(end, missing); - } - - TEST(Document, ConstEndElement) { - const mmb::Document doc; - mmb::ConstElement end = doc.end(); - ASSERT_FALSE(end.ok()); - mmb::ConstElement missing = doc.root().leftChild(); - ASSERT_EQUALS(end, missing); - missing = doc.root().rightChild(); - ASSERT_EQUALS(end, missing); - missing = doc.root().leftSibling(); - ASSERT_EQUALS(end, missing); - missing = doc.root().rightSibling(); - ASSERT_EQUALS(end, missing); - } - - TEST(Element, EmptyDocHasNoChildren) { - mmb::Document doc; - ASSERT_FALSE(doc.root().hasChildren()); - } - - TEST(Element, PopulatedDocHasChildren) { - mmb::Document doc; - ASSERT_OK(doc.root().appendInt("a", 1)); - ASSERT_TRUE(doc.root().hasChildren()); - mmb::Element lc = doc.root().leftChild(); - ASSERT_FALSE(lc.hasChildren()); - } - - TEST(Element, LazyEmptyDocHasNoChildren) { - static const char inJson[] = "{}"; - mongo::BSONObj inObj = mongo::fromjson(inJson); - mmb::Document doc(inObj); - ASSERT_FALSE(doc.root().hasChildren()); - } - - TEST(Element, LazySingletonDocHasChildren) { - static const char inJson[] = "{ a : 1 }"; - mongo::BSONObj inObj = mongo::fromjson(inJson); - mmb::Document doc(inObj); - ASSERT_TRUE(doc.root().hasChildren()); - ASSERT_FALSE(doc.root().leftChild().hasChildren()); - } - - TEST(Element, LazyConstDoubletonDocHasChildren) { - static const char inJson[] = "{ a : 1, b : 2 }"; - mongo::BSONObj inObj = mongo::fromjson(inJson); - const mmb::Document doc(inObj); - ASSERT_TRUE(doc.root().hasChildren()); - ASSERT_FALSE(doc.root().leftChild().hasChildren()); - ASSERT_FALSE(doc.root().rightChild().hasChildren()); - ASSERT_FALSE(doc.root().leftChild() == doc.root().rightChild()); - } - - TEST(Document, AddChildToEmptyOpaqueSubobject) { - mongo::BSONObj inObj = mongo::fromjson("{a: {}}"); - mmb::Document doc(inObj); - - mmb::Element elem = doc.root()["a"]; - ASSERT_TRUE(elem.ok()); - - mmb::Element newElem = doc.makeElementInt("0", 1); - ASSERT_TRUE(newElem.ok()); - - ASSERT_OK(elem.pushBack(newElem)); - } - - TEST(Element, IsNumeric) { - mmb::Document doc; - - mmb::Element elt = doc.makeElementNull("dummy"); - ASSERT_FALSE(elt.isNumeric()); - - elt = doc.makeElementInt("dummy", 42); - ASSERT_TRUE(elt.isNumeric()); - - elt = doc.makeElementString("dummy", "dummy"); - ASSERT_FALSE(elt.isNumeric()); - - elt = doc.makeElementLong("dummy", 42); - ASSERT_TRUE(elt.isNumeric()); - - elt = doc.makeElementBool("dummy", false); - ASSERT_FALSE(elt.isNumeric()); - - elt = doc.makeElementDouble("dummy", 42.0); - ASSERT_TRUE(elt.isNumeric()); - } - - TEST(Element, IsIntegral) { - mmb::Document doc; - - mmb::Element elt = doc.makeElementNull("dummy"); - ASSERT_FALSE(elt.isIntegral()); - - elt = doc.makeElementInt("dummy", 42); - ASSERT_TRUE(elt.isIntegral()); - - elt = doc.makeElementString("dummy", "dummy"); - ASSERT_FALSE(elt.isIntegral()); - - elt = doc.makeElementLong("dummy", 42); - ASSERT_TRUE(elt.isIntegral()); - - elt = doc.makeElementDouble("dummy", 42.0); - ASSERT_FALSE(elt.isIntegral()); - } - - TEST(Document, ArraySerialization) { - - static const char inJson[] = - "{ " - " 'a' : { 'b' : [ 'c', 'd' ] } " - "}"; - - mongo::BSONObj inObj = mongo::fromjson(inJson); - mmb::Document doc(inObj); - - mmb::Element root = doc.root(); - mmb::Element a = root.leftChild(); - mmb::Element b = a.leftChild(); - mmb::Element new_array = doc.makeElementArray("XXX"); - mmb::Element e = doc.makeElementString("e", "e"); - new_array.pushBack(e); - b.pushBack(new_array); - - static const char outJson[] = - "{ " - " 'a' : { 'b' : [ 'c', 'd', [ 'e' ] ] } " - "}"; - - const mongo::BSONObj outObj = doc.getObject(); - ASSERT_EQUALS(mongo::fromjson(outJson), outObj); - } - - TEST(Document, SetValueBSONElementFieldNameHandling) { - static const char inJson[] = "{ a : 4 }"; - mongo::BSONObj inObj = mongo::fromjson(inJson); - mmb::Document doc(inObj); - - static const char inJson2[] = "{ b : 5 }"; - mongo::BSONObj inObj2 = mongo::fromjson(inJson2); - mongo::BSONObjIterator iterator = inObj2.begin(); - - ASSERT_TRUE(iterator.more()); - const mongo::BSONElement b = iterator.next(); - - mmb::Element a = doc.root().leftChild(); - a.setValueBSONElement(b); - - static const char outJson[] = "{ a : 5 }"; - ASSERT_EQUALS(mongo::fromjson(outJson), doc.getObject()); - } - - TEST(Document, CreateElementWithEmptyFieldName) { - mmb::Document doc; - mmb::Element noname = doc.makeElementObject(mongo::StringData()); - ASSERT_TRUE(noname.ok()); - ASSERT_EQUALS(mongo::StringData(), noname.getFieldName()); - } - - TEST(Document, CreateElementFromBSONElement) { - mongo::BSONObj obj = mongo::fromjson("{a:1}}"); - mmb::Document doc; - ASSERT_OK(doc.root().appendElement(obj["a"])); - - mmb::Element newElem = doc.root()["a"]; - ASSERT_TRUE(newElem.ok()); - ASSERT_EQUALS(newElem.getType(), mongo::NumberInt); - ASSERT_EQUALS(newElem.getValueInt(), 1); - } - - TEST(Document, toStringEmpty) { - mongo::BSONObj obj; - mmb::Document doc; - ASSERT_EQUALS(obj.toString(), doc.toString()); - } - - TEST(Document, toStringComplex) { - mongo::BSONObj obj = mongo::fromjson("{a : 1, b : [1, 2, 3], c : 'c'}"); - mmb::Document doc(obj); - ASSERT_EQUALS(obj.toString(), doc.toString()); - } - - TEST(Document, toStringEphemeralObject) { - mmb::Document doc; - mmb::Element e = doc.makeElementObject("foo"); - ASSERT_OK(doc.root().pushBack(e)); - ASSERT_OK(e.appendDouble("d", 1.0)); - ASSERT_OK(e.appendString("s", "str")); - ASSERT_EQUALS( - mongo::fromjson("{ foo: { d : 1.0, s : 'str' } }").firstElement().toString(), - e.toString()); - } - - TEST(Document, toStringEphemeralArray) { - mmb::Document doc; - mmb::Element e = doc.makeElementArray("foo"); - ASSERT_OK(doc.root().pushBack(e)); - ASSERT_OK(e.appendDouble(mongo::StringData(), 1.0)); - ASSERT_OK(e.appendString(mongo::StringData(), "str")); - ASSERT_EQUALS( - mongo::fromjson("{ foo: [ 1.0, 'str' ] }").firstElement().toString(), - e.toString()); - } - - TEST(Document, ElementCloningToDifferentDocument) { - - const char initial[] = "{ a : 1, b : [ 1, 2, 3 ], c : { 'c' : 'c' }, d : [ 4, 5, 6 ] }"; - - mmb::Document source(mongo::fromjson(initial)); - - // Dirty the 'd' node and parents. - source.root()["d"].pushBack(source.makeElementInt(mongo::StringData(), 7)); - - mmb::Document target; - - mmb::Element newElement = target.makeElement(source.root()["d"]); - ASSERT_TRUE(newElement.ok()); - mongo::Status status = target.root().pushBack(newElement); - ASSERT_OK(status); - const char* expected = - "{ d : [ 4, 5, 6, 7 ] }"; - ASSERT_EQUALS(mongo::fromjson(expected), target); - - newElement = target.makeElement(source.root()["b"]); - ASSERT_TRUE(newElement.ok()); - status = target.root().pushBack(newElement); - ASSERT_OK(status); - expected = - "{ d : [ 4, 5, 6, 7 ], b : [ 1, 2, 3 ] }"; - ASSERT_EQUALS(mongo::fromjson(expected), target); - - newElement = target.makeElementWithNewFieldName("C", source.root()["c"]); - ASSERT_TRUE(newElement.ok()); - status = target.root().pushBack(newElement); - ASSERT_OK(status); - expected = - "{ d : [ 4, 5, 6, 7 ], b : [ 1, 2, 3 ], C : { 'c' : 'c' } }"; - ASSERT_EQUALS(mongo::fromjson(expected), target); - } - - TEST(Document, ElementCloningToSameDocument) { - - const char initial[] = "{ a : 1, b : [ 1, 2, 3 ], c : { 'c' : 'c' }, d : [ 4, 5, 6 ] }"; - - mmb::Document doc(mongo::fromjson(initial)); - - // Dirty the 'd' node and parents. - doc.root()["d"].pushBack(doc.makeElementInt(mongo::StringData(), 7)); - - mmb::Element newElement = doc.makeElement(doc.root()["d"]); - ASSERT_TRUE(newElement.ok()); - mongo::Status status = doc.root().pushBack(newElement); - ASSERT_OK(status); - const char* expected = - "{ " - " a : 1, b : [ 1, 2, 3 ], c : { 'c' : 'c' }, d : [ 4, 5, 6, 7 ], " - " d : [ 4, 5, 6, 7 ] " - "}"; - ASSERT_EQUALS(mongo::fromjson(expected), doc); - - newElement = doc.makeElement(doc.root()["b"]); - ASSERT_TRUE(newElement.ok()); - status = doc.root().pushBack(newElement); - ASSERT_OK(status); - expected = - "{ " - " a : 1, b : [ 1, 2, 3 ], c : { 'c' : 'c' }, d : [ 4, 5, 6, 7 ], " - " d : [ 4, 5, 6, 7 ], " - " b : [ 1, 2, 3 ] " - "}"; - ASSERT_EQUALS(mongo::fromjson(expected), doc); - - newElement = doc.makeElementWithNewFieldName("C", doc.root()["c"]); - ASSERT_TRUE(newElement.ok()); - status = doc.root().pushBack(newElement); - ASSERT_OK(status); - expected = - "{ " - " a : 1, b : [ 1, 2, 3 ], c : { 'c' : 'c' }, d : [ 4, 5, 6, 7 ], " - " d : [ 4, 5, 6, 7 ], " - " b : [ 1, 2, 3 ], " - " C : { 'c' : 'c' } " - "}"; - ASSERT_EQUALS(mongo::fromjson(expected), doc); - } + [ e3 ]..[ e4 ] + / | \ + / | \ + [ e7 ]..[ e5 ]..[ e6 ] + */ + + mmb::Document doc; + + mmb::Element e2 = doc.makeElementObject("e2"); + mmb::Element e3 = doc.makeElementObject("e3"); + mmb::Element e4 = doc.makeElementObject("e4"); + mmb::Element e5 = doc.makeElementObject("e5"); + mmb::Element e6 = doc.makeElementObject("e6"); + mmb::Element e7 = doc.makeElementObject("e7"); + + ASSERT_EQUALS(e2.pushBack(e3), mongo::Status::OK()); + ASSERT_EQUALS(e2.pushBack(e4), mongo::Status::OK()); + ASSERT_EQUALS(e3.pushBack(e5), mongo::Status::OK()); + ASSERT_EQUALS(e5.addSiblingRight(e6), mongo::Status::OK()); + ASSERT_EQUALS(e5.addSiblingLeft(e7), mongo::Status::OK()); + + ASSERT_EQUALS("e5", e7.rightSibling().getFieldName()); + ASSERT_EQUALS("e7", e5.leftSibling().getFieldName()); + ASSERT_EQUALS("e6", e5.rightSibling().getFieldName()); + ASSERT_EQUALS("e5", e6.leftSibling().getFieldName()); + + ASSERT_EQUALS("e3", e5.parent().getFieldName()); + ASSERT_EQUALS("e3", e6.parent().getFieldName()); + ASSERT_EQUALS("e3", e7.parent().getFieldName()); + ASSERT_EQUALS("e7", e3.leftChild().getFieldName()); + ASSERT_EQUALS("e6", e3.rightChild().getFieldName()); +} + +TEST(TopologyBuilding, AddSubtreeBottomUp) { + /* + [ e3 ] + / | \ + / | \ + [ e4 ]..[ e5 ]..[ e6 ] + | + | + [ e8 ] + / \ + / \ + [ e9 ]..[ e10] + */ + mmb::Document doc; + + mmb::Element e3 = doc.makeElementObject("e3"); + mmb::Element e4 = doc.makeElementObject("e4"); + mmb::Element e5 = doc.makeElementObject("e5"); + mmb::Element e6 = doc.makeElementObject("e6"); + mmb::Element e8 = doc.makeElementObject("e8"); + mmb::Element e9 = doc.makeElementObject("e9"); + mmb::Element e10 = doc.makeElementObject("e10"); + + ASSERT_EQUALS(e3.pushBack(e4), mongo::Status::OK()); + ASSERT_EQUALS(e3.pushBack(e5), mongo::Status::OK()); + ASSERT_EQUALS(e3.pushBack(e6), mongo::Status::OK()); + ASSERT_EQUALS(e8.pushBack(e9), mongo::Status::OK()); + ASSERT_EQUALS(e8.pushBack(e10), mongo::Status::OK()); + ASSERT_EQUALS(e5.pushBack(e8), mongo::Status::OK()); + + ASSERT_EQUALS("e8", e9.parent().getFieldName()); + ASSERT_EQUALS("e8", e10.parent().getFieldName()); + ASSERT_EQUALS("e9", e8.leftChild().getFieldName()); + ASSERT_EQUALS("e10", e8.rightChild().getFieldName()); + ASSERT_EQUALS("e9", e10.leftSibling().getFieldName()); + ASSERT_EQUALS("e10", e9.rightSibling().getFieldName()); + ASSERT_EQUALS("e5", e8.parent().getFieldName()); + ASSERT_EQUALS("e8", e5.leftChild().getFieldName()); + ASSERT_EQUALS("e8", e5.rightChild().getFieldName()); +} + +TEST(TopologyBuilding, RemoveLeafNode) { + /* + [ e0 ] [ e0 ] + / \ => \ + / \ \ + [ e1 ] [ e2 ] [ e2 ] + */ + mmb::Document doc; + + mmb::Element e0 = doc.makeElementObject("e0"); + mmb::Element e1 = doc.makeElementObject("e1"); + mmb::Element e2 = doc.makeElementObject("e2"); + + ASSERT_EQUALS(e0.pushBack(e1), mongo::Status::OK()); + ASSERT_EQUALS(e0.pushBack(e2), mongo::Status::OK()); + ASSERT_EQUALS(e1.remove(), mongo::Status::OK()); + + ASSERT_EQUALS("e2", e0.leftChild().getFieldName()); + ASSERT_EQUALS("e2", e0.rightChild().getFieldName()); +} + +TEST(TopologyBuilding, RemoveSubtree) { + /* + [ e3 ] [ e3 ] + / | \ / \ + / | \ / \ + [ e4 ]..[ e5 ]..[ e6 ] [ e4 ] [ e6 ] + | => + | + [ e8 ] + / \ + / \ + [ e9 ]..[ e10] + */ + mmb::Document doc; + + mmb::Element e3 = doc.makeElementObject("e3"); + mmb::Element e4 = doc.makeElementObject("e4"); + mmb::Element e5 = doc.makeElementObject("e5"); + mmb::Element e6 = doc.makeElementObject("e6"); + mmb::Element e8 = doc.makeElementObject("e8"); + mmb::Element e9 = doc.makeElementObject("e9"); + mmb::Element e10 = doc.makeElementObject("e10"); + + ASSERT_EQUALS(e3.pushBack(e4), mongo::Status::OK()); + ASSERT_EQUALS(e3.pushBack(e5), mongo::Status::OK()); + ASSERT_EQUALS(e5.addSiblingRight(e6), mongo::Status::OK()); + + ASSERT_EQUALS(e8.pushBack(e9), mongo::Status::OK()); + ASSERT_EQUALS(e8.pushBack(e10), mongo::Status::OK()); + ASSERT_EQUALS(e5.pushBack(e8), mongo::Status::OK()); + ASSERT_EQUALS(e5.remove(), mongo::Status::OK()); + + ASSERT_EQUALS("e3", e4.parent().getFieldName()); + ASSERT_EQUALS("e3", e6.parent().getFieldName()); + ASSERT_EQUALS("e4", e3.leftChild().getFieldName()); + ASSERT_EQUALS("e6", e3.rightChild().getFieldName()); +} + +TEST(TopologyBuilding, RenameNode) { + /* + + [ e0 ] [ f0 ] + / \ => / \ + / \ / \ + [ e1 ]..[ e2 ] [ e1 ]..[ e2 ] + */ + + mmb::Document doc; + + mmb::Element e0 = doc.makeElementObject("e0"); + mmb::Element e1 = doc.makeElementObject("e1"); + mmb::Element e2 = doc.makeElementObject("e2"); + + ASSERT_EQUALS(e0.pushBack(e1), mongo::Status::OK()); + ASSERT_EQUALS(e0.pushBack(e2), mongo::Status::OK()); + ASSERT_EQUALS(e0.rename("f0"), mongo::Status::OK()); + ASSERT_EQUALS("f0", e0.getFieldName()); +} + +TEST(TopologyBuilding, MoveNode) { + /* + [ e0 ] [ e0 ] + / \ / | \ + / \ / | \ + [ e1 ]..[ e2 ] [ e1 ]..[ e2 ]..[ e3 ] + / => / \ + / / \ + [ e3 ] [ e4 ]..[ e5 ] + / \ + / \ + [ e4 ]..[ e5 ] + */ + + mmb::Document doc; + + mmb::Element e0 = doc.makeElementObject("e0"); + mmb::Element e1 = doc.makeElementObject("e1"); + mmb::Element e2 = doc.makeElementObject("e2"); + mmb::Element e3 = doc.makeElementObject("e3"); + mmb::Element e4 = doc.makeElementObject("e4"); + mmb::Element e5 = doc.makeElementObject("e5"); + + ASSERT_EQUALS(e0.pushBack(e1), mongo::Status::OK()); + ASSERT_EQUALS(e0.pushBack(e2), mongo::Status::OK()); + ASSERT_EQUALS(e2.pushBack(e3), mongo::Status::OK()); + ASSERT_EQUALS(e3.pushBack(e4), mongo::Status::OK()); + ASSERT_EQUALS(e3.pushBack(e5), mongo::Status::OK()); + + ASSERT_EQUALS("e0", e0.getFieldName()); + ASSERT_EQUALS("e1", e0.leftChild().getFieldName()); + ASSERT_EQUALS("e2", e0.rightChild().getFieldName()); + ASSERT_EQUALS("e0", e1.parent().getFieldName()); + ASSERT_EQUALS("e0", e2.parent().getFieldName()); + ASSERT_EQUALS("e2", e1.rightSibling().getFieldName()); + ASSERT_EQUALS("e1", e2.leftSibling().getFieldName()); + ASSERT_EQUALS("e3", e2.leftChild().getFieldName()); + ASSERT_EQUALS("e3", e2.rightChild().getFieldName()); + ASSERT_EQUALS("e4", e3.leftChild().getFieldName()); + ASSERT_EQUALS("e5", e3.rightChild().getFieldName()); + ASSERT_EQUALS("e5", e4.rightSibling().getFieldName()); + ASSERT_EQUALS("e4", e5.leftSibling().getFieldName()); + + ASSERT_EQUALS(e3.remove(), mongo::Status::OK()); + ASSERT_EQUALS(e0.pushBack(e3), mongo::Status::OK()); + + ASSERT_EQUALS("e0", e3.parent().getFieldName()); + ASSERT_EQUALS("e1", e0.leftChild().getFieldName()); + ASSERT_EQUALS("e3", e0.rightChild().getFieldName()); + ASSERT_EQUALS("e3", e2.rightSibling().getFieldName()); + ASSERT_EQUALS("e2", e3.leftSibling().getFieldName()); + ASSERT_EQUALS("e4", e3.leftChild().getFieldName()); + ASSERT_EQUALS("e5", e3.rightChild().getFieldName()); +} + +TEST(TopologyBuilding, CantAddAttachedAsLeftSibling) { + mmb::Document doc; + ASSERT_OK(doc.root().appendString("foo", "foo")); + mmb::Element foo = doc.root().rightChild(); + ASSERT_TRUE(foo.ok()); + ASSERT_OK(doc.root().appendString("bar", "bar")); + mmb::Element bar = doc.root().rightChild(); + ASSERT_TRUE(bar.ok()); + ASSERT_NOT_OK(foo.addSiblingLeft(bar)); +} + +TEST(TopologyBuilding, CantAddAttachedAsRightSibling) { + mmb::Document doc; + ASSERT_OK(doc.root().appendString("foo", "foo")); + mmb::Element foo = doc.root().rightChild(); + ASSERT_TRUE(foo.ok()); + ASSERT_OK(doc.root().appendString("bar", "bar")); + mmb::Element bar = doc.root().rightChild(); + ASSERT_TRUE(bar.ok()); + ASSERT_NOT_OK(foo.addSiblingRight(bar)); +} + +TEST(TopologyBuilding, CantAddAttachedAsChild) { + mmb::Document doc; + mmb::Element foo = doc.makeElementObject("foo"); + ASSERT_TRUE(foo.ok()); + ASSERT_OK(doc.root().pushBack(foo)); + ASSERT_OK(doc.root().appendString("bar", "bar")); + mmb::Element bar = doc.root().rightChild(); + ASSERT_TRUE(bar.ok()); + ASSERT_NOT_OK(foo.pushFront(bar)); + ASSERT_NOT_OK(foo.pushBack(bar)); +} + +TEST(TopologyBuilding, CantAddChildrenToNonObject) { + mmb::Document doc; + ASSERT_OK(doc.root().appendString("foo", "bar")); + mmb::Element foo = doc.root().rightChild(); + ASSERT_TRUE(foo.ok()); + mmb::Element bar = doc.makeElementString("bar", "bar"); + ASSERT_TRUE(bar.ok()); + ASSERT_NOT_OK(foo.pushFront(bar)); + ASSERT_NOT_OK(foo.pushBack(bar)); +} + +TEST(TopologyBuilding, CantAddLeftSiblingToDetached) { + mmb::Document doc; + ASSERT_OK(doc.root().appendString("foo", "foo")); + mmb::Element foo = doc.root().rightChild(); + ASSERT_TRUE(foo.ok()); + ASSERT_OK(foo.remove()); + ASSERT_FALSE(foo.parent().ok()); + mmb::Element bar = doc.makeElementString("bar", "bar"); + ASSERT_TRUE(bar.ok()); + ASSERT_NOT_OK(foo.addSiblingLeft(bar)); +} + +TEST(TopologyBuilding, CantAddRightSiblingToDetached) { + mmb::Document doc; + ASSERT_OK(doc.root().appendString("foo", "foo")); + mmb::Element foo = doc.root().rightChild(); + ASSERT_TRUE(foo.ok()); + ASSERT_OK(foo.remove()); + ASSERT_FALSE(foo.parent().ok()); + mmb::Element bar = doc.makeElementString("bar", "bar"); + ASSERT_TRUE(bar.ok()); + ASSERT_NOT_OK(foo.addSiblingRight(bar)); +} + +TEST(TopologyBuilding, AddSiblingLeftIntrusion) { + mmb::Document doc; + ASSERT_OK(doc.root().appendString("first", "first")); + mmb::Element first = doc.root().rightChild(); + ASSERT_TRUE(first.ok()); + ASSERT_OK(doc.root().appendString("last", "last")); + mmb::Element last = doc.root().rightChild(); + ASSERT_TRUE(last.ok()); + + ASSERT_EQUALS(first, last.leftSibling()); + ASSERT_EQUALS(last, first.rightSibling()); + + mmb::Element middle = doc.makeElementString("middle", "middle"); + ASSERT_TRUE(middle.ok()); + ASSERT_OK(last.addSiblingLeft(middle)); + + ASSERT_EQUALS(middle, first.rightSibling()); + ASSERT_EQUALS(middle, last.leftSibling()); + + ASSERT_EQUALS(first, middle.leftSibling()); + ASSERT_EQUALS(last, middle.rightSibling()); +} + +TEST(TopologyBuilding, AddSiblingRightIntrusion) { + mmb::Document doc; + ASSERT_OK(doc.root().appendString("first", "first")); + mmb::Element first = doc.root().rightChild(); + ASSERT_TRUE(first.ok()); + ASSERT_OK(doc.root().appendString("last", "last")); + mmb::Element last = doc.root().rightChild(); + ASSERT_TRUE(last.ok()); + mmb::Element middle = doc.makeElementString("middle", "middle"); + ASSERT_TRUE(middle.ok()); + ASSERT_OK(first.addSiblingRight(middle)); + ASSERT_EQUALS(first, middle.leftSibling()); + ASSERT_EQUALS(last, middle.rightSibling()); +} + +TEST(ArrayAPI, SimpleNumericArray) { + /* + { a : [] } create + { a : [10] } pushBack + { a : [10, 20] } pushBack + { a : [10, 20, 30] } pushBack + { a : [10, 20, 30, 40] } pushBack + { a : [5, 10, 20, 30, 40] } pushFront + { a : [0, 5, 10, 20, 30, 40] } pushFront + { a : [0, 5, 10, 20, 30] } popBack + { a : [5, 10, 20, 30] } popFront + { a : [10, 20, 30] } popFront + { a : [10, 20] } popBack + { a : [20] } popFront + { a : [100] } set + { a : [] } popFront + */ + + mmb::Document doc; + + mmb::Element e1 = doc.makeElementArray("a"); + ASSERT_FALSE(e1[0].ok()); + ASSERT_FALSE(e1[1].ok()); + + mmb::Element e2 = doc.makeElementInt("", 10); + ASSERT_EQUALS(10, e2.getValueInt()); + mmb::Element e3 = doc.makeElementInt("", 20); + ASSERT_EQUALS(20, e3.getValueInt()); + mmb::Element e4 = doc.makeElementInt("", 30); + ASSERT_EQUALS(30, e4.getValueInt()); + mmb::Element e5 = doc.makeElementInt("", 40); + ASSERT_EQUALS(40, e5.getValueInt()); + mmb::Element e6 = doc.makeElementInt("", 5); + ASSERT_EQUALS(5, e6.getValueInt()); + mmb::Element e7 = doc.makeElementInt("", 0); + ASSERT_EQUALS(0, e7.getValueInt()); + + ASSERT_EQUALS(e1.pushBack(e2), mongo::Status::OK()); + ASSERT_EQUALS(e1.pushBack(e3), mongo::Status::OK()); + ASSERT_EQUALS(e1.pushBack(e4), mongo::Status::OK()); + ASSERT_EQUALS(e1.pushBack(e5), mongo::Status::OK()); + ASSERT_EQUALS(e1.pushFront(e6), mongo::Status::OK()); + ASSERT_EQUALS(e1.pushFront(e7), mongo::Status::OK()); + + ASSERT_EQUALS(size_t(6), mmb::countChildren(e1)); + ASSERT_EQUALS(0, e1[0].getValueInt()); + ASSERT_EQUALS(5, e1[1].getValueInt()); + ASSERT_EQUALS(10, e1[2].getValueInt()); + ASSERT_EQUALS(20, e1[3].getValueInt()); + ASSERT_EQUALS(30, e1[4].getValueInt()); + ASSERT_EQUALS(40, e1[5].getValueInt()); + ASSERT_EQUALS(40, e1.rightChild().getValueInt()); + ASSERT_EQUALS(e1.popBack(), mongo::Status::OK()); + + ASSERT_EQUALS(size_t(5), mmb::countChildren(e1)); + ASSERT_EQUALS(0, e1[0].getValueInt()); + ASSERT_EQUALS(5, e1[1].getValueInt()); + ASSERT_EQUALS(10, e1[2].getValueInt()); + ASSERT_EQUALS(20, e1[3].getValueInt()); + ASSERT_EQUALS(30, e1[4].getValueInt()); + ASSERT_EQUALS(0, e1.leftChild().getValueInt()); + ASSERT_EQUALS(e1.popFront(), mongo::Status::OK()); + + ASSERT_EQUALS(size_t(4), mmb::countChildren(e1)); + ASSERT_EQUALS(5, e1[0].getValueInt()); + ASSERT_EQUALS(10, e1[1].getValueInt()); + ASSERT_EQUALS(20, e1[2].getValueInt()); + ASSERT_EQUALS(30, e1[3].getValueInt()); + ASSERT_EQUALS(5, e1.leftChild().getValueInt()); + ASSERT_EQUALS(e1.popFront(), mongo::Status::OK()); + + ASSERT_EQUALS(size_t(3), mmb::countChildren(e1)); + ASSERT_EQUALS(10, e1[0].getValueInt()); + ASSERT_EQUALS(20, e1[1].getValueInt()); + ASSERT_EQUALS(30, e1[2].getValueInt()); + ASSERT_EQUALS(30, e1.rightChild().getValueInt()); + ASSERT_EQUALS(e1.popBack(), mongo::Status::OK()); + + ASSERT_EQUALS(size_t(2), mmb::countChildren(e1)); + ASSERT_EQUALS(10, e1[0].getValueInt()); + ASSERT_EQUALS(20, e1[1].getValueInt()); + ASSERT_EQUALS(10, e1.leftChild().getValueInt()); + ASSERT_EQUALS(e1.popFront(), mongo::Status::OK()); + + ASSERT_EQUALS(size_t(1), mmb::countChildren(e1)); + ASSERT_EQUALS(20, e1[0].getValueInt()); + + ASSERT_EQUALS(e1[0].setValueInt(100), mongo::Status::OK()); + ASSERT_EQUALS(100, e1[0].getValueInt()); + ASSERT_EQUALS(100, e1.leftChild().getValueInt()); + ASSERT_EQUALS(size_t(1), mmb::countChildren(e1)); + ASSERT_EQUALS(e1.popFront(), mongo::Status::OK()); + + ASSERT_EQUALS(size_t(0), mmb::countChildren(e1)); + ASSERT_FALSE(e1[0].ok()); + ASSERT_FALSE(e1[1].ok()); +} + +TEST(Element, setters) { + mmb::Document doc; + + mmb::Element t0 = doc.makeElementNull("t0"); + + t0.setValueBool(true); + ASSERT_EQUALS(mongo::Bool, t0.getType()); + + t0.setValueInt(12345); + ASSERT_EQUALS(mongo::NumberInt, t0.getType()); + + t0.setValueLong(12345LL); + ASSERT_EQUALS(mongo::NumberLong, t0.getType()); + + t0.setValueTimestamp(mongo::Timestamp()); + ASSERT_EQUALS(mongo::bsonTimestamp, t0.getType()); + + t0.setValueDate(mongo::Date_t::fromMillisSinceEpoch(12345LL)); + ASSERT_EQUALS(mongo::Date, t0.getType()); + + t0.setValueDouble(123.45); + ASSERT_EQUALS(mongo::NumberDouble, t0.getType()); + + t0.setValueOID(mongo::OID("47cc67093475061e3d95369d")); + ASSERT_EQUALS(mongo::jstOID, t0.getType()); + + t0.setValueRegex("[a-zA-Z]?", ""); + ASSERT_EQUALS(mongo::RegEx, t0.getType()); + + t0.setValueString("foo bar baz"); + ASSERT_EQUALS(mongo::String, t0.getType()); +} + +TEST(Element, toString) { + mongo::BSONObj obj = mongo::fromjson("{ a : 1, b : [1, 2, 3], c : { x : 'x' } }"); + mmb::Document doc(obj); + + // Deserialize the 'c' but keep its value the same. + mmb::Element c = doc.root().rightChild(); + ASSERT_TRUE(c.ok()); + ASSERT_OK(c.appendString("y", "y")); + ASSERT_OK(c.popBack()); + + // 'a' + mongo::BSONObjIterator iter(obj); + mmb::Element docChild = doc.root().leftChild(); + ASSERT_TRUE(docChild.ok()); + ASSERT_EQUALS(iter.next().toString(), docChild.toString()); + + // 'b' + docChild = docChild.rightSibling(); + ASSERT_TRUE(docChild.ok()); + ASSERT_TRUE(iter.more()); + ASSERT_EQUALS(iter.next().toString(), mmb::ConstElement(docChild).toString()); + + // 'c' + docChild = docChild.rightSibling(); + ASSERT_TRUE(docChild.ok()); + ASSERT_TRUE(iter.more()); + ASSERT_EQUALS(iter.next().toString(), docChild.toString()); + + // eoo + docChild = docChild.rightSibling(); + ASSERT_FALSE(iter.more()); + ASSERT_FALSE(docChild.ok()); +} + +TEST(TimestampType, createElement) { + mmb::Document doc; + + mmb::Element t0 = doc.makeElementTimestamp("t0", mongo::Timestamp()); + ASSERT(mongo::Timestamp() == t0.getValueTimestamp()); + + mmb::Element t1 = doc.makeElementTimestamp("t1", mongo::Timestamp(123, 456)); + ASSERT(mongo::Timestamp(123, 456) == t1.getValueTimestamp()); +} + +TEST(TimestampType, setElement) { + mmb::Document doc; + + mmb::Element t0 = doc.makeElementTimestamp("t0", mongo::Timestamp()); + t0.setValueTimestamp(mongo::Timestamp(123, 456)); + ASSERT(mongo::Timestamp(123, 456) == t0.getValueTimestamp()); + + // Try setting to other types and back to Timestamp + t0.setValueLong(1234567890); + ASSERT_EQUALS(1234567890LL, t0.getValueLong()); + t0.setValueTimestamp(mongo::Timestamp(789, 321)); + ASSERT(mongo::Timestamp(789, 321) == t0.getValueTimestamp()); + + t0.setValueString("foo bar baz"); + ASSERT_EQUALS("foo bar baz", t0.getValueString()); + t0.setValueTimestamp(mongo::Timestamp(9876, 5432)); + ASSERT(mongo::Timestamp(9876, 5432) == t0.getValueTimestamp()); +} + +TEST(TimestampType, appendElement) { + mmb::Document doc; + + mmb::Element t0 = doc.makeElementObject("e0"); + t0.appendTimestamp("a timestamp field", mongo::Timestamp(1352151971, 471)); + + mmb::Element it = mmb::findFirstChildNamed(t0, "a timestamp field"); + ASSERT_TRUE(it.ok()); + ASSERT(mongo::Timestamp(1352151971, 471) == it.getValueTimestamp()); +} + +TEST(SafeNumType, createElement) { + mmb::Document doc; + + mmb::Element t0 = doc.makeElementSafeNum("t0", mongo::SafeNum(123.456)); + ASSERT_EQUALS(mongo::SafeNum(123.456), t0.getValueSafeNum()); +} + +// Try getting SafeNums from different types. +TEST(SafeNumType, getSafeNum) { + mmb::Document doc; + + mmb::Element t0 = doc.makeElementInt("t0", 1234567890); + ASSERT_EQUALS(1234567890, t0.getValueInt()); + mongo::SafeNum num = t0.getValueSafeNum(); + ASSERT_EQUALS(num, 1234567890); + + t0.setValueLong(1234567890LL); + ASSERT_EQUALS(1234567890LL, t0.getValueLong()); + num = t0.getValueSafeNum(); + ASSERT_EQUALS(num, 1234567890LL); + + t0.setValueDouble(123.456789); + ASSERT_EQUALS(123.456789, t0.getValueDouble()); + num = t0.getValueSafeNum(); + ASSERT_EQUALS(num, 123.456789); +} + +TEST(SafeNumType, setSafeNum) { + mmb::Document doc; + + mmb::Element t0 = doc.makeElementSafeNum("t0", mongo::SafeNum(123456)); + t0.setValueSafeNum(mongo::SafeNum(654321)); + ASSERT_EQUALS(mongo::SafeNum(654321), t0.getValueSafeNum()); + + // Try setting to other types and back to SafeNum + t0.setValueLong(1234567890); + ASSERT_EQUALS(1234567890LL, t0.getValueLong()); + t0.setValueSafeNum(mongo::SafeNum(1234567890)); + ASSERT_EQUALS(mongo::SafeNum(1234567890), t0.getValueSafeNum()); + + t0.setValueString("foo bar baz"); + + mongo::StringData left = "foo bar baz"; + mongo::StringData right = t0.getValueString(); + ASSERT_EQUALS(left, right); + + ASSERT_EQUALS(mongo::StringData("foo bar baz"), t0.getValueString()); + t0.setValueSafeNum(mongo::SafeNum(12345)); + ASSERT_EQUALS(mongo::SafeNum(12345), t0.getValueSafeNum()); +} + +TEST(SafeNumType, appendElement) { + mmb::Document doc; + + mmb::Element t0 = doc.makeElementObject("e0"); + t0.appendSafeNum("a timestamp field", mongo::SafeNum(1352151971LL)); + + mmb::Element it = findFirstChildNamed(t0, "a timestamp field"); + ASSERT_TRUE(it.ok()); + ASSERT_EQUALS(mongo::SafeNum(1352151971LL), it.getValueSafeNum()); +} + +TEST(OIDType, getOidValue) { + mmb::Document doc; + mmb::Element t0 = doc.makeElementObject("e0"); + const mongo::OID generated = mongo::OID::gen(); + t0.appendOID("myOid", generated); + mmb::Element it = findFirstChildNamed(t0, "myOid"); + const mongo::OID recovered = mongo::OID(it.getValueOID()); + ASSERT_EQUALS(generated, recovered); +} + +TEST(OIDType, nullOID) { + mmb::Document doc; + mmb::Element t0 = doc.makeElementObject("e0"); + const mongo::OID withNull("50a9c82263e413ad0028faad"); + t0.appendOID("myOid", withNull); + mmb::Element it = findFirstChildNamed(t0, "myOid"); + const mongo::OID recovered = mongo::OID(it.getValueOID()); + ASSERT_EQUALS(withNull, recovered); +} + +static const char jsonSample[] = + "{_id:ObjectId(\"47cc67093475061e3d95369d\")," + "query:\"kate hudson\"," + "owner:1234567887654321," + "date:\"2011-05-13T14:22:46.777Z\"," + "score:123.456," + "field1:Infinity," + "\"field2\":-Infinity," + "\"field3\":NaN," + "users:[" + "{uname:\"@aaaa\",editid:\"123\",date:1303959350,yes_votes:0,no_votes:0}," + "{uname:\"@bbbb\",editid:\"456\",date:1303959350,yes_votes:0,no_votes:0}," + "{uname:\"@cccc\",editid:\"789\",date:1303959350,yes_votes:0,no_votes:0}]," + "pattern:/match.*this/," + "lastfield:\"last\"}"; + +TEST(Serialization, RoundTrip) { + mongo::BSONObj obj = mongo::fromjson(jsonSample); + mmb::Document doc(obj.copy()); + mongo::BSONObj built = doc.getObject(); + ASSERT_EQUALS(obj, built); +} + +TEST(Documentation, Example1) { + // Create a new document + mmb::Document doc; + ASSERT_EQUALS(mongo::fromjson("{}"), doc.getObject()); + + // Get the root of the document. + mmb::Element root = doc.root(); + + // Create a new mongo::NumberInt typed Element to represent life, the universe, and + // everything, then push that Element into the root object, making it a child of root. + mmb::Element e0 = doc.makeElementInt("ltuae", 42); + ASSERT_OK(root.pushBack(e0)); + ASSERT_EQUALS(mongo::fromjson("{ ltuae : 42 }"), doc.getObject()); + + // Create a new empty mongo::Object-typed Element named 'magic', and push it back as a + // child of the root, making it a sibling of e0. + mmb::Element e1 = doc.makeElementObject("magic"); + ASSERT_OK(root.pushBack(e1)); + ASSERT_EQUALS(mongo::fromjson("{ ltuae : 42, magic : {} }"), doc.getObject()); + + // Create a new mongo::NumberDouble typed Element to represent Pi, and insert it as child + // of the new object we just created. + mmb::Element e3 = doc.makeElementDouble("pi", 3.14); + ASSERT_OK(e1.pushBack(e3)); + ASSERT_EQUALS(mongo::fromjson("{ ltuae : 42, magic : { pi : 3.14 } }"), doc.getObject()); + + // Create a new mongo::NumberDouble to represent Plancks constant in electrovolt + // micrometers, and add it as a child of the 'magic' object. + mmb::Element e4 = doc.makeElementDouble("hbar", 1.239); + ASSERT_OK(e1.pushBack(e4)); + ASSERT_EQUALS(mongo::fromjson("{ ltuae : 42, magic : { pi : 3.14, hbar : 1.239 } }"), + doc.getObject()); + + // Rename the parent element of 'hbar' to be 'constants'. + ASSERT_OK(e4.parent().rename("constants")); + ASSERT_EQUALS(mongo::fromjson("{ ltuae : 42, constants : { pi : 3.14, hbar : 1.239 } }"), + doc.getObject()); + + // Rename 'ltuae' to 'answer' by accessing it as the root objects left child. + ASSERT_OK(doc.root().leftChild().rename("answer")); + ASSERT_EQUALS(mongo::fromjson("{ answer : 42, constants : { pi : 3.14, hbar : 1.239 } }"), + doc.getObject()); + + // Sort the constants by name. + mmb::sortChildren(doc.root().rightChild(), mmb::FieldNameLessThan()); + ASSERT_EQUALS(mongo::fromjson("{ answer : 42, constants : { hbar : 1.239, pi : 3.14 } }"), + doc.getObject()); +} + +TEST(Documentation, Example2) { + static const char inJson[] = + "{" + " 'whale': { 'alive': true, 'dv': -9.8, 'height': 50.0, attrs : [ 'big' ] }," + " 'petunias': { 'alive': true, 'dv': -9.8, 'height': 50.0 } " + "}"; + mongo::BSONObj obj = mongo::fromjson(inJson); + + // Create a new document representing BSONObj with the above contents. + mmb::Document doc(obj); + + // The whale hits the planet and dies. + mmb::Element whale = mmb::findFirstChildNamed(doc.root(), "whale"); + ASSERT_TRUE(whale.ok()); + // Find the 'dv' field in the whale. + mmb::Element whale_deltav = mmb::findFirstChildNamed(whale, "dv"); + ASSERT_TRUE(whale_deltav.ok()); + // Set the dv field to zero. + ASSERT_OK(whale_deltav.setValueDouble(0.0)); + // Find the 'height' field in the whale. + mmb::Element whale_height = mmb::findFirstChildNamed(whale, "height"); + ASSERT_TRUE(whale_height.ok()); + // Set the height field to zero. + ASSERT_OK(whale_height.setValueDouble(0)); + // Find the 'alive' field, and set it to false. + mmb::Element whale_alive = mmb::findFirstChildNamed(whale, "alive"); + ASSERT_TRUE(whale_alive.ok()); + ASSERT_OK(whale_alive.setValueBool(false)); + + // The petunias survive, update its fields much like we did above. + mmb::Element petunias = mmb::findFirstChildNamed(doc.root(), "petunias"); + ASSERT_TRUE(petunias.ok()); + mmb::Element petunias_deltav = mmb::findFirstChildNamed(petunias, "dv"); + ASSERT_TRUE(petunias_deltav.ok()); + ASSERT_OK(petunias_deltav.setValueDouble(0.0)); + mmb::Element petunias_height = mmb::findFirstChildNamed(petunias, "height"); + ASSERT_TRUE(petunias_height.ok()); + ASSERT_OK(petunias_height.setValueDouble(0)); + + // Replace the whale by its wreckage, saving only its attributes: + // Construct a new mongo::Object element for the ex-whale. + mmb::Element ex_whale = doc.makeElementObject("ex-whale"); + ASSERT_OK(doc.root().pushBack(ex_whale)); + // Find the attributes of the old 'whale' element. + mmb::Element whale_attrs = mmb::findFirstChildNamed(whale, "attrs"); + // Remove the attributes from the whale (they remain valid, but detached). + ASSERT_OK(whale_attrs.remove()); + // Insert the attributes into the ex-whale. + ASSERT_OK(ex_whale.pushBack(whale_attrs)); + // Remove the whale object. + ASSERT_OK(whale.remove()); + + static const char outJson[] = + "{" + " 'petunias': { 'alive': true, 'dv': 0.0, 'height': 0 }," + " 'ex-whale': { 'attrs': [ 'big' ] } })" + "}"; + + mongo::BSONObjBuilder builder; + doc.writeTo(&builder); + ASSERT_EQUALS(mongo::fromjson(outJson), doc.getObject()); +} - TEST(Document, RootCloningToDifferentDocument) { - - const char initial[] = "{ a : 1, b : [ 1, 2, 3 ], c : { 'c' : 'c' }, d : [ 4, 5, 6 ] }"; - - mmb::Document source(mongo::fromjson(initial)); - - // Dirty the 'd' node and parents. - source.root()["d"].pushBack(source.makeElementInt(mongo::StringData(), 7)); - - mmb::Document target; - - mmb::Element newElement = target.makeElementWithNewFieldName("X", source.root()); - mongo::Status status = target.root().pushBack(newElement); - ASSERT_OK(status); - const char expected[] = - "{ X : { a : 1, b : [ 1, 2, 3 ], c : { 'c' : 'c' }, d : [ 4, 5, 6, 7 ] } }"; - - ASSERT_EQUALS(mongo::fromjson(expected), target); - } - - TEST(Document, RootCloningToSameDocument) { - - const char initial[] = "{ a : 1, b : [ 1, 2, 3 ], c : { 'c' : 'c' }, d : [ 4, 5, 6 ] }"; - - mmb::Document doc(mongo::fromjson(initial)); - - // Dirty the 'd' node and parents. - doc.root()["d"].pushBack(doc.makeElementInt(mongo::StringData(), 7)); - - mmb::Element newElement = doc.makeElementWithNewFieldName("X", doc.root()); - mongo::Status status = doc.root().pushBack(newElement); - ASSERT_OK(status); - const char expected[] = - "{ " - " a : 1, b : [ 1, 2, 3 ], c : { 'c' : 'c' }, d : [ 4, 5, 6, 7 ], " - "X : { a : 1, b : [ 1, 2, 3 ], c : { 'c' : 'c' }, d : [ 4, 5, 6, 7 ] }" - "}"; - - ASSERT_EQUALS(mongo::fromjson(expected), doc); - } - - TEST(Element, PopOpsOnEmpty) { - mmb::Document doc; - mmb::Element root = doc.root(); - ASSERT_NOT_OK(root.popFront()); - ASSERT_NOT_OK(root.popBack()); - } - - TEST(Document, NameOfRootElementIsEmpty) { - mmb::Document doc; - // NOTE: You really shouldn't rely on this behavior; this test is mostly for coverage. - ASSERT_EQUALS(mongo::StringData(), doc.root().getFieldName()); - } - - TEST(Document, SetValueOnRootFails) { - mmb::Document doc; - ASSERT_NOT_OK(doc.root().setValueInt(5)); - } - - TEST(Document, ValueOfEphemeralObjectElementIsEmpty) { - mmb::Document doc; - mmb::Element root = doc.root(); - mmb::Element ephemeralObject = doc.makeElementObject("foo"); - ASSERT_OK(root.pushBack(ephemeralObject)); - ASSERT_FALSE(ephemeralObject.hasValue()); - // NOTE: You really shouldn't rely on this behavior; this test is mostly for coverage. - ASSERT_EQUALS(mongo::BSONElement(), ephemeralObject.getValue()); - } - - TEST(Element, RemovingRemovedElementFails) { - // Once an Element is removed, you can't remove it again until you re-attach it - // somewhere. However, its children are still manipulable. - mmb::Document doc(mongo::fromjson("{ a : { b : 'c' } }")); - mmb::Element a = doc.root().leftChild(); - ASSERT_TRUE(a.ok()); - ASSERT_OK(a.remove()); - ASSERT_NOT_OK(a.remove()); - mmb::Element b = a.leftChild(); - ASSERT_OK(b.remove()); - ASSERT_NOT_OK(b.remove()); - ASSERT_OK(a.pushBack(b)); - ASSERT_OK(b.remove()); - } - - namespace { - // Checks that two BSONElements are byte-for-byte identical. - bool identical(const mongo::BSONElement& lhs, const mongo::BSONElement& rhs) { - if (lhs.size() != rhs.size()) - return false; - return std::memcmp(lhs.rawdata(), rhs.rawdata(), lhs.size()) == 0; - } - } // namespace - - TEST(TypeSupport, EncodingEquivalenceDouble) { - mongo::BSONObjBuilder builder; - const char name[] = "thing"; - const double value1 = 3.1415926; - builder.append(name, value1); - mongo::BSONObj source = builder.done(); - const mongo::BSONElement thing = source.firstElement(); - ASSERT_TRUE(thing.type() == mongo::NumberDouble); - - mmb::Document doc; - - // Construct via direct call to append/make - ASSERT_OK(doc.root().appendDouble(name, value1)); - mmb::Element a = doc.root().rightChild(); - ASSERT_TRUE(a.ok()); - ASSERT_EQUALS(a.getType(), mongo::NumberDouble); - ASSERT_TRUE(a.hasValue()); - ASSERT_EQUALS(value1, mmb::ConstElement(a).getValueDouble()); - - // Construct via call passing BSON element - ASSERT_OK(doc.root().appendElement(thing)); - mmb::Element b = doc.root().rightChild(); - ASSERT_TRUE(b.ok()); - ASSERT_EQUALS(b.getType(), mongo::NumberDouble); - ASSERT_TRUE(b.hasValue()); - - // Construct via setValue call. - ASSERT_OK(doc.root().appendNull(name)); - mmb::Element c = doc.root().rightChild(); - ASSERT_TRUE(c.ok()); - c.setValueDouble(value1); - ASSERT_EQUALS(c.getType(), mongo::NumberDouble); - ASSERT_TRUE(c.hasValue()); - - // Ensure identity: - ASSERT_TRUE(identical(thing, mmb::ConstElement(a).getValue())); - ASSERT_TRUE(identical(a.getValue(), b.getValue())); - ASSERT_TRUE(identical(b.getValue(), c.getValue())); - } - - TEST(TypeSupport, EncodingEquivalenceString) { - mongo::BSONObjBuilder builder; - const char name[] = "thing"; - const std::string value1 = "value1"; - builder.append(name, value1); - mongo::BSONObj source = builder.done(); - const mongo::BSONElement thing = source.firstElement(); - ASSERT_TRUE(thing.type() == mongo::String); - - mmb::Document doc; - - // Construct via direct call to append/make - ASSERT_OK(doc.root().appendString(name, value1)); - mmb::Element a = doc.root().rightChild(); - ASSERT_TRUE(a.ok()); - ASSERT_EQUALS(a.getType(), mongo::String); - ASSERT_TRUE(a.hasValue()); - ASSERT_EQUALS(value1, mmb::ConstElement(a).getValueString()); - - // Construct via call passing BSON element - ASSERT_OK(doc.root().appendElement(thing)); - mmb::Element b = doc.root().rightChild(); - ASSERT_TRUE(b.ok()); - ASSERT_EQUALS(b.getType(), mongo::String); - ASSERT_TRUE(b.hasValue()); - - // Construct via setValue call. - ASSERT_OK(doc.root().appendNull(name)); - mmb::Element c = doc.root().rightChild(); - ASSERT_TRUE(c.ok()); - c.setValueString(value1); - ASSERT_EQUALS(c.getType(), mongo::String); - ASSERT_TRUE(c.hasValue()); - - // Ensure identity: - ASSERT_TRUE(identical(thing, mmb::ConstElement(a).getValue())); - ASSERT_TRUE(identical(a.getValue(), b.getValue())); - ASSERT_TRUE(identical(b.getValue(), c.getValue())); - } - - TEST(TypeSupport, EncodingEquivalenceObject) { - mongo::BSONObjBuilder builder; - const char name[] = "thing"; - const mongo::BSONObj value1 = mongo::fromjson("{ a : 1, b : 2.0, c : 'hello' }"); - builder.append(name, value1); - mongo::BSONObj source = builder.done(); - const mongo::BSONElement thing = source.firstElement(); - ASSERT_TRUE(thing.type() == mongo::Object); - - mmb::Document doc; - - // Construct via direct call to append/make - ASSERT_OK(doc.root().appendObject(name, value1)); - mmb::Element a = doc.root().rightChild(); - ASSERT_TRUE(a.ok()); - ASSERT_EQUALS(a.getType(), mongo::Object); - ASSERT_TRUE(a.hasValue()); - ASSERT_EQUALS(value1, mmb::ConstElement(a).getValueObject()); - - // Construct via call passing BSON element - ASSERT_OK(doc.root().appendElement(thing)); - mmb::Element b = doc.root().rightChild(); - ASSERT_TRUE(b.ok()); - ASSERT_EQUALS(b.getType(), mongo::Object); - ASSERT_TRUE(b.hasValue()); - - // Construct via setValue call. - ASSERT_OK(doc.root().appendNull(name)); - mmb::Element c = doc.root().rightChild(); - ASSERT_TRUE(c.ok()); - c.setValueObject(value1); - ASSERT_EQUALS(c.getType(), mongo::Object); - ASSERT_TRUE(c.hasValue()); - - // Ensure identity: - ASSERT_TRUE(identical(thing, mmb::ConstElement(a).getValue())); - ASSERT_TRUE(identical(a.getValue(), b.getValue())); - ASSERT_TRUE(identical(b.getValue(), c.getValue())); - } - - TEST(TypeSupport, EncodingEquivalenceArray) { - mongo::BSONObjBuilder builder; - const char name[] = "thing"; - const mongo::BSONObj dummy = (mongo::fromjson("{ x : [ 1, 2.0, 'hello' ] } ")); - const mongo::BSONArray value1(dummy.firstElement().embeddedObject()); - builder.append(name, value1); - mongo::BSONObj source = builder.done(); - const mongo::BSONElement thing = source.firstElement(); - ASSERT_TRUE(thing.type() == mongo::Array); - - mmb::Document doc; - - // Construct via direct call to append/make - ASSERT_OK(doc.root().appendArray(name, value1)); - mmb::Element a = doc.root().rightChild(); - ASSERT_TRUE(a.ok()); - ASSERT_EQUALS(a.getType(), mongo::Array); - ASSERT_TRUE(a.hasValue()); - ASSERT_EQUALS(value1, mmb::ConstElement(a).getValueArray()); - - // Construct via call passing BSON element - ASSERT_OK(doc.root().appendElement(thing)); - mmb::Element b = doc.root().rightChild(); - ASSERT_TRUE(b.ok()); - ASSERT_EQUALS(b.getType(), mongo::Array); - ASSERT_TRUE(b.hasValue()); - - // Construct via setValue call. - ASSERT_OK(doc.root().appendNull(name)); - mmb::Element c = doc.root().rightChild(); - ASSERT_TRUE(c.ok()); - c.setValueArray(value1); - ASSERT_EQUALS(c.getType(), mongo::Array); - ASSERT_TRUE(c.hasValue()); - - // Ensure identity: - ASSERT_TRUE(identical(thing, mmb::ConstElement(a).getValue())); - ASSERT_TRUE(identical(a.getValue(), b.getValue())); - ASSERT_TRUE(identical(b.getValue(), c.getValue())); - } - - TEST(TypeSupport, EncodingEquivalenceBinary) { - mongo::BSONObjBuilder builder; - const char name[] = "thing"; - const mongo::BinDataType value1 = mongo::newUUID; - const unsigned char value2[] = { - 0x00, 0x9D, 0x15, 0xA3, - 0x3B, 0xCC, 0x46, 0x60, - 0x90, 0x45, 0xEF, 0x54, - 0x77, 0x8A, 0x87, 0x0C - }; - builder.appendBinData(name, sizeof(value2), value1, &value2[0]); - mongo::BSONObj source = builder.done(); - const mongo::BSONElement thing = source.firstElement(); - ASSERT_TRUE(thing.type() == mongo::BinData); - - mmb::Document doc; - - // Construct via direct call to append/make - ASSERT_OK(doc.root().appendBinary(name, sizeof(value2), value1, &value2[0])); - mmb::Element a = doc.root().rightChild(); - ASSERT_TRUE(a.ok()); - ASSERT_EQUALS(a.getType(), mongo::BinData); - ASSERT_TRUE(a.hasValue()); - - // Construct via call passing BSON element - ASSERT_OK(doc.root().appendElement(thing)); - mmb::Element b = doc.root().rightChild(); - ASSERT_TRUE(b.ok()); - ASSERT_EQUALS(b.getType(), mongo::BinData); - ASSERT_TRUE(b.hasValue()); - - // Construct via setValue call. - ASSERT_OK(doc.root().appendNull(name)); - mmb::Element c = doc.root().rightChild(); - ASSERT_TRUE(c.ok()); - c.setValueBinary(sizeof(value2), value1, &value2[0]); - ASSERT_EQUALS(c.getType(), mongo::BinData); - ASSERT_TRUE(c.hasValue()); - - // Ensure identity: - ASSERT_TRUE(identical(thing, mmb::ConstElement(a).getValue())); - ASSERT_TRUE(identical(a.getValue(), b.getValue())); - ASSERT_TRUE(identical(b.getValue(), c.getValue())); - } - - TEST(TypeSupport, EncodingEquivalenceUndefined) { - mongo::BSONObjBuilder builder; - const char name[] = "thing"; - builder.appendUndefined(name); - mongo::BSONObj source = builder.done(); - const mongo::BSONElement thing = source.firstElement(); - ASSERT_TRUE(thing.type() == mongo::Undefined); - - mmb::Document doc; - - // Construct via direct call to append/make - ASSERT_OK(doc.root().appendUndefined(name)); - mmb::Element a = doc.root().rightChild(); - ASSERT_TRUE(a.ok()); - ASSERT_EQUALS(a.getType(), mongo::Undefined); - ASSERT_TRUE(a.hasValue()); - ASSERT_TRUE(mmb::ConstElement(a).isValueUndefined()); - - // Construct via call passing BSON element - ASSERT_OK(doc.root().appendElement(thing)); - mmb::Element b = doc.root().rightChild(); - ASSERT_TRUE(b.ok()); - ASSERT_EQUALS(b.getType(), mongo::Undefined); - ASSERT_TRUE(b.hasValue()); - - // Construct via setValue call. - ASSERT_OK(doc.root().appendNull(name)); - mmb::Element c = doc.root().rightChild(); - ASSERT_TRUE(c.ok()); - c.setValueUndefined(); - ASSERT_EQUALS(c.getType(), mongo::Undefined); - ASSERT_TRUE(c.hasValue()); - - // Ensure identity: - ASSERT_TRUE(identical(thing, mmb::ConstElement(a).getValue())); - ASSERT_TRUE(identical(a.getValue(), b.getValue())); - ASSERT_TRUE(identical(b.getValue(), c.getValue())); - } - - TEST(TypeSupport, EncodingEquivalenceOID) { - mongo::BSONObjBuilder builder; - const char name[] = "thing"; - const mongo::OID value1 = mongo::OID::gen(); - builder.append(name, value1); - mongo::BSONObj source = builder.done(); - const mongo::BSONElement thing = source.firstElement(); - ASSERT_TRUE(thing.type() == mongo::jstOID); - - mmb::Document doc; - - // Construct via direct call to append/make - ASSERT_OK(doc.root().appendOID(name, value1)); - mmb::Element a = doc.root().rightChild(); - ASSERT_TRUE(a.ok()); - ASSERT_EQUALS(a.getType(), mongo::jstOID); - ASSERT_TRUE(a.hasValue()); - ASSERT_EQUALS(value1, mmb::ConstElement(a).getValueOID()); - - // Construct via call passing BSON element - ASSERT_OK(doc.root().appendElement(thing)); - mmb::Element b = doc.root().rightChild(); - ASSERT_TRUE(b.ok()); - ASSERT_EQUALS(b.getType(), mongo::jstOID); - ASSERT_TRUE(b.hasValue()); - - // Construct via setValue call. - ASSERT_OK(doc.root().appendNull(name)); - mmb::Element c = doc.root().rightChild(); - ASSERT_TRUE(c.ok()); - c.setValueOID(value1); - ASSERT_EQUALS(c.getType(), mongo::jstOID); - ASSERT_TRUE(c.hasValue()); - - // Ensure identity: - ASSERT_TRUE(identical(thing, mmb::ConstElement(a).getValue())); - ASSERT_TRUE(identical(a.getValue(), b.getValue())); - ASSERT_TRUE(identical(b.getValue(), c.getValue())); - } - - TEST(TypeSupport, EncodingEquivalenceBoolean) { - mongo::BSONObjBuilder builder; - const char name[] = "thing"; - const bool value1 = true; - builder.append(name, value1); - mongo::BSONObj source = builder.done(); - const mongo::BSONElement thing = source.firstElement(); - ASSERT_TRUE(thing.type() == mongo::Bool); - - mmb::Document doc; - - // Construct via direct call to append/make - ASSERT_OK(doc.root().appendBool(name, value1)); - mmb::Element a = doc.root().rightChild(); - ASSERT_TRUE(a.ok()); - ASSERT_EQUALS(a.getType(), mongo::Bool); - ASSERT_TRUE(a.hasValue()); - ASSERT_EQUALS(value1, mmb::ConstElement(a).getValueBool()); - - // Construct via call passing BSON element - ASSERT_OK(doc.root().appendElement(thing)); - mmb::Element b = doc.root().rightChild(); - ASSERT_TRUE(b.ok()); - ASSERT_EQUALS(b.getType(), mongo::Bool); - ASSERT_TRUE(b.hasValue()); - - // Construct via setValue call. - ASSERT_OK(doc.root().appendNull(name)); - mmb::Element c = doc.root().rightChild(); - ASSERT_TRUE(c.ok()); - c.setValueBool(value1); - ASSERT_EQUALS(c.getType(), mongo::Bool); - ASSERT_TRUE(c.hasValue()); - - // Ensure identity: - ASSERT_TRUE(identical(thing, mmb::ConstElement(a).getValue())); - ASSERT_TRUE(identical(a.getValue(), b.getValue())); - ASSERT_TRUE(identical(b.getValue(), c.getValue())); - } - - TEST(TypeSupport, EncodingEquivalenceDate) { - mongo::BSONObjBuilder builder; - const char name[] = "thing"; - const mongo::Date_t value1 = mongo::jsTime(); - builder.append(name, value1); - mongo::BSONObj source = builder.done(); - const mongo::BSONElement thing = source.firstElement(); - ASSERT_TRUE(thing.type() == mongo::Date); - - mmb::Document doc; - - // Construct via direct call to append/make - ASSERT_OK(doc.root().appendDate(name, value1)); - mmb::Element a = doc.root().rightChild(); - ASSERT_TRUE(a.ok()); - ASSERT_EQUALS(a.getType(), mongo::Date); - ASSERT_TRUE(a.hasValue()); - ASSERT_EQUALS(value1, mmb::ConstElement(a).getValueDate()); - - // Construct via call passing BSON element - ASSERT_OK(doc.root().appendElement(thing)); - mmb::Element b = doc.root().rightChild(); - ASSERT_TRUE(b.ok()); - ASSERT_EQUALS(b.getType(), mongo::Date); - ASSERT_TRUE(b.hasValue()); - - // Construct via setValue call. - ASSERT_OK(doc.root().appendNull(name)); - mmb::Element c = doc.root().rightChild(); - ASSERT_TRUE(c.ok()); - c.setValueDate(value1); - ASSERT_EQUALS(c.getType(), mongo::Date); - ASSERT_TRUE(c.hasValue()); - - // Ensure identity: - ASSERT_TRUE(identical(thing, mmb::ConstElement(a).getValue())); - ASSERT_TRUE(identical(a.getValue(), b.getValue())); - ASSERT_TRUE(identical(b.getValue(), c.getValue())); - } - - TEST(TypeSupport, EncodingEquivalenceNull) { - mongo::BSONObjBuilder builder; - const char name[] = "thing"; - builder.appendNull(name); - mongo::BSONObj source = builder.done(); - const mongo::BSONElement thing = source.firstElement(); - ASSERT_TRUE(thing.type() == mongo::jstNULL); - - mmb::Document doc; - - // Construct via direct call to append/make - ASSERT_OK(doc.root().appendNull(name)); - mmb::Element a = doc.root().rightChild(); - ASSERT_TRUE(a.ok()); - ASSERT_EQUALS(a.getType(), mongo::jstNULL); - ASSERT_TRUE(a.hasValue()); - ASSERT_TRUE(mmb::ConstElement(a).isValueNull()); - - // Construct via call passing BSON element - ASSERT_OK(doc.root().appendElement(thing)); - mmb::Element b = doc.root().rightChild(); - ASSERT_TRUE(b.ok()); - ASSERT_EQUALS(b.getType(), mongo::jstNULL); - ASSERT_TRUE(b.hasValue()); - - // Construct via setValue call. - ASSERT_OK(doc.root().appendUndefined(name)); - mmb::Element c = doc.root().rightChild(); - ASSERT_TRUE(c.ok()); - c.setValueNull(); - ASSERT_EQUALS(c.getType(), mongo::jstNULL); - ASSERT_TRUE(c.hasValue()); - - // Ensure identity: - ASSERT_TRUE(identical(thing, mmb::ConstElement(a).getValue())); - ASSERT_TRUE(identical(a.getValue(), b.getValue())); - ASSERT_TRUE(identical(b.getValue(), c.getValue())); - } - - TEST(TypeSupport, EncodingEquivalenceRegex) { - mongo::BSONObjBuilder builder; - const char name[] = "thing"; - const std::string value1 = "some_regex_data"; - const std::string value2 = "flags"; - builder.appendRegex(name, value1, value2); - mongo::BSONObj source = builder.done(); - const mongo::BSONElement thing = source.firstElement(); - ASSERT_TRUE(thing.type() == mongo::RegEx); - - mmb::Document doc; - - // Construct via direct call to append/make - ASSERT_OK(doc.root().appendRegex(name, value1, value2)); - mmb::Element a = doc.root().rightChild(); - ASSERT_TRUE(a.ok()); - ASSERT_EQUALS(a.getType(), mongo::RegEx); - ASSERT_TRUE(a.hasValue()); - - // Construct via call passing BSON element - ASSERT_OK(doc.root().appendElement(thing)); - mmb::Element b = doc.root().rightChild(); - ASSERT_TRUE(b.ok()); - ASSERT_EQUALS(b.getType(), mongo::RegEx); - ASSERT_TRUE(b.hasValue()); - - // Construct via setValue call. - ASSERT_OK(doc.root().appendNull(name)); - mmb::Element c = doc.root().rightChild(); - ASSERT_TRUE(c.ok()); - c.setValueRegex(value1, value2); - ASSERT_EQUALS(c.getType(), mongo::RegEx); - ASSERT_TRUE(c.hasValue()); - - // Ensure identity: - ASSERT_TRUE(identical(thing, mmb::ConstElement(a).getValue())); - ASSERT_TRUE(identical(a.getValue(), b.getValue())); - ASSERT_TRUE(identical(b.getValue(), c.getValue())); - } - - TEST(TypeSupport, EncodingEquivalenceDBRef) { - mongo::BSONObjBuilder builder; - const char name[] = "thing"; - const std::string value1 = "some_ns"; - const mongo::OID value2 = mongo::OID::gen(); - builder.appendDBRef(name, value1, value2); - mongo::BSONObj source = builder.done(); - const mongo::BSONElement thing = source.firstElement(); - ASSERT_TRUE(thing.type() == mongo::DBRef); - - mmb::Document doc; - - // Construct via direct call to append/make - ASSERT_OK(doc.root().appendDBRef(name, value1, value2)); - mmb::Element a = doc.root().rightChild(); - ASSERT_TRUE(a.ok()); - ASSERT_EQUALS(a.getType(), mongo::DBRef); - ASSERT_TRUE(a.hasValue()); - - // Construct via call passing BSON element - ASSERT_OK(doc.root().appendElement(thing)); - mmb::Element b = doc.root().rightChild(); - ASSERT_TRUE(b.ok()); - ASSERT_EQUALS(b.getType(), mongo::DBRef); - ASSERT_TRUE(b.hasValue()); - - // Construct via setValue call. - ASSERT_OK(doc.root().appendNull(name)); - mmb::Element c = doc.root().rightChild(); - ASSERT_TRUE(c.ok()); - c.setValueDBRef(value1, value2); - ASSERT_EQUALS(c.getType(), mongo::DBRef); - ASSERT_TRUE(c.hasValue()); - - // Ensure identity: - ASSERT_TRUE(identical(thing, mmb::ConstElement(a).getValue())); - ASSERT_TRUE(identical(a.getValue(), b.getValue())); - ASSERT_TRUE(identical(b.getValue(), c.getValue())); - } - - TEST(TypeSupport, EncodingEquivalenceCode) { - mongo::BSONObjBuilder builder; - const char name[] = "thing"; - const std::string value1 = "{ print 4; }"; - builder.appendCode(name, value1); - mongo::BSONObj source = builder.done(); - const mongo::BSONElement thing = source.firstElement(); - ASSERT_TRUE(thing.type() == mongo::Code); - - mmb::Document doc; - - // Construct via direct call to append/make - ASSERT_OK(doc.root().appendCode(name, value1)); - mmb::Element a = doc.root().rightChild(); - ASSERT_TRUE(a.ok()); - ASSERT_EQUALS(a.getType(), mongo::Code); - ASSERT_TRUE(a.hasValue()); - - // Construct via call passing BSON element - ASSERT_OK(doc.root().appendElement(thing)); - mmb::Element b = doc.root().rightChild(); - ASSERT_TRUE(b.ok()); - ASSERT_EQUALS(b.getType(), mongo::Code); - ASSERT_TRUE(b.hasValue()); - - // Construct via setValue call. - ASSERT_OK(doc.root().appendNull(name)); - mmb::Element c = doc.root().rightChild(); - ASSERT_TRUE(c.ok()); - c.setValueCode(value1); - ASSERT_EQUALS(c.getType(), mongo::Code); - ASSERT_TRUE(c.hasValue()); - - // Ensure identity: - ASSERT_TRUE(identical(thing, mmb::ConstElement(a).getValue())); - ASSERT_TRUE(identical(a.getValue(), b.getValue())); - ASSERT_TRUE(identical(b.getValue(), c.getValue())); - } - - TEST(TypeSupport, EncodingEquivalenceSymbol) { - mongo::BSONObjBuilder builder; - const char name[] = "thing"; - const std::string value1 = "#symbol"; - builder.appendSymbol(name, value1); - mongo::BSONObj source = builder.done(); - const mongo::BSONElement thing = source.firstElement(); - ASSERT_TRUE(thing.type() == mongo::Symbol); - - mmb::Document doc; - - // Construct via direct call to append/make - ASSERT_OK(doc.root().appendSymbol(name, value1)); - mmb::Element a = doc.root().rightChild(); - ASSERT_TRUE(a.ok()); - ASSERT_EQUALS(a.getType(), mongo::Symbol); - ASSERT_TRUE(a.hasValue()); - ASSERT_EQUALS(value1, mmb::ConstElement(a).getValueSymbol()); - - // Construct via call passing BSON element - ASSERT_OK(doc.root().appendElement(thing)); - mmb::Element b = doc.root().rightChild(); - ASSERT_TRUE(b.ok()); - ASSERT_EQUALS(b.getType(), mongo::Symbol); - ASSERT_TRUE(b.hasValue()); - - // Construct via setValue call. - ASSERT_OK(doc.root().appendNull(name)); - mmb::Element c = doc.root().rightChild(); - ASSERT_TRUE(c.ok()); - c.setValueSymbol(value1); - ASSERT_EQUALS(c.getType(), mongo::Symbol); - ASSERT_TRUE(c.hasValue()); - - // Ensure identity: - ASSERT_TRUE(identical(thing, mmb::ConstElement(a).getValue())); - ASSERT_TRUE(identical(a.getValue(), b.getValue())); - ASSERT_TRUE(identical(b.getValue(), c.getValue())); - } - - TEST(TypeSupport, EncodingEquivalenceCodeWithScope) { - mongo::BSONObjBuilder builder; - const char name[] = "thing"; - const std::string value1 = "print x;"; - const mongo::BSONObj value2 = mongo::fromjson("{ x : 4 }"); - builder.appendCodeWScope(name, value1, value2); - mongo::BSONObj source = builder.done(); - const mongo::BSONElement thing = source.firstElement(); - ASSERT_TRUE(thing.type() == mongo::CodeWScope); - - mmb::Document doc; - - // Construct via direct call to append/make - ASSERT_OK(doc.root().appendCodeWithScope(name, value1, value2)); - mmb::Element a = doc.root().rightChild(); - ASSERT_TRUE(a.ok()); - ASSERT_EQUALS(a.getType(), mongo::CodeWScope); - ASSERT_TRUE(a.hasValue()); - - // Construct via call passing BSON element - ASSERT_OK(doc.root().appendElement(thing)); - mmb::Element b = doc.root().rightChild(); - ASSERT_TRUE(b.ok()); - ASSERT_EQUALS(b.getType(), mongo::CodeWScope); - ASSERT_TRUE(b.hasValue()); - - // Construct via setValue call. - ASSERT_OK(doc.root().appendNull(name)); - mmb::Element c = doc.root().rightChild(); - ASSERT_TRUE(c.ok()); - c.setValueCodeWithScope(value1, value2); - ASSERT_EQUALS(c.getType(), mongo::CodeWScope); - ASSERT_TRUE(c.hasValue()); - - // Ensure identity: - ASSERT_TRUE(identical(thing, mmb::ConstElement(a).getValue())); - ASSERT_TRUE(identical(a.getValue(), b.getValue())); - ASSERT_TRUE(identical(b.getValue(), c.getValue())); - } - - TEST(TypeSupport, EncodingEquivalenceInt) { - mongo::BSONObjBuilder builder; - const char name[] = "thing"; - const int value1 = true; - builder.append(name, value1); - mongo::BSONObj source = builder.done(); - const mongo::BSONElement thing = source.firstElement(); - ASSERT_TRUE(thing.type() == mongo::NumberInt); - - mmb::Document doc; - - // Construct via direct call to append/make - ASSERT_OK(doc.root().appendInt(name, value1)); - mmb::Element a = doc.root().rightChild(); - ASSERT_TRUE(a.ok()); - ASSERT_EQUALS(a.getType(), mongo::NumberInt); - ASSERT_TRUE(a.hasValue()); - ASSERT_EQUALS(value1, mmb::ConstElement(a).getValueInt()); - - // Construct via call passing BSON element - ASSERT_OK(doc.root().appendElement(thing)); - mmb::Element b = doc.root().rightChild(); - ASSERT_TRUE(b.ok()); - ASSERT_EQUALS(b.getType(), mongo::NumberInt); - ASSERT_TRUE(b.hasValue()); - - // Construct via setValue call. - ASSERT_OK(doc.root().appendNull(name)); - mmb::Element c = doc.root().rightChild(); - ASSERT_TRUE(c.ok()); - c.setValueInt(value1); - ASSERT_EQUALS(c.getType(), mongo::NumberInt); - ASSERT_TRUE(c.hasValue()); - - // Ensure identity: - ASSERT_TRUE(identical(thing, mmb::ConstElement(a).getValue())); - ASSERT_TRUE(identical(a.getValue(), b.getValue())); - ASSERT_TRUE(identical(b.getValue(), c.getValue())); - } - - TEST(TypeSupport, EncodingEquivalenceTimestamp) { - mongo::BSONObjBuilder builder; - const char name[] = "thing"; - const mongo::Timestamp value1 = mongo::Timestamp(mongo::jsTime()); - builder.append(name, value1); - mongo::BSONObj source = builder.done(); - const mongo::BSONElement thing = source.firstElement(); - ASSERT_TRUE(thing.type() == mongo::bsonTimestamp); - - mmb::Document doc; - - // Construct via direct call to append/make - ASSERT_OK(doc.root().appendTimestamp(name, value1)); - mmb::Element a = doc.root().rightChild(); - ASSERT_TRUE(a.ok()); - ASSERT_EQUALS(a.getType(), mongo::bsonTimestamp); - ASSERT_TRUE(a.hasValue()); - ASSERT_TRUE(value1 == mmb::ConstElement(a).getValueTimestamp()); - - // Construct via call passing BSON element - ASSERT_OK(doc.root().appendElement(thing)); - mmb::Element b = doc.root().rightChild(); - ASSERT_TRUE(b.ok()); - ASSERT_EQUALS(b.getType(), mongo::bsonTimestamp); - ASSERT_TRUE(b.hasValue()); - - // Construct via setValue call. - ASSERT_OK(doc.root().appendNull(name)); - mmb::Element c = doc.root().rightChild(); - ASSERT_TRUE(c.ok()); - c.setValueTimestamp(value1); - ASSERT_EQUALS(c.getType(), mongo::bsonTimestamp); - ASSERT_TRUE(c.hasValue()); - - // Ensure identity: - ASSERT_TRUE(identical(thing, mmb::ConstElement(a).getValue())); - ASSERT_TRUE(identical(a.getValue(), b.getValue())); - ASSERT_TRUE(identical(b.getValue(), c.getValue())); - } - - TEST(TypeSupport, EncodingEquivalenceLong) { - mongo::BSONObjBuilder builder; - const char name[] = "thing"; - const long long value1 = 420000000000000LL; - builder.append(name, value1); - mongo::BSONObj source = builder.done(); - const mongo::BSONElement thing = source.firstElement(); - ASSERT_TRUE(thing.type() == mongo::NumberLong); - - mmb::Document doc; - - // Construct via direct call to append/make - ASSERT_OK(doc.root().appendLong(name, value1)); - mmb::Element a = doc.root().rightChild(); - ASSERT_TRUE(a.ok()); - ASSERT_EQUALS(a.getType(), mongo::NumberLong); - ASSERT_TRUE(a.hasValue()); - ASSERT_EQUALS(value1, mmb::ConstElement(a).getValueLong()); - - // Construct via call passing BSON element - ASSERT_OK(doc.root().appendElement(thing)); - mmb::Element b = doc.root().rightChild(); - ASSERT_TRUE(b.ok()); - ASSERT_EQUALS(b.getType(), mongo::NumberLong); - ASSERT_TRUE(b.hasValue()); - - // Construct via setValue call. - ASSERT_OK(doc.root().appendNull(name)); - mmb::Element c = doc.root().rightChild(); - ASSERT_TRUE(c.ok()); - c.setValueLong(value1); - ASSERT_EQUALS(c.getType(), mongo::NumberLong); - ASSERT_TRUE(c.hasValue()); - - // Ensure identity: - ASSERT_TRUE(identical(thing, mmb::ConstElement(a).getValue())); - ASSERT_TRUE(identical(a.getValue(), b.getValue())); - ASSERT_TRUE(identical(b.getValue(), c.getValue())); - } - - TEST(TypeSupport, EncodingEquivalenceMinKey) { - mongo::BSONObjBuilder builder; - const char name[] = "thing"; - builder.appendMinKey(name); - mongo::BSONObj source = builder.done(); - const mongo::BSONElement thing = source.firstElement(); - ASSERT_TRUE(thing.type() == mongo::MinKey); - - mmb::Document doc; - - // Construct via direct call to append/make - ASSERT_OK(doc.root().appendMinKey(name)); - mmb::Element a = doc.root().rightChild(); - ASSERT_TRUE(a.ok()); - ASSERT_EQUALS(a.getType(), mongo::MinKey); - ASSERT_TRUE(a.hasValue()); - ASSERT_TRUE(mmb::ConstElement(a).isValueMinKey()); - - // Construct via call passing BSON element - ASSERT_OK(doc.root().appendElement(thing)); - mmb::Element b = doc.root().rightChild(); - ASSERT_TRUE(b.ok()); - ASSERT_EQUALS(b.getType(), mongo::MinKey); - ASSERT_TRUE(b.hasValue()); - - // Construct via setValue call. - ASSERT_OK(doc.root().appendNull(name)); - mmb::Element c = doc.root().rightChild(); - ASSERT_TRUE(c.ok()); - c.setValueMinKey(); - ASSERT_EQUALS(c.getType(), mongo::MinKey); - ASSERT_TRUE(c.hasValue()); - - // Ensure identity: - ASSERT_TRUE(identical(thing, mmb::ConstElement(a).getValue())); - ASSERT_TRUE(identical(a.getValue(), b.getValue())); - ASSERT_TRUE(identical(b.getValue(), c.getValue())); - } - - TEST(TypeSupport, EncodingEquivalenceMaxKey) { - mongo::BSONObjBuilder builder; - const char name[] = "thing"; - builder.appendMaxKey(name); - mongo::BSONObj source = builder.done(); - const mongo::BSONElement thing = source.firstElement(); - ASSERT_TRUE(thing.type() == mongo::MaxKey); - - mmb::Document doc; - - // Construct via direct call to append/make - ASSERT_OK(doc.root().appendMaxKey(name)); - mmb::Element a = doc.root().rightChild(); - ASSERT_TRUE(a.ok()); - ASSERT_EQUALS(a.getType(), mongo::MaxKey); - ASSERT_TRUE(a.hasValue()); - ASSERT_TRUE(mmb::ConstElement(a).isValueMaxKey()); - - // Construct via call passing BSON element - ASSERT_OK(doc.root().appendElement(thing)); - mmb::Element b = doc.root().rightChild(); - ASSERT_TRUE(b.ok()); - ASSERT_EQUALS(b.getType(), mongo::MaxKey); - ASSERT_TRUE(b.hasValue()); - - // Construct via setValue call. - ASSERT_OK(doc.root().appendNull(name)); - mmb::Element c = doc.root().rightChild(); - ASSERT_TRUE(c.ok()); - c.setValueMaxKey(); - ASSERT_EQUALS(c.getType(), mongo::MaxKey); - ASSERT_TRUE(c.hasValue()); - - // Ensure identity: - ASSERT_TRUE(identical(thing, mmb::ConstElement(a).getValue())); - ASSERT_TRUE(identical(a.getValue(), b.getValue())); - ASSERT_TRUE(identical(b.getValue(), c.getValue())); - } - - TEST(Document, ManipulateComplexObjInLeafHeap) { - // Test that an object with complex substructure that lives in the leaf builder can be - // manipulated in the same way as an object with complex substructure that lives - // freely. - mmb::Document doc; - static const char inJson[] = "{ a: 1, b: 2, d : ['w', 'x', 'y', 'z'] }"; - mmb::Element embedded = doc.makeElementObject("embedded", mongo::fromjson(inJson)); - ASSERT_OK(doc.root().pushBack(embedded)); - mmb::Element free = doc.makeElementObject("free"); - ASSERT_OK(doc.root().pushBack(free)); - - mmb::Element e_a = embedded.leftChild(); - ASSERT_TRUE(e_a.ok()); - ASSERT_EQUALS("a", e_a.getFieldName()); - mmb::Element e_b = e_a.rightSibling(); - ASSERT_TRUE(e_b.ok()); - ASSERT_EQUALS("b", e_b.getFieldName()); - - mmb::Element new_c = doc.makeElementDouble("c", 2.0); - ASSERT_TRUE(new_c.ok()); - ASSERT_OK(e_b.addSiblingRight(new_c)); - - mmb::Element e_d = new_c.rightSibling(); - ASSERT_TRUE(e_d.ok()); - ASSERT_EQUALS("d", e_d.getFieldName()); - - mmb::Element e_d_0 = e_d.leftChild(); - ASSERT_TRUE(e_d_0.ok()); - - mmb::Element e_d_1 = e_d_0.rightSibling(); - ASSERT_TRUE(e_d_1.ok()); - - mmb::Element e_d_2 = e_d_1.rightSibling(); - ASSERT_TRUE(e_d_2.ok()); - - ASSERT_OK(e_d_1.remove()); - - static const char outJson[] = - "{ embedded: { a: 1, b: 2, c: 2.0, d : ['w', 'y', 'z'] }, free: {} }"; - ASSERT_EQUALS(mongo::fromjson(outJson), doc.getObject()); - } - - TEST(DocumentInPlace, EphemeralDocumentsDoNotUseInPlaceMode) { - mmb::Document doc; - ASSERT_FALSE(doc.isInPlaceModeEnabled()); - } - - TEST(DocumentInPlace, InPlaceModeIsHonored1) { - mongo::BSONObj obj; - mmb::Document doc(obj, mmb::Document::kInPlaceEnabled); - ASSERT_TRUE(doc.isInPlaceModeEnabled()); - doc.disableInPlaceUpdates(); - ASSERT_FALSE(doc.isInPlaceModeEnabled()); - } - - TEST(DocumentInPlace, InPlaceModeIsHonored2) { - mongo::BSONObj obj; - mmb::Document doc(obj, mmb::Document::kInPlaceDisabled); - ASSERT_FALSE(doc.isInPlaceModeEnabled()); - doc.disableInPlaceUpdates(); - ASSERT_FALSE(doc.isInPlaceModeEnabled()); - } - - TEST(DocumentInPlace, InPlaceModeWorksWithNoMutations) { - mongo::BSONObj obj; - mmb::Document doc(obj, mmb::Document::kInPlaceEnabled); - ASSERT_TRUE(doc.isInPlaceModeEnabled()); - const char* source = NULL; - mmb::DamageVector damages; - ASSERT_TRUE(damages.empty()); - doc.getInPlaceUpdates(&damages, &source); - ASSERT_TRUE(damages.empty()); - ASSERT_NOT_EQUALS(static_cast<const char*>(NULL), source); - ASSERT_TRUE(doc.isInPlaceModeEnabled()); - } - - TEST(DocumentInPlace, InPlaceModeIsDisabledByAddSiblingLeft) { - mongo::BSONObj obj = mongo::fromjson("{ foo : 'foo' }"); - mmb::Document doc(obj, mmb::Document::kInPlaceEnabled); - ASSERT_TRUE(doc.isInPlaceModeEnabled()); - mmb::Element newElt = doc.makeElementInt("bar", 42); - ASSERT_TRUE(doc.isInPlaceModeEnabled()); - ASSERT_OK(doc.root().leftChild().addSiblingLeft(newElt)); - ASSERT_FALSE(doc.isInPlaceModeEnabled()); - } - - TEST(DocumentInPlace, InPlaceModeIsDisabledByAddSiblingRight) { - mongo::BSONObj obj = mongo::fromjson("{ foo : 'foo' }"); - mmb::Document doc(obj, mmb::Document::kInPlaceEnabled); - ASSERT_TRUE(doc.isInPlaceModeEnabled()); - mmb::Element newElt = doc.makeElementInt("bar", 42); - ASSERT_TRUE(doc.isInPlaceModeEnabled()); - ASSERT_OK(doc.root().leftChild().addSiblingRight(newElt)); - ASSERT_FALSE(doc.isInPlaceModeEnabled()); - } - - TEST(DocumentInPlace, InPlaceModeIsDisabledByRemove) { - mongo::BSONObj obj = mongo::fromjson("{ foo : 'foo' }"); - mmb::Document doc(obj, mmb::Document::kInPlaceEnabled); - ASSERT_TRUE(doc.isInPlaceModeEnabled()); - ASSERT_OK(doc.root().leftChild().remove()); - ASSERT_FALSE(doc.isInPlaceModeEnabled()); - } - - // NOTE: Someday, we may do in-place renames, but renaming 'foo' to 'foobar' will never - // work because the sizes don't match. Validate that this disables in-place updates. - TEST(DocumentInPlace, InPlaceModeIsDisabledByRename) { - mongo::BSONObj obj = mongo::fromjson("{ foo : 'foo' }"); - mmb::Document doc(obj, mmb::Document::kInPlaceEnabled); - ASSERT_TRUE(doc.isInPlaceModeEnabled()); - ASSERT_OK(doc.root().leftChild().rename("foobar")); - ASSERT_FALSE(doc.isInPlaceModeEnabled()); - } - - TEST(DocumentInPlace, InPlaceModeIsDisabledByPushFront) { - mongo::BSONObj obj = mongo::fromjson("{ foo : 'foo' }"); - mmb::Document doc(obj, mmb::Document::kInPlaceEnabled); - ASSERT_TRUE(doc.isInPlaceModeEnabled()); - mmb::Element newElt = doc.makeElementInt("bar", 42); - ASSERT_TRUE(doc.isInPlaceModeEnabled()); - ASSERT_OK(doc.root().pushFront(newElt)); - ASSERT_FALSE(doc.isInPlaceModeEnabled()); - } - - TEST(DocumentInPlace, InPlaceModeIsDisabledByPushBack) { - mongo::BSONObj obj = mongo::fromjson("{ foo : 'foo' }"); - mmb::Document doc(obj, mmb::Document::kInPlaceEnabled); - ASSERT_TRUE(doc.isInPlaceModeEnabled()); - mmb::Element newElt = doc.makeElementInt("bar", 42); - ASSERT_TRUE(doc.isInPlaceModeEnabled()); - ASSERT_OK(doc.root().pushBack(newElt)); - ASSERT_FALSE(doc.isInPlaceModeEnabled()); - } - - TEST(DocumentInPlace, InPlaceModeIsDisabledByPopFront) { - mongo::BSONObj obj = mongo::fromjson("{ foo : 'foo' }"); - mmb::Document doc(obj, mmb::Document::kInPlaceEnabled); - ASSERT_TRUE(doc.isInPlaceModeEnabled()); - ASSERT_OK(doc.root().popFront()); - ASSERT_FALSE(doc.isInPlaceModeEnabled()); - } - - TEST(DocumentInPlace, InPlaceModeIsDisabledByPopBack) { - mongo::BSONObj obj = mongo::fromjson("{ foo : 'foo' }"); - mmb::Document doc(obj, mmb::Document::kInPlaceEnabled); - ASSERT_TRUE(doc.isInPlaceModeEnabled()); - ASSERT_OK(doc.root().popBack()); - ASSERT_FALSE(doc.isInPlaceModeEnabled()); - } - - TEST(DocumentInPlace, ReserveDamageEventsIsAlwaysSafeToCall) { - mongo::BSONObj obj = mongo::fromjson("{ foo : 'foo' }"); - mmb::Document doc(obj, mmb::Document::kInPlaceEnabled); - ASSERT_TRUE(doc.isInPlaceModeEnabled()); - doc.reserveDamageEvents(10); - doc.disableInPlaceUpdates(); - ASSERT_FALSE(doc.isInPlaceModeEnabled()); - doc.reserveDamageEvents(10); - } - - TEST(DocumentInPlace, GettingInPlaceUpdatesWhenDisabledClearsArguments) { - mongo::BSONObj obj = mongo::fromjson("{ foo : 'foo' }"); - mmb::Document doc(obj, mmb::Document::kInPlaceDisabled); - ASSERT_FALSE(doc.isInPlaceModeEnabled()); - - mmb::DamageVector damages; - const mmb::DamageEvent event = { 0 }; - damages.push_back(event); - const char* source = "foo"; - ASSERT_FALSE(doc.getInPlaceUpdates(&damages, &source)); - ASSERT_TRUE(damages.empty()); - ASSERT_EQUALS(static_cast<const char*>(NULL), source); - - damages.push_back(event); - source = "bar"; - size_t size = 1; - ASSERT_FALSE(doc.getInPlaceUpdates(&damages, &source, &size)); - ASSERT_TRUE(damages.empty()); - ASSERT_EQUALS(static_cast<const char*>(NULL), source); - ASSERT_EQUALS(0U, size); - } - - // This isn't a great test since we aren't testing all possible combinations of compatible - // and incompatible sets, but since all setValueX calls decay to the internal setValue, we - // can be pretty sure that this will at least check the logic somewhat. - TEST(DocumentInPlace, InPlaceModeIsDisabledByIncompatibleSetValue) { - mongo::BSONObj obj(mongo::fromjson("{ foo : false }")); - mmb::Document doc(obj, mmb::Document::kInPlaceEnabled); - ASSERT_TRUE(doc.isInPlaceModeEnabled()); - mmb::Element foo = doc.root().leftChild(); - ASSERT_TRUE(foo.ok()); - ASSERT_OK(foo.setValueString("foo")); - ASSERT_FALSE(doc.isInPlaceModeEnabled()); - } - - TEST(DocumentInPlace, DisablingInPlaceDoesNotDiscardUpdates) { - mongo::BSONObj obj(mongo::fromjson("{ foo : false, bar : true }")); - mmb::Document doc(obj, mmb::Document::kInPlaceEnabled); - ASSERT_TRUE(doc.isInPlaceModeEnabled()); - - mmb::Element foo = doc.root().leftChild(); - ASSERT_TRUE(foo.ok()); - ASSERT_OK(foo.setValueBool(true)); - ASSERT_TRUE(doc.isInPlaceModeEnabled()); - - mmb::Element bar = doc.root().rightChild(); - ASSERT_TRUE(bar.ok()); - ASSERT_OK(bar.setValueBool(false)); - ASSERT_TRUE(doc.isInPlaceModeEnabled()); - - ASSERT_OK(doc.root().appendString("baz", "baz")); - ASSERT_FALSE(doc.isInPlaceModeEnabled()); - - static const char outJson[] = - "{ foo : true, bar : false, baz : 'baz' }"; - ASSERT_EQUALS(mongo::fromjson(outJson), doc.getObject()); - } - - TEST(DocumentInPlace, StringLifecycle) { - mongo::BSONObj obj(mongo::fromjson("{ x : 'foo' }")); - mmb::Document doc(obj, mmb::Document::kInPlaceEnabled); - - mmb::Element x = doc.root().leftChild(); - - mmb::DamageVector damages; - const char* source = NULL; - - x.setValueString("bar"); - ASSERT_TRUE(doc.getInPlaceUpdates(&damages, &source)); - ASSERT_EQUALS(1U, damages.size()); - apply(&obj, damages, source); - ASSERT_TRUE(x.hasValue()); - ASSERT_TRUE(x.isType(mongo::String)); - ASSERT_EQUALS("bar", x.getValueString()); - - // TODO: When in-place updates for leaf elements is implemented, add tests here. - } - - TEST(DocumentInPlace, BinDataLifecycle) { - const char kData1[] = "\x01\x02\x03\x04\x05\x06"; - const char kData2[] = "\x10\x20\x30\x40\x50\x60"; - - const mongo::BSONBinData binData1(kData1, sizeof(kData1) - 1, mongo::BinDataGeneral); - const mongo::BSONBinData binData2(kData2, sizeof(kData2) - 1, mongo::bdtCustom); - - mongo::BSONObj obj(BSON("x" << binData1)); - mmb::Document doc(obj, mmb::Document::kInPlaceEnabled); - - mmb::Element x = doc.root().leftChild(); - - mmb::DamageVector damages; - const char* source = NULL; - - x.setValueBinary(binData2.length, binData2.type, binData2.data); - ASSERT_TRUE(doc.getInPlaceUpdates(&damages, &source)); - ASSERT_EQUALS(1U, damages.size()); - apply(&obj, damages, source); - ASSERT_TRUE(x.hasValue()); - ASSERT_TRUE(x.isType(mongo::BinData)); - - mongo::BSONElement value = x.getValue(); - ASSERT_EQUALS(binData2.type, value.binDataType()); - int len = 0; - const char* const data = value.binDataClean(len); - ASSERT_EQUALS(binData2.length, len); - ASSERT_EQUALS(0, std::memcmp(data, kData2, len)); - - // TODO: When in-place updates for leaf elements is implemented, add tests here. - } - - TEST(DocumentInPlace, OIDLifecycle) { - const mongo::OID oid1 = mongo::OID::gen(); - const mongo::OID oid2 = mongo::OID::gen(); - ASSERT_NOT_EQUALS(oid1, oid2); - - mongo::BSONObj obj(BSON("x" << oid1)); - mmb::Document doc(obj, mmb::Document::kInPlaceEnabled); - - mmb::Element x = doc.root().leftChild(); - - mmb::DamageVector damages; - const char* source = NULL; - - x.setValueOID(oid2); - ASSERT_TRUE(doc.getInPlaceUpdates(&damages, &source)); - ASSERT_EQUALS(1U, damages.size()); - apply(&obj, damages, source); - ASSERT_TRUE(x.hasValue()); - ASSERT_TRUE(x.isType(mongo::jstOID)); - ASSERT_EQUALS(oid2, x.getValueOID()); - - // TODO: When in-place updates for leaf elements is implemented, add tests here. - } - - TEST(DocumentInPlace, BooleanLifecycle) { - mongo::BSONObj obj(mongo::fromjson("{ x : false }")); - mmb::Document doc(obj, mmb::Document::kInPlaceEnabled); - - mmb::Element x = doc.root().leftChild(); - - mmb::DamageVector damages; - const char* source = NULL; - - x.setValueBool(false); - ASSERT_TRUE(doc.getInPlaceUpdates(&damages, &source)); - ASSERT_EQUALS(1U, damages.size()); - apply(&obj, damages, source); - ASSERT_TRUE(x.hasValue()); - ASSERT_TRUE(x.isType(mongo::Bool)); - ASSERT_EQUALS(false, x.getValueBool()); - - // TODO: Re-enable when in-place updates to leaf elements is supported - // x.setValueBool(true); - // ASSERT_TRUE(doc.getInPlaceUpdates(&damages, &source)); - // apply(&obj, damages, source); - // ASSERT_TRUE(x.hasValue()); - // ASSERT_TRUE(x.isType(mongo::Bool)); - // ASSERT_EQUALS(true, x.getValueBool()); - } - - TEST(DocumentInPlace, DateLifecycle) { - mongo::BSONObj obj(BSON("x" << mongo::Date_t::fromMillisSinceEpoch(1000))); - mmb::Document doc(obj, mmb::Document::kInPlaceEnabled); - - mmb::Element x = doc.root().leftChild(); - - mmb::DamageVector damages; - const char* source = NULL; - - x.setValueDate(mongo::Date_t::fromMillisSinceEpoch(20000)); - ASSERT_TRUE(doc.getInPlaceUpdates(&damages, &source)); - ASSERT_EQUALS(1U, damages.size()); - apply(&obj, damages, source); - ASSERT_TRUE(x.hasValue()); - ASSERT_TRUE(x.isType(mongo::Date)); - ASSERT_EQUALS(mongo::Date_t::fromMillisSinceEpoch(20000), x.getValueDate()); - - // TODO: When in-place updates for leaf elements is implemented, add tests here. - } - - TEST(DocumentInPlace, NumberIntLifecycle) { - const int value1 = 42; - const int value2 = 3; - mongo::BSONObj obj(BSON("x" << value1)); - mmb::Document doc(obj, mmb::Document::kInPlaceEnabled); - - mmb::Element x = doc.root().leftChild(); - - mmb::DamageVector damages; - const char* source = NULL; - - x.setValueInt(value2); - ASSERT_TRUE(doc.getInPlaceUpdates(&damages, &source)); - ASSERT_EQUALS(1U, damages.size()); - apply(&obj, damages, source); - ASSERT_TRUE(x.hasValue()); - ASSERT_TRUE(x.isType(mongo::NumberInt)); - ASSERT_EQUALS(value2, x.getValueInt()); - - // TODO: Re-enable when in-place updates to leaf elements is supported - // x.setValueInt(value1); - // ASSERT_TRUE(doc.getInPlaceUpdates(&damages, &source)); - // apply(&obj, damages, source); - // ASSERT_TRUE(x.hasValue()); - // ASSERT_TRUE(x.isType(mongo::NumberInt)); - // ASSERT_EQUALS(value1, x.getValueInt()); - } - - TEST(DocumentInPlace, TimestampLifecycle) { - mongo::BSONObj obj(BSON("x" << mongo::Timestamp(mongo::Date_t::fromMillisSinceEpoch(1000)))); - mmb::Document doc(obj, mmb::Document::kInPlaceEnabled); - - mmb::Element x = doc.root().leftChild(); - - mmb::DamageVector damages; - const char* source = NULL; - - x.setValueTimestamp(mongo::Timestamp(mongo::Date_t::fromMillisSinceEpoch(20000))); - ASSERT_TRUE(doc.getInPlaceUpdates(&damages, &source)); - ASSERT_EQUALS(1U, damages.size()); - apply(&obj, damages, source); - ASSERT_TRUE(x.hasValue()); - ASSERT_TRUE(x.isType(mongo::bsonTimestamp)); - ASSERT_TRUE(mongo::Timestamp(20000U) == x.getValueTimestamp()); - - // TODO: When in-place updates for leaf elements is implemented, add tests here. - } - - TEST(DocumentInPlace, NumberLongLifecycle) { - const long long value1 = 42; - const long long value2 = 3; - - mongo::BSONObj obj(BSON("x" << value1)); - mmb::Document doc(obj, mmb::Document::kInPlaceEnabled); - - mmb::Element x = doc.root().leftChild(); - - mmb::DamageVector damages; - const char* source = NULL; - - x.setValueLong(value2); - ASSERT_TRUE(doc.getInPlaceUpdates(&damages, &source)); - ASSERT_EQUALS(1U, damages.size()); - apply(&obj, damages, source); - ASSERT_TRUE(x.hasValue()); - ASSERT_TRUE(x.isType(mongo::NumberLong)); - ASSERT_EQUALS(value2, x.getValueLong()); - - // TODO: Re-enable when in-place updates to leaf elements is supported - // x.setValueLong(value1); - // ASSERT_TRUE(doc.getInPlaceUpdates(&damages, &source)); - // apply(&obj, damages, source); - // ASSERT_TRUE(x.hasValue()); - // ASSERT_TRUE(x.isType(mongo::NumberLong)); - // ASSERT_EQUALS(value1, x.getValueLong()); - } - - TEST(DocumentInPlace, NumberDoubleLifecycle) { - const double value1 = 32.0; - const double value2 = 2.0; - - mongo::BSONObj obj(BSON("x" << value1)); - mmb::Document doc(obj, mmb::Document::kInPlaceEnabled); - - mmb::Element x = doc.root().leftChild(); - - mmb::DamageVector damages; - const char* source = NULL; - - x.setValueDouble(value2); - ASSERT_TRUE(doc.getInPlaceUpdates(&damages, &source)); - ASSERT_EQUALS(1U, damages.size()); - apply(&obj, damages, source); - ASSERT_TRUE(x.hasValue()); - ASSERT_TRUE(x.isType(mongo::NumberDouble)); - ASSERT_EQUALS(value2, x.getValueDouble()); - - // TODO: Re-enable when in-place updates to leaf elements is supported - // x.setValueDouble(value1); - // ASSERT_TRUE(doc.getInPlaceUpdates(&damages, &source)); - // apply(&obj, damages, source); - // ASSERT_TRUE(x.hasValue()); - // ASSERT_TRUE(x.isType(mongo::NumberDouble)); - // ASSERT_EQUALS(value1, x.getValueDouble()); - } - - // Doubles and longs are the same size, 8 bytes, so we should be able to do in-place - // updates between them. - TEST(DocumentInPlace, DoubleToLongAndBack) { - const double value1 = 32.0; - const long long value2 = 42; - - mongo::BSONObj obj(BSON("x" << value1)); - mmb::Document doc(obj, mmb::Document::kInPlaceEnabled); - - mmb::Element x = doc.root().leftChild(); - - mmb::DamageVector damages; - const char* source = NULL; - - x.setValueLong(value2); - ASSERT_TRUE(doc.getInPlaceUpdates(&damages, &source)); - // We changed the type, so we get an extra damage event. - ASSERT_EQUALS(2U, damages.size()); - apply(&obj, damages, source); - ASSERT_TRUE(x.hasValue()); - ASSERT_TRUE(x.isType(mongo::NumberLong)); - ASSERT_EQUALS(value2, x.getValueLong()); - - // TODO: Re-enable when in-place updates to leaf elements is supported - // x.setValueDouble(value1); - // ASSERT_TRUE(doc.getInPlaceUpdates(&damages, &source)); - // apply(&obj, damages, source); - // ASSERT_TRUE(x.hasValue()); - // ASSERT_TRUE(x.isType(mongo::NumberDouble)); - // ASSERT_EQUALS(value1, x.getValueDouble()); - } - - TEST(DocumentComparison, SimpleComparison) { - const mongo::BSONObj obj = - mongo::fromjson("{ a : 'a', b : ['b', 'b', 'b'], c : { one : 1.0 } }"); - - const mmb::Document doc1(obj.getOwned()); - ASSERT_EQUALS(0, doc1.compareWithBSONObj(obj)); - const mmb::Document doc2(obj.getOwned()); - ASSERT_EQUALS(0, doc1.compareWith(doc2)); - ASSERT_EQUALS(0, doc2.compareWith(doc1)); - } - - TEST(DocumentComparison, SimpleComparisonWithDeserializedElements) { - const mongo::BSONObj obj = - mongo::fromjson("{ a : 'a', b : ['b', 'b', 'b'], c : { one : 1.0 } }"); - - // Perform an operation on 'b' that doesn't change the serialized value, but - // deserializes the node. - mmb::Document doc1(obj.getOwned()); - const mmb::Document doc1Copy(obj.getOwned()); - mmb::Element b = doc1.root()["b"]; +namespace { +void apply(mongo::BSONObj* obj, const mmb::DamageVector& damages, const char* source) { + const mmb::DamageVector::const_iterator end = damages.end(); + mmb::DamageVector::const_iterator where = damages.begin(); + char* const target = const_cast<char*>(obj->objdata()); + for (; where != end; ++where) { + std::memcpy(target + where->targetOffset, source + where->sourceOffset, where->size); + } +} +} // namespace + +TEST(Documentation, Example2InPlaceWithDamageVector) { + static const char inJson[] = + "{" + " 'whale': { 'alive': true, 'dv': -9.8, 'height': 50.0, attrs : [ 'big' ] }," + " 'petunias': { 'alive': true, 'dv': -9.8, 'height': 50.0 } " + "}"; + + // Make the object, and make a copy for reference. + mongo::BSONObj obj = mongo::fromjson(inJson); + const mongo::BSONObj copyOfObj = obj.getOwned(); + ASSERT_EQUALS(obj, copyOfObj); + + // Create a new document representing BSONObj with the above contents. + mmb::Document doc(obj, mmb::Document::kInPlaceEnabled); + ASSERT_EQUALS(obj, doc); + ASSERT_EQUALS(copyOfObj, doc); + + // Enable in-place mutation for this document + ASSERT_EQUALS(mmb::Document::kInPlaceEnabled, doc.getCurrentInPlaceMode()); + + // The whale hits the planet and dies. + mmb::Element whale = mmb::findFirstChildNamed(doc.root(), "whale"); + ASSERT_TRUE(whale.ok()); + // Find the 'dv' field in the whale. + mmb::Element whale_deltav = mmb::findFirstChildNamed(whale, "dv"); + ASSERT_TRUE(whale_deltav.ok()); + // Set the dv field to zero. + ASSERT_OK(whale_deltav.setValueDouble(0.0)); + // Find the 'height' field in the whale. + mmb::Element whale_height = mmb::findFirstChildNamed(whale, "height"); + ASSERT_TRUE(whale_height.ok()); + // Set the height field to zero. + ASSERT_OK(whale_height.setValueDouble(0)); + // Find the 'alive' field, and set it to false. + mmb::Element whale_alive = mmb::findFirstChildNamed(whale, "alive"); + ASSERT_TRUE(whale_alive.ok()); + ASSERT_OK(whale_alive.setValueBool(false)); + + // The petunias survive, update its fields much like we did above. + mmb::Element petunias = mmb::findFirstChildNamed(doc.root(), "petunias"); + ASSERT_TRUE(petunias.ok()); + mmb::Element petunias_deltav = mmb::findFirstChildNamed(petunias, "dv"); + ASSERT_TRUE(petunias_deltav.ok()); + ASSERT_OK(petunias_deltav.setValueDouble(0.0)); + mmb::Element petunias_height = mmb::findFirstChildNamed(petunias, "height"); + ASSERT_TRUE(petunias_height.ok()); + ASSERT_OK(petunias_height.setValueDouble(0)); + + // Demonstrate that while the document has changed, the underlying BSONObj has not yet + // changed. + ASSERT_FALSE(obj == doc); + ASSERT_EQUALS(copyOfObj, obj); + + // Ensure that in-place updates are still enabled. + ASSERT_EQUALS(mmb::Document::kInPlaceEnabled, doc.getCurrentInPlaceMode()); + + // Extract the damage events + mmb::DamageVector damages; + const char* source = NULL; + size_t size = 0; + ASSERT_EQUALS(true, doc.getInPlaceUpdates(&damages, &source, &size)); + ASSERT_NOT_EQUALS(0U, damages.size()); + ASSERT_NOT_EQUALS(static_cast<const char*>(NULL), source); + ASSERT_NOT_EQUALS(0U, size); + + apply(&obj, damages, source); + + static const char outJson[] = + "{" + " 'whale': { 'alive': false, 'dv': 0, 'height': 0, attrs : [ 'big' ] }," + " 'petunias': { 'alive': true, 'dv': 0, 'height': 0 } " + "}"; + mongo::BSONObj outObj = mongo::fromjson(outJson); + + ASSERT_EQUALS(outObj, doc); + + mongo::BSONObjBuilder builder; + doc.writeTo(&builder); + ASSERT_EQUALS(mongo::fromjson(outJson), doc.getObject()); +} + +TEST(Documentation, Example3) { + static const char inJson[] = + "{" + " 'xs': { 'x' : 'x', 'X' : 'X' }," + " 'ys': { 'y' : 'y' }" + "}"; + mongo::BSONObj inObj = mongo::fromjson(inJson); + + mmb::Document doc(inObj); + mmb::Element xs = doc.root().leftChild(); + ASSERT_TRUE(xs.ok()); + mmb::Element ys = xs.rightSibling(); + ASSERT_TRUE(ys.ok()); + mmb::Element dne = ys.rightSibling(); + ASSERT_FALSE(dne.ok()); + mmb::Element ycaps = doc.makeElementString("Y", "Y"); + ASSERT_OK(ys.pushBack(ycaps)); + mmb::Element pun = doc.makeElementArray("why"); + ASSERT_OK(ys.pushBack(pun)); + pun.appendString("na", "not"); + mongo::BSONObj outObj = doc.getObject(); + + static const char outJson[] = + "{" + " 'xs': { 'x' : 'x', 'X' : 'X' }," + " 'ys': { 'y' : 'y', 'Y' : 'Y', 'why' : ['not'] }" + "}"; + ASSERT_EQUALS(mongo::fromjson(outJson), outObj); +} + +TEST(Document, LifecycleConstructDefault) { + // Verify the state of a newly created empty Document. + mmb::Document doc; + ASSERT_TRUE(doc.root().ok()); + ASSERT_TRUE(const_cast<const mmb::Document&>(doc).root().ok()); + ASSERT_TRUE(doc.root().isType(mongo::Object)); + ASSERT_FALSE(doc.root().leftSibling().ok()); + ASSERT_FALSE(doc.root().rightSibling().ok()); + ASSERT_FALSE(doc.root().leftChild().ok()); + ASSERT_FALSE(doc.root().rightChild().ok()); + ASSERT_FALSE(doc.root().parent().ok()); + ASSERT_FALSE(doc.root().hasValue()); +} + +TEST(Document, LifecycleConstructEmptyBSONObj) { + // Verify the state of a newly created empty Document where the construction argument + // is an empty BSONObj. + mongo::BSONObj obj; + mmb::Document doc(obj); + ASSERT_TRUE(doc.root().ok()); + ASSERT_TRUE(const_cast<const mmb::Document&>(doc).root().ok()); + ASSERT_TRUE(doc.root().isType(mongo::Object)); + ASSERT_FALSE(doc.root().leftSibling().ok()); + ASSERT_FALSE(doc.root().rightSibling().ok()); + ASSERT_FALSE(doc.root().leftChild().ok()); + ASSERT_FALSE(doc.root().rightChild().ok()); + ASSERT_FALSE(doc.root().parent().ok()); + ASSERT_FALSE(doc.root().hasValue()); +} + +TEST(Document, LifecycleConstructSimpleBSONObj) { + // Verify the state of a newly created Document where the construction argument is a + // simple (flat) BSONObj. + mongo::BSONObj obj = mongo::fromjson("{ e1: 1, e2: 'hello', e3: false }"); + mmb::Document doc(obj); + + // Check the state of the root. + ASSERT_TRUE(doc.root().ok()); + ASSERT_TRUE(const_cast<const mmb::Document&>(doc).root().ok()); + ASSERT_TRUE(doc.root().isType(mongo::Object)); + ASSERT_FALSE(doc.root().parent().ok()); + ASSERT_FALSE(doc.root().leftSibling().ok()); + ASSERT_FALSE(doc.root().rightSibling().ok()); + ASSERT_FALSE(doc.root().hasValue()); + + mmb::ConstElement e1Child = doc.root().leftChild(); + // Check the connectivity of 'e1'. + ASSERT_TRUE(e1Child.ok()); + ASSERT_EQUALS(doc.root(), e1Child.parent()); + ASSERT_FALSE(e1Child.leftSibling().ok()); + ASSERT_TRUE(e1Child.rightSibling().ok()); + ASSERT_FALSE(e1Child.leftChild().ok()); + ASSERT_FALSE(e1Child.rightChild().ok()); + + // Check the type, name, and value of 'e1'. + ASSERT_TRUE(e1Child.isType(mongo::NumberInt)); + ASSERT_EQUALS("e1", e1Child.getFieldName()); + ASSERT_TRUE(e1Child.hasValue()); + ASSERT_EQUALS(int32_t(1), e1Child.getValueInt()); + + mmb::ConstElement e2Child = e1Child.rightSibling(); + // Check the connectivity of 'e2'. + ASSERT_TRUE(e2Child.ok()); + ASSERT_EQUALS(doc.root(), e2Child.parent()); + ASSERT_TRUE(e2Child.leftSibling().ok()); + ASSERT_TRUE(e2Child.rightSibling().ok()); + ASSERT_EQUALS(e1Child, e2Child.leftSibling()); + ASSERT_FALSE(e2Child.leftChild().ok()); + ASSERT_FALSE(e2Child.rightChild().ok()); + + // Check the type, name and value of 'e2'. + ASSERT_TRUE(e2Child.isType(mongo::String)); + ASSERT_EQUALS("e2", e2Child.getFieldName()); + ASSERT_TRUE(e2Child.hasValue()); + ASSERT_EQUALS("hello", e2Child.getValueString()); + + mmb::ConstElement e3Child = e2Child.rightSibling(); + // Check the connectivity of 'e3'. + ASSERT_TRUE(e3Child.ok()); + ASSERT_EQUALS(doc.root(), e3Child.parent()); + ASSERT_TRUE(e3Child.leftSibling().ok()); + ASSERT_FALSE(e3Child.rightSibling().ok()); + ASSERT_EQUALS(e2Child, e3Child.leftSibling()); + ASSERT_FALSE(e2Child.leftChild().ok()); + ASSERT_FALSE(e2Child.rightChild().ok()); + + // Check the type, name and value of 'e3'. + ASSERT_TRUE(e3Child.isType(mongo::Bool)); + ASSERT_EQUALS("e3", e3Child.getFieldName()); + ASSERT_TRUE(e3Child.hasValue()); + ASSERT_EQUALS(false, e3Child.getValueBool()); +} + +TEST(Document, RenameDeserialization) { + // Regression test for a bug where certain rename operations failed to deserialize up + // the tree correctly, resulting in a lost rename + static const char inJson[] = + "{" + " 'a' : { 'b' : { 'c' : { 'd' : 4 } } }" + "}"; + mongo::BSONObj inObj = mongo::fromjson(inJson); + + mmb::Document doc(inObj); + mmb::Element a = doc.root().leftChild(); + ASSERT_TRUE(a.ok()); + mmb::Element b = a.leftChild(); + ASSERT_TRUE(b.ok()); + mmb::Element c = b.leftChild(); + ASSERT_TRUE(c.ok()); + c.rename("C"); + mongo::BSONObj outObj = doc.getObject(); + static const char outJson[] = + "{" + " 'a' : { 'b' : { 'C' : { 'd' : 4 } } }" + "}"; + ASSERT_EQUALS(mongo::fromjson(outJson), outObj); +} + +TEST(Document, CantRenameRootElement) { + mmb::Document doc; + ASSERT_NOT_OK(doc.root().rename("foo")); +} + +TEST(Document, RemoveElementWithOpaqueRightSibling) { + // Regression test for a bug where removing an element with an opaque right sibling + // would access an invalidated rep. Note that this test may or may not fail depending + // on the details of memory allocation: failures would be clearly visible with + // valgrind, however. + static const char inJson[] = + "{" + " 'a' : 1, 'b' : 2, 'c' : 3" + "}"; + + mongo::BSONObj inObj = mongo::fromjson(inJson); + mmb::Document doc(inObj); + + mmb::Element a = doc.root().leftChild(); + ASSERT_TRUE(a.ok()); + a.remove(); + + static const char outJson[] = + "{" + " 'b' : 2, 'c' : 3" + "}"; + mongo::BSONObj outObj = doc.getObject(); + ASSERT_EQUALS(mongo::fromjson(outJson), outObj); +} + +TEST(Document, AddRightSiblingToElementWithOpaqueRightSibling) { + // Regression test for a bug where adding a right sibling to a node with an opaque + // right sibling would potentially access an invalidated rep. Like the 'remove' test + // above, this may or may not crash, but would be visible under a memory checking tool. + static const char inJson[] = + "{" + " 'a' : 1, 'b' : 2, 'c' : 3" + "}"; + + mongo::BSONObj inObj = mongo::fromjson(inJson); + mmb::Document doc(inObj); + + mmb::Element a = doc.root().leftChild(); + ASSERT_TRUE(a.ok()); + mmb::Element newElt = doc.makeElementString("X", "X"); + ASSERT_OK(a.addSiblingRight(newElt)); + + static const char outJson[] = + "{" + " 'a' : 1, 'X' : 'X', 'b' : 2, 'c' : 3" + "}"; + mongo::BSONObj outObj = doc.getObject(); + ASSERT_EQUALS(mongo::fromjson(outJson), outObj); +} + +TEST(Document, ArrayIndexedAccessFromJson) { + static const char inJson[] = + "{" + " a : 1, b : [{ c : 1 }]" + "}"; + + mongo::BSONObj inObj = mongo::fromjson(inJson); + mmb::Document doc(inObj); + + mmb::Element a = doc.root().leftChild(); + ASSERT_TRUE(a.ok()); + ASSERT_EQUALS("a", a.getFieldName()); + ASSERT_EQUALS(mongo::NumberInt, a.getType()); + + mmb::Element b = a.rightSibling(); + ASSERT_TRUE(b.ok()); + ASSERT_EQUALS("b", b.getFieldName()); + ASSERT_EQUALS(mongo::Array, b.getType()); + + mmb::Element b0 = b[0]; + ASSERT_TRUE(b0.ok()); + ASSERT_EQUALS("0", b0.getFieldName()); + ASSERT_EQUALS(mongo::Object, b0.getType()); +} + +TEST(Document, ArrayIndexedAccessFromManuallyBuilt) { + mmb::Document doc; + mmb::Element root = doc.root(); + ASSERT_TRUE(root.ok()); + { + ASSERT_OK(root.appendInt("a", 1)); + mmb::Element b = doc.makeElementArray("b"); ASSERT_TRUE(b.ok()); - mmb::Element b0 = b[0]; + ASSERT_OK(root.pushBack(b)); + mmb::Element b0 = doc.makeElementObject("ignored"); ASSERT_TRUE(b0.ok()); - ASSERT_OK(b0.remove()); ASSERT_OK(b.pushBack(b0)); - // Ensure that it compares correctly against the source object. - ASSERT_EQUALS(0, doc1.compareWithBSONObj(obj)); - // Ensure that it compares correctly against a pristine document. - ASSERT_EQUALS(0, doc1.compareWith(doc1Copy)); - ASSERT_EQUALS(0, doc1Copy.compareWith(doc1)); - - // Perform an operation on 'c' that doesn't change the serialized value, but - // deserializeds the node. - mmb::Document doc2(obj.getOwned()); - const mmb::Document doc2Copy(obj.getOwned()); - mmb::Element c = doc2.root()["c"]; - ASSERT_TRUE(c.ok()); - mmb::Element c1 = c.leftChild(); - ASSERT_TRUE(c1.ok()); - ASSERT_OK(c1.remove()); - ASSERT_OK(c.pushBack(c1)); - // Ensure that it compares correctly against the source object - ASSERT_EQUALS(0, doc2.compareWithBSONObj(obj)); - // Ensure that it compares correctly against a pristine document. - ASSERT_EQUALS(0, doc2.compareWith(doc2Copy)); - ASSERT_EQUALS(0, doc2Copy.compareWith(doc2)); - - // Ensure that the two deserialized documents compare with each other correctly. - ASSERT_EQUALS(0, doc1.compareWith(doc2)); - ASSERT_EQUALS(0, doc2.compareWith(doc1)); - } - - TEST(UnorderedEqualityChecker, Identical) { - const mongo::BSONObj b1 = mongo::fromjson( - "{ a : [1, 2, { 'a' : 'b', 'x' : 'y' } ], b : { x : 1, y : 2, z : 3 } }"); - - const mongo::BSONObj b2 = b1.getOwned(); + ASSERT_OK(b0.appendInt("c", 1)); + } + + mmb::Element a = doc.root().leftChild(); + ASSERT_TRUE(a.ok()); + ASSERT_EQUALS("a", a.getFieldName()); + ASSERT_EQUALS(mongo::NumberInt, a.getType()); + + mmb::Element b = a.rightSibling(); + ASSERT_TRUE(b.ok()); + ASSERT_EQUALS("b", b.getFieldName()); + ASSERT_EQUALS(mongo::Array, b.getType()); + + mmb::Element b0 = b[0]; + ASSERT_TRUE(b0.ok()); + ASSERT_EQUALS("ignored", b0.getFieldName()); + ASSERT_EQUALS(mongo::Object, b0.getType()); +} + +TEST(Document, EndElement) { + mmb::Document doc; + mmb::Element end = doc.end(); + ASSERT_FALSE(end.ok()); + mmb::Element missing = doc.root().leftChild(); + ASSERT_EQUALS(end, missing); + missing = doc.root().rightChild(); + ASSERT_EQUALS(end, missing); + missing = doc.root().leftSibling(); + ASSERT_EQUALS(end, missing); + missing = doc.root().rightSibling(); + ASSERT_EQUALS(end, missing); +} + +TEST(Document, ConstEndElement) { + const mmb::Document doc; + mmb::ConstElement end = doc.end(); + ASSERT_FALSE(end.ok()); + mmb::ConstElement missing = doc.root().leftChild(); + ASSERT_EQUALS(end, missing); + missing = doc.root().rightChild(); + ASSERT_EQUALS(end, missing); + missing = doc.root().leftSibling(); + ASSERT_EQUALS(end, missing); + missing = doc.root().rightSibling(); + ASSERT_EQUALS(end, missing); +} + +TEST(Element, EmptyDocHasNoChildren) { + mmb::Document doc; + ASSERT_FALSE(doc.root().hasChildren()); +} + +TEST(Element, PopulatedDocHasChildren) { + mmb::Document doc; + ASSERT_OK(doc.root().appendInt("a", 1)); + ASSERT_TRUE(doc.root().hasChildren()); + mmb::Element lc = doc.root().leftChild(); + ASSERT_FALSE(lc.hasChildren()); +} + +TEST(Element, LazyEmptyDocHasNoChildren) { + static const char inJson[] = "{}"; + mongo::BSONObj inObj = mongo::fromjson(inJson); + mmb::Document doc(inObj); + ASSERT_FALSE(doc.root().hasChildren()); +} + +TEST(Element, LazySingletonDocHasChildren) { + static const char inJson[] = "{ a : 1 }"; + mongo::BSONObj inObj = mongo::fromjson(inJson); + mmb::Document doc(inObj); + ASSERT_TRUE(doc.root().hasChildren()); + ASSERT_FALSE(doc.root().leftChild().hasChildren()); +} + +TEST(Element, LazyConstDoubletonDocHasChildren) { + static const char inJson[] = "{ a : 1, b : 2 }"; + mongo::BSONObj inObj = mongo::fromjson(inJson); + const mmb::Document doc(inObj); + ASSERT_TRUE(doc.root().hasChildren()); + ASSERT_FALSE(doc.root().leftChild().hasChildren()); + ASSERT_FALSE(doc.root().rightChild().hasChildren()); + ASSERT_FALSE(doc.root().leftChild() == doc.root().rightChild()); +} + +TEST(Document, AddChildToEmptyOpaqueSubobject) { + mongo::BSONObj inObj = mongo::fromjson("{a: {}}"); + mmb::Document doc(inObj); + + mmb::Element elem = doc.root()["a"]; + ASSERT_TRUE(elem.ok()); + + mmb::Element newElem = doc.makeElementInt("0", 1); + ASSERT_TRUE(newElem.ok()); + + ASSERT_OK(elem.pushBack(newElem)); +} + +TEST(Element, IsNumeric) { + mmb::Document doc; + + mmb::Element elt = doc.makeElementNull("dummy"); + ASSERT_FALSE(elt.isNumeric()); + + elt = doc.makeElementInt("dummy", 42); + ASSERT_TRUE(elt.isNumeric()); + + elt = doc.makeElementString("dummy", "dummy"); + ASSERT_FALSE(elt.isNumeric()); + + elt = doc.makeElementLong("dummy", 42); + ASSERT_TRUE(elt.isNumeric()); + + elt = doc.makeElementBool("dummy", false); + ASSERT_FALSE(elt.isNumeric()); + + elt = doc.makeElementDouble("dummy", 42.0); + ASSERT_TRUE(elt.isNumeric()); +} + +TEST(Element, IsIntegral) { + mmb::Document doc; + + mmb::Element elt = doc.makeElementNull("dummy"); + ASSERT_FALSE(elt.isIntegral()); + + elt = doc.makeElementInt("dummy", 42); + ASSERT_TRUE(elt.isIntegral()); + + elt = doc.makeElementString("dummy", "dummy"); + ASSERT_FALSE(elt.isIntegral()); + + elt = doc.makeElementLong("dummy", 42); + ASSERT_TRUE(elt.isIntegral()); + + elt = doc.makeElementDouble("dummy", 42.0); + ASSERT_FALSE(elt.isIntegral()); +} + +TEST(Document, ArraySerialization) { + static const char inJson[] = + "{ " + " 'a' : { 'b' : [ 'c', 'd' ] } " + "}"; + + mongo::BSONObj inObj = mongo::fromjson(inJson); + mmb::Document doc(inObj); + + mmb::Element root = doc.root(); + mmb::Element a = root.leftChild(); + mmb::Element b = a.leftChild(); + mmb::Element new_array = doc.makeElementArray("XXX"); + mmb::Element e = doc.makeElementString("e", "e"); + new_array.pushBack(e); + b.pushBack(new_array); + + static const char outJson[] = + "{ " + " 'a' : { 'b' : [ 'c', 'd', [ 'e' ] ] } " + "}"; + + const mongo::BSONObj outObj = doc.getObject(); + ASSERT_EQUALS(mongo::fromjson(outJson), outObj); +} + +TEST(Document, SetValueBSONElementFieldNameHandling) { + static const char inJson[] = "{ a : 4 }"; + mongo::BSONObj inObj = mongo::fromjson(inJson); + mmb::Document doc(inObj); + + static const char inJson2[] = "{ b : 5 }"; + mongo::BSONObj inObj2 = mongo::fromjson(inJson2); + mongo::BSONObjIterator iterator = inObj2.begin(); + + ASSERT_TRUE(iterator.more()); + const mongo::BSONElement b = iterator.next(); + + mmb::Element a = doc.root().leftChild(); + a.setValueBSONElement(b); + + static const char outJson[] = "{ a : 5 }"; + ASSERT_EQUALS(mongo::fromjson(outJson), doc.getObject()); +} + +TEST(Document, CreateElementWithEmptyFieldName) { + mmb::Document doc; + mmb::Element noname = doc.makeElementObject(mongo::StringData()); + ASSERT_TRUE(noname.ok()); + ASSERT_EQUALS(mongo::StringData(), noname.getFieldName()); +} + +TEST(Document, CreateElementFromBSONElement) { + mongo::BSONObj obj = mongo::fromjson("{a:1}}"); + mmb::Document doc; + ASSERT_OK(doc.root().appendElement(obj["a"])); + + mmb::Element newElem = doc.root()["a"]; + ASSERT_TRUE(newElem.ok()); + ASSERT_EQUALS(newElem.getType(), mongo::NumberInt); + ASSERT_EQUALS(newElem.getValueInt(), 1); +} + +TEST(Document, toStringEmpty) { + mongo::BSONObj obj; + mmb::Document doc; + ASSERT_EQUALS(obj.toString(), doc.toString()); +} + +TEST(Document, toStringComplex) { + mongo::BSONObj obj = mongo::fromjson("{a : 1, b : [1, 2, 3], c : 'c'}"); + mmb::Document doc(obj); + ASSERT_EQUALS(obj.toString(), doc.toString()); +} + +TEST(Document, toStringEphemeralObject) { + mmb::Document doc; + mmb::Element e = doc.makeElementObject("foo"); + ASSERT_OK(doc.root().pushBack(e)); + ASSERT_OK(e.appendDouble("d", 1.0)); + ASSERT_OK(e.appendString("s", "str")); + ASSERT_EQUALS(mongo::fromjson("{ foo: { d : 1.0, s : 'str' } }").firstElement().toString(), + e.toString()); +} + +TEST(Document, toStringEphemeralArray) { + mmb::Document doc; + mmb::Element e = doc.makeElementArray("foo"); + ASSERT_OK(doc.root().pushBack(e)); + ASSERT_OK(e.appendDouble(mongo::StringData(), 1.0)); + ASSERT_OK(e.appendString(mongo::StringData(), "str")); + ASSERT_EQUALS(mongo::fromjson("{ foo: [ 1.0, 'str' ] }").firstElement().toString(), + e.toString()); +} + +TEST(Document, ElementCloningToDifferentDocument) { + const char initial[] = "{ a : 1, b : [ 1, 2, 3 ], c : { 'c' : 'c' }, d : [ 4, 5, 6 ] }"; + + mmb::Document source(mongo::fromjson(initial)); + + // Dirty the 'd' node and parents. + source.root()["d"].pushBack(source.makeElementInt(mongo::StringData(), 7)); + + mmb::Document target; + + mmb::Element newElement = target.makeElement(source.root()["d"]); + ASSERT_TRUE(newElement.ok()); + mongo::Status status = target.root().pushBack(newElement); + ASSERT_OK(status); + const char* expected = "{ d : [ 4, 5, 6, 7 ] }"; + ASSERT_EQUALS(mongo::fromjson(expected), target); + + newElement = target.makeElement(source.root()["b"]); + ASSERT_TRUE(newElement.ok()); + status = target.root().pushBack(newElement); + ASSERT_OK(status); + expected = "{ d : [ 4, 5, 6, 7 ], b : [ 1, 2, 3 ] }"; + ASSERT_EQUALS(mongo::fromjson(expected), target); + + newElement = target.makeElementWithNewFieldName("C", source.root()["c"]); + ASSERT_TRUE(newElement.ok()); + status = target.root().pushBack(newElement); + ASSERT_OK(status); + expected = "{ d : [ 4, 5, 6, 7 ], b : [ 1, 2, 3 ], C : { 'c' : 'c' } }"; + ASSERT_EQUALS(mongo::fromjson(expected), target); +} + +TEST(Document, ElementCloningToSameDocument) { + const char initial[] = "{ a : 1, b : [ 1, 2, 3 ], c : { 'c' : 'c' }, d : [ 4, 5, 6 ] }"; + + mmb::Document doc(mongo::fromjson(initial)); + + // Dirty the 'd' node and parents. + doc.root()["d"].pushBack(doc.makeElementInt(mongo::StringData(), 7)); + + mmb::Element newElement = doc.makeElement(doc.root()["d"]); + ASSERT_TRUE(newElement.ok()); + mongo::Status status = doc.root().pushBack(newElement); + ASSERT_OK(status); + const char* expected = + "{ " + " a : 1, b : [ 1, 2, 3 ], c : { 'c' : 'c' }, d : [ 4, 5, 6, 7 ], " + " d : [ 4, 5, 6, 7 ] " + "}"; + ASSERT_EQUALS(mongo::fromjson(expected), doc); + + newElement = doc.makeElement(doc.root()["b"]); + ASSERT_TRUE(newElement.ok()); + status = doc.root().pushBack(newElement); + ASSERT_OK(status); + expected = + "{ " + " a : 1, b : [ 1, 2, 3 ], c : { 'c' : 'c' }, d : [ 4, 5, 6, 7 ], " + " d : [ 4, 5, 6, 7 ], " + " b : [ 1, 2, 3 ] " + "}"; + ASSERT_EQUALS(mongo::fromjson(expected), doc); + + newElement = doc.makeElementWithNewFieldName("C", doc.root()["c"]); + ASSERT_TRUE(newElement.ok()); + status = doc.root().pushBack(newElement); + ASSERT_OK(status); + expected = + "{ " + " a : 1, b : [ 1, 2, 3 ], c : { 'c' : 'c' }, d : [ 4, 5, 6, 7 ], " + " d : [ 4, 5, 6, 7 ], " + " b : [ 1, 2, 3 ], " + " C : { 'c' : 'c' } " + "}"; + ASSERT_EQUALS(mongo::fromjson(expected), doc); +} + +TEST(Document, RootCloningToDifferentDocument) { + const char initial[] = "{ a : 1, b : [ 1, 2, 3 ], c : { 'c' : 'c' }, d : [ 4, 5, 6 ] }"; + + mmb::Document source(mongo::fromjson(initial)); + + // Dirty the 'd' node and parents. + source.root()["d"].pushBack(source.makeElementInt(mongo::StringData(), 7)); + + mmb::Document target; + + mmb::Element newElement = target.makeElementWithNewFieldName("X", source.root()); + mongo::Status status = target.root().pushBack(newElement); + ASSERT_OK(status); + const char expected[] = + "{ X : { a : 1, b : [ 1, 2, 3 ], c : { 'c' : 'c' }, d : [ 4, 5, 6, 7 ] } }"; + + ASSERT_EQUALS(mongo::fromjson(expected), target); +} + +TEST(Document, RootCloningToSameDocument) { + const char initial[] = "{ a : 1, b : [ 1, 2, 3 ], c : { 'c' : 'c' }, d : [ 4, 5, 6 ] }"; + + mmb::Document doc(mongo::fromjson(initial)); + + // Dirty the 'd' node and parents. + doc.root()["d"].pushBack(doc.makeElementInt(mongo::StringData(), 7)); + + mmb::Element newElement = doc.makeElementWithNewFieldName("X", doc.root()); + mongo::Status status = doc.root().pushBack(newElement); + ASSERT_OK(status); + const char expected[] = + "{ " + " a : 1, b : [ 1, 2, 3 ], c : { 'c' : 'c' }, d : [ 4, 5, 6, 7 ], " + "X : { a : 1, b : [ 1, 2, 3 ], c : { 'c' : 'c' }, d : [ 4, 5, 6, 7 ] }" + "}"; + + ASSERT_EQUALS(mongo::fromjson(expected), doc); +} + +TEST(Element, PopOpsOnEmpty) { + mmb::Document doc; + mmb::Element root = doc.root(); + ASSERT_NOT_OK(root.popFront()); + ASSERT_NOT_OK(root.popBack()); +} + +TEST(Document, NameOfRootElementIsEmpty) { + mmb::Document doc; + // NOTE: You really shouldn't rely on this behavior; this test is mostly for coverage. + ASSERT_EQUALS(mongo::StringData(), doc.root().getFieldName()); +} + +TEST(Document, SetValueOnRootFails) { + mmb::Document doc; + ASSERT_NOT_OK(doc.root().setValueInt(5)); +} + +TEST(Document, ValueOfEphemeralObjectElementIsEmpty) { + mmb::Document doc; + mmb::Element root = doc.root(); + mmb::Element ephemeralObject = doc.makeElementObject("foo"); + ASSERT_OK(root.pushBack(ephemeralObject)); + ASSERT_FALSE(ephemeralObject.hasValue()); + // NOTE: You really shouldn't rely on this behavior; this test is mostly for coverage. + ASSERT_EQUALS(mongo::BSONElement(), ephemeralObject.getValue()); +} + +TEST(Element, RemovingRemovedElementFails) { + // Once an Element is removed, you can't remove it again until you re-attach it + // somewhere. However, its children are still manipulable. + mmb::Document doc(mongo::fromjson("{ a : { b : 'c' } }")); + mmb::Element a = doc.root().leftChild(); + ASSERT_TRUE(a.ok()); + ASSERT_OK(a.remove()); + ASSERT_NOT_OK(a.remove()); + mmb::Element b = a.leftChild(); + ASSERT_OK(b.remove()); + ASSERT_NOT_OK(b.remove()); + ASSERT_OK(a.pushBack(b)); + ASSERT_OK(b.remove()); +} - ASSERT_EQUALS(mmb::unordered(b1), mmb::unordered(b2)); - } - - TEST(UnorderedEqualityChecker, DifferentValuesAreNotEqual) { - const mongo::BSONObj b1 = mongo::fromjson( - "{ a : [ 1, 2, { 'a' : 'b', 'x' : 'y' } ], b : { x : 1, y : 2, z : 3 } }"); - const mongo::BSONObj b2 = mongo::fromjson( - "{ a : [ 1, 2, { 'a' : 'b', 'x' : 'y' } ], b : { x : 1, y : 2, z : 4 } }"); - - ASSERT_NOT_EQUALS(mmb::unordered(b1), mmb::unordered(b2)); - } - - TEST(UnorderedEqualityChecker, DifferentTypesAreNotEqual) { - const mongo::BSONObj b1 = mongo::fromjson( - "{ a : [ 1, 2, { 'a' : 'b', 'x' : 'y' } ], b : { x : 1, y : 2, z : 3 } }"); - const mongo::BSONObj b2 = mongo::fromjson( - "{ a : [ 1, 2, { 'a' : 'b', 'x' : 'y' } ], b : { x : 1, y : '2', z : 3 } }"); - - ASSERT_NOT_EQUALS(mmb::unordered(b1), mmb::unordered(b2)); - } - - TEST(UnorderedEqualityChecker, DifferentFieldNamesAreNotEqual) { - const mongo::BSONObj b1 = mongo::fromjson( - "{ a : [ 1, 2, { 'a' : 'b', 'x' : 'y' } ], b : { x : 1, y : 2, z : 3 } }"); - const mongo::BSONObj b2 = mongo::fromjson( - "{ a : [ 1, 2, { 'a' : 'b', 'x' : 'y' } ], b : { x : 1, Y : 2, z : 3 } }"); - - ASSERT_NOT_EQUALS(mmb::unordered(b1), mmb::unordered(b2)); - } - - TEST(UnorderedEqualityChecker, MissingFieldsInObjectAreNotEqual) { - const mongo::BSONObj b1 = mongo::fromjson( - "{ a : [ 1, 2, { 'a' : 'b', 'x' : 'y' } ], b : { x : 1, y : 2, z : 3 } }"); - const mongo::BSONObj b2 = mongo::fromjson( - "{ a : [ 1, 2, { 'a' : 'b', 'x' : 'y' } ], b : { x : 1, z : 3 } }"); - - ASSERT_NOT_EQUALS(mmb::unordered(b1), mmb::unordered(b2)); - } - - TEST(UnorderedEqualityChecker, ObjectOrderingIsNotConsidered) { - const mongo::BSONObj b1 = mongo::fromjson( - "{ a : [ 1, 2, { 'a' : 'b', 'x' : 'y' } ], b : { x : 1, y : 2, z : 3 } }"); - const mongo::BSONObj b2 = mongo::fromjson( - "{ b : { y : 2, z : 3 , x : 1 }, a : [ 1, 2, { 'a' : 'b', 'x' : 'y' } ] }"); - - ASSERT_EQUALS(mmb::unordered(b1), mmb::unordered(b2)); - } - - TEST(UnorderedEqualityChecker, ArrayOrderingIsConsidered) { - const mongo::BSONObj b1 = mongo::fromjson( - "{ a : [ 1, 2, { 'a' : 'b', 'x' : 'y' } ], b : { x : 1, y : 2, z : 3 } }"); - - const mongo::BSONObj b2 = mongo::fromjson( - "{ a : [ 1, { 'a' : 'b', 'x' : 'y' }, 2 ], b : { x : 1, y : 2, z : 3 } }"); - - ASSERT_NOT_EQUALS(mmb::unordered(b1), mmb::unordered(b2)); - } - - TEST(UnorderedEqualityChecker, MissingItemsInArrayAreNotEqual) { - const mongo::BSONObj b1 = mongo::fromjson( - "{ a : [ 1, 2, { 'a' : 'b', 'x' : 'y' } ], b : { x : 1, y : 2, z : 3 } }"); - const mongo::BSONObj b2 = mongo::fromjson( - "{ a : [ 1, { 'a' : 'b', 'x' : 'y' } ], b : { x : 1, z : 3 } }"); - - ASSERT_NOT_EQUALS(mmb::unordered(b1), mmb::unordered(b2)); - } - -} // namespace +namespace { +// Checks that two BSONElements are byte-for-byte identical. +bool identical(const mongo::BSONElement& lhs, const mongo::BSONElement& rhs) { + if (lhs.size() != rhs.size()) + return false; + return std::memcmp(lhs.rawdata(), rhs.rawdata(), lhs.size()) == 0; +} +} // namespace + +TEST(TypeSupport, EncodingEquivalenceDouble) { + mongo::BSONObjBuilder builder; + const char name[] = "thing"; + const double value1 = 3.1415926; + builder.append(name, value1); + mongo::BSONObj source = builder.done(); + const mongo::BSONElement thing = source.firstElement(); + ASSERT_TRUE(thing.type() == mongo::NumberDouble); + + mmb::Document doc; + + // Construct via direct call to append/make + ASSERT_OK(doc.root().appendDouble(name, value1)); + mmb::Element a = doc.root().rightChild(); + ASSERT_TRUE(a.ok()); + ASSERT_EQUALS(a.getType(), mongo::NumberDouble); + ASSERT_TRUE(a.hasValue()); + ASSERT_EQUALS(value1, mmb::ConstElement(a).getValueDouble()); + + // Construct via call passing BSON element + ASSERT_OK(doc.root().appendElement(thing)); + mmb::Element b = doc.root().rightChild(); + ASSERT_TRUE(b.ok()); + ASSERT_EQUALS(b.getType(), mongo::NumberDouble); + ASSERT_TRUE(b.hasValue()); + + // Construct via setValue call. + ASSERT_OK(doc.root().appendNull(name)); + mmb::Element c = doc.root().rightChild(); + ASSERT_TRUE(c.ok()); + c.setValueDouble(value1); + ASSERT_EQUALS(c.getType(), mongo::NumberDouble); + ASSERT_TRUE(c.hasValue()); + + // Ensure identity: + ASSERT_TRUE(identical(thing, mmb::ConstElement(a).getValue())); + ASSERT_TRUE(identical(a.getValue(), b.getValue())); + ASSERT_TRUE(identical(b.getValue(), c.getValue())); +} + +TEST(TypeSupport, EncodingEquivalenceString) { + mongo::BSONObjBuilder builder; + const char name[] = "thing"; + const std::string value1 = "value1"; + builder.append(name, value1); + mongo::BSONObj source = builder.done(); + const mongo::BSONElement thing = source.firstElement(); + ASSERT_TRUE(thing.type() == mongo::String); + + mmb::Document doc; + + // Construct via direct call to append/make + ASSERT_OK(doc.root().appendString(name, value1)); + mmb::Element a = doc.root().rightChild(); + ASSERT_TRUE(a.ok()); + ASSERT_EQUALS(a.getType(), mongo::String); + ASSERT_TRUE(a.hasValue()); + ASSERT_EQUALS(value1, mmb::ConstElement(a).getValueString()); + + // Construct via call passing BSON element + ASSERT_OK(doc.root().appendElement(thing)); + mmb::Element b = doc.root().rightChild(); + ASSERT_TRUE(b.ok()); + ASSERT_EQUALS(b.getType(), mongo::String); + ASSERT_TRUE(b.hasValue()); + + // Construct via setValue call. + ASSERT_OK(doc.root().appendNull(name)); + mmb::Element c = doc.root().rightChild(); + ASSERT_TRUE(c.ok()); + c.setValueString(value1); + ASSERT_EQUALS(c.getType(), mongo::String); + ASSERT_TRUE(c.hasValue()); + + // Ensure identity: + ASSERT_TRUE(identical(thing, mmb::ConstElement(a).getValue())); + ASSERT_TRUE(identical(a.getValue(), b.getValue())); + ASSERT_TRUE(identical(b.getValue(), c.getValue())); +} + +TEST(TypeSupport, EncodingEquivalenceObject) { + mongo::BSONObjBuilder builder; + const char name[] = "thing"; + const mongo::BSONObj value1 = mongo::fromjson("{ a : 1, b : 2.0, c : 'hello' }"); + builder.append(name, value1); + mongo::BSONObj source = builder.done(); + const mongo::BSONElement thing = source.firstElement(); + ASSERT_TRUE(thing.type() == mongo::Object); + + mmb::Document doc; + + // Construct via direct call to append/make + ASSERT_OK(doc.root().appendObject(name, value1)); + mmb::Element a = doc.root().rightChild(); + ASSERT_TRUE(a.ok()); + ASSERT_EQUALS(a.getType(), mongo::Object); + ASSERT_TRUE(a.hasValue()); + ASSERT_EQUALS(value1, mmb::ConstElement(a).getValueObject()); + + // Construct via call passing BSON element + ASSERT_OK(doc.root().appendElement(thing)); + mmb::Element b = doc.root().rightChild(); + ASSERT_TRUE(b.ok()); + ASSERT_EQUALS(b.getType(), mongo::Object); + ASSERT_TRUE(b.hasValue()); + + // Construct via setValue call. + ASSERT_OK(doc.root().appendNull(name)); + mmb::Element c = doc.root().rightChild(); + ASSERT_TRUE(c.ok()); + c.setValueObject(value1); + ASSERT_EQUALS(c.getType(), mongo::Object); + ASSERT_TRUE(c.hasValue()); + + // Ensure identity: + ASSERT_TRUE(identical(thing, mmb::ConstElement(a).getValue())); + ASSERT_TRUE(identical(a.getValue(), b.getValue())); + ASSERT_TRUE(identical(b.getValue(), c.getValue())); +} + +TEST(TypeSupport, EncodingEquivalenceArray) { + mongo::BSONObjBuilder builder; + const char name[] = "thing"; + const mongo::BSONObj dummy = (mongo::fromjson("{ x : [ 1, 2.0, 'hello' ] } ")); + const mongo::BSONArray value1(dummy.firstElement().embeddedObject()); + builder.append(name, value1); + mongo::BSONObj source = builder.done(); + const mongo::BSONElement thing = source.firstElement(); + ASSERT_TRUE(thing.type() == mongo::Array); + + mmb::Document doc; + + // Construct via direct call to append/make + ASSERT_OK(doc.root().appendArray(name, value1)); + mmb::Element a = doc.root().rightChild(); + ASSERT_TRUE(a.ok()); + ASSERT_EQUALS(a.getType(), mongo::Array); + ASSERT_TRUE(a.hasValue()); + ASSERT_EQUALS(value1, mmb::ConstElement(a).getValueArray()); + + // Construct via call passing BSON element + ASSERT_OK(doc.root().appendElement(thing)); + mmb::Element b = doc.root().rightChild(); + ASSERT_TRUE(b.ok()); + ASSERT_EQUALS(b.getType(), mongo::Array); + ASSERT_TRUE(b.hasValue()); + + // Construct via setValue call. + ASSERT_OK(doc.root().appendNull(name)); + mmb::Element c = doc.root().rightChild(); + ASSERT_TRUE(c.ok()); + c.setValueArray(value1); + ASSERT_EQUALS(c.getType(), mongo::Array); + ASSERT_TRUE(c.hasValue()); + + // Ensure identity: + ASSERT_TRUE(identical(thing, mmb::ConstElement(a).getValue())); + ASSERT_TRUE(identical(a.getValue(), b.getValue())); + ASSERT_TRUE(identical(b.getValue(), c.getValue())); +} + +TEST(TypeSupport, EncodingEquivalenceBinary) { + mongo::BSONObjBuilder builder; + const char name[] = "thing"; + const mongo::BinDataType value1 = mongo::newUUID; + const unsigned char value2[] = {0x00, + 0x9D, + 0x15, + 0xA3, + 0x3B, + 0xCC, + 0x46, + 0x60, + 0x90, + 0x45, + 0xEF, + 0x54, + 0x77, + 0x8A, + 0x87, + 0x0C}; + builder.appendBinData(name, sizeof(value2), value1, &value2[0]); + mongo::BSONObj source = builder.done(); + const mongo::BSONElement thing = source.firstElement(); + ASSERT_TRUE(thing.type() == mongo::BinData); + + mmb::Document doc; + + // Construct via direct call to append/make + ASSERT_OK(doc.root().appendBinary(name, sizeof(value2), value1, &value2[0])); + mmb::Element a = doc.root().rightChild(); + ASSERT_TRUE(a.ok()); + ASSERT_EQUALS(a.getType(), mongo::BinData); + ASSERT_TRUE(a.hasValue()); + + // Construct via call passing BSON element + ASSERT_OK(doc.root().appendElement(thing)); + mmb::Element b = doc.root().rightChild(); + ASSERT_TRUE(b.ok()); + ASSERT_EQUALS(b.getType(), mongo::BinData); + ASSERT_TRUE(b.hasValue()); + + // Construct via setValue call. + ASSERT_OK(doc.root().appendNull(name)); + mmb::Element c = doc.root().rightChild(); + ASSERT_TRUE(c.ok()); + c.setValueBinary(sizeof(value2), value1, &value2[0]); + ASSERT_EQUALS(c.getType(), mongo::BinData); + ASSERT_TRUE(c.hasValue()); + + // Ensure identity: + ASSERT_TRUE(identical(thing, mmb::ConstElement(a).getValue())); + ASSERT_TRUE(identical(a.getValue(), b.getValue())); + ASSERT_TRUE(identical(b.getValue(), c.getValue())); +} + +TEST(TypeSupport, EncodingEquivalenceUndefined) { + mongo::BSONObjBuilder builder; + const char name[] = "thing"; + builder.appendUndefined(name); + mongo::BSONObj source = builder.done(); + const mongo::BSONElement thing = source.firstElement(); + ASSERT_TRUE(thing.type() == mongo::Undefined); + + mmb::Document doc; + + // Construct via direct call to append/make + ASSERT_OK(doc.root().appendUndefined(name)); + mmb::Element a = doc.root().rightChild(); + ASSERT_TRUE(a.ok()); + ASSERT_EQUALS(a.getType(), mongo::Undefined); + ASSERT_TRUE(a.hasValue()); + ASSERT_TRUE(mmb::ConstElement(a).isValueUndefined()); + + // Construct via call passing BSON element + ASSERT_OK(doc.root().appendElement(thing)); + mmb::Element b = doc.root().rightChild(); + ASSERT_TRUE(b.ok()); + ASSERT_EQUALS(b.getType(), mongo::Undefined); + ASSERT_TRUE(b.hasValue()); + + // Construct via setValue call. + ASSERT_OK(doc.root().appendNull(name)); + mmb::Element c = doc.root().rightChild(); + ASSERT_TRUE(c.ok()); + c.setValueUndefined(); + ASSERT_EQUALS(c.getType(), mongo::Undefined); + ASSERT_TRUE(c.hasValue()); + + // Ensure identity: + ASSERT_TRUE(identical(thing, mmb::ConstElement(a).getValue())); + ASSERT_TRUE(identical(a.getValue(), b.getValue())); + ASSERT_TRUE(identical(b.getValue(), c.getValue())); +} + +TEST(TypeSupport, EncodingEquivalenceOID) { + mongo::BSONObjBuilder builder; + const char name[] = "thing"; + const mongo::OID value1 = mongo::OID::gen(); + builder.append(name, value1); + mongo::BSONObj source = builder.done(); + const mongo::BSONElement thing = source.firstElement(); + ASSERT_TRUE(thing.type() == mongo::jstOID); + + mmb::Document doc; + + // Construct via direct call to append/make + ASSERT_OK(doc.root().appendOID(name, value1)); + mmb::Element a = doc.root().rightChild(); + ASSERT_TRUE(a.ok()); + ASSERT_EQUALS(a.getType(), mongo::jstOID); + ASSERT_TRUE(a.hasValue()); + ASSERT_EQUALS(value1, mmb::ConstElement(a).getValueOID()); + + // Construct via call passing BSON element + ASSERT_OK(doc.root().appendElement(thing)); + mmb::Element b = doc.root().rightChild(); + ASSERT_TRUE(b.ok()); + ASSERT_EQUALS(b.getType(), mongo::jstOID); + ASSERT_TRUE(b.hasValue()); + + // Construct via setValue call. + ASSERT_OK(doc.root().appendNull(name)); + mmb::Element c = doc.root().rightChild(); + ASSERT_TRUE(c.ok()); + c.setValueOID(value1); + ASSERT_EQUALS(c.getType(), mongo::jstOID); + ASSERT_TRUE(c.hasValue()); + + // Ensure identity: + ASSERT_TRUE(identical(thing, mmb::ConstElement(a).getValue())); + ASSERT_TRUE(identical(a.getValue(), b.getValue())); + ASSERT_TRUE(identical(b.getValue(), c.getValue())); +} + +TEST(TypeSupport, EncodingEquivalenceBoolean) { + mongo::BSONObjBuilder builder; + const char name[] = "thing"; + const bool value1 = true; + builder.append(name, value1); + mongo::BSONObj source = builder.done(); + const mongo::BSONElement thing = source.firstElement(); + ASSERT_TRUE(thing.type() == mongo::Bool); + + mmb::Document doc; + + // Construct via direct call to append/make + ASSERT_OK(doc.root().appendBool(name, value1)); + mmb::Element a = doc.root().rightChild(); + ASSERT_TRUE(a.ok()); + ASSERT_EQUALS(a.getType(), mongo::Bool); + ASSERT_TRUE(a.hasValue()); + ASSERT_EQUALS(value1, mmb::ConstElement(a).getValueBool()); + + // Construct via call passing BSON element + ASSERT_OK(doc.root().appendElement(thing)); + mmb::Element b = doc.root().rightChild(); + ASSERT_TRUE(b.ok()); + ASSERT_EQUALS(b.getType(), mongo::Bool); + ASSERT_TRUE(b.hasValue()); + + // Construct via setValue call. + ASSERT_OK(doc.root().appendNull(name)); + mmb::Element c = doc.root().rightChild(); + ASSERT_TRUE(c.ok()); + c.setValueBool(value1); + ASSERT_EQUALS(c.getType(), mongo::Bool); + ASSERT_TRUE(c.hasValue()); + + // Ensure identity: + ASSERT_TRUE(identical(thing, mmb::ConstElement(a).getValue())); + ASSERT_TRUE(identical(a.getValue(), b.getValue())); + ASSERT_TRUE(identical(b.getValue(), c.getValue())); +} + +TEST(TypeSupport, EncodingEquivalenceDate) { + mongo::BSONObjBuilder builder; + const char name[] = "thing"; + const mongo::Date_t value1 = mongo::jsTime(); + builder.append(name, value1); + mongo::BSONObj source = builder.done(); + const mongo::BSONElement thing = source.firstElement(); + ASSERT_TRUE(thing.type() == mongo::Date); + + mmb::Document doc; + + // Construct via direct call to append/make + ASSERT_OK(doc.root().appendDate(name, value1)); + mmb::Element a = doc.root().rightChild(); + ASSERT_TRUE(a.ok()); + ASSERT_EQUALS(a.getType(), mongo::Date); + ASSERT_TRUE(a.hasValue()); + ASSERT_EQUALS(value1, mmb::ConstElement(a).getValueDate()); + + // Construct via call passing BSON element + ASSERT_OK(doc.root().appendElement(thing)); + mmb::Element b = doc.root().rightChild(); + ASSERT_TRUE(b.ok()); + ASSERT_EQUALS(b.getType(), mongo::Date); + ASSERT_TRUE(b.hasValue()); + + // Construct via setValue call. + ASSERT_OK(doc.root().appendNull(name)); + mmb::Element c = doc.root().rightChild(); + ASSERT_TRUE(c.ok()); + c.setValueDate(value1); + ASSERT_EQUALS(c.getType(), mongo::Date); + ASSERT_TRUE(c.hasValue()); + + // Ensure identity: + ASSERT_TRUE(identical(thing, mmb::ConstElement(a).getValue())); + ASSERT_TRUE(identical(a.getValue(), b.getValue())); + ASSERT_TRUE(identical(b.getValue(), c.getValue())); +} + +TEST(TypeSupport, EncodingEquivalenceNull) { + mongo::BSONObjBuilder builder; + const char name[] = "thing"; + builder.appendNull(name); + mongo::BSONObj source = builder.done(); + const mongo::BSONElement thing = source.firstElement(); + ASSERT_TRUE(thing.type() == mongo::jstNULL); + + mmb::Document doc; + + // Construct via direct call to append/make + ASSERT_OK(doc.root().appendNull(name)); + mmb::Element a = doc.root().rightChild(); + ASSERT_TRUE(a.ok()); + ASSERT_EQUALS(a.getType(), mongo::jstNULL); + ASSERT_TRUE(a.hasValue()); + ASSERT_TRUE(mmb::ConstElement(a).isValueNull()); + + // Construct via call passing BSON element + ASSERT_OK(doc.root().appendElement(thing)); + mmb::Element b = doc.root().rightChild(); + ASSERT_TRUE(b.ok()); + ASSERT_EQUALS(b.getType(), mongo::jstNULL); + ASSERT_TRUE(b.hasValue()); + + // Construct via setValue call. + ASSERT_OK(doc.root().appendUndefined(name)); + mmb::Element c = doc.root().rightChild(); + ASSERT_TRUE(c.ok()); + c.setValueNull(); + ASSERT_EQUALS(c.getType(), mongo::jstNULL); + ASSERT_TRUE(c.hasValue()); + + // Ensure identity: + ASSERT_TRUE(identical(thing, mmb::ConstElement(a).getValue())); + ASSERT_TRUE(identical(a.getValue(), b.getValue())); + ASSERT_TRUE(identical(b.getValue(), c.getValue())); +} + +TEST(TypeSupport, EncodingEquivalenceRegex) { + mongo::BSONObjBuilder builder; + const char name[] = "thing"; + const std::string value1 = "some_regex_data"; + const std::string value2 = "flags"; + builder.appendRegex(name, value1, value2); + mongo::BSONObj source = builder.done(); + const mongo::BSONElement thing = source.firstElement(); + ASSERT_TRUE(thing.type() == mongo::RegEx); + + mmb::Document doc; + + // Construct via direct call to append/make + ASSERT_OK(doc.root().appendRegex(name, value1, value2)); + mmb::Element a = doc.root().rightChild(); + ASSERT_TRUE(a.ok()); + ASSERT_EQUALS(a.getType(), mongo::RegEx); + ASSERT_TRUE(a.hasValue()); + + // Construct via call passing BSON element + ASSERT_OK(doc.root().appendElement(thing)); + mmb::Element b = doc.root().rightChild(); + ASSERT_TRUE(b.ok()); + ASSERT_EQUALS(b.getType(), mongo::RegEx); + ASSERT_TRUE(b.hasValue()); + + // Construct via setValue call. + ASSERT_OK(doc.root().appendNull(name)); + mmb::Element c = doc.root().rightChild(); + ASSERT_TRUE(c.ok()); + c.setValueRegex(value1, value2); + ASSERT_EQUALS(c.getType(), mongo::RegEx); + ASSERT_TRUE(c.hasValue()); + + // Ensure identity: + ASSERT_TRUE(identical(thing, mmb::ConstElement(a).getValue())); + ASSERT_TRUE(identical(a.getValue(), b.getValue())); + ASSERT_TRUE(identical(b.getValue(), c.getValue())); +} + +TEST(TypeSupport, EncodingEquivalenceDBRef) { + mongo::BSONObjBuilder builder; + const char name[] = "thing"; + const std::string value1 = "some_ns"; + const mongo::OID value2 = mongo::OID::gen(); + builder.appendDBRef(name, value1, value2); + mongo::BSONObj source = builder.done(); + const mongo::BSONElement thing = source.firstElement(); + ASSERT_TRUE(thing.type() == mongo::DBRef); + + mmb::Document doc; + + // Construct via direct call to append/make + ASSERT_OK(doc.root().appendDBRef(name, value1, value2)); + mmb::Element a = doc.root().rightChild(); + ASSERT_TRUE(a.ok()); + ASSERT_EQUALS(a.getType(), mongo::DBRef); + ASSERT_TRUE(a.hasValue()); + + // Construct via call passing BSON element + ASSERT_OK(doc.root().appendElement(thing)); + mmb::Element b = doc.root().rightChild(); + ASSERT_TRUE(b.ok()); + ASSERT_EQUALS(b.getType(), mongo::DBRef); + ASSERT_TRUE(b.hasValue()); + + // Construct via setValue call. + ASSERT_OK(doc.root().appendNull(name)); + mmb::Element c = doc.root().rightChild(); + ASSERT_TRUE(c.ok()); + c.setValueDBRef(value1, value2); + ASSERT_EQUALS(c.getType(), mongo::DBRef); + ASSERT_TRUE(c.hasValue()); + + // Ensure identity: + ASSERT_TRUE(identical(thing, mmb::ConstElement(a).getValue())); + ASSERT_TRUE(identical(a.getValue(), b.getValue())); + ASSERT_TRUE(identical(b.getValue(), c.getValue())); +} + +TEST(TypeSupport, EncodingEquivalenceCode) { + mongo::BSONObjBuilder builder; + const char name[] = "thing"; + const std::string value1 = "{ print 4; }"; + builder.appendCode(name, value1); + mongo::BSONObj source = builder.done(); + const mongo::BSONElement thing = source.firstElement(); + ASSERT_TRUE(thing.type() == mongo::Code); + + mmb::Document doc; + + // Construct via direct call to append/make + ASSERT_OK(doc.root().appendCode(name, value1)); + mmb::Element a = doc.root().rightChild(); + ASSERT_TRUE(a.ok()); + ASSERT_EQUALS(a.getType(), mongo::Code); + ASSERT_TRUE(a.hasValue()); + + // Construct via call passing BSON element + ASSERT_OK(doc.root().appendElement(thing)); + mmb::Element b = doc.root().rightChild(); + ASSERT_TRUE(b.ok()); + ASSERT_EQUALS(b.getType(), mongo::Code); + ASSERT_TRUE(b.hasValue()); + + // Construct via setValue call. + ASSERT_OK(doc.root().appendNull(name)); + mmb::Element c = doc.root().rightChild(); + ASSERT_TRUE(c.ok()); + c.setValueCode(value1); + ASSERT_EQUALS(c.getType(), mongo::Code); + ASSERT_TRUE(c.hasValue()); + + // Ensure identity: + ASSERT_TRUE(identical(thing, mmb::ConstElement(a).getValue())); + ASSERT_TRUE(identical(a.getValue(), b.getValue())); + ASSERT_TRUE(identical(b.getValue(), c.getValue())); +} + +TEST(TypeSupport, EncodingEquivalenceSymbol) { + mongo::BSONObjBuilder builder; + const char name[] = "thing"; + const std::string value1 = "#symbol"; + builder.appendSymbol(name, value1); + mongo::BSONObj source = builder.done(); + const mongo::BSONElement thing = source.firstElement(); + ASSERT_TRUE(thing.type() == mongo::Symbol); + + mmb::Document doc; + + // Construct via direct call to append/make + ASSERT_OK(doc.root().appendSymbol(name, value1)); + mmb::Element a = doc.root().rightChild(); + ASSERT_TRUE(a.ok()); + ASSERT_EQUALS(a.getType(), mongo::Symbol); + ASSERT_TRUE(a.hasValue()); + ASSERT_EQUALS(value1, mmb::ConstElement(a).getValueSymbol()); + + // Construct via call passing BSON element + ASSERT_OK(doc.root().appendElement(thing)); + mmb::Element b = doc.root().rightChild(); + ASSERT_TRUE(b.ok()); + ASSERT_EQUALS(b.getType(), mongo::Symbol); + ASSERT_TRUE(b.hasValue()); + + // Construct via setValue call. + ASSERT_OK(doc.root().appendNull(name)); + mmb::Element c = doc.root().rightChild(); + ASSERT_TRUE(c.ok()); + c.setValueSymbol(value1); + ASSERT_EQUALS(c.getType(), mongo::Symbol); + ASSERT_TRUE(c.hasValue()); + + // Ensure identity: + ASSERT_TRUE(identical(thing, mmb::ConstElement(a).getValue())); + ASSERT_TRUE(identical(a.getValue(), b.getValue())); + ASSERT_TRUE(identical(b.getValue(), c.getValue())); +} + +TEST(TypeSupport, EncodingEquivalenceCodeWithScope) { + mongo::BSONObjBuilder builder; + const char name[] = "thing"; + const std::string value1 = "print x;"; + const mongo::BSONObj value2 = mongo::fromjson("{ x : 4 }"); + builder.appendCodeWScope(name, value1, value2); + mongo::BSONObj source = builder.done(); + const mongo::BSONElement thing = source.firstElement(); + ASSERT_TRUE(thing.type() == mongo::CodeWScope); + + mmb::Document doc; + + // Construct via direct call to append/make + ASSERT_OK(doc.root().appendCodeWithScope(name, value1, value2)); + mmb::Element a = doc.root().rightChild(); + ASSERT_TRUE(a.ok()); + ASSERT_EQUALS(a.getType(), mongo::CodeWScope); + ASSERT_TRUE(a.hasValue()); + + // Construct via call passing BSON element + ASSERT_OK(doc.root().appendElement(thing)); + mmb::Element b = doc.root().rightChild(); + ASSERT_TRUE(b.ok()); + ASSERT_EQUALS(b.getType(), mongo::CodeWScope); + ASSERT_TRUE(b.hasValue()); + + // Construct via setValue call. + ASSERT_OK(doc.root().appendNull(name)); + mmb::Element c = doc.root().rightChild(); + ASSERT_TRUE(c.ok()); + c.setValueCodeWithScope(value1, value2); + ASSERT_EQUALS(c.getType(), mongo::CodeWScope); + ASSERT_TRUE(c.hasValue()); + + // Ensure identity: + ASSERT_TRUE(identical(thing, mmb::ConstElement(a).getValue())); + ASSERT_TRUE(identical(a.getValue(), b.getValue())); + ASSERT_TRUE(identical(b.getValue(), c.getValue())); +} + +TEST(TypeSupport, EncodingEquivalenceInt) { + mongo::BSONObjBuilder builder; + const char name[] = "thing"; + const int value1 = true; + builder.append(name, value1); + mongo::BSONObj source = builder.done(); + const mongo::BSONElement thing = source.firstElement(); + ASSERT_TRUE(thing.type() == mongo::NumberInt); + + mmb::Document doc; + + // Construct via direct call to append/make + ASSERT_OK(doc.root().appendInt(name, value1)); + mmb::Element a = doc.root().rightChild(); + ASSERT_TRUE(a.ok()); + ASSERT_EQUALS(a.getType(), mongo::NumberInt); + ASSERT_TRUE(a.hasValue()); + ASSERT_EQUALS(value1, mmb::ConstElement(a).getValueInt()); + + // Construct via call passing BSON element + ASSERT_OK(doc.root().appendElement(thing)); + mmb::Element b = doc.root().rightChild(); + ASSERT_TRUE(b.ok()); + ASSERT_EQUALS(b.getType(), mongo::NumberInt); + ASSERT_TRUE(b.hasValue()); + + // Construct via setValue call. + ASSERT_OK(doc.root().appendNull(name)); + mmb::Element c = doc.root().rightChild(); + ASSERT_TRUE(c.ok()); + c.setValueInt(value1); + ASSERT_EQUALS(c.getType(), mongo::NumberInt); + ASSERT_TRUE(c.hasValue()); + + // Ensure identity: + ASSERT_TRUE(identical(thing, mmb::ConstElement(a).getValue())); + ASSERT_TRUE(identical(a.getValue(), b.getValue())); + ASSERT_TRUE(identical(b.getValue(), c.getValue())); +} + +TEST(TypeSupport, EncodingEquivalenceTimestamp) { + mongo::BSONObjBuilder builder; + const char name[] = "thing"; + const mongo::Timestamp value1 = mongo::Timestamp(mongo::jsTime()); + builder.append(name, value1); + mongo::BSONObj source = builder.done(); + const mongo::BSONElement thing = source.firstElement(); + ASSERT_TRUE(thing.type() == mongo::bsonTimestamp); + + mmb::Document doc; + + // Construct via direct call to append/make + ASSERT_OK(doc.root().appendTimestamp(name, value1)); + mmb::Element a = doc.root().rightChild(); + ASSERT_TRUE(a.ok()); + ASSERT_EQUALS(a.getType(), mongo::bsonTimestamp); + ASSERT_TRUE(a.hasValue()); + ASSERT_TRUE(value1 == mmb::ConstElement(a).getValueTimestamp()); + + // Construct via call passing BSON element + ASSERT_OK(doc.root().appendElement(thing)); + mmb::Element b = doc.root().rightChild(); + ASSERT_TRUE(b.ok()); + ASSERT_EQUALS(b.getType(), mongo::bsonTimestamp); + ASSERT_TRUE(b.hasValue()); + + // Construct via setValue call. + ASSERT_OK(doc.root().appendNull(name)); + mmb::Element c = doc.root().rightChild(); + ASSERT_TRUE(c.ok()); + c.setValueTimestamp(value1); + ASSERT_EQUALS(c.getType(), mongo::bsonTimestamp); + ASSERT_TRUE(c.hasValue()); + + // Ensure identity: + ASSERT_TRUE(identical(thing, mmb::ConstElement(a).getValue())); + ASSERT_TRUE(identical(a.getValue(), b.getValue())); + ASSERT_TRUE(identical(b.getValue(), c.getValue())); +} + +TEST(TypeSupport, EncodingEquivalenceLong) { + mongo::BSONObjBuilder builder; + const char name[] = "thing"; + const long long value1 = 420000000000000LL; + builder.append(name, value1); + mongo::BSONObj source = builder.done(); + const mongo::BSONElement thing = source.firstElement(); + ASSERT_TRUE(thing.type() == mongo::NumberLong); + + mmb::Document doc; + + // Construct via direct call to append/make + ASSERT_OK(doc.root().appendLong(name, value1)); + mmb::Element a = doc.root().rightChild(); + ASSERT_TRUE(a.ok()); + ASSERT_EQUALS(a.getType(), mongo::NumberLong); + ASSERT_TRUE(a.hasValue()); + ASSERT_EQUALS(value1, mmb::ConstElement(a).getValueLong()); + + // Construct via call passing BSON element + ASSERT_OK(doc.root().appendElement(thing)); + mmb::Element b = doc.root().rightChild(); + ASSERT_TRUE(b.ok()); + ASSERT_EQUALS(b.getType(), mongo::NumberLong); + ASSERT_TRUE(b.hasValue()); + + // Construct via setValue call. + ASSERT_OK(doc.root().appendNull(name)); + mmb::Element c = doc.root().rightChild(); + ASSERT_TRUE(c.ok()); + c.setValueLong(value1); + ASSERT_EQUALS(c.getType(), mongo::NumberLong); + ASSERT_TRUE(c.hasValue()); + + // Ensure identity: + ASSERT_TRUE(identical(thing, mmb::ConstElement(a).getValue())); + ASSERT_TRUE(identical(a.getValue(), b.getValue())); + ASSERT_TRUE(identical(b.getValue(), c.getValue())); +} + +TEST(TypeSupport, EncodingEquivalenceMinKey) { + mongo::BSONObjBuilder builder; + const char name[] = "thing"; + builder.appendMinKey(name); + mongo::BSONObj source = builder.done(); + const mongo::BSONElement thing = source.firstElement(); + ASSERT_TRUE(thing.type() == mongo::MinKey); + + mmb::Document doc; + + // Construct via direct call to append/make + ASSERT_OK(doc.root().appendMinKey(name)); + mmb::Element a = doc.root().rightChild(); + ASSERT_TRUE(a.ok()); + ASSERT_EQUALS(a.getType(), mongo::MinKey); + ASSERT_TRUE(a.hasValue()); + ASSERT_TRUE(mmb::ConstElement(a).isValueMinKey()); + + // Construct via call passing BSON element + ASSERT_OK(doc.root().appendElement(thing)); + mmb::Element b = doc.root().rightChild(); + ASSERT_TRUE(b.ok()); + ASSERT_EQUALS(b.getType(), mongo::MinKey); + ASSERT_TRUE(b.hasValue()); + + // Construct via setValue call. + ASSERT_OK(doc.root().appendNull(name)); + mmb::Element c = doc.root().rightChild(); + ASSERT_TRUE(c.ok()); + c.setValueMinKey(); + ASSERT_EQUALS(c.getType(), mongo::MinKey); + ASSERT_TRUE(c.hasValue()); + + // Ensure identity: + ASSERT_TRUE(identical(thing, mmb::ConstElement(a).getValue())); + ASSERT_TRUE(identical(a.getValue(), b.getValue())); + ASSERT_TRUE(identical(b.getValue(), c.getValue())); +} + +TEST(TypeSupport, EncodingEquivalenceMaxKey) { + mongo::BSONObjBuilder builder; + const char name[] = "thing"; + builder.appendMaxKey(name); + mongo::BSONObj source = builder.done(); + const mongo::BSONElement thing = source.firstElement(); + ASSERT_TRUE(thing.type() == mongo::MaxKey); + + mmb::Document doc; + + // Construct via direct call to append/make + ASSERT_OK(doc.root().appendMaxKey(name)); + mmb::Element a = doc.root().rightChild(); + ASSERT_TRUE(a.ok()); + ASSERT_EQUALS(a.getType(), mongo::MaxKey); + ASSERT_TRUE(a.hasValue()); + ASSERT_TRUE(mmb::ConstElement(a).isValueMaxKey()); + + // Construct via call passing BSON element + ASSERT_OK(doc.root().appendElement(thing)); + mmb::Element b = doc.root().rightChild(); + ASSERT_TRUE(b.ok()); + ASSERT_EQUALS(b.getType(), mongo::MaxKey); + ASSERT_TRUE(b.hasValue()); + + // Construct via setValue call. + ASSERT_OK(doc.root().appendNull(name)); + mmb::Element c = doc.root().rightChild(); + ASSERT_TRUE(c.ok()); + c.setValueMaxKey(); + ASSERT_EQUALS(c.getType(), mongo::MaxKey); + ASSERT_TRUE(c.hasValue()); + + // Ensure identity: + ASSERT_TRUE(identical(thing, mmb::ConstElement(a).getValue())); + ASSERT_TRUE(identical(a.getValue(), b.getValue())); + ASSERT_TRUE(identical(b.getValue(), c.getValue())); +} + +TEST(Document, ManipulateComplexObjInLeafHeap) { + // Test that an object with complex substructure that lives in the leaf builder can be + // manipulated in the same way as an object with complex substructure that lives + // freely. + mmb::Document doc; + static const char inJson[] = "{ a: 1, b: 2, d : ['w', 'x', 'y', 'z'] }"; + mmb::Element embedded = doc.makeElementObject("embedded", mongo::fromjson(inJson)); + ASSERT_OK(doc.root().pushBack(embedded)); + mmb::Element free = doc.makeElementObject("free"); + ASSERT_OK(doc.root().pushBack(free)); + + mmb::Element e_a = embedded.leftChild(); + ASSERT_TRUE(e_a.ok()); + ASSERT_EQUALS("a", e_a.getFieldName()); + mmb::Element e_b = e_a.rightSibling(); + ASSERT_TRUE(e_b.ok()); + ASSERT_EQUALS("b", e_b.getFieldName()); + + mmb::Element new_c = doc.makeElementDouble("c", 2.0); + ASSERT_TRUE(new_c.ok()); + ASSERT_OK(e_b.addSiblingRight(new_c)); + + mmb::Element e_d = new_c.rightSibling(); + ASSERT_TRUE(e_d.ok()); + ASSERT_EQUALS("d", e_d.getFieldName()); + + mmb::Element e_d_0 = e_d.leftChild(); + ASSERT_TRUE(e_d_0.ok()); + + mmb::Element e_d_1 = e_d_0.rightSibling(); + ASSERT_TRUE(e_d_1.ok()); + + mmb::Element e_d_2 = e_d_1.rightSibling(); + ASSERT_TRUE(e_d_2.ok()); + + ASSERT_OK(e_d_1.remove()); + + static const char outJson[] = + "{ embedded: { a: 1, b: 2, c: 2.0, d : ['w', 'y', 'z'] }, free: {} }"; + ASSERT_EQUALS(mongo::fromjson(outJson), doc.getObject()); +} + +TEST(DocumentInPlace, EphemeralDocumentsDoNotUseInPlaceMode) { + mmb::Document doc; + ASSERT_FALSE(doc.isInPlaceModeEnabled()); +} + +TEST(DocumentInPlace, InPlaceModeIsHonored1) { + mongo::BSONObj obj; + mmb::Document doc(obj, mmb::Document::kInPlaceEnabled); + ASSERT_TRUE(doc.isInPlaceModeEnabled()); + doc.disableInPlaceUpdates(); + ASSERT_FALSE(doc.isInPlaceModeEnabled()); +} + +TEST(DocumentInPlace, InPlaceModeIsHonored2) { + mongo::BSONObj obj; + mmb::Document doc(obj, mmb::Document::kInPlaceDisabled); + ASSERT_FALSE(doc.isInPlaceModeEnabled()); + doc.disableInPlaceUpdates(); + ASSERT_FALSE(doc.isInPlaceModeEnabled()); +} + +TEST(DocumentInPlace, InPlaceModeWorksWithNoMutations) { + mongo::BSONObj obj; + mmb::Document doc(obj, mmb::Document::kInPlaceEnabled); + ASSERT_TRUE(doc.isInPlaceModeEnabled()); + const char* source = NULL; + mmb::DamageVector damages; + ASSERT_TRUE(damages.empty()); + doc.getInPlaceUpdates(&damages, &source); + ASSERT_TRUE(damages.empty()); + ASSERT_NOT_EQUALS(static_cast<const char*>(NULL), source); + ASSERT_TRUE(doc.isInPlaceModeEnabled()); +} + +TEST(DocumentInPlace, InPlaceModeIsDisabledByAddSiblingLeft) { + mongo::BSONObj obj = mongo::fromjson("{ foo : 'foo' }"); + mmb::Document doc(obj, mmb::Document::kInPlaceEnabled); + ASSERT_TRUE(doc.isInPlaceModeEnabled()); + mmb::Element newElt = doc.makeElementInt("bar", 42); + ASSERT_TRUE(doc.isInPlaceModeEnabled()); + ASSERT_OK(doc.root().leftChild().addSiblingLeft(newElt)); + ASSERT_FALSE(doc.isInPlaceModeEnabled()); +} + +TEST(DocumentInPlace, InPlaceModeIsDisabledByAddSiblingRight) { + mongo::BSONObj obj = mongo::fromjson("{ foo : 'foo' }"); + mmb::Document doc(obj, mmb::Document::kInPlaceEnabled); + ASSERT_TRUE(doc.isInPlaceModeEnabled()); + mmb::Element newElt = doc.makeElementInt("bar", 42); + ASSERT_TRUE(doc.isInPlaceModeEnabled()); + ASSERT_OK(doc.root().leftChild().addSiblingRight(newElt)); + ASSERT_FALSE(doc.isInPlaceModeEnabled()); +} + +TEST(DocumentInPlace, InPlaceModeIsDisabledByRemove) { + mongo::BSONObj obj = mongo::fromjson("{ foo : 'foo' }"); + mmb::Document doc(obj, mmb::Document::kInPlaceEnabled); + ASSERT_TRUE(doc.isInPlaceModeEnabled()); + ASSERT_OK(doc.root().leftChild().remove()); + ASSERT_FALSE(doc.isInPlaceModeEnabled()); +} + +// NOTE: Someday, we may do in-place renames, but renaming 'foo' to 'foobar' will never +// work because the sizes don't match. Validate that this disables in-place updates. +TEST(DocumentInPlace, InPlaceModeIsDisabledByRename) { + mongo::BSONObj obj = mongo::fromjson("{ foo : 'foo' }"); + mmb::Document doc(obj, mmb::Document::kInPlaceEnabled); + ASSERT_TRUE(doc.isInPlaceModeEnabled()); + ASSERT_OK(doc.root().leftChild().rename("foobar")); + ASSERT_FALSE(doc.isInPlaceModeEnabled()); +} + +TEST(DocumentInPlace, InPlaceModeIsDisabledByPushFront) { + mongo::BSONObj obj = mongo::fromjson("{ foo : 'foo' }"); + mmb::Document doc(obj, mmb::Document::kInPlaceEnabled); + ASSERT_TRUE(doc.isInPlaceModeEnabled()); + mmb::Element newElt = doc.makeElementInt("bar", 42); + ASSERT_TRUE(doc.isInPlaceModeEnabled()); + ASSERT_OK(doc.root().pushFront(newElt)); + ASSERT_FALSE(doc.isInPlaceModeEnabled()); +} + +TEST(DocumentInPlace, InPlaceModeIsDisabledByPushBack) { + mongo::BSONObj obj = mongo::fromjson("{ foo : 'foo' }"); + mmb::Document doc(obj, mmb::Document::kInPlaceEnabled); + ASSERT_TRUE(doc.isInPlaceModeEnabled()); + mmb::Element newElt = doc.makeElementInt("bar", 42); + ASSERT_TRUE(doc.isInPlaceModeEnabled()); + ASSERT_OK(doc.root().pushBack(newElt)); + ASSERT_FALSE(doc.isInPlaceModeEnabled()); +} + +TEST(DocumentInPlace, InPlaceModeIsDisabledByPopFront) { + mongo::BSONObj obj = mongo::fromjson("{ foo : 'foo' }"); + mmb::Document doc(obj, mmb::Document::kInPlaceEnabled); + ASSERT_TRUE(doc.isInPlaceModeEnabled()); + ASSERT_OK(doc.root().popFront()); + ASSERT_FALSE(doc.isInPlaceModeEnabled()); +} + +TEST(DocumentInPlace, InPlaceModeIsDisabledByPopBack) { + mongo::BSONObj obj = mongo::fromjson("{ foo : 'foo' }"); + mmb::Document doc(obj, mmb::Document::kInPlaceEnabled); + ASSERT_TRUE(doc.isInPlaceModeEnabled()); + ASSERT_OK(doc.root().popBack()); + ASSERT_FALSE(doc.isInPlaceModeEnabled()); +} + +TEST(DocumentInPlace, ReserveDamageEventsIsAlwaysSafeToCall) { + mongo::BSONObj obj = mongo::fromjson("{ foo : 'foo' }"); + mmb::Document doc(obj, mmb::Document::kInPlaceEnabled); + ASSERT_TRUE(doc.isInPlaceModeEnabled()); + doc.reserveDamageEvents(10); + doc.disableInPlaceUpdates(); + ASSERT_FALSE(doc.isInPlaceModeEnabled()); + doc.reserveDamageEvents(10); +} + +TEST(DocumentInPlace, GettingInPlaceUpdatesWhenDisabledClearsArguments) { + mongo::BSONObj obj = mongo::fromjson("{ foo : 'foo' }"); + mmb::Document doc(obj, mmb::Document::kInPlaceDisabled); + ASSERT_FALSE(doc.isInPlaceModeEnabled()); + + mmb::DamageVector damages; + const mmb::DamageEvent event = {0}; + damages.push_back(event); + const char* source = "foo"; + ASSERT_FALSE(doc.getInPlaceUpdates(&damages, &source)); + ASSERT_TRUE(damages.empty()); + ASSERT_EQUALS(static_cast<const char*>(NULL), source); + + damages.push_back(event); + source = "bar"; + size_t size = 1; + ASSERT_FALSE(doc.getInPlaceUpdates(&damages, &source, &size)); + ASSERT_TRUE(damages.empty()); + ASSERT_EQUALS(static_cast<const char*>(NULL), source); + ASSERT_EQUALS(0U, size); +} + +// This isn't a great test since we aren't testing all possible combinations of compatible +// and incompatible sets, but since all setValueX calls decay to the internal setValue, we +// can be pretty sure that this will at least check the logic somewhat. +TEST(DocumentInPlace, InPlaceModeIsDisabledByIncompatibleSetValue) { + mongo::BSONObj obj(mongo::fromjson("{ foo : false }")); + mmb::Document doc(obj, mmb::Document::kInPlaceEnabled); + ASSERT_TRUE(doc.isInPlaceModeEnabled()); + mmb::Element foo = doc.root().leftChild(); + ASSERT_TRUE(foo.ok()); + ASSERT_OK(foo.setValueString("foo")); + ASSERT_FALSE(doc.isInPlaceModeEnabled()); +} + +TEST(DocumentInPlace, DisablingInPlaceDoesNotDiscardUpdates) { + mongo::BSONObj obj(mongo::fromjson("{ foo : false, bar : true }")); + mmb::Document doc(obj, mmb::Document::kInPlaceEnabled); + ASSERT_TRUE(doc.isInPlaceModeEnabled()); + + mmb::Element foo = doc.root().leftChild(); + ASSERT_TRUE(foo.ok()); + ASSERT_OK(foo.setValueBool(true)); + ASSERT_TRUE(doc.isInPlaceModeEnabled()); + + mmb::Element bar = doc.root().rightChild(); + ASSERT_TRUE(bar.ok()); + ASSERT_OK(bar.setValueBool(false)); + ASSERT_TRUE(doc.isInPlaceModeEnabled()); + + ASSERT_OK(doc.root().appendString("baz", "baz")); + ASSERT_FALSE(doc.isInPlaceModeEnabled()); + + static const char outJson[] = "{ foo : true, bar : false, baz : 'baz' }"; + ASSERT_EQUALS(mongo::fromjson(outJson), doc.getObject()); +} + +TEST(DocumentInPlace, StringLifecycle) { + mongo::BSONObj obj(mongo::fromjson("{ x : 'foo' }")); + mmb::Document doc(obj, mmb::Document::kInPlaceEnabled); + + mmb::Element x = doc.root().leftChild(); + + mmb::DamageVector damages; + const char* source = NULL; + + x.setValueString("bar"); + ASSERT_TRUE(doc.getInPlaceUpdates(&damages, &source)); + ASSERT_EQUALS(1U, damages.size()); + apply(&obj, damages, source); + ASSERT_TRUE(x.hasValue()); + ASSERT_TRUE(x.isType(mongo::String)); + ASSERT_EQUALS("bar", x.getValueString()); + + // TODO: When in-place updates for leaf elements is implemented, add tests here. +} + +TEST(DocumentInPlace, BinDataLifecycle) { + const char kData1[] = "\x01\x02\x03\x04\x05\x06"; + const char kData2[] = "\x10\x20\x30\x40\x50\x60"; + + const mongo::BSONBinData binData1(kData1, sizeof(kData1) - 1, mongo::BinDataGeneral); + const mongo::BSONBinData binData2(kData2, sizeof(kData2) - 1, mongo::bdtCustom); + + mongo::BSONObj obj(BSON("x" << binData1)); + mmb::Document doc(obj, mmb::Document::kInPlaceEnabled); + + mmb::Element x = doc.root().leftChild(); + + mmb::DamageVector damages; + const char* source = NULL; + + x.setValueBinary(binData2.length, binData2.type, binData2.data); + ASSERT_TRUE(doc.getInPlaceUpdates(&damages, &source)); + ASSERT_EQUALS(1U, damages.size()); + apply(&obj, damages, source); + ASSERT_TRUE(x.hasValue()); + ASSERT_TRUE(x.isType(mongo::BinData)); + + mongo::BSONElement value = x.getValue(); + ASSERT_EQUALS(binData2.type, value.binDataType()); + int len = 0; + const char* const data = value.binDataClean(len); + ASSERT_EQUALS(binData2.length, len); + ASSERT_EQUALS(0, std::memcmp(data, kData2, len)); + + // TODO: When in-place updates for leaf elements is implemented, add tests here. +} + +TEST(DocumentInPlace, OIDLifecycle) { + const mongo::OID oid1 = mongo::OID::gen(); + const mongo::OID oid2 = mongo::OID::gen(); + ASSERT_NOT_EQUALS(oid1, oid2); + + mongo::BSONObj obj(BSON("x" << oid1)); + mmb::Document doc(obj, mmb::Document::kInPlaceEnabled); + + mmb::Element x = doc.root().leftChild(); + + mmb::DamageVector damages; + const char* source = NULL; + + x.setValueOID(oid2); + ASSERT_TRUE(doc.getInPlaceUpdates(&damages, &source)); + ASSERT_EQUALS(1U, damages.size()); + apply(&obj, damages, source); + ASSERT_TRUE(x.hasValue()); + ASSERT_TRUE(x.isType(mongo::jstOID)); + ASSERT_EQUALS(oid2, x.getValueOID()); + + // TODO: When in-place updates for leaf elements is implemented, add tests here. +} + +TEST(DocumentInPlace, BooleanLifecycle) { + mongo::BSONObj obj(mongo::fromjson("{ x : false }")); + mmb::Document doc(obj, mmb::Document::kInPlaceEnabled); + + mmb::Element x = doc.root().leftChild(); + + mmb::DamageVector damages; + const char* source = NULL; + + x.setValueBool(false); + ASSERT_TRUE(doc.getInPlaceUpdates(&damages, &source)); + ASSERT_EQUALS(1U, damages.size()); + apply(&obj, damages, source); + ASSERT_TRUE(x.hasValue()); + ASSERT_TRUE(x.isType(mongo::Bool)); + ASSERT_EQUALS(false, x.getValueBool()); + + // TODO: Re-enable when in-place updates to leaf elements is supported + // x.setValueBool(true); + // ASSERT_TRUE(doc.getInPlaceUpdates(&damages, &source)); + // apply(&obj, damages, source); + // ASSERT_TRUE(x.hasValue()); + // ASSERT_TRUE(x.isType(mongo::Bool)); + // ASSERT_EQUALS(true, x.getValueBool()); +} + +TEST(DocumentInPlace, DateLifecycle) { + mongo::BSONObj obj(BSON("x" << mongo::Date_t::fromMillisSinceEpoch(1000))); + mmb::Document doc(obj, mmb::Document::kInPlaceEnabled); + + mmb::Element x = doc.root().leftChild(); + + mmb::DamageVector damages; + const char* source = NULL; + + x.setValueDate(mongo::Date_t::fromMillisSinceEpoch(20000)); + ASSERT_TRUE(doc.getInPlaceUpdates(&damages, &source)); + ASSERT_EQUALS(1U, damages.size()); + apply(&obj, damages, source); + ASSERT_TRUE(x.hasValue()); + ASSERT_TRUE(x.isType(mongo::Date)); + ASSERT_EQUALS(mongo::Date_t::fromMillisSinceEpoch(20000), x.getValueDate()); + + // TODO: When in-place updates for leaf elements is implemented, add tests here. +} + +TEST(DocumentInPlace, NumberIntLifecycle) { + const int value1 = 42; + const int value2 = 3; + mongo::BSONObj obj(BSON("x" << value1)); + mmb::Document doc(obj, mmb::Document::kInPlaceEnabled); + + mmb::Element x = doc.root().leftChild(); + + mmb::DamageVector damages; + const char* source = NULL; + + x.setValueInt(value2); + ASSERT_TRUE(doc.getInPlaceUpdates(&damages, &source)); + ASSERT_EQUALS(1U, damages.size()); + apply(&obj, damages, source); + ASSERT_TRUE(x.hasValue()); + ASSERT_TRUE(x.isType(mongo::NumberInt)); + ASSERT_EQUALS(value2, x.getValueInt()); + + // TODO: Re-enable when in-place updates to leaf elements is supported + // x.setValueInt(value1); + // ASSERT_TRUE(doc.getInPlaceUpdates(&damages, &source)); + // apply(&obj, damages, source); + // ASSERT_TRUE(x.hasValue()); + // ASSERT_TRUE(x.isType(mongo::NumberInt)); + // ASSERT_EQUALS(value1, x.getValueInt()); +} + +TEST(DocumentInPlace, TimestampLifecycle) { + mongo::BSONObj obj(BSON("x" << mongo::Timestamp(mongo::Date_t::fromMillisSinceEpoch(1000)))); + mmb::Document doc(obj, mmb::Document::kInPlaceEnabled); + + mmb::Element x = doc.root().leftChild(); + + mmb::DamageVector damages; + const char* source = NULL; + + x.setValueTimestamp(mongo::Timestamp(mongo::Date_t::fromMillisSinceEpoch(20000))); + ASSERT_TRUE(doc.getInPlaceUpdates(&damages, &source)); + ASSERT_EQUALS(1U, damages.size()); + apply(&obj, damages, source); + ASSERT_TRUE(x.hasValue()); + ASSERT_TRUE(x.isType(mongo::bsonTimestamp)); + ASSERT_TRUE(mongo::Timestamp(20000U) == x.getValueTimestamp()); + + // TODO: When in-place updates for leaf elements is implemented, add tests here. +} + +TEST(DocumentInPlace, NumberLongLifecycle) { + const long long value1 = 42; + const long long value2 = 3; + + mongo::BSONObj obj(BSON("x" << value1)); + mmb::Document doc(obj, mmb::Document::kInPlaceEnabled); + + mmb::Element x = doc.root().leftChild(); + + mmb::DamageVector damages; + const char* source = NULL; + + x.setValueLong(value2); + ASSERT_TRUE(doc.getInPlaceUpdates(&damages, &source)); + ASSERT_EQUALS(1U, damages.size()); + apply(&obj, damages, source); + ASSERT_TRUE(x.hasValue()); + ASSERT_TRUE(x.isType(mongo::NumberLong)); + ASSERT_EQUALS(value2, x.getValueLong()); + + // TODO: Re-enable when in-place updates to leaf elements is supported + // x.setValueLong(value1); + // ASSERT_TRUE(doc.getInPlaceUpdates(&damages, &source)); + // apply(&obj, damages, source); + // ASSERT_TRUE(x.hasValue()); + // ASSERT_TRUE(x.isType(mongo::NumberLong)); + // ASSERT_EQUALS(value1, x.getValueLong()); +} + +TEST(DocumentInPlace, NumberDoubleLifecycle) { + const double value1 = 32.0; + const double value2 = 2.0; + + mongo::BSONObj obj(BSON("x" << value1)); + mmb::Document doc(obj, mmb::Document::kInPlaceEnabled); + + mmb::Element x = doc.root().leftChild(); + + mmb::DamageVector damages; + const char* source = NULL; + + x.setValueDouble(value2); + ASSERT_TRUE(doc.getInPlaceUpdates(&damages, &source)); + ASSERT_EQUALS(1U, damages.size()); + apply(&obj, damages, source); + ASSERT_TRUE(x.hasValue()); + ASSERT_TRUE(x.isType(mongo::NumberDouble)); + ASSERT_EQUALS(value2, x.getValueDouble()); + + // TODO: Re-enable when in-place updates to leaf elements is supported + // x.setValueDouble(value1); + // ASSERT_TRUE(doc.getInPlaceUpdates(&damages, &source)); + // apply(&obj, damages, source); + // ASSERT_TRUE(x.hasValue()); + // ASSERT_TRUE(x.isType(mongo::NumberDouble)); + // ASSERT_EQUALS(value1, x.getValueDouble()); +} + +// Doubles and longs are the same size, 8 bytes, so we should be able to do in-place +// updates between them. +TEST(DocumentInPlace, DoubleToLongAndBack) { + const double value1 = 32.0; + const long long value2 = 42; + + mongo::BSONObj obj(BSON("x" << value1)); + mmb::Document doc(obj, mmb::Document::kInPlaceEnabled); + + mmb::Element x = doc.root().leftChild(); + + mmb::DamageVector damages; + const char* source = NULL; + + x.setValueLong(value2); + ASSERT_TRUE(doc.getInPlaceUpdates(&damages, &source)); + // We changed the type, so we get an extra damage event. + ASSERT_EQUALS(2U, damages.size()); + apply(&obj, damages, source); + ASSERT_TRUE(x.hasValue()); + ASSERT_TRUE(x.isType(mongo::NumberLong)); + ASSERT_EQUALS(value2, x.getValueLong()); + + // TODO: Re-enable when in-place updates to leaf elements is supported + // x.setValueDouble(value1); + // ASSERT_TRUE(doc.getInPlaceUpdates(&damages, &source)); + // apply(&obj, damages, source); + // ASSERT_TRUE(x.hasValue()); + // ASSERT_TRUE(x.isType(mongo::NumberDouble)); + // ASSERT_EQUALS(value1, x.getValueDouble()); +} + +TEST(DocumentComparison, SimpleComparison) { + const mongo::BSONObj obj = + mongo::fromjson("{ a : 'a', b : ['b', 'b', 'b'], c : { one : 1.0 } }"); + + const mmb::Document doc1(obj.getOwned()); + ASSERT_EQUALS(0, doc1.compareWithBSONObj(obj)); + const mmb::Document doc2(obj.getOwned()); + ASSERT_EQUALS(0, doc1.compareWith(doc2)); + ASSERT_EQUALS(0, doc2.compareWith(doc1)); +} + +TEST(DocumentComparison, SimpleComparisonWithDeserializedElements) { + const mongo::BSONObj obj = + mongo::fromjson("{ a : 'a', b : ['b', 'b', 'b'], c : { one : 1.0 } }"); + + // Perform an operation on 'b' that doesn't change the serialized value, but + // deserializes the node. + mmb::Document doc1(obj.getOwned()); + const mmb::Document doc1Copy(obj.getOwned()); + mmb::Element b = doc1.root()["b"]; + ASSERT_TRUE(b.ok()); + mmb::Element b0 = b[0]; + ASSERT_TRUE(b0.ok()); + ASSERT_OK(b0.remove()); + ASSERT_OK(b.pushBack(b0)); + // Ensure that it compares correctly against the source object. + ASSERT_EQUALS(0, doc1.compareWithBSONObj(obj)); + // Ensure that it compares correctly against a pristine document. + ASSERT_EQUALS(0, doc1.compareWith(doc1Copy)); + ASSERT_EQUALS(0, doc1Copy.compareWith(doc1)); + + // Perform an operation on 'c' that doesn't change the serialized value, but + // deserializeds the node. + mmb::Document doc2(obj.getOwned()); + const mmb::Document doc2Copy(obj.getOwned()); + mmb::Element c = doc2.root()["c"]; + ASSERT_TRUE(c.ok()); + mmb::Element c1 = c.leftChild(); + ASSERT_TRUE(c1.ok()); + ASSERT_OK(c1.remove()); + ASSERT_OK(c.pushBack(c1)); + // Ensure that it compares correctly against the source object + ASSERT_EQUALS(0, doc2.compareWithBSONObj(obj)); + // Ensure that it compares correctly against a pristine document. + ASSERT_EQUALS(0, doc2.compareWith(doc2Copy)); + ASSERT_EQUALS(0, doc2Copy.compareWith(doc2)); + + // Ensure that the two deserialized documents compare with each other correctly. + ASSERT_EQUALS(0, doc1.compareWith(doc2)); + ASSERT_EQUALS(0, doc2.compareWith(doc1)); +} + +TEST(UnorderedEqualityChecker, Identical) { + const mongo::BSONObj b1 = + mongo::fromjson("{ a : [1, 2, { 'a' : 'b', 'x' : 'y' } ], b : { x : 1, y : 2, z : 3 } }"); + + const mongo::BSONObj b2 = b1.getOwned(); + + ASSERT_EQUALS(mmb::unordered(b1), mmb::unordered(b2)); +} + +TEST(UnorderedEqualityChecker, DifferentValuesAreNotEqual) { + const mongo::BSONObj b1 = + mongo::fromjson("{ a : [ 1, 2, { 'a' : 'b', 'x' : 'y' } ], b : { x : 1, y : 2, z : 3 } }"); + const mongo::BSONObj b2 = + mongo::fromjson("{ a : [ 1, 2, { 'a' : 'b', 'x' : 'y' } ], b : { x : 1, y : 2, z : 4 } }"); + + ASSERT_NOT_EQUALS(mmb::unordered(b1), mmb::unordered(b2)); +} + +TEST(UnorderedEqualityChecker, DifferentTypesAreNotEqual) { + const mongo::BSONObj b1 = + mongo::fromjson("{ a : [ 1, 2, { 'a' : 'b', 'x' : 'y' } ], b : { x : 1, y : 2, z : 3 } }"); + const mongo::BSONObj b2 = mongo::fromjson( + "{ a : [ 1, 2, { 'a' : 'b', 'x' : 'y' } ], b : { x : 1, y : '2', z : 3 } }"); + + ASSERT_NOT_EQUALS(mmb::unordered(b1), mmb::unordered(b2)); +} + +TEST(UnorderedEqualityChecker, DifferentFieldNamesAreNotEqual) { + const mongo::BSONObj b1 = + mongo::fromjson("{ a : [ 1, 2, { 'a' : 'b', 'x' : 'y' } ], b : { x : 1, y : 2, z : 3 } }"); + const mongo::BSONObj b2 = + mongo::fromjson("{ a : [ 1, 2, { 'a' : 'b', 'x' : 'y' } ], b : { x : 1, Y : 2, z : 3 } }"); + + ASSERT_NOT_EQUALS(mmb::unordered(b1), mmb::unordered(b2)); +} + +TEST(UnorderedEqualityChecker, MissingFieldsInObjectAreNotEqual) { + const mongo::BSONObj b1 = + mongo::fromjson("{ a : [ 1, 2, { 'a' : 'b', 'x' : 'y' } ], b : { x : 1, y : 2, z : 3 } }"); + const mongo::BSONObj b2 = + mongo::fromjson("{ a : [ 1, 2, { 'a' : 'b', 'x' : 'y' } ], b : { x : 1, z : 3 } }"); + + ASSERT_NOT_EQUALS(mmb::unordered(b1), mmb::unordered(b2)); +} + +TEST(UnorderedEqualityChecker, ObjectOrderingIsNotConsidered) { + const mongo::BSONObj b1 = + mongo::fromjson("{ a : [ 1, 2, { 'a' : 'b', 'x' : 'y' } ], b : { x : 1, y : 2, z : 3 } }"); + const mongo::BSONObj b2 = mongo::fromjson( + "{ b : { y : 2, z : 3 , x : 1 }, a : [ 1, 2, { 'a' : 'b', 'x' : 'y' } ] }"); + + ASSERT_EQUALS(mmb::unordered(b1), mmb::unordered(b2)); +} + +TEST(UnorderedEqualityChecker, ArrayOrderingIsConsidered) { + const mongo::BSONObj b1 = + mongo::fromjson("{ a : [ 1, 2, { 'a' : 'b', 'x' : 'y' } ], b : { x : 1, y : 2, z : 3 } }"); + + const mongo::BSONObj b2 = + mongo::fromjson("{ a : [ 1, { 'a' : 'b', 'x' : 'y' }, 2 ], b : { x : 1, y : 2, z : 3 } }"); + + ASSERT_NOT_EQUALS(mmb::unordered(b1), mmb::unordered(b2)); +} + +TEST(UnorderedEqualityChecker, MissingItemsInArrayAreNotEqual) { + const mongo::BSONObj b1 = + mongo::fromjson("{ a : [ 1, 2, { 'a' : 'b', 'x' : 'y' } ], b : { x : 1, y : 2, z : 3 } }"); + const mongo::BSONObj b2 = + mongo::fromjson("{ a : [ 1, { 'a' : 'b', 'x' : 'y' } ], b : { x : 1, z : 3 } }"); + + ASSERT_NOT_EQUALS(mmb::unordered(b1), mmb::unordered(b2)); +} + +} // namespace diff --git a/src/mongo/bson/mutable/mutable_bson_test_utils.cpp b/src/mongo/bson/mutable/mutable_bson_test_utils.cpp index 131c6df7929..70217ddb049 100644 --- a/src/mongo/bson/mutable/mutable_bson_test_utils.cpp +++ b/src/mongo/bson/mutable/mutable_bson_test_utils.cpp @@ -42,177 +42,172 @@ namespace mongo { namespace mutablebson { - namespace { - - inline void assertSameSign(int lhs, int rhs) { - if (lhs == 0) { - ASSERT_EQUALS(rhs, 0); - } else if (lhs < 0) { - ASSERT_LESS_THAN(rhs, 0); - } else { - ASSERT_GREATER_THAN(rhs, 0); - } - } - - inline void assertOppositeSign(int lhs, int rhs) { - if (lhs == 0) { - ASSERT_EQUALS(rhs, 0); - } else if (lhs < 0) { - ASSERT_GREATER_THAN(rhs, 0); - } else { - ASSERT_LESS_THAN(rhs, 0); - } - } +namespace { + +inline void assertSameSign(int lhs, int rhs) { + if (lhs == 0) { + ASSERT_EQUALS(rhs, 0); + } else if (lhs < 0) { + ASSERT_LESS_THAN(rhs, 0); + } else { + ASSERT_GREATER_THAN(rhs, 0); + } +} + +inline void assertOppositeSign(int lhs, int rhs) { + if (lhs == 0) { + ASSERT_EQUALS(rhs, 0); + } else if (lhs < 0) { + ASSERT_GREATER_THAN(rhs, 0); + } else { + ASSERT_LESS_THAN(rhs, 0); + } +} - void addChildrenToVector(ConstElement elt, std::vector<ConstElement>* accumulator) { - ConstElement current = elt.leftChild(); - while (current.ok()) { - accumulator->push_back(current); - current = current.rightSibling(); - } +void addChildrenToVector(ConstElement elt, std::vector<ConstElement>* accumulator) { + ConstElement current = elt.leftChild(); + while (current.ok()) { + accumulator->push_back(current); + current = current.rightSibling(); + } +} + +bool checkDocNoOrderingImpl(ConstElement lhs, ConstElement rhs) { + const BSONType lhsType = lhs.getType(); + const BSONType rhsType = rhs.getType(); + + if (lhsType == mongo::Object) { + if (rhsType != mongo::Object) + return false; + + // For objects, sort the children by field name, then compare in that order. + + std::vector<ConstElement> lhsChildren; + addChildrenToVector(lhs, &lhsChildren); + std::vector<ConstElement> rhsChildren; + addChildrenToVector(rhs, &rhsChildren); + if (lhsChildren.size() != rhsChildren.size()) + return false; + + // NOTE: if you have repeated field names, this is not necessarily going to + // work. This is unlikely to be a problem in practice, but we could write a + // more sophisticated comparator if we need to: perhaps one that ordered first + // by field name, then by type, then by woCompare. Performance isn't important + // here. + std::sort(lhsChildren.begin(), lhsChildren.end(), FieldNameLessThan()); + std::sort(rhsChildren.begin(), rhsChildren.end(), FieldNameLessThan()); + + typedef std::vector<ConstElement>::const_iterator iter; + iter lhsWhere = lhsChildren.begin(); + iter rhsWhere = rhsChildren.begin(); + const iter lhsEnd = lhsChildren.end(); + + for (; lhsWhere != lhsEnd; ++lhsWhere, ++rhsWhere) { + if (lhsWhere->getFieldName() != rhsWhere->getFieldName()) + return false; + + if (!checkDocNoOrderingImpl(*lhsWhere, *rhsWhere)) + return false; } - bool checkDocNoOrderingImpl(ConstElement lhs, ConstElement rhs) { - const BSONType lhsType = lhs.getType(); - const BSONType rhsType = rhs.getType(); - - if (lhsType == mongo::Object) { - - if (rhsType != mongo::Object) - return false; - - // For objects, sort the children by field name, then compare in that order. - - std::vector<ConstElement> lhsChildren; - addChildrenToVector(lhs, &lhsChildren); - std::vector<ConstElement> rhsChildren; - addChildrenToVector(rhs, &rhsChildren); - if (lhsChildren.size() != rhsChildren.size()) - return false; - - // NOTE: if you have repeated field names, this is not necessarily going to - // work. This is unlikely to be a problem in practice, but we could write a - // more sophisticated comparator if we need to: perhaps one that ordered first - // by field name, then by type, then by woCompare. Performance isn't important - // here. - std::sort(lhsChildren.begin(), lhsChildren.end(), FieldNameLessThan()); - std::sort(rhsChildren.begin(), rhsChildren.end(), FieldNameLessThan()); - - typedef std::vector<ConstElement>::const_iterator iter; - iter lhsWhere = lhsChildren.begin(); - iter rhsWhere = rhsChildren.begin(); - const iter lhsEnd = lhsChildren.end(); - - for (; lhsWhere != lhsEnd; ++lhsWhere, ++rhsWhere) { - - if (lhsWhere->getFieldName() != rhsWhere->getFieldName()) - return false; + return true; - if (!checkDocNoOrderingImpl(*lhsWhere, *rhsWhere)) - return false; - } + } else if (lhsType == mongo::Array) { + if (rhsType != mongo::Array) + return false; - return true; + // For arrays, since they are ordered, we don't need the sorting step. + const size_t lhsChildren = countChildren(lhs); + const size_t rhsChildren = countChildren(rhs); - } else if (lhsType == mongo::Array) { + if (lhsChildren != rhsChildren) + return false; - if (rhsType != mongo::Array) - return false; + if (lhsChildren == 0) + return true; - // For arrays, since they are ordered, we don't need the sorting step. - const size_t lhsChildren = countChildren(lhs); - const size_t rhsChildren = countChildren(rhs); + ConstElement lhsChild = lhs.leftChild(); + ConstElement rhsChild = rhs.leftChild(); - if (lhsChildren != rhsChildren) - return false; + while (lhsChild.ok()) { + if (!checkDocNoOrderingImpl(lhsChild, rhsChild)) + return false; - if (lhsChildren == 0) - return true; - - ConstElement lhsChild = lhs.leftChild(); - ConstElement rhsChild = rhs.leftChild(); - - while (lhsChild.ok()) { - if (!checkDocNoOrderingImpl(lhsChild, rhsChild)) - return false; - - lhsChild = lhsChild.rightSibling(); - rhsChild = rhsChild.rightSibling(); - } - - return true; - - } else { - // This is some leaf type. We've already checked or ignored field names, so - // don't recheck it here. - return lhs.compareWithElement(rhs, false) == 0; - } + lhsChild = lhsChild.rightSibling(); + rhsChild = rhsChild.rightSibling(); } - } // namespace + return true; - // TODO: We should really update this to be an ASSERT_ something, so that we can print out - // the expected and actual documents. - bool checkDoc(const Document& lhs, const BSONObj& rhs) { + } else { + // This is some leaf type. We've already checked or ignored field names, so + // don't recheck it here. + return lhs.compareWithElement(rhs, false) == 0; + } +} - // Get the fundamental result via BSONObj's woCompare path. This is the best starting - // point, because we think that Document::getObject and the serialization mechanism is - // pretty well sorted. - BSONObj fromLhs = lhs.getObject(); - const int primaryResult = fromLhs.woCompare(rhs); +} // namespace - // Validate primary result via other comparison paths. - const int secondaryResult = lhs.compareWithBSONObj(rhs); +// TODO: We should really update this to be an ASSERT_ something, so that we can print out +// the expected and actual documents. +bool checkDoc(const Document& lhs, const BSONObj& rhs) { + // Get the fundamental result via BSONObj's woCompare path. This is the best starting + // point, because we think that Document::getObject and the serialization mechanism is + // pretty well sorted. + BSONObj fromLhs = lhs.getObject(); + const int primaryResult = fromLhs.woCompare(rhs); - assertSameSign(primaryResult, secondaryResult); + // Validate primary result via other comparison paths. + const int secondaryResult = lhs.compareWithBSONObj(rhs); - // Check that mutables serialized result matches against its origin. - ASSERT_EQUALS(0, lhs.compareWithBSONObj(fromLhs)); + assertSameSign(primaryResult, secondaryResult); - return (primaryResult == 0); - } + // Check that mutables serialized result matches against its origin. + ASSERT_EQUALS(0, lhs.compareWithBSONObj(fromLhs)); - bool checkDoc(const Document& lhs, const Document& rhs) { + return (primaryResult == 0); +} - const int primaryResult = lhs.compareWith(rhs); +bool checkDoc(const Document& lhs, const Document& rhs) { + const int primaryResult = lhs.compareWith(rhs); - const BSONObj fromLhs = lhs.getObject(); - const BSONObj fromRhs = rhs.getObject(); + const BSONObj fromLhs = lhs.getObject(); + const BSONObj fromRhs = rhs.getObject(); - const int result_d_o = lhs.compareWithBSONObj(fromRhs); - const int result_o_d = rhs.compareWithBSONObj(fromLhs); + const int result_d_o = lhs.compareWithBSONObj(fromRhs); + const int result_o_d = rhs.compareWithBSONObj(fromLhs); - assertSameSign(primaryResult, result_d_o); - assertOppositeSign(primaryResult, result_o_d); + assertSameSign(primaryResult, result_d_o); + assertOppositeSign(primaryResult, result_o_d); - ASSERT_EQUALS(0, lhs.compareWithBSONObj(fromLhs)); - ASSERT_EQUALS(0, rhs.compareWithBSONObj(fromRhs)); + ASSERT_EQUALS(0, lhs.compareWithBSONObj(fromLhs)); + ASSERT_EQUALS(0, rhs.compareWithBSONObj(fromRhs)); - return (primaryResult == 0); - } + return (primaryResult == 0); +} - std::ostream& operator<<(std::ostream& stream, const Document& doc) { - stream << doc.toString(); - return stream; - } +std::ostream& operator<<(std::ostream& stream, const Document& doc) { + stream << doc.toString(); + return stream; +} - std::ostream& operator<<(std::ostream& stream, const ConstElement& elt) { - stream << elt.toString(); - return stream; - } +std::ostream& operator<<(std::ostream& stream, const ConstElement& elt) { + stream << elt.toString(); + return stream; +} - bool checkEqualNoOrdering(const Document& lhs, const Document& rhs) { - return checkDocNoOrderingImpl(lhs.root(), rhs.root()); - } +bool checkEqualNoOrdering(const Document& lhs, const Document& rhs) { + return checkDocNoOrderingImpl(lhs.root(), rhs.root()); +} - std::ostream& operator<<(std::ostream& stream, const UnorderedWrapper_Doc& uw_d) { - return stream << uw_d.doc; - } +std::ostream& operator<<(std::ostream& stream, const UnorderedWrapper_Doc& uw_d) { + return stream << uw_d.doc; +} - std::ostream& operator<<(std::ostream& stream, const UnorderedWrapper_Obj& uw_o) { - const Document d(uw_o.obj); - return stream << d; - } +std::ostream& operator<<(std::ostream& stream, const UnorderedWrapper_Obj& uw_o) { + const Document d(uw_o.obj); + return stream << d; +} -} // namespace mutablebson -} // namespace mongo +} // namespace mutablebson +} // namespace mongo diff --git a/src/mongo/bson/mutable/mutable_bson_test_utils.h b/src/mongo/bson/mutable/mutable_bson_test_utils.h index a3b8a25084c..c52a7cbe6ed 100644 --- a/src/mongo/bson/mutable/mutable_bson_test_utils.h +++ b/src/mongo/bson/mutable/mutable_bson_test_utils.h @@ -31,106 +31,104 @@ namespace mongo { - class BSONObj; +class BSONObj; namespace mutablebson { - // - // Utilities for mutable BSON unit tests. - // +// +// Utilities for mutable BSON unit tests. +// - /** - * Catch all comparator between a mutable 'doc' and the expected BSON 'exp'. It compares - * (a) 'doc's generated object, (b) 'exp', the expected object, and (c) 'doc(exp)', a - * document created from 'exp'. Returns true if all three are equal, otherwise false. - */ - bool checkDoc(const Document& lhs, const BSONObj& rhs); - bool checkDoc(const Document& lhs, const Document& rhs); +/** + * Catch all comparator between a mutable 'doc' and the expected BSON 'exp'. It compares + * (a) 'doc's generated object, (b) 'exp', the expected object, and (c) 'doc(exp)', a + * document created from 'exp'. Returns true if all three are equal, otherwise false. + */ +bool checkDoc(const Document& lhs, const BSONObj& rhs); +bool checkDoc(const Document& lhs, const Document& rhs); - inline bool operator==(const Document& lhs, const Document& rhs) { - return checkDoc(lhs, rhs); - } +inline bool operator==(const Document& lhs, const Document& rhs) { + return checkDoc(lhs, rhs); +} - inline bool operator==(const BSONObj& lhs, const Document& rhs) { - return checkDoc(rhs, lhs); - } +inline bool operator==(const BSONObj& lhs, const Document& rhs) { + return checkDoc(rhs, lhs); +} - inline bool operator==(const Document& lhs, const BSONObj& rhs) { - return checkDoc(lhs, rhs); - } +inline bool operator==(const Document& lhs, const BSONObj& rhs) { + return checkDoc(lhs, rhs); +} - /** Stream out a document; useful within ASSERT calls */ - std::ostream& operator<<(std::ostream& stream, const Document& doc); +/** Stream out a document; useful within ASSERT calls */ +std::ostream& operator<<(std::ostream& stream, const Document& doc); - /** Stream out an element; useful within ASSERT calls */ - std::ostream& operator<<(std::ostream& stream, const ConstElement& elt); +/** Stream out an element; useful within ASSERT calls */ +std::ostream& operator<<(std::ostream& stream, const ConstElement& elt); - /** Check that the two provided Documents are equivalent modulo field ordering in Object - * Elements. Leaf values are considered equal via woCompare. - */ - bool checkEqualNoOrdering(const Document& lhs, const Document& rhs); +/** Check that the two provided Documents are equivalent modulo field ordering in Object + * Elements. Leaf values are considered equal via woCompare. + */ +bool checkEqualNoOrdering(const Document& lhs, const Document& rhs); - struct UnorderedWrapper_Obj { - inline explicit UnorderedWrapper_Obj(const BSONObj& o) - : obj(o) {} - const BSONObj& obj; - }; +struct UnorderedWrapper_Obj { + inline explicit UnorderedWrapper_Obj(const BSONObj& o) : obj(o) {} + const BSONObj& obj; +}; - struct UnorderedWrapper_Doc { - inline explicit UnorderedWrapper_Doc(const Document& d) - : doc(d) {} - const Document& doc; - }; +struct UnorderedWrapper_Doc { + inline explicit UnorderedWrapper_Doc(const Document& d) : doc(d) {} + const Document& doc; +}; - inline UnorderedWrapper_Doc unordered(const Document& d) { - return UnorderedWrapper_Doc(d); - } +inline UnorderedWrapper_Doc unordered(const Document& d) { + return UnorderedWrapper_Doc(d); +} - inline UnorderedWrapper_Obj unordered(const BSONObj& o) { - return UnorderedWrapper_Obj(o); - } +inline UnorderedWrapper_Obj unordered(const BSONObj& o) { + return UnorderedWrapper_Obj(o); +} - inline bool operator==(const UnorderedWrapper_Doc& lhs, const UnorderedWrapper_Doc& rhs) { - return checkEqualNoOrdering(lhs.doc, rhs.doc); - } +inline bool operator==(const UnorderedWrapper_Doc& lhs, const UnorderedWrapper_Doc& rhs) { + return checkEqualNoOrdering(lhs.doc, rhs.doc); +} - inline bool operator!=(const UnorderedWrapper_Doc& lhs, const UnorderedWrapper_Doc& rhs) { - return !(lhs == rhs); - } +inline bool operator!=(const UnorderedWrapper_Doc& lhs, const UnorderedWrapper_Doc& rhs) { + return !(lhs == rhs); +} - inline bool operator==(const UnorderedWrapper_Obj& lhs, const UnorderedWrapper_Obj& rhs) { - const Document dlhs(lhs.obj); - const Document drhs(rhs.obj); - return checkEqualNoOrdering(dlhs, drhs); - } +inline bool operator==(const UnorderedWrapper_Obj& lhs, const UnorderedWrapper_Obj& rhs) { + const Document dlhs(lhs.obj); + const Document drhs(rhs.obj); + return checkEqualNoOrdering(dlhs, drhs); +} - inline bool operator!=(const UnorderedWrapper_Obj& lhs, const UnorderedWrapper_Obj& rhs) { - return !(lhs == rhs); - } +inline bool operator!=(const UnorderedWrapper_Obj& lhs, const UnorderedWrapper_Obj& rhs) { + return !(lhs == rhs); +} - inline bool operator==(const UnorderedWrapper_Doc& lhs, const UnorderedWrapper_Obj& rhs) { - const Document drhs(rhs.obj); - return checkEqualNoOrdering(lhs.doc, drhs); - } +inline bool operator==(const UnorderedWrapper_Doc& lhs, const UnorderedWrapper_Obj& rhs) { + const Document drhs(rhs.obj); + return checkEqualNoOrdering(lhs.doc, drhs); +} - inline bool operator!=(const UnorderedWrapper_Doc& lhs, const UnorderedWrapper_Obj& rhs) { - return !(lhs == rhs); - } +inline bool operator!=(const UnorderedWrapper_Doc& lhs, const UnorderedWrapper_Obj& rhs) { + return !(lhs == rhs); +} - inline bool operator==(const UnorderedWrapper_Obj& lhs, const UnorderedWrapper_Doc& rhs) { - const Document dlhs(lhs.obj); - return checkEqualNoOrdering(dlhs, rhs.doc); - } +inline bool operator==(const UnorderedWrapper_Obj& lhs, const UnorderedWrapper_Doc& rhs) { + const Document dlhs(lhs.obj); + return checkEqualNoOrdering(dlhs, rhs.doc); +} - inline bool operator!=(const UnorderedWrapper_Obj& lhs, const UnorderedWrapper_Doc& rhs) { - return !(lhs == rhs); - } +inline bool operator!=(const UnorderedWrapper_Obj& lhs, const UnorderedWrapper_Doc& rhs) { + return !(lhs == rhs); +} - std::ostream& operator<<(std::ostream& stream, const UnorderedWrapper_Doc& uw_d); - std::ostream& operator<<(std::ostream& stream, const UnorderedWrapper_Obj& uw_o); +std::ostream& operator<<(std::ostream& stream, const UnorderedWrapper_Doc& uw_d); +std::ostream& operator<<(std::ostream& stream, const UnorderedWrapper_Obj& uw_o); -} // namespace mutablebson -} // namespace mongo +} // namespace mutablebson +} // namespace mongo diff --git a/src/mongo/bson/oid.cpp b/src/mongo/bson/oid.cpp index 209d43a8a38..a78bcbde682 100644 --- a/src/mongo/bson/oid.cpp +++ b/src/mongo/bson/oid.cpp @@ -42,130 +42,127 @@ namespace mongo { namespace { - std::unique_ptr<AtomicUInt32> counter; - - const std::size_t kTimestampOffset = 0; - const std::size_t kInstanceUniqueOffset = kTimestampOffset + - OID::kTimestampSize; - const std::size_t kIncrementOffset = kInstanceUniqueOffset + - OID::kInstanceUniqueSize; - OID::InstanceUnique _instanceUnique; -} // namespace - - MONGO_INITIALIZER_GENERAL(OIDGeneration, MONGO_NO_PREREQUISITES, ("default")) - (InitializerContext* context) { - std::unique_ptr<SecureRandom> entropy(SecureRandom::create()); - counter.reset(new AtomicUInt32(uint32_t(entropy->nextInt64()))); - _instanceUnique = OID::InstanceUnique::generate(*entropy); - return Status::OK(); - } - - OID::Increment OID::Increment::next() { - uint64_t nextCtr = counter->fetchAndAdd(1); - OID::Increment incr; - - incr.bytes[0] = uint8_t(nextCtr >> 16); - incr.bytes[1] = uint8_t(nextCtr >> 8); - incr.bytes[2] = uint8_t(nextCtr); - - return incr; - } - - OID::InstanceUnique OID::InstanceUnique::generate(SecureRandom& entropy) { - int64_t rand = entropy.nextInt64(); - OID::InstanceUnique u; - std::memcpy(u.bytes, &rand, kInstanceUniqueSize); - return u; - } - - void OID::setTimestamp(const OID::Timestamp timestamp) { - _view().write<BigEndian<Timestamp>>(timestamp, kTimestampOffset); - } - - void OID::setInstanceUnique(const OID::InstanceUnique unique) { - // Byte order doesn't matter here - _view().write<InstanceUnique>(unique, kInstanceUniqueOffset); - } - - void OID::setIncrement(const OID::Increment inc) { - _view().write<Increment>(inc, kIncrementOffset); - } - - OID::Timestamp OID::getTimestamp() const { - return view().read<BigEndian<Timestamp>>(kTimestampOffset); - } - - OID::InstanceUnique OID::getInstanceUnique() const { - // Byte order doesn't matter here - return view().read<InstanceUnique>(kInstanceUniqueOffset); - } - - OID::Increment OID::getIncrement() const { - return view().read<Increment>(kIncrementOffset); - } +std::unique_ptr<AtomicUInt32> counter; - void OID::hash_combine(size_t &seed) const { - uint32_t v; - for (int i = 0; i != kOIDSize; i += sizeof(uint32_t)) { - memcpy(&v, _data + i, sizeof(uint32_t)); - boost::hash_combine(seed, v); - } - } - - size_t OID::Hasher::operator() (const OID& oid) const { - size_t seed = 0; - oid.hash_combine(seed); - return seed; - } - - void OID::regenMachineId() { - std::unique_ptr<SecureRandom> entropy(SecureRandom::create()); - _instanceUnique = InstanceUnique::generate(*entropy); - } - - unsigned OID::getMachineId() { - uint32_t ret = 0; - std::memcpy(&ret, _instanceUnique.bytes, sizeof(uint32_t)); - return ret; - } - - void OID::justForked() { - regenMachineId(); - } - - void OID::init() { - // each set* method handles endianness - setTimestamp(time(0)); - setInstanceUnique(_instanceUnique); - setIncrement(Increment::next()); - } - - void OID::init( const std::string& s ) { - verify( s.size() == 24 ); - const char *p = s.c_str(); - for (std::size_t i = 0; i < kOIDSize; i++) { - _data[i] = fromHex(p); - p += 2; - } - } - - void OID::init(Date_t date, bool max) { - setTimestamp(uint32_t(date.toMillisSinceEpoch() / 1000)); - uint64_t rest = max ? std::numeric_limits<uint64_t>::max() : 0u; - std::memcpy(_view().view(kInstanceUniqueOffset), &rest, - kInstanceUniqueSize + kIncrementSize); - } - - time_t OID::asTimeT() const { - return getTimestamp(); - } - - std::string OID::toString() const { - return toHexLower(_data, kOIDSize); - } +const std::size_t kTimestampOffset = 0; +const std::size_t kInstanceUniqueOffset = kTimestampOffset + OID::kTimestampSize; +const std::size_t kIncrementOffset = kInstanceUniqueOffset + OID::kInstanceUniqueSize; +OID::InstanceUnique _instanceUnique; +} // namespace - std::string OID::toIncString() const { - return toHexLower(getIncrement().bytes, kIncrementSize); - } +MONGO_INITIALIZER_GENERAL(OIDGeneration, MONGO_NO_PREREQUISITES, ("default")) +(InitializerContext* context) { + std::unique_ptr<SecureRandom> entropy(SecureRandom::create()); + counter.reset(new AtomicUInt32(uint32_t(entropy->nextInt64()))); + _instanceUnique = OID::InstanceUnique::generate(*entropy); + return Status::OK(); +} + +OID::Increment OID::Increment::next() { + uint64_t nextCtr = counter->fetchAndAdd(1); + OID::Increment incr; + + incr.bytes[0] = uint8_t(nextCtr >> 16); + incr.bytes[1] = uint8_t(nextCtr >> 8); + incr.bytes[2] = uint8_t(nextCtr); + + return incr; +} + +OID::InstanceUnique OID::InstanceUnique::generate(SecureRandom& entropy) { + int64_t rand = entropy.nextInt64(); + OID::InstanceUnique u; + std::memcpy(u.bytes, &rand, kInstanceUniqueSize); + return u; +} + +void OID::setTimestamp(const OID::Timestamp timestamp) { + _view().write<BigEndian<Timestamp>>(timestamp, kTimestampOffset); +} + +void OID::setInstanceUnique(const OID::InstanceUnique unique) { + // Byte order doesn't matter here + _view().write<InstanceUnique>(unique, kInstanceUniqueOffset); +} + +void OID::setIncrement(const OID::Increment inc) { + _view().write<Increment>(inc, kIncrementOffset); +} + +OID::Timestamp OID::getTimestamp() const { + return view().read<BigEndian<Timestamp>>(kTimestampOffset); +} + +OID::InstanceUnique OID::getInstanceUnique() const { + // Byte order doesn't matter here + return view().read<InstanceUnique>(kInstanceUniqueOffset); +} + +OID::Increment OID::getIncrement() const { + return view().read<Increment>(kIncrementOffset); +} + +void OID::hash_combine(size_t& seed) const { + uint32_t v; + for (int i = 0; i != kOIDSize; i += sizeof(uint32_t)) { + memcpy(&v, _data + i, sizeof(uint32_t)); + boost::hash_combine(seed, v); + } +} + +size_t OID::Hasher::operator()(const OID& oid) const { + size_t seed = 0; + oid.hash_combine(seed); + return seed; +} + +void OID::regenMachineId() { + std::unique_ptr<SecureRandom> entropy(SecureRandom::create()); + _instanceUnique = InstanceUnique::generate(*entropy); +} + +unsigned OID::getMachineId() { + uint32_t ret = 0; + std::memcpy(&ret, _instanceUnique.bytes, sizeof(uint32_t)); + return ret; +} + +void OID::justForked() { + regenMachineId(); +} + +void OID::init() { + // each set* method handles endianness + setTimestamp(time(0)); + setInstanceUnique(_instanceUnique); + setIncrement(Increment::next()); +} + +void OID::init(const std::string& s) { + verify(s.size() == 24); + const char* p = s.c_str(); + for (std::size_t i = 0; i < kOIDSize; i++) { + _data[i] = fromHex(p); + p += 2; + } +} + +void OID::init(Date_t date, bool max) { + setTimestamp(uint32_t(date.toMillisSinceEpoch() / 1000)); + uint64_t rest = max ? std::numeric_limits<uint64_t>::max() : 0u; + std::memcpy(_view().view(kInstanceUniqueOffset), &rest, kInstanceUniqueSize + kIncrementSize); +} + +time_t OID::asTimeT() const { + return getTimestamp(); +} + +std::string OID::toString() const { + return toHexLower(_data, kOIDSize); +} + +std::string OID::toIncString() const { + return toHexLower(getIncrement().bytes, kIncrementSize); +} } // namespace mongo diff --git a/src/mongo/bson/oid.h b/src/mongo/bson/oid.h index a4ae3269d5f..476e15d1048 100644 --- a/src/mongo/bson/oid.h +++ b/src/mongo/bson/oid.h @@ -36,195 +36,203 @@ #include "mongo/util/time_support.h" namespace mongo { - class SecureRandom; - +class SecureRandom; + +/** + * Object ID type. + * BSON objects typically have an _id field for the object id. This field should be the first + * member of the object when present. The OID class is a special type that is a 12 byte id which + * is likely to be unique to the system. You may also use other types for _id's. + * When _id field is missing from a BSON object, on an insert the database may insert one + * automatically in certain circumstances. + * + * The BSON ObjectID is a 12-byte value consisting of a 4-byte timestamp (seconds since epoch), + * in the highest order 4 bytes followed by a 5 byte value unique to this machine AND process, + * followed by a 3 byte counter. + * + * 4 byte timestamp 5 byte process unique 3 byte counter + * |<----------------->|<---------------------->|<-------------> + * OID layout: [----|----|----|----|----|----|----|----|----|----|----|----] + * 0 4 8 12 + * + * The timestamp is a big endian 4 byte signed-integer. + * + * The process unique is an arbitrary sequence of 5 bytes. There are no endianness concerns + * since it is never interpreted as a multi-byte value. + * + * The counter is a big endian 3 byte unsigned integer. + * + * Note: The timestamp and counter are big endian (in contrast to the rest of BSON) because + * we use memcmp to order OIDs, and we want to ensure an increasing order. + * + * Warning: You MUST call OID::justForked() after a fork(). This ensures that each process will + * generate unique OIDs. + */ +class OID { +public: /** - * Object ID type. - * BSON objects typically have an _id field for the object id. This field should be the first - * member of the object when present. The OID class is a special type that is a 12 byte id which - * is likely to be unique to the system. You may also use other types for _id's. - * When _id field is missing from a BSON object, on an insert the database may insert one - * automatically in certain circumstances. - * - * The BSON ObjectID is a 12-byte value consisting of a 4-byte timestamp (seconds since epoch), - * in the highest order 4 bytes followed by a 5 byte value unique to this machine AND process, - * followed by a 3 byte counter. - * - * 4 byte timestamp 5 byte process unique 3 byte counter - * |<----------------->|<---------------------->|<-------------> - * OID layout: [----|----|----|----|----|----|----|----|----|----|----|----] - * 0 4 8 12 - * - * The timestamp is a big endian 4 byte signed-integer. - * - * The process unique is an arbitrary sequence of 5 bytes. There are no endianness concerns - * since it is never interpreted as a multi-byte value. - * - * The counter is a big endian 3 byte unsigned integer. - * - * Note: The timestamp and counter are big endian (in contrast to the rest of BSON) because - * we use memcmp to order OIDs, and we want to ensure an increasing order. - * - * Warning: You MUST call OID::justForked() after a fork(). This ensures that each process will - * generate unique OIDs. + * Functor compatible with std::hash for std::unordered_{map,set} + * Warning: The hash function is subject to change. Do not use in cases where hashes need + * to be consistent across versions. */ - class OID { - public: - - /** - * Functor compatible with std::hash for std::unordered_{map,set} - * Warning: The hash function is subject to change. Do not use in cases where hashes need - * to be consistent across versions. - */ - struct Hasher { - size_t operator() (const OID& oid) const; - }; - - OID() : _data() {} - - enum { - kOIDSize = 12, - kTimestampSize = 4, - kInstanceUniqueSize = 5, - kIncrementSize = 3 - }; - - /** init from a 24 char hex std::string */ - explicit OID(const std::string &s) { - init(s); - } - - /** init from a reference to a 12-byte array */ - explicit OID(const unsigned char (&arr)[kOIDSize]) { - std::memcpy(_data, arr, sizeof(arr)); - } - - /** initialize to 'null' */ - void clear() { std::memset(_data, 0, kOIDSize); } - - int compare( const OID& other ) const { return memcmp( _data , other._data , kOIDSize ); } - - /** @return the object ID output as 24 hex digits */ - std::string toString() const; - /** @return the random/sequential part of the object ID as 6 hex digits */ - std::string toIncString() const; - - static OID gen() { - OID o((no_initialize_tag())); - o.init(); - return o; - } - - // Caller must ensure that the buffer is valid for kOIDSize bytes. - // this is templated because some places use unsigned char vs signed char - template<typename T> - static OID from(T* buf) { - OID o((no_initialize_tag())); - std::memcpy(o._data, buf, OID::kOIDSize); - return o; - } - - static OID max() { - OID o((no_initialize_tag())); - std::memset(o._data, 0xFF, kOIDSize); - return o; - } - - /** sets the contents to a new oid / randomized value */ - void init(); - - /** init from a 24 char hex std::string */ - void init( const std::string& s ); - - /** Set to the min/max OID that could be generated at given timestamp. */ - void init( Date_t date, bool max=false ); - - time_t asTimeT() const; - Date_t asDateT() const { return Date_t::fromMillisSinceEpoch(asTimeT() * 1000LL); } - - // True iff the OID is not empty - bool isSet() const { - return compare(OID()) != 0; - } - - /** - * this is not consistent - * do not store on disk - */ - void hash_combine(size_t &seed) const; - - /** call this after a fork to update the process id */ - static void justForked(); - - static unsigned getMachineId(); // used by the 'features' command - static void regenMachineId(); - - // Timestamp is 4 bytes so we just use int32_t - typedef int32_t Timestamp; - - // Wrappers so we can return stuff by value. - struct InstanceUnique { - static InstanceUnique generate(SecureRandom& entropy); - uint8_t bytes[kInstanceUniqueSize]; - }; - - struct Increment { - public: - static Increment next(); - uint8_t bytes[kIncrementSize]; - }; - - void setTimestamp(Timestamp timestamp); - void setInstanceUnique(InstanceUnique unique); - void setIncrement(Increment inc); - - Timestamp getTimestamp() const; - InstanceUnique getInstanceUnique() const; - Increment getIncrement() const; - - ConstDataView view() const { - return ConstDataView(_data); - } - - private: - // Internal mutable view - DataView _view() { - return DataView(_data); - } - - // When we are going to immediately overwrite the bytes, there is no point in zero - // initializing the data first. - struct no_initialize_tag {}; - explicit OID(no_initialize_tag) {} - - char _data[kOIDSize]; + struct Hasher { + size_t operator()(const OID& oid) const; }; - inline std::ostream& operator<<(std::ostream &s, const OID &o) { - return (s << o.toString()); + OID() : _data() {} + + enum { kOIDSize = 12, kTimestampSize = 4, kInstanceUniqueSize = 5, kIncrementSize = 3 }; + + /** init from a 24 char hex std::string */ + explicit OID(const std::string& s) { + init(s); + } + + /** init from a reference to a 12-byte array */ + explicit OID(const unsigned char(&arr)[kOIDSize]) { + std::memcpy(_data, arr, sizeof(arr)); + } + + /** initialize to 'null' */ + void clear() { + std::memset(_data, 0, kOIDSize); + } + + int compare(const OID& other) const { + return memcmp(_data, other._data, kOIDSize); + } + + /** @return the object ID output as 24 hex digits */ + std::string toString() const; + /** @return the random/sequential part of the object ID as 6 hex digits */ + std::string toIncString() const; + + static OID gen() { + OID o((no_initialize_tag())); + o.init(); + return o; + } + + // Caller must ensure that the buffer is valid for kOIDSize bytes. + // this is templated because some places use unsigned char vs signed char + template <typename T> + static OID from(T* buf) { + OID o((no_initialize_tag())); + std::memcpy(o._data, buf, OID::kOIDSize); + return o; + } + + static OID max() { + OID o((no_initialize_tag())); + std::memset(o._data, 0xFF, kOIDSize); + return o; + } + + /** sets the contents to a new oid / randomized value */ + void init(); + + /** init from a 24 char hex std::string */ + void init(const std::string& s); + + /** Set to the min/max OID that could be generated at given timestamp. */ + void init(Date_t date, bool max = false); + + time_t asTimeT() const; + Date_t asDateT() const { + return Date_t::fromMillisSinceEpoch(asTimeT() * 1000LL); } - inline StringBuilder& operator<<(StringBuilder& s, const OID& o) { - return (s << o.toString()); + // True iff the OID is not empty + bool isSet() const { + return compare(OID()) != 0; } - /** Formatting mode for generating JSON from BSON. - See <http://dochub.mongodb.org/core/mongodbextendedjson> - for details. - */ - enum JsonStringFormat { - /** strict RFC format */ - Strict, - /** 10gen format, which is close to JS format. This form is understandable by - javascript running inside the Mongo server via eval() */ - TenGen, - /** Javascript JSON compatible */ - JS + /** + * this is not consistent + * do not store on disk + */ + void hash_combine(size_t& seed) const; + + /** call this after a fork to update the process id */ + static void justForked(); + + static unsigned getMachineId(); // used by the 'features' command + static void regenMachineId(); + + // Timestamp is 4 bytes so we just use int32_t + typedef int32_t Timestamp; + + // Wrappers so we can return stuff by value. + struct InstanceUnique { + static InstanceUnique generate(SecureRandom& entropy); + uint8_t bytes[kInstanceUniqueSize]; + }; + + struct Increment { + public: + static Increment next(); + uint8_t bytes[kIncrementSize]; }; - inline bool operator==(const OID& lhs, const OID& rhs) { return lhs.compare(rhs) == 0; } - inline bool operator!=(const OID& lhs, const OID& rhs) { return lhs.compare(rhs) != 0; } - inline bool operator<(const OID& lhs, const OID& rhs) { return lhs.compare(rhs) < 0; } - inline bool operator<=(const OID& lhs, const OID& rhs) { return lhs.compare(rhs) <= 0; } + void setTimestamp(Timestamp timestamp); + void setInstanceUnique(InstanceUnique unique); + void setIncrement(Increment inc); + + Timestamp getTimestamp() const; + InstanceUnique getInstanceUnique() const; + Increment getIncrement() const; + + ConstDataView view() const { + return ConstDataView(_data); + } + +private: + // Internal mutable view + DataView _view() { + return DataView(_data); + } + + // When we are going to immediately overwrite the bytes, there is no point in zero + // initializing the data first. + struct no_initialize_tag {}; + explicit OID(no_initialize_tag) {} + + char _data[kOIDSize]; +}; + +inline std::ostream& operator<<(std::ostream& s, const OID& o) { + return (s << o.toString()); +} + +inline StringBuilder& operator<<(StringBuilder& s, const OID& o) { + return (s << o.toString()); +} + +/** Formatting mode for generating JSON from BSON. + See <http://dochub.mongodb.org/core/mongodbextendedjson> + for details. +*/ +enum JsonStringFormat { + /** strict RFC format */ + Strict, + /** 10gen format, which is close to JS format. This form is understandable by + javascript running inside the Mongo server via eval() */ + TenGen, + /** Javascript JSON compatible */ + JS +}; + +inline bool operator==(const OID& lhs, const OID& rhs) { + return lhs.compare(rhs) == 0; +} +inline bool operator!=(const OID& lhs, const OID& rhs) { + return lhs.compare(rhs) != 0; +} +inline bool operator<(const OID& lhs, const OID& rhs) { + return lhs.compare(rhs) < 0; +} +inline bool operator<=(const OID& lhs, const OID& rhs) { + return lhs.compare(rhs) <= 0; +} } // namespace mongo diff --git a/src/mongo/bson/oid_test.cpp b/src/mongo/bson/oid_test.cpp index 437acad54d2..7a0e6d6773d 100644 --- a/src/mongo/bson/oid_test.cpp +++ b/src/mongo/bson/oid_test.cpp @@ -32,116 +32,121 @@ namespace { - using mongo::OID; +using mongo::OID; - TEST(Equals, Simple) { - OID o1 = OID::gen(); +TEST(Equals, Simple) { + OID o1 = OID::gen(); - ASSERT_EQUALS(o1, o1); - ASSERT_TRUE(o1 == o1); - ASSERT_EQUALS(o1.compare(o1), 0); - } - - TEST(NotEquals, Simple) { - OID o1 = OID::gen(); - OID o2 = OID::gen(); - - ASSERT_FALSE(o1 == o2); - ASSERT_TRUE(o1 != o2); - ASSERT_NOT_EQUALS(o1.compare(o2), 0); - } - - TEST(Increasing, Simple) { - OID o1 = OID::gen(); - OID o2 = OID::gen(); - ASSERT_TRUE(o1 < o2); - } - - TEST(IsSet, Simple) { - OID o; - ASSERT_FALSE(o.isSet()); - o.init(); - ASSERT_TRUE(o.isSet()); - } - - TEST(JustForked, Simple) { - OID o1 = OID::gen(); - OID::justForked(); - OID o2 = OID::gen(); + ASSERT_EQUALS(o1, o1); + ASSERT_TRUE(o1 == o1); + ASSERT_EQUALS(o1.compare(o1), 0); +} - ASSERT_TRUE(std::memcmp(o1.getInstanceUnique().bytes, o2.getInstanceUnique().bytes, - OID::kInstanceUniqueSize) != 0); - } +TEST(NotEquals, Simple) { + OID o1 = OID::gen(); + OID o2 = OID::gen(); - TEST(TimestampIsBigEndian, Endianness) { - OID o1; // zeroed - OID::Timestamp ts = 123; - o1.setTimestamp(ts); + ASSERT_FALSE(o1 == o2); + ASSERT_TRUE(o1 != o2); + ASSERT_NOT_EQUALS(o1.compare(o2), 0); +} - int32_t ts_big = mongo::endian::nativeToBig<int32_t>(123); +TEST(Increasing, Simple) { + OID o1 = OID::gen(); + OID o2 = OID::gen(); + ASSERT_TRUE(o1 < o2); +} - const char* oidBytes = o1.view().view(); - ASSERT(std::memcmp(&ts_big, oidBytes, sizeof(int32_t)) == 0); - } +TEST(IsSet, Simple) { + OID o; + ASSERT_FALSE(o.isSet()); + o.init(); + ASSERT_TRUE(o.isSet()); +} - TEST(IncrementIsBigEndian, Endianness) { - OID o1; // zeroed - OID::Increment incr; - // Increment is a 3 byte counter big endian - incr.bytes[0] = 0xBEu; - incr.bytes[1] = 0xADu; - incr.bytes[2] = 0xDEu; +TEST(JustForked, Simple) { + OID o1 = OID::gen(); + OID::justForked(); + OID o2 = OID::gen(); - o1.setIncrement(incr); + ASSERT_TRUE(std::memcmp(o1.getInstanceUnique().bytes, + o2.getInstanceUnique().bytes, + OID::kInstanceUniqueSize) != 0); +} - const char* oidBytes = o1.view().view(); - oidBytes += OID::kTimestampSize + OID::kInstanceUniqueSize; +TEST(TimestampIsBigEndian, Endianness) { + OID o1; // zeroed + OID::Timestamp ts = 123; + o1.setTimestamp(ts); - // now at start of increment - ASSERT_EQUALS(uint8_t(oidBytes[0]), 0xBEu); - ASSERT_EQUALS(uint8_t(oidBytes[1]), 0xADu); - ASSERT_EQUALS(uint8_t(oidBytes[2]), 0xDEu); - } + int32_t ts_big = mongo::endian::nativeToBig<int32_t>(123); - TEST(Basic, Deserialize) { + const char* oidBytes = o1.view().view(); + ASSERT(std::memcmp(&ts_big, oidBytes, sizeof(int32_t)) == 0); +} - uint8_t OIDbytes[] = { - 0xDEu, 0xADu, 0xBEu, 0xEFu, // timestamp is -559038737 (signed) - 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, // unique is 0 - 0x11u, 0x22u, 0x33u // increment is 1122867 - }; +TEST(IncrementIsBigEndian, Endianness) { + OID o1; // zeroed + OID::Increment incr; + // Increment is a 3 byte counter big endian + incr.bytes[0] = 0xBEu; + incr.bytes[1] = 0xADu; + incr.bytes[2] = 0xDEu; - OID o1 = OID::from(OIDbytes); + o1.setIncrement(incr); - ASSERT_EQUALS(o1.getTimestamp(), -559038737); - OID::InstanceUnique u = o1.getInstanceUnique(); - for (std::size_t i = 0; i < OID::kInstanceUniqueSize; ++i) { - ASSERT_EQUALS(u.bytes[i], 0x00u); - } - OID::Increment i = o1.getIncrement(); + const char* oidBytes = o1.view().view(); + oidBytes += OID::kTimestampSize + OID::kInstanceUniqueSize; - // construct a uint32_t from increment - // recall that i is a big-endian 3 byte unsigned integer - uint32_t incr = - ((uint32_t(i.bytes[0]) << 16)) | - ((uint32_t(i.bytes[1]) << 8)) | - uint32_t(i.bytes[2]); + // now at start of increment + ASSERT_EQUALS(uint8_t(oidBytes[0]), 0xBEu); + ASSERT_EQUALS(uint8_t(oidBytes[1]), 0xADu); + ASSERT_EQUALS(uint8_t(oidBytes[2]), 0xDEu); +} - ASSERT_EQUALS(1122867u, incr); +TEST(Basic, Deserialize) { + uint8_t OIDbytes[] = { + 0xDEu, + 0xADu, + 0xBEu, + 0xEFu, // timestamp is -559038737 (signed) + 0x00u, + 0x00u, + 0x00u, + 0x00u, + 0x00u, // unique is 0 + 0x11u, + 0x22u, + 0x33u // increment is 1122867 + }; + + OID o1 = OID::from(OIDbytes); + + ASSERT_EQUALS(o1.getTimestamp(), -559038737); + OID::InstanceUnique u = o1.getInstanceUnique(); + for (std::size_t i = 0; i < OID::kInstanceUniqueSize; ++i) { + ASSERT_EQUALS(u.bytes[i], 0x00u); } + OID::Increment i = o1.getIncrement(); - TEST(Basic, FromString) { + // construct a uint32_t from increment + // recall that i is a big-endian 3 byte unsigned integer + uint32_t incr = + ((uint32_t(i.bytes[0]) << 16)) | ((uint32_t(i.bytes[1]) << 8)) | uint32_t(i.bytes[2]); - std::string oidStr("541b1a00e8a23afa832b218e"); - uint8_t oidBytes[] = {0x54u, 0x1Bu, 0x1Au, 0x00u, - 0xE8u, 0xA2u, 0x3Au, 0xFAu, - 0x83u, 0x2Bu, 0x21u, 0x8Eu}; + ASSERT_EQUALS(1122867u, incr); +} - ASSERT_EQUALS(OID(oidStr), OID::from(oidBytes)); - } +TEST(Basic, FromString) { + std::string oidStr("541b1a00e8a23afa832b218e"); + uint8_t oidBytes[] = { + 0x54u, 0x1Bu, 0x1Au, 0x00u, 0xE8u, 0xA2u, 0x3Au, 0xFAu, 0x83u, 0x2Bu, 0x21u, 0x8Eu}; - TEST(Basic, FromStringToString) { - std::string fromStr("541b1a00e8a23afa832b218e"); - ASSERT_EQUALS(OID(fromStr).toString(), fromStr); - } + ASSERT_EQUALS(OID(oidStr), OID::from(oidBytes)); +} + +TEST(Basic, FromStringToString) { + std::string fromStr("541b1a00e8a23afa832b218e"); + ASSERT_EQUALS(OID(fromStr).toString(), fromStr); +} } diff --git a/src/mongo/bson/ordering.h b/src/mongo/bson/ordering.h index cb21dfb22d7..d6e26e0aadc 100644 --- a/src/mongo/bson/ordering.h +++ b/src/mongo/bson/ordering.h @@ -33,55 +33,57 @@ namespace mongo { - // todo: ideally move to db/ instead of bson/, but elim any dependencies first +// todo: ideally move to db/ instead of bson/, but elim any dependencies first - /** A precomputation of a BSON index or sort key pattern. That is something like: - { a : 1, b : -1 } - The constructor is private to make conversion more explicit so we notice where we call make(). - Over time we should push this up higher and higher. - */ - class Ordering { - unsigned bits; - Ordering(unsigned b) : bits(b) { } - public: - Ordering(const Ordering& r) : bits(r.bits) { } - void operator=(const Ordering& r) { - bits = r.bits; - } +/** A precomputation of a BSON index or sort key pattern. That is something like: + { a : 1, b : -1 } + The constructor is private to make conversion more explicit so we notice where we call make(). + Over time we should push this up higher and higher. +*/ +class Ordering { + unsigned bits; + Ordering(unsigned b) : bits(b) {} - /** so, for key pattern { a : 1, b : -1 } - get(0) == 1 - get(1) == -1 - */ - int get(int i) const { - return ((1 << i) & bits) ? -1 : 1; - } +public: + Ordering(const Ordering& r) : bits(r.bits) {} + void operator=(const Ordering& r) { + bits = r.bits; + } - // for woCompare... - unsigned descending(unsigned mask) const { return bits & mask; } + /** so, for key pattern { a : 1, b : -1 } + get(0) == 1 + get(1) == -1 + */ + int get(int i) const { + return ((1 << i) & bits) ? -1 : 1; + } - /*operator std::string() const { - StringBuilder buf; - for ( unsigned i=0; i<nkeys; i++) - buf.append( get(i) > 0 ? "+" : "-" ); - return buf.str(); - }*/ + // for woCompare... + unsigned descending(unsigned mask) const { + return bits & mask; + } - static Ordering make(const BSONObj& obj) { - unsigned b = 0; - BSONObjIterator k(obj); - unsigned n = 0; - while( 1 ) { - BSONElement e = k.next(); - if( e.eoo() ) - break; - uassert( 13103, "too many compound keys", n <= 31 ); - if( e.number() < 0 ) - b |= (1 << n); - n++; - } - return Ordering(b); - } - }; + /*operator std::string() const { + StringBuilder buf; + for ( unsigned i=0; i<nkeys; i++) + buf.append( get(i) > 0 ? "+" : "-" ); + return buf.str(); + }*/ + static Ordering make(const BSONObj& obj) { + unsigned b = 0; + BSONObjIterator k(obj); + unsigned n = 0; + while (1) { + BSONElement e = k.next(); + if (e.eoo()) + break; + uassert(13103, "too many compound keys", n <= 31); + if (e.number() < 0) + b |= (1 << n); + n++; + } + return Ordering(b); + } +}; } diff --git a/src/mongo/bson/timestamp.cpp b/src/mongo/bson/timestamp.cpp index cfd43fe8fe2..68d63d63e3f 100644 --- a/src/mongo/bson/timestamp.cpp +++ b/src/mongo/bson/timestamp.cpp @@ -38,37 +38,36 @@ namespace mongo { - Timestamp Timestamp::max() { - unsigned int t = static_cast<unsigned int>(std::numeric_limits<int32_t>::max()); - unsigned int i = std::numeric_limits<uint32_t>::max(); - return Timestamp(t, i); - } - - void Timestamp::append(BufBuilder& builder, const StringData& fieldName) const { - // No endian conversions needed, since we store in-memory representation - // in little endian format, regardless of target endian. - builder.appendNum( static_cast<char>(bsonTimestamp) ); - builder.appendStr( fieldName ); - builder.appendNum( asULL() ); - } +Timestamp Timestamp::max() { + unsigned int t = static_cast<unsigned int>(std::numeric_limits<int32_t>::max()); + unsigned int i = std::numeric_limits<uint32_t>::max(); + return Timestamp(t, i); +} - std::string Timestamp::toStringLong() const { - std::stringstream ss; - ss << time_t_to_String_short(secs) << ' '; - ss << std::hex << secs << ':' << i; - return ss.str(); - } +void Timestamp::append(BufBuilder& builder, const StringData& fieldName) const { + // No endian conversions needed, since we store in-memory representation + // in little endian format, regardless of target endian. + builder.appendNum(static_cast<char>(bsonTimestamp)); + builder.appendStr(fieldName); + builder.appendNum(asULL()); +} - std::string Timestamp::toStringPretty() const { - std::stringstream ss; - ss << time_t_to_String_short(secs) << ':' << std::hex << i; - return ss.str(); - } +std::string Timestamp::toStringLong() const { + std::stringstream ss; + ss << time_t_to_String_short(secs) << ' '; + ss << std::hex << secs << ':' << i; + return ss.str(); +} - std::string Timestamp::toString() const { - std::stringstream ss; - ss << std::hex << secs << ':' << i; - return ss.str(); - } +std::string Timestamp::toStringPretty() const { + std::stringstream ss; + ss << time_t_to_String_short(secs) << ':' << std::hex << i; + return ss.str(); +} +std::string Timestamp::toString() const { + std::stringstream ss; + ss << std::hex << secs << ':' << i; + return ss.str(); +} } diff --git a/src/mongo/bson/timestamp.h b/src/mongo/bson/timestamp.h index 7ec80bb8488..47e2bf936e7 100644 --- a/src/mongo/bson/timestamp.h +++ b/src/mongo/bson/timestamp.h @@ -34,90 +34,94 @@ namespace mongo { +/** + * Timestamp: A combination of a count of seconds since the POSIX epoch plus an ordinal value. + */ +class Timestamp { +public: + // Maximum Timestamp value. + static Timestamp max(); + + /** + * DEPRECATED Constructor that builds a Timestamp from a Date_t by using the + * high-order 4 bytes of "date" for the "secs" field and the low-order 4 bytes + * for the "i" field. + */ + explicit Timestamp(Date_t date) : Timestamp(date.toULL()) {} + /** - * Timestamp: A combination of a count of seconds since the POSIX epoch plus an ordinal value. + * DEPRECATED Constructor that builds a Timestamp from a 64-bit unsigned integer by using + * the high-order 4 bytes of "v" for the "secs" field and the low-order 4 bytes for the "i" + * field. */ - class Timestamp { - public: - // Maximum Timestamp value. - static Timestamp max(); - - /** - * DEPRECATED Constructor that builds a Timestamp from a Date_t by using the - * high-order 4 bytes of "date" for the "secs" field and the low-order 4 bytes - * for the "i" field. - */ - explicit Timestamp(Date_t date) : Timestamp(date.toULL()) {} - - /** - * DEPRECATED Constructor that builds a Timestamp from a 64-bit unsigned integer by using - * the high-order 4 bytes of "v" for the "secs" field and the low-order 4 bytes for the "i" - * field. - */ - explicit Timestamp(unsigned long long v) : Timestamp(v >> 32, v) {} - - Timestamp(Seconds s, unsigned increment) : Timestamp(s.count(), increment) {} - - Timestamp(unsigned a, unsigned b) : i(b), secs(a) { - dassert(secs <= static_cast<unsigned>(std::numeric_limits<int>::max())); - } - - Timestamp() = default; - - unsigned getSecs() const { - return secs; - } - - unsigned getInc() const { - return i; - } - - unsigned long long asULL() const { - unsigned long long result = secs; - result <<= 32; - result |= i; - return result; - } - long long asLL() const { - return static_cast<long long>(asULL()); - } - - bool isNull() const { return secs == 0; } - - std::string toStringLong() const; - - std::string toStringPretty() const; - - std::string toString() const; - - bool operator==(const Timestamp& r) const { - return tie() == r.tie(); - } - bool operator!=(const Timestamp& r) const { - return tie() != r.tie(); - } - bool operator<(const Timestamp& r) const { - return tie() < r.tie(); - } - bool operator<=(const Timestamp& r) const { - return tie() <= r.tie(); - } - bool operator>(const Timestamp& r) const { - return tie() > r.tie(); - } - bool operator>=(const Timestamp& r) const { - return tie() >= r.tie(); - } - - // Append the BSON representation of this Timestamp to the given BufBuilder with the given - // name. This lives here because Timestamp manages its own serialization format. - void append(BufBuilder& builder, const StringData& fieldName) const; - - private: - std::tuple<unsigned, unsigned> tie() const { return std::tie(secs, i); } - - unsigned i = 0; - unsigned secs = 0; - }; - -} // namespace mongo + explicit Timestamp(unsigned long long v) : Timestamp(v >> 32, v) {} + + Timestamp(Seconds s, unsigned increment) : Timestamp(s.count(), increment) {} + + Timestamp(unsigned a, unsigned b) : i(b), secs(a) { + dassert(secs <= static_cast<unsigned>(std::numeric_limits<int>::max())); + } + + Timestamp() = default; + + unsigned getSecs() const { + return secs; + } + + unsigned getInc() const { + return i; + } + + unsigned long long asULL() const { + unsigned long long result = secs; + result <<= 32; + result |= i; + return result; + } + long long asLL() const { + return static_cast<long long>(asULL()); + } + + bool isNull() const { + return secs == 0; + } + + std::string toStringLong() const; + + std::string toStringPretty() const; + + std::string toString() const; + + bool operator==(const Timestamp& r) const { + return tie() == r.tie(); + } + bool operator!=(const Timestamp& r) const { + return tie() != r.tie(); + } + bool operator<(const Timestamp& r) const { + return tie() < r.tie(); + } + bool operator<=(const Timestamp& r) const { + return tie() <= r.tie(); + } + bool operator>(const Timestamp& r) const { + return tie() > r.tie(); + } + bool operator>=(const Timestamp& r) const { + return tie() >= r.tie(); + } + + // Append the BSON representation of this Timestamp to the given BufBuilder with the given + // name. This lives here because Timestamp manages its own serialization format. + void append(BufBuilder& builder, const StringData& fieldName) const; + +private: + std::tuple<unsigned, unsigned> tie() const { + return std::tie(secs, i); + } + + unsigned i = 0; + unsigned secs = 0; +}; + +} // namespace mongo diff --git a/src/mongo/bson/util/bson_check.h b/src/mongo/bson/util/bson_check.h index 203ee94ae8a..4f2585e9e75 100644 --- a/src/mongo/bson/util/bson_check.h +++ b/src/mongo/bson/util/bson_check.h @@ -34,53 +34,51 @@ namespace mongo { - /** - * Confirms that "o" only contains fields whose names are in "begin".."end", - * and that no field name occurs multiple times. - * - * On failure, returns BadValue and a message naming the unexpected field or DuplicateKey and a - * message naming the repeated field. "objectName" is included in the message, for reporting - * purposes. - */ - template <typename Iter> - Status bsonCheckOnlyHasFields(StringData objectName, - const BSONObj& o, - const Iter& begin, - const Iter& end) { - std::vector<int> occurrences(std::distance(begin, end), 0); - for (BSONObj::iterator iter(o); iter.more();) { - const BSONElement e = iter.next(); - const Iter found = std::find(begin, end, e.fieldNameStringData()); - if (found != end) { - ++occurrences[std::distance(begin, found)]; - } - else { - return Status(ErrorCodes::BadValue, - str::stream() << - "Unexpected field " << e.fieldName() << " in " << objectName); - } +/** + * Confirms that "o" only contains fields whose names are in "begin".."end", + * and that no field name occurs multiple times. + * + * On failure, returns BadValue and a message naming the unexpected field or DuplicateKey and a + * message naming the repeated field. "objectName" is included in the message, for reporting + * purposes. + */ +template <typename Iter> +Status bsonCheckOnlyHasFields(StringData objectName, + const BSONObj& o, + const Iter& begin, + const Iter& end) { + std::vector<int> occurrences(std::distance(begin, end), 0); + for (BSONObj::iterator iter(o); iter.more();) { + const BSONElement e = iter.next(); + const Iter found = std::find(begin, end, e.fieldNameStringData()); + if (found != end) { + ++occurrences[std::distance(begin, found)]; + } else { + return Status(ErrorCodes::BadValue, + str::stream() << "Unexpected field " << e.fieldName() << " in " + << objectName); } - int i = 0; - for(Iter curr = begin; curr != end; ++curr, ++i) { - if (occurrences[i] > 1) { - return Status(ErrorCodes::DuplicateKey, - str::stream() << - "Field " << *curr << " appears " << occurrences[i] << - " times in " << objectName); - } + } + int i = 0; + for (Iter curr = begin; curr != end; ++curr, ++i) { + if (occurrences[i] > 1) { + return Status(ErrorCodes::DuplicateKey, + str::stream() << "Field " << *curr << " appears " << occurrences[i] + << " times in " << objectName); } - return Status::OK(); } + return Status::OK(); +} - /** - * Same as above, but operates over an array of string-ish items, "legals", instead - * of "begin".."end". - */ - template <typename StringType, int N> - Status bsonCheckOnlyHasFields(StringData objectName, - const BSONObj& o, - const StringType (&legals)[N]) { - return bsonCheckOnlyHasFields(objectName, o, &legals[0], legals + N); - } +/** + * Same as above, but operates over an array of string-ish items, "legals", instead + * of "begin".."end". + */ +template <typename StringType, int N> +Status bsonCheckOnlyHasFields(StringData objectName, + const BSONObj& o, + const StringType(&legals)[N]) { + return bsonCheckOnlyHasFields(objectName, o, &legals[0], legals + N); +} } // namespace mongo diff --git a/src/mongo/bson/util/bson_check_test.cpp b/src/mongo/bson/util/bson_check_test.cpp index 6df4962d7db..f14da7fa459 100644 --- a/src/mongo/bson/util/bson_check_test.cpp +++ b/src/mongo/bson/util/bson_check_test.cpp @@ -33,48 +33,43 @@ namespace mongo { namespace { - TEST(BsonCheck, CheckNothingLegal) { - const char *const *nada = NULL; - ASSERT_OK(bsonCheckOnlyHasFields("", BSONObj(), nada, nada)); - ASSERT_EQUALS(ErrorCodes::BadValue, - bsonCheckOnlyHasFields("", BSON("a" << 1), nada, nada)); - } +TEST(BsonCheck, CheckNothingLegal) { + const char* const* nada = NULL; + ASSERT_OK(bsonCheckOnlyHasFields("", BSONObj(), nada, nada)); + ASSERT_EQUALS(ErrorCodes::BadValue, bsonCheckOnlyHasFields("", BSON("a" << 1), nada, nada)); +} - const char *const legals[] = { - "aField", - "anotherField", - "thirdField" - }; +const char* const legals[] = {"aField", "anotherField", "thirdField"}; - TEST(BsonCheck, CheckHasOnlyOnEmptyObject){ - ASSERT_OK(bsonCheckOnlyHasFields( - "", - BSONObj(), - legals)); - } +TEST(BsonCheck, CheckHasOnlyOnEmptyObject) { + ASSERT_OK(bsonCheckOnlyHasFields("", BSONObj(), legals)); +} - TEST(BsonCheck, CheckHasOnlyLegalFields) { - ASSERT_OK(bsonCheckOnlyHasFields( - "", - BSON("aField" << "value" << "thirdField" << 1 << "anotherField" << 2), - legals)); - ASSERT_OK(bsonCheckOnlyHasFields( - "", - BSON("aField" << "value" << "thirdField" << 1), - legals)); +TEST(BsonCheck, CheckHasOnlyLegalFields) { + ASSERT_OK(bsonCheckOnlyHasFields("", + BSON("aField" + << "value" + << "thirdField" << 1 << "anotherField" << 2), + legals)); + ASSERT_OK(bsonCheckOnlyHasFields("", + BSON("aField" + << "value" + << "thirdField" << 1), + legals)); - ASSERT_EQUALS(ErrorCodes::BadValue, bsonCheckOnlyHasFields( - "", - BSON("aField" << "value" << "illegal" << 4 << "thirdField" << 1), - legals)); - } + ASSERT_EQUALS(ErrorCodes::BadValue, + bsonCheckOnlyHasFields("", + BSON("aField" + << "value" + << "illegal" << 4 << "thirdField" << 1), + legals)); +} - TEST(BsonCheck, CheckNoDuplicates) { - ASSERT_EQUALS(ErrorCodes::DuplicateKey, bsonCheckOnlyHasFields( - "", - BSON("aField" << 1 << "anotherField" << 2 << "aField" << 3), - legals)); - } +TEST(BsonCheck, CheckNoDuplicates) { + ASSERT_EQUALS(ErrorCodes::DuplicateKey, + bsonCheckOnlyHasFields( + "", BSON("aField" << 1 << "anotherField" << 2 << "aField" << 3), legals)); +} } // namespace } // namespace mongo diff --git a/src/mongo/bson/util/bson_extract.cpp b/src/mongo/bson/util/bson_extract.cpp index ef8f5110568..d5b5b83e6da 100644 --- a/src/mongo/bson/util/bson_extract.cpp +++ b/src/mongo/bson/util/bson_extract.cpp @@ -32,163 +32,149 @@ namespace mongo { - Status bsonExtractField(const BSONObj& object, - StringData fieldName, - BSONElement* outElement) { - BSONElement element = object.getField(fieldName); - if (element.eoo()) - return Status(ErrorCodes::NoSuchKey, - mongoutils::str::stream() << "Missing expected field \"" << - fieldName.toString() << "\""); - *outElement = element; - return Status::OK(); - } +Status bsonExtractField(const BSONObj& object, StringData fieldName, BSONElement* outElement) { + BSONElement element = object.getField(fieldName); + if (element.eoo()) + return Status(ErrorCodes::NoSuchKey, + mongoutils::str::stream() << "Missing expected field \"" + << fieldName.toString() << "\""); + *outElement = element; + return Status::OK(); +} - Status bsonExtractTypedField(const BSONObj& object, - StringData fieldName, - BSONType type, - BSONElement* outElement) { - Status status = bsonExtractField(object, fieldName, outElement); - if (!status.isOK()) - return status; - if (type != outElement->type()) { - return Status(ErrorCodes::TypeMismatch, - mongoutils::str::stream() << "\"" << fieldName << - "\" had the wrong type. Expected " << typeName(type) << - ", found " << typeName(outElement->type())); - } - return Status::OK(); +Status bsonExtractTypedField(const BSONObj& object, + StringData fieldName, + BSONType type, + BSONElement* outElement) { + Status status = bsonExtractField(object, fieldName, outElement); + if (!status.isOK()) + return status; + if (type != outElement->type()) { + return Status(ErrorCodes::TypeMismatch, + mongoutils::str::stream() + << "\"" << fieldName << "\" had the wrong type. Expected " + << typeName(type) << ", found " << typeName(outElement->type())); } + return Status::OK(); +} - Status bsonExtractBooleanField(const BSONObj& object, - StringData fieldName, - bool* out) { - BSONElement element; - Status status = bsonExtractTypedField(object, fieldName, Bool, &element); - if (!status.isOK()) - return status; - *out = element.boolean(); +Status bsonExtractBooleanField(const BSONObj& object, StringData fieldName, bool* out) { + BSONElement element; + Status status = bsonExtractTypedField(object, fieldName, Bool, &element); + if (!status.isOK()) + return status; + *out = element.boolean(); + return Status::OK(); +} + +Status bsonExtractBooleanFieldWithDefault(const BSONObj& object, + StringData fieldName, + bool defaultValue, + bool* out) { + BSONElement value; + Status status = bsonExtractField(object, fieldName, &value); + if (status == ErrorCodes::NoSuchKey) { + *out = defaultValue; + return Status::OK(); + } else if (!status.isOK()) { + return status; + } else if (!value.isNumber() && !value.isBoolean()) { + return Status(ErrorCodes::TypeMismatch, + mongoutils::str::stream() << "Expected boolean or number type for field \"" + << fieldName << "\", found " + << typeName(value.type())); + } else { + *out = value.trueValue(); return Status::OK(); } +} - Status bsonExtractBooleanFieldWithDefault(const BSONObj& object, - StringData fieldName, - bool defaultValue, - bool* out) { - BSONElement value; - Status status = bsonExtractField(object, fieldName, &value); - if (status == ErrorCodes::NoSuchKey) { - *out = defaultValue; - return Status::OK(); - } - else if (!status.isOK()) { - return status; - } - else if (!value.isNumber() && !value.isBoolean()) { - return Status(ErrorCodes::TypeMismatch, mongoutils::str::stream() << - "Expected boolean or number type for field \"" << fieldName << - "\", found " << typeName(value.type())); - } - else { - *out = value.trueValue(); - return Status::OK(); - } - } +Status bsonExtractStringField(const BSONObj& object, StringData fieldName, std::string* out) { + BSONElement element; + Status status = bsonExtractTypedField(object, fieldName, String, &element); + if (!status.isOK()) + return status; + *out = element.str(); + return Status::OK(); +} - Status bsonExtractStringField(const BSONObj& object, - StringData fieldName, - std::string* out) { - BSONElement element; - Status status = bsonExtractTypedField(object, fieldName, String, &element); - if (!status.isOK()) - return status; - *out = element.str(); - return Status::OK(); - } +Status bsonExtractTimestampField(const BSONObj& object, StringData fieldName, Timestamp* out) { + BSONElement element; + Status status = bsonExtractTypedField(object, fieldName, bsonTimestamp, &element); + if (!status.isOK()) + return status; + *out = element.timestamp(); + return Status::OK(); +} - Status bsonExtractTimestampField(const BSONObj& object, - StringData fieldName, - Timestamp* out) { - BSONElement element; - Status status = bsonExtractTypedField(object, fieldName, bsonTimestamp, &element); - if (!status.isOK()) - return status; - *out = element.timestamp(); - return Status::OK(); - } +Status bsonExtractOIDField(const BSONObj& object, StringData fieldName, OID* out) { + BSONElement element; + Status status = bsonExtractTypedField(object, fieldName, jstOID, &element); + if (!status.isOK()) + return status; + *out = element.OID(); + return Status::OK(); +} - Status bsonExtractOIDField(const BSONObj& object, - StringData fieldName, - OID* out) { - BSONElement element; - Status status = bsonExtractTypedField(object, fieldName, jstOID, &element); - if (!status.isOK()) - return status; - *out = element.OID(); - return Status::OK(); +Status bsonExtractOIDFieldWithDefault(const BSONObj& object, + StringData fieldName, + const OID& defaultValue, + OID* out) { + Status status = bsonExtractOIDField(object, fieldName, out); + if (status == ErrorCodes::NoSuchKey) { + *out = defaultValue; + } else if (!status.isOK()) { + return status; } + return Status::OK(); +} - Status bsonExtractOIDFieldWithDefault(const BSONObj& object, - StringData fieldName, - const OID& defaultValue, - OID* out) { - Status status = bsonExtractOIDField(object, fieldName, out); - if (status == ErrorCodes::NoSuchKey) { - *out = defaultValue; - } - else if (!status.isOK()) { - return status; - } - return Status::OK(); +Status bsonExtractStringFieldWithDefault(const BSONObj& object, + StringData fieldName, + StringData defaultValue, + std::string* out) { + Status status = bsonExtractStringField(object, fieldName, out); + if (status == ErrorCodes::NoSuchKey) { + *out = defaultValue.toString(); + } else if (!status.isOK()) { + return status; } + return Status::OK(); +} - Status bsonExtractStringFieldWithDefault(const BSONObj& object, - StringData fieldName, - StringData defaultValue, - std::string* out) { - Status status = bsonExtractStringField(object, fieldName, out); - if (status == ErrorCodes::NoSuchKey) { - *out = defaultValue.toString(); - } - else if (!status.isOK()) { - return status; - } - return Status::OK(); +Status bsonExtractIntegerField(const BSONObj& object, StringData fieldName, long long* out) { + BSONElement value; + Status status = bsonExtractField(object, fieldName, &value); + if (!status.isOK()) + return status; + if (!value.isNumber()) { + return Status(ErrorCodes::TypeMismatch, + mongoutils::str::stream() << "Expected field \"" << fieldName + << "\" to have numeric type, but found " + << typeName(value.type())); } - - Status bsonExtractIntegerField(const BSONObj& object, - StringData fieldName, - long long* out) { - BSONElement value; - Status status = bsonExtractField(object, fieldName, &value); - if (!status.isOK()) - return status; - if (!value.isNumber()) { - return Status(ErrorCodes::TypeMismatch, mongoutils::str::stream() << - "Expected field \"" << fieldName << - "\" to have numeric type, but found " << typeName(value.type())); - } - long long result = value.safeNumberLong(); - if (result != value.numberDouble()) { - return Status(ErrorCodes::BadValue, mongoutils::str::stream() << - "Expected field \"" << fieldName << "\" to have a value " - "exactly representable as a 64-bit integer, but found " << - value); - } - *out = result; - return Status::OK(); + long long result = value.safeNumberLong(); + if (result != value.numberDouble()) { + return Status(ErrorCodes::BadValue, + mongoutils::str::stream() + << "Expected field \"" << fieldName + << "\" to have a value " + "exactly representable as a 64-bit integer, but found " << value); } + *out = result; + return Status::OK(); +} - Status bsonExtractIntegerFieldWithDefault(const BSONObj& object, - StringData fieldName, - long long defaultValue, - long long* out) { - Status status = bsonExtractIntegerField(object, fieldName, out); - if (status == ErrorCodes::NoSuchKey) { - *out = defaultValue; - status = Status::OK(); - } - return status; +Status bsonExtractIntegerFieldWithDefault(const BSONObj& object, + StringData fieldName, + long long defaultValue, + long long* out) { + Status status = bsonExtractIntegerField(object, fieldName, out); + if (status == ErrorCodes::NoSuchKey) { + *out = defaultValue; + status = Status::OK(); } + return status; +} } // namespace mongo diff --git a/src/mongo/bson/util/bson_extract.h b/src/mongo/bson/util/bson_extract.h index 4558e18b22d..fa7e3bbc9ab 100644 --- a/src/mongo/bson/util/bson_extract.h +++ b/src/mongo/bson/util/bson_extract.h @@ -36,152 +36,140 @@ namespace mongo { - class BSONObj; - class BSONElement; - class OID; - class Timestamp; - - /** - * Finds an element named "fieldName" in "object". - * - * Returns Status::OK() and sets "*outElement" to the found element on success. Returns - * ErrorCodes::NoSuchKey if there are no matches. - */ - Status bsonExtractField(const BSONObj& object, - StringData fieldName, - BSONElement* outElement); - - /** - * Finds an element named "fieldName" in "object". - * - * Returns Status::OK() and sets *outElement to the found element on success. Returns - * ErrorCodes::NoSuchKey if there are no matches for "fieldName", and ErrorCodes::TypeMismatch - * if the type of the matching element is not "type". For return values other than - * Status::OK(), the resulting value of "*outElement" is undefined. - */ - Status bsonExtractTypedField(const BSONObj& object, - StringData fieldName, - BSONType type, - BSONElement* outElement); - - /** - * Finds a bool-like element named "fieldName" in "object". - * - * Returns Status::OK() and sets *out to the found element's boolean value on success. Returns - * ErrorCodes::NoSuchKey if there are no matches for "fieldName", and ErrorCodes::TypeMismatch - * if the type of the matching element is not Bool or a number type. For return values other - * than Status::OK(), the resulting value of "*out" is undefined. - */ - Status bsonExtractBooleanField(const BSONObj& object, - StringData fieldName, - bool* out); - - /** - * Finds an element named "fieldName" in "object" that represents an integral value. - * - * Returns Status::OK() and sets *out to the element's 64-bit integer value representation on - * success. Returns ErrorCodes::NoSuchKey if there are no matches for "fieldName". Returns - * ErrorCodes::TypeMismatch if the value of the matching element is not of a numeric type. - * Returns ErrorCodes::BadValue if the value does not have an exact 64-bit integer - * representation. For return values other than Status::OK(), the resulting value of "*out" is - * undefined. - */ - Status bsonExtractIntegerField(const BSONObj& object, - StringData fieldName, - long long* out); - - /** - * Finds a string-typed element named "fieldName" in "object" and stores its value in "out". - * - * Returns Status::OK() and sets *out to the found element's std::string value on success. Returns - * ErrorCodes::NoSuchKey if there are no matches for "fieldName", and ErrorCodes::TypeMismatch - * if the type of the matching element is not String. For return values other than - * Status::OK(), the resulting value of "*out" is undefined. - */ - Status bsonExtractStringField(const BSONObj& object, - StringData fieldName, - std::string* out); - - /** - * Finds an Timestamp-typed element named "fieldName" in "object" and stores its value in "out". - * - * Returns Status::OK() and sets *out to the found element's Timestamp value on success. Returns - * ErrorCodes::NoSuchKey if there are no matches for "fieldName", and ErrorCodes::TypeMismatch - * if the type of the matching element is not Timestamp. For return values other than - * Status::OK(), the resulting value of "*out" is undefined. - */ - Status bsonExtractTimestampField(const BSONObj& object, - StringData fieldName, - Timestamp* out); - - /** - * Finds an OID-typed element named "fieldName" in "object" and stores its value in "out". - * - * Returns Status::OK() and sets *out to the found element's OID value on success. Returns - * ErrorCodes::NoSuchKey if there are no matches for "fieldName", and ErrorCodes::TypeMismatch - * if the type of the matching element is not OID. For return values other than Status::OK(), - * the resulting value of "*out" is undefined. - */ - Status bsonExtractOIDField(const BSONObj& object, - StringData fieldName, - OID* out); - - /** - * Finds a bool-like element named "fieldName" in "object". - * - * If a field named "fieldName" is present, and is either a number or boolean type, stores the - * truth value of the field into "*out". If no field named "fieldName" is present, sets "*out" - * to "defaultValue". In these cases, returns Status::OK(). - * - * If "fieldName" is present more than once, behavior is undefined. If the found field is not a - * boolean or number, returns ErrorCodes::TypeMismatch. - */ - Status bsonExtractBooleanFieldWithDefault(const BSONObj& object, - StringData fieldName, - bool defaultValue, - bool* out); - - /** - * Finds an element named "fieldName" in "object" that represents an integral value. - * - * If a field named "fieldName" is present and is a value of numeric type with an exact 64-bit - * integer representation, returns that representation in *out and returns Status::OK(). If - * there is no field named "fieldName", stores defaultValue into *out and returns Status::OK(). - * If the field is found, but has non-numeric type, returns ErrorCodes::TypeMismatch. If the - * value has numeric type, but cannot be represented as a 64-bit integer, returns - * ErrorCodes::BadValue. - */ - Status bsonExtractIntegerFieldWithDefault(const BSONObj& object, - StringData fieldName, - long long defaultValue, - long long* out); - - /** - * Finds a std::string element named "fieldName" in "object". - * - * If a field named "fieldName" is present, and is a string, stores the value of the field into - * "*out". If no field named fieldName is present, sets "*out" to "defaultValue". In these - * cases, returns Status::OK(). - * - * If "fieldName" is present more than once, behavior is undefined. If the found field is not a - * string, returns ErrorCodes::TypeMismatch. - */ - Status bsonExtractStringFieldWithDefault(const BSONObj& object, - StringData fieldName, - StringData defaultValue, - std::string* out); - - /** - * Finds an OID-typed element named "fieldName" in "object" and stores its value in *out. - * - * Returns Status::OK() and sets *out to the found element's OID value on success. If no field - * named "fieldName" is present, *out is set to "defaultValue" and Status::OK() is returned. - * Returns ErrorCodes::TypeMismatch if the type of the matching element is not OID. For return - * values other than Status::OK(), the resulting value of *out is undefined. - */ - Status bsonExtractOIDFieldWithDefault(const BSONObj& object, +class BSONObj; +class BSONElement; +class OID; +class Timestamp; + +/** + * Finds an element named "fieldName" in "object". + * + * Returns Status::OK() and sets "*outElement" to the found element on success. Returns + * ErrorCodes::NoSuchKey if there are no matches. + */ +Status bsonExtractField(const BSONObj& object, StringData fieldName, BSONElement* outElement); + +/** + * Finds an element named "fieldName" in "object". + * + * Returns Status::OK() and sets *outElement to the found element on success. Returns + * ErrorCodes::NoSuchKey if there are no matches for "fieldName", and ErrorCodes::TypeMismatch + * if the type of the matching element is not "type". For return values other than + * Status::OK(), the resulting value of "*outElement" is undefined. + */ +Status bsonExtractTypedField(const BSONObj& object, + StringData fieldName, + BSONType type, + BSONElement* outElement); + +/** + * Finds a bool-like element named "fieldName" in "object". + * + * Returns Status::OK() and sets *out to the found element's boolean value on success. Returns + * ErrorCodes::NoSuchKey if there are no matches for "fieldName", and ErrorCodes::TypeMismatch + * if the type of the matching element is not Bool or a number type. For return values other + * than Status::OK(), the resulting value of "*out" is undefined. + */ +Status bsonExtractBooleanField(const BSONObj& object, StringData fieldName, bool* out); + +/** + * Finds an element named "fieldName" in "object" that represents an integral value. + * + * Returns Status::OK() and sets *out to the element's 64-bit integer value representation on + * success. Returns ErrorCodes::NoSuchKey if there are no matches for "fieldName". Returns + * ErrorCodes::TypeMismatch if the value of the matching element is not of a numeric type. + * Returns ErrorCodes::BadValue if the value does not have an exact 64-bit integer + * representation. For return values other than Status::OK(), the resulting value of "*out" is + * undefined. + */ +Status bsonExtractIntegerField(const BSONObj& object, StringData fieldName, long long* out); + +/** + * Finds a string-typed element named "fieldName" in "object" and stores its value in "out". + * + * Returns Status::OK() and sets *out to the found element's std::string value on success. Returns + * ErrorCodes::NoSuchKey if there are no matches for "fieldName", and ErrorCodes::TypeMismatch + * if the type of the matching element is not String. For return values other than + * Status::OK(), the resulting value of "*out" is undefined. + */ +Status bsonExtractStringField(const BSONObj& object, StringData fieldName, std::string* out); + +/** + * Finds an Timestamp-typed element named "fieldName" in "object" and stores its value in "out". + * + * Returns Status::OK() and sets *out to the found element's Timestamp value on success. Returns + * ErrorCodes::NoSuchKey if there are no matches for "fieldName", and ErrorCodes::TypeMismatch + * if the type of the matching element is not Timestamp. For return values other than + * Status::OK(), the resulting value of "*out" is undefined. + */ +Status bsonExtractTimestampField(const BSONObj& object, StringData fieldName, Timestamp* out); + +/** + * Finds an OID-typed element named "fieldName" in "object" and stores its value in "out". + * + * Returns Status::OK() and sets *out to the found element's OID value on success. Returns + * ErrorCodes::NoSuchKey if there are no matches for "fieldName", and ErrorCodes::TypeMismatch + * if the type of the matching element is not OID. For return values other than Status::OK(), + * the resulting value of "*out" is undefined. + */ +Status bsonExtractOIDField(const BSONObj& object, StringData fieldName, OID* out); + +/** + * Finds a bool-like element named "fieldName" in "object". + * + * If a field named "fieldName" is present, and is either a number or boolean type, stores the + * truth value of the field into "*out". If no field named "fieldName" is present, sets "*out" + * to "defaultValue". In these cases, returns Status::OK(). + * + * If "fieldName" is present more than once, behavior is undefined. If the found field is not a + * boolean or number, returns ErrorCodes::TypeMismatch. + */ +Status bsonExtractBooleanFieldWithDefault(const BSONObj& object, + StringData fieldName, + bool defaultValue, + bool* out); + +/** + * Finds an element named "fieldName" in "object" that represents an integral value. + * + * If a field named "fieldName" is present and is a value of numeric type with an exact 64-bit + * integer representation, returns that representation in *out and returns Status::OK(). If + * there is no field named "fieldName", stores defaultValue into *out and returns Status::OK(). + * If the field is found, but has non-numeric type, returns ErrorCodes::TypeMismatch. If the + * value has numeric type, but cannot be represented as a 64-bit integer, returns + * ErrorCodes::BadValue. + */ +Status bsonExtractIntegerFieldWithDefault(const BSONObj& object, StringData fieldName, - const OID& defaultValue, - OID* out); + long long defaultValue, + long long* out); + +/** + * Finds a std::string element named "fieldName" in "object". + * + * If a field named "fieldName" is present, and is a string, stores the value of the field into + * "*out". If no field named fieldName is present, sets "*out" to "defaultValue". In these + * cases, returns Status::OK(). + * + * If "fieldName" is present more than once, behavior is undefined. If the found field is not a + * string, returns ErrorCodes::TypeMismatch. + */ +Status bsonExtractStringFieldWithDefault(const BSONObj& object, + StringData fieldName, + StringData defaultValue, + std::string* out); + +/** + * Finds an OID-typed element named "fieldName" in "object" and stores its value in *out. + * + * Returns Status::OK() and sets *out to the found element's OID value on success. If no field + * named "fieldName" is present, *out is set to "defaultValue" and Status::OK() is returned. + * Returns ErrorCodes::TypeMismatch if the type of the matching element is not OID. For return + * values other than Status::OK(), the resulting value of *out is undefined. + */ +Status bsonExtractOIDFieldWithDefault(const BSONObj& object, + StringData fieldName, + const OID& defaultValue, + OID* out); } // namespace mongo diff --git a/src/mongo/bson/util/bson_extract_test.cpp b/src/mongo/bson/util/bson_extract_test.cpp index f65699326b4..dc4b97f8a8f 100644 --- a/src/mongo/bson/util/bson_extract_test.cpp +++ b/src/mongo/bson/util/bson_extract_test.cpp @@ -35,7 +35,8 @@ using namespace mongo; TEST(ExtractBSON, ExtractField) { - BSONObj obj = BSON("a" << 1 << "b" << "hello"); + BSONObj obj = BSON("a" << 1 << "b" + << "hello"); BSONElement element; ASSERT_OK(bsonExtractField(obj, "a", &element)); ASSERT_EQUALS(1, element.Int()); @@ -45,7 +46,8 @@ TEST(ExtractBSON, ExtractField) { } TEST(ExtractBSON, ExtractTypedField) { - BSONObj obj = BSON("a" << 1 << "b" << "hello"); + BSONObj obj = BSON("a" << 1 << "b" + << "hello"); BSONElement element; ASSERT_OK(bsonExtractTypedField(obj, "a", NumberInt, &element)); ASSERT_EQUALS(1, element.Int()); @@ -53,12 +55,14 @@ TEST(ExtractBSON, ExtractTypedField) { ASSERT_EQUALS(std::string("hello"), element.str()); ASSERT_EQUALS(ErrorCodes::NoSuchKey, bsonExtractTypedField(obj, "c", String, &element)); ASSERT_EQUALS(ErrorCodes::TypeMismatch, bsonExtractTypedField(obj, "a", String, &element)); - ASSERT_EQUALS(ErrorCodes::TypeMismatch, bsonExtractTypedField(obj, "b", NumberDouble, &element)); + ASSERT_EQUALS(ErrorCodes::TypeMismatch, + bsonExtractTypedField(obj, "b", NumberDouble, &element)); } TEST(ExtractBSON, ExtractStringField) { - BSONObj obj = BSON("a" << 1 << "b" << "hello"); + BSONObj obj = BSON("a" << 1 << "b" + << "hello"); std::string s; ASSERT_EQUALS(ErrorCodes::TypeMismatch, bsonExtractStringField(obj, "a", &s)); ASSERT_OK(bsonExtractStringField(obj, "b", &s)); @@ -67,7 +71,8 @@ TEST(ExtractBSON, ExtractStringField) { } TEST(ExtractBSON, ExtractStringFieldWithDefault) { - BSONObj obj = BSON("a" << 1 << "b" << "hello"); + BSONObj obj = BSON("a" << 1 << "b" + << "hello"); std::string s; ASSERT_EQUALS(ErrorCodes::TypeMismatch, bsonExtractStringFieldWithDefault(obj, "a", "default", &s)); @@ -79,8 +84,12 @@ TEST(ExtractBSON, ExtractStringFieldWithDefault) { } TEST(ExtractBSON, ExtractBooleanFieldWithDefault) { - BSONObj obj1 = BSON("a" << 1 << "b" << "hello" << "c" << true); - BSONObj obj2 = BSON("a" << 0 << "b" << "hello" << "c" << false); + BSONObj obj1 = BSON("a" << 1 << "b" + << "hello" + << "c" << true); + BSONObj obj2 = BSON("a" << 0 << "b" + << "hello" + << "c" << false); bool b; b = false; ASSERT_OK(bsonExtractBooleanFieldWithDefault(obj1, "a", false, &b)); @@ -112,40 +121,18 @@ TEST(ExtractBSON, ExtractBooleanFieldWithDefault) { TEST(ExtractBSON, ExtractIntegerField) { long long v; - ASSERT_EQUALS(ErrorCodes::NoSuchKey, bsonExtractIntegerField( - BSON("a" << 1), - "b", - &v)); - ASSERT_OK(bsonExtractIntegerFieldWithDefault( - BSON("a" << 1), - "b", - -1LL, - &v)); + ASSERT_EQUALS(ErrorCodes::NoSuchKey, bsonExtractIntegerField(BSON("a" << 1), "b", &v)); + ASSERT_OK(bsonExtractIntegerFieldWithDefault(BSON("a" << 1), "b", -1LL, &v)); ASSERT_EQUALS(-1LL, v); - ASSERT_EQUALS(ErrorCodes::TypeMismatch, bsonExtractIntegerField( - BSON("a" << false), - "a", - &v)); - ASSERT_EQUALS(ErrorCodes::BadValue, bsonExtractIntegerField( - BSON("a" << std::numeric_limits<float>::quiet_NaN()), - "a", - &v)); - ASSERT_EQUALS(ErrorCodes::BadValue, bsonExtractIntegerField( - BSON("a" << pow(2.0, 64)), - "a", - &v)); - ASSERT_EQUALS(ErrorCodes::BadValue, bsonExtractIntegerField( - BSON("a" << -1.5), - "a", - &v)); - ASSERT_OK(bsonExtractIntegerField( - BSON("a" << -pow(2.0, 55)), - "a", - &v)); + ASSERT_EQUALS(ErrorCodes::TypeMismatch, bsonExtractIntegerField(BSON("a" << false), "a", &v)); + ASSERT_EQUALS( + ErrorCodes::BadValue, + bsonExtractIntegerField(BSON("a" << std::numeric_limits<float>::quiet_NaN()), "a", &v)); + ASSERT_EQUALS(ErrorCodes::BadValue, + bsonExtractIntegerField(BSON("a" << pow(2.0, 64)), "a", &v)); + ASSERT_EQUALS(ErrorCodes::BadValue, bsonExtractIntegerField(BSON("a" << -1.5), "a", &v)); + ASSERT_OK(bsonExtractIntegerField(BSON("a" << -pow(2.0, 55)), "a", &v)); ASSERT_EQUALS(-(1LL << 55), v); - ASSERT_OK(bsonExtractIntegerField( - BSON("a" << 5178), - "a", - &v)); + ASSERT_OK(bsonExtractIntegerField(BSON("a" << 5178), "a", &v)); ASSERT_EQUALS(5178, v); } diff --git a/src/mongo/bson/util/builder.h b/src/mongo/bson/util/builder.h index 816034af2b2..bf0668a6da5 100644 --- a/src/mongo/bson/util/builder.h +++ b/src/mongo/bson/util/builder.h @@ -45,379 +45,413 @@ #include "mongo/util/assert_util.h" namespace mongo { - /* Accessing unaligned doubles on ARM generates an alignment trap and aborts with SIGBUS on Linux. - Wrapping the double in a packed struct forces gcc to generate code that works with unaligned values too. - The generated code for other architectures (which already allow unaligned accesses) is the same as if - there was a direct pointer access. +/* Accessing unaligned doubles on ARM generates an alignment trap and aborts with SIGBUS on Linux. + Wrapping the double in a packed struct forces gcc to generate code that works with unaligned values too. + The generated code for other architectures (which already allow unaligned accesses) is the same as if + there was a direct pointer access. +*/ +struct PackedDouble { + double d; +} PACKED_DECL; + + +/* Note the limit here is rather arbitrary and is simply a standard. generally the code works + with any object that fits in ram. + + Also note that the server has some basic checks to enforce this limit but those checks are not exhaustive + for example need to check for size too big after + update $push (append) operation + various db.eval() type operations +*/ +const int BSONObjMaxUserSize = 16 * 1024 * 1024; + +/* + Sometimes we need objects slightly larger - an object in the replication local.oplog + is slightly larger than a user object for example. +*/ +const int BSONObjMaxInternalSize = BSONObjMaxUserSize + (16 * 1024); + +const int BufferMaxSize = 64 * 1024 * 1024; + +template <typename Allocator> +class StringBuilderImpl; + +class TrivialAllocator { +public: + void* Malloc(size_t sz) { + return mongoMalloc(sz); + } + void* Realloc(void* p, size_t sz) { + return mongoRealloc(p, sz); + } + void Free(void* p) { + free(p); + } +}; + +class StackAllocator { +public: + enum { SZ = 512 }; + void* Malloc(size_t sz) { + if (sz <= SZ) + return buf; + return mongoMalloc(sz); + } + void* Realloc(void* p, size_t sz) { + if (p == buf) { + if (sz <= SZ) + return buf; + void* d = mongoMalloc(sz); + if (d == 0) + msgasserted(15912, "out of memory StackAllocator::Realloc"); + memcpy(d, p, SZ); + return d; + } + return mongoRealloc(p, sz); + } + void Free(void* p) { + if (p != buf) + free(p); + } + +private: + char buf[SZ]; +}; + +template <class Allocator> +class _BufBuilder { + // non-copyable, non-assignable + _BufBuilder(const _BufBuilder&); + _BufBuilder& operator=(const _BufBuilder&); + Allocator al; + +public: + _BufBuilder(int initsize = 512) : size(initsize) { + if (size > 0) { + data = (char*)al.Malloc(size); + if (data == 0) + msgasserted(10000, "out of memory BufBuilder"); + } else { + data = 0; + } + l = 0; + reservedBytes = 0; + } + ~_BufBuilder() { + kill(); + } + + void kill() { + if (data) { + al.Free(data); + data = 0; + } + } + + void reset() { + l = 0; + reservedBytes = 0; + } + void reset(int maxSize) { + l = 0; + reservedBytes = 0; + if (maxSize && size > maxSize) { + al.Free(data); + data = (char*)al.Malloc(maxSize); + if (data == 0) + msgasserted(15913, "out of memory BufBuilder::reset"); + size = maxSize; + } + } + + /** leave room for some stuff later + @return point to region that was skipped. pointer may change later (on realloc), so for immediate use only */ - struct PackedDouble { - double d; - } PACKED_DECL; - - - /* Note the limit here is rather arbitrary and is simply a standard. generally the code works - with any object that fits in ram. - - Also note that the server has some basic checks to enforce this limit but those checks are not exhaustive - for example need to check for size too big after - update $push (append) operation - various db.eval() type operations - */ - const int BSONObjMaxUserSize = 16 * 1024 * 1024; - - /* - Sometimes we need objects slightly larger - an object in the replication local.oplog - is slightly larger than a user object for example. - */ - const int BSONObjMaxInternalSize = BSONObjMaxUserSize + ( 16 * 1024 ); - - const int BufferMaxSize = 64 * 1024 * 1024; - - template <typename Allocator> - class StringBuilderImpl; - - class TrivialAllocator { - public: - void* Malloc(size_t sz) { return mongoMalloc(sz); } - void* Realloc(void *p, size_t sz) { return mongoRealloc(p, sz); } - void Free(void *p) { free(p); } - }; - - class StackAllocator { - public: - enum { SZ = 512 }; - void* Malloc(size_t sz) { - if( sz <= SZ ) return buf; - return mongoMalloc(sz); - } - void* Realloc(void *p, size_t sz) { - if( p == buf ) { - if( sz <= SZ ) return buf; - void *d = mongoMalloc(sz); - if ( d == 0 ) - msgasserted( 15912 , "out of memory StackAllocator::Realloc" ); - memcpy(d, p, SZ); - return d; - } - return mongoRealloc(p, sz); - } - void Free(void *p) { - if( p != buf ) - free(p); - } - private: - char buf[SZ]; - }; - - template< class Allocator > - class _BufBuilder { - // non-copyable, non-assignable - _BufBuilder( const _BufBuilder& ); - _BufBuilder& operator=( const _BufBuilder& ); - Allocator al; - public: - _BufBuilder(int initsize = 512) : size(initsize) { - if ( size > 0 ) { - data = (char *) al.Malloc(size); - if( data == 0 ) - msgasserted(10000, "out of memory BufBuilder"); - } - else { - data = 0; - } - l = 0; - reservedBytes = 0; - } - ~_BufBuilder() { kill(); } - - void kill() { - if ( data ) { - al.Free(data); - data = 0; - } - } - - void reset() { - l = 0; - reservedBytes = 0; - } - void reset( int maxSize ) { - l = 0; - reservedBytes = 0; - if ( maxSize && size > maxSize ) { - al.Free(data); - data = (char*)al.Malloc(maxSize); - if ( data == 0 ) - msgasserted( 15913 , "out of memory BufBuilder::reset" ); - size = maxSize; - } - } - - /** leave room for some stuff later - @return point to region that was skipped. pointer may change later (on realloc), so for immediate use only - */ - char* skip(int n) { return grow(n); } - - /* note this may be deallocated (realloced) if you keep writing. */ - char* buf() { return data; } - const char* buf() const { return data; } - - /* assume ownership of the buffer - you must then free() it */ - void decouple() { data = 0; } - - void appendUChar(unsigned char j) { - BOOST_STATIC_ASSERT(CHAR_BIT == 8); - appendNumImpl(j); - } - void appendChar(char j) { - appendNumImpl(j); - } - void appendNum(char j) { - appendNumImpl(j); - } - void appendNum(short j) { - BOOST_STATIC_ASSERT(sizeof(short) == 2); - appendNumImpl(j); - } - void appendNum(int j) { - BOOST_STATIC_ASSERT(sizeof(int) == 4); - appendNumImpl(j); - } - void appendNum(unsigned j) { - appendNumImpl(j); - } - - // Bool does not have a well defined encoding. - void appendNum(bool j) = delete; - - void appendNum(double j) { - BOOST_STATIC_ASSERT(sizeof(double) == 8); - appendNumImpl(j); - } - void appendNum(long long j) { - BOOST_STATIC_ASSERT(sizeof(long long) == 8); - appendNumImpl(j); - } - void appendNum(unsigned long long j) { - appendNumImpl(j); - } - - void appendBuf(const void *src, size_t len) { - memcpy(grow((int) len), src, len); - } - - template<class T> - void appendStruct(const T& s) { - appendBuf(&s, sizeof(T)); - } - - void appendStr(StringData str , bool includeEndingNull = true ) { - const int len = str.size() + ( includeEndingNull ? 1 : 0 ); - str.copyTo( grow(len), includeEndingNull ); - } - - /** @return length of current std::string */ - int len() const { return l; } - void setlen( int newLen ) { l = newLen; } - /** @return size of the buffer */ - int getSize() const { return size; } - - /* returns the pre-grow write position */ - inline char* grow(int by) { - int oldlen = l; - int newLen = l + by; - int minSize = newLen + reservedBytes; - if ( minSize > size ) { - grow_reallocate(minSize); - } - l = newLen; - return data + oldlen; - } - - /** - * Reserve room for some number of bytes to be claimed at a later time. - */ - void reserveBytes(int bytes) { - int minSize = l + reservedBytes + bytes; - if (minSize > size) - grow_reallocate(minSize); - - // This must happen *after* any attempt to grow. - reservedBytes += bytes; - } - - /** - * Claim an earlier reservation of some number of bytes. These bytes must already have been - * reserved. Appends of up to this many bytes immediately following a claim are - * guaranteed to succeed without a need to reallocate. - */ - void claimReservedBytes(int bytes) { - invariant(reservedBytes >= bytes); - reservedBytes -= bytes; - } - - private: - template<typename T> - void appendNumImpl(T t) { - // NOTE: For now, we assume that all things written - // by a BufBuilder are intended for external use: either written to disk - // or to the wire. Since all of our encoding formats are little endian, - // we bake that assumption in here. This decision should be revisited soon. - DataView(grow(sizeof(t))).write(tagLittleEndian(t)); - } - - - /* "slow" portion of 'grow()' */ - void NOINLINE_DECL grow_reallocate(int minSize) { - int a = 64; - while (a < minSize) - a = a * 2; - - if ( a > BufferMaxSize ) { - std::stringstream ss; - ss << "BufBuilder attempted to grow() to " << a << " bytes, past the 64MB limit."; - msgasserted(13548, ss.str().c_str()); - } - data = (char *) al.Realloc(data, a); - if ( data == NULL ) - msgasserted( 16070 , "out of memory BufBuilder::grow_reallocate" ); - size = a; - } - - char *data; - int l; - int size; - int reservedBytes; // eagerly grow_reallocate to keep this many bytes of spare room. - - friend class StringBuilderImpl<Allocator>; - }; - - typedef _BufBuilder<TrivialAllocator> BufBuilder; - - /** The StackBufBuilder builds smaller datasets on the stack instead of using malloc. - this can be significantly faster for small bufs. However, you can not decouple() the - buffer with StackBufBuilder. - While designed to be a variable on the stack, if you were to dynamically allocate one, - nothing bad would happen. In fact in some circumstances this might make sense, say, - embedded in some other object. - */ - class StackBufBuilder : public _BufBuilder<StackAllocator> { - public: - StackBufBuilder() : _BufBuilder<StackAllocator>(StackAllocator::SZ) { } - void decouple(); // not allowed. not implemented. - }; + char* skip(int n) { + return grow(n); + } + + /* note this may be deallocated (realloced) if you keep writing. */ + char* buf() { + return data; + } + const char* buf() const { + return data; + } + + /* assume ownership of the buffer - you must then free() it */ + void decouple() { + data = 0; + } + + void appendUChar(unsigned char j) { + BOOST_STATIC_ASSERT(CHAR_BIT == 8); + appendNumImpl(j); + } + void appendChar(char j) { + appendNumImpl(j); + } + void appendNum(char j) { + appendNumImpl(j); + } + void appendNum(short j) { + BOOST_STATIC_ASSERT(sizeof(short) == 2); + appendNumImpl(j); + } + void appendNum(int j) { + BOOST_STATIC_ASSERT(sizeof(int) == 4); + appendNumImpl(j); + } + void appendNum(unsigned j) { + appendNumImpl(j); + } + + // Bool does not have a well defined encoding. + void appendNum(bool j) = delete; + + void appendNum(double j) { + BOOST_STATIC_ASSERT(sizeof(double) == 8); + appendNumImpl(j); + } + void appendNum(long long j) { + BOOST_STATIC_ASSERT(sizeof(long long) == 8); + appendNumImpl(j); + } + void appendNum(unsigned long long j) { + appendNumImpl(j); + } + + void appendBuf(const void* src, size_t len) { + memcpy(grow((int)len), src, len); + } + + template <class T> + void appendStruct(const T& s) { + appendBuf(&s, sizeof(T)); + } + + void appendStr(StringData str, bool includeEndingNull = true) { + const int len = str.size() + (includeEndingNull ? 1 : 0); + str.copyTo(grow(len), includeEndingNull); + } + + /** @return length of current std::string */ + int len() const { + return l; + } + void setlen(int newLen) { + l = newLen; + } + /** @return size of the buffer */ + int getSize() const { + return size; + } + + /* returns the pre-grow write position */ + inline char* grow(int by) { + int oldlen = l; + int newLen = l + by; + int minSize = newLen + reservedBytes; + if (minSize > size) { + grow_reallocate(minSize); + } + l = newLen; + return data + oldlen; + } + + /** + * Reserve room for some number of bytes to be claimed at a later time. + */ + void reserveBytes(int bytes) { + int minSize = l + reservedBytes + bytes; + if (minSize > size) + grow_reallocate(minSize); + + // This must happen *after* any attempt to grow. + reservedBytes += bytes; + } + + /** + * Claim an earlier reservation of some number of bytes. These bytes must already have been + * reserved. Appends of up to this many bytes immediately following a claim are + * guaranteed to succeed without a need to reallocate. + */ + void claimReservedBytes(int bytes) { + invariant(reservedBytes >= bytes); + reservedBytes -= bytes; + } + +private: + template <typename T> + void appendNumImpl(T t) { + // NOTE: For now, we assume that all things written + // by a BufBuilder are intended for external use: either written to disk + // or to the wire. Since all of our encoding formats are little endian, + // we bake that assumption in here. This decision should be revisited soon. + DataView(grow(sizeof(t))).write(tagLittleEndian(t)); + } + + + /* "slow" portion of 'grow()' */ + void NOINLINE_DECL grow_reallocate(int minSize) { + int a = 64; + while (a < minSize) + a = a * 2; + + if (a > BufferMaxSize) { + std::stringstream ss; + ss << "BufBuilder attempted to grow() to " << a << " bytes, past the 64MB limit."; + msgasserted(13548, ss.str().c_str()); + } + data = (char*)al.Realloc(data, a); + if (data == NULL) + msgasserted(16070, "out of memory BufBuilder::grow_reallocate"); + size = a; + } + + char* data; + int l; + int size; + int reservedBytes; // eagerly grow_reallocate to keep this many bytes of spare room. + + friend class StringBuilderImpl<Allocator>; +}; + +typedef _BufBuilder<TrivialAllocator> BufBuilder; + +/** The StackBufBuilder builds smaller datasets on the stack instead of using malloc. + this can be significantly faster for small bufs. However, you can not decouple() the + buffer with StackBufBuilder. + While designed to be a variable on the stack, if you were to dynamically allocate one, + nothing bad would happen. In fact in some circumstances this might make sense, say, + embedded in some other object. +*/ +class StackBufBuilder : public _BufBuilder<StackAllocator> { +public: + StackBufBuilder() : _BufBuilder<StackAllocator>(StackAllocator::SZ) {} + void decouple(); // not allowed. not implemented. +}; #if defined(_WIN32) && _MSC_VER < 1900 #pragma push_macro("snprintf") #define snprintf _snprintf #endif - /** std::stringstream deals with locale so this is a lot faster than std::stringstream for UTF8 */ - template <typename Allocator> - class StringBuilderImpl { - public: - // Sizes are determined based on the number of characters in 64-bit + the trailing '\0' - static const size_t MONGO_DBL_SIZE = 3 + DBL_MANT_DIG - DBL_MIN_EXP + 1; - static const size_t MONGO_S32_SIZE = 12; - static const size_t MONGO_U32_SIZE = 11; - static const size_t MONGO_S64_SIZE = 23; - static const size_t MONGO_U64_SIZE = 22; - static const size_t MONGO_S16_SIZE = 7; - static const size_t MONGO_PTR_SIZE = 19; // Accounts for the 0x prefix - - StringBuilderImpl() { } - - StringBuilderImpl& operator<<( double x ) { - return SBNUM( x , MONGO_DBL_SIZE , "%g" ); - } - StringBuilderImpl& operator<<( int x ) { - return SBNUM( x , MONGO_S32_SIZE , "%d" ); - } - StringBuilderImpl& operator<<( unsigned x ) { - return SBNUM( x , MONGO_U32_SIZE , "%u" ); - } - StringBuilderImpl& operator<<( long x ) { - return SBNUM( x , MONGO_S64_SIZE , "%ld" ); - } - StringBuilderImpl& operator<<( unsigned long x ) { - return SBNUM( x , MONGO_U64_SIZE , "%lu" ); - } - StringBuilderImpl& operator<<( long long x ) { - return SBNUM( x , MONGO_S64_SIZE , "%lld" ); - } - StringBuilderImpl& operator<<( unsigned long long x ) { - return SBNUM( x , MONGO_U64_SIZE , "%llu" ); - } - StringBuilderImpl& operator<<( short x ) { - return SBNUM( x , MONGO_S16_SIZE , "%hd" ); - } - StringBuilderImpl& operator<<(const void* x) { - if (sizeof(x) == 8) { - return SBNUM(x, MONGO_PTR_SIZE, "0x%llX"); - } - else { - return SBNUM(x, MONGO_PTR_SIZE, "0x%lX"); - } - } - StringBuilderImpl& operator<<( char c ) { - _buf.grow( 1 )[0] = c; - return *this; - } - StringBuilderImpl& operator<<(const char* str) { - return *this << StringData(str); - } - StringBuilderImpl& operator<<(StringData str) { - append(str); - return *this; - } - - void appendDoubleNice( double x ) { - const int prev = _buf.l; - const int maxSize = 32; - char * start = _buf.grow( maxSize ); - int z = snprintf( start , maxSize , "%.16g" , x ); - verify( z >= 0 ); - verify( z < maxSize ); - _buf.l = prev + z; - if( strchr(start, '.') == 0 && strchr(start, 'E') == 0 && strchr(start, 'N') == 0 ) { - write( ".0" , 2 ); - } - } - - void write( const char* buf, int len) { memcpy( _buf.grow( len ) , buf , len ); } - - void append( StringData str ) { str.copyTo( _buf.grow( str.size() ), false ); } - - void reset( int maxSize = 0 ) { _buf.reset( maxSize ); } - - std::string str() const { return std::string(_buf.data, _buf.l); } - - /** size of current std::string */ - int len() const { return _buf.l; } - - private: - _BufBuilder<Allocator> _buf; - - // non-copyable, non-assignable - StringBuilderImpl( const StringBuilderImpl& ); - StringBuilderImpl& operator=( const StringBuilderImpl& ); - - template <typename T> - StringBuilderImpl& SBNUM(T val,int maxSize,const char *macro) { - int prev = _buf.l; - int z = snprintf( _buf.grow(maxSize) , maxSize , macro , (val) ); - verify( z >= 0 ); - verify( z < maxSize ); - _buf.l = prev + z; - return *this; - } - }; - - typedef StringBuilderImpl<TrivialAllocator> StringBuilder; - typedef StringBuilderImpl<StackAllocator> StackStringBuilder; +/** std::stringstream deals with locale so this is a lot faster than std::stringstream for UTF8 */ +template <typename Allocator> +class StringBuilderImpl { +public: + // Sizes are determined based on the number of characters in 64-bit + the trailing '\0' + static const size_t MONGO_DBL_SIZE = 3 + DBL_MANT_DIG - DBL_MIN_EXP + 1; + static const size_t MONGO_S32_SIZE = 12; + static const size_t MONGO_U32_SIZE = 11; + static const size_t MONGO_S64_SIZE = 23; + static const size_t MONGO_U64_SIZE = 22; + static const size_t MONGO_S16_SIZE = 7; + static const size_t MONGO_PTR_SIZE = 19; // Accounts for the 0x prefix + + StringBuilderImpl() {} + + StringBuilderImpl& operator<<(double x) { + return SBNUM(x, MONGO_DBL_SIZE, "%g"); + } + StringBuilderImpl& operator<<(int x) { + return SBNUM(x, MONGO_S32_SIZE, "%d"); + } + StringBuilderImpl& operator<<(unsigned x) { + return SBNUM(x, MONGO_U32_SIZE, "%u"); + } + StringBuilderImpl& operator<<(long x) { + return SBNUM(x, MONGO_S64_SIZE, "%ld"); + } + StringBuilderImpl& operator<<(unsigned long x) { + return SBNUM(x, MONGO_U64_SIZE, "%lu"); + } + StringBuilderImpl& operator<<(long long x) { + return SBNUM(x, MONGO_S64_SIZE, "%lld"); + } + StringBuilderImpl& operator<<(unsigned long long x) { + return SBNUM(x, MONGO_U64_SIZE, "%llu"); + } + StringBuilderImpl& operator<<(short x) { + return SBNUM(x, MONGO_S16_SIZE, "%hd"); + } + StringBuilderImpl& operator<<(const void* x) { + if (sizeof(x) == 8) { + return SBNUM(x, MONGO_PTR_SIZE, "0x%llX"); + } else { + return SBNUM(x, MONGO_PTR_SIZE, "0x%lX"); + } + } + StringBuilderImpl& operator<<(char c) { + _buf.grow(1)[0] = c; + return *this; + } + StringBuilderImpl& operator<<(const char* str) { + return *this << StringData(str); + } + StringBuilderImpl& operator<<(StringData str) { + append(str); + return *this; + } + + void appendDoubleNice(double x) { + const int prev = _buf.l; + const int maxSize = 32; + char* start = _buf.grow(maxSize); + int z = snprintf(start, maxSize, "%.16g", x); + verify(z >= 0); + verify(z < maxSize); + _buf.l = prev + z; + if (strchr(start, '.') == 0 && strchr(start, 'E') == 0 && strchr(start, 'N') == 0) { + write(".0", 2); + } + } + + void write(const char* buf, int len) { + memcpy(_buf.grow(len), buf, len); + } + + void append(StringData str) { + str.copyTo(_buf.grow(str.size()), false); + } + + void reset(int maxSize = 0) { + _buf.reset(maxSize); + } + + std::string str() const { + return std::string(_buf.data, _buf.l); + } + + /** size of current std::string */ + int len() const { + return _buf.l; + } + +private: + _BufBuilder<Allocator> _buf; + + // non-copyable, non-assignable + StringBuilderImpl(const StringBuilderImpl&); + StringBuilderImpl& operator=(const StringBuilderImpl&); + + template <typename T> + StringBuilderImpl& SBNUM(T val, int maxSize, const char* macro) { + int prev = _buf.l; + int z = snprintf(_buf.grow(maxSize), maxSize, macro, (val)); + verify(z >= 0); + verify(z < maxSize); + _buf.l = prev + z; + return *this; + } +}; + +typedef StringBuilderImpl<TrivialAllocator> StringBuilder; +typedef StringBuilderImpl<StackAllocator> StackStringBuilder; #if defined(_WIN32) && _MSC_VER < 1900 #undef snprintf #pragma pop_macro("snprintf") #endif -} // namespace mongo +} // namespace mongo diff --git a/src/mongo/bson/util/builder_test.cpp b/src/mongo/bson/util/builder_test.cpp index 04c7df1c655..f40da3ac1b1 100644 --- a/src/mongo/bson/util/builder_test.cpp +++ b/src/mongo/bson/util/builder_test.cpp @@ -32,39 +32,38 @@ #include "mongo/bson/util/builder.h" namespace mongo { - TEST( Builder, String1 ) { - const char * big = "eliot was here"; - StringData small( big, 5 ); - ASSERT_EQUALS( small, "eliot" ); +TEST(Builder, String1) { + const char* big = "eliot was here"; + StringData small(big, 5); + ASSERT_EQUALS(small, "eliot"); - BufBuilder bb; - bb.appendStr( small ); + BufBuilder bb; + bb.appendStr(small); - ASSERT_EQUALS( 0, strcmp( bb.buf(), "eliot" ) ); - ASSERT_EQUALS( 0, strcmp( "eliot", bb.buf() ) ); - } + ASSERT_EQUALS(0, strcmp(bb.buf(), "eliot")); + ASSERT_EQUALS(0, strcmp("eliot", bb.buf())); +} - TEST(Builder, StringBuilderAddress) { - const void* longPtr = reinterpret_cast<const void*>(-1); - const void* shortPtr = reinterpret_cast<const void*>(0xDEADBEEF); - const void* nullPtr = NULL; +TEST(Builder, StringBuilderAddress) { + const void* longPtr = reinterpret_cast<const void*>(-1); + const void* shortPtr = reinterpret_cast<const void*>(0xDEADBEEF); + const void* nullPtr = NULL; - StringBuilder sb; - sb << longPtr; + StringBuilder sb; + sb << longPtr; - if (sizeof(longPtr) == 8) { - ASSERT_EQUALS("0xFFFFFFFFFFFFFFFF", sb.str()); - } - else { - ASSERT_EQUALS("0xFFFFFFFF", sb.str()); - } + if (sizeof(longPtr) == 8) { + ASSERT_EQUALS("0xFFFFFFFFFFFFFFFF", sb.str()); + } else { + ASSERT_EQUALS("0xFFFFFFFF", sb.str()); + } - sb.reset(); - sb << shortPtr; - ASSERT_EQUALS("0xDEADBEEF", sb.str()); + sb.reset(); + sb << shortPtr; + ASSERT_EQUALS("0xDEADBEEF", sb.str()); - sb.reset(); - sb << nullPtr; - ASSERT_EQUALS("0x0", sb.str()); - } + sb.reset(); + sb << nullPtr; + ASSERT_EQUALS("0x0", sb.str()); +} } |