Skip to content
This repository has been archived by the owner on Nov 11, 2020. It is now read-only.

Internal cursor improvements for hint, sort, RP and recreation #105

Merged
merged 4 commits into from
Apr 23, 2013
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
53 changes: 42 additions & 11 deletions lib/Doctrine/MongoDB/Cursor.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,15 +44,17 @@ class Cursor implements Iterator
protected $numRetries;
protected $query = array();
protected $fields = array();
protected $hints = array();
protected $hint;
protected $immortal;
protected $options = array();
protected $batchSize;
protected $limit;
protected $readPreference;
protected $readPreferenceTags;
protected $skip;
protected $slaveOkay;
protected $snapshot;
protected $sorts = array();
protected $sort;
protected $tailable;
protected $timeout;

Expand Down Expand Up @@ -99,8 +101,8 @@ public function getFields()
public function recreate()
{
$this->mongoCursor = $this->collection->getMongoCollection()->find($this->query, $this->fields);
foreach ($this->hints as $hint) {
$this->mongoCursor->hint($hint);
if ($this->hint !== null) {
$this->mongoCursor->hint($this->hint);
}
if ($this->immortal !== null) {
$this->mongoCursor->immortal($this->immortal);
Expand All @@ -120,11 +122,19 @@ public function recreate()
if ($this->slaveOkay !== null) {
$this->setMongoCursorSlaveOkay($this->slaveOkay);
}
// Set read preferences after slaveOkay, since they may be more specific
if ($this->readPreference !== null) {
if ($this->readPreferenceTags !== null) {
$this->mongoCursor->setReadPreference($this->readPreference, $this->readPreferenceTags);
} else {
$this->mongoCursor->setReadPreference($this->readPreference);
}
}
if ($this->snapshot) {
$this->mongoCursor->snapshot();
}
foreach ($this->sorts as $sort) {
$this->mongoCursor->sort($sort);
if ($this->sort !== null) {
$this->mongoCursor->sort($this->sort);
}
if ($this->tailable !== null) {
$this->mongoCursor->tailable($this->tailable);
Expand Down Expand Up @@ -204,7 +214,7 @@ public function hasNext()

public function hint(array $keyPattern)
{
$this->hints[] = $keyPattern;
$this->hint = $keyPattern;
$this->mongoCursor->hint($keyPattern);
return $this;
}
Expand Down Expand Up @@ -339,7 +349,7 @@ public function sort($fields)
}
$fields[$fieldName] = (integer) $order;
}
$this->sorts[] = $fields;
$this->sort = $fields;
$this->mongoCursor->sort($fields);
return $this;
}
Expand Down Expand Up @@ -392,13 +402,34 @@ public function getReadPreference()
return $this->getMongoDB()->getReadPreference();
}

/**
* Set the read preference.
*
* This method returns the Cursor object to allow method chaining, unlike
* the base MongoCursor method, which returns a boolean value indicating
* success or failure.
*
* @param string $readPreference
* @param array $tags
* @return Cursor
* @throws InvalidArgumentException if MongoCursor::setReadPreference() fails
*/
public function setReadPreference($readPreference, array $tags = null)
{
if (isset($tags)) {
return $this->mongoCursor->setReadPreference($readPreference, $tags);
$this->readPreference = $readPreference;
$this->readPreferenceTags = $tags;

if ($tags !== null) {
$retval = $this->mongoCursor->setReadPreference($readPreference, $tags);
} else {
$retval = $this->mongoCursor->setReadPreference($readPreference);
}

return $this->mongoCursor->setReadPreference($readPreference);
if ($retval !== true) {
throw new \InvalidArgumentException('Invalid arguments for MongoCursor::setReadPreference()');
}

return $this;
}

protected function retry(\Closure $retry, $recreate = false)
Expand Down
151 changes: 147 additions & 4 deletions tests/Doctrine/MongoDB/Tests/CursorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -204,8 +204,144 @@ public function testSetReadPreference()

$cursor = $this->getTestCursor($this->getMockConnection(), $this->getMockCollection(), $mongoCursor);

$this->assertTrue($cursor->setReadPreference(\MongoClient::RP_PRIMARY));
$this->assertTrue($cursor->setReadPreference(\MongoClient::RP_SECONDARY_PREFERRED, array(array('dc' => 'east'))));
$this->assertSame($cursor, $cursor->setReadPreference(\MongoClient::RP_PRIMARY));
$this->assertSame($cursor, $cursor->setReadPreference(\MongoClient::RP_SECONDARY_PREFERRED, array(array('dc' => 'east'))));
}

/**
* In practice, MongoCursor::setReadPreference() will raise an E_WARNING
* before we throw an exception.
*
* @expectedException InvalidArgumentException
*/
public function testSetReadPreferenceExceptionForInvalidReadPreference()
{
if (!method_exists('MongoCursor', 'setReadPreference')) {
$this->markTestSkipped('This test is not applicable to drivers without MongoCursor::setReadPreference()');
}

$mongoCursor = $this->getMockMongoCursor();

$mongoCursor->expects($this->once())
->method('setReadPreference')
->with('InvalidReadPreference')
->will($this->returnValue(false));

$cursor = $this->getTestCursor($this->getMockConnection(), $this->getMockCollection(), $mongoCursor);

$cursor->setReadPreference('InvalidReadPreference');
}

/**
* In practice, MongoCursor::setReadPreference() will raise an E_WARNING
* before we throw an exception.
*
* @expectedException InvalidArgumentException
*/
public function testSetReadPreferenceExceptionForInvalidTags()
{
if (!method_exists('MongoCursor', 'setReadPreference')) {
$this->markTestSkipped('This test is not applicable to drivers without MongoCursor::setReadPreference()');
}

$mongoCursor = $this->getMockMongoCursor();

$mongoCursor->expects($this->once())
->method('setReadPreference')
->with(\MongoClient::RP_PRIMARY, array(array('dc' => 'east')))
->will($this->returnValue(false));

$cursor = $this->getTestCursor($this->getMockConnection(), $this->getMockCollection(), $mongoCursor);

$cursor->setReadPreference(\MongoClient::RP_PRIMARY, array(array('dc' => 'east')));
}

public function testRecreate()
{
if (!method_exists('MongoCursor', 'setReadPreference')) {
$this->markTestSkipped('This test requires MongoCursor::setReadPreference()');
}

$self = $this;

$setCursorExpectations = function($mongoCursor) use ($self) {
$mongoCursor->expects($self->once())
->method('hint')
->with(array('x' => 1));
$mongoCursor->expects($self->once())
->method('immortal')
->with(false);
$mongoCursor->expects($self->at(2))
->method('addOption')
->with('$min', array('x' => 9000));
$mongoCursor->expects($self->at(3))
->method('addOption')
->with('$max', array('x' => 9999));
$mongoCursor->expects($self->once())
->method('batchSize')
->with(10);
$mongoCursor->expects($self->once())
->method('limit')
->with(20);
$mongoCursor->expects($self->once())
->method('skip')
->with(0);
$mongoCursor->expects($self->at(7))
->method('setReadPreference')
->with(\MongoClient::RP_PRIMARY)
->will($self->returnValue(true));
$mongoCursor->expects($self->at(8))
->method('setReadPreference')
->with(\MongoClient::RP_NEAREST, array(array('dc' => 'east')))
->will($self->returnValue(true));
$mongoCursor->expects($self->once())
->method('snapshot');
$mongoCursor->expects($self->once())
->method('sort')
->with(array('x' => -1));
$mongoCursor->expects($self->once())
->method('tailable')
->with(false);
$mongoCursor->expects($self->once())
->method('timeout')
->with(1000);
};

$mongoCursor = $this->getMockMongoCursor();
$recreatedMongoCursor = $this->getMockMongoCursor();

$setCursorExpectations($mongoCursor);
$setCursorExpectations($recreatedMongoCursor);

$mongoCollection = $this->getMockCollection();
$mongoCollection->expects($this->once())
->method('find')
->with(array('x' => 9500), array())
->will($this->returnValue($recreatedMongoCursor));

$collection = $this->getMockCollection();
$collection->expects($this->once())
->method('getMongoCollection')
->will($this->returnValue($mongoCollection));

$cursor = $this->getTestCursor($this->getMockConnection(), $collection, $mongoCursor, array('x' => 9500));

$cursor
->hint(array('x' => 1))
->immortal(false)
->addOption('$min', array('x' => 9000))
->addOption('$max', array('x' => 9999))
->batchSize(10)
->limit(20)
->skip(0)
->slaveOkay(false)
->setReadPreference(\MongoClient::RP_NEAREST, array(array('dc' => 'east')))
->snapshot()
->sort(array('x' => -1))
->tailable(false)
->timeout(1000);

$cursor->recreate();
}

private function getMockMongoCursor()
Expand All @@ -215,6 +351,13 @@ private function getMockMongoCursor()
->getMock();
}

private function getMockMongoCollection()
{
return $this->getMockBuilder('MongoCollection')
->disableOriginalConstructor()
->getMock();
}

private function getMockCollection()
{
return $this->getMockBuilder('Doctrine\MongoDB\Collection')
Expand All @@ -229,8 +372,8 @@ private function getMockConnection()
->getMock();
}

private function getTestCursor(Connection $connection, Collection $collection, \MongoCursor $mongoCursor)
private function getTestCursor(Connection $connection, Collection $collection, \MongoCursor $mongoCursor, $query = array())
{
return new Cursor($connection, $collection, $mongoCursor);
return new Cursor($connection, $collection, $mongoCursor, $query);
}
}