Skip to content

PHPLIB-58: CRUD spec functional tests #4

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 19 commits into from
Apr 30, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
c520df8
Split Database and Collection functional tests
jmikola Apr 27, 2015
c991d3a
PHPLIB-91: Ensure count/distinct filters serialize as BSON objects
jmikola Apr 28, 2015
87cd989
PHPLIB-58: Functional tests for CRUD spec read methods
jmikola Mar 17, 2015
619c27d
PHPLIB-92: Update methods should use "multi" option
jmikola Apr 29, 2015
719fd25
PHPLIB-93: Insert result classes should always track IDs
jmikola Apr 29, 2015
30444c7
Restore Collection::_massageFindAndModifyOptions()
jmikola Apr 29, 2015
fd3f617
Return findAndModify result document as an array
jmikola Apr 29, 2015
5c64633
Clean up after passing Collection functional tests
jmikola Apr 29, 2015
89391d2
Hash test case names to avoid hitting namespace limits
jmikola Apr 29, 2015
362a27f
PHPLIB-58: Functional tests for CRUD spec write methods
jmikola Apr 29, 2015
d41c3f2
PHPLIB-96: Fix replacement/upsert test failures for 2.4
jmikola Apr 30, 2015
fed26a9
PHPLIB-97: Cast count() results to integers
jmikola Apr 30, 2015
786025d
PHPLIB-95: Massage findAndModify null results before 3.0
jmikola Apr 30, 2015
3e80df3
PHPLIB-98: Add multiple server versions to Travis CI
jmikola Apr 29, 2015
0241eda
PHPLIB-86: Fix aggregate() useCursor default and 2.4 compatibility
jmikola Apr 30, 2015
4e22eb2
Skip $out aggregation test for MongoDB 2.4
jmikola Apr 30, 2015
5076ff1
Do not check modifiedCount for updates on 2.4
jmikola Apr 30, 2015
9d218a7
PHPLIB-98: Ensure mongod service is started for all versions
jmikola Apr 30, 2015
8e89680
Reduce default server selection timeout for tests
jmikola Apr 30, 2015
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 21 additions & 4 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,29 @@ php:
- 5.6

env:
- MONGODB_VERSION=alpha
global:
- KEY_SERVER="hkp://keyserver.ubuntu.com:80"
- MONGO_REPO_URI="http://repo.mongodb.com/apt/ubuntu"
- MONGO_REPO_TYPE="precise/mongodb-enterprise/"
- SOURCES_LOC="/etc/apt/sources.list.d/mongodb.list"
matrix:
- DRIVER_VERSION=alpha SERVER_VERSION=2.4
- DRIVER_VERSION=alpha SERVER_VERSION=2.6
- DRIVER_VERSION=alpha SERVER_VERSION=3.0

services: mongodb
before_install:
- sudo apt-key adv --keyserver ${KEY_SERVER} --recv 7F0CEB10
- echo "deb ${MONGO_REPO_URI} ${MONGO_REPO_TYPE}${SERVER_VERSION} multiverse" | sudo tee ${SOURCES_LOC}
- sudo apt-get update -qq

install:
- if dpkg --compare-versions ${SERVER_VERSION} le "2.4"; then export SERVER_PACKAGE=mongodb-10gen-enterprise; else export SERVER_PACKAGE=mongodb-enterprise; fi
- sudo apt-get install ${SERVER_PACKAGE}

before_script:
- mongo --eval 'tojson(db.runCommand({buildInfo:1}))'
- pecl install -f mongodb-${MONGODB_VERSION}
- if dpkg --compare-versions ${SERVER_VERSION} le "2.4"; then export SERVER_SERVICE=mongodb; else export SERVER_SERVICE=mongod; fi
- if ! nc -z localhost 27017; then sudo service ${SERVER_SERVICE} start; fi
- mongod --version
- pecl install -f mongodb-${DRIVER_VERSION}
- php --ri mongodb
- composer install --dev --no-interaction --prefer-source
2 changes: 1 addition & 1 deletion phpunit.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

<php>
<ini name="error_reporting" value="-1"/>
<env name="MONGODB_URI" value="mongodb://127.0.0.1:27017"/>
<env name="MONGODB_URI" value="mongodb://127.0.0.1:27017/?serverSelectionTimeoutMS=100"/>
<env name="MONGODB_DATABASE" value="phplib_test"/>
</php>

Expand Down
202 changes: 124 additions & 78 deletions src/Collection.php
Original file line number Diff line number Diff line change
Expand Up @@ -71,37 +71,75 @@ public function __construct(Manager $manager, $namespace, WriteConcern $writeCon

/**
* Runs an aggregation framework pipeline
* NOTE: The return value of this method depends on your MongoDB server version
* and possibly options.
* MongoDB 2.6 (and later) will return a Cursor by default
* MongoDB pre 2.6 will return an ArrayIterator
*
* Note: this method's return value depends on the MongoDB server version
* and the "useCursor" option. If "useCursor" is true, a Cursor will be
* returned; otherwise, an ArrayIterator is returned, which wraps the
* "result" array from the command response document.
*
* @see http://docs.mongodb.org/manual/reference/command/aggregate/
* @see Collection::getAggregateOptions() for supported $options
*
* @param array $pipeline The pipeline to execute
* @param array $options Additional options
* @return Iterator
*/
public function aggregate(array $pipeline, array $options = array())
{
$options = array_merge($this->getAggregateOptions(), $options);
$options = $this->_massageAggregateOptions($options);
$cmd = array(
"aggregate" => $this->collname,
"pipeline" => $pipeline,
) + $options;
$readPreference = new ReadPreference(ReadPreference::RP_PRIMARY);
$server = $this->manager->selectServer($readPreference);

if (FeatureDetection::isSupported($server, FeatureDetection::API_AGGREGATE_CURSOR)) {
$options = array_merge(
array(
/**
* Enables writing to temporary files. When set to true, aggregation stages
* can write data to the _tmp subdirectory in the dbPath directory. The
* default is false.
*
* @see http://docs.mongodb.org/manual/reference/command/aggregate/
*/
'allowDiskUse' => false,
/**
* The number of documents to return per batch.
*
* @see http://docs.mongodb.org/manual/reference/command/aggregate/
*/
'batchSize' => 0,
/**
* The maximum amount of time to allow the query to run.
*
* @see http://docs.mongodb.org/manual/reference/command/aggregate/
*/
'maxTimeMS' => 0,
/**
* Indicates if the results should be provided as a cursor.
*
* @see http://docs.mongodb.org/manual/reference/command/aggregate/
*/
'useCursor' => true,
),
$options
);
}

$cursor = $this->_runCommand($this->dbname, $cmd);
$options = $this->_massageAggregateOptions($options);
$command = new Command(array(
'aggregate' => $this->collname,
'pipeline' => $pipeline,
) + $options);
$cursor = $server->executeCommand($this->dbname, $command);

if (isset($cmd["cursor"]) && $cmd["cursor"]) {
if ( ! empty($options["cursor"])) {
return $cursor;
}

$doc = current($cursor->toArray());

if ($doc["ok"]) {
return new \ArrayIterator($doc["result"]);
return new \ArrayIterator(array_map(
function (\stdClass $document) { return (array) $document; },
$doc["result"]
));
}

throw $this->_generateCommandException($doc);
Expand Down Expand Up @@ -239,12 +277,12 @@ public function count(array $filter = array(), array $options = array())
{
$cmd = array(
"count" => $this->collname,
"query" => $filter,
"query" => (object) $filter,
) + $options;

$doc = current($this->_runCommand($this->dbname, $cmd)->toArray());
if ($doc["ok"]) {
return $doc["n"];
return (integer) $doc["n"];
}
throw $this->_generateCommandException($doc);
}
Expand Down Expand Up @@ -363,7 +401,7 @@ public function distinct($fieldName, array $filter = array(), array $options = a
$cmd = array(
"distinct" => $this->collname,
"key" => $fieldName,
"query" => $filter,
"query" => (object) $filter,
) + $options;

$doc = current($this->_runCommand($this->dbname, $cmd)->toArray());
Expand Down Expand Up @@ -497,7 +535,7 @@ public function findOneAndDelete(array $filter, array $options = array())

$doc = current($this->_runCommand($this->dbname, $cmd)->toArray());
if ($doc["ok"]) {
return $doc["value"];
return is_object($doc["value"]) ? (array) $doc["value"] : $doc["value"];
}

throw $this->_generateCommandException($doc);
Expand Down Expand Up @@ -534,7 +572,7 @@ public function findOneAndReplace(array $filter, array $replacement, array $opti

$doc = current($this->_runCommand($this->dbname, $cmd)->toArray());
if ($doc["ok"]) {
return $doc["value"];
return $this->_massageFindAndModifyResult($doc, $options);
}

throw $this->_generateCommandException($doc);
Expand Down Expand Up @@ -572,61 +610,12 @@ public function findOneAndUpdate(array $filter, array $update, array $options =

$doc = current($this->_runCommand($this->dbname, $cmd)->toArray());
if ($doc["ok"]) {
return $doc["value"];
return $this->_massageFindAndModifyResult($doc, $options);
}

throw $this->_generateCommandException($doc);
}

/**
* Retrieves all aggregate options with their default values.
*
* @return array of Collection::aggregate() options
*/
public function getAggregateOptions()
{
$opts = array(
/**
* Enables writing to temporary files. When set to true, aggregation stages
* can write data to the _tmp subdirectory in the dbPath directory. The
* default is false.
*
* @see http://docs.mongodb.org/manual/reference/command/aggregate/
*/
"allowDiskUse" => false,

/**
* The number of documents to return per batch.
*
* @see http://docs.mongodb.org/manual/reference/command/aggregate/
*/
"batchSize" => 0,

/**
* The maximum amount of time to allow the query to run.
*
* @see http://docs.mongodb.org/manual/reference/command/aggregate/
*/
"maxTimeMS" => 0,

/**
* Indicates if the results should be provided as a cursor.
*
* The default for this value depends on the version of the server.
* - Servers >= 2.6 will use a default of true.
* - Servers < 2.6 will use a default of false.
*
* As with any other property, this value can be changed.
*
* @see http://docs.mongodb.org/manual/reference/command/aggregate/
*/
"useCursor" => true,
);

/* FIXME: Add a version check for useCursor */
return $opts;
}

/**
* Retrieves all Bulk Write options with their default values.
*
Expand Down Expand Up @@ -961,7 +950,7 @@ public function getWriteOptions()
*
* @see http://docs.mongodb.org/manual/reference/command/insert/
*
* @param array $documents The documents to insert
* @param array[]|object[] $documents The documents to insert
* @return InsertManyResult
*/
public function insertMany(array $documents)
Expand All @@ -976,6 +965,8 @@ public function insertMany(array $documents)

if ($insertedId !== null) {
$insertedIds[$i] = $insertedId;
} else {
$insertedIds[$i] = is_array($document) ? $document['_id'] : $document->_id;
}
}

Expand All @@ -989,18 +980,21 @@ public function insertMany(array $documents)
*
* @see http://docs.mongodb.org/manual/reference/command/insert/
*
* @param array $document The document to insert
* @param array $options Additional options
* @param array|object $document The document to insert
* @return InsertOneResult
*/
public function insertOne(array $document)
public function insertOne($document)
{
$options = array_merge($this->getWriteOptions());

$bulk = new BulkWrite($options["ordered"]);
$id = $bulk->insert($document);
$wr = $this->manager->executeBulkWrite($this->ns, $bulk, $this->wc);

if ($id === null) {
$id = is_array($document) ? $document['_id'] : $document->_id;
}

return new InsertOneResult($wr, $id);
}

Expand Down Expand Up @@ -1038,7 +1032,7 @@ public function replaceOne(array $filter, array $update, array $options = array(
if (isset($firstKey[0]) && $firstKey[0] == '$') {
throw new InvalidArgumentException("First key in \$update must NOT be a \$operator");
}
$wr = $this->_update($filter, $update, $options);
$wr = $this->_update($filter, $update, $options + array("multi" => false));

return new UpdateResult($wr);
}
Expand All @@ -1057,7 +1051,7 @@ public function replaceOne(array $filter, array $update, array $options = array(
*/
public function updateMany(array $filter, $update, array $options = array())
{
$wr = $this->_update($filter, $update, $options + array("limit" => 0));
$wr = $this->_update($filter, $update, $options + array("multi" => true));

return new UpdateResult($wr);
}
Expand All @@ -1080,7 +1074,7 @@ public function updateOne(array $filter, array $update, array $options = array()
if (!isset($firstKey[0]) || $firstKey[0] != '$') {
throw new InvalidArgumentException("First key in \$update must be a \$operator");
}
$wr = $this->_update($filter, $update, $options);
$wr = $this->_update($filter, $update, $options + array("multi" => false));

return new UpdateResult($wr);
}
Expand Down Expand Up @@ -1146,14 +1140,66 @@ final protected function _generateCommandException($doc)
*/
protected function _massageAggregateOptions($options)
{
if ($options["useCursor"]) {
$options["cursor"] = array("batchSize" => $options["batchSize"]);
if ( ! empty($options["useCursor"])) {
$options["cursor"] = isset($options["batchSize"])
? array("batchSize" => (integer) $options["batchSize"])
: new stdClass;
}
unset($options["useCursor"], $options["batchSize"]);

return $options;
}

/**
* Internal helper for massaging findandmodify options
* @internal
*/
final protected function _massageFindAndModifyOptions($options, $update = array())
{
$ret = array(
"sort" => $options["sort"],
"new" => isset($options["returnDocument"]) ? $options["returnDocument"] == self::FIND_ONE_AND_RETURN_AFTER : false,
"fields" => $options["projection"],
"upsert" => isset($options["upsert"]) ? $options["upsert"] : false,
);
if ($update) {
$ret["update"] = $update;
} else {
$ret["remove"] = true;
}
return $ret;
}

/**
* Internal helper for massaging the findAndModify result.
*
* @internal
* @param array $result
* @param array $options
* @return array|null
*/
final protected function _massageFindAndModifyResult(array $result, array $options)
{
if ($result['value'] === null) {
return null;
}

/* Prior to 3.0, findAndModify returns an empty document instead of null
* when an upsert is performed and the pre-modified document was
* requested.
*/
if ($options['upsert'] && ! $options['new'] &&
isset($result['lastErrorObject']->updatedExisting) &&
! $result['lastErrorObject']->updatedExisting) {

return null;
}

return is_object($result["value"])
? (array) $result['value']
: $result['value'];
}

/**
* Constructs the Query Wire Protocol field 'flags' based on $options
* provided to other helpers
Expand Down
1 change: 1 addition & 0 deletions src/FeatureDetection.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ class FeatureDetection
const API_LISTCOLLECTIONS_CMD = 3;
const API_LISTINDEXES_CMD = 3;
const API_CREATEINDEXES_CMD = 2;
const API_AGGREGATE_CURSOR = 2;

/**
* Return whether the server supports a particular feature.
Expand Down
Loading