Skip to content

Commit 4c4a0b9

Browse files
committed
PHPLIB-1122: Test BSON objects with BulkWrite operations
1 parent 266f993 commit 4c4a0b9

File tree

2 files changed

+246
-6
lines changed

2 files changed

+246
-6
lines changed

tests/Operation/BulkWriteFunctionalTest.php

Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,21 @@
22

33
namespace MongoDB\Tests\Operation;
44

5+
use MongoDB\BSON\Document;
56
use MongoDB\BSON\ObjectId;
7+
use MongoDB\BSON\PackedArray;
68
use MongoDB\BulkWriteResult;
79
use MongoDB\Collection;
810
use MongoDB\Driver\BulkWrite as Bulk;
911
use MongoDB\Driver\WriteConcern;
1012
use MongoDB\Exception\BadMethodCallException;
13+
use MongoDB\Model\BSONArray;
1114
use MongoDB\Model\BSONDocument;
1215
use MongoDB\Operation\BulkWrite;
1316
use MongoDB\Tests\CommandObserver;
17+
use stdClass;
1418

19+
use function is_array;
1520
use function version_compare;
1621

1722
class 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);

tests/Operation/BulkWriteTest.php

Lines changed: 43 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,11 @@
22

33
namespace MongoDB\Tests\Operation;
44

5+
use MongoDB\BSON\Document;
6+
use MongoDB\BSON\PackedArray;
57
use MongoDB\Exception\InvalidArgumentException;
8+
use MongoDB\Model\BSONArray;
9+
use MongoDB\Model\BSONDocument;
610
use MongoDB\Operation\BulkWrite;
711

812
class BulkWriteTest extends TestCase
@@ -164,15 +168,33 @@ public function testReplaceOneReplacementArgumentTypeCheck($replacement): void
164168
]);
165169
}
166170

167-
public function testReplaceOneReplacementArgumentRequiresNoOperators(): void
171+
/** @dataProvider provideInvalidReplacementValues */
172+
public function testReplaceOneReplacementArgumentRequiresNoOperators($replacement): void
168173
{
169174
$this->expectException(InvalidArgumentException::class);
170175
$this->expectExceptionMessage('First key in $operations[0]["replaceOne"][1] is an update operator');
171176
new BulkWrite($this->getDatabaseName(), $this->getCollectionName(), [
172-
[BulkWrite::REPLACE_ONE => [['_id' => 1], ['$inc' => ['x' => 1]]]],
177+
[BulkWrite::REPLACE_ONE => [['x' => 1], $replacement]],
173178
]);
174179
}
175180

181+
public function provideInvalidReplacementValues(): array
182+
{
183+
return [
184+
'update:array' => [['$set' => ['x' => 1]]],
185+
'update:object' => [(object) ['$set' => ['x' => 1]]],
186+
'update:Serializable' => [new BSONDocument(['$set' => ['x' => 1]])],
187+
'update:Document' => [Document::fromPHP(['$set' => ['x' => 1]])],
188+
// TODO: Enable the following tests when implementing PHPLIB-1129
189+
//'pipeline:array' => [[['$set' => ['x' => 1]]]],
190+
//'pipeline:Serializable' => [new BSONArray([['$set' => ['x' => 1]]])],
191+
//'pipeline:PackedArray' => [PackedArray::fromPHP([['$set' => ['x' => 1]]])],
192+
//'empty_pipeline:array' => [[]],
193+
//'empty_pipeline:Serializable' => [new BSONArray([])],
194+
//'empty_pipeline:PackedArray' => [PackedArray::fromPHP([])],
195+
];
196+
}
197+
176198
/** @dataProvider provideInvalidDocumentValues */
177199
public function testReplaceOneCollationOptionTypeCheck($collation): void
178200
{
@@ -236,15 +258,29 @@ public function testUpdateManyUpdateArgumentTypeCheck($update): void
236258
]);
237259
}
238260

239-
public function testUpdateManyUpdateArgumentRequiresOperatorsOrPipeline(): void
261+
/** @dataProvider provideInvalidUpdateValues */
262+
public function testUpdateManyUpdateArgumentRequiresOperatorsOrPipeline($update): void
240263
{
241264
$this->expectException(InvalidArgumentException::class);
242265
$this->expectExceptionMessage('First key in $operations[0]["updateMany"][1] is neither an update operator nor a pipeline');
243266
new BulkWrite($this->getDatabaseName(), $this->getCollectionName(), [
244-
[BulkWrite::UPDATE_MANY => [['_id' => ['$gt' => 1]], ['x' => 1]]],
267+
[BulkWrite::UPDATE_MANY => [['x' => 1], $update]],
245268
]);
246269
}
247270

271+
public function provideInvalidUpdateValues(): array
272+
{
273+
return [
274+
'replacement:array' => [['x' => 1]],
275+
'replacement:object' => [(object) ['x' => 1]],
276+
'replacement:Serializable' => [new BSONDocument(['x' => 1])],
277+
'replacement:Document' => [Document::fromPHP(['x' => 1])],
278+
'empty_pipeline:array' => [[]],
279+
'empty_pipeline:Serializable' => [new BSONArray([])],
280+
'empty_pipeline:PackedArray' => [PackedArray::fromPHP([])],
281+
];
282+
}
283+
248284
/** @dataProvider provideInvalidArrayValues */
249285
public function testUpdateManyArrayFiltersOptionTypeCheck($arrayFilters): void
250286
{
@@ -313,12 +349,13 @@ public function testUpdateOneUpdateArgumentTypeCheck($update): void
313349
]);
314350
}
315351

316-
public function testUpdateOneUpdateArgumentRequiresOperatorsOrPipeline(): void
352+
/** @dataProvider provideInvalidUpdateValues */
353+
public function testUpdateOneUpdateArgumentRequiresOperatorsOrPipeline($update): void
317354
{
318355
$this->expectException(InvalidArgumentException::class);
319356
$this->expectExceptionMessage('First key in $operations[0]["updateOne"][1] is neither an update operator nor a pipeline');
320357
new BulkWrite($this->getDatabaseName(), $this->getCollectionName(), [
321-
[BulkWrite::UPDATE_ONE => [['_id' => 1], ['x' => 1]]],
358+
[BulkWrite::UPDATE_ONE => [['x' => 1], $update]],
322359
]);
323360
}
324361

0 commit comments

Comments
 (0)