Skip to content

Commit 775e1e5

Browse files
committed
PHPLIB-789: Snapshot query examples
Includes work-arounds for SnapshotUnavailable(246) errors on replica sets and sharded clusters.
1 parent b032a82 commit 775e1e5

File tree

1 file changed

+150
-0
lines changed

1 file changed

+150
-0
lines changed

tests/DocumentationExamplesTest.php

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,15 @@
44

55
use MongoDB\BSON\ObjectId;
66
use MongoDB\BSON\UTCDateTime;
7+
use MongoDB\Collection;
78
use MongoDB\Database;
89
use MongoDB\Driver\Cursor;
910
use MongoDB\Driver\Exception\Exception;
1011
use MongoDB\Driver\ReadPreference;
1112
use MongoDB\Driver\WriteConcern;
1213

1314
use function in_array;
15+
use function microtime;
1416
use function ob_end_clean;
1517
use function ob_start;
1618
use function var_dump;
@@ -1575,6 +1577,119 @@ public function testCausalConsistency(): void
15751577
ob_end_clean();
15761578
}
15771579

1580+
public function testSnapshotQueries(): void
1581+
{
1582+
if (version_compare($this->getServerVersion(), '5.0.0', '<')) {
1583+
$this->markTestSkipped('Snapshot queries outside of transactions are not supported');
1584+
}
1585+
1586+
if (! ($this->isReplicaSet() || $this->isShardedClusterUsingReplicasets())) {
1587+
$this->markTestSkipped('Snapshot read concern is only supported with replicasets');
1588+
}
1589+
1590+
$client = static::createTestClient();
1591+
1592+
$catsCollection = $client->selectCollection('pets', 'cats');
1593+
$catsCollection->drop();
1594+
$catsCollection->insertMany([
1595+
['name' => 'Whiskers', 'color' => 'white', 'adoptable' => true],
1596+
['name' => 'Garfield', 'color' => 'orange', 'adoptable' => false],
1597+
]);
1598+
1599+
$dogsCollection = $client->selectCollection('pets', 'dogs');
1600+
$dogsCollection->drop();
1601+
$dogsCollection->insertMany([
1602+
['name' => 'Toto', 'color' => 'black', 'adoptable' => true],
1603+
['name' => 'Milo', 'color' => 'black', 'adoptable' => false],
1604+
['name' => 'Brian', 'color' => 'white', 'adoptable' => true],
1605+
]);
1606+
1607+
if ($this->isShardedCluster()) {
1608+
$this->preventStaleDbVersionError('pets', 'cats');
1609+
$this->preventStaleDbVersionError('pets', 'dogs');
1610+
} else {
1611+
$this->waitForSnapshot('pets', 'cats');
1612+
$this->waitForSnapshot('pets', 'dogs');
1613+
}
1614+
1615+
ob_start();
1616+
1617+
// Start Snapshot Query Example 1
1618+
$catsCollection = $client->selectCollection('pets', 'cats');
1619+
$dogsCollection = $client->selectCollection('pets', 'dogs');
1620+
1621+
$session = $client->startSession(['snapshot' => true]);
1622+
1623+
$adoptablePetsCount = $catsCollection->aggregate(
1624+
[
1625+
['$match' => ['adoptable' => true]],
1626+
['$count' => 'adoptableCatsCount'],
1627+
],
1628+
['session' => $session]
1629+
)->toArray()[0]->adoptableCatsCount;
1630+
1631+
$adoptablePetsCount += $dogsCollection->aggregate(
1632+
[
1633+
['$match' => ['adoptable' => true]],
1634+
['$count' => 'adoptableDogsCount'],
1635+
],
1636+
['session' => $session]
1637+
)->toArray()[0]->adoptableDogsCount;
1638+
1639+
var_dump($adoptablePetsCount);
1640+
// End Snapshot Query Example 1
1641+
1642+
ob_end_clean();
1643+
1644+
$this->assertSame(3, $adoptablePetsCount);
1645+
1646+
$catsCollection->drop();
1647+
$dogsCollection->drop();
1648+
1649+
$salesCollection = $client->selectCollection('retail', 'sales');
1650+
$salesCollection->drop();
1651+
$salesCollection->insertMany([
1652+
['shoeType' => 'boot', 'price' => 30, 'saleDate' => new UTCDateTime()],
1653+
]);
1654+
1655+
if ($this->isShardedCluster()) {
1656+
$this->preventStaleDbVersionError('retail', 'sales');
1657+
} else {
1658+
$this->waitForSnapshot('retail', 'sales');
1659+
}
1660+
1661+
// Start Snapshot Query Example 2
1662+
$salesCollection = $client->selectCollection('retail', 'sales');
1663+
1664+
$session = $client->startSession(['snapshot' => true]);
1665+
1666+
$totalDailySales = $salesCollection->aggregate(
1667+
[
1668+
[
1669+
'$match' => [
1670+
'$expr' => [
1671+
'$gt' => ['$saleDate', [
1672+
'$dateSubtract' => [
1673+
'startDate' => '$$NOW',
1674+
'unit' => 'day',
1675+
'amount' => 1,
1676+
],
1677+
],
1678+
],
1679+
],
1680+
],
1681+
],
1682+
['$count' => 'totalDailySales'],
1683+
],
1684+
['session' => $session]
1685+
)->toArray()[0]->totalDailySales;
1686+
// End Snapshot Query Example 2
1687+
1688+
$this->assertSame(1, $totalDailySales);
1689+
1690+
$salesCollection->drop();
1691+
}
1692+
15781693
/**
15791694
* @doesNotPerformAssertions
15801695
*/
@@ -1751,4 +1866,39 @@ private function assertInventoryCount($count): void
17511866
{
17521867
$this->assertCollectionCount($this->getDatabaseName() . '.' . $this->getCollectionName(), $count);
17531868
}
1869+
1870+
private function waitForSnapshot(string $databaseName, string $collectionName): void
1871+
{
1872+
$collection = new Collection($this->manager, $databaseName, $collectionName);
1873+
$session = $this->manager->startSession(['snapshot' => true]);
1874+
1875+
// TODO: use hrtime() once the library requires PHP 7.3+
1876+
$startTime = microtime(true);
1877+
1878+
do {
1879+
try {
1880+
$collection->aggregate(
1881+
[['$match' => ['_id' => ['$exists' => true]]]],
1882+
['session' => $session]
1883+
);
1884+
1885+
break;
1886+
} catch (CommandException $e) {
1887+
if ($e->getCode() === 246 /* SnapshotUnavailable */) {
1888+
continue;
1889+
}
1890+
1891+
throw $e;
1892+
}
1893+
} while (microtime(true) < $startTime + 10);
1894+
}
1895+
1896+
/**
1897+
* @see https://jira.mongodb.org/browse/SERVER-39704
1898+
*/
1899+
private function preventStaleDbVersionError(string $databaseName, string $collectionName): void
1900+
{
1901+
$collection = new Collection($this->manager, $databaseName, $collectionName);
1902+
$collection->distinct('foo');
1903+
}
17541904
}

0 commit comments

Comments
 (0)