Skip to content

Commit 85447eb

Browse files
authored
PHPLIB-1492: Support sort option for updateOne and replaceOne (#1605)
Addresses both single-statement updates and bulk writes. * Require PHPC 1.21-dev for CI builds
1 parent d1aea99 commit 85447eb

File tree

15 files changed

+140
-25
lines changed

15 files changed

+140
-25
lines changed

.evergreen/config/generated/build/build-extension.yml

+36-8
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.evergreen/config/templates/build/build-extension.yml

+9-2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
vars:
66
PHP_VERSION: "%phpVersion%"
77
- func: "compile extension"
8+
# TODO: remove once 1.21.0 is released
9+
vars:
10+
EXTENSION_BRANCH: "v1.x"
811
- func: "upload extension"
912
- name: "build-php-%phpVersion%-lowest"
1013
tags: ["build", "php%phpVersion%", "lowest", "pr", "tag"]
@@ -14,7 +17,9 @@
1417
PHP_VERSION: "%phpVersion%"
1518
- func: "compile extension"
1619
vars:
17-
EXTENSION_VERSION: "1.20.0"
20+
# TODO: change to "EXTENSION_VERSION: 1.21.0" once 1.21.0 is released
21+
# EXTENSION_VERSION: "1.21.0"
22+
EXTENSION_BRANCH: "v1.x"
1823
- func: "upload extension"
1924
- name: "build-php-%phpVersion%-next-stable"
2025
tags: ["build", "php%phpVersion%", "next-stable", "pr", "tag"]
@@ -24,7 +29,9 @@
2429
PHP_VERSION: "%phpVersion%"
2530
- func: "compile extension"
2631
vars:
27-
EXTENSION_BRANCH: "v1.20"
32+
# TODO: change to "v1.21" once 1.21.0 is released
33+
# EXTENSION_BRANCH: "v1.21"
34+
EXTENSION_BRANCH: "v1.x"
2835
- func: "upload extension"
2936
- name: "build-php-%phpVersion%-next-minor"
3037
tags: ["build", "php%phpVersion%", "next-minor"]

.github/workflows/coding-standards.yml

+3-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@ on:
1313

1414
env:
1515
PHP_VERSION: "8.2"
16-
DRIVER_VERSION: "stable"
16+
# TODO: change to "stable" once 1.21.0 is released
17+
# DRIVER_VERSION: "stable"
18+
DRIVER_VERSION: "mongodb/[email protected]"
1719

1820
jobs:
1921
phpcs:

.github/workflows/generator.yml

+2-2
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@ on:
1313

1414
env:
1515
PHP_VERSION: "8.2"
16-
# TODO: change to "stable" once 1.20.0 is released
16+
# TODO: change to "stable" once 1.21.0 is released
1717
# DRIVER_VERSION: "stable"
18-
DRIVER_VERSION: "mongodb/mongo-php-driver@v1.20"
18+
DRIVER_VERSION: "mongodb/mongo-php-driver@v1.x"
1919

2020
jobs:
2121
psalm:

.github/workflows/static-analysis.yml

+3-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@ on:
1919

2020
env:
2121
PHP_VERSION: "8.2"
22-
DRIVER_VERSION: "stable"
22+
# TODO: change to "stable" once 1.21.0 is released
23+
# DRIVER_VERSION: "stable"
24+
DRIVER_VERSION: "mongodb/[email protected]"
2325

2426
jobs:
2527
psalm:

.github/workflows/tests.yml

+3-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@ on:
1212
- "feature/*"
1313

1414
env:
15-
DRIVER_VERSION: "stable"
15+
# TODO: change to "stable" once 1.21.0 is released
16+
# DRIVER_VERSION: "stable"
17+
DRIVER_VERSION: "mongodb/[email protected]"
1618

1719
jobs:
1820
phpunit:

composer.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
"php": "^8.1",
1414
"ext-hash": "*",
1515
"ext-json": "*",
16-
"ext-mongodb": "^1.20.0",
16+
"ext-mongodb": "^1.21.0",
1717
"composer-runtime-api": "^2.0",
1818
"psr/log": "^1.1.4|^2|^3",
1919
"symfony/polyfill-php80": "^1.27",

src/Operation/BulkWrite.php

+20
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,14 @@ class BulkWrite implements Executable
9191
* * upsert (boolean): When true, a new document is created if no document
9292
* matches the query. The default is false.
9393
*
94+
* Supported options for replaceOne and updateOne operations:
95+
*
96+
* * sort (document): Determines which document the operation modifies if
97+
* the query selects multiple documents.
98+
*
99+
* This is not supported for server versions < 8.0 and will result in an
100+
* exception at execution time if used.
101+
*
94102
* Supported options for updateMany and updateOne operations:
95103
*
96104
* * arrayFilters (document array): A set of filters specifying to which
@@ -372,6 +380,10 @@ private function validateOperations(array $operations, ?DocumentCodec $codec, En
372380
throw InvalidArgumentException::expectedDocumentType(sprintf('$operations[%d]["%s"][2]["collation"]', $i, $type), $args[2]['collation']);
373381
}
374382

383+
if (isset($args[2]['sort']) && ! is_document($args[2]['sort'])) {
384+
throw InvalidArgumentException::expectedDocumentType(sprintf('$operations[%d]["%s"][2]["sort"]', $i, $type), $args[2]['sort']);
385+
}
386+
375387
if (! is_bool($args[2]['upsert'])) {
376388
throw InvalidArgumentException::invalidType(sprintf('$operations[%d]["%s"][2]["upsert"]', $i, $type), $args[2]['upsert'], 'boolean');
377389
}
@@ -413,6 +425,14 @@ private function validateOperations(array $operations, ?DocumentCodec $codec, En
413425
throw InvalidArgumentException::expectedDocumentType(sprintf('$operations[%d]["%s"][2]["collation"]', $i, $type), $args[2]['collation']);
414426
}
415427

428+
if (isset($args[2]['sort']) && ! is_document($args[2]['sort'])) {
429+
throw InvalidArgumentException::expectedDocumentType(sprintf('$operations[%d]["%s"][2]["sort"]', $i, $type), $args[2]['sort']);
430+
}
431+
432+
if (isset($args[2]['sort']) && $args[2]['multi']) {
433+
throw new InvalidArgumentException(sprintf('"sort" option cannot be used with $operations[%d]["%s"]', $i, $type));
434+
}
435+
416436
if (! is_bool($args[2]['upsert'])) {
417437
throw InvalidArgumentException::invalidType(sprintf('$operations[%d]["%s"][2]["upsert"]', $i, $type), $args[2]['upsert'], 'boolean');
418438
}

src/Operation/ReplaceOne.php

+6
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,12 @@ class ReplaceOne implements Executable
7474
* Parameters can then be accessed as variables in an aggregate
7575
* expression context (e.g. "$$var").
7676
*
77+
* * sort (document): Determines which document the operation modifies if
78+
* the query selects multiple documents.
79+
*
80+
* This is not supported for server versions < 8.0 and will result in an
81+
* exception at execution time if used.
82+
*
7783
* * writeConcern (MongoDB\Driver\WriteConcern): Write concern.
7884
*
7985
* @param string $databaseName Database name

src/Operation/Update.php

+12-2
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,14 @@ public function __construct(private string $databaseName, private string $collec
149149
throw InvalidArgumentException::expectedDocumentType('"let" option', $options['let']);
150150
}
151151

152+
if (isset($options['sort']) && ! is_document($options['sort'])) {
153+
throw InvalidArgumentException::expectedDocumentType('"sort" option', $options['sort']);
154+
}
155+
156+
if (isset($options['sort']) && $options['multi']) {
157+
throw new InvalidArgumentException('"sort" option cannot be used with multi-document updates');
158+
}
159+
152160
if (isset($options['bypassDocumentValidation']) && ! $options['bypassDocumentValidation']) {
153161
unset($options['bypassDocumentValidation']);
154162
}
@@ -270,8 +278,10 @@ private function createUpdateOptions(): array
270278
}
271279
}
272280

273-
if (isset($this->options['collation'])) {
274-
$updateOptions['collation'] = (object) $this->options['collation'];
281+
foreach (['collation', 'sort'] as $option) {
282+
if (isset($this->options[$option])) {
283+
$updateOptions[$option] = (object) $this->options[$option];
284+
}
275285
}
276286

277287
return $updateOptions;

src/Operation/UpdateOne.php

+6
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,12 @@ class UpdateOne implements Executable, Explainable
7272
* Parameters can then be accessed as variables in an aggregate
7373
* expression context (e.g. "$$var").
7474
*
75+
* * sort (document): Determines which document the operation modifies if
76+
* the query selects multiple documents.
77+
*
78+
* This is not supported for server versions < 8.0 and will result in an
79+
* exception at execution time if used.
80+
*
7581
* * writeConcern (MongoDB\Driver\WriteConcern): Write concern.
7682
*
7783
* @param string $databaseName Database name

tests/Operation/BulkWriteTest.php

+29
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,16 @@ public function testReplaceOneCollationOptionTypeCheck($collation): void
217217
]);
218218
}
219219

220+
#[DataProvider('provideInvalidDocumentValues')]
221+
public function testReplaceOneSortOptionTypeCheck($sort): void
222+
{
223+
$this->expectException(InvalidArgumentException::class);
224+
$this->expectExceptionMessageMatches('/Expected \$operations\[0\]\["replaceOne"\]\[2\]\["sort"\] to have type "document" \(array or object\) but found ".+"/');
225+
new BulkWrite($this->getDatabaseName(), $this->getCollectionName(), [
226+
[BulkWrite::REPLACE_ONE => [['x' => 1], ['y' => 1], ['sort' => $sort]]],
227+
]);
228+
}
229+
220230
#[DataProvider('provideInvalidBooleanValues')]
221231
public function testReplaceOneUpsertOptionTypeCheck($upsert): void
222232
{
@@ -322,6 +332,15 @@ public function testUpdateManyCollationOptionTypeCheck($collation): void
322332
]);
323333
}
324334

335+
public function testUpdateManyProhibitsSortOption(): void
336+
{
337+
$this->expectException(InvalidArgumentException::class);
338+
$this->expectExceptionMessage('"sort" option cannot be used with $operations[0]["updateMany"]');
339+
new BulkWrite($this->getDatabaseName(), $this->getCollectionName(), [
340+
[BulkWrite::UPDATE_MANY => [['x' => 1], ['$set' => ['y' => 1]], ['sort' => ['z' => 1]]]],
341+
]);
342+
}
343+
325344
#[DataProvider('provideInvalidBooleanValues')]
326345
public function testUpdateManyUpsertOptionTypeCheck($upsert): void
327346
{
@@ -410,6 +429,16 @@ public function testUpdateOneCollationOptionTypeCheck($collation): void
410429
]);
411430
}
412431

432+
#[DataProvider('provideInvalidDocumentValues')]
433+
public function testUpdateOneSortOptionTypeCheck($sort): void
434+
{
435+
$this->expectException(InvalidArgumentException::class);
436+
$this->expectExceptionMessageMatches('/Expected \$operations\[0\]\["updateOne"\]\[2\]\["sort"\] to have type "document" \(array or object\) but found ".+"/');
437+
new BulkWrite($this->getDatabaseName(), $this->getCollectionName(), [
438+
[BulkWrite::UPDATE_ONE => [['x' => 1], ['$set' => ['y' => 1]], ['sort' => $sort]]],
439+
]);
440+
}
441+
413442
#[DataProvider('provideInvalidBooleanValues')]
414443
public function testUpdateOneUpsertOptionTypeCheck($upsert): void
415444
{

tests/Operation/UpdateTest.php

+8
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ public static function provideInvalidConstructorOptions()
4040
'collation' => self::getInvalidDocumentValues(),
4141
'hint' => self::getInvalidHintValues(),
4242
'multi' => self::getInvalidBooleanValues(),
43+
'sort' => self::getInvalidDocumentValues(),
4344
'session' => self::getInvalidSessionValues(),
4445
'upsert' => self::getInvalidBooleanValues(),
4546
'writeConcern' => self::getInvalidWriteConcernValues(),
@@ -55,6 +56,13 @@ public function testConstructorMultiOptionProhibitsReplacementDocumentOrEmptyPip
5556
new Update($this->getDatabaseName(), $this->getCollectionName(), ['x' => 1], $update, ['multi' => true]);
5657
}
5758

59+
public function testConstructorMultiOptionProhibitsSortOption(): void
60+
{
61+
$this->expectException(InvalidArgumentException::class);
62+
$this->expectExceptionMessage('"sort" option cannot be used with multi-document updates');
63+
new Update($this->getDatabaseName(), $this->getCollectionName(), ['x' => 1], ['$set' => ['x' => 2]], ['multi' => true, 'sort' => ['x' => 1]]);
64+
}
65+
5866
public function testExplainableCommandDocument(): void
5967
{
6068
$options = [

tests/UnifiedSpecTests/UnifiedSpecTest.php

-5
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,6 @@ class UnifiedSpecTest extends FunctionalTestCase
3737
// mongoc_cluster_stream_for_server does not retry handshakes (CDRIVER-4532, PHPLIB-1033, PHPLIB-1042)
3838
'retryable-reads/retryable reads handshake failures' => 'Handshakes are not retried (CDRIVER-4532)',
3939
'retryable-writes/retryable writes handshake failures' => 'Handshakes are not retried (CDRIVER-4532)',
40-
// sort option for update operations is not supported (PHPLIB-1492)
41-
'crud/BulkWrite replaceOne-sort' => 'Sort for replace operations is not supported (PHPLIB-1492)',
42-
'crud/BulkWrite updateOne-sort' => 'Sort for update operations is not supported (PHPLIB-1492)',
43-
'crud/replaceOne-sort' => 'Sort for replace operations is not supported (PHPLIB-1492)',
44-
'crud/updateOne-sort' => 'Sort for update operations is not supported (PHPLIB-1492)',
4540
'crud/bypassDocumentValidation' => 'bypassDocumentValidation is handled by libmongoc (PHPLIB-1576)',
4641
];
4742

0 commit comments

Comments
 (0)