FindFunctionalTest.php 9.45 KB
Newer Older
1 2 3 4
<?php

namespace MongoDB\Tests\Operation;

5
use IteratorIterator;
6
use MongoDB\Driver\BulkWrite;
7
use MongoDB\Driver\ReadPreference;
8
use MongoDB\Operation\CreateCollection;
9
use MongoDB\Operation\CreateIndexes;
10
use MongoDB\Operation\Find;
11
use MongoDB\Tests\CommandObserver;
12 13
use function microtime;
use function version_compare;
14 15 16

class FindFunctionalTest extends FunctionalTestCase
{
17 18
    public function testDefaultReadConcernIsOmitted()
    {
19 20
        (new CommandObserver())->observe(
            function () {
21 22 23 24 25 26 27 28 29
                $operation = new Find(
                    $this->getDatabaseName(),
                    $this->getCollectionName(),
                    [],
                    ['readConcern' => $this->createDefaultReadConcern()]
                );

                $operation->execute($this->getPrimaryServer());
            },
30
            function (array $event) {
31
                $this->assertObjectNotHasAttribute('readConcern', $event['started']->getCommand());
32 33 34
            }
        );
    }
35 36 37

    public function testHintOption()
    {
38
        $bulkWrite = new BulkWrite();
39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85
        $bulkWrite->insert(['_id' => 1, 'x' => 1]);
        $bulkWrite->insert(['_id' => 2, 'x' => 2]);
        $bulkWrite->insert(['_id' => 3, 'y' => 3]);
        $this->manager->executeBulkWrite($this->getNamespace(), $bulkWrite);

        $createIndexes = new CreateIndexes($this->getDatabaseName(), $this->getCollectionName(), [
            ['key' => ['x' => 1], 'sparse' => true, 'name' => 'sparse_x'],
            ['key' => ['y' => 1]],
        ]);
        $createIndexes->execute($this->getPrimaryServer());

        $hintsUsingSparseIndex = [
            ['x' => 1],
            'sparse_x',
        ];

        foreach ($hintsUsingSparseIndex as $hint) {
            $operation = new Find($this->getDatabaseName(), $this->getCollectionName(), [], ['hint' => $hint]);
            $cursor = $operation->execute($this->getPrimaryServer());

            $expectedDocuments = [
                (object) ['_id' => 1, 'x' => 1],
                (object) ['_id' => 2, 'x' => 2],
            ];

            $this->assertEquals($expectedDocuments, $cursor->toArray());
        }

        $hintsNotUsingSparseIndex = [
            ['_id' => 1],
            ['y' => 1],
            'y_1',
        ];

        foreach ($hintsNotUsingSparseIndex as $hint) {
            $operation = new Find($this->getDatabaseName(), $this->getCollectionName(), [], ['hint' => $hint]);
            $cursor = $operation->execute($this->getPrimaryServer());

            $expectedDocuments = [
                (object) ['_id' => 1, 'x' => 1],
                (object) ['_id' => 2, 'x' => 2],
                (object) ['_id' => 3, 'y' => 3],
            ];

            $this->assertEquals($expectedDocuments, $cursor->toArray());
        }
    }
86

87 88 89 90 91 92
    public function testSessionOption()
    {
        if (version_compare($this->getServerVersion(), '3.6.0', '<')) {
            $this->markTestSkipped('Sessions are not supported');
        }

93 94
        (new CommandObserver())->observe(
            function () {
95 96 97 98 99 100 101 102 103
                $operation = new Find(
                    $this->getDatabaseName(),
                    $this->getCollectionName(),
                    [],
                    ['session' => $this->createSession()]
                );

                $operation->execute($this->getPrimaryServer());
            },
104
            function (array $event) {
105
                $this->assertObjectHasAttribute('lsid', $event['started']->getCommand());
106 107 108 109
            }
        );
    }

110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152
    /**
     * @dataProvider provideTypeMapOptionsAndExpectedDocuments
     */
    public function testTypeMapOption(array $typeMap, array $expectedDocuments)
    {
        $this->createFixtures(3);

        $operation = new Find($this->getDatabaseName(), $this->getCollectionName(), [], ['typeMap' => $typeMap]);
        $cursor = $operation->execute($this->getPrimaryServer());

        $this->assertEquals($expectedDocuments, $cursor->toArray());
    }

    public function provideTypeMapOptionsAndExpectedDocuments()
    {
        return [
            [
                ['root' => 'array', 'document' => 'array'],
                [
                    ['_id' => 1, 'x' => ['foo' => 'bar']],
                    ['_id' => 2, 'x' => ['foo' => 'bar']],
                    ['_id' => 3, 'x' => ['foo' => 'bar']],
                ],
            ],
            [
                ['root' => 'object', 'document' => 'array'],
                [
                    (object) ['_id' => 1, 'x' => ['foo' => 'bar']],
                    (object) ['_id' => 2, 'x' => ['foo' => 'bar']],
                    (object) ['_id' => 3, 'x' => ['foo' => 'bar']],
                ],
            ],
            [
                ['root' => 'array', 'document' => 'stdClass'],
                [
                    ['_id' => 1, 'x' => (object) ['foo' => 'bar']],
                    ['_id' => 2, 'x' => (object) ['foo' => 'bar']],
                    ['_id' => 3, 'x' => (object) ['foo' => 'bar']],
                ],
            ],
        ];
    }

153 154
    public function testMaxAwaitTimeMS()
    {
155 156 157 158
        if (version_compare($this->getServerVersion(), '3.2.0', '<')) {
            $this->markTestSkipped('maxAwaitTimeMS option is not supported');
        }

159 160 161 162 163
        $maxAwaitTimeMS = 100;

        /* Calculate an approximate pivot to use for time assertions. We will
         * assert that the duration of blocking responses is greater than this
         * value, and vice versa. */
164
        $pivot = $maxAwaitTimeMS * 0.001 * 0.9;
165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180

        // Create a capped collection.
        $databaseName = $this->getDatabaseName();
        $cappedCollectionName = $this->getCollectionName();
        $cappedCollectionOptions = [
            'capped' => true,
            'max' => 100,
            'size' => 1048576,
        ];

        $operation = new CreateCollection($databaseName, $cappedCollectionName, $cappedCollectionOptions);
        $operation->execute($this->getPrimaryServer());

        // Insert documents into the capped collection.
        $bulkWrite = new BulkWrite(['ordered' => true]);
        $bulkWrite->insert(['_id' => 1]);
181
        $bulkWrite->insert(['_id' => 2]);
182 183 184 185
        $result = $this->manager->executeBulkWrite($this->getNamespace(), $bulkWrite);

        $operation = new Find($databaseName, $cappedCollectionName, [], ['cursorType' => Find::TAILABLE_AWAIT, 'maxAwaitTimeMS' => $maxAwaitTimeMS]);
        $cursor = $operation->execute($this->getPrimaryServer());
186
        $it = new IteratorIterator($cursor);
187

188 189 190
        /* The initial query includes the one and only document in its result
         * batch, so we should not expect a delay. */
        $startTime = microtime(true);
191
        $it->rewind();
192
        $duration = microtime(true) - $startTime;
193
        $this->assertLessThan($pivot, $duration);
194 195 196 197 198 199 200

        $this->assertTrue($it->valid());
        $this->assertSameDocument(['_id' => 1], $it->current());

        /* Advancing again takes us to the last document of the result batch,
         * but still should not issue a getMore */
        $startTime = microtime(true);
201
        $it->next();
202
        $duration = microtime(true) - $startTime;
203
        $this->assertLessThan($pivot, $duration);
204 205 206 207 208 209 210 211 212

        $this->assertTrue($it->valid());
        $this->assertSameDocument(['_id' => 2], $it->current());

        /* Now that we've reached the end of the initial result batch, advancing
         * again will issue a getMore. Expect to wait at least maxAwaitTimeMS,
         * since no new documents should be inserted to wake up the server's
         * query thread. Also ensure we don't wait too long (server default is
         * one second). */
213 214
        $startTime = microtime(true);
        $it->next();
215
        $duration = microtime(true) - $startTime;
216
        $this->assertGreaterThan($pivot, $duration);
217 218 219
        $this->assertLessThan(0.5, $duration);

        $this->assertFalse($it->valid());
220 221
    }

222 223 224 225 226
    public function testReadPreferenceWithinTransaction()
    {
        $this->skipIfTransactionsAreNotSupported();

        // Collection must be created before the transaction starts
227
        $this->createCollection();
228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256

        $session = $this->manager->startSession();
        $session->startTransaction();

        try {
            $this->createFixtures(3, ['session' => $session]);

            $filter = ['_id' => ['$lt' => 3]];
            $options = [
                'readPreference' => new ReadPreference('primary'),
                'session' => $session,
            ];

            $operation = new Find($this->getDatabaseName(), $this->getCollectionName(), $filter, $options);
            $cursor = $operation->execute($this->getPrimaryServer());

            $expected = [
                ['_id' => 1, 'x' => ['foo' => 'bar']],
                ['_id' => 2, 'x' => ['foo' => 'bar']],
            ];

            $this->assertSameDocuments($expected, $cursor);

            $session->commitTransaction();
        } finally {
            $session->endSession();
        }
    }

257 258 259 260
    /**
     * Create data fixtures.
     *
     * @param integer $n
261
     * @param array   $executeBulkWriteOptions
262
     */
263
    private function createFixtures($n, array $executeBulkWriteOptions = [])
264 265 266 267 268 269 270 271 272 273
    {
        $bulkWrite = new BulkWrite(['ordered' => true]);

        for ($i = 1; $i <= $n; $i++) {
            $bulkWrite->insert([
                '_id' => $i,
                'x' => (object) ['foo' => 'bar'],
            ]);
        }

274
        $result = $this->manager->executeBulkWrite($this->getNamespace(), $bulkWrite, $executeBulkWriteOptions);
275 276 277 278

        $this->assertEquals($n, $result->getInsertedCount());
    }
}