// update.h
/**
* Copyright (C) 2008 10gen Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
#include "../pch.h"
#include "jsobj.h"
#include "../util/embedded_builder.h"
#include "matcher.h"
namespace mongo {
class ModState;
class ModSetState;
/* Used for modifiers such as $inc, $set, $push, ...
* stores the info about a single operation
* once created should never be modified
*/
struct Mod {
// See opFromStr below
// 0 1 2 3 4 5 6 7 8 9 10 11 12 13
enum Op { INC, SET, PUSH, PUSH_ALL, PULL, PULL_ALL , POP, UNSET, BITAND, BITOR , BIT , ADDTOSET, RENAME_FROM, RENAME_TO } op;
static const char* modNames[];
static unsigned modNamesNum;
const char *fieldName;
const char *shortFieldName;
BSONElement elt; // x:5 note: this is the actual element from the updateobj
boost::shared_ptr matcher;
bool matcherOnPrimitive;
void init( Op o , BSONElement& e ) {
op = o;
elt = e;
if ( op == PULL && e.type() == Object ) {
BSONObj t = e.embeddedObject();
if ( t.firstElement().getGtLtOp() == 0 ) {
matcher.reset( new Matcher( t ) );
matcherOnPrimitive = false;
}
else {
matcher.reset( new Matcher( BSON( "" << t ) ) );
matcherOnPrimitive = true;
}
}
}
void setFieldName( const char * s ) {
fieldName = s;
shortFieldName = strrchr( fieldName , '.' );
if ( shortFieldName )
shortFieldName++;
else
shortFieldName = fieldName;
}
/**
* @param in incrememnts the actual value inside in
*/
void incrementMe( BSONElement& in ) const {
BSONElementManipulator manip( in );
switch ( in.type() ) {
case NumberDouble:
manip.setNumber( elt.numberDouble() + in.numberDouble() );
break;
case NumberLong:
manip.setLong( elt.numberLong() + in.numberLong() );
break;
case NumberInt:
manip.setInt( elt.numberInt() + in.numberInt() );
break;
default:
assert(0);
}
}
void IncrementMe( BSONElement& in ) const {
BSONElementManipulator manip( in );
switch ( in.type() ) {
case NumberDouble:
manip.SetNumber( elt.numberDouble() + in.numberDouble() );
break;
case NumberLong:
manip.SetLong( elt.numberLong() + in.numberLong() );
break;
case NumberInt:
manip.SetInt( elt.numberInt() + in.numberInt() );
break;
default:
assert(0);
}
}
template< class Builder >
void appendIncremented( Builder& bb , const BSONElement& in, ModState& ms ) const;
bool operator<( const Mod &other ) const {
return strcmp( fieldName, other.fieldName ) < 0;
}
bool arrayDep() const {
switch (op) {
case PUSH:
case PUSH_ALL:
case POP:
return true;
default:
return false;
}
}
static bool isIndexed( const string& fullName , const set& idxKeys ) {
const char * fieldName = fullName.c_str();
// check if there is an index key that is a parent of mod
for( const char *dot = strchr( fieldName, '.' ); dot; dot = strchr( dot + 1, '.' ) )
if ( idxKeys.count( string( fieldName, dot - fieldName ) ) )
return true;
// check if there is an index key equal to mod
if ( idxKeys.count(fullName) )
return true;
// check if there is an index key that is a child of mod
set< string >::const_iterator j = idxKeys.upper_bound( fullName );
if ( j != idxKeys.end() && j->find( fullName ) == 0 && (*j)[fullName.size()] == '.' )
return true;
return false;
}
bool isIndexed( const set& idxKeys ) const {
string fullName = fieldName;
if ( isIndexed( fullName , idxKeys ) )
return true;
if ( strstr( fieldName , "." ) ) {
// check for a.0.1
StringBuilder buf( fullName.size() + 1 );
for ( size_t i=0; i 0 && fullName[i-1] == '.' &&
i+1
void apply( Builder& b , BSONElement in , ModState& ms ) const;
/**
* @return true iff toMatch should be removed from the array
*/
bool _pullElementMatch( BSONElement& toMatch ) const;
void _checkForAppending( const BSONElement& e ) const {
if ( e.type() == Object ) {
// this is a tiny bit slow, but rare and important
// only when setting something TO an object, not setting something in an object
// and it checks for { $set : { x : { 'a.b' : 1 } } }
// which is feel has been common
uassert( 12527 , "not okForStorage" , e.embeddedObject().okForStorage() );
}
}
bool isEach() const {
if ( elt.type() != Object )
return false;
BSONElement e = elt.embeddedObject().firstElement();
if ( e.type() != Array )
return false;
return strcmp( e.fieldName() , "$each" ) == 0;
}
BSONObj getEach() const {
return elt.embeddedObjectUserCheck().firstElement().embeddedObjectUserCheck();
}
void parseEach( BSONElementSet& s ) const {
BSONObjIterator i(getEach());
while ( i.more() ) {
s.insert( i.next() );
}
}
const char *renameFrom() const {
massert( 13492, "mod must be RENAME_TO type", op == Mod::RENAME_TO );
return elt.fieldName();
}
};
/**
* stores a set of Mods
* once created, should never be changed
*/
class ModSet : boost::noncopyable {
typedef map ModHolder;
ModHolder _mods;
int _isIndexed;
bool _hasDynamicArray;
static void extractFields( map< string, BSONElement > &fields, const BSONElement &top, const string &base );
FieldCompareResult compare( const ModHolder::iterator &m, map< string, BSONElement >::iterator &p, const map< string, BSONElement >::iterator &pEnd ) const {
bool mDone = ( m == _mods.end() );
bool pDone = ( p == pEnd );
assert( ! mDone );
assert( ! pDone );
if ( mDone && pDone )
return SAME;
// If one iterator is done we want to read from the other one, so say the other one is lower.
if ( mDone )
return RIGHT_BEFORE;
if ( pDone )
return LEFT_BEFORE;
return compareDottedFieldNames( m->first, 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;
if ( haveModForField( left.c_str() ) )
return false;
}
return true;
}
static Mod::Op opFromStr( const char *fn ) {
assert( fn[0] == '$' );
switch( fn[1] ) {
case 'i': {
if ( fn[2] == 'n' && fn[3] == 'c' && fn[4] == 0 )
return Mod::INC;
break;
}
case 's': {
if ( fn[2] == 'e' && fn[3] == 't' && fn[4] == 0 )
return Mod::SET;
break;
}
case 'p': {
if ( fn[2] == 'u' ) {
if ( fn[3] == 's' && fn[4] == 'h' ) {
if ( fn[5] == 0 )
return Mod::PUSH;
if ( fn[5] == 'A' && fn[6] == 'l' && fn[7] == 'l' && fn[8] == 0 )
return Mod::PUSH_ALL;
}
else if ( fn[3] == 'l' && fn[4] == 'l' ) {
if ( fn[5] == 0 )
return Mod::PULL;
if ( fn[5] == 'A' && fn[6] == 'l' && fn[7] == 'l' && fn[8] == 0 )
return Mod::PULL_ALL;
}
}
else if ( fn[2] == 'o' && fn[3] == 'p' && fn[4] == 0 )
return Mod::POP;
break;
}
case 'u': {
if ( fn[2] == 'n' && fn[3] == 's' && fn[4] == 'e' && fn[5] == 't' && fn[6] == 0 )
return Mod::UNSET;
break;
}
case 'b': {
if ( fn[2] == 'i' && fn[3] == 't' ) {
if ( fn[4] == 0 )
return Mod::BIT;
if ( fn[4] == 'a' && fn[5] == 'n' && fn[6] == 'd' && fn[7] == 0 )
return Mod::BITAND;
if ( fn[4] == 'o' && fn[5] == 'r' && fn[6] == 0 )
return Mod::BITOR;
}
break;
}
case 'a': {
if ( fn[2] == 'd' && fn[3] == 'd' ) {
// add
if ( fn[4] == 'T' && fn[5] == 'o' && fn[6] == 'S' && fn[7] == 'e' && fn[8] == 't' && fn[9] == 0 )
return Mod::ADDTOSET;
}
break;
}
case 'r': {
if ( fn[2] == 'e' && fn[3] == 'n' && fn[4] == 'a' && fn[5] == 'm' && fn[6] =='e' ) {
return Mod::RENAME_TO; // with this return code we handle both RENAME_TO and RENAME_FROM
}
break;
}
default: break;
}
uassert( 10161 , "Invalid modifier specified " + string( fn ), false );
return Mod::INC;
}
ModSet() {}
void updateIsIndexed( const Mod &m, const set &idxKeys, const set *backgroundKeys ) {
if ( m.isIndexed( idxKeys ) ||
(backgroundKeys && m.isIndexed(*backgroundKeys)) ) {
_isIndexed++;
}
}
public:
ModSet( const BSONObj &from ,
const set& idxKeys = set(),
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
*/
auto_ptr prepare( const BSONObj& obj ) const;
/**
* given a query pattern, builds an object suitable for an upsert
* will take the query spec and combine all $ operators
*/
BSONObj createNewFromQuery( const BSONObj& query );
/**
*
*/
int isIndexed() const {
return _isIndexed;
}
unsigned size() const { return _mods.size(); }
bool haveModForField( const char *fieldName ) const {
return _mods.find( fieldName ) != _mods.end();
}
bool haveConflictingMod( const string& fieldName ) {
size_t idx = fieldName.find( '.' );
if ( idx == string::npos )
idx = fieldName.size();
ModHolder::const_iterator start = _mods.lower_bound(fieldName.substr(0,idx));
for ( ; start != _mods.end(); start++ ) {
FieldCompareResult r = compareDottedFieldNames( fieldName , start->first );
switch ( r ) {
case LEFT_SUBFIELD: return true;
case LEFT_BEFORE: return false;
case SAME: return true;
case RIGHT_BEFORE: return false;
case RIGHT_SUBFIELD: return true;
}
}
return false;
}
};
/**
* stores any information about a single Mod operating on a single Object
*/
class ModState {
public:
const Mod * m;
BSONElement old;
BSONElement newVal;
BSONObj _objData;
const char * fixedOpName;
BSONElement * fixed;
int pushStartSize;
BSONType incType;
int incint;
double incdouble;
long long inclong;
bool dontApply;
ModState() {
fixedOpName = 0;
fixed = 0;
pushStartSize = -1;
incType = EOO;
dontApply = false;
}
Mod::Op op() const {
return m->op;
}
const char * fieldName() const {
return m->fieldName;
}
bool needOpLogRewrite() const {
if ( dontApply )
return false;
if ( fixed || fixedOpName || incType )
return true;
switch( op() ) {
case Mod::RENAME_FROM:
case Mod::RENAME_TO:
return true;
case Mod::BIT:
case Mod::BITAND:
case Mod::BITOR:
// TODO: should we convert this to $set?
return false;
default:
return false;
}
}
void appendForOpLog( BSONObjBuilder& b ) const;
template< class Builder >
void apply( Builder& b , BSONElement in ) {
m->apply( b , in , *this );
}
template< class Builder >
void appendIncValue( Builder& b , bool useFullName ) const {
const char * n = useFullName ? m->fieldName : m->shortFieldName;
switch ( incType ) {
case NumberDouble:
b.append( n , incdouble ); break;
case NumberLong:
b.append( n , inclong ); break;
case NumberInt:
b.append( n , incint ); break;
default:
assert(0);
}
}
string toString() const;
template< class Builder >
void handleRename( Builder &newObjBuilder, const char *shortFieldName );
};
/**
* this is used to hold state, meta data while applying a ModSet to a BSONObj
* the goal is to make ModSet const so its re-usable
*/
class ModSetState : boost::noncopyable {
struct FieldCmp {
bool operator()( const string &l, const string &r ) const {
return lexNumCmp( l.c_str(), r.c_str() ) < 0;
}
};
typedef map ModStateHolder;
const BSONObj& _obj;
ModStateHolder _mods;
bool _inPlacePossible;
BSONObj _newFromMods; // keep this data alive, as oplog generation may depend on it
ModSetState( const BSONObj& obj )
: _obj( obj ) , _inPlacePossible(true) {
}
/**
* @return if in place is still possible
*/
bool amIInPlacePossible( bool inPlacePossible ) {
if ( ! inPlacePossible )
_inPlacePossible = false;
return _inPlacePossible;
}
template< class Builder >
void createNewFromMods( const string& root , Builder& b , const BSONObj &obj );
template< class Builder >
void _appendNewFromMods( const string& root , ModState& m , Builder& b , set& onedownseen );
template< class Builder >
void appendNewFromMod( ModState& ms , Builder& b ) {
if ( ms.dontApply ) {
return;
}
//const Mod& m = *(ms.m); // HACK
Mod& m = *((Mod*)(ms.m)); // HACK
switch ( m.op ) {
case Mod::PUSH:
case Mod::ADDTOSET: {
if ( m.isEach() ) {
b.appendArray( m.shortFieldName , m.getEach() );
}
else {
BSONObjBuilder arr( b.subarrayStart( m.shortFieldName ) );
arr.appendAs( m.elt, "0" );
arr.done();
}
break;
}
case Mod::PUSH_ALL: {
b.appendAs( m.elt, m.shortFieldName );
break;
}
case Mod::UNSET:
case Mod::PULL:
case Mod::PULL_ALL:
// no-op b/c unset/pull of nothing does nothing
break;
case Mod::INC:
ms.fixedOpName = "$set";
case Mod::SET: {
m._checkForAppending( m.elt );
b.appendAs( m.elt, m.shortFieldName );
break;
}
// shouldn't see RENAME_FROM here
case Mod::RENAME_TO:
ms.handleRename( b, m.shortFieldName );
break;
default:
stringstream ss;
ss << "unknown mod in appendNewFromMod: " << m.op;
throw UserException( 9015, ss.str() );
}
}
public:
bool canApplyInPlace() const {
return _inPlacePossible;
}
/**
* modified underlying _obj
* @param isOnDisk - true means this is an on disk object, and this update needs to be made durable
*/
void applyModsInPlace( bool isOnDisk );
BSONObj createNewFromMods();
// re-writing for oplog
bool needOpLogRewrite() const {
for ( ModStateHolder::const_iterator i = _mods.begin(); i != _mods.end(); i++ )
if ( i->second.needOpLogRewrite() )
return true;
return false;
}
BSONObj getOpLogRewrite() const {
BSONObjBuilder b;
for ( ModStateHolder::const_iterator i = _mods.begin(); i != _mods.end(); i++ )
i->second.appendForOpLog( b );
return b.obj();
}
bool haveArrayDepMod() const {
for ( ModStateHolder::const_iterator i = _mods.begin(); i != _mods.end(); i++ )
if ( i->second.m->arrayDep() )
return true;
return false;
}
void appendSizeSpecForArrayDepMods( BSONObjBuilder &b ) const {
for ( ModStateHolder::const_iterator i = _mods.begin(); i != _mods.end(); i++ ) {
const ModState& m = i->second;
if ( m.m->arrayDep() ) {
if ( m.pushStartSize == -1 )
b.appendNull( m.fieldName() );
else
b << m.fieldName() << BSON( "$size" << m.pushStartSize );
}
}
}
string toString() const;
friend class ModSet;
};
}