diff --git a/.evergreen/config/generated/build/build-extension.yml b/.evergreen/config/generated/build/build-extension.yml index 1ccde5f97..a74e18776 100644 --- a/.evergreen/config/generated/build/build-extension.yml +++ b/.evergreen/config/generated/build/build-extension.yml @@ -7,6 +7,9 @@ tasks: vars: PHP_VERSION: "8.4" - func: "compile extension" + # TODO: remove once 1.21.0 is released + vars: + EXTENSION_BRANCH: "v1.x" - func: "upload extension" - name: "build-php-8.4-lowest" tags: ["build", "php8.4", "lowest", "pr", "tag"] @@ -16,7 +19,9 @@ tasks: PHP_VERSION: "8.4" - func: "compile extension" vars: - EXTENSION_VERSION: "1.20.0" + # TODO: change to "EXTENSION_VERSION: 1.21.0" once 1.21.0 is released + # EXTENSION_VERSION: "1.21.0" + EXTENSION_BRANCH: "v1.x" - func: "upload extension" - name: "build-php-8.4-next-stable" tags: ["build", "php8.4", "next-stable", "pr", "tag"] @@ -26,7 +31,9 @@ tasks: PHP_VERSION: "8.4" - func: "compile extension" vars: - EXTENSION_BRANCH: "v1.20" + # TODO: change to "v1.21" once 1.21.0 is released + # EXTENSION_BRANCH: "v1.21" + EXTENSION_BRANCH: "v1.x" - func: "upload extension" - name: "build-php-8.4-next-minor" tags: ["build", "php8.4", "next-minor"] @@ -45,6 +52,9 @@ tasks: vars: PHP_VERSION: "8.3" - func: "compile extension" + # TODO: remove once 1.21.0 is released + vars: + EXTENSION_BRANCH: "v1.x" - func: "upload extension" - name: "build-php-8.3-lowest" tags: ["build", "php8.3", "lowest", "pr", "tag"] @@ -54,7 +64,9 @@ tasks: PHP_VERSION: "8.3" - func: "compile extension" vars: - EXTENSION_VERSION: "1.20.0" + # TODO: change to "EXTENSION_VERSION: 1.21.0" once 1.21.0 is released + # EXTENSION_VERSION: "1.21.0" + EXTENSION_BRANCH: "v1.x" - func: "upload extension" - name: "build-php-8.3-next-stable" tags: ["build", "php8.3", "next-stable", "pr", "tag"] @@ -64,7 +76,9 @@ tasks: PHP_VERSION: "8.3" - func: "compile extension" vars: - EXTENSION_BRANCH: "v1.20" + # TODO: change to "v1.21" once 1.21.0 is released + # EXTENSION_BRANCH: "v1.21" + EXTENSION_BRANCH: "v1.x" - func: "upload extension" - name: "build-php-8.3-next-minor" tags: ["build", "php8.3", "next-minor"] @@ -83,6 +97,9 @@ tasks: vars: PHP_VERSION: "8.2" - func: "compile extension" + # TODO: remove once 1.21.0 is released + vars: + EXTENSION_BRANCH: "v1.x" - func: "upload extension" - name: "build-php-8.2-lowest" tags: ["build", "php8.2", "lowest", "pr", "tag"] @@ -92,7 +109,9 @@ tasks: PHP_VERSION: "8.2" - func: "compile extension" vars: - EXTENSION_VERSION: "1.20.0" + # TODO: change to "EXTENSION_VERSION: 1.21.0" once 1.21.0 is released + # EXTENSION_VERSION: "1.21.0" + EXTENSION_BRANCH: "v1.x" - func: "upload extension" - name: "build-php-8.2-next-stable" tags: ["build", "php8.2", "next-stable", "pr", "tag"] @@ -102,7 +121,9 @@ tasks: PHP_VERSION: "8.2" - func: "compile extension" vars: - EXTENSION_BRANCH: "v1.20" + # TODO: change to "v1.21" once 1.21.0 is released + # EXTENSION_BRANCH: "v1.21" + EXTENSION_BRANCH: "v1.x" - func: "upload extension" - name: "build-php-8.2-next-minor" tags: ["build", "php8.2", "next-minor"] @@ -121,6 +142,9 @@ tasks: vars: PHP_VERSION: "8.1" - func: "compile extension" + # TODO: remove once 1.21.0 is released + vars: + EXTENSION_BRANCH: "v1.x" - func: "upload extension" - name: "build-php-8.1-lowest" tags: ["build", "php8.1", "lowest", "pr", "tag"] @@ -130,7 +154,9 @@ tasks: PHP_VERSION: "8.1" - func: "compile extension" vars: - EXTENSION_VERSION: "1.20.0" + # TODO: change to "EXTENSION_VERSION: 1.21.0" once 1.21.0 is released + # EXTENSION_VERSION: "1.21.0" + EXTENSION_BRANCH: "v1.x" - func: "upload extension" - name: "build-php-8.1-next-stable" tags: ["build", "php8.1", "next-stable", "pr", "tag"] @@ -140,7 +166,9 @@ tasks: PHP_VERSION: "8.1" - func: "compile extension" vars: - EXTENSION_BRANCH: "v1.20" + # TODO: change to "v1.21" once 1.21.0 is released + # EXTENSION_BRANCH: "v1.21" + EXTENSION_BRANCH: "v1.x" - func: "upload extension" - name: "build-php-8.1-next-minor" tags: ["build", "php8.1", "next-minor"] diff --git a/.evergreen/config/templates/build/build-extension.yml b/.evergreen/config/templates/build/build-extension.yml index 869c0a9e4..98da2976d 100644 --- a/.evergreen/config/templates/build/build-extension.yml +++ b/.evergreen/config/templates/build/build-extension.yml @@ -5,6 +5,9 @@ vars: PHP_VERSION: "%phpVersion%" - func: "compile extension" + # TODO: remove once 1.21.0 is released + vars: + EXTENSION_BRANCH: "v1.x" - func: "upload extension" - name: "build-php-%phpVersion%-lowest" tags: ["build", "php%phpVersion%", "lowest", "pr", "tag"] @@ -14,7 +17,9 @@ PHP_VERSION: "%phpVersion%" - func: "compile extension" vars: - EXTENSION_VERSION: "1.20.0" + # TODO: change to "EXTENSION_VERSION: 1.21.0" once 1.21.0 is released + # EXTENSION_VERSION: "1.21.0" + EXTENSION_BRANCH: "v1.x" - func: "upload extension" - name: "build-php-%phpVersion%-next-stable" tags: ["build", "php%phpVersion%", "next-stable", "pr", "tag"] @@ -24,7 +29,9 @@ PHP_VERSION: "%phpVersion%" - func: "compile extension" vars: - EXTENSION_BRANCH: "v1.20" + # TODO: change to "v1.21" once 1.21.0 is released + # EXTENSION_BRANCH: "v1.21" + EXTENSION_BRANCH: "v1.x" - func: "upload extension" - name: "build-php-%phpVersion%-next-minor" tags: ["build", "php%phpVersion%", "next-minor"] diff --git a/.github/workflows/coding-standards.yml b/.github/workflows/coding-standards.yml index 462e4d67e..10bcc251d 100644 --- a/.github/workflows/coding-standards.yml +++ b/.github/workflows/coding-standards.yml @@ -13,7 +13,9 @@ on: env: PHP_VERSION: "8.2" - DRIVER_VERSION: "stable" + # TODO: change to "stable" once 1.21.0 is released + # DRIVER_VERSION: "stable" + DRIVER_VERSION: "mongodb/mongo-php-driver@v1.x" jobs: phpcs: diff --git a/.github/workflows/generator.yml b/.github/workflows/generator.yml index 2427a41eb..fd755509c 100644 --- a/.github/workflows/generator.yml +++ b/.github/workflows/generator.yml @@ -13,9 +13,9 @@ on: env: PHP_VERSION: "8.2" - # TODO: change to "stable" once 1.20.0 is released + # TODO: change to "stable" once 1.21.0 is released # DRIVER_VERSION: "stable" - DRIVER_VERSION: "mongodb/mongo-php-driver@v1.20" + DRIVER_VERSION: "mongodb/mongo-php-driver@v1.x" jobs: psalm: diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml index 0103bdcd1..5a7d6fbf3 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -19,7 +19,9 @@ on: env: PHP_VERSION: "8.2" - DRIVER_VERSION: "stable" + # TODO: change to "stable" once 1.21.0 is released + # DRIVER_VERSION: "stable" + DRIVER_VERSION: "mongodb/mongo-php-driver@v1.x" jobs: psalm: diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 36640a724..194414fb4 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -12,7 +12,9 @@ on: - "feature/*" env: - DRIVER_VERSION: "stable" + # TODO: change to "stable" once 1.21.0 is released + # DRIVER_VERSION: "stable" + DRIVER_VERSION: "mongodb/mongo-php-driver@v1.x" jobs: phpunit: diff --git a/composer.json b/composer.json index 3b5738ae1..118943bb3 100644 --- a/composer.json +++ b/composer.json @@ -13,7 +13,7 @@ "php": "^8.1", "ext-hash": "*", "ext-json": "*", - "ext-mongodb": "^1.20.0", + "ext-mongodb": "^1.21.0", "composer-runtime-api": "^2.0", "psr/log": "^1.1.4|^2|^3", "symfony/polyfill-php80": "^1.27", diff --git a/src/Operation/BulkWrite.php b/src/Operation/BulkWrite.php index 5c0b67ea5..c8f695675 100644 --- a/src/Operation/BulkWrite.php +++ b/src/Operation/BulkWrite.php @@ -91,6 +91,14 @@ class BulkWrite implements Executable * * upsert (boolean): When true, a new document is created if no document * matches the query. The default is false. * + * Supported options for replaceOne and updateOne operations: + * + * * sort (document): Determines which document the operation modifies if + * the query selects multiple documents. + * + * This is not supported for server versions < 8.0 and will result in an + * exception at execution time if used. + * * Supported options for updateMany and updateOne operations: * * * arrayFilters (document array): A set of filters specifying to which @@ -372,6 +380,10 @@ private function validateOperations(array $operations, ?DocumentCodec $codec, En throw InvalidArgumentException::expectedDocumentType(sprintf('$operations[%d]["%s"][2]["collation"]', $i, $type), $args[2]['collation']); } + if (isset($args[2]['sort']) && ! is_document($args[2]['sort'])) { + throw InvalidArgumentException::expectedDocumentType(sprintf('$operations[%d]["%s"][2]["sort"]', $i, $type), $args[2]['sort']); + } + if (! is_bool($args[2]['upsert'])) { throw InvalidArgumentException::invalidType(sprintf('$operations[%d]["%s"][2]["upsert"]', $i, $type), $args[2]['upsert'], 'boolean'); } @@ -413,6 +425,14 @@ private function validateOperations(array $operations, ?DocumentCodec $codec, En throw InvalidArgumentException::expectedDocumentType(sprintf('$operations[%d]["%s"][2]["collation"]', $i, $type), $args[2]['collation']); } + if (isset($args[2]['sort']) && ! is_document($args[2]['sort'])) { + throw InvalidArgumentException::expectedDocumentType(sprintf('$operations[%d]["%s"][2]["sort"]', $i, $type), $args[2]['sort']); + } + + if (isset($args[2]['sort']) && $args[2]['multi']) { + throw new InvalidArgumentException(sprintf('"sort" option cannot be used with $operations[%d]["%s"]', $i, $type)); + } + if (! is_bool($args[2]['upsert'])) { throw InvalidArgumentException::invalidType(sprintf('$operations[%d]["%s"][2]["upsert"]', $i, $type), $args[2]['upsert'], 'boolean'); } diff --git a/src/Operation/ReplaceOne.php b/src/Operation/ReplaceOne.php index 3cec99ff0..843640923 100644 --- a/src/Operation/ReplaceOne.php +++ b/src/Operation/ReplaceOne.php @@ -74,6 +74,12 @@ class ReplaceOne implements Executable * Parameters can then be accessed as variables in an aggregate * expression context (e.g. "$$var"). * + * * sort (document): Determines which document the operation modifies if + * the query selects multiple documents. + * + * This is not supported for server versions < 8.0 and will result in an + * exception at execution time if used. + * * * writeConcern (MongoDB\Driver\WriteConcern): Write concern. * * @param string $databaseName Database name diff --git a/src/Operation/Update.php b/src/Operation/Update.php index 5d011da28..2016d89a8 100644 --- a/src/Operation/Update.php +++ b/src/Operation/Update.php @@ -149,6 +149,14 @@ public function __construct(private string $databaseName, private string $collec throw InvalidArgumentException::expectedDocumentType('"let" option', $options['let']); } + if (isset($options['sort']) && ! is_document($options['sort'])) { + throw InvalidArgumentException::expectedDocumentType('"sort" option', $options['sort']); + } + + if (isset($options['sort']) && $options['multi']) { + throw new InvalidArgumentException('"sort" option cannot be used with multi-document updates'); + } + if (isset($options['bypassDocumentValidation']) && ! $options['bypassDocumentValidation']) { unset($options['bypassDocumentValidation']); } @@ -270,8 +278,10 @@ private function createUpdateOptions(): array } } - if (isset($this->options['collation'])) { - $updateOptions['collation'] = (object) $this->options['collation']; + foreach (['collation', 'sort'] as $option) { + if (isset($this->options[$option])) { + $updateOptions[$option] = (object) $this->options[$option]; + } } return $updateOptions; diff --git a/src/Operation/UpdateOne.php b/src/Operation/UpdateOne.php index 17bc4a554..d7bbe57cb 100644 --- a/src/Operation/UpdateOne.php +++ b/src/Operation/UpdateOne.php @@ -72,6 +72,12 @@ class UpdateOne implements Executable, Explainable * Parameters can then be accessed as variables in an aggregate * expression context (e.g. "$$var"). * + * * sort (document): Determines which document the operation modifies if + * the query selects multiple documents. + * + * This is not supported for server versions < 8.0 and will result in an + * exception at execution time if used. + * * * writeConcern (MongoDB\Driver\WriteConcern): Write concern. * * @param string $databaseName Database name diff --git a/tests/Operation/BulkWriteTest.php b/tests/Operation/BulkWriteTest.php index d64fc351f..30d5c6374 100644 --- a/tests/Operation/BulkWriteTest.php +++ b/tests/Operation/BulkWriteTest.php @@ -217,6 +217,16 @@ public function testReplaceOneCollationOptionTypeCheck($collation): void ]); } + #[DataProvider('provideInvalidDocumentValues')] + public function testReplaceOneSortOptionTypeCheck($sort): void + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessageMatches('/Expected \$operations\[0\]\["replaceOne"\]\[2\]\["sort"\] to have type "document" \(array or object\) but found ".+"/'); + new BulkWrite($this->getDatabaseName(), $this->getCollectionName(), [ + [BulkWrite::REPLACE_ONE => [['x' => 1], ['y' => 1], ['sort' => $sort]]], + ]); + } + #[DataProvider('provideInvalidBooleanValues')] public function testReplaceOneUpsertOptionTypeCheck($upsert): void { @@ -322,6 +332,15 @@ public function testUpdateManyCollationOptionTypeCheck($collation): void ]); } + public function testUpdateManyProhibitsSortOption(): void + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('"sort" option cannot be used with $operations[0]["updateMany"]'); + new BulkWrite($this->getDatabaseName(), $this->getCollectionName(), [ + [BulkWrite::UPDATE_MANY => [['x' => 1], ['$set' => ['y' => 1]], ['sort' => ['z' => 1]]]], + ]); + } + #[DataProvider('provideInvalidBooleanValues')] public function testUpdateManyUpsertOptionTypeCheck($upsert): void { @@ -410,6 +429,16 @@ public function testUpdateOneCollationOptionTypeCheck($collation): void ]); } + #[DataProvider('provideInvalidDocumentValues')] + public function testUpdateOneSortOptionTypeCheck($sort): void + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessageMatches('/Expected \$operations\[0\]\["updateOne"\]\[2\]\["sort"\] to have type "document" \(array or object\) but found ".+"/'); + new BulkWrite($this->getDatabaseName(), $this->getCollectionName(), [ + [BulkWrite::UPDATE_ONE => [['x' => 1], ['$set' => ['y' => 1]], ['sort' => $sort]]], + ]); + } + #[DataProvider('provideInvalidBooleanValues')] public function testUpdateOneUpsertOptionTypeCheck($upsert): void { diff --git a/tests/Operation/UpdateTest.php b/tests/Operation/UpdateTest.php index 6c6f590d5..9617c77e8 100644 --- a/tests/Operation/UpdateTest.php +++ b/tests/Operation/UpdateTest.php @@ -40,6 +40,7 @@ public static function provideInvalidConstructorOptions() 'collation' => self::getInvalidDocumentValues(), 'hint' => self::getInvalidHintValues(), 'multi' => self::getInvalidBooleanValues(), + 'sort' => self::getInvalidDocumentValues(), 'session' => self::getInvalidSessionValues(), 'upsert' => self::getInvalidBooleanValues(), 'writeConcern' => self::getInvalidWriteConcernValues(), @@ -55,6 +56,13 @@ public function testConstructorMultiOptionProhibitsReplacementDocumentOrEmptyPip new Update($this->getDatabaseName(), $this->getCollectionName(), ['x' => 1], $update, ['multi' => true]); } + public function testConstructorMultiOptionProhibitsSortOption(): void + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('"sort" option cannot be used with multi-document updates'); + new Update($this->getDatabaseName(), $this->getCollectionName(), ['x' => 1], ['$set' => ['x' => 2]], ['multi' => true, 'sort' => ['x' => 1]]); + } + public function testExplainableCommandDocument(): void { $options = [ diff --git a/tests/UnifiedSpecTests/UnifiedSpecTest.php b/tests/UnifiedSpecTests/UnifiedSpecTest.php index 5dad387fb..b41a67f38 100644 --- a/tests/UnifiedSpecTests/UnifiedSpecTest.php +++ b/tests/UnifiedSpecTests/UnifiedSpecTest.php @@ -37,11 +37,6 @@ class UnifiedSpecTest extends FunctionalTestCase // mongoc_cluster_stream_for_server does not retry handshakes (CDRIVER-4532, PHPLIB-1033, PHPLIB-1042) 'retryable-reads/retryable reads handshake failures' => 'Handshakes are not retried (CDRIVER-4532)', 'retryable-writes/retryable writes handshake failures' => 'Handshakes are not retried (CDRIVER-4532)', - // sort option for update operations is not supported (PHPLIB-1492) - 'crud/BulkWrite replaceOne-sort' => 'Sort for replace operations is not supported (PHPLIB-1492)', - 'crud/BulkWrite updateOne-sort' => 'Sort for update operations is not supported (PHPLIB-1492)', - 'crud/replaceOne-sort' => 'Sort for replace operations is not supported (PHPLIB-1492)', - 'crud/updateOne-sort' => 'Sort for update operations is not supported (PHPLIB-1492)', 'crud/bypassDocumentValidation' => 'bypassDocumentValidation is handled by libmongoc (PHPLIB-1576)', ]; diff --git a/tests/UnifiedSpecTests/Util.php b/tests/UnifiedSpecTests/Util.php index 8adb2923a..d508d7d65 100644 --- a/tests/UnifiedSpecTests/Util.php +++ b/tests/UnifiedSpecTests/Util.php @@ -105,10 +105,10 @@ final class Util 'findOne' => ['let', 'filter', 'session', 'allowDiskUse', 'allowPartialResults', 'batchSize', 'collation', 'comment', 'cursorType', 'hint', 'max', 'maxAwaitTimeMS', 'maxScan', 'maxTimeMS', 'min', 'modifiers', 'noCursorTimeout', 'oplogReplay', 'projection', 'returnKey', 'showRecordId', 'skip', 'snapshot', 'sort'], 'findOneAndReplace' => ['let', 'returnDocument', 'filter', 'replacement', 'session', 'projection', 'returnDocument', 'upsert', 'arrayFilters', 'bypassDocumentValidation', 'collation', 'hint', 'maxTimeMS', 'new', 'remove', 'sort', 'comment'], 'rename' => ['to', 'comment', 'dropTarget'], - 'replaceOne' => ['let', 'filter', 'replacement', 'session', 'upsert', 'arrayFilters', 'bypassDocumentValidation', 'collation', 'hint', 'comment'], + 'replaceOne' => ['let', 'filter', 'replacement', 'session', 'upsert', 'arrayFilters', 'bypassDocumentValidation', 'collation', 'hint', 'comment', 'sort'], 'findOneAndUpdate' => ['let', 'returnDocument', 'filter', 'update', 'session', 'upsert', 'projection', 'remove', 'arrayFilters', 'bypassDocumentValidation', 'collation', 'hint', 'maxTimeMS', 'sort', 'comment'], 'updateMany' => ['let', 'filter', 'update', 'session', 'upsert', 'arrayFilters', 'bypassDocumentValidation', 'collation', 'hint', 'comment'], - 'updateOne' => ['let', 'filter', 'update', 'session', 'upsert', 'arrayFilters', 'bypassDocumentValidation', 'collation', 'hint', 'comment'], + 'updateOne' => ['let', 'filter', 'update', 'session', 'upsert', 'arrayFilters', 'bypassDocumentValidation', 'collation', 'hint', 'comment', 'sort'], 'updateSearchIndex' => ['name', 'definition'], 'insertMany' => ['documents', 'session', 'ordered', 'bypassDocumentValidation', 'comment'], 'insertOne' => ['document', 'session', 'bypassDocumentValidation', 'comment'],