Skip to content

Commit 1160a5b

Browse files
committed
Merge branch 'v1.7'
* v1.7: PHPLIB-554: Stop using admin database for encryption keys PHPLIB-522: sort outcome collection by ID Use UnsupportedException instead of CommandException Add missing description Don't duplicate description for docs PHPLIB-544: Add commitQuorum option to createIndexes
2 parents e05ff85 + 898a66f commit 1160a5b

33 files changed

+202
-93
lines changed

docs/includes/apiargs-MongoDBCollection-method-createIndex-option.yaml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,23 @@
11
arg_name: option
2+
name: commitQuorum
3+
type: string|integer
4+
description: |
5+
Specifies how many data-bearing members of a replica set, including the
6+
primary, must complete the index builds successfully before the primary marks
7+
the indexes as ready.
8+
9+
This option accepts the same values for the ``w`` field in a write concern
10+
plus ``"votingMembers"``, which indicates all voting data-bearing nodes.
11+
12+
This is not supported for server versions prior to 4.4 and will result in an
13+
exception at execution time if used.
14+
15+
.. versionadded:: 1.7
16+
interface: phpmethod
17+
operation: ~
18+
optional: true
19+
---
20+
arg_name: option
221
name: unique
322
type: boolean
423
description: |

docs/includes/apiargs-MongoDBCollection-method-createIndexes-option.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
source:
2+
file: apiargs-MongoDBCollection-method-createIndex-option.yaml
3+
ref: commitQuorum
4+
---
15
source:
26
file: apiargs-common-option.yaml
37
ref: maxTimeMS

src/Collection.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -356,7 +356,7 @@ public function countDocuments($filter = [], array $options = [])
356356
*/
357357
public function createIndex($key, array $options = [])
358358
{
359-
$commandOptionKeys = ['maxTimeMS' => 1, 'session' => 1, 'writeConcern' => 1];
359+
$commandOptionKeys = ['commitQuorum' => 1, 'maxTimeMS' => 1, 'session' => 1, 'writeConcern' => 1];
360360
$indexOptions = array_diff_key($options, $commandOptionKeys);
361361
$commandOptions = array_intersect_key($options, $commandOptionKeys);
362362

src/Exception/UnsupportedException.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,17 @@ public static function collationNotSupported()
3939
return new static('Collations are not supported by the server executing this operation');
4040
}
4141

42+
/**
43+
* Thrown when the commitQuorum option for createIndexes is not supported
44+
* by a server.
45+
*
46+
* @return self
47+
*/
48+
public static function commitQuorumNotSupported()
49+
{
50+
return new static('The "commitQuorum" option is not supported by the server executing this operation');
51+
}
52+
4253
/**
4354
* Thrown when explain is not supported by a server.
4455
*

src/Operation/CreateIndexes.php

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
use function array_map;
2929
use function is_array;
3030
use function is_integer;
31+
use function is_string;
3132
use function MongoDB\server_supports_feature;
3233
use function sprintf;
3334

@@ -47,6 +48,9 @@ class CreateIndexes implements Executable
4748
/** @var integer */
4849
private static $wireVersionForWriteConcern = 5;
4950

51+
/** @var integer */
52+
private static $wireVersionForCommitQuorum = 9;
53+
5054
/** @var string */
5155
private $databaseName;
5256

@@ -67,6 +71,10 @@ class CreateIndexes implements Executable
6771
*
6872
* Supported options:
6973
*
74+
* * commitQuorum (integer|string): Specifies how many data-bearing members
75+
* of a replica set, including the primary, must complete the index
76+
* builds successfully before the primary marks the indexes as ready.
77+
*
7078
* * maxTimeMS (integer): The maximum amount of time to allow the query to
7179
* run.
7280
*
@@ -115,6 +123,10 @@ public function __construct($databaseName, $collectionName, array $indexes, arra
115123
$expectedIndex += 1;
116124
}
117125

126+
if (isset($options['commitQuorum']) && ! is_string($options['commitQuorum']) && ! is_integer($options['commitQuorum'])) {
127+
throw InvalidArgumentException::invalidType('"commitQuorum" option', $options['commitQuorum'], ['integer', 'string']);
128+
}
129+
118130
if (isset($options['maxTimeMS']) && ! is_integer($options['maxTimeMS'])) {
119131
throw InvalidArgumentException::invalidType('"maxTimeMS" option', $options['maxTimeMS'], 'integer');
120132
}
@@ -202,6 +214,16 @@ private function executeCommand(Server $server)
202214
'indexes' => $this->indexes,
203215
];
204216

217+
if (isset($this->options['commitQuorum'])) {
218+
/* Drivers MUST manually raise an error if this option is specified
219+
* when creating an index on a pre 4.4 server. */
220+
if (! server_supports_feature($server, self::$wireVersionForCommitQuorum)) {
221+
throw UnsupportedException::commitQuorumNotSupported();
222+
}
223+
224+
$cmd['commitQuorum'] = $this->options['commitQuorum'];
225+
}
226+
205227
if (isset($this->options['maxTimeMS'])) {
206228
$cmd['maxTimeMS'] = $this->options['maxTimeMS'];
207229
}

tests/Operation/CreateIndexesFunctionalTest.php

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
use InvalidArgumentException;
66
use MongoDB\Driver\Exception\RuntimeException;
7+
use MongoDB\Driver\Server;
8+
use MongoDB\Exception\UnsupportedException;
79
use MongoDB\Model\IndexInfo;
810
use MongoDB\Operation\CreateIndexes;
911
use MongoDB\Operation\ListIndexes;
@@ -171,6 +173,52 @@ function (array $event) {
171173
);
172174
}
173175

176+
public function testCommitQuorumOption()
177+
{
178+
if (version_compare($this->getServerVersion(), '4.3.4', '<')) {
179+
$this->markTestSkipped('commitQuorum is not supported');
180+
}
181+
182+
if ($this->getPrimaryServer()->getType() !== Server::TYPE_RS_PRIMARY) {
183+
$this->markTestSkipped('commitQuorum is only supported on replica sets');
184+
}
185+
186+
(new CommandObserver())->observe(
187+
function () {
188+
$operation = new CreateIndexes(
189+
$this->getDatabaseName(),
190+
$this->getCollectionName(),
191+
[['key' => ['x' => 1]]],
192+
['commitQuorum' => 'majority']
193+
);
194+
195+
$operation->execute($this->getPrimaryServer());
196+
},
197+
function (array $event) {
198+
$this->assertObjectHasAttribute('commitQuorum', $event['started']->getCommand());
199+
}
200+
);
201+
}
202+
203+
public function testCommitQuorumUnsupported()
204+
{
205+
if (version_compare($this->getServerVersion(), '4.3.4', '>=')) {
206+
$this->markTestSkipped('commitQuorum is supported');
207+
}
208+
209+
$operation = new CreateIndexes(
210+
$this->getDatabaseName(),
211+
$this->getCollectionName(),
212+
[['key' => ['x' => 1]]],
213+
['commitQuorum' => 'majority']
214+
);
215+
216+
$this->expectException(UnsupportedException::class);
217+
$this->expectExceptionMessage('The "commitQuorum" option is not supported by the server executing this operation');
218+
219+
$operation->execute($this->getPrimaryServer());
220+
}
221+
174222
/**
175223
* Asserts that an index with the given name exists for the collection.
176224
*

tests/Operation/CreateIndexesTest.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use MongoDB\Exception\InvalidArgumentException;
66
use MongoDB\Operation\CreateIndexes;
7+
use stdClass;
78

89
class CreateIndexesTest extends TestCase
910
{
@@ -27,6 +28,10 @@ public function provideInvalidConstructorOptions()
2728
{
2829
$options = [];
2930

31+
foreach ([3.14, true, [], new stdClass()] as $value) {
32+
$options[][] = ['commitQuorum' => $value];
33+
}
34+
3035
foreach ($this->getInvalidIntegerValues() as $value) {
3136
$options[][] = ['maxTimeMS' => $value];
3237
}

tests/SpecTests/ClientSideEncryptionSpecTest.php

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ public static function assertCommandMatches(stdClass $expected, stdClass $actual
6868
* @param stdClass $test Individual "tests[]" document
6969
* @param array $runOn Top-level "runOn" array with server requirements
7070
* @param array $data Top-level "data" array to initialize collection
71-
* @param array|null $keyVaultData Top-level "key_vault_data" array to initialize admin.datakeys collection
71+
* @param array|null $keyVaultData Top-level "key_vault_data" array to initialize keyvault.datakeys collection
7272
* @param object|null $jsonSchema Top-level "json_schema" array to initialize collection
7373
* @param string $databaseName Name of database under test
7474
* @param string $collectionName Name of collection under test
@@ -170,11 +170,11 @@ public function testDataKeyAndDoubleEncryption(Closure $test)
170170
{
171171
$client = new Client(static::getUri());
172172

173-
$client->selectCollection('admin', 'datakeys')->drop();
173+
$client->selectCollection('keyvault', 'datakeys')->drop();
174174
$client->selectCollection('db', 'coll')->drop();
175175

176176
$encryptionOpts = [
177-
'keyVaultNamespace' => 'admin.datakeys',
177+
'keyVaultNamespace' => 'keyvault.datakeys',
178178
'kmsProviders' => [
179179
'aws' => Context::getAWSCredentials(),
180180
'local' => ['key' => new Binary(base64_decode(self::LOCAL_MASTERKEY), 0)],
@@ -231,7 +231,7 @@ function ($command) use (&$commands) {
231231
$test->assertSame('insert', $insert->getCommandName());
232232
$test->assertSame(WriteConcern::MAJORITY, $insert->getCommand()->writeConcern->w);
233233

234-
$keys = $client->selectCollection('admin', 'datakeys')->find(['_id' => $localDatakeyId]);
234+
$keys = $client->selectCollection('keyvault', 'datakeys')->find(['_id' => $localDatakeyId]);
235235
$keys = iterator_to_array($keys);
236236
$test->assertCount(1, $keys);
237237

@@ -277,7 +277,7 @@ function ($command) use (&$commands) {
277277
$test->assertSame('insert', $insert->getCommandName());
278278
$test->assertSame(WriteConcern::MAJORITY, $insert->getCommand()->writeConcern->w);
279279

280-
$keys = $client->selectCollection('admin', 'datakeys')->find(['_id' => $awsDatakeyId]);
280+
$keys = $client->selectCollection('keyvault', 'datakeys')->find(['_id' => $awsDatakeyId]);
281281
$keys = iterator_to_array($keys);
282282
$test->assertCount(1, $keys);
283283

@@ -314,16 +314,16 @@ public function testExternalKeyVault($withExternalKeyVault)
314314
{
315315
$client = new Client(static::getUri());
316316

317-
$client->selectCollection('admin', 'datakeys')->drop();
317+
$client->selectCollection('keyvault', 'datakeys')->drop();
318318
$client->selectCollection('db', 'coll')->drop();
319319

320320
$keyId = $client
321-
->selectCollection('admin', 'datakeys')
321+
->selectCollection('keyvault', 'datakeys')
322322
->insertOne($this->decodeJson(file_get_contents(__DIR__ . '/client-side-encryption/external/external-key.json')))
323323
->getInsertedId();
324324

325325
$encryptionOpts = [
326-
'keyVaultNamespace' => 'admin.datakeys',
326+
'keyVaultNamespace' => 'keyvault.datakeys',
327327
'kmsProviders' => [
328328
'local' => ['key' => new Binary(base64_decode(self::LOCAL_MASTERKEY), 0)],
329329
],
@@ -372,14 +372,14 @@ public function testBSONSizeLimitsAndBatchSplitting()
372372
{
373373
$client = new Client(static::getUri());
374374

375-
$client->selectCollection('admin', 'datakeys')->drop();
375+
$client->selectCollection('keyvault', 'datakeys')->drop();
376376
$client->selectCollection('db', 'coll')->drop();
377377

378378
$client->selectDatabase('db')->createCollection('coll', ['validator' => ['$jsonSchema' => $this->decodeJson(file_get_contents(__DIR__ . '/client-side-encryption/limits/limits-schema.json'))]]);
379-
$client->selectCollection('admin', 'datakeys')->insertOne($this->decodeJson(file_get_contents(__DIR__ . '/client-side-encryption/limits/limits-key.json')));
379+
$client->selectCollection('keyvault', 'datakeys')->insertOne($this->decodeJson(file_get_contents(__DIR__ . '/client-side-encryption/limits/limits-key.json')));
380380

381381
$autoEncryptionOpts = [
382-
'keyVaultNamespace' => 'admin.datakeys',
382+
'keyVaultNamespace' => 'keyvault.datakeys',
383383
'kmsProviders' => [
384384
'local' => ['key' => new Binary(base64_decode(self::LOCAL_MASTERKEY), 0)],
385385
],
@@ -464,7 +464,7 @@ public function testViewsAreProhibited()
464464
$client->selectDatabase('db')->command(['create' => 'view', 'viewOn' => 'coll']);
465465

466466
$autoEncryptionOpts = [
467-
'keyVaultNamespace' => 'admin.datakeys',
467+
'keyVaultNamespace' => 'keyvault.datakeys',
468468
'kmsProviders' => [
469469
'local' => ['key' => new Binary(base64_decode(self::LOCAL_MASTERKEY), 0)],
470470
],
@@ -503,14 +503,14 @@ public function testCorpus($schemaMap = true)
503503
->createCollection('coll', ['validator' => ['$jsonSchema' => $schema]]);
504504
}
505505

506-
$client->selectDatabase('admin')->dropCollection('datakeys');
507-
$client->selectCollection('admin', 'datakeys')->insertMany([
506+
$client->selectDatabase('keyvault')->dropCollection('datakeys');
507+
$client->selectCollection('keyvault', 'datakeys')->insertMany([
508508
$this->decodeJson(file_get_contents(__DIR__ . '/client-side-encryption/corpus/corpus-key-local.json')),
509509
$this->decodeJson(file_get_contents(__DIR__ . '/client-side-encryption/corpus/corpus-key-aws.json')),
510510
]);
511511

512512
$encryptionOpts = [
513-
'keyVaultNamespace' => 'admin.datakeys',
513+
'keyVaultNamespace' => 'keyvault.datakeys',
514514
'kmsProviders' => [
515515
'aws' => Context::getAWSCredentials(),
516516
'local' => ['key' => new Binary(base64_decode(self::LOCAL_MASTERKEY), 0)],
@@ -561,7 +561,7 @@ public function testCustomEndpoint()
561561
$client = new Client(static::getUri());
562562

563563
$encryptionOpts = [
564-
'keyVaultNamespace' => 'admin.datakeys',
564+
'keyVaultNamespace' => 'keyvault.datakeys',
565565
'kmsProviders' => [
566566
'aws' => Context::getAWSCredentials(),
567567
],
@@ -615,7 +615,7 @@ public function testCustomEndpoint()
615615
public function testBypassSpawningMongocryptdViaBypassSpawn()
616616
{
617617
$autoEncryptionOpts = [
618-
'keyVaultNamespace' => 'admin.datakeys',
618+
'keyVaultNamespace' => 'keyvault.datakeys',
619619
'kmsProviders' => [
620620
'local' => ['key' => new Binary(base64_decode(self::LOCAL_MASTERKEY), 0)],
621621
],
@@ -648,7 +648,7 @@ public function testBypassSpawningMongocryptdViaBypassSpawn()
648648
public function testBypassSpawningMongocryptdViaBypassAutoEncryption()
649649
{
650650
$autoEncryptionOpts = [
651-
'keyVaultNamespace' => 'admin.datakeys',
651+
'keyVaultNamespace' => 'keyvault.datakeys',
652652
'kmsProviders' => [
653653
'local' => ['key' => new Binary(base64_decode(self::LOCAL_MASTERKEY), 0)],
654654
],
@@ -743,7 +743,7 @@ private function insertKeyVaultData(array $keyVaultData = null)
743743
}
744744

745745
$context = $this->getContext();
746-
$collection = $context->selectCollection('admin', 'datakeys', ['writeConcern' => new WriteConcern(WriteConcern::MAJORITY)] + $context->defaultWriteOptions);
746+
$collection = $context->selectCollection('keyvault', 'datakeys', ['writeConcern' => new WriteConcern(WriteConcern::MAJORITY)] + $context->defaultWriteOptions);
747747
$collection->drop();
748748
$collection->insertMany($keyVaultData);
749749

tests/SpecTests/Context.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ public static function fromClientSideEncryption(stdClass $test, $databaseName, $
112112
$autoEncryptionOptions = [];
113113

114114
if (isset($clientOptions['autoEncryptOpts'])) {
115-
$autoEncryptionOptions = (array) $clientOptions['autoEncryptOpts'] + ['keyVaultNamespace' => 'admin.datakeys'];
115+
$autoEncryptionOptions = (array) $clientOptions['autoEncryptOpts'] + ['keyVaultNamespace' => 'keyvault.datakeys'];
116116
unset($clientOptions['autoEncryptOpts']);
117117

118118
if (isset($autoEncryptionOptions['kmsProviders']->aws)) {

tests/SpecTests/FunctionalTestCase.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ protected function assertOutcomeCollectionData(array $expectedDocuments, $result
106106

107107
$mi = new MultipleIterator(MultipleIterator::MIT_NEED_ANY);
108108
$mi->attachIterator(new ArrayIterator($expectedDocuments));
109-
$mi->attachIterator(new IteratorIterator($outcomeCollection->find()));
109+
$mi->attachIterator(new IteratorIterator($outcomeCollection->find([], ['sort' => ['_id' => 1]])));
110110

111111
foreach ($mi as $documents) {
112112
list($expectedDocument, $actualDocument) = $documents;

0 commit comments

Comments
 (0)