summaryrefslogtreecommitdiff
path: root/src/mongo/bson
diff options
context:
space:
mode:
Diffstat (limited to 'src/mongo/bson')
-rw-r--r--src/mongo/bson/bson_field.h210
-rw-r--r--src/mongo/bson/bson_field_test.cpp54
-rw-r--r--src/mongo/bson/bson_obj_data_type_test.cpp48
-rw-r--r--src/mongo/bson/bson_obj_test.cpp501
-rw-r--r--src/mongo/bson/bson_validate.cpp594
-rw-r--r--src/mongo/bson/bson_validate.h16
-rw-r--r--src/mongo/bson/bson_validate_test.cpp445
-rw-r--r--src/mongo/bson/bsonelement.cpp918
-rw-r--r--src/mongo/bson/bsonelement.h1055
-rw-r--r--src/mongo/bson/bsonmisc.cpp148
-rw-r--r--src/mongo/bson/bsonmisc.h475
-rw-r--r--src/mongo/bson/bsonobj.cpp1530
-rw-r--r--src/mongo/bson/bsonobj.h1303
-rw-r--r--src/mongo/bson/bsonobjbuilder.cpp328
-rw-r--r--src/mongo/bson/bsonobjbuilder.h1591
-rw-r--r--src/mongo/bson/bsonobjbuilder_test.cpp400
-rw-r--r--src/mongo/bson/bsontypes.cpp146
-rw-r--r--src/mongo/bson/bsontypes.h172
-rw-r--r--src/mongo/bson/json.cpp2120
-rw-r--r--src/mongo/bson/json.h865
-rw-r--r--src/mongo/bson/mutable/algorithm.h453
-rw-r--r--src/mongo/bson/mutable/const_element-inl.h312
-rw-r--r--src/mongo/bson/mutable/const_element.h176
-rw-r--r--src/mongo/bson/mutable/damage_vector.h32
-rw-r--r--src/mongo/bson/mutable/document-inl.h74
-rw-r--r--src/mongo/bson/mutable/document.cpp4741
-rw-r--r--src/mongo/bson/mutable/document.h859
-rw-r--r--src/mongo/bson/mutable/element-inl.h246
-rw-r--r--src/mongo/bson/mutable/element.cpp294
-rw-r--r--src/mongo/bson/mutable/element.h1003
-rw-r--r--src/mongo/bson/mutable/mutable_bson_algo_test.cpp571
-rw-r--r--src/mongo/bson/mutable/mutable_bson_test.cpp5938
-rw-r--r--src/mongo/bson/mutable/mutable_bson_test_utils.cpp281
-rw-r--r--src/mongo/bson/mutable/mutable_bson_test_utils.h150
-rw-r--r--src/mongo/bson/oid.cpp243
-rw-r--r--src/mongo/bson/oid.h368
-rw-r--r--src/mongo/bson/oid_test.cpp187
-rw-r--r--src/mongo/bson/ordering.h92
-rw-r--r--src/mongo/bson/timestamp.cpp57
-rw-r--r--src/mongo/bson/timestamp.h174
-rw-r--r--src/mongo/bson/util/bson_check.h86
-rw-r--r--src/mongo/bson/util/bson_check_test.cpp69
-rw-r--r--src/mongo/bson/util/bson_extract.cpp268
-rw-r--r--src/mongo/bson/util/bson_extract.h280
-rw-r--r--src/mongo/bson/util/bson_extract_test.cpp67
-rw-r--r--src/mongo/bson/util/builder.h762
-rw-r--r--src/mongo/bson/util/builder_test.cpp55
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());
+}
}