summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--db/jsobj.h2
-rw-r--r--db/query.cpp112
-rw-r--r--dbtests/updatetests.cpp42
3 files changed, 108 insertions, 48 deletions
diff --git a/db/jsobj.h b/db/jsobj.h
index b551b4bf828..c30a44112df 100644
--- a/db/jsobj.h
+++ b/db/jsobj.h
@@ -1199,6 +1199,8 @@ namespace mongo {
#endif
inline BSONObj BSONElement::embeddedObjectUserCheck() {
+ printStackTrace();
+ out() << "this: " << *this << endl;
uassert( "invalid parameter: expected an object", type()==Object || type()==Array );
return BSONObj(value());
}
diff --git a/db/query.cpp b/db/query.cpp
index cc300b5e4f2..f0241484e1a 100644
--- a/db/query.cpp
+++ b/db/query.cpp
@@ -163,6 +163,56 @@ namespace mongo {
return nDeleted;
}
+ class EmbeddedBuilder {
+ public:
+ EmbeddedBuilder() {
+ addBuilder( "" );
+ }
+ // It is assumed that the calls to appendAs will be made with the 'name'
+ // parameter in lex ascending order.
+ void appendAs( const BSONElement &e, string name ) {
+ cout << "appendAs: " << name << endl;
+ int i = 1, n = builders_.size();
+ while( i < n && name.substr( 0, builders_[ i ].first.length() ) == builders_[ i ].first ) {
+ name = name.substr( builders_[ i ].first.length() + 1 );
+ cout << "name now: " << name << endl;
+ ++i;
+ }
+ for( int j = n - 1; j >= i; --j ) {
+ builders_[ j - 1 ].second->append( builders_[ j ].first.c_str(), builders_[ j ].second->done() );
+ cout << "popping: " << builders_.back().first << endl;
+ builders_.pop_back();
+ }
+ for( string next = splitDot( name ); !next.empty(); next = splitDot( name ) ) {
+ addBuilder( next );
+ }
+ cout << "name appending: " << name << endl;
+ builders_.back().second->appendAs( e, name.c_str() );
+ }
+ void appendSelf( BSONObjBuilder &b ) {
+ for( int j = builders_.size() - 1; j >= 1; --j ) {
+ builders_[ j - 1 ].second->append( builders_[ j ].first.c_str(), builders_[ j ].second->done() );
+ builders_.pop_back();
+ }
+ b.appendElements( builders_.back().second->done() );
+ }
+ static string splitDot( string & str ) {
+ size_t pos = str.find( '.' );
+ if ( pos == string::npos )
+ return "";
+ string ret = str.substr( 0, pos );
+ str = str.substr( pos + 1 );
+ return ret;
+ }
+ private:
+ void addBuilder( const string &name ) {
+ cout << "adding builder (" << name << ")" << endl;
+ builders_.push_back( make_pair( name, shared_ptr< BSONObjBuilder >( new BSONObjBuilder() ) ) );
+ }
+ typedef vector< pair< string, shared_ptr< BSONObjBuilder > > > Stack;
+ Stack builders_;
+ };
+
struct Mod {
enum Op { INC, SET } op;
const char *fieldName;
@@ -199,6 +249,16 @@ namespace mongo {
return -1;
return strcmp( m->fieldName, p->first.c_str() );
}
+ bool mayAddEmbedded( map< string, BSONElement > &existing, string right ) {
+ for( string left = EmbeddedBuilder::splitDot( right );
+ left.length() > 0 && left[ left.length() - 1 ] != '.';
+ left += "." + EmbeddedBuilder::splitDot( right ) ) {
+ if ( existing.count( left ) > 0 && existing[ left ].type() != Object )
+ return false;
+ left += "." + EmbeddedBuilder::splitDot( right );
+ }
+ return true;
+ }
public:
void getMods( const BSONObj &from );
bool applyModsInPlace( const BSONObj &obj ) const;
@@ -279,54 +339,8 @@ namespace mongo {
return true;
}
- class EmbeddedBuilder {
- public:
- EmbeddedBuilder() {
- addBuilder( "" );
- }
- // It is assumed that the calls to appendAs will be made with the 'name'
- // parameter in lex ascending order.
- void appendAs( const BSONElement &e, string name ) {
- cout << "appendAs: " << name << endl;
- int i = 0, n = builders_.size();
- while( i < n && name.substr( 0, builders_[ i ].first.length() ) == builders_[ i ].first ) {
- name = name.substr( builders_[ i ].first.length() );
- ++i;
- }
- for( int j = n - 1; j >= i; --j ) {
- builders_[ j - 1 ].second->append( builders_[ j ].first.c_str(), builders_[ j ].second->done() );
- builders_.pop_back();
- }
- for( string next = splitDot( name ); !next.empty(); next = splitDot( name ) ) {
- addBuilder( next );
- }
- builders_.back().second->appendAs( e, name.c_str() );
- }
- void appendSelf( BSONObjBuilder &b ) {
- for( int j = builders_.size() - 1; j >= 1; --j ) {
- builders_[ j - 1 ].second->append( builders_[ j ].first.c_str(), builders_[ j ].second->done() );
- builders_.pop_back();
- }
- b.appendElements( builders_.back().second->done() );
- }
- private:
- void addBuilder( const string &name ) {
- builders_.push_back( make_pair( name, shared_ptr< BSONObjBuilder >( new BSONObjBuilder() ) ) );
- }
- string splitDot( string & str ) {
- size_t pos = str.find( '.' );
- if ( pos == string::npos )
- return "";
- string ret = str.substr( 0, pos );
- str = str.substr( pos + 1 );
- return ret;
- }
- typedef vector< pair< string, shared_ptr< BSONObjBuilder > > > Stack;
- Stack builders_;
- };
-
void ModSet::extractFields( map< string, BSONElement > &fields, const BSONElement &top, const string &base ) {
- if ( top.type() != Object && top.type() != Array ) {
+ if ( top.type() != Object ) {
fields[ base + top.fieldName() ] = top;
return;
}
@@ -368,12 +382,14 @@ namespace mongo {
m->setn( m->elt.number() );
} else if ( m->op == Mod::SET ) {
// nothing
- }
+ }
b2.appendAs( m->elt, m->fieldName );
++m;
++p;
} else if ( cmp < 0 ) {
// Here may be $inc or $set
+ uassert( "Modifier spec implies existence of an encapsulating object with a name that already represents a non-object",
+ mayAddEmbedded( existing, m->fieldName ) );
b2.appendAs( m->elt, m->fieldName );
++m;
} else if ( cmp > 0 ) {
diff --git a/dbtests/updatetests.cpp b/dbtests/updatetests.cpp
index 8a1c0e7cd25..2bb3136d026 100644
--- a/dbtests/updatetests.cpp
+++ b/dbtests/updatetests.cpp
@@ -223,6 +223,44 @@ namespace UpdateTests {
}
};
+ class UnorderedNewSet : public SetBase {
+ public:
+ void run() {
+ client().insert( ns(), fromjson( "{'_id':0}" ) );
+ client().update( ns(), BSONObj(), BSON( "$set" << BSON( "f.g.h" << 3.0 << "f.g.a" << 2.0 ) ) );
+ ASSERT( client().findOne( ns(), Query() ).woCompare( fromjson( "{'_id':0,f:{g:{a:2,h:3}}}" ) ) == 0 );
+ }
+ };
+
+ class UnorderedNewSetAdjacent : public SetBase {
+ public:
+ void run() {
+ client().insert( ns(), fromjson( "{'_id':0}" ) );
+ client().update( ns(), BSONObj(), BSON( "$set" << BSON( "f.g.h.b" << 3.0 << "f.g.a.b" << 2.0 ) ) );
+ ASSERT( client().findOne( ns(), Query() ).woCompare( fromjson( "{'_id':0,f:{g:{a:{b:2},h:{b:3}}}}" ) ) == 0 );
+ }
+ };
+
+ class ArrayEmbeddedSet : public SetBase {
+ public:
+ void run() {
+ client().insert( ns(), fromjson( "{'_id':0,z:[4,'b']}" ) );
+ client().update( ns(), BSONObj(), BSON( "$set" << BSON( "z.0" << "a" ) ) );
+ out() << "one: " << client().findOne( ns(), Query() ) << endl;
+ ASSERT( client().findOne( ns(), Query() ).woCompare( fromjson( "{'_id':0,z:[4,'b']}" ) ) == 0 );
+ }
+ };
+
+ class AttemptEmbedInExistingNum : public SetBase {
+ public:
+ void run() {
+ client().insert( ns(), fromjson( "{'_id':0,a:1}" ) );
+ client().update( ns(), BSONObj(), BSON( "$set" << BSON( "a.b" << 1 ) ) );
+ out() << "one: " << client().findOne( ns(), Query() ) << endl;
+ ASSERT( client().findOne( ns(), Query() ).woCompare( fromjson( "{'_id':0,a:1}" ) ) == 0 );
+ }
+ };
+
class All : public UnitTest::Suite {
public:
All() {
@@ -244,6 +282,10 @@ namespace UpdateTests {
add< SetMissingDotted >();
add< SetAdjacentDotted >();
add< IncMissing >();
+ add< UnorderedNewSet >();
+ add< UnorderedNewSetAdjacent >();
+ add< ArrayEmbeddedSet >();
+ add< AttemptEmbedInExistingNum >();
}
};