ResultCacheTest.php 11 KB
Newer Older
1 2
<?php

3
namespace Doctrine\DBAL\Tests\Functional;
4

Sergei Morozov's avatar
Sergei Morozov committed
5
use Doctrine\Common\Cache\ArrayCache;
6
use Doctrine\DBAL\Cache\QueryCacheProfile;
7
use Doctrine\DBAL\Driver\Result;
Gabriel Caruso's avatar
Gabriel Caruso committed
8
use Doctrine\DBAL\Logging\DebugStack;
Sergei Morozov's avatar
Sergei Morozov committed
9
use Doctrine\DBAL\Schema\Table;
10
use Doctrine\DBAL\Tests\FunctionalTestCase;
11

12 13 14 15
use function array_change_key_case;
use function array_shift;
use function array_values;
use function is_array;
16

Grégoire Paris's avatar
Grégoire Paris committed
17
use const CASE_LOWER;
18

19
class ResultCacheTest extends FunctionalTestCase
20
{
21
    /** @var list<array{test_int: int, test_string: string}> */
Sergei Morozov's avatar
Sergei Morozov committed
22 23 24 25 26
    private $expectedResult = [
        ['test_int' => 100, 'test_string' => 'foo'],
        ['test_int' => 200, 'test_string' => 'bar'],
        ['test_int' => 300, 'test_string' => 'baz'],
    ];
Sergei Morozov's avatar
Sergei Morozov committed
27 28

    /** @var DebugStack */
29 30
    private $sqlLogger;

31
    protected function setUp(): void
32 33 34
    {
        parent::setUp();

Sergei Morozov's avatar
Sergei Morozov committed
35
        $table = new Table('caching');
36
        $table->addColumn('test_int', 'integer');
Sergei Morozov's avatar
Sergei Morozov committed
37 38
        $table->addColumn('test_string', 'string', ['notnull' => false]);
        $table->setPrimaryKey(['test_int']);
39

Sergei Morozov's avatar
Sergei Morozov committed
40
        $sm = $this->connection->getSchemaManager();
41
        $sm->createTable($table);
42

jeroendedauw's avatar
jeroendedauw committed
43
        foreach ($this->expectedResult as $row) {
Sergei Morozov's avatar
Sergei Morozov committed
44
            $this->connection->insert('caching', $row);
45 46
        }

47
        $config = $this->connection->getConfiguration();
Sergei Morozov's avatar
Sergei Morozov committed
48
        $config->setSQLLogger($this->sqlLogger = new DebugStack());
49

Sergei Morozov's avatar
Sergei Morozov committed
50
        $cache = new ArrayCache();
51
        $config->setResultCacheImpl($cache);
52 53
    }

54
    protected function tearDown(): void
55
    {
Sergei Morozov's avatar
Sergei Morozov committed
56
        $this->connection->getSchemaManager()->dropTable('caching');
57 58 59 60

        parent::tearDown();
    }

61
    public function testCacheFetchAssociative(): void
62
    {
63
        $this->assertCacheNonCacheSelectSameFetchModeAreEqual(
64
            $this->expectedResult,
65 66
            static function (Result $result) {
                return $result->fetchAssociative();
67
            }
68
        );
69 70
    }

71
    public function testFetchNumeric(): void
72
    {
Sergei Morozov's avatar
Sergei Morozov committed
73
        $expectedResult = [];
jeroendedauw's avatar
jeroendedauw committed
74
        foreach ($this->expectedResult as $v) {
75 76
            $expectedResult[] = array_values($v);
        }
77

78 79
        $this->assertCacheNonCacheSelectSameFetchModeAreEqual(
            $expectedResult,
80 81
            static function (Result $result) {
                return $result->fetchNumeric();
82 83
            }
        );
84
    }
85

86
    public function testFetchOne(): void
87
    {
Sergei Morozov's avatar
Sergei Morozov committed
88
        $expectedResult = [];
jeroendedauw's avatar
jeroendedauw committed
89
        foreach ($this->expectedResult as $v) {
90 91
            $expectedResult[] = array_shift($v);
        }
92

93 94
        $this->assertCacheNonCacheSelectSameFetchModeAreEqual(
            $expectedResult,
95 96
            static function (Result $result) {
                return $result->fetchOne();
97 98
            }
        );
99
    }
100

101
    public function testMixingFetch(): void
102
    {
Sergei Morozov's avatar
Sergei Morozov committed
103
        $numExpectedResult = [];
jeroendedauw's avatar
jeroendedauw committed
104
        foreach ($this->expectedResult as $v) {
105 106
            $numExpectedResult[] = array_values($v);
        }
Grégoire Paris's avatar
Grégoire Paris committed
107

Sergei Morozov's avatar
Sergei Morozov committed
108 109 110 111
        $stmt = $this->connection->executeQuery(
            'SELECT * FROM caching ORDER BY test_int ASC',
            [],
            [],
112
            new QueryCacheProfile(0, 'testcachekey')
Sergei Morozov's avatar
Sergei Morozov committed
113
        );
114

115 116
        $data = $this->hydrateViaFetchAll($stmt, static function (Result $result) {
            return $result->fetchAllAssociative();
117
        });
118

119
        self::assertEquals($this->expectedResult, $data);
120

Sergei Morozov's avatar
Sergei Morozov committed
121 122 123 124
        $stmt = $this->connection->executeQuery(
            'SELECT * FROM caching ORDER BY test_int ASC',
            [],
            [],
125
            new QueryCacheProfile(0, 'testcachekey')
Sergei Morozov's avatar
Sergei Morozov committed
126
        );
127

128 129
        $data = $this->hydrateViaFetchAll($stmt, static function (Result $result) {
            return $result->fetchAllNumeric();
130
        });
131

132
        self::assertEquals($numExpectedResult, $data);
133 134
    }

135
    /**
136
     * @dataProvider fetchProvider
137
     */
138
    public function testFetchViaIteration(callable $fetch, callable $fetchAll): void
139
    {
Sergei Morozov's avatar
Sergei Morozov committed
140 141 142 143
        $stmt = $this->connection->executeQuery(
            'SELECT * FROM caching ORDER BY test_int ASC',
            [],
            [],
144
            new QueryCacheProfile(0, 'testcachekey')
Sergei Morozov's avatar
Sergei Morozov committed
145 146
        );

147
        $data = $this->hydrateViaFetchAll($stmt, $fetchAll);
148

Sergei Morozov's avatar
Sergei Morozov committed
149 150 151 152
        $stmt = $this->connection->executeQuery(
            'SELECT * FROM caching ORDER BY test_int ASC',
            [],
            [],
153
            new QueryCacheProfile(0, 'testcachekey')
Sergei Morozov's avatar
Sergei Morozov committed
154 155
        );

156
        $dataIterator = $this->hydrateViaIteration($stmt, $fetch);
157

Grégoire Paris's avatar
Grégoire Paris committed
158
        self::assertEquals($data, $dataIterator);
159 160
    }

161
    public function testFetchAndFinishSavesCache(): void
162
    {
163
        $result = $this->connection->executeQuery(
Sergei Morozov's avatar
Sergei Morozov committed
164 165 166
            'SELECT * FROM caching ORDER BY test_int ASC',
            [],
            [],
167
            new QueryCacheProfile(0, 'testcachekey')
Sergei Morozov's avatar
Sergei Morozov committed
168
        );
169

170
        while (($row = $result->fetchAssociative()) !== false) {
171 172 173
            $data[] = $row;
        }

174
        $result = $this->connection->executeQuery(
Sergei Morozov's avatar
Sergei Morozov committed
175 176 177
            'SELECT * FROM caching ORDER BY test_int ASC',
            [],
            [],
178
            new QueryCacheProfile(0, 'testcachekey')
Sergei Morozov's avatar
Sergei Morozov committed
179
        );
180

181
        while (($row = $result->fetchNumeric()) !== false) {
182 183 184
            $data[] = $row;
        }

185
        self::assertCount(1, $this->sqlLogger->queries);
186 187
    }

188
    public function testDontFinishNoCache(): void
189
    {
190
        $result = $this->connection->executeQuery(
Sergei Morozov's avatar
Sergei Morozov committed
191 192 193
            'SELECT * FROM caching ORDER BY test_int ASC',
            [],
            [],
194
            new QueryCacheProfile(0, 'testcachekey')
Sergei Morozov's avatar
Sergei Morozov committed
195
        );
196

197
        $result->fetchAssociative();
198

199
        $result = $this->connection->executeQuery(
Sergei Morozov's avatar
Sergei Morozov committed
200 201 202
            'SELECT * FROM caching ORDER BY test_int ASC',
            [],
            [],
203
            new QueryCacheProfile(0, 'testcachekey')
Sergei Morozov's avatar
Sergei Morozov committed
204
        );
205

206 207
        $this->hydrateViaIteration($result, static function (Result $result) {
            return $result->fetchNumeric();
208
        });
209

Gabriel Caruso's avatar
Gabriel Caruso committed
210
        self::assertCount(2, $this->sqlLogger->queries);
211 212
    }

213
    public function testFetchAllSavesCache(): void
214 215
    {
        $layerCache = new ArrayCache();
Sergei Morozov's avatar
Sergei Morozov committed
216

217
        $result = $this->connection->executeQuery(
218 219 220 221 222 223
            'SELECT * FROM caching WHERE test_int > 500',
            [],
            [],
            new QueryCacheProfile(0, 'testcachekey', $layerCache)
        );
        $result->fetchAllAssociative();
224 225 226 227

        self::assertCount(1, $layerCache->fetch('testcachekey'));
    }

228
    public function testFetchColumn(): void
229 230 231 232 233 234
    {
        $query = $this->connection->getDatabasePlatform()
            ->getDummySelectSQL('1');

        $qcp = new QueryCacheProfile(0, 0, new ArrayCache());

235 236
        $result = $this->connection->executeCacheQuery($query, [], [], $qcp);
        $result->fetchFirstColumn();
237

238
        $query = $this->connection->executeCacheQuery($query, [], [], $qcp);
239

240
        self::assertEquals([1], $query->fetchFirstColumn());
241 242
    }

243
    /**
244
     * @param array<int, array<int, int|string>>|list<int> $expectedResult
245
     */
246
    private function assertCacheNonCacheSelectSameFetchModeAreEqual(array $expectedResult, callable $fetchMode): void
247
    {
Sergei Morozov's avatar
Sergei Morozov committed
248 249 250 251
        $stmt = $this->connection->executeQuery(
            'SELECT * FROM caching ORDER BY test_int ASC',
            [],
            [],
252
            new QueryCacheProfile(0, 'testcachekey')
Sergei Morozov's avatar
Sergei Morozov committed
253
        );
254

255
        self::assertEquals(2, $stmt->columnCount());
256
        $data = $this->hydrateViaIteration($stmt, $fetchMode);
257
        self::assertEquals($expectedResult, $data);
258

Sergei Morozov's avatar
Sergei Morozov committed
259 260 261 262
        $stmt = $this->connection->executeQuery(
            'SELECT * FROM caching ORDER BY test_int ASC',
            [],
            [],
263
            new QueryCacheProfile(0, 'testcachekey')
Sergei Morozov's avatar
Sergei Morozov committed
264
        );
265

266
        self::assertEquals(2, $stmt->columnCount());
267
        $data = $this->hydrateViaIteration($stmt, $fetchMode);
268
        self::assertEquals($expectedResult, $data);
Sergei Morozov's avatar
Sergei Morozov committed
269
        self::assertCount(1, $this->sqlLogger->queries, 'just one dbal hit');
270 271
    }

272
    public function testEmptyResultCache(): void
273
    {
Sergei Morozov's avatar
Sergei Morozov committed
274 275 276 277 278 279 280
        $stmt = $this->connection->executeQuery(
            'SELECT * FROM caching WHERE test_int > 500',
            [],
            [],
            new QueryCacheProfile(10, 'emptycachekey')
        );

281 282
        $this->hydrateViaIteration($stmt, static function (Result $result) {
            return $result->fetchAssociative();
283
        });
284

Sergei Morozov's avatar
Sergei Morozov committed
285 286 287 288 289 290
        $stmt = $this->connection->executeQuery(
            'SELECT * FROM caching WHERE test_int > 500',
            [],
            [],
            new QueryCacheProfile(10, 'emptycachekey')
        );
291

292 293
        $this->hydrateViaIteration($stmt, static function (Result $result) {
            return $result->fetchAssociative();
294
        });
295

Sergei Morozov's avatar
Sergei Morozov committed
296
        self::assertCount(1, $this->sqlLogger->queries, 'just one dbal hit');
297
    }
298

299
    public function testChangeCacheImpl(): void
300
    {
Sergei Morozov's avatar
Sergei Morozov committed
301 302 303 304 305 306 307
        $stmt = $this->connection->executeQuery(
            'SELECT * FROM caching WHERE test_int > 500',
            [],
            [],
            new QueryCacheProfile(10, 'emptycachekey')
        );

308 309
        $this->hydrateViaIteration($stmt, static function (Result $result) {
            return $result->fetchAssociative();
310
        });
311

Sergei Morozov's avatar
Sergei Morozov committed
312
        $secondCache = new ArrayCache();
313

Sergei Morozov's avatar
Sergei Morozov committed
314 315 316 317 318 319 320
        $stmt = $this->connection->executeQuery(
            'SELECT * FROM caching WHERE test_int > 500',
            [],
            [],
            new QueryCacheProfile(10, 'emptycachekey', $secondCache)
        );

321 322
        $this->hydrateViaIteration($stmt, static function (Result $result) {
            return $result->fetchAssociative();
323
        });
324

Sergei Morozov's avatar
Sergei Morozov committed
325 326
        self::assertCount(2, $this->sqlLogger->queries, 'two hits');
        self::assertCount(1, $secondCache->fetch('emptycachekey'));
327 328
    }

329 330 331
    /**
     * @return iterable<string,array<int,mixed>>
     */
332
    public static function fetchProvider(): iterable
333 334
    {
        yield 'associative' => [
335 336
            static function (Result $result) {
                return $result->fetchAssociative();
337
            },
338 339
            static function (Result $result) {
                return $result->fetchAllAssociative();
340
            },
341 342 343
        ];

        yield 'numeric' => [
344 345
            static function (Result $result) {
                return $result->fetchNumeric();
346
            },
347 348
            static function (Result $result) {
                return $result->fetchAllNumeric();
349 350 351 352
            },
        ];

        yield 'column' => [
353 354
            static function (Result $result) {
                return $result->fetchOne();
355
            },
356 357
            static function (Result $result) {
                return $result->fetchFirstColumn();
358
            },
359 360 361
        ];
    }

362 363 364
    /**
     * @return array<int, mixed>
     */
365
    private function hydrateViaFetchAll(Result $result, callable $fetchAll): array
366
    {
Sergei Morozov's avatar
Sergei Morozov committed
367
        $data = [];
368

369
        foreach ($fetchAll($result) as $row) {
370
            $data[] = is_array($row) ? array_change_key_case($row, CASE_LOWER) : $row;
371
        }
Grégoire Paris's avatar
Grégoire Paris committed
372

373
        return $data;
374
    }
375

376 377 378
    /**
     * @return array<int, mixed>
     */
379
    private function hydrateViaIteration(Result $result, callable $fetch): array
380
    {
Sergei Morozov's avatar
Sergei Morozov committed
381
        $data = [];
382

383
        while (($row = $fetch($result)) !== false) {
384
            $data[] = is_array($row) ? array_change_key_case($row, CASE_LOWER) : $row;
385
        }
Grégoire Paris's avatar
Grégoire Paris committed
386

387 388
        return $data;
    }
389
}