Unverified Commit 7b084d30 authored by Andreas Braun's avatar Andreas Braun

Merge pull request #729

* phplib-537:
  PHPLIB-537: Skip change stream tests on single node clusters
  PHPLIB-537: Allow more iterations for change stream spec tests on sharded clusters
  PHPLIB-537: "Cursor not found" error is no longer resumable
  PHPLIB-537: Use whitelist to check if a change stream is resumable
parents 9fa37e73 b26a7a35
...@@ -42,13 +42,31 @@ class ChangeStream implements Iterator ...@@ -42,13 +42,31 @@ class ChangeStream implements Iterator
*/ */
const CURSOR_NOT_FOUND = 43; const CURSOR_NOT_FOUND = 43;
/** @var array */ /** @var int[] */
private static $nonResumableErrorCodes = [ private static $resumableErrorCodes = [
136, // CappedPositionLost 6, // HostUnreachable
237, // CursorKilled 7, // HostNotFound
11601, // Interrupted 89, // NetworkTimeout
91, // ShutdownInProgress
189, // PrimarySteppedDown
262, // ExceededTimeLimit
9001, // SocketException
10107, // NotMaster
11600, // InterruptedAtShutdown
11602, // InterruptedDueToReplStateChange
13435, // NotMasterNoSlaveOk
13436, // NotMasterOrSecondary
63, // StaleShardVersion
150, // StaleEpoch
13388, // StaleConfig
234, // RetryChangeStream
133, // FailedToSatisfyReadPreference
216, // ElectionInProgress
]; ];
/** @var int */
private static $wireVersionForResumableChangeStreamError = 9;
/** @var callable */ /** @var callable */
private $resumeCallable; private $resumeCallable;
...@@ -180,15 +198,11 @@ class ChangeStream implements Iterator ...@@ -180,15 +198,11 @@ class ChangeStream implements Iterator
return false; return false;
} }
if ($exception->hasErrorLabel('NonResumableChangeStreamError')) { if (server_supports_feature($this->iterator->getServer(), self::$wireVersionForResumableChangeStreamError)) {
return false; return $exception->hasErrorLabel('ResumableChangeStreamError');
}
if (in_array($exception->getCode(), self::$nonResumableErrorCodes)) {
return false;
} }
return true; return in_array($exception->getCode(), self::$resumableErrorCodes);
} }
/** /**
......
...@@ -24,6 +24,7 @@ use MongoDB\Driver\Monitoring\CommandFailedEvent; ...@@ -24,6 +24,7 @@ use MongoDB\Driver\Monitoring\CommandFailedEvent;
use MongoDB\Driver\Monitoring\CommandStartedEvent; use MongoDB\Driver\Monitoring\CommandStartedEvent;
use MongoDB\Driver\Monitoring\CommandSubscriber; use MongoDB\Driver\Monitoring\CommandSubscriber;
use MongoDB\Driver\Monitoring\CommandSucceededEvent; use MongoDB\Driver\Monitoring\CommandSucceededEvent;
use MongoDB\Driver\Server;
use MongoDB\Exception\InvalidArgumentException; use MongoDB\Exception\InvalidArgumentException;
use MongoDB\Exception\ResumeTokenException; use MongoDB\Exception\ResumeTokenException;
use MongoDB\Exception\UnexpectedValueException; use MongoDB\Exception\UnexpectedValueException;
...@@ -63,6 +64,9 @@ class ChangeStreamIterator extends IteratorIterator implements CommandSubscriber ...@@ -63,6 +64,9 @@ class ChangeStreamIterator extends IteratorIterator implements CommandSubscriber
/** @var array|object|null */ /** @var array|object|null */
private $resumeToken; private $resumeToken;
/** @var Server */
private $server;
/** /**
* @internal * @internal
* @param Cursor $cursor * @param Cursor $cursor
...@@ -90,6 +94,7 @@ class ChangeStreamIterator extends IteratorIterator implements CommandSubscriber ...@@ -90,6 +94,7 @@ class ChangeStreamIterator extends IteratorIterator implements CommandSubscriber
$this->isRewindNop = ($firstBatchSize === 0); $this->isRewindNop = ($firstBatchSize === 0);
$this->postBatchResumeToken = $postBatchResumeToken; $this->postBatchResumeToken = $postBatchResumeToken;
$this->resumeToken = $initialResumeToken; $this->resumeToken = $initialResumeToken;
$this->server = $cursor->getServer();
} }
/** @internal */ /** @internal */
...@@ -152,6 +157,14 @@ class ChangeStreamIterator extends IteratorIterator implements CommandSubscriber ...@@ -152,6 +157,14 @@ class ChangeStreamIterator extends IteratorIterator implements CommandSubscriber
return $this->resumeToken; return $this->resumeToken;
} }
/**
* Returns the server the cursor is running on.
*/
public function getServer() : Server
{
return $this->server;
}
/** /**
* @see https://php.net/iteratoriterator.key * @see https://php.net/iteratoriterator.key
* @return mixed * @return mixed
......
This diff is collapsed.
...@@ -4,6 +4,7 @@ namespace MongoDB\Tests\SpecTests; ...@@ -4,6 +4,7 @@ namespace MongoDB\Tests\SpecTests;
use ArrayIterator; use ArrayIterator;
use LogicException; use LogicException;
use MongoDB\BSON\Int64;
use MongoDB\ChangeStream; use MongoDB\ChangeStream;
use MongoDB\Driver\Exception\Exception; use MongoDB\Driver\Exception\Exception;
use MongoDB\Model\BSONDocument; use MongoDB\Model\BSONDocument;
...@@ -22,16 +23,27 @@ use function glob; ...@@ -22,16 +23,27 @@ use function glob;
class ChangeStreamsSpecTest extends FunctionalTestCase class ChangeStreamsSpecTest extends FunctionalTestCase
{ {
/** @var array */ /** @var array */
private static $incompleteTests = ['change-streams-errors: Change Stream should error when _id is projected out' => 'PHPC-1419']; private static $incompleteTests = [];
/** /**
* Assert that the expected and actual command documents match. * Assert that the expected and actual command documents match.
* *
* Note: this method may modify the $expected object.
*
* @param stdClass $expected Expected command document * @param stdClass $expected Expected command document
* @param stdClass $actual Actual command document * @param stdClass $actual Actual command document
*/ */
public static function assertCommandMatches(stdClass $expected, stdClass $actual) public static function assertCommandMatches(stdClass $expected, stdClass $actual)
{ {
if (isset($expected->getMore) && $expected->getMore === 42) {
static::assertObjectHasAttribute('getMore', $actual);
static::assertThat($actual->getMore, static::logicalOr(
static::isInstanceOf(Int64::class),
static::isType('integer')
));
unset($expected->getMore);
}
static::assertDocumentsMatch($expected, $actual); static::assertDocumentsMatch($expected, $actual);
} }
...@@ -74,9 +86,13 @@ class ChangeStreamsSpecTest extends FunctionalTestCase ...@@ -74,9 +86,13 @@ class ChangeStreamsSpecTest extends FunctionalTestCase
$this->markTestIncomplete(self::$incompleteTests[$this->dataDescription()]); $this->markTestIncomplete(self::$incompleteTests[$this->dataDescription()]);
} }
if ($this->isShardedCluster() && ! $this->isShardedClusterUsingReplicasets()) {
$this->markTestSkipped('$changeStream is only supported with replicasets');
}
$this->checkServerRequirements($this->createRunOn($test)); $this->checkServerRequirements($this->createRunOn($test));
if (! isset($databaseName, $collectionName, $database2Name, $collection2Name)) { if (! isset($databaseName, $collectionName)) {
$this->fail('Required database and collection names are unset'); $this->fail('Required database and collection names are unset');
} }
...@@ -84,7 +100,10 @@ class ChangeStreamsSpecTest extends FunctionalTestCase ...@@ -84,7 +100,10 @@ class ChangeStreamsSpecTest extends FunctionalTestCase
$this->setContext($context); $this->setContext($context);
$this->dropDatabasesAndCreateCollection($databaseName, $collectionName); $this->dropDatabasesAndCreateCollection($databaseName, $collectionName);
if (isset($database2Name, $collection2Name)) {
$this->dropDatabasesAndCreateCollection($database2Name, $collection2Name); $this->dropDatabasesAndCreateCollection($database2Name, $collection2Name);
}
if (isset($test->failPoint)) { if (isset($test->failPoint)) {
$this->configureFailPoint($test->failPoint); $this->configureFailPoint($test->failPoint);
...@@ -248,6 +267,13 @@ class ChangeStreamsSpecTest extends FunctionalTestCase ...@@ -248,6 +267,13 @@ class ChangeStreamsSpecTest extends FunctionalTestCase
* to return as many results as are expected. Require at least one * to return as many results as are expected. Require at least one
* iteration to allow next() a chance to throw for error tests. */ * iteration to allow next() a chance to throw for error tests. */
$maxIterations = $limit + 1; $maxIterations = $limit + 1;
/* On sharded clusters, allow for empty getMore calls due to sharding
* architecture */
if ($this->isShardedCluster()) {
$maxIterations *= 5;
}
$events = []; $events = [];
for ($i = 0, $changeStream->rewind(); $i < $maxIterations; $i++, $changeStream->next()) { for ($i = 0, $changeStream->rewind(); $i < $maxIterations; $i++, $changeStream->next()) {
......
...@@ -54,9 +54,7 @@ ...@@ -54,9 +54,7 @@
"cursor": {}, "cursor": {},
"pipeline": [ "pipeline": [
{ {
"$changeStream": { "$changeStream": {}
"fullDocument": "default"
}
}, },
{ {
"$unsupported": "foo" "$unsupported": "foo"
...@@ -104,10 +102,54 @@ ...@@ -104,10 +102,54 @@
], ],
"result": { "result": {
"error": { "error": {
"code": 280, "code": 280
"errorLabels": [ }
"NonResumableChangeStreamError" }
] },
{
"description": "change stream errors on MaxTimeMSExpired",
"minServerVersion": "4.2",
"failPoint": {
"configureFailPoint": "failCommand",
"mode": {
"times": 1
},
"data": {
"failCommands": [
"getMore"
],
"errorCode": 50,
"closeConnection": false
}
},
"target": "collection",
"topology": [
"replicaset",
"sharded"
],
"changeStreamPipeline": [
{
"$project": {
"_id": 0
}
}
],
"changeStreamOptions": {},
"operations": [
{
"database": "change-stream-tests",
"collection": "test",
"name": "insertOne",
"arguments": {
"document": {
"z": 3
}
}
}
],
"result": {
"error": {
"code": 50
} }
} }
} }
......
collection_name: &collection_name "test"
database_name: &database_name "change-stream-tests"
collection2_name: &collection2_name "test2"
database2_name: &database2_name "change-stream-tests-2"
tests:
-
description: The watch helper must not throw a custom exception when executed against a single server topology, but instead depend on a server error
minServerVersion: "3.6.0"
target: collection
topology:
- single
changeStreamPipeline: []
changeStreamOptions: {}
operations: []
expectations: []
result:
error:
code: 40573
-
description: Change Stream should error when an invalid aggregation stage is passed in
minServerVersion: "3.6.0"
target: collection
topology:
- replicaset
changeStreamPipeline:
-
$unsupported: foo
changeStreamOptions: {}
operations:
-
database: *database_name
collection: *collection_name
name: insertOne
arguments:
document:
z: 3
expectations:
-
command_started_event:
command:
aggregate: *collection_name
cursor: {}
pipeline:
-
$changeStream:
fullDocument: default
-
$unsupported: foo
command_name: aggregate
database_name: *database_name
result:
error:
code: 40324
-
description: Change Stream should error when _id is projected out
minServerVersion: "4.1.11"
target: collection
topology:
- replicaset
- sharded
changeStreamPipeline:
-
$project: { _id: 0 }
changeStreamOptions: {}
operations:
-
database: *database_name
collection: *collection_name
name: insertOne
arguments:
document:
z: 3
result:
error:
code: 280
errorLabels: [ "NonResumableChangeStreamError" ]
...@@ -33,9 +33,7 @@ ...@@ -33,9 +33,7 @@
"cursor": {}, "cursor": {},
"pipeline": [ "pipeline": [
{ {
"$changeStream": { "$changeStream": {}
"fullDocument": "default"
}
} }
] ]
}, },
...@@ -153,9 +151,7 @@ ...@@ -153,9 +151,7 @@
"cursor": {}, "cursor": {},
"pipeline": [ "pipeline": [
{ {
"$changeStream": { "$changeStream": {}
"fullDocument": "default"
}
} }
] ]
}, },
...@@ -226,9 +222,7 @@ ...@@ -226,9 +222,7 @@
"cursor": {}, "cursor": {},
"pipeline": [ "pipeline": [
{ {
"$changeStream": { "$changeStream": {}
"fullDocument": "default"
}
}, },
{ {
"$match": { "$match": {
...@@ -312,9 +306,7 @@ ...@@ -312,9 +306,7 @@
"cursor": {}, "cursor": {},
"pipeline": [ "pipeline": [
{ {
"$changeStream": { "$changeStream": {}
"fullDocument": "default"
}
} }
] ]
}, },
...@@ -404,7 +396,6 @@ ...@@ -404,7 +396,6 @@
"pipeline": [ "pipeline": [
{ {
"$changeStream": { "$changeStream": {
"fullDocument": "default",
"allChangesForCluster": true "allChangesForCluster": true
} }
} }
...@@ -523,9 +514,7 @@ ...@@ -523,9 +514,7 @@
"cursor": {}, "cursor": {},
"pipeline": [ "pipeline": [
{ {
"$changeStream": { "$changeStream": {}
"fullDocument": "default"
}
} }
] ]
}, },
...@@ -611,9 +600,7 @@ ...@@ -611,9 +600,7 @@
"cursor": {}, "cursor": {},
"pipeline": [ "pipeline": [
{ {
"$changeStream": { "$changeStream": {}
"fullDocument": "default"
}
} }
] ]
}, },
...@@ -665,9 +652,7 @@ ...@@ -665,9 +652,7 @@
"cursor": {}, "cursor": {},
"pipeline": [ "pipeline": [
{ {
"$changeStream": { "$changeStream": {}
"fullDocument": "default"
}
} }
] ]
}, },
...@@ -756,9 +741,7 @@ ...@@ -756,9 +741,7 @@
}, },
"pipeline": [ "pipeline": [
{ {
"$changeStream": { "$changeStream": {}
"fullDocument": "default"
}
} }
] ]
}, },
......
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment