From d3da0d7bbc6ace93df2eecdc3d5862b8e5cac154 Mon Sep 17 00:00:00 2001 From: Eliot Horowitz Date: Sat, 6 Mar 2010 22:12:58 -0500 Subject: [PATCH] array match value mod update( {a.x : 5 } , { a.~.y ... } ) SERVER-631 --- db/matcher.cpp | 44 ++++++++++++++-------- db/matcher.h | 17 ++++++--- db/update.cpp | 70 ++++++++++++++++++++++++++--------- db/update.h | 9 +++++ jstests/update_arraymatch1.js | 16 ++++++++ 5 files changed, 117 insertions(+), 39 deletions(-) create mode 100644 jstests/update_arraymatch1.js diff --git a/db/matcher.cpp b/db/matcher.cpp index 91fff7fcea1..dde6adcf7e9 100644 --- a/db/matcher.cpp +++ b/db/matcher.cpp @@ -29,8 +29,8 @@ namespace mongo { - //#include "minilex.h" - //MiniLex minilex; + //#define DEBUGMATCHER(x) cout << x << endl; +#define DEBUGMATCHER(x) class Where { public: @@ -162,7 +162,7 @@ namespace mongo { if ( details ) details->loadedObject = true; - return _docMatcher.matches(recLoc.rec()); + return _docMatcher.matches(recLoc.rec() , details ); } @@ -423,8 +423,8 @@ namespace mongo { return (op & z); } - int Matcher::matchesNe(const char *fieldName, const BSONElement &toMatch, const BSONObj &obj, const ElementMatcher& bm ) { - int ret = matchesDotted( fieldName, toMatch, obj, BSONObj::Equality, bm ); + int Matcher::matchesNe(const char *fieldName, const BSONElement &toMatch, const BSONObj &obj, const ElementMatcher& bm , MatchDetails * details ) { + int ret = matchesDotted( fieldName, toMatch, obj, BSONObj::Equality, bm , false , details ); if ( bm.toMatch.type() != jstNULL ) return ( ret <= 0 ) ? 1 : 0; else @@ -456,7 +456,8 @@ namespace mongo { 0 missing element 1 match */ - int Matcher::matchesDotted(const char *fieldName, const BSONElement& toMatch, const BSONObj& obj, int compareOp, const ElementMatcher& em , bool isArr) { + int Matcher::matchesDotted(const char *fieldName, const BSONElement& toMatch, const BSONObj& obj, int compareOp, const ElementMatcher& em , bool isArr, MatchDetails * details ) { + DEBUGMATCHER( "\t matchesDotted : " << fieldName << " hasDetails: " << ( details ? "yes" : "no" ) ); if ( compareOp == BSONObj::opALL ) { if ( em.allMatchers.size() ){ @@ -507,10 +508,10 @@ namespace mongo { } if ( compareOp == BSONObj::NE ) - return matchesNe( fieldName, toMatch, obj, em ); + return matchesNe( fieldName, toMatch, obj, em , details ); if ( compareOp == BSONObj::NIN ) { for( set::const_iterator i = em.myset->begin(); i != em.myset->end(); ++i ) { - int ret = matchesNe( fieldName, *i, obj, em ); + int ret = matchesNe( fieldName, *i, obj, em , details ); if ( ret != 1 ) return ret; } @@ -540,12 +541,12 @@ namespace mongo { ; else { BSONObj eo = se.embeddedObject(); - return matchesDotted(p+1, toMatch, eo, compareOp, em, se.type() == Array); + return matchesDotted(p+1, toMatch, eo, compareOp, em, se.type() == Array , details ); } } if ( isArr ) { - + DEBUGMATCHER( "\t\t isArr 1 : obj : " << obj ); BSONObjIterator ai(obj); bool found = false; while ( ai.moreWithEOO() ) { @@ -553,15 +554,20 @@ namespace mongo { if( strcmp(z.fieldName(),fieldName) == 0 && valuesMatch(z, toMatch, compareOp, em) ) { // "field." array notation was used + if ( details ) + details->elemMatchKey = z.fieldName(); return 1; } if ( z.type() == Object ) { BSONObj eo = z.embeddedObject(); - int cmp = matchesDotted(fieldName, toMatch, eo, compareOp, em, false); + int cmp = matchesDotted(fieldName, toMatch, eo, compareOp, em, false, details ); if ( cmp > 0 ) { + if ( details ) + details->elemMatchKey = z.fieldName(); return 1; - } else if ( cmp < 0 ) { + } + else if ( cmp < 0 ) { found = true; } } @@ -583,7 +589,7 @@ namespace mongo { valuesMatch(e, toMatch, compareOp, em ) ) { return 1; } else if ( e.type() == Array && compareOp != BSONObj::opSIZE ) { - + cout << "YES1" << endl; BSONObjIterator ai(e.embeddedObject()); while ( ai.moreWithEOO() ) { @@ -591,11 +597,17 @@ namespace mongo { if ( compareOp == BSONObj::opELEM_MATCH ){ // SERVER-377 - if ( z.type() == Object && em.subMatcher->matches( z.embeddedObject() ) ) + if ( z.type() == Object && em.subMatcher->matches( z.embeddedObject() ) ){ + if ( details ) + details->elemMatchKey = z.fieldName(); return 1; + } } else { if ( valuesMatch( z, toMatch, compareOp, em) ) { + cout << "YO : " << z << endl; + if ( details ) + details->elemMatchKey = z.fieldName(); return 1; } } @@ -634,7 +646,7 @@ namespace mongo { /* See if an object matches the query. */ - bool Matcher::matches(const BSONObj& jsobj ) { + bool Matcher::matches(const BSONObj& jsobj , MatchDetails * details ) { /* assuming there is usually only one thing to match. if more this could be slow sometimes. */ @@ -643,7 +655,7 @@ namespace mongo { ElementMatcher& bm = basics[i]; BSONElement& m = bm.toMatch; // -1=mismatch. 0=missing element. 1=match - int cmp = matchesDotted(m.fieldName(), m, jsobj, bm.compareOp, bm ); + int cmp = matchesDotted(m.fieldName(), m, jsobj, bm.compareOp, bm , false , details ); if ( bm.compareOp != BSONObj::opEXISTS && bm.isNot ) cmp = -cmp; if ( cmp < 0 ) diff --git a/db/matcher.h b/db/matcher.h index 72d6ec59c33..5f657de6230 100644 --- a/db/matcher.h +++ b/db/matcher.h @@ -91,11 +91,18 @@ namespace mongo { void reset(){ loadedObject = false; - elemMatchKey = BSONElement(); + elemMatchKey = 0; } + string toString() const { + stringstream ss; + ss << "loadedObject: " << loadedObject << " "; + ss << "elemMatchKey: " << ( elemMatchKey ? elemMatchKey : "NULL" ) << " "; + return ss.str(); + } + bool loadedObject; - BSONElement elemMatchKey; + const char * elemMatchKey; // warning, this may go out of scope if matched object does }; /* Match BSON objects against a query pattern. @@ -116,12 +123,12 @@ namespace mongo { int matchesDotted( const char *fieldName, const BSONElement& toMatch, const BSONObj& obj, - int compareOp, const ElementMatcher& bm, bool isArr = false); + int compareOp, const ElementMatcher& bm, bool isArr , MatchDetails * details ); int matchesNe( const char *fieldName, const BSONElement &toMatch, const BSONObj &obj, - const ElementMatcher&bm); + const ElementMatcher&bm, MatchDetails * details ); public: static int opDirection(int op) { @@ -134,7 +141,7 @@ namespace mongo { ~Matcher(); - bool matches(const BSONObj& j); + bool matches(const BSONObj& j, MatchDetails * details = 0 ); bool keyMatch() const { return !all && !haveSize && !hasArray && !haveNeg; } diff --git a/db/update.cpp b/db/update.cpp index 6737676fb54..cf1f1d7dd61 100644 --- a/db/update.cpp +++ b/db/update.cpp @@ -592,7 +592,7 @@ namespace mongo { const BSONObj &from , const set& idxKeys, const set *backgroundKeys) - : _isIndexed(0) { + : _isIndexed(0) , _hasDynamicArray( false ) { BSONObjIterator it(from); @@ -618,6 +618,8 @@ namespace mongo { uassert( 10152 , "Modifier $inc allowed for numbers only", f.isNumber() || op != Mod::INC ); uassert( 10153 , "Modifier $pushAll/pullAll allowed for arrays only", f.type() == Array || ( op != Mod::PUSH_ALL && op != Mod::PULL_ALL ) ); + _hasDynamicArray = _hasDynamicArray || strstr( fieldName , ".~" ) > 0; + Mod m; m.init( op , f ); m.setFieldName( f.fieldName() ); @@ -632,6 +634,27 @@ namespace mongo { } } + + ModSet * ModSet::fixDynamicArray( const char * elemMatchKey ) const { + ModSet * n = new ModSet(); + n->_isIndexed = _isIndexed; + n->_hasDynamicArray = _hasDynamicArray; + for ( ModHolder::const_iterator i=_mods.begin(); i!=_mods.end(); i++ ){ + string s = i->first; + size_t idx = s.find( ".~" ); + if ( idx == string::npos ){ + n->_mods[s] = i->second; + continue; + } + StringBuilder buf(s.size()+strlen(elemMatchKey)); + buf << s.substr(0,idx+1) << elemMatchKey << s.substr(idx+2); + string fixed = buf.str(); + n->_mods[fixed] = i->second; + ModHolder::iterator temp = n->_mods.find( fixed ); + temp->second.setFieldName( temp->first.c_str() ); + } + return n; + } void checkNoMods( BSONObj o ) { BSONObjIterator i( o ); @@ -645,40 +668,42 @@ namespace mongo { class UpdateOp : public QueryOp { public: - UpdateOp() : nscanned_() {} + UpdateOp() : _nscanned() {} virtual void init() { BSONObj pattern = qp().query(); - c_.reset( qp().newCursor().release() ); - if ( !c_->ok() ) + _c.reset( qp().newCursor().release() ); + if ( ! _c->ok() ) setComplete(); else - matcher_.reset( new CoveredIndexMatcher( pattern, qp().indexKey() ) ); + _matcher.reset( new CoveredIndexMatcher( pattern, qp().indexKey() ) ); } virtual void next() { - if ( !c_->ok() ) { + if ( ! _c->ok() ) { setComplete(); return; } - nscanned_++; - if ( matcher_->matches(c_->currKey(), c_->currLoc()) ) { + _nscanned++; + if ( _matcher->matches(_c->currKey(), _c->currLoc(), &_details ) ) { setComplete(); return; } - c_->advance(); + _c->advance(); } bool curMatches(){ - return matcher_->matches(c_->currKey(), c_->currLoc() ); + return _matcher->matches(_c->currKey(), _c->currLoc() , &_details ); } virtual bool mayRecordPlan() const { return false; } virtual QueryOp *clone() const { return new UpdateOp(); } - shared_ptr< Cursor > c() { return c_; } - long long nscanned() const { return nscanned_; } + shared_ptr< Cursor > c() { return _c; } + long long nscanned() const { return _nscanned; } + MatchDetails& getMatchDetails(){ return _details; } private: - shared_ptr< Cursor > c_; - long long nscanned_; - auto_ptr< CoveredIndexMatcher > matcher_; + shared_ptr< Cursor > _c; + long long _nscanned; + auto_ptr< CoveredIndexMatcher > _matcher; + MatchDetails _details; }; @@ -736,7 +761,7 @@ namespace mongo { c->advance(); continue; } - + BSONObj js(r); BSONObj pattern = patternOrig; @@ -763,7 +788,7 @@ namespace mongo { /* look for $inc etc. note as listed here, all fields to inc must be this type, you can't set some regular ones at the moment. */ if ( isOperatorUpdate ) { - + if ( multi ){ c->advance(); // go to next record in case this one moves if ( seenObjects.count( loc ) ) @@ -776,7 +801,16 @@ namespace mongo { const BSONObj& onDisk = loc.obj(); - auto_ptr mss = mods->prepare( onDisk ); + ModSet * useMods = mods.get(); + + auto_ptr mymodset; + if ( u->getMatchDetails().elemMatchKey && mods->hasDynamicArray() ){ + useMods = mods->fixDynamicArray( u->getMatchDetails().elemMatchKey ); + mymodset.reset( useMods ); + } + + + auto_ptr mss = useMods->prepare( onDisk ); if ( modsIsIndexed <= 0 && mss->canApplyInPlace() ){ mss->applyModsInPlace();// const_cast(onDisk) ); diff --git a/db/update.h b/db/update.h index bf49fc28b02..2101203dfac 100644 --- a/db/update.h +++ b/db/update.h @@ -153,6 +153,7 @@ namespace mongo { s.insert( i.next() ); } } + }; /** @@ -163,6 +164,7 @@ namespace mongo { typedef map ModHolder; ModHolder _mods; int _isIndexed; + bool _hasDynamicArray; static void extractFields( map< string, BSONElement > &fields, const BSONElement &top, const string &base ); @@ -255,6 +257,8 @@ namespace mongo { return Mod::INC; } + ModSet(){} + public: ModSet( const BSONObj &from , @@ -262,6 +266,11 @@ namespace mongo { const set* backgroundKeys = 0 ); + // TODO: this is inefficient - should probably just handle when iterating + ModSet * fixDynamicArray( const char * elemMatchKey ) const; + + bool hasDynamicArray() const { return _hasDynamicArray; } + /** * creates a ModSetState suitable for operation on obj * doesn't change or modify this ModSet or any underying Mod diff --git a/jstests/update_arraymatch1.js b/jstests/update_arraymatch1.js new file mode 100644 index 00000000000..7fd932cf2da --- /dev/null +++ b/jstests/update_arraymatch1.js @@ -0,0 +1,16 @@ + +t = db.update_arraymatch1 +t.drop(); + +o = { _id : 1 , a : [ { x : 1 , y : 1 } , { x : 2 , y : 2 } , { x : 3 , y : 3 } ] } +t.insert( o ); +assert.eq( o , t.findOne() , "A1" ); + +q = { "a.x" : 2 } +t.update( q , { $set : { b : 5 } } ) +o.b = 5 +assert.eq( o , t.findOne() , "A2" ) + +t.update( { "a.x" : 2 } , { $inc : { "a.~.y" : 1 } } ) +o.a[1].y++; +assert.eq( o , t.findOne() , "A3" );