From 490d6201df88b67dfe08803746fe8f41f804b14b Mon Sep 17 00:00:00 2001 From: Andreas Braun Date: Thu, 16 Jun 2016 09:23:45 +0200 Subject: [PATCH 1/2] Extract connection options from query string --- lib/Mongo/MongoClient.php | 87 ++++++++++++- .../MongoDbAdapter/Mongo/MongoClientTest.php | 122 ++++++++++++++++++ 2 files changed, 208 insertions(+), 1 deletion(-) diff --git a/lib/Mongo/MongoClient.php b/lib/Mongo/MongoClient.php index 46026a38..38bf0475 100644 --- a/lib/Mongo/MongoClient.php +++ b/lib/Mongo/MongoClient.php @@ -86,6 +86,8 @@ public function __construct($server = 'default', array $options = ['connect' => $server = 'mongodb://' . self::DEFAULT_HOST . ':' . self::DEFAULT_PORT; } + $this->applyConnectionOptions($server, $options); + $this->server = $server; if (false === strpos($this->server, 'mongodb://')) { $this->server = 'mongodb://'.$this->server; @@ -348,5 +350,88 @@ function __sleep() 'connected', 'status', 'server', 'persistent' ]; } -} + /** + * @param $server + * @return array + */ + private function extractUrlOptions($server) + { + $queryOptions = explode('&', parse_url($server, PHP_URL_QUERY)); + + $options = []; + foreach ($queryOptions as $option) { + if (strpos($option, '=') === false) { + continue; + } + + $keyValue = explode('=', $option); + if ($keyValue[0] === 'readPreferenceTags') { + $options[$keyValue[0]][] = $this->getReadPreferenceTags($keyValue[1]); + } else { + $options[$keyValue[0]] = $keyValue[1]; + } + } + + return $options; + } + + /** + * @param $readPreferenceTagString + * @return array + */ + private function getReadPreferenceTags($readPreferenceTagString) + { + $tagSets = []; + foreach (explode(',', $readPreferenceTagString) as $index => $tagSet) { + $tags = explode(':', $tagSet); + $tagSets[$tags[0]] = $tags[1]; + } + + return $tagSets; + } + + /** + * @param string $server + * @param array $options + */ + private function applyConnectionOptions($server, array $options) + { + $urlOptions = $this->extractUrlOptions($server); + + if (isset($urlOptions['wTimeout'])) { + $urlOptions['wTimeoutMS'] = $urlOptions['wTimeout']; + unset($urlOptions['wTimeout']); + } + + if (isset($options['wTimeout'])) { + $options['wTimeoutMS'] = $options['wTimeout']; + unset($options['wTimeout']); + } + + if (isset($options['readPreferenceTags'])) { + $options['readPreferenceTags'] = [$this->getReadPreferenceTags($options['readPreferenceTags'])]; + + // Special handling for readPreferenceTags which are merged + if (isset($urlOptions['readPreferenceTags'])) { + $options['readPreferenceTags'] = array_merge($urlOptions['readPreferenceTags'], $options['readPreferenceTags']); + } + } + + $urlOptions = array_merge($urlOptions, $options); + + if (isset($urlOptions['slaveOkay'])) { + $this->setReadPreferenceFromSlaveOkay($urlOptions['slaveOkay']); + } elseif (isset($urlOptions['readPreference']) || isset($urlOptions['readPreferenceTags'])) { + $readPreference = isset($urlOptions['readPreference']) ? $urlOptions['readPreference'] : null; + $tags = isset($urlOptions['readPreferenceTags']) ? $urlOptions['readPreferenceTags'] : null; + $this->setReadPreferenceFromParameters($readPreference, $tags); + } + + if (isset($urlOptions['w']) || isset($urlOptions['wTimeoutMs'])) { + $writeConcern = (isset($urlOptions['w'])) ? $urlOptions['w'] : 1; + $wTimeout = (isset($urlOptions['wTimeoutMs'])) ? $urlOptions['wTimeoutMs'] : null; + $this->setWriteConcern($writeConcern, $wTimeout); + } + } +} diff --git a/tests/Alcaeus/MongoDbAdapter/Mongo/MongoClientTest.php b/tests/Alcaeus/MongoDbAdapter/Mongo/MongoClientTest.php index 75316829..c856082a 100644 --- a/tests/Alcaeus/MongoDbAdapter/Mongo/MongoClientTest.php +++ b/tests/Alcaeus/MongoDbAdapter/Mongo/MongoClientTest.php @@ -115,4 +115,126 @@ public function testNoPrefixUri() $client = $this->getClient(null, 'localhost'); $this->assertNotNull($client); } + + /** + * @dataProvider dataReadPreferenceOptionsAreInherited + */ + public function testReadPreferenceOptionsAreInherited($options, $uri, $expectedTagsets) + { + $client = $this->getClient($options, $uri); + $collection = $client->selectCollection('test', 'foo'); + + $this->assertSame( + [ + 'type' => \MongoClient::RP_SECONDARY_PREFERRED, + 'tagsets' => $expectedTagsets + ], + $collection->getReadPreference() + ); + } + + public static function dataReadPreferenceOptionsAreInherited() + { + $options = [ + 'readPreference' => \MongoClient::RP_SECONDARY_PREFERRED, + 'readPreferenceTags' => 'a:b', + ]; + + $overriddenOptions = [ + 'readPreference' => \MongoClient::RP_NEAREST, + 'readPreferenceTags' => 'c:d', + ]; + + $multipleTagsets = [ + 'readPreference' => \MongoClient::RP_SECONDARY_PREFERRED, + 'readPreferenceTags' => 'a:b,c:d', + ]; + + return [ + 'optionsArray' => [ + 'options' => $options, + 'uri' => 'mongodb://localhost', + 'expectedTagsets' => [['a' => 'b']], + ], + 'queryString' => [ + 'options' => [], + 'uri' => 'mongodb://localhost/?' . self::makeOptionString($options), + 'expectedTagsets' => [['a' => 'b']], + ], + 'multipleInQueryString' => [ + 'options' => [], + 'uri' => 'mongodb://localhost/?' . self::makeOptionString($options) . '&readPreferenceTags=c:d', + 'expectedTagsets' => [['a' => 'b'], ['c' => 'd']], + ], + 'overridden' => [ + 'options' => $options, + 'uri' => 'mongodb://localhost/?' . self::makeOptionString($overriddenOptions), + 'expectedTagsets' => [['c' => 'd'], ['a' => 'b']], + ], + 'multipleTagsetsOptions' => [ + 'options' => $multipleTagsets, + 'uri' => 'mongodb://localhost', + 'expectedTagsets' => [['a' => 'b', 'c' => 'd']], + ], + 'multipleTagsetsQueryString' => [ + 'options' => null, + 'uri' => 'mongodb://localhost/?' . self::makeOptionString($multipleTagsets), + 'expectedTagsets' => [['a' => 'b', 'c' => 'd']], + ], + ]; + } + + /** + * @dataProvider dataWriteConcernOptionsAreInherited + */ + public function testWriteConcernOptionsAreInherited($options, $uri) + { + $client = $this->getClient($options, $uri); + $collection = $client->selectCollection('test', 'foo'); + + $this->assertSame(['w' => 'majority', 'wtimeout' => 666], $collection->getWriteConcern()); + } + + public static function dataWriteConcernOptionsAreInherited() + { + $options = [ + 'w' => 'majority', + 'wTimeoutMs' => 666, + ]; + + $overriddenOptions = [ + 'w' => '2', + 'wTimeoutMs' => 333, + ]; + + return [ + 'optionsArray' => [ + 'options' => $options, + 'uri' => 'mongodb://localhost', + ], + 'queryString' => [ + 'options' => [], + 'uri' => 'mongodb://localhost/?' . self::makeOptionString($options), + ], + 'overridden' => [ + 'options' => $options, + 'uri' => 'mongodb://localhost/?' . self::makeOptionString($overriddenOptions), + ] + ]; + } + + /** + * @param array $options + * @return string + */ + private static function makeOptionString(array $options) + { + return implode('&', array_map( + function ($key, $value) { + return $key . '=' . $value; + }, + array_keys($options), + array_values($options) + )); + } } From 643efd987976671554dc2b576594ad92610441bc Mon Sep 17 00:00:00 2001 From: Andreas Braun Date: Wed, 22 Jun 2016 07:03:48 +0200 Subject: [PATCH 2/2] Add changelog entry --- CHANGELOG-1.0.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG-1.0.md b/CHANGELOG-1.0.md index 289e3043..38130e3a 100644 --- a/CHANGELOG-1.0.md +++ b/CHANGELOG-1.0.md @@ -3,7 +3,7 @@ CHANGELOG This changelog references the relevant changes done in minor version updates. -1.0.4 (xxxx-xx-xx) +1.0.4 (2016-06-22) ------------------ All issues and pull requests under this release may be found under the @@ -13,6 +13,9 @@ milestone. * [#115](https://github.com/alcaeus/mongo-php-adapter/pull/115) fixes an error where using the alternate syntax for `MongoCollection::aggregate` would lead to empty aggregation pipelines + * [#116](https://github.com/alcaeus/mongo-php-adapter/pull/116) fixes a bug + where read preference and write concern was not applied if it was passed in the + constructor. 1.0.3 (2016-04-13) ------------------