Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Contribute to GitLab
Sign in
Toggle navigation
M
mongo-php-library
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
sinan
mongo-php-library
Commits
b19328ac
Commit
b19328ac
authored
Jun 25, 2019
by
Jeremy Mikola
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #626
parents
9f457142
fdf87ad0
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
134 additions
and
10 deletions
+134
-10
ChangeStream.php
src/ChangeStream.php
+24
-8
WatchFunctionalTest.php
tests/Operation/WatchFunctionalTest.php
+110
-2
No files found.
src/ChangeStream.php
View file @
b19328ac
...
...
@@ -50,6 +50,11 @@ class ChangeStream implements Iterator
private
$resumeCallable
;
private
$csIt
;
private
$key
=
0
;
/**
* Whether the change stream has advanced to its first result. This is used
* to determine whether $key should be incremented after an iteration event.
*/
private
$hasAdvanced
=
false
;
/**
...
...
@@ -102,7 +107,7 @@ class ChangeStream implements Iterator
{
try
{
$this
->
csIt
->
next
();
$this
->
onIteration
(
true
);
$this
->
onIteration
(
$this
->
hasAdvanced
);
}
catch
(
RuntimeException
$e
)
{
if
(
$this
->
isResumableError
(
$e
))
{
$this
->
resume
();
...
...
@@ -118,6 +123,9 @@ class ChangeStream implements Iterator
{
try
{
$this
->
csIt
->
rewind
();
/* Unlike next() and resume(), the decision to increment the key
* does not depend on whether the change stream has advanced. This
* ensures that multiple calls to rewind() do not alter state. */
$this
->
onIteration
(
false
);
}
catch
(
RuntimeException
$e
)
{
if
(
$this
->
isResumableError
(
$e
))
{
...
...
@@ -193,12 +201,12 @@ class ChangeStream implements Iterator
}
/**
* Perform housekeeping after an iteration event
(i.e. next or rewind)
.
* Perform housekeeping after an iteration event.
*
* @param boolean $i
sNext Whether the iteration event was a call to next()
* @param boolean $i
ncrementKey Increment $key if there is a current result
* @throws ResumeTokenException
*/
private
function
onIteration
(
$i
sNext
)
private
function
onIteration
(
$i
ncrementKey
)
{
/* If the cursorId is 0, the server has invalidated the cursor and we
* will never perform another getMore nor need to resume since any
...
...
@@ -210,13 +218,13 @@ class ChangeStream implements Iterator
$this
->
resumeCallable
=
null
;
}
/* Return early if there is not a current result. Avoid any attempt to
* increment the iterator's key or extract a resume token */
if
(
!
$this
->
valid
())
{
return
;
}
/* Increment the key if the iteration event was a call to next() and we
* have already advanced past the first result. */
if
(
$isNext
&&
$this
->
hasAdvanced
)
{
if
(
$incrementKey
)
{
$this
->
key
++
;
}
...
...
@@ -234,6 +242,14 @@ class ChangeStream implements Iterator
$newChangeStream
=
call_user_func
(
$this
->
resumeCallable
,
$this
->
resumeToken
);
$this
->
csIt
=
$newChangeStream
->
csIt
;
$this
->
csIt
->
rewind
();
$this
->
onIteration
(
false
);
/* Note: if we are resuming after a call to ChangeStream::rewind(),
* $hasAdvanced will always be false. For it to be true, rewind() would
* need to have thrown a RuntimeException with a resumable error, which
* can only happen during the first call to IteratorIterator::rewind()
* before onIteration() has a chance to set $hasAdvanced to true.
* Otherwise, IteratorIterator::rewind() would either NOP (consecutive
* rewinds) or throw a LogicException (rewind after next), neither of
* which would result in a call to resume(). */
$this
->
onIteration
(
$this
->
hasAdvanced
);
}
}
tests/Operation/WatchFunctionalTest.php
View file @
b19328ac
...
...
@@ -8,6 +8,7 @@ use MongoDB\Driver\Manager;
use
MongoDB\Driver\ReadPreference
;
use
MongoDB\Driver\Server
;
use
MongoDB\Driver\Exception\ConnectionTimeoutException
;
use
MongoDB\Driver\Exception\LogicException
;
use
MongoDB\Exception\ResumeTokenException
;
use
MongoDB\Operation\CreateCollection
;
use
MongoDB\Operation\DatabaseCommand
;
...
...
@@ -225,6 +226,62 @@ class WatchFunctionalTest extends FunctionalTestCase
$this
->
assertEquals
(
$expectedOperationTime
,
$command
->
pipeline
[
0
]
->
{
'$changeStream'
}
->
startAtOperationTime
);
}
public
function
testRewindMultipleTimesWithResults
()
{
$operation
=
new
Watch
(
$this
->
manager
,
$this
->
getDatabaseName
(),
$this
->
getCollectionName
(),
[],
$this
->
defaultOptions
);
$changeStream
=
$operation
->
execute
(
$this
->
getPrimaryServer
());
$this
->
insertDocument
([
'x'
=>
1
]);
$this
->
insertDocument
([
'x'
=>
2
]);
$changeStream
->
rewind
();
$this
->
assertTrue
(
$changeStream
->
valid
());
$this
->
assertSame
(
0
,
$changeStream
->
key
());
$this
->
assertNotNull
(
$changeStream
->
current
());
// Subsequent rewind does not change iterator state
$changeStream
->
rewind
();
$this
->
assertTrue
(
$changeStream
->
valid
());
$this
->
assertSame
(
0
,
$changeStream
->
key
());
$this
->
assertNotNull
(
$changeStream
->
current
());
$changeStream
->
next
();
$this
->
assertTrue
(
$changeStream
->
valid
());
$this
->
assertSame
(
1
,
$changeStream
->
key
());
$this
->
assertNotNull
(
$changeStream
->
current
());
// Rewinding after advancing the iterator is an error
$this
->
expectException
(
LogicException
::
class
);
$changeStream
->
rewind
();
}
public
function
testRewindMultipleTimesWithNoResults
()
{
$operation
=
new
Watch
(
$this
->
manager
,
$this
->
getDatabaseName
(),
$this
->
getCollectionName
(),
[],
$this
->
defaultOptions
);
$changeStream
=
$operation
->
execute
(
$this
->
getPrimaryServer
());
$changeStream
->
rewind
();
$this
->
assertFalse
(
$changeStream
->
valid
());
$this
->
assertNull
(
$changeStream
->
key
());
$this
->
assertNull
(
$changeStream
->
current
());
// Subsequent rewind does not change iterator state
$changeStream
->
rewind
();
$this
->
assertFalse
(
$changeStream
->
valid
());
$this
->
assertNull
(
$changeStream
->
key
());
$this
->
assertNull
(
$changeStream
->
current
());
$changeStream
->
next
();
$this
->
assertFalse
(
$changeStream
->
valid
());
$this
->
assertNull
(
$changeStream
->
key
());
$this
->
assertNull
(
$changeStream
->
current
());
// Rewinding after advancing the iterator is an error
$this
->
expectException
(
LogicException
::
class
);
$changeStream
->
rewind
();
}
public
function
testRewindResumesAfterConnectionException
()
{
/* In order to trigger a dropped connection, we'll use a new client with
...
...
@@ -322,20 +379,67 @@ class WatchFunctionalTest extends FunctionalTestCase
$this
->
assertMatchesDocument
(
$expectedResult
,
$changeStream
->
current
());
}
public
function
testResume
TokenIsUpdatedAfterResuming
()
public
function
testResume
MultipleTimesInSuccession
()
{
$this
->
insertDocument
([
'_id'
=>
1
]);
$operation
=
new
CreateCollection
(
$this
->
getDatabaseName
(),
$this
->
getCollectionName
());
$operation
->
execute
(
$this
->
getPrimaryServer
());
$operation
=
new
Watch
(
$this
->
manager
,
$this
->
getDatabaseName
(),
$this
->
getCollectionName
(),
[],
$this
->
defaultOptions
);
$changeStream
=
$operation
->
execute
(
$this
->
getPrimaryServer
());
/* Killing the cursor when there are no results will test that neither
* the initial rewind() nor its resume attempt incremented the key. */
$this
->
killChangeStreamCursor
(
$changeStream
);
$changeStream
->
rewind
();
$this
->
assertFalse
(
$changeStream
->
valid
());
$this
->
assertNull
(
$changeStream
->
key
());
$this
->
assertNull
(
$changeStream
->
current
());
$this
->
insertDocument
([
'_id'
=>
1
]);
/* Killing the cursor a second time when there is a result will test
* that the resume attempt picks up the latest change. */
$this
->
killChangeStreamCursor
(
$changeStream
);
$changeStream
->
rewind
();
$this
->
assertTrue
(
$changeStream
->
valid
());
$this
->
assertSame
(
0
,
$changeStream
->
key
());
$expectedResult
=
[
'_id'
=>
$changeStream
->
current
()
->
_id
,
'operationType'
=>
'insert'
,
'fullDocument'
=>
[
'_id'
=>
1
],
'ns'
=>
[
'db'
=>
$this
->
getDatabaseName
(),
'coll'
=>
$this
->
getCollectionName
()],
'documentKey'
=>
[
'_id'
=>
1
],
];
$this
->
assertMatchesDocument
(
$expectedResult
,
$changeStream
->
current
());
/* Killing the cursor a second time will not trigger a resume until
* ChangeStream::next() is called. A successive call to rewind() should
* not change the iterator's state and preserve the current result. */
$this
->
killChangeStreamCursor
(
$changeStream
);
$changeStream
->
rewind
();
$this
->
assertTrue
(
$changeStream
->
valid
());
$this
->
assertSame
(
0
,
$changeStream
->
key
());
$expectedResult
=
[
'_id'
=>
$changeStream
->
current
()
->
_id
,
'operationType'
=>
'insert'
,
'fullDocument'
=>
[
'_id'
=>
1
],
'ns'
=>
[
'db'
=>
$this
->
getDatabaseName
(),
'coll'
=>
$this
->
getCollectionName
()],
'documentKey'
=>
[
'_id'
=>
1
],
];
$this
->
assertMatchesDocument
(
$expectedResult
,
$changeStream
->
current
());
$this
->
insertDocument
([
'_id'
=>
2
]);
$changeStream
->
next
();
$this
->
assertTrue
(
$changeStream
->
valid
());
$this
->
assertSame
(
1
,
$changeStream
->
key
());
$expectedResult
=
[
'_id'
=>
$changeStream
->
current
()
->
_id
,
...
...
@@ -353,6 +457,7 @@ class WatchFunctionalTest extends FunctionalTestCase
$changeStream
->
next
();
$this
->
assertTrue
(
$changeStream
->
valid
());
$this
->
assertSame
(
2
,
$changeStream
->
key
());
$expectedResult
=
[
'_id'
=>
$changeStream
->
current
()
->
_id
,
...
...
@@ -374,6 +479,7 @@ class WatchFunctionalTest extends FunctionalTestCase
$changeStream
->
next
();
$this
->
assertTrue
(
$changeStream
->
valid
());
$this
->
assertSame
(
3
,
$changeStream
->
key
());
$expectedResult
=
[
'_id'
=>
$changeStream
->
current
()
->
_id
,
...
...
@@ -689,6 +795,8 @@ class WatchFunctionalTest extends FunctionalTestCase
$this
->
insertDocument
([
'x'
=>
1
]);
$this
->
insertDocument
([
'x'
=>
2
]);
/* Note: we intentionally do not start iteration with rewind() to ensure
* that next() behaves identically when called without rewind(). */
$changeStream
->
next
();
$this
->
assertSame
(
0
,
$changeStream
->
key
());
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment