// updatetests.cpp : unit tests relating to update requests // /** * 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 "mongo/pch.h" #include "mongo/client/dbclientcursor.h" #include "mongo/db/db.h" #include "mongo/db/instance.h" #include "mongo/db/json.h" #include "mongo/db/lasterror.h" #include "mongo/db/ops/update.h" #include "mongo/db/ops/update_internal.h" #include "mongo/dbtests/dbtests.h" namespace UpdateTests { class ClientBase { public: // NOTE: Not bothering to backup the old error record. ClientBase() { mongo::lastError.reset( new LastError() ); } ~ClientBase() { mongo::lastError.release(); } protected: static void insert( const char *ns, BSONObj o ) { client_.insert( ns, o ); } static void update( const char *ns, BSONObj q, BSONObj o, bool upsert = 0 ) { client_.update( ns, Query( q ), o, upsert ); } static bool error() { return !client_.getPrevError().getField( "err" ).isNull(); } DBDirectClient &client() const { return client_; } private: static DBDirectClient client_; }; DBDirectClient ClientBase::client_; class Fail : public ClientBase { public: virtual ~Fail() {} void run() { prep(); ASSERT( !error() ); doIt(); ASSERT( error() ); } protected: const char *ns() { return "unittests.UpdateTests_Fail"; } virtual void prep() { insert( ns(), fromjson( "{a:1}" ) ); } virtual void doIt() = 0; }; class ModId : public Fail { void doIt() { update( ns(), BSONObj(), fromjson( "{$set:{'_id':4}}" ) ); } }; class ModNonmodMix : public Fail { void doIt() { update( ns(), BSONObj(), fromjson( "{$set:{a:4},z:3}" ) ); } }; class InvalidMod : public Fail { void doIt() { update( ns(), BSONObj(), fromjson( "{$awk:{a:4}}" ) ); } }; class ModNotFirst : public Fail { void doIt() { update( ns(), BSONObj(), fromjson( "{z:3,$set:{a:4}}" ) ); } }; class ModDuplicateFieldSpec : public Fail { void doIt() { update( ns(), BSONObj(), fromjson( "{$set:{a:4},$inc:{a:1}}" ) ); } }; class IncNonNumber : public Fail { void doIt() { update( ns(), BSONObj(), fromjson( "{$inc:{a:'d'}}" ) ); } }; class PushAllNonArray : public Fail { void doIt() { insert( ns(), fromjson( "{a:[1]}" ) ); update( ns(), BSONObj(), fromjson( "{$pushAll:{a:'d'}}" ) ); } }; class PullAllNonArray : public Fail { void doIt() { insert( ns(), fromjson( "{a:[1]}" ) ); update( ns(), BSONObj(), fromjson( "{$pullAll:{a:'d'}}" ) ); } }; class IncTargetNonNumber : public Fail { void doIt() { insert( ns(), BSON( "a" << "a" ) ); update( ns(), BSON( "a" << "a" ), fromjson( "{$inc:{a:1}}" ) ); } }; class SetBase : public ClientBase { public: ~SetBase() { client().dropCollection( ns() ); } protected: const char *ns() { return "unittests.updatetests.SetBase"; } }; class SetNum : public SetBase { public: void run() { client().insert( ns(), BSON( "a" << 1 ) ); client().update( ns(), BSON( "a" << 1 ), BSON( "$set" << BSON( "a" << 4 ) ) ); ASSERT( !client().findOne( ns(), BSON( "a" << 4 ) ).isEmpty() ); } }; class SetString : public SetBase { public: void run() { client().insert( ns(), BSON( "a" << "b" ) ); client().update( ns(), BSON( "a" << "b" ), BSON( "$set" << BSON( "a" << "c" ) ) ); ASSERT( !client().findOne( ns(), BSON( "a" << "c" ) ).isEmpty() ); } }; class SetStringDifferentLength : public SetBase { public: void run() { client().insert( ns(), BSON( "a" << "b" ) ); client().update( ns(), BSON( "a" << "b" ), BSON( "$set" << BSON( "a" << "cd" ) ) ); ASSERT( !client().findOne( ns(), BSON( "a" << "cd" ) ).isEmpty() ); } }; class SetStringToNum : public SetBase { public: void run() { client().insert( ns(), BSON( "a" << "b" ) ); client().update( ns(), Query(), BSON( "$set" << BSON( "a" << 5 ) ) ); ASSERT( !client().findOne( ns(), BSON( "a" << 5 ) ).isEmpty() ); } }; class SetStringToNumInPlace : public SetBase { public: void run() { client().insert( ns(), BSON( "a" << "bcd" ) ); client().update( ns(), Query(), BSON( "$set" << BSON( "a" << 5.0 ) ) ); ASSERT( !client().findOne( ns(), BSON( "a" << 5.0 ) ).isEmpty() ); } }; class SetOnInsertFromEmpty : public SetBase { public: void run() { // Try with upsert false first. client().insert( ns(), BSONObj() /* empty document */); client().update( ns(), Query(), BSON( "$setOnInsert" << BSON( "a" << 1 ) ), false ); ASSERT( client().findOne( ns(), BSON( "a" << 1 ) ).isEmpty() ); // Then with upsert true. client().update( ns(), Query(), BSON( "$setOnInsert" << BSON( "a" << 1 ) ), true ); ASSERT( client().findOne( ns(), BSON( "a" << 1 ) ).isEmpty() ); } }; class SetOnInsertFromNonExistent : public SetBase { public: void run() { // Try with upsert false first. client().update( ns(), Query(), BSON( "$setOnInsert" << BSON( "a" << 1 ) ), false ); ASSERT( client().findOne( ns(), BSON( "a" << 1 ) ).isEmpty() ); // Then with upsert true. client().update( ns(), Query(), BSON( "$setOnInsert" << BSON( "a" << 1 ) ), true ); ASSERT( !client().findOne( ns(), BSON( "a" << 1 ) ).isEmpty() ); } }; class SetOnInsertFromNonExistentWithQuery : public SetBase { public: void run() { Query q("{a:1}"); // Try with upsert false first. client().update( ns(), q, BSON( "$setOnInsert" << BSON( "b" << 1 ) ), false ); ASSERT( client().findOne( ns(), BSON( "a" << 1 ) ).isEmpty() ); // Then with upsert true. client().update( ns(), q, BSON( "$setOnInsert" << BSON( "b" << 1 ) ), true ); ASSERT( !client().findOne( ns(), BSON( "a" << 1 << "b" << 1) ).isEmpty() ); } }; class SetOnInsertFromNonExistentWithQueryOverField : public SetBase { public: void run() { Query q("{a:1}"); // same field that we'll setOnInsert on // Try with upsert false first. client().update( ns(), q, BSON( "$setOnInsert" << BSON( "a" << 2 ) ), false ); ASSERT( client().findOne( ns(), BSON( "a" << 1 ) ).isEmpty() ); // Then with upsert true. client().update( ns(), q, BSON( "$setOnInsert" << BSON( "a" << 2 ) ), true ); ASSERT( !client().findOne( ns(), BSON( "a" << 2 ) ).isEmpty() ); } }; class SetOnInsertMissingField : public SetBase { public: void run() { BSONObj res = fromjson("{'_id':0, a:1}"); client().insert( ns(), res ); client().update( ns(), Query(), BSON( "$setOnInsert" << BSON( "b" << 1 ) ) ); ASSERT( client().findOne( ns(), BSON( "a" << 1 ) ).woCompare( res ) == 0 ); } }; class SetOnInsertExisting : public SetBase { public: void run() { client().insert( ns(), BSON( "a" << 1 ) ); client().update( ns(), Query(), BSON( "$setOnInsert" << BSON( "a" << 2 ) ) ); ASSERT( !client().findOne( ns(), BSON( "a" << 1 ) ).isEmpty() ); } }; class SetOnInsertMixed : public SetBase { public: void run() { // Try with upsert false first. client().update( ns(), Query(), BSON( "$set" << BSON( "a" << 1 ) << "$setOnInsert" << BSON( "b" << 2 ) ), false ); ASSERT( client().findOne( ns(), BSON( "a" << 1 << "b" << 2 ) ).isEmpty() ); // Then with upsert true. client().update( ns(), Query(), BSON( "$set" << BSON( "a" << 1 ) << "$setOnInsert" << BSON( "b" << 2 ) ), true ); ASSERT( !client().findOne( ns(), BSON( "a" << 1 << "b" << 2 ) ).isEmpty() ); } }; class SetOnInsertMissingParent : public SetBase { public: void run() { // In a mod that uses dontApply, we should be careful not to create a // parent unneccesarily. BSONObj initial = fromjson( "{'_id':0}" ); BSONObj final = fromjson( "{'_id':0, d:1}" ); client().insert( ns(), initial ); client().update( ns(), initial, BSON( "$setOnInsert" << BSON( "a.b" << 1 ) << "$set" << BSON( "d" << 1 ) ) ); ASSERT_EQUALS( client().findOne( ns(), initial ), final ); } }; class ModDotted : public SetBase { public: void run() { client().insert( ns(), fromjson( "{a:{b:4}}" ) ); client().update( ns(), Query(), BSON( "$inc" << BSON( "a.b" << 10 ) ) ); ASSERT( !client().findOne( ns(), BSON( "a.b" << 14 ) ).isEmpty() ); client().update( ns(), Query(), BSON( "$set" << BSON( "a.b" << 55 ) ) ); ASSERT( !client().findOne( ns(), BSON( "a.b" << 55 ) ).isEmpty() ); } }; class SetInPlaceDotted : public SetBase { public: void run() { client().insert( ns(), fromjson( "{a:{b:'cdef'}}" ) ); client().update( ns(), Query(), BSON( "$set" << BSON( "a.b" << "llll" ) ) ); ASSERT( !client().findOne( ns(), BSON( "a.b" << "llll" ) ).isEmpty() ); } }; class SetRecreateDotted : public SetBase { public: void run() { client().insert( ns(), fromjson( "{'_id':0,a:{b:'cdef'}}" ) ); client().update( ns(), Query(), BSON( "$set" << BSON( "a.b" << "lllll" ) ) ); ASSERT( client().findOne( ns(), BSON( "a.b" << "lllll" ) ).woCompare( fromjson( "{'_id':0,a:{b:'lllll'}}" ) ) == 0 ); } }; class SetMissingDotted : public SetBase { public: void run() { client().insert( ns(), fromjson( "{'_id':0}" ) ); client().update( ns(), BSONObj(), BSON( "$set" << BSON( "a.b" << "lllll" ) ) ); ASSERT( client().findOne( ns(), BSON( "a.b" << "lllll" ) ).woCompare( fromjson( "{'_id':0,a:{b:'lllll'}}" ) ) == 0 ); } }; class SetAdjacentDotted : public SetBase { public: void run() { client().insert( ns(), fromjson( "{'_id':0,a:{c:4}}" ) ); client().update( ns(), Query(), BSON( "$set" << BSON( "a.b" << "lllll" ) ) ); ASSERT_EQUALS( client().findOne( ns(), BSON( "a.b" << "lllll" ) ) , fromjson( "{'_id':0,a:{b:'lllll',c:4}}" ) ); } }; class IncMissing : public SetBase { public: void run() { client().insert( ns(), fromjson( "{'_id':0}" ) ); client().update( ns(), Query(), BSON( "$inc" << BSON( "f" << 3.0 ) ) ); ASSERT( client().findOne( ns(), Query() ).woCompare( fromjson( "{'_id':0,f:3}" ) ) == 0 ); } }; class MultiInc : public SetBase { public: string s() { stringstream ss; auto_ptr cc = client().query( ns() , Query().sort( BSON( "_id" << 1 ) ) ); bool first = true; while ( cc->more() ) { if ( first ) first = false; else ss << ","; BSONObj o = cc->next(); ss << o["x"].numberInt(); } return ss.str(); } void run() { client().insert( ns(), BSON( "_id" << 1 << "x" << 1 ) ); client().insert( ns(), BSON( "_id" << 2 << "x" << 5 ) ); ASSERT_EQUALS( "1,5" , s() ); client().update( ns() , BSON( "_id" << 1 ) , BSON( "$inc" << BSON( "x" << 1 ) ) ); ASSERT_EQUALS( "2,5" , s() ); client().update( ns() , BSONObj() , BSON( "$inc" << BSON( "x" << 1 ) ) ); ASSERT_EQUALS( "3,5" , s() ); client().update( ns() , BSONObj() , BSON( "$inc" << BSON( "x" << 1 ) ) , false , true ); ASSERT_EQUALS( "4,6" , s() ); } }; class UnorderedNewSet : public SetBase { public: void run() { client().insert( ns(), fromjson( "{'_id':0}" ) ); client().update( ns(), Query(), 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(), Query(), BSON( "$set" << BSON( "z.0" << "a" ) ) ); ASSERT_EQUALS( client().findOne( ns(), Query() ) , fromjson( "{'_id':0,z:['a','b']}" ) ); } }; class AttemptEmbedInExistingNum : public SetBase { public: void run() { client().insert( ns(), fromjson( "{'_id':0,a:1}" ) ); client().update( ns(), Query(), BSON( "$set" << BSON( "a.b" << 1 ) ) ); ASSERT( client().findOne( ns(), Query() ).woCompare( fromjson( "{'_id':0,a:1}" ) ) == 0 ); } }; class AttemptEmbedConflictsWithOtherSet : public SetBase { public: void run() { client().insert( ns(), fromjson( "{'_id':0}" ) ); client().update( ns(), Query(), BSON( "$set" << BSON( "a" << 2 << "a.b" << 1 ) ) ); ASSERT_EQUALS( client().findOne( ns(), Query() ) , fromjson( "{'_id':0}" ) ); } }; class ModMasksEmbeddedConflict : public SetBase { public: void run() { client().insert( ns(), fromjson( "{'_id':0,a:{b:2}}" ) ); client().update( ns(), Query(), BSON( "$set" << BSON( "a" << 2 << "a.b" << 1 ) ) ); ASSERT( client().findOne( ns(), Query() ).woCompare( fromjson( "{'_id':0,a:{b:2}}" ) ) == 0 ); } }; class ModOverwritesExistingObject : public SetBase { public: void run() { client().insert( ns(), fromjson( "{'_id':0,a:{b:2}}" ) ); client().update( ns(), Query(), BSON( "$set" << BSON( "a" << BSON( "c" << 2 ) ) ) ); ASSERT( client().findOne( ns(), Query() ).woCompare( fromjson( "{'_id':0,a:{c:2}}" ) ) == 0 ); } }; class InvalidEmbeddedSet : public Fail { public: virtual void doIt() { client().update( ns(), Query(), BSON( "$set" << BSON( "a." << 1 ) ) ); } }; class UpsertMissingEmbedded : public SetBase { public: void run() { client().update( ns(), Query(), BSON( "$set" << BSON( "a.b" << 1 ) ), true ); ASSERT( !client().findOne( ns(), QUERY( "a.b" << 1 ) ).isEmpty() ); } }; class Push : public SetBase { public: void run() { client().insert( ns(), fromjson( "{'_id':0,a:[1]}" ) ); client().update( ns(), Query(), BSON( "$push" << BSON( "a" << 5 ) ) ); ASSERT_EQUALS( client().findOne( ns(), Query() ) , fromjson( "{'_id':0,a:[1,5]}" ) ); } }; class PushInvalidEltType : public SetBase { public: void run() { client().insert( ns(), fromjson( "{'_id':0,a:1}" ) ); client().update( ns(), Query(), BSON( "$push" << BSON( "a" << 5 ) ) ); ASSERT( client().findOne( ns(), Query() ).woCompare( fromjson( "{'_id':0,a:1}" ) ) == 0 ); } }; class PushConflictsWithOtherMod : public SetBase { public: void run() { client().insert( ns(), fromjson( "{'_id':0,a:[1]}" ) ); client().update( ns(), Query(), BSON( "$set" << BSON( "a" << 1 ) <<"$push" << BSON( "a" << 5 ) ) ); ASSERT( client().findOne( ns(), Query() ).woCompare( fromjson( "{'_id':0,a:[1]}" ) ) == 0 ); } }; class PushFromNothing : public SetBase { public: void run() { client().insert( ns(), fromjson( "{'_id':0}" ) ); client().update( ns(), Query(), BSON( "$push" << BSON( "a" << 5 ) ) ); ASSERT_EQUALS( client().findOne( ns(), Query() ) , fromjson( "{'_id':0,a:[5]}" ) ); } }; class PushFromEmpty : public SetBase { public: void run() { client().insert( ns(), fromjson( "{'_id':0,a:[]}" ) ); client().update( ns(), Query(), BSON( "$push" << BSON( "a" << 5 ) ) ); ASSERT( client().findOne( ns(), Query() ).woCompare( fromjson( "{'_id':0,a:[5]}" ) ) == 0 ); } }; class PushInsideNothing : public SetBase { public: void run() { client().insert( ns(), fromjson( "{'_id':0}" ) ); client().update( ns(), Query(), BSON( "$push" << BSON( "a.b" << 5 ) ) ); ASSERT( client().findOne( ns(), Query() ).woCompare( fromjson( "{'_id':0,a:{b:[5]}}" ) ) == 0 ); } }; class CantPushInsideOtherMod : public SetBase { public: void run() { client().insert( ns(), fromjson( "{'_id':0}" ) ); client().update( ns(), Query(), BSON( "$set" << BSON( "a" << BSONObj() ) << "$push" << BSON( "a.b" << 5 ) ) ); ASSERT( client().findOne( ns(), Query() ).woCompare( fromjson( "{'_id':0}" ) ) == 0 ); } }; class CantPushTwice : public SetBase { public: void run() { client().insert( ns(), fromjson( "{'_id':0,a:[]}" ) ); client().update( ns(), Query(), BSON( "$push" << BSON( "a" << 4 ) << "$push" << BSON( "a" << 5 ) ) ); ASSERT( client().findOne( ns(), Query() ).woCompare( fromjson( "{'_id':0,a:[]}" ) ) == 0 ); } }; class SetEncapsulationConflictsWithExistingType : public SetBase { public: void run() { client().insert( ns(), fromjson( "{'_id':0,a:{b:4}}" ) ); client().update( ns(), Query(), BSON( "$set" << BSON( "a.b.c" << 4.0 ) ) ); ASSERT( client().findOne( ns(), Query() ).woCompare( fromjson( "{'_id':0,a:{b:4}}" ) ) == 0 ); } }; class CantPushToParent : public SetBase { public: void run() { client().insert( ns(), fromjson( "{'_id':0,a:{b:4}}" ) ); client().update( ns(), Query(), BSON( "$push" << BSON( "a" << 4.0 ) ) ); ASSERT( client().findOne( ns(), Query() ).woCompare( fromjson( "{'_id':0,a:{b:4}}" ) ) == 0 ); } }; class PushEachSimple : public SetBase { public: void run() { client().insert( ns(), fromjson( "{'_id':0,a:[1]}" ) ); // { $push : { a : { $each : [ 2, 3 ] } } } BSONObj pushObj = BSON( "$each" << BSON_ARRAY( 2 << 3 ) ); client().update( ns(), Query(), BSON( "$push" << BSON( "a" << pushObj ) ) ); ASSERT_EQUALS( client().findOne( ns(), Query() ) , fromjson( "{'_id':0,a:[1,2,3]}" ) ); } }; class PushEachFromEmpty : public SetBase { public: void run() { client().insert( ns(), fromjson( "{'_id':0,a:[]}" ) ); // { $push : { a : { $each : [ 1, 2, 3 ] } } } BSONObj pushObj = BSON( "$each" << BSON_ARRAY( 1 << 2 << 3 ) ); client().update( ns(), Query(), BSON( "$push" << BSON( "a" << pushObj ) ) ); ASSERT_EQUALS( client().findOne( ns(), Query() ) , fromjson( "{'_id':0,a:[1,2,3]}" ) ); } }; class PushSliceBelowFull : public SetBase { public: void run() { client().insert( ns(), fromjson( "{'_id':0,a:[1]}" ) ); // { $push : { a : { $each : [ 2 ] , $slice : -3 } } } BSONObj pushObj = BSON( "$each" << BSON_ARRAY( 2 ) << "$slice" << -3 ); client().update( ns(), Query(), BSON( "$push" << BSON( "a" << pushObj ) ) ); ASSERT_EQUALS( client().findOne( ns(), Query() ) , fromjson( "{'_id':0,a:[1,2]}" ) ); } }; class PushSliceReachedFullExact : public SetBase { public: void run() { client().insert( ns(), fromjson( "{'_id':0,a:[1]}" ) ); // { $push : { a : { $each : [ 2 ] , $slice : -2 } } } BSONObj pushObj = BSON( "$each" << BSON_ARRAY( 2 ) << "$slice" << -2 ); client().update( ns(), Query(), BSON( "$push" << BSON( "a" << pushObj ) ) ); ASSERT_EQUALS( client().findOne( ns(), Query() ) , fromjson( "{'_id':0,a:[1,2]}" ) ); } }; class PushSliceReachedFullWithEach : public SetBase { public: void run() { client().insert( ns(), fromjson( "{'_id':0,a:[1]}" ) ); // { $push : { a : { $each : [ 2 , 3 ] , $slice : -2 } } } BSONObj pushObj = BSON( "$each" << BSON_ARRAY( 2 << 3 ) << "$slice" << -2 ); client().update( ns(), Query(), BSON( "$push" << BSON( "a" << pushObj ) ) ); ASSERT_EQUALS( client().findOne( ns(), Query() ) , fromjson( "{'_id':0,a:[2,3]}" ) ); } }; class PushSliceReachedFullWithBoth : public SetBase { public: void run() { client().insert( ns(), fromjson( "{'_id':0,a:[1,2]}" ) ); // { $push : { a : { $each : [ 3 ] , $slice : -2 } } } BSONObj pushObj = BSON( "$each" << BSON_ARRAY( 3 ) << "$slice" << -2 ); client().update( ns(), Query(), BSON( "$push" << BSON( "a" << pushObj ) ) ); ASSERT_EQUALS( client().findOne( ns(), Query() ) , fromjson( "{'_id':0,a:[2,3]}" ) ); } }; class PushSliceToZero : public SetBase { public: void run() { client().insert( ns(), fromjson( "{'_id':0,a:[1,2]}" ) ); // { $push : { a : { $each : [ 3 ] , $slice : 0 } } } BSONObj pushObj = BSON( "$each" << BSON_ARRAY( 3 ) << "$slice" << 0 ); client().update( ns(), Query(), BSON( "$push" << BSON( "a" << pushObj ) ) ); ASSERT_EQUALS( client().findOne( ns(), Query() ) , fromjson( "{'_id':0,a:[]}" ) ); } }; class PushSliceToZeroFromNothing : public SetBase { public: void run() { client().insert( ns(), fromjson( "{'_id':0}" ) ); // { $push : { a : { $each : [ 3 ] , $slice : 0 } } } BSONObj pushObj = BSON( "$each" << BSON_ARRAY( 3 ) << "$slice" << 0 ); client().update( ns(), Query(), BSON( "$push" << BSON( "a" << pushObj ) ) ); ASSERT_EQUALS( client().findOne( ns(), Query() ) , fromjson( "{'_id':0,a:[]}" ) ); } }; class PushSliceFromNothing : public SetBase { public: void run() { client().insert( ns(), fromjson( "{'_id':0}" ) ); // { $push : { a : { $each : [ 1 , 2 ] , $slice : -3 } } } BSONObj pushObj = BSON( "$each" << BSON_ARRAY( 1 << 2 ) << "$slice" << -3 ); client().update( ns(), Query(), BSON( "$push" << BSON( "a" << pushObj ) ) ); ASSERT_EQUALS( client().findOne( ns(), Query() ) , fromjson( "{'_id':0,a:[1,2]}" ) ); } }; class PushSliceLongerThanSliceFromNothing : public SetBase { public: void run() { client().insert( ns(), fromjson( "{'_id':0}" ) ); // { $push : { a : { $each : [ 1 , 2 , 3 ] , $slice : -2 } } } BSONObj pushObj = BSON( "$each" << BSON_ARRAY( 1 << 2 << 3 ) << "$slice" << -2 ); client().update( ns(), Query(), BSON( "$push" << BSON( "a" << pushObj ) ) ); ASSERT_EQUALS( client().findOne( ns(), Query() ) , fromjson( "{'_id':0,a:[2,3]}" ) ); } }; class PushSliceFromEmpty : public SetBase { public: void run() { client().insert( ns(), fromjson( "{'_id':0,a:[]}" ) ); // { $push : { a : { $each : [ 1 ] , $slice : -3 } } } BSONObj pushObj = BSON( "$each" << BSON_ARRAY( 1 ) << "$slice" << -3 ); client().update( ns(), Query(), BSON( "$push" << BSON( "a" << pushObj ) ) ); ASSERT_EQUALS( client().findOne( ns(), Query() ) , fromjson( "{'_id':0,a:[1]}" ) ); } }; class PushSliceLongerThanSliceFromEmpty : public SetBase { public: void run() { client().insert( ns(), fromjson( "{'_id':0,a:[]}" ) ); // { $push : { a : { $each : [ 1 , 2 , 3 ] , $slice : -2 } } } BSONObj pushObj = BSON( "$each" << BSON_ARRAY( 1 << 2 << 3 ) << "$slice" << -2 ); client().update( ns(), Query(), BSON( "$push" << BSON( "a" << pushObj ) ) ); ASSERT_EQUALS( client().findOne( ns(), Query() ) , fromjson( "{'_id':0,a:[2,3]}" ) ); } }; class PushSliceTwoFields : public SetBase { public: void run() { client().insert( ns(), fromjson( "{'_id':0,a:[1,2],b:[3,4]}" ) ); // { $push: { a: { $each: [ 5 ] , $slice : -2 }, { b: $each: [ 6 ] , $slice: -1 } } } BSONObj objA = BSON( "$each" << BSON_ARRAY( 5 ) << "$slice" << -2 ); BSONObj objB = BSON( "$each" << BSON_ARRAY( 6 ) << "$slice" << -1 ); client().update( ns(), Query(), BSON( "$push" << BSON( "a" << objA << "b" << objB ) ) ); ASSERT_EQUALS( client().findOne( ns(), Query() ) , fromjson("{'_id':0,a:[2,5],b:[6]}")); } }; class PushSliceAndNormal : public SetBase { public: void run() { client().insert( ns(), fromjson( "{'_id':0,a:[1,2],b:[3]}" ) ); // { $push : { a : { $each : [ 5 ] , $slice : -2 } , { b : 4 } } BSONObj objA = BSON( "$each" << BSON_ARRAY( 5 ) << "$slice" << -2 ); client().update( ns(), Query(), BSON("$push" << BSON("a" << objA << "b" << 4))); ASSERT_EQUALS(client().findOne(ns(), Query()) , fromjson("{'_id':0,a:[2,5],b:[3,4]}")); } }; class PushSliceTwoFieldsConflict : public SetBase { public: void run() { client().insert( ns(), fromjson( "{'_id':0,a:[1],b:[3]}" ) ); // { $push: { a: { $each: [ 5 ] , $slice: -2 } , { a: $each: [ 6 ] , $slice: -1 } } } BSONObj objA = BSON( "$each" << BSON_ARRAY( 5 ) << "$slice" << -2 ); BSONObj other = BSON( "$each" << BSON_ARRAY( 6 ) << "$slice" << -1 ); client().update( ns(), Query(), BSON( "$push" << BSON( "a" << objA << "a" << other ) ) ); ASSERT(client().findOne( ns(), Query()).woCompare(fromjson("{'_id':0,a:[1],b:[3]}"))==0); } }; class PushSliceAndNormalConflict : public SetBase { public: void run() { client().insert( ns(), fromjson( "{'_id':0,a:[1],b:[3]}" ) ); // { $push : { a : { $each : [ 5 ] , $slice : -2 } , { a : 4 } } } BSONObj objA = BSON( "$each" << BSON_ARRAY( 5 ) << "$slice" << -2 ); client().update( ns(), Query(), BSON( "$push" << BSON( "a" << objA << "a" << 4 ) ) ); ASSERT(client().findOne( ns(), Query()).woCompare(fromjson("{'_id':0,a:[1],b:[3]}"))==0); } }; class PushSliceInvalidEachType : public SetBase { public: void run() { client().insert( ns(), fromjson( "{'_id':0,a:[1,2]}" ) ); // { $push : { a : { $each : 3 , $slice : -2 } } } BSONObj pushObj = BSON( "$each" << 3 << "$slice" << -2 ); client().update( ns(), Query(), BSON( "$push" << BSON( "a" << pushObj ) ) ); ASSERT( client().findOne(ns(), Query()).woCompare(fromjson("{'_id':0,a:[1,2]}")) == 0); } }; class PushSliceInvalidSliceType : public SetBase { public: void run() { client().insert( ns(), fromjson( "{'_id':0,a:[1,2]}" ) ); // { $push : { a : { $each : [ 3 ], $slice : [ -2 ] } } } BSONObj pushObj = BSON( "$each" << BSON_ARRAY(3) << "$slice" << BSON_ARRAY(-2) ); client().update( ns(), Query(), BSON( "$push" << BSON( "a" << pushObj ) ) ); ASSERT( client().findOne( ns(), Query() ).woCompare(fromjson("{'_id':0,a:[1,2]}")) == 0); } }; class PushSliceInvalidSliceValue : public SetBase { public: void run() { client().insert( ns(), fromjson( "{'_id':0,a:[1,2]}" ) ); // { $push : { a : { $each : [ 3 ], $slice : 2 } } } BSONObj pushObj = BSON( "$each" << BSON_ARRAY(3) << "$slice" << 2 ); client().update( ns(), Query(), BSON( "$push" << BSON( "a" << pushObj ) ) ); ASSERT( client().findOne( ns(), Query() ).woCompare(fromjson("{'_id':0,a:[1,2]}")) == 0); } }; class PushSliceInvalidSliceDouble : public SetBase { public: void run() { client().insert( ns(), fromjson( "{'_id':0,a:[1,2]}" ) ); // { $push : { a : { $each : [ 3 ], $slice : -2.1 } } } BSONObj pushObj = BSON( "$each" << BSON_ARRAY(3) << "$slice" << -2.1 ); client().update( ns(), Query(), BSON( "$push" << BSON( "a" << pushObj ) ) ); ASSERT( client().findOne( ns(), Query() ).woCompare(fromjson("{'_id':0,a:[1,2]}")) == 0); } }; class PushSliceValidSliceDouble : public SetBase { public: void run() { client().insert( ns(), fromjson( "{'_id':0,a:[1,2]}" ) ); // { $push : { a : { $each : [ 3 ], $slice : -2.0 } } } BSONObj pushObj = BSON( "$each" << BSON_ARRAY(3) << "$slice" << -2.0 ); client().update( ns(), Query(), BSON( "$push" << BSON( "a" << pushObj ) ) ); ASSERT_EQUALS(client().findOne(ns(), Query()) , fromjson("{'_id':0,a:[2,3]}")); } }; class PushSliceInvalidSlice : public SetBase { public: void run() { client().insert( ns(), fromjson( "{'_id':0,a:[1,2]}" ) ); // { $push : { a : { $each : [ 3 ], $xxxx : 2 } } } BSONObj pushObj = BSON( "$each" << BSON_ARRAY(3) << "$xxxx" << 2 ); client().update( ns(), Query(), BSON( "$push" << BSON( "a" << pushObj ) ) ); ASSERT( client().findOne( ns(), Query() ).woCompare(fromjson("{'_id':0,a:[1,2]}")) == 0); } }; // // We'd like to test the ability of $push with $sort in the following sequence of tests. We // try to enumerate all the possibilities of where the final element would come from: the // document, the $push itself, or both. // class PushSortBase : public ClientBase { public: ~PushSortBase() { client().dropCollection( ns() ); } protected: enum UpdateType { // Sorts ascending and slices the back of the array. TOPK_ASC = 0, // Sorts descending and slices the front of the array. TOPK_DESC = 1, // Sorts ascending and slices the back of the array. BOTTOMK_ASC = 2, // Sorts descending and slices the front of the array. BOTTOMK_DESC = 3 }; const char* ns() { return "unittest.updatetests.PushSortBase"; } void setParams( const BSONArray& fields, const BSONArray& values, const BSONArray& sort, int size ) { _fieldNames = fields; _fieldValues = values; _sortFields = sort; _sliceSize = size; } /** * Generates the update expression portion of an update command given one of the * possible types of update. */ BSONObj getUpdate( int updateType ) { BSONObjBuilder updateBuilder; BSONObjBuilder pushBuilder( updateBuilder.subobjStart( "$push" ) ); BSONObjBuilder fieldBuilder( pushBuilder.subobjStart( "x" ) ); // Builds $each: [ {a:1,b:1,...}, {a:2,b:2,...}, ... ] BSONArrayBuilder eachBuilder( fieldBuilder.subarrayStart( "$each" ) ); BSONObjIterator itVal( _fieldValues ); while ( itVal.more() ) { BSONObjBuilder eachObjBuilder; BSONElement val = itVal.next(); BSONObjIterator itName( _fieldNames ); while ( itName.more() ) { BSONElement name = itName.next(); eachObjBuilder.append( name.String(), val.Int() ); } eachBuilder.append( eachObjBuilder.done() ); } eachBuilder.done(); // Builds $slice portion. fieldBuilder.append( "$slice", updateType < 2 ? -_sliceSize : _sliceSize); // Builds $sort: portion BSONObjBuilder patternBuilder( fieldBuilder.subobjStart( "$sort" ) ); BSONObjIterator itSort( _sortFields ); while ( itSort.more() ) { BSONElement sortField = itSort.next(); patternBuilder.append( sortField.String(), updateType%2 ? -1 : 1 ); } patternBuilder.done(); fieldBuilder.done(); pushBuilder.done(); return updateBuilder.obj(); } void check( BSONObj expected ) { std::cout << expected.toString() << std::endl; std::cout << client().findOne( ns(), Query() ) << std::endl; ASSERT( client().findOne( ns(), Query() ).woCompare( expected ) == 0 ); } private: BSONArray _fieldNames; BSONArray _fieldValues; BSONArray _sortFields; int _sliceSize; }; class PushSortBelowFull : public PushSortBase { public: void run() { // With the following parameters // fields in values in // the each array each array field to sort size setParams( BSON_ARRAY( "a" << "b" ), BSON_ARRAY( 2 ), BSON_ARRAY( "b" ), 3 ); // Generates the four variations below (but for now we're only using negative slice). // TOPK_ASC: $push: { x: { $each: [ {a:2,b:2} ], $slice:-3, $sort: { b:1 } } } // TOPK_DESC: $push: { x: { $each: [ {a:2,b:2} ], $slice:-3, $sort: { b:-1 } } } // BOTTOMK_ASC: $push: { x: { $each: [ {a:2,b:2} ], $slice:3, $sort: { b:1 } } } // BOTTOMK_DESC: $push: { x: { $each: [ {a:2,b:2} ], $slice:3, $sort: { b:-1 } } } for ( int i = 0; i < 2; i++ ) { // i < 4 when we have positive $slice client().dropCollection( ns() ); client().insert( ns(), fromjson( "{'_id':0,x:[{a:1,b:1}]}" ) ); BSONObj result; BSONObj expected; switch ( i ) { case TOPK_ASC: case BOTTOMK_ASC: client().update( ns(), Query(), getUpdate(i) ); result = client().findOne( ns(), Query() ); expected = fromjson( "{'_id':0,x:[{a:1,b:1},{a:2,b:2}]}" ); ASSERT_EQUALS( result, expected ); break; case TOPK_DESC: case BOTTOMK_DESC: client().update( ns(), Query(), getUpdate(i) ); result = client().findOne( ns(), Query() ); expected = fromjson( "{'_id':0,x:[{a:2,b:2},{a:1,b:1}]}" ) ; ASSERT_EQUALS( result, expected ); break; } } } }; class PushSortReachedFullExact : public PushSortBase { public: void run() { // With the following parameters // fields in values in // the each array each array field to sort size setParams(BSON_ARRAY( "a"<<"b" ), BSON_ARRAY( 2 ), BSON_ARRAY( "b" ), 2 ); // Generates the four variations below (but for now we're only using negative slice). // TOPK_ASC: $push: { x: { $each: [ {a:2,b:2} ], $slice:-2, $sort: { b:1 } } } // TOPK_DESC: $push: { x: { $each: [ {a:2,b:2} ], $slice:-2, $sort: { b:-1 } } } // BOTTOMK_ASC: $push: { x: { $each: [ {a:2,b:2} ], $slice:2, $sort: { b:1 } } } // BOTTOMK_DESC: $push: { x: { $each: [ {a:2,b:2} ], $slice:2, $sort: { b:-1 } } } for ( int i = 0; i < 2; i++ ) { // i < 4 when we have positive $slice client().dropCollection( ns() ); client().insert( ns(), fromjson( "{'_id':0,x:[{a:1,b:1}]}" ) ); BSONObj result; BSONObj expected; switch (i) { case TOPK_ASC: case BOTTOMK_ASC: client().update( ns(), Query(), getUpdate(i) ); result = client().findOne( ns(), Query() ); expected = fromjson( "{'_id':0,x:[{a:1,b:1},{a:2,b:2}]}" ); ASSERT_EQUALS( result, expected ); break; case TOPK_DESC: case BOTTOMK_DESC: client().update( ns(), Query(), getUpdate(i) ); result = client().findOne( ns(), Query() ); expected = fromjson( "{'_id':0,x:[{a:2,b:2},{a:1,b:1}]}" ); ASSERT_EQUALS( result, expected ); break; } } } }; class PushSortReachedFullWithBoth : public PushSortBase { public: void run() { // With the following parameters // fields in values in // the each array each array field to sort size setParams( BSON_ARRAY( "a"<<"b" ), BSON_ARRAY( 2 ), BSON_ARRAY( "b" ), 2 ); // Generates the four variations below (but for now we're only using negative slice). // TOPK_ASC: $push: { x: { $each: [ {a:2,b:2} ], $slice:-2, $sort: { b:1 } } } // TOPK_DESC: $push: { x: { $each: [ {a:2,b:2} ], $slice:-2, $sort: { b:-1 } } } // BOTTOMK_ASC: $push: { x: { $each: [ {a:2,b:2} ], $slice:2, $sort: { b:1 } } } // BOTTOMK_DESC: $push: { x: { $each: [ {a:2,b:2} ], $slice:2, $sort: { b:-1 } } } for ( int i = 0; i < 2; i++ ) { // i < 4 when we have positive $slice client().dropCollection( ns() ); client().insert( ns(), fromjson( "{'_id':0,x:[{a:1,b:1},{a:3,b:3}]}" ) ); BSONObj result; BSONObj expected; switch ( i ) { case TOPK_ASC: client().update( ns(), Query(), getUpdate(i) ); result = client().findOne( ns(), Query() ); expected = fromjson( "{'_id':0,x:[{a:2,b:2},{a:3,b:3}]}" ); ASSERT_EQUALS( result, expected ); break; case TOPK_DESC: client().update( ns(), Query(), getUpdate(i) ); result = client().findOne( ns(), Query() ); expected = fromjson( "{'_id':0,x:[{a:2,b:2},{a:1,b:1}]}" ); ASSERT_EQUALS( result, expected ); break; case BOTTOMK_ASC: case BOTTOMK_DESC: // Implement me. break; } } } }; class PushSortToZero : public PushSortBase { public: void run() { // With the following parameters // fields in values in // the each array each array field to sort size setParams( BSON_ARRAY( "a"<<"b" ), BSON_ARRAY( 2 ), BSON_ARRAY( "b" ), 0 ); // Generates the four variations below (but for now we're only using negative slice). // TOPK_ASC: $push: { x: { $each: [ {a:2,b:2} ], $slice:0, $sort: { b:1 } } } // TOPK_DESC: $push: { x: { $each: [ {a:2,b:2} ], $slice:0, $sort: { b:-1 } } } // BOTTOMK_ASC: $push: { x: { $each: [ {a:2,b:2} ], $slice:0, $sort: { b:1 } } } // BOTTOMK_DESC: $push: { x: { $each: [ {a:2,b:2} ], $slice:0, $sort: { b:-1 } } } for ( int i = 0; i < 2; i++ ) { // i < 4 when we have positive $slice client().dropCollection( ns() ); client().insert( ns(), fromjson( "{'_id':0,x:[{a:1,b:1},{a:3,b:3}]}" ) ); BSONObj result; BSONObj expected; switch ( i ) { default: client().update( ns(), Query(), getUpdate(i) ); result = client().findOne( ns(), Query() ); expected = fromjson( "{'_id':0,x:[]}" ); ASSERT_EQUALS( result, expected ); break; } } } }; class PushSortToZeroFromNothing : public PushSortBase { public: void run() { // With the following parameters // fields in values in // the each array each array field to sort size setParams( BSON_ARRAY( "a"<<"b" ), BSON_ARRAY( 2 ), BSON_ARRAY( "b" ), 0 ); // Generates the four variations below (but for now we're only using negative slice). // TOPK_ASC: $push: { x: { $each: [ {a:2,b:2} ], $slice:0, $sort: { b:1 } } } // TOPK_DESC: $push: { x: { $each: [ {a:2,b:2} ], $slice:0, $sort: { b:-1 } } } // BOTTOMK_ASC: $push: { x: { $each: [ {a:2,b:2} ], $slice:0, $sort: { b:1 } } } // BOTTOMK_DESC: $push: { x: { $each: [ {a:2,b:2} ], $slice:0, $sort: { b:-1 } } } for ( int i = 0; i < 2; i++ ) { // i < 4 when we have positive $slice client().dropCollection( ns() ); client().insert( ns(), fromjson( "{'_id':0}" ) ); BSONObj result; BSONObj expected; switch ( i ) { default: client().update( ns(), Query(), getUpdate(i) ); result = client().findOne( ns(), Query() ); expected = fromjson( "{'_id':0,x:[]}" ); ASSERT_EQUALS( result, expected ); break; } } } }; class PushSortFromNothing : public PushSortBase { public: void run() { // With the following parameters // fields in values in // the each array each array field to sort size setParams(BSON_ARRAY( "a"<<"b" ), BSON_ARRAY( 2 << 1 ), BSON_ARRAY( "b" ), 2 ); // Generates the four variations below (but for now we're only using negative slice). // = [ {a:2,b:2}, {a:1,b:1} ] // Generates the four variations below // TOPK_ASC: $push: { x: { $each: [ ], $slice:-2, $sort: { b:1 } } } // TOPK_DESC: $push: { x: { $each: [ ], $slice:-2, $sort: { b:-1 } } } // BOTTOMK_ASC: $push: { x: { $each: [ ], $slice:2, $sort: { b:1 } } } // BOTTOMK_DESC: $push: { x: { $each: [ ], $slice:2, $sort: { b:-1 } } } for ( int i = 0; i < 2; i++ ) { // i < 4 when we have positive $slice client().dropCollection( ns() ); client().insert( ns(), fromjson( "{'_id':0}" ) ); BSONObj result; BSONObj expected; switch (i) { case TOPK_ASC: case BOTTOMK_ASC: client().update( ns(), Query(), getUpdate(i) ); result = client().findOne( ns(), Query() ); expected = fromjson( "{'_id':0,x:[{a:1,b:1},{a:2,b:2}]}" ); ASSERT_EQUALS( result, expected ); break; case TOPK_DESC: case BOTTOMK_DESC: client().update( ns(), Query(), getUpdate(i) ); result = client().findOne( ns(), Query() ); expected = fromjson( "{'_id':0,x:[{a:2,b:2},{a:1,b:1}]}" ); ASSERT_EQUALS( result, expected ); break; } } } }; class PushSortLongerThanSliceFromNothing : public PushSortBase { public: void run() { // With the following parameters // fields in values in // the each array each array field to sort size setParams(BSON_ARRAY( "a"<<"b" ), BSON_ARRAY( 2 << 1 << 3), BSON_ARRAY( "b" ), 2 ); // Generates the four variations below (but for now we're only using negative slice). // = [ {a:2,b:2}, {a:1,b:1}, {a:3,b:3} ] // TOPK_ASC: $push: { x: { $each: [ ], $slice:-2, $sort: { b:1 } } } // TOPK_DESC: $push: { x: { $each: [ ], $slice:-2, $sort: { b:-1 } } } // BOTTOMK_ASC: $push: { x: { $each: [ ], $slice:2, $sort: { b:1 } } } // BOTTOMK_DESC: $push: { x: { $each: [ ], $slice:2, $sort: { b:-1 } } } for ( int i = 0; i < 2; i++ ) { // i < 4 when we have positive $slice client().dropCollection( ns() ); client().insert( ns(), fromjson( "{'_id':0}" ) ); BSONObj result; BSONObj expected; switch (i) { case TOPK_ASC: client().update( ns(), Query(), getUpdate(i) ); result = client().findOne( ns(), Query() ); expected = fromjson( "{'_id':0,x:[{a:2,b:2},{a:3,b:3}]}" ); ASSERT_EQUALS( result, expected ); break; case TOPK_DESC: client().update( ns(), Query(), getUpdate(i) ); result = client().findOne( ns(), Query() ); expected = fromjson( "{'_id':0,x:[{a:2,b:2},{a:1,b:1}]}" ); ASSERT_EQUALS( result, expected ); break; case BOTTOMK_ASC: case BOTTOMK_DESC: // Implement me. break; } } } }; class PushSortFromEmpty : public PushSortBase { public: void run() { // With the following parameters // fields in values in // the each array each array field to sort size setParams(BSON_ARRAY( "a"<<"b" ), BSON_ARRAY( 2 << 1 ), BSON_ARRAY( "b" ), 2 ); // Generates the four variations below (but for now we're only using negative slice). // = [ {a:2,b:2}, {a:1,b:1} ] // TOPK_ASC: $push: { x: { $each: [ ], $slice:-2, $sort: { b:1 } } } // TOPK_DESC: $push: { x: { $each: [ ], $slice:-2, $sort: { b:-1 } } } // BOTTOMK_ASC: $push: { x: { $each: [ ], $slice:2, $sort: { b:1 } } } // BOTTOMK_DESC: $push: { x: { $each: [ ], $slice:2, $sort: { b:-1 } } } for ( int i = 0; i < 2; i++ ) { // i < 4 when we have positive $slice client().dropCollection( ns() ); client().insert( ns(), fromjson( "{'_id':0,x:[]}" ) ); BSONObj result; BSONObj expected; switch (i) { case TOPK_ASC: case BOTTOMK_ASC: client().update( ns(), Query(), getUpdate(i) ); result = client().findOne( ns(), Query() ); expected = fromjson( "{'_id':0,x:[{a:1,b:1},{a:2,b:2}]}" ); ASSERT_EQUALS( result, expected ); break; case TOPK_DESC: case BOTTOMK_DESC: client().update( ns(), Query(), getUpdate(i) ); result = client().findOne( ns(), Query() ); expected = fromjson( "{'_id':0,x:[{a:2,b:2},{a:1,b:1}]}" ); ASSERT_EQUALS( result, expected ); break; } } } }; class PushSortLongerThanSliceFromEmpty : public PushSortBase { public: void run() { // With the following parameters // fields in values in // the each array each array field to sort size setParams(BSON_ARRAY( "a"<<"b" ), BSON_ARRAY( 2 << 1 << 3), BSON_ARRAY( "b" ), 2 ); // Generates the four variations below (but for now we're only using negative slice). // = [ {a:2,b:2}, {a:1,b:1}, {a:3,b:3} ] // TOPK_ASC: $push: { x: { $each: [ ], $slice:-2, $sort: { b:1 } } } // TOPK_DESC: $push: { x: { $each: [ ], $slice:-2, $sort: { b:-1 } } } // BOTTOMK_ASC: $push: { x: { $each: [ ], $slice:2, $sort: { b:1 } } } // BOTTOMK_DESC: $push: { x: { $each: [ ], $slice:2, $sort: { b:-1 } } } for ( int i = 0; i < 2; i++ ) { // i < 4 when we have positive $slice client().dropCollection( ns() ); client().insert( ns(), fromjson( "{'_id':0,x:[]}" ) ); BSONObj result; BSONObj expected; switch (i) { case TOPK_ASC: client().update( ns(), Query(), getUpdate(i) ); result = client().findOne( ns(), Query() ); expected = fromjson( "{'_id':0,x:[{a:2,b:2},{a:3,b:3}]}" ); ASSERT_EQUALS( result, expected ); break; case TOPK_DESC: client().update( ns(), Query(), getUpdate(i) ); result = client().findOne( ns(), Query() ); expected = fromjson( "{'_id':0,x:[{a:2,b:2},{a:1,b:1}]}" ); ASSERT_EQUALS( result, expected ); break; case BOTTOMK_ASC: case BOTTOMK_DESC: // Implement me. break; } } } }; class PushSortSortMixed { public: void run() { BSONObj objs[3]; objs[0] = fromjson( "{a:1, b:1}" ); objs[1] = fromjson( "{a:3, b:1}" ); objs[2] = fromjson( "{a:2, b:3}" ); vector workArea; for ( int i = 0; i < 3; i++ ) { workArea.push_back( objs[i] ); } sort( workArea.begin(), workArea.end(), ProjectKeyCmp( BSON("b" << 1 << "a" << -1) ) ); ASSERT_EQUALS( workArea[0], objs[1] ); ASSERT_EQUALS( workArea[1], objs[0] ); ASSERT_EQUALS( workArea[2], objs[2] ); } }; class PushSortSortOutOfOrderFields { public: void run() { BSONObj objs[3]; objs[0] = fromjson( "{b:1, a:1}" ); objs[1] = fromjson( "{a:3, b:2}" ); objs[2] = fromjson( "{b:3, a:2}" ); vector workArea; for ( int i = 0; i < 3; i++ ) { workArea.push_back( objs[i] ); } sort( workArea.begin(), workArea.end(), ProjectKeyCmp( BSON("a" << 1 << "b" << 1) ) ); ASSERT_EQUALS( workArea[0], objs[0] ); ASSERT_EQUALS( workArea[1], objs[2] ); ASSERT_EQUALS( workArea[2], objs[1] ); } }; class PushSortSortExtraFields { public: void run() { BSONObj objs[3]; objs[0] = fromjson( "{b:1, c:2, a:1}" ); objs[1] = fromjson( "{c:1, a:3, b:2}" ); objs[2] = fromjson( "{b:3, a:2}" ); vector workArea; for ( int i = 0; i < 3; i++ ) { workArea.push_back( objs[i] ); } sort( workArea.begin(), workArea.end(), ProjectKeyCmp( BSON("a" << 1 << "b" << 1) ) ); ASSERT_EQUALS( workArea[0], objs[0] ); ASSERT_EQUALS( workArea[1], objs[2] ); ASSERT_EQUALS( workArea[2], objs[1] ); } }; class PushSortSortMissingFields { public: void run() { BSONObj objs[3]; objs[0] = fromjson( "{a:2, b:2}" ); objs[1] = fromjson( "{a:1}" ); objs[2] = fromjson( "{a:3, b:3, c:3}" ); vector workArea; for ( int i = 0; i < 3; i++ ) { workArea.push_back( objs[i] ); } sort( workArea.begin(), workArea.end(), ProjectKeyCmp( BSON("b" << 1 << "c" << 1) ) ); ASSERT_EQUALS( workArea[0], objs[1] ); ASSERT_EQUALS( workArea[1], objs[0] ); ASSERT_EQUALS( workArea[2], objs[2] ); } }; class PushSortSortNestedFields { public: void run() { BSONObj objs[3]; objs[0] = fromjson( "{a:{b:{c:2, d:0}}}" ); objs[1] = fromjson( "{a:{b:{c:1, d:2}}}" ); objs[2] = fromjson( "{a:{b:{c:3, d:1}}}" ); vector workArea; for ( int i = 0; i < 3; i++ ) { workArea.push_back( objs[i] ); } sort( workArea.begin(), workArea.end(), ProjectKeyCmp( fromjson( "{'a.b.d':-1}" ) ) ); ASSERT_EQUALS( workArea[0], objs[1] ); ASSERT_EQUALS( workArea[1], objs[2] ); ASSERT_EQUALS( workArea[2], objs[0] ); sort( workArea.begin(), workArea.end(), ProjectKeyCmp( fromjson( "{'a.b':1}" ) ) ); ASSERT_EQUALS( workArea[0], objs[1] ); ASSERT_EQUALS( workArea[1], objs[0] ); ASSERT_EQUALS( workArea[2], objs[2] ); } }; class PushSortInvalidSortPattern : public SetBase { public: void run() { // Sort pattern validation is made during update command checking. Therefore, to // catch bad patterns, we have to write updated that use them. BSONObj expected = fromjson( "{'_id':0,x:[{a:1}, {a:2}]}" ); client().insert( ns(), expected ); // { $push : { x : { $each : [ {a:3} ], $slice:-2, $sort : {a..d:1} } } } BSONObj pushObj = BSON( "$each" << BSON_ARRAY( BSON( "a" << 3 ) ) << "$slice" << -2 << "$sort" << BSON( "a..d" << 1 ) ); client().update( ns(), Query(), BSON( "$push" << BSON( "x" << pushObj ) ) ); BSONObj result = client().findOne( ns(), Query() ); ASSERT_EQUALS( result, expected ); // { $push : { x : { $each : [ {a:3} ], $slice:-2, $sort : {a.:1} } } } pushObj = BSON( "$each" << BSON_ARRAY( BSON( "a" << 3 ) ) << "$slice" << -2 << "$sort" << BSON( "a." << 1 ) ); client().update( ns(), Query(), BSON( "$push" << BSON( "x" << pushObj ) ) ); result = client().findOne( ns(), Query() ); ASSERT_EQUALS( result, expected ); // { $push : { x : { $each : [ {a:3} ], $slice:-2, $sort : {.b:1} } } } pushObj = BSON( "$each" << BSON_ARRAY( BSON( "a" << 3 ) ) << "$slice" << -2 << "$sort" << BSON( ".b" << 1 ) ); client().update( ns(), Query(), BSON( "$push" << BSON( "x" << pushObj ) ) ); result = client().findOne( ns(), Query() ); ASSERT_EQUALS( result, expected ); // { $push : { x : { $each : [ {a:3} ], $slice:-2, $sort : {.:1} } } } pushObj = BSON( "$each" << BSON_ARRAY( BSON( "a" << 3 ) ) << "$slice" << -2 << "$sort" << BSON( "." << 1 ) ); client().update( ns(), Query(), BSON( "$push" << BSON( "x" << pushObj ) ) ); result = client().findOne( ns(), Query() ); ASSERT_EQUALS( result, expected ); // { $push : { x : { $each : [ {a:3} ], $slice:-2, $sort : {'':1} } } } pushObj = BSON( "$each" << BSON_ARRAY( BSON( "a" << 3 ) ) << "$slice" << -2 << "$sort" << BSON( "" << 1 ) ); client().update( ns(), Query(), BSON( "$push" << BSON( "x" << pushObj ) ) ); result = client().findOne( ns(), Query() ); ASSERT_EQUALS( result, expected ); } }; class PushSortInvalidEachType : public SetBase { public: void run() { BSONObj expected = fromjson( "{'_id':0,x:[{a:1},{a:2}]}" ); client().insert( ns(), expected ); // { $push : { x : { $each : [ 3 ], $slice:-2, $sort : {a:1} } } } BSONObj pushObj = BSON( "$each" << BSON_ARRAY( 3 ) << "$slice" << -2 << "$sort" << BSON( "a" << 1 ) ); client().update( ns(), Query(), BSON( "$push" << BSON( "x" << pushObj ) ) ); BSONObj result = client().findOne( ns(), Query() ); ASSERT_EQUALS( result, expected ); } }; class PushSortInvalidBaseArray : public SetBase { public: void run() { BSONObj expected = fromjson( "{'_id':0,x:[1,2]}" ); client().insert( ns(), expected ); // { $push : { x : { $each : [ {a:3} ], $slice:-2, $sort : {a:1} } } } BSONObj pushObj = BSON( "$each" << BSON_ARRAY( BSON( "a" << 3 ) ) << "$slice" << -2 << "$sort" << BSON( "a" << 1 ) ); client().update( ns(), Query(), BSON( "$push" << BSON( "x" << pushObj ) ) ); BSONObj result = client().findOne( ns(), Query() ); ASSERT_EQUALS( result, expected ); } }; class PushSortInvalidSortType : public SetBase { public: void run() { BSONObj expected = fromjson( "{'_id':0,x:[{a:1},{a:2}]}" ); client().insert( ns(), expected ); // { $push : { x : { $each : [ {a:3} ], $slice:-2, $sort : 2} } } BSONObj pushObj = BSON( "$each" << BSON_ARRAY( BSON( "a" << 3 ) ) << "$slice" << -2 << "$sort" << 2 ); client().update( ns(), Query(), BSON( "$push" << BSON( "x" << pushObj ) ) ); BSONObj result = client().findOne( ns(), Query() ); ASSERT_EQUALS( result, expected ); } }; class PushSortInvalidSortValue : public SetBase { public: void run() { BSONObj expected = fromjson( "{'_id':0,x:[{a:1},{a:2}]}" ); client().insert( ns(), expected ); // { $push : { x : { $each : [ {a:3} ], $slice:2, $sort : {a:1} } } } BSONObj pushObj = BSON( "$each" << BSON_ARRAY( BSON( "a" << 3 ) ) << "$slice" << 2 << "$sort" << BSON( "a" << 1 ) ); client().update( ns(), Query(), BSON( "$push" << BSON( "x" << pushObj ) ) ); BSONObj result = client().findOne( ns(), Query() ); ASSERT_EQUALS( result, expected ); } }; class PushSortInvalidSortDouble : public SetBase { public: void run() { BSONObj expected = fromjson( "{'_id':0,x:[{a:1},{a:2}]}" ); client().insert( ns(), expected ); // { $push : { x : { $each : [ {a:3} ], $slice:-2.1, $sort : {a:1} } } } BSONObj pushObj = BSON( "$each" << BSON_ARRAY( BSON( "a" << 3 ) ) << "$slice" << -2.1 << "$sort" << BSON( "a" << 1 ) ); client().update( ns(), Query(), BSON( "$push" << BSON( "x" << pushObj ) ) ); BSONObj result = client().findOne( ns(), Query() ); ASSERT_EQUALS( result, expected ); } }; class PushSortValidSortDouble : public SetBase { public: void run() { client().insert( ns(), fromjson( "{'_id':0,x:[{a:1},{a:2}]}" ) ); // { $push : { x : { $each : [ {a:3} ], $slice:-2.0, $sort : {a:1} } } } BSONObj pushObj = BSON( "$each" << BSON_ARRAY( BSON( "a" << 3 ) ) << "$slice" << -2.0 << "$sort" << BSON( "a" << 1 ) ); client().update( ns(), Query(), BSON( "$push" << BSON( "x" << pushObj ) ) ); BSONObj expected = fromjson( "{'_id':0,x:[{a:2},{a:3}]}" ); BSONObj result = client().findOne( ns(), Query() ); ASSERT_EQUALS( result, expected ); } }; class PushSortInvalidSortSort : public SetBase { public: void run() { BSONObj expected = fromjson( "{'_id':0,x:[{a:1},{a:2}]}" ); client().insert( ns(), expected ); // { $push : { x : { $each : [ {a:3} ], $slice:-2.0, $sort : [2, 1] } } } BSONObj pushObj = BSON( "$each" << BSON_ARRAY( BSON( "a" << 3 ) ) << "$slice" << -2.0 << "$sort" << BSON_ARRAY( 2 << 1 ) ); client().update( ns(), Query(), BSON( "$push" << BSON( "x" << pushObj ) ) ); BSONObj result = client().findOne( ns(), Query() ); ASSERT_EQUALS( result, expected ); } }; class PushSortInvalidSortSortOrder : public SetBase { public: void run() { BSONObj expected = fromjson( "{'_id':0,x:[{a:1},{a:2}]}" ); client().insert( ns(), expected ); // { $push : { x : { $each : [ {a:3} ], $slice:-2, $sort : {a:10} } } } BSONObj pushObj = BSON( "$each" << BSON_ARRAY( BSON( "a" << 3 ) ) << "$slice" << -2 << "$sort" << BSON( "a" << 10 ) ); client().update( ns(), Query(), BSON( "$push" << BSON( "x" << pushObj ) ) ); BSONObj result = client().findOne( ns(), Query() ); ASSERT_EQUALS( result, expected ); } }; class PushSortInvertedSortAndSlice : public SetBase { public: void run() { client().insert( ns(), fromjson( "{'_id':0,x:[{a:1},{a:3}]}" ) ); // { $push : { x : { $each : [ {a:2} ], $sort: {a:1}, $slice:-2 } } } BSONObj pushObj = BSON( "$each" << BSON_ARRAY( BSON( "a" << 2 ) ) << "$sort" << BSON( "a" << 1 ) << "$slice" << -2.0 ); client().update( ns(), Query(), BSON( "$push" << BSON( "x" << pushObj ) ) ); BSONObj expected = fromjson( "{'_id':0,x:[{a:2},{a:3}]}" ); BSONObj result = client().findOne( ns(), Query() ); ASSERT_EQUALS( result, expected ); } }; class PushSortInvalidDuplicatedSort : public SetBase { public: void run() { BSONObj expected = fromjson( "{'_id':0,x:[{a:1},{a:3}]}" ); client().insert( ns(), expected ); // { $push : { x : { $each : [ {a:2} ], $sort : {a:1}, $sort: {a:1} } } } BSONObj pushObj = BSON( "$each" << BSON_ARRAY( BSON( "a" << 2 ) ) << "$sort" << BSON( "a" << 1 ) << "$sort" << BSON( "a" << 1 ) ); client().update( ns(), Query(), BSON( "$push" << BSON( "x" << pushObj ) ) ); BSONObj result = client().findOne( ns(), Query() ); ASSERT_EQUALS( result, expected ); } }; class PushSortInvalidMissingSliceTo : public SetBase { public: void run() { BSONObj expected = fromjson( "{'_id':0,x:[{a:1},{a:3}]}" ); client().insert( ns(), expected ); // { $push : { x : { $each : [ {a:2} ], $sort : {a:1} } } } BSONObj pushObj = BSON( "$each" << BSON_ARRAY( BSON( "a" << 2 ) ) << "$sort" << BSON( "a" << 1 ) ); client().update( ns(), Query(), BSON( "$push" << BSON( "x" << pushObj ) ) ); BSONObj result = client().findOne( ns(), Query() ); ASSERT_EQUALS( result, expected ); } }; class CantIncParent : public SetBase { public: void run() { client().insert( ns(), fromjson( "{'_id':0,a:{b:4}}" ) ); client().update( ns(), Query(), BSON( "$inc" << BSON( "a" << 4.0 ) ) ); ASSERT( client().findOne( ns(), Query() ).woCompare( fromjson( "{'_id':0,a:{b:4}}" ) ) == 0 ); } }; class DontDropEmpty : public SetBase { public: void run() { client().insert( ns(), fromjson( "{'_id':0,a:{b:{}}}" ) ); client().update( ns(), Query(), BSON( "$set" << BSON( "a.c" << 4.0 ) ) ); ASSERT( client().findOne( ns(), Query() ).woCompare( fromjson( "{'_id':0,a:{b:{},c:4}}" ) ) == 0 ); } }; class InsertInEmpty : public SetBase { public: void run() { client().insert( ns(), fromjson( "{'_id':0,a:{b:{}}}" ) ); client().update( ns(), Query(), BSON( "$set" << BSON( "a.b.f" << 4.0 ) ) ); ASSERT( client().findOne( ns(), Query() ).woCompare( fromjson( "{'_id':0,a:{b:{f:4}}}" ) ) == 0 ); } }; class IndexParentOfMod : public SetBase { public: void run() { client().ensureIndex( ns(), BSON( "a" << 1 ) ); client().insert( ns(), fromjson( "{'_id':0}" ) ); client().update( ns(), Query(), fromjson( "{$set:{'a.b':4}}" ) ); ASSERT_EQUALS( fromjson( "{'_id':0,a:{b:4}}" ) , client().findOne( ns(), Query() ) ); ASSERT_EQUALS( fromjson( "{'_id':0,a:{b:4}}" ) , client().findOne( ns(), fromjson( "{'a.b':4}" ) ) ); // make sure the index works } }; class IndexModSet : public SetBase { public: void run() { client().ensureIndex( ns(), BSON( "a.b" << 1 ) ); client().insert( ns(), fromjson( "{'_id':0,a:{b:3}}" ) ); client().update( ns(), Query(), fromjson( "{$set:{'a.b':4}}" ) ); ASSERT_EQUALS( fromjson( "{'_id':0,a:{b:4}}" ) , client().findOne( ns(), Query() ) ); ASSERT_EQUALS( fromjson( "{'_id':0,a:{b:4}}" ) , client().findOne( ns(), fromjson( "{'a.b':4}" ) ) ); // make sure the index works } }; class PreserveIdWithIndex : public SetBase { // Not using $set, but base class is still useful public: void run() { client().insert( ns(), BSON( "_id" << 55 << "i" << 5 ) ); client().update( ns(), BSON( "i" << 5 ), BSON( "i" << 6 ) ); ASSERT( !client().findOne( ns(), Query( BSON( "_id" << 55 ) ).hint ( "{\"_id\":ObjectId(\"000000000000000000000000\")}" ) ).isEmpty() ); } }; class CheckNoMods : public SetBase { public: void run() { client().update( ns(), BSONObj(), BSON( "i" << 5 << "$set" << BSON( "q" << 3 ) ), true ); ASSERT( error() ); } }; class UpdateMissingToNull : public SetBase { public: void run() { client().insert( ns(), BSON( "a" << 5 ) ); client().update( ns(), BSON( "a" << 5 ), fromjson( "{$set:{b:null}}" ) ); ASSERT_EQUALS( jstNULL, client().findOne( ns(), QUERY( "a" << 5 ) ).getField( "b" ).type() ); } }; /** SERVER-4777 */ class TwoModsWithinDuplicatedField : public SetBase { public: void run() { client().insert( ns(), BSON( "_id" << 0 << "a" << 1 << "x" << BSONObj() << "x" << BSONObj() << "z" << 5 ) ); client().update( ns(), BSONObj(), BSON( "$set" << BSON( "x.b" << 1 << "x.c" << 1 ) ) ); ASSERT_EQUALS( BSON( "_id" << 0 << "a" << 1 << "x" << BSON( "b" << 1 << "c" << 1 ) << "x" << BSONObj() << "z" << 5 ), client().findOne( ns(), BSONObj() ) ); } }; /** SERVER-4777 */ class ThreeModsWithinDuplicatedField : public SetBase { public: void run() { client().insert( ns(), BSON( "_id" << 0 << "x" << BSONObj() << "x" << BSONObj() << "x" << BSONObj() ) ); client().update( ns(), BSONObj(), BSON( "$set" << BSON( "x.b" << 1 << "x.c" << 1 << "x.d" << 1 ) ) ); ASSERT_EQUALS( BSON( "_id" << 0 << "x" << BSON( "b" << 1 << "c" << 1 << "d" << 1 ) << "x" << BSONObj() << "x" << BSONObj() ), client().findOne( ns(), BSONObj() ) ); } }; class TwoModsBeforeExistingField : public SetBase { public: void run() { client().insert( ns(), BSON( "_id" << 0 << "x" << 5 ) ); client().update( ns(), BSONObj(), BSON( "$set" << BSON( "a" << 1 << "b" << 1 << "x" << 10 ) ) ); ASSERT_EQUALS( BSON( "_id" << 0 << "a" << 1 << "b" << 1 << "x" << 10 ), client().findOne( ns(), BSONObj() ) ); } }; namespace ModSetTests { class internal1 { public: void run() { BSONObj b = BSON( "$inc" << BSON( "x" << 1 << "a.b" << 1 ) ); ModSet m(b); ASSERT( m.haveModForField( "x" ) ); ASSERT( m.haveModForField( "a.b" ) ); ASSERT( ! m.haveModForField( "y" ) ); ASSERT( ! m.haveModForField( "a.c" ) ); ASSERT( ! m.haveModForField( "a" ) ); ASSERT( m.haveConflictingMod( "x" ) ); ASSERT( m.haveConflictingMod( "a" ) ); ASSERT( m.haveConflictingMod( "a.b" ) ); ASSERT( ! m.haveConflictingMod( "a.bc" ) ); ASSERT( ! m.haveConflictingMod( "a.c" ) ); ASSERT( ! m.haveConflictingMod( "a.a" ) ); } }; class Base { public: virtual ~Base() {} void test( BSONObj morig , BSONObj in , BSONObj wanted ) { BSONObj m = morig.copy(); ModSet set(m); BSONObj out = set.prepare(in)->createNewFromMods(); ASSERT_EQUALS( wanted , out ); } }; class inc1 : public Base { public: void run() { BSONObj m = BSON( "$inc" << BSON( "x" << 1 ) ); test( m , BSON( "x" << 5 ) , BSON( "x" << 6 ) ); test( m , BSON( "a" << 5 ) , BSON( "a" << 5 << "x" << 1 ) ); test( m , BSON( "z" << 5 ) , BSON( "x" << 1 << "z" << 5 ) ); } }; class inc2 : public Base { public: void run() { BSONObj m = BSON( "$inc" << BSON( "a.b" << 1 ) ); test( m , BSONObj() , BSON( "a" << BSON( "b" << 1 ) ) ); test( m , BSON( "a" << BSON( "b" << 2 ) ) , BSON( "a" << BSON( "b" << 3 ) ) ); m = BSON( "$inc" << BSON( "a.b" << 1 << "a.c" << 1 ) ); test( m , BSONObj() , BSON( "a" << BSON( "b" << 1 << "c" << 1 ) ) ); } }; class set1 : public Base { public: void run() { test( BSON( "$set" << BSON( "x" << 17 ) ) , BSONObj() , BSON( "x" << 17 ) ); test( BSON( "$set" << BSON( "x" << 17 ) ) , BSON( "x" << 5 ) , BSON( "x" << 17 ) ); test( BSON( "$set" << BSON( "x.a" << 17 ) ) , BSON( "z" << 5 ) , BSON( "x" << BSON( "a" << 17 )<< "z" << 5 ) ); } }; class push1 : public Base { public: void run() { test( BSON( "$push" << BSON( "a" << 5 ) ) , fromjson( "{a:[1]}" ) , fromjson( "{a:[1,5]}" ) ); } }; class IncRewriteInPlace { public: void run() { BSONObj obj = BSON( "a" << 2 ); BSONObj mod = BSON( "$inc" << BSON( "a" << 1 ) ); ModSet modSet( mod ); auto_ptr modSetState = modSet.prepare( obj ); ASSERT_TRUE( modSetState->canApplyInPlace() ); modSetState->applyModsInPlace(false); ASSERT_EQUALS( BSON( "$set" << BSON( "a" << 3 ) ), modSetState->getOpLogRewrite() ); } }; // Check if not applying in place changes anything. class InRewriteForceNotInPlace { public: void run() { BSONObj obj = BSON( "a" << 2 ); BSONObj mod = BSON( "$inc" << BSON( "a" << 1 ) ); ModSet modSet( mod ); auto_ptr modSetState = modSet.prepare( obj ); modSetState->createNewFromMods(); ASSERT_EQUALS( BSON( "$set" << BSON( "a" << 3 ) ), modSetState->getOpLogRewrite() ); } }; class IncRewriteNestedArray { public: void run() { BSONObj obj = BSON( "a" << BSON_ARRAY( 2 ) ); BSONObj mod = BSON( "$inc" << BSON( "a.0" << 1 ) ); ModSet modSet( mod ); auto_ptr modSetState = modSet.prepare( obj ); ASSERT_TRUE( modSetState->canApplyInPlace() ); modSetState->applyModsInPlace(false); ASSERT_EQUALS( BSON( "$set" << BSON( "a.0" << 3 ) ), modSetState->getOpLogRewrite() ); } }; class IncRewriteExistingField { public: void run() { BSONObj obj = BSON( "a" << 2 ); BSONObj mod = BSON( "$inc" << BSON( "a" << 1 ) << "$set" << BSON( "b" << 2) ); ModSet modSet( mod ); auto_ptr modSetState = modSet.prepare( obj ); ASSERT_FALSE( modSetState->canApplyInPlace() ); modSetState->createNewFromMods(); ASSERT_EQUALS( BSON( "$set" << BSON( "a" << 3 << "b" << 2)), modSetState->getOpLogRewrite() ); } }; class IncRewriteNonExistingField { public: void run() { BSONObj obj = BSON( "c" << 1 ); BSONObj mod = BSON( "$inc" << BSON( "a" << 1 ) << "$set" << BSON( "b" << 2) ); ModSet modSet( mod ); auto_ptr modSetState = modSet.prepare( obj ); ASSERT_FALSE( modSetState->canApplyInPlace() ); modSetState->createNewFromMods(); ASSERT_EQUALS( BSON( "$set" << BSON( "a" << 1 << "b" << 2)), modSetState->getOpLogRewrite() ); } }; // A no-op $setOnInsert would not interfere with in-placeness and won't log. class SetOnInsertRewriteInPlace { public: void run() { BSONObj obj = BSON( "a" << 2 ); BSONObj mod = BSON( "$setOnInsert" << BSON( "a" << 1 ) ); ModSet modSet( mod ); auto_ptr modSetState = modSet.prepare( obj ); ASSERT_TRUE( modSetState->canApplyInPlace() ); modSetState->applyModsInPlace(false); ASSERT_EQUALS( BSONObj(), modSetState->getOpLogRewrite() ); } }; // A no-op $setOnInsert that was forced not in-place doesn't log. class SetOnInsertRewriteExistingField { public: void run() { BSONObj obj = BSON( "a" << 2 ); BSONObj mod = BSON( "$setOnInsert" << BSON( "a" << 1 ) ); ModSet modSet( mod ); auto_ptr modSetState = modSet.prepare( obj ); // force not in place modSetState->createNewFromMods(); ASSERT_EQUALS( BSONObj(), modSetState->getOpLogRewrite() ); } }; // Push is never applied in place class PushRewriteExistingField { public: void run() { BSONObj obj = BSON( "a" << BSON_ARRAY( 1 << 2 ) ); BSONObj mod = BSON( "$push" << BSON( "a" << 3 ) ); ModSet modSet( mod ); auto_ptr modSetState = modSet.prepare( obj ); ASSERT_FALSE( modSetState->canApplyInPlace() ); modSetState->createNewFromMods(); ASSERT_EQUALS( BSON( "$set" << BSON( "a.2" << 3 ) ), modSetState->getOpLogRewrite() ); } }; class PushSliceRewriteExistingField { public: void run() { BSONObj obj = BSON( "a" << BSON_ARRAY( 1 << 2 ) ); // { $push : { a : { $each : [ 3 ] , $slice :-2 } } } BSONObj pushObj = BSON( "$each" << BSON_ARRAY( 3 ) << "$slice" << -2 ); BSONObj mod = BSON( "$push" << BSON( "a" << pushObj ) ); ModSet modSet( mod ); auto_ptr modSetState = modSet.prepare( obj ); ASSERT_FALSE( modSetState->canApplyInPlace() ); modSetState->createNewFromMods(); ASSERT_EQUALS( BSON( "$set" << BSON( "a" << BSON_ARRAY( 2 << 3 ) ) ), modSetState->getOpLogRewrite() ); } }; class PushSortRewriteExistingField { public: void run() { BSONObj obj = BSON( "x" << BSON_ARRAY( BSON( "a" << 1 ) << BSON( "a" << 2 ) ) ); // { $push : { a : { $each : [ {a:3} ], $slice:-2, $sort : {a:1} } } } BSONObj pushObj = BSON( "$each" << BSON_ARRAY( BSON( "a" << 3 ) ) << "$slice" << -2 << "$sort" << BSON( "a" << 1 ) ); BSONObj mod = BSON( "$push" << BSON( "x" << pushObj ) ); ModSet modSet( mod ); auto_ptr modSetState = modSet.prepare( obj ); ASSERT_FALSE( modSetState->canApplyInPlace() ); modSetState->createNewFromMods(); ASSERT_EQUALS( BSON( "$set" << BSON( "x" << BSON_ARRAY( BSON( "a" << 2 ) << BSON( "a" << 3 ) ) ) ), modSetState->getOpLogRewrite() ); } }; class PushRewriteNonExistingField { public: void run() { BSONObj obj = BSON( "b" << 1 ); BSONObj mod = BSON( "$push" << BSON( "a" << 1 ) ); ModSet modSet( mod ); auto_ptr modSetState = modSet.prepare( obj ); ASSERT_FALSE( modSetState->canApplyInPlace() ); modSetState->createNewFromMods(); ASSERT_EQUALS( BSON( "$set" << BSON( "a" << BSON_ARRAY( 1 ) ) ), modSetState->getOpLogRewrite() ); } }; class PushSliceRewriteNonExistingField { public: void run() { BSONObj obj = BSON( "b" << 1 ); // { $push : { a : { $each : [ 1 , 2 ] , $slice:-2 } } } BSONObj pushObj = BSON( "$each" << BSON_ARRAY( 1 << 2) << "$slice" << -2 ); BSONObj mod = BSON( "$push" << BSON( "a" << pushObj ) ); ModSet modSet( mod ); auto_ptr modSetState = modSet.prepare( obj ); ASSERT_FALSE( modSetState->canApplyInPlace() ); modSetState->createNewFromMods(); ASSERT_EQUALS( BSON( "$set" << BSON( "a" << BSON_ARRAY( 1 << 2 ) ) ), modSetState->getOpLogRewrite() ); } }; class PushSliceRewriteNested { public: void run() { BSONObj obj = fromjson( "{ a:{ b:[ 1, 2 ] } }" ); BSONObj mod = fromjson( "{ $push: { 'a.b': { $each: [3] , $slice:-2 } } }" ); ModSet modSet( mod ); auto_ptr modSetState = modSet.prepare( obj ); ASSERT_FALSE( modSetState->canApplyInPlace() ); modSetState->createNewFromMods(); ASSERT_EQUALS( BSON( "$set" << BSON( "a.b" << BSON_ARRAY( 2 << 3 ) ) ), modSetState->getOpLogRewrite() ); } }; class PushSortRewriteNonExistingField { public: void run() { BSONObj obj = BSON( "b" << 1 ); // { $push : { x : { $each : [ {a:1},{a:2} ], $slice:-2, $sort : {a:1} } } } BSONObj pushObj = BSON( "$each" << BSON_ARRAY( BSON( "a" << 1 ) << BSON( "a" << 2 ) ) << "$slice" << -2 << "$sort" << BSON( "a" << 1 ) ); BSONObj mod = BSON( "$push" << BSON( "x" << pushObj ) ); ModSet modSet( mod ); auto_ptr modSetState = modSet.prepare( obj ); ASSERT_FALSE( modSetState->canApplyInPlace() ); modSetState->createNewFromMods(); ASSERT_EQUALS( BSON( "$set" << BSON( "x" << BSON_ARRAY( BSON( "a" << 1 ) << BSON( "a" << 2 ) ) ) ), modSetState->getOpLogRewrite() ); } }; class PushAllRewriteExistingField { public: void run() { BSONObj obj = BSON( "a" << BSON_ARRAY( 1 << 2 ) ); BSONObj modAll = BSON( "$pushAll" << BSON( "a" << BSON_ARRAY( 3 << 4 << 5 ) ) ); ModSet modSetAll( modAll ); auto_ptr modSetStateAll = modSetAll.prepare( obj ); ASSERT_FALSE( modSetStateAll->canApplyInPlace() ); modSetStateAll->createNewFromMods(); ASSERT_EQUALS( BSON( "$set" << BSON( "a" << BSON_ARRAY( 1 << 2 << 3 << 4 << 5) ) ), modSetStateAll->getOpLogRewrite() ); } }; class PushAllRewriteNonExistingField { public: void run() { BSONObj obj = BSON( "b" << 1 ); BSONObj modAll = BSON( "$pushAll" << BSON( "a" << BSON_ARRAY( 1 << 2 << 3) ) ); ModSet modSetAll( modAll ); auto_ptr modSetStateAll = modSetAll.prepare( obj ); ASSERT_FALSE( modSetStateAll->canApplyInPlace() ); modSetStateAll->createNewFromMods(); ASSERT_EQUALS( BSON( "$set" << BSON( "a" << BSON_ARRAY( 1 << 2 << 3 ) ) ), modSetStateAll->getOpLogRewrite() ); } }; // Pull is only in place if it's a no-op. class PullRewriteInPlace { public: void run() { BSONObj obj = BSON( "a" << BSON_ARRAY( 1 << 2 ) ); BSONObj modMatcher = BSON( "$pull" << BSON( "a" << BSON( "$gt" << 3 ) ) ); ModSet modSetMatcher( modMatcher ); auto_ptr modSetStateMatcher = modSetMatcher.prepare( obj ); ASSERT_TRUE( modSetStateMatcher->canApplyInPlace() ); modSetStateMatcher->applyModsInPlace(false); ASSERT_EQUALS( BSON( "$set" << BSON( "a" << BSON_ARRAY( 1 << 2) ) ), modSetStateMatcher->getOpLogRewrite() ); } }; class PullRewriteForceNotInPlace { public: void run() { BSONObj obj = BSON( "a" << BSON_ARRAY( 1 << 2 ) ); BSONObj modMatcher = BSON( "$pull" << BSON( "a" << BSON( "$gt" << 3 ) ) ); ModSet modSetMatcher( modMatcher ); auto_ptr modSetStateMatcher = modSetMatcher.prepare( obj ); modSetStateMatcher->createNewFromMods(); ASSERT_EQUALS( BSON( "$set" << BSON( "a" << BSON_ARRAY( 1 << 2) ) ), modSetStateMatcher->getOpLogRewrite() ); } }; class PullRewriteNonExistingUnsets { public: void run() { BSONObj obj; BSONObj modMatcher = BSON( "$pull" << BSON( "a" << BSON( "$gt" << 3 ) ) ); ModSet modSetMatcher( modMatcher ); auto_ptr modSetStateMatcher = modSetMatcher.prepare( obj ); ASSERT_FALSE( modSetStateMatcher->canApplyInPlace() ); modSetStateMatcher->createNewFromMods(); ASSERT_EQUALS( BSON( "$unset" << BSON( "a" << 1 ) ), modSetStateMatcher->getOpLogRewrite() ); } }; class PullRewriteExistingField { public: void run() { BSONObj obj = BSON( "a" << BSON_ARRAY( 1 << 2 ) ); BSONObj mod = BSON( "$pull" << BSON( "a" << 1 ) ); ModSet modSet( mod ); auto_ptr modSetState = modSet.prepare( obj ); ASSERT_FALSE( modSetState->canApplyInPlace() ); modSetState->createNewFromMods(); ASSERT_EQUALS( BSON( "$set" << BSON( "a" << BSON_ARRAY( 2 ) ) ), modSetState->getOpLogRewrite() ); } }; class PullRewriteLastExistingField { public: void run() { // check last pull corner case BSONObj obj = BSON( "a" << BSON_ARRAY( 2 ) ); BSONObj mod = BSON( "$pull" << BSON( "a" << 2 ) ); ModSet modSetLast( mod ); auto_ptr modSetStateLast = modSetLast.prepare( obj ); ASSERT_FALSE( modSetStateLast->canApplyInPlace() ); modSetStateLast->createNewFromMods(); ASSERT_EQUALS( BSON( "$set" << BSON( "a" << BSONArray() ) ), modSetStateLast->getOpLogRewrite() ); } }; class PullRewriteNonExistingField { public: void run() { BSONObj obj = BSON( "b" << 1 ); BSONObj mod = BSON( "$pull" << BSON( "a" << 1 ) ); ModSet modSet( mod ); auto_ptr modSetState = modSet.prepare( obj ); ASSERT_FALSE( modSetState->canApplyInPlace() ); modSetState->createNewFromMods(); ASSERT_EQUALS( BSON( "$unset" << BSON( "a" << 1 ) ), modSetState->getOpLogRewrite() ); } }; class TwoNestedPulls { public: void run() { BSONObj obj = fromjson( "{ a:{ b:[ 1, 2 ], c:[ 1, 2 ] } }" ); BSONObj mod = fromjson( "{ $pull:{ 'a.b':2, 'a.c':2 } }" ); ModSet modSet( mod ); auto_ptr modSetState = modSet.prepare( obj ); ASSERT_FALSE( modSetState->canApplyInPlace() ); modSetState->createNewFromMods(); ASSERT_EQUALS( fromjson( "{ $set:{ 'a.b':[ 1 ] , 'a.c':[ 1 ] } }" ), modSetState->getOpLogRewrite() ); } }; // Pop is only applied in place if the target array remains the same size (i.e. if // it is empty already. class PopRewriteEmptyArray { public: void run() { BSONObj obj = BSON( "a" << BSONArray() ); BSONObj mod = BSON( "$pop" << BSON( "a" << 1 ) ); ModSet modSet( mod ); auto_ptr modSetState = modSet.prepare( obj ); ASSERT_TRUE( modSetState->canApplyInPlace() ); modSetState->applyModsInPlace(false); ASSERT_EQUALS( BSON( "$set" << BSON( "a" << BSONArray() ) ), modSetState->getOpLogRewrite() ); } }; class PopRewriteLastElement { public: void run() { BSONObj obj = BSON( "a" << BSON_ARRAY( 1 ) ); BSONObj mod = BSON( "$pop" << BSON( "a" << 1 ) ); ModSet modSet( mod ); auto_ptr modSetState = modSet.prepare( obj ); ASSERT_FALSE( modSetState->canApplyInPlace() ); modSetState->createNewFromMods(); ASSERT_EQUALS( BSON( "$set" << BSON( "a" << BSONArray() ) ), modSetState->getOpLogRewrite() ); } }; class PopRewriteExistingField { public: void run() { BSONObj obj = BSON( "a" << BSON_ARRAY( 1 << 2) ); BSONObj mod = BSON( "$pop" << BSON( "a" << 1 ) ); ModSet modSet( mod ); auto_ptr modSetState = modSet.prepare( obj ); ASSERT_FALSE( modSetState->canApplyInPlace() ); modSetState->createNewFromMods(); ASSERT_EQUALS( BSON( "$set" << BSON( "a" << BSON_ARRAY( 1 ) ) ), modSetState->getOpLogRewrite() ); } }; class PopRewriteNonExistingField { public: void run() { BSONObj obj = BSON( "a" << BSON_ARRAY( 1 ) ); BSONObj mod = BSON( "$pop" << BSON( "b" << 1 ) ); ModSet modSet( mod ); auto_ptr modSetState = modSet.prepare( obj ); ASSERT_FALSE( modSetState->canApplyInPlace() ); modSetState->createNewFromMods(); ASSERT_EQUALS( BSON( "$unset" << BSON( "b" << 1 ) ), modSetState->getOpLogRewrite() ); } }; // AddToSet is in place if it is a no-op. class AddToSetRewriteInPlace { public: void run() { BSONObj obj = BSON( "a" << BSON_ARRAY( 1 << 2 ) ); BSONObj mod = BSON( "$addToSet" << BSON( "a" << 1 ) ); ModSet modSet( mod ); auto_ptr modSetState = modSet.prepare( obj ); ASSERT_TRUE( modSetState->canApplyInPlace() ); modSetState->applyModsInPlace(false); ASSERT_EQUALS( BSON( "$set" << BSON( "a" << BSON_ARRAY( 1 << 2 ) ) ), modSetState->getOpLogRewrite() ); } }; class AddToSetRewriteForceNotInPlace { public: void run() { BSONObj obj = BSON( "a" << BSON_ARRAY( 1 << 2 ) ); BSONObj mod = BSON( "$addToSet" << BSON( "a" << 1 ) ); ModSet modSet( mod ); auto_ptr modSetState = modSet.prepare( obj ); modSetState->createNewFromMods(); ASSERT_EQUALS( BSON( "$set" << BSON( "a.0" << 1 ) ), modSetState->getOpLogRewrite() ); } }; class AddToSetRewriteExistingField { public: void run() { BSONObj obj = BSON( "a" << BSON_ARRAY( 1 ) ); BSONObj mod = BSON( "$addToSet" << BSON( "a" << 2 ) ); ModSet modSet( mod ); auto_ptr modSetState = modSet.prepare( obj ); ASSERT_FALSE( modSetState->canApplyInPlace() ); modSetState->createNewFromMods(); ASSERT_EQUALS( BSON( "$set" << BSON( "a.1" << 2 ) ), modSetState->getOpLogRewrite() ); } }; class AddToSetRewriteNonExistingField { public: void run() { BSONObj obj = BSON( "a" << BSON_ARRAY( 1 ) ); BSONObj mod = BSON( "$addToSet" << BSON( "b" << 1 ) ); ModSet modSet( mod ); auto_ptr modSetState = modSet.prepare( obj ); ASSERT_FALSE( modSetState->canApplyInPlace() ); modSetState->createNewFromMods(); ASSERT_EQUALS( BSON( "$set" << BSON( "b" << BSON_ARRAY( 1 ) ) ), modSetState->getOpLogRewrite() ); } }; // Rename doesn't log if both fields are not present. class RenameRewriteBothNonExistent { public: void run() { BSONObj obj = BSON( "a" << 1 ); BSONObj mod = BSON( "$rename" << BSON( "b" << "c" ) ); ModSet modSet( mod ); auto_ptr modSetState = modSet.prepare( obj ); ASSERT_TRUE( modSetState->canApplyInPlace() ); modSetState->applyModsInPlace(false); ASSERT_EQUALS( BSONObj(), modSetState->getOpLogRewrite() ); } }; class RenameRewriteExistingToField { public: void run() { BSONObj obj = BSON( "b" << 100 ); BSONObj mod = BSON( "$rename" << BSON( "a" << "b" ) ); ModSet modSet( mod ); auto_ptr modSetState = modSet.prepare( obj ); ASSERT_TRUE( modSetState->canApplyInPlace() ); modSetState->applyModsInPlace(false); ASSERT_EQUALS( BSONObj(), modSetState->getOpLogRewrite() ); } }; class RenameRewriteExistingFromField { public: void run() { BSONObj obj = BSON( "a" << 100 ); BSONObj mod = BSON( "$rename" << BSON( "a" << "b" ) ); ModSet modSet( mod ); auto_ptr modSetState = modSet.prepare( obj ); ASSERT_FALSE( modSetState->canApplyInPlace() ); modSetState->createNewFromMods(); ASSERT_EQUALS( BSON( "$set" << BSON( "b" << 100 ) << "$unset" << BSON ( "a" << 1 ) ), modSetState->getOpLogRewrite() ); } }; class RenameRewriteBothExistingField { public: void run() { BSONObj obj = BSON( "a" << 100 << "b" << 200); BSONObj mod = BSON( "$rename" << BSON( "a" << "b" ) ); ModSet modSet( mod ); auto_ptr modSetState = modSet.prepare( obj ); ASSERT_FALSE( modSetState->canApplyInPlace() ); modSetState->createNewFromMods(); ASSERT_EQUALS( BSON( "$set" << BSON( "b" << 100 ) << "$unset" << BSON ( "a" << 1 ) ), modSetState->getOpLogRewrite() ); } }; // $bit is never applied in place currently class BitRewriteExistingField { public: void run() { BSONObj obj = BSON( "a" << 0 ); BSONObj mod = BSON( "$bit" << BSON( "a" << BSON( "or" << 1 ) ) ); ModSet modSet( mod ); auto_ptr modSetState = modSet.prepare( obj ); ASSERT_FALSE( modSetState->canApplyInPlace() ); modSetState->createNewFromMods(); ASSERT_EQUALS( BSON( "$set" << BSON( "a" << 1 ) ), modSetState->getOpLogRewrite() ); } }; class BitRewriteNonExistingField { public: void run() { BSONObj obj = BSON( "a" << 0 ); BSONObj mod = BSON( "$bit" << BSON( "b" << BSON( "or" << 1 ) ) ); ModSet modSet( mod ); auto_ptr modSetState = modSet.prepare( obj ); ASSERT_FALSE( modSetState->canApplyInPlace() ); modSetState->createNewFromMods(); ASSERT_EQUALS( BSON( "$set" << BSON( "b" << 1 ) ), modSetState->getOpLogRewrite() ); } }; class SetIsNotRewritten { public: void run() { BSONObj obj = BSON( "a" << 0 ); BSONObj mod = BSON( "$set" << BSON( "b" << 1 ) ); ModSet modSet( mod ); auto_ptr modSetState = modSet.prepare( obj ); ASSERT_FALSE( modSetState->canApplyInPlace() ); modSetState->createNewFromMods(); ASSERT_EQUALS( BSON( "$set" << BSON( "b" << 1 ) ), modSetState->getOpLogRewrite() ); } }; class UnsetIsNotRewritten { public: void run() { BSONObj obj = BSON( "a" << 0 ); BSONObj mod = BSON( "$unset" << BSON( "a" << 1 ) ); ModSet modSet( mod ); auto_ptr modSetState = modSet.prepare( obj ); ASSERT_FALSE( modSetState->canApplyInPlace() ); modSetState->createNewFromMods(); ASSERT_EQUALS( BSON( "$unset" << BSON( "a" << 1 ) ), modSetState->getOpLogRewrite() ); } }; class MultiSets { public: void run() { BSONObj obj = BSON( "_id" << 1 << "a" << 1 << "b" << 1 ); BSONObj mod = BSON( "$set" << BSON( "a" << 2 << "b" << 2 ) ); ModSet modSet( mod ); auto_ptr modSetState = modSet.prepare( obj ); ASSERT_TRUE( modSetState->canApplyInPlace() ); ASSERT_EQUALS( mod, modSetState->getOpLogRewrite() ); } }; class PositionalWithoutElemMatchKey { public: void run() { BSONObj querySpec = BSONObj(); BSONObj modSpec = BSON( "$set" << BSON( "a.$" << 1 ) ); ModSet modSet( modSpec ); // A positional operator must be replaced with an array index before calling // prepare(). ASSERT_THROWS( modSet.prepare( querySpec ), UserException ); } }; class PositionalWithoutNestedElemMatchKey { public: void run() { BSONObj querySpec = BSONObj(); BSONObj modSpec = BSON( "$set" << BSON( "a.b.c.$.e.f" << 1 ) ); ModSet modSet( modSpec ); // A positional operator must be replaced with an array index before calling // prepare(). ASSERT_THROWS( modSet.prepare( querySpec ), UserException ); } }; class DbrefPassesPositionalValidation { public: void run() { BSONObj querySpec = BSONObj(); BSONObj modSpec = BSON( "$set" << BSON( "a.$ref" << "foo" << "a.$id" << 0 ) ); ModSet modSet( modSpec ); // A positional operator must be replaced with an array index before calling // prepare(), but $ prefixed fields encoding dbrefs are allowed. modSet.prepare( querySpec ); // Does not throw. } }; class NoPositionalValidationOnReplication { public: void run() { BSONObj querySpec = BSONObj(); BSONObj modSpec = BSON( "$set" << BSON( "a.$" << 1 ) ); ModSet modSet( modSpec, IndexPathSet(), true ); // No positional operator validation is performed if a ModSet is 'forReplication'. modSet.prepare( querySpec ); // Does not throw. } }; class NoPositionalValidationOnPartialFixedArrayReplication { public: void run() { BSONObj querySpec = BSONObj( BSON( "a.b" << 1 ) ); BSONObj modSpec = BSON( "$set" << BSON( "a.$.b.$" << 1 ) ); ModSet modSet( modSpec, IndexPathSet(), true ); // Attempt to fix the positional operator fields. scoped_ptr fixedMods( modSet.fixDynamicArray( "0" ) ); // The first positional field is replaced, but the second is not (until SERVER-831 // is implemented). ASSERT( fixedMods->haveModForField( "a.0.b.$" ) ); // No positional operator validation is performed if a ModSet is 'forReplication', // even after an attempt to fix the positional operator fields. fixedMods->prepare( querySpec ); // Does not throw. } }; class CreateNewFromQueryExcludeNot { public: void run() { BSONObj querySpec = BSON( "a" << BSON( "$not" << BSON( "$lt" << 1 ) ) ); BSONObj modSpec = BSON( "$set" << BSON( "b" << 1 ) ); ModSet modSet( modSpec ); // Because a $not operator is applied to the 'a' field, the 'a' field is excluded // from the resulting document. ASSERT_EQUALS( BSON( "b" << 1 ), modSet.createNewFromQuery( querySpec ) ); } }; }; namespace basic { class Base : public ClientBase { protected: virtual const char * ns() = 0; virtual void dotest() = 0; void insert( const BSONObj& o ) { client().insert( ns() , o ); } void update( const BSONObj& m ) { client().update( ns() , BSONObj() , m ); } BSONObj findOne() { return client().findOne( ns() , BSONObj() ); } void test( const char* initial , const char* mod , const char* after ) { test( fromjson( initial ) , fromjson( mod ) , fromjson( after ) ); } void test( const BSONObj& initial , const BSONObj& mod , const BSONObj& after ) { client().dropCollection( ns() ); insert( initial ); update( mod ); ASSERT_EQUALS( after , findOne() ); client().dropCollection( ns() ); } public: Base() {} virtual ~Base() { } void run() { client().dropCollection( ns() ); dotest(); client().dropCollection( ns() ); } }; class SingleTest : public Base { virtual BSONObj initial() = 0; virtual BSONObj mod() = 0; virtual BSONObj after() = 0; void dotest() { test( initial() , mod() , after() ); } }; class inc1 : public SingleTest { virtual BSONObj initial() { return BSON( "_id" << 1 << "x" << 1 ); } virtual BSONObj mod() { return BSON( "$inc" << BSON( "x" << 2 ) ); } virtual BSONObj after() { return BSON( "_id" << 1 << "x" << 3 ); } virtual const char * ns() { return "unittests.inc1"; } }; class inc2 : public SingleTest { virtual BSONObj initial() { return BSON( "_id" << 1 << "x" << 1 ); } virtual BSONObj mod() { return BSON( "$inc" << BSON( "x" << 2.5 ) ); } virtual BSONObj after() { return BSON( "_id" << 1 << "x" << 3.5 ); } virtual const char * ns() { return "unittests.inc2"; } }; class inc3 : public SingleTest { virtual BSONObj initial() { return BSON( "_id" << 1 << "x" << 537142123123LL ); } virtual BSONObj mod() { return BSON( "$inc" << BSON( "x" << 2 ) ); } virtual BSONObj after() { return BSON( "_id" << 1 << "x" << 537142123125LL ); } virtual const char * ns() { return "unittests.inc3"; } }; class inc4 : public SingleTest { virtual BSONObj initial() { return BSON( "_id" << 1 << "x" << 537142123123LL ); } virtual BSONObj mod() { return BSON( "$inc" << BSON( "x" << 2LL ) ); } virtual BSONObj after() { return BSON( "_id" << 1 << "x" << 537142123125LL ); } virtual const char * ns() { return "unittests.inc4"; } }; class inc5 : public SingleTest { virtual BSONObj initial() { return BSON( "_id" << 1 << "x" << 537142123123LL ); } virtual BSONObj mod() { return BSON( "$inc" << BSON( "x" << 2.0 ) ); } virtual BSONObj after() { return BSON( "_id" << 1 << "x" << 537142123125LL ); } virtual const char * ns() { return "unittests.inc5"; } }; class inc6 : public Base { virtual const char * ns() { return "unittests.inc6"; } virtual BSONObj initial() { return BSONObj(); } virtual BSONObj mod() { return BSONObj(); } virtual BSONObj after() { return BSONObj(); } void dotest() { long long start = numeric_limits::max() - 5; long long max = numeric_limits::max() + 5ll; client().insert( ns() , BSON( "x" << (int)start ) ); ASSERT( findOne()["x"].type() == NumberInt ); while ( start < max ) { update( BSON( "$inc" << BSON( "x" << 1 ) ) ); start += 1; ASSERT_EQUALS( start , findOne()["x"].numberLong() ); // SERVER-2005 } ASSERT( findOne()["x"].type() == NumberLong ); } }; class bit1 : public Base { const char * ns() { return "unittests.bit1"; } void dotest() { test( BSON( "_id" << 1 << "x" << 3 ) , BSON( "$bit" << BSON( "x" << BSON( "and" << 2 ) ) ) , BSON( "_id" << 1 << "x" << ( 3 & 2 ) ) ); test( BSON( "_id" << 1 << "x" << 1 ) , BSON( "$bit" << BSON( "x" << BSON( "or" << 4 ) ) ) , BSON( "_id" << 1 << "x" << ( 1 | 4 ) ) ); test( BSON( "_id" << 1 << "x" << 3 ) , BSON( "$bit" << BSON( "x" << BSON( "and" << 2 << "or" << 8 ) ) ) , BSON( "_id" << 1 << "x" << ( ( 3 & 2 ) | 8 ) ) ); test( BSON( "_id" << 1 << "x" << 3 ) , BSON( "$bit" << BSON( "x" << BSON( "or" << 2 << "and" << 8 ) ) ) , BSON( "_id" << 1 << "x" << ( ( 3 | 2 ) & 8 ) ) ); } }; class unset : public Base { const char * ns() { return "unittests.unset"; } void dotest() { test( "{_id:1,x:1}" , "{$unset:{x:1}}" , "{_id:1}" ); } }; class setswitchint : public Base { const char * ns() { return "unittests.int1"; } void dotest() { test( BSON( "_id" << 1 << "x" << 1 ) , BSON( "$set" << BSON( "x" << 5.6 ) ) , BSON( "_id" << 1 << "x" << 5.6 ) ); test( BSON( "_id" << 1 << "x" << 5.6 ) , BSON( "$set" << BSON( "x" << 1 ) ) , BSON( "_id" << 1 << "x" << 1 ) ); } }; }; class All : public Suite { public: All() : Suite( "update" ) { } void setupTests() { add< ModId >(); add< ModNonmodMix >(); add< InvalidMod >(); add< ModNotFirst >(); add< ModDuplicateFieldSpec >(); add< IncNonNumber >(); add< PushAllNonArray >(); add< PullAllNonArray >(); add< IncTargetNonNumber >(); add< SetNum >(); add< SetString >(); add< SetStringDifferentLength >(); add< SetStringToNum >(); add< SetStringToNumInPlace >(); add< SetOnInsertFromEmpty >(); add< SetOnInsertFromNonExistent >(); add< SetOnInsertFromNonExistentWithQuery >(); add< SetOnInsertFromNonExistentWithQueryOverField >(); add< SetOnInsertMissingField >(); add< SetOnInsertExisting >(); add< SetOnInsertMixed >(); add< SetOnInsertMissingParent >(); add< ModDotted >(); add< SetInPlaceDotted >(); add< SetRecreateDotted >(); add< SetMissingDotted >(); add< SetAdjacentDotted >(); add< IncMissing >(); add< MultiInc >(); add< UnorderedNewSet >(); add< UnorderedNewSetAdjacent >(); add< ArrayEmbeddedSet >(); add< AttemptEmbedInExistingNum >(); add< AttemptEmbedConflictsWithOtherSet >(); add< ModMasksEmbeddedConflict >(); add< ModOverwritesExistingObject >(); add< InvalidEmbeddedSet >(); add< UpsertMissingEmbedded >(); add< Push >(); add< PushInvalidEltType >(); add< PushConflictsWithOtherMod >(); add< PushFromNothing >(); add< PushFromEmpty >(); add< PushInsideNothing >(); add< CantPushInsideOtherMod >(); add< CantPushTwice >(); add< SetEncapsulationConflictsWithExistingType >(); add< CantPushToParent >(); add< PushEachSimple >(); add< PushEachFromEmpty >(); add< PushSliceBelowFull >(); add< PushSliceReachedFullExact >(); add< PushSliceReachedFullWithEach >(); add< PushSliceReachedFullWithBoth >(); add< PushSliceToZero >(); add< PushSliceToZeroFromNothing >(); add< PushSliceFromNothing >(); add< PushSliceLongerThanSliceFromNothing >(); add< PushSliceFromEmpty >(); add< PushSliceLongerThanSliceFromEmpty >(); add< PushSliceTwoFields >(); add< PushSliceAndNormal >(); add< PushSliceTwoFieldsConflict >(); add< PushSliceAndNormalConflict >(); add< PushSliceInvalidEachType >(); add< PushSliceInvalidSliceType >(); add< PushSliceInvalidSliceValue >(); add< PushSliceInvalidSliceDouble >(); add< PushSliceValidSliceDouble >(); add< PushSliceInvalidSlice >(); add< PushSortBelowFull >(); add< PushSortReachedFullExact >(); add< PushSortReachedFullWithBoth >(); add< PushSortToZero >(); add< PushSortToZeroFromNothing >(); add< PushSortFromNothing >(); add< PushSortLongerThanSliceFromNothing >(); add< PushSortFromEmpty >(); add< PushSortLongerThanSliceFromEmpty >(); add< PushSortSortMixed >(); add< PushSortSortOutOfOrderFields >(); add< PushSortSortExtraFields >(); add< PushSortSortMissingFields >(); add< PushSortSortNestedFields >(); add< PushSortInvalidSortPattern >(); add< PushSortInvalidEachType >(); add< PushSortInvalidBaseArray >(); add< PushSortInvalidSortType >(); add< PushSortInvalidSortValue >(); add< PushSortInvalidSortDouble >(); add< PushSortValidSortDouble >(); add< PushSortInvalidSortSort >(); add< PushSortInvalidSortSortOrder >(); add< PushSortInvertedSortAndSlice >(); add< PushSortInvalidDuplicatedSort >(); add< PushSortInvalidMissingSliceTo >(); add< CantIncParent >(); add< DontDropEmpty >(); add< InsertInEmpty >(); add< IndexParentOfMod >(); add< IndexModSet >(); add< PreserveIdWithIndex >(); add< CheckNoMods >(); add< UpdateMissingToNull >(); add< TwoModsWithinDuplicatedField >(); add< ThreeModsWithinDuplicatedField >(); add< TwoModsBeforeExistingField >(); add< ModSetTests::internal1 >(); add< ModSetTests::inc1 >(); add< ModSetTests::inc2 >(); add< ModSetTests::set1 >(); add< ModSetTests::push1 >(); add< ModSetTests::IncRewriteInPlace >(); add< ModSetTests::InRewriteForceNotInPlace >(); add< ModSetTests::IncRewriteNestedArray >(); add< ModSetTests::IncRewriteExistingField >(); add< ModSetTests::IncRewriteNonExistingField >(); add< ModSetTests::SetOnInsertRewriteInPlace >(); add< ModSetTests::SetOnInsertRewriteExistingField >(); add< ModSetTests::PushRewriteExistingField >(); add< ModSetTests::PushSliceRewriteExistingField >(); add< ModSetTests::PushSortRewriteExistingField >(); add< ModSetTests::PushRewriteNonExistingField >(); add< ModSetTests::PushSliceRewriteNonExistingField >(); add< ModSetTests::PushSliceRewriteNested >(); add< ModSetTests::PushSortRewriteNonExistingField >(); add< ModSetTests::PushAllRewriteExistingField >(); add< ModSetTests::PushAllRewriteNonExistingField >(); add< ModSetTests::PullRewriteInPlace >(); add< ModSetTests::PullRewriteForceNotInPlace >(); add< ModSetTests::PullRewriteNonExistingUnsets >(); add< ModSetTests::PullRewriteExistingField >(); add< ModSetTests::PullRewriteLastExistingField >(); add< ModSetTests::PullRewriteNonExistingField >(); add< ModSetTests::TwoNestedPulls >(); add< ModSetTests::PopRewriteEmptyArray >(); add< ModSetTests::PopRewriteLastElement >(); add< ModSetTests::PopRewriteExistingField >(); add< ModSetTests::PopRewriteNonExistingField >(); add< ModSetTests::AddToSetRewriteInPlace >(); add< ModSetTests::AddToSetRewriteForceNotInPlace >(); add< ModSetTests::AddToSetRewriteExistingField >(); add< ModSetTests::AddToSetRewriteNonExistingField >(); add< ModSetTests::RenameRewriteBothNonExistent >(); add< ModSetTests::RenameRewriteExistingToField >(); add< ModSetTests::RenameRewriteExistingFromField >(); add< ModSetTests::RenameRewriteBothExistingField >(); add< ModSetTests::BitRewriteExistingField >(); // XXX $bit over non-existing field is missing. Probably out of scope to fix it here. // add< ModSetTests::BitRewriteNonExistingField >(); add< ModSetTests::SetIsNotRewritten >(); add< ModSetTests::UnsetIsNotRewritten >(); add< ModSetTests::MultiSets >(); add< ModSetTests::PositionalWithoutElemMatchKey >(); add< ModSetTests::PositionalWithoutNestedElemMatchKey >(); add< ModSetTests::DbrefPassesPositionalValidation >(); add< ModSetTests::NoPositionalValidationOnReplication >(); add< ModSetTests::NoPositionalValidationOnPartialFixedArrayReplication >(); add< ModSetTests::CreateNewFromQueryExcludeNot >(); add< basic::inc1 >(); add< basic::inc2 >(); add< basic::inc3 >(); add< basic::inc4 >(); add< basic::inc5 >(); add< basic::inc6 >(); add< basic::bit1 >(); add< basic::unset >(); add< basic::setswitchint >(); } } myall; } // namespace UpdateTests