22
33namespace MongoDB \Tests \Operation ;
44
5+ use MongoDB \BSON \Document ;
56use MongoDB \BSON \ObjectId ;
7+ use MongoDB \BSON \PackedArray ;
68use MongoDB \BulkWriteResult ;
79use MongoDB \Collection ;
810use MongoDB \Driver \BulkWrite as Bulk ;
911use MongoDB \Driver \WriteConcern ;
1012use MongoDB \Exception \BadMethodCallException ;
13+ use MongoDB \Model \BSONArray ;
1114use MongoDB \Model \BSONDocument ;
1215use MongoDB \Operation \BulkWrite ;
1316use MongoDB \Tests \CommandObserver ;
17+ use stdClass ;
1418
19+ use function is_array ;
1520use function version_compare ;
1621
1722class BulkWriteFunctionalTest extends FunctionalTestCase
@@ -57,6 +62,60 @@ public function testInserts(): void
5762 $ this ->assertSameDocuments ($ expected , $ this ->collection ->find ());
5863 }
5964
65+ /**
66+ * @dataProvider provideDocumentsWithIds
67+ * @dataProvider provideDocumentsWithoutIds
68+ */
69+ public function testInsertDocumentEncoding ($ document , stdClass $ expectedDocument ): void
70+ {
71+ (new CommandObserver ())->observe (
72+ function () use ($ document , $ expectedDocument ): void {
73+ $ operation = new BulkWrite (
74+ $ this ->getDatabaseName (),
75+ $ this ->getCollectionName (),
76+ [['insertOne ' => [$ document ]]]
77+ );
78+
79+ $ result = $ operation ->execute ($ this ->getPrimaryServer ());
80+
81+ // Replace _id placeholder if necessary
82+ if ($ expectedDocument ->_id === null ) {
83+ $ expectedDocument ->_id = $ result ->getInsertedIds ()[0 ];
84+ }
85+ },
86+ function (array $ event ) use ($ expectedDocument ): void {
87+ $ this ->assertEquals ($ expectedDocument , $ event ['started ' ]->getCommand ()->documents [0 ] ?? null );
88+ }
89+ );
90+ }
91+
92+ public function provideDocumentsWithIds (): array
93+ {
94+ $ expectedDocument = (object ) ['_id ' => 1 ];
95+
96+ return [
97+ 'with_id:array ' => [['_id ' => 1 ], $ expectedDocument ],
98+ 'with_id:object ' => [(object ) ['_id ' => 1 ], $ expectedDocument ],
99+ 'with_id:Serializable ' => [new BSONDocument (['_id ' => 1 ]), $ expectedDocument ],
100+ 'with_id:Document ' => [Document::fromPHP (['_id ' => 1 ]), $ expectedDocument ],
101+ ];
102+ }
103+
104+ public function provideDocumentsWithoutIds (): array
105+ {
106+ /* Note: _id placeholders must be replaced with generated ObjectIds. We
107+ * also clone the value for each data set since tests may need to modify
108+ * the object. */
109+ $ expectedDocument = (object ) ['_id ' => null , 'x ' => 1 ];
110+
111+ return [
112+ 'without_id:array ' => [['x ' => 1 ], clone $ expectedDocument ],
113+ 'without_id:object ' => [(object ) ['x ' => 1 ], clone $ expectedDocument ],
114+ 'without_id:Serializable ' => [new BSONDocument (['x ' => 1 ]), clone $ expectedDocument ],
115+ 'without_id:Document ' => [Document::fromPHP (['x ' => 1 ]), clone $ expectedDocument ],
116+ ];
117+ }
118+
60119 public function testUpdates (): void
61120 {
62121 $ this ->createFixtures (4 );
@@ -93,6 +152,127 @@ public function testUpdates(): void
93152 $ this ->assertSameDocuments ($ expected , $ this ->collection ->find ());
94153 }
95154
155+ /** @dataProvider provideFilterDocuments */
156+ public function testUpdateFilterDocuments ($ filter , stdClass $ expectedFilter ): void
157+ {
158+ (new CommandObserver ())->observe (
159+ function () use ($ filter ): void {
160+ $ operation = new BulkWrite (
161+ $ this ->getDatabaseName (),
162+ $ this ->getCollectionName (),
163+ [
164+ ['replaceOne ' => [$ filter , ['x ' => 1 ]]],
165+ ['updateOne ' => [$ filter , ['$set ' => ['x ' => 1 ]]]],
166+ ['updateMany ' => [$ filter , ['$set ' => ['x ' => 1 ]]]],
167+ ]
168+ );
169+
170+ $ operation ->execute ($ this ->getPrimaryServer ());
171+ },
172+ function (array $ event ) use ($ expectedFilter ): void {
173+ $ this ->assertEquals ($ expectedFilter , $ event ['started ' ]->getCommand ()->updates [0 ]->q ?? null );
174+ $ this ->assertEquals ($ expectedFilter , $ event ['started ' ]->getCommand ()->updates [1 ]->q ?? null );
175+ $ this ->assertEquals ($ expectedFilter , $ event ['started ' ]->getCommand ()->updates [2 ]->q ?? null );
176+ }
177+ );
178+ }
179+
180+ public function provideFilterDocuments (): array
181+ {
182+ $ expectedQuery = (object ) ['x ' => 1 ];
183+
184+ return [
185+ 'array ' => [['x ' => 1 ], $ expectedQuery ],
186+ 'object ' => [(object ) ['x ' => 1 ], $ expectedQuery ],
187+ 'Serializable ' => [new BSONDocument (['x ' => 1 ]), $ expectedQuery ],
188+ 'Document ' => [Document::fromPHP (['x ' => 1 ]), $ expectedQuery ],
189+ ];
190+ }
191+
192+ /** @dataProvider provideReplacementDocuments */
193+ public function testReplacementDocuments ($ replacement , stdClass $ expectedReplacement ): void
194+ {
195+ (new CommandObserver ())->observe (
196+ function () use ($ replacement ): void {
197+ $ operation = new BulkWrite (
198+ $ this ->getDatabaseName (),
199+ $ this ->getCollectionName (),
200+ [['replaceOne ' => [['x ' => 1 ], $ replacement ]]]
201+ );
202+
203+ $ operation ->execute ($ this ->getPrimaryServer ());
204+ },
205+ function (array $ event ) use ($ expectedReplacement ): void {
206+ $ this ->assertEquals ($ expectedReplacement , $ event ['started ' ]->getCommand ()->updates [0 ]->u ?? null );
207+ }
208+ );
209+ }
210+
211+ public function provideReplacementDocuments (): array
212+ {
213+ $ expected = (object ) ['x ' => 1 ];
214+
215+ return [
216+ 'replacement:array ' => [['x ' => 1 ], $ expected ],
217+ 'replacement:object ' => [(object ) ['x ' => 1 ], $ expected ],
218+ 'replacement:Serializable ' => [new BSONDocument (['x ' => 1 ]), $ expected ],
219+ 'replacement:Document ' => [Document::fromPHP (['x ' => 1 ]), $ expected ],
220+ ];
221+ }
222+
223+ /**
224+ * @dataProvider provideUpdateDocuments
225+ * @dataProvider provideUpdatePipelines
226+ */
227+ public function testUpdateDocuments ($ update , $ expectedUpdate ): void
228+ {
229+ if (is_array ($ expectedUpdate ) && version_compare ($ this ->getServerVersion (), '4.2.0 ' , '< ' )) {
230+ $ this ->markTestSkipped ('Pipeline-style updates are not supported ' );
231+ }
232+
233+ (new CommandObserver ())->observe (
234+ function () use ($ update ): void {
235+ $ operation = new BulkWrite (
236+ $ this ->getDatabaseName (),
237+ $ this ->getCollectionName (),
238+ [
239+ ['updateOne ' => [['x ' => 1 ], $ update ]],
240+ ['updateMany ' => [['x ' => 1 ], $ update ]],
241+ ]
242+ );
243+
244+ $ operation ->execute ($ this ->getPrimaryServer ());
245+ },
246+ function (array $ event ) use ($ expectedUpdate ): void {
247+ $ this ->assertEquals ($ expectedUpdate , $ event ['started ' ]->getCommand ()->updates [0 ]->u ?? null );
248+ $ this ->assertEquals ($ expectedUpdate , $ event ['started ' ]->getCommand ()->updates [1 ]->u ?? null );
249+ }
250+ );
251+ }
252+
253+ public function provideUpdateDocuments (): array
254+ {
255+ $ expected = (object ) ['$set ' => (object ) ['x ' => 1 ]];
256+
257+ return [
258+ 'update:array ' => [['$set ' => ['x ' => 1 ]], $ expected ],
259+ 'update:object ' => [(object ) ['$set ' => ['x ' => 1 ]], $ expected ],
260+ 'update:Serializable ' => [new BSONDocument (['$set ' => ['x ' => 1 ]]), $ expected ],
261+ 'update:Document ' => [Document::fromPHP (['$set ' => ['x ' => 1 ]]), $ expected ],
262+ ];
263+ }
264+
265+ public function provideUpdatePipelines (): array
266+ {
267+ $ expected = [(object ) ['$set ' => (object ) ['x ' => 1 ]]];
268+
269+ return [
270+ 'pipeline:array ' => [[['$set ' => ['x ' => 1 ]]], $ expected ],
271+ 'pipeline:Serializable ' => [new BSONArray ([['$set ' => ['x ' => 1 ]]]), $ expected ],
272+ 'pipeline:PackedArray ' => [PackedArray::fromPHP ([['$set ' => ['x ' => 1 ]]]), $ expected ],
273+ ];
274+ }
275+
96276 public function testDeletes (): void
97277 {
98278 $ this ->createFixtures (4 );
@@ -115,6 +295,29 @@ public function testDeletes(): void
115295 $ this ->assertSameDocuments ($ expected , $ this ->collection ->find ());
116296 }
117297
298+ /** @dataProvider provideFilterDocuments */
299+ public function testDeleteFilterDocuments ($ filter , stdClass $ expectedQuery ): void
300+ {
301+ (new CommandObserver ())->observe (
302+ function () use ($ filter ): void {
303+ $ operation = new BulkWrite (
304+ $ this ->getDatabaseName (),
305+ $ this ->getCollectionName (),
306+ [
307+ ['deleteOne ' => [$ filter ]],
308+ ['deleteMany ' => [$ filter ]],
309+ ]
310+ );
311+
312+ $ operation ->execute ($ this ->getPrimaryServer ());
313+ },
314+ function (array $ event ) use ($ expectedQuery ): void {
315+ $ this ->assertEquals ($ expectedQuery , $ event ['started ' ]->getCommand ()->deletes [0 ]->q ?? null );
316+ $ this ->assertEquals ($ expectedQuery , $ event ['started ' ]->getCommand ()->deletes [1 ]->q ?? null );
317+ }
318+ );
319+ }
320+
118321 public function testMixedOrderedOperations (): void
119322 {
120323 $ this ->createFixtures (3 );
0 commit comments