mirror of
https://github.com/mongodb/mongo.git
synced 2024-12-01 09:32:32 +01:00
array match value mod update( {a.x : 5 } , { a.~.y ... } ) SERVER-631
This commit is contained in:
parent
da4507b7c6
commit
d3da0d7bbc
@ -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<BSONElement,element_lt>::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.<n>" 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 )
|
||||
|
17
db/matcher.h
17
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; }
|
||||
|
||||
|
@ -592,7 +592,7 @@ namespace mongo {
|
||||
const BSONObj &from ,
|
||||
const set<string>& idxKeys,
|
||||
const set<string> *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<ModSetState> mss = mods->prepare( onDisk );
|
||||
ModSet * useMods = mods.get();
|
||||
|
||||
auto_ptr<ModSet> mymodset;
|
||||
if ( u->getMatchDetails().elemMatchKey && mods->hasDynamicArray() ){
|
||||
useMods = mods->fixDynamicArray( u->getMatchDetails().elemMatchKey );
|
||||
mymodset.reset( useMods );
|
||||
}
|
||||
|
||||
|
||||
auto_ptr<ModSetState> mss = useMods->prepare( onDisk );
|
||||
|
||||
if ( modsIsIndexed <= 0 && mss->canApplyInPlace() ){
|
||||
mss->applyModsInPlace();// const_cast<BSONObj&>(onDisk) );
|
||||
|
@ -153,6 +153,7 @@ namespace mongo {
|
||||
s.insert( i.next() );
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
@ -163,6 +164,7 @@ namespace mongo {
|
||||
typedef map<string,Mod> 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<string>* 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
|
||||
|
16
jstests/update_arraymatch1.js
Normal file
16
jstests/update_arraymatch1.js
Normal file
@ -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" );
|
Loading…
Reference in New Issue
Block a user