ResultCacheTest.php 8.99 KB
Newer Older
1 2 3
<?php

namespace Doctrine\Tests\DBAL\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\ResultStatement;
8
use Doctrine\DBAL\FetchMode;
Gabriel Caruso's avatar
Gabriel Caruso committed
9
use Doctrine\DBAL\Logging\DebugStack;
Sergei Morozov's avatar
Sergei Morozov committed
10 11
use Doctrine\DBAL\Schema\Table;
use Doctrine\Tests\DbalFunctionalTestCase;
12

13 14 15 16 17
use function array_change_key_case;
use function array_merge;
use function array_shift;
use function array_values;
use function is_array;
18

Grégoire Paris's avatar
Grégoire Paris committed
19
use const CASE_LOWER;
20 21 22 23

/**
 * @group DDC-217
 */
Sergei Morozov's avatar
Sergei Morozov committed
24
class ResultCacheTest extends DbalFunctionalTestCase
25
{
26
    /** @var list<array{test_int: int, test_string: string}> */
Sergei Morozov's avatar
Sergei Morozov committed
27 28 29
    private $expectedResult = [['test_int' => 100, 'test_string' => 'foo'], ['test_int' => 200, 'test_string' => 'bar'], ['test_int' => 300, 'test_string' => 'baz']];

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

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

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

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

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

Grégoire Paris's avatar
Grégoire Paris committed
48
        $config = $this->connection->getConfiguration();
Sergei Morozov's avatar
Sergei Morozov committed
49
        $config->setSQLLogger($this->sqlLogger = new DebugStack());
50

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

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

        parent::tearDown();
    }

62
    public function testCacheFetchAssoc(): void
63
    {
64
        $this->assertCacheNonCacheSelectSameFetchModeAreEqual(
65 66 67
            $this->expectedResult,
            FetchMode::ASSOCIATIVE
        );
68 69
    }

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

77
        $this->assertCacheNonCacheSelectSameFetchModeAreEqual($expectedResult, FetchMode::NUMERIC);
78 79
    }

80
    public function testFetchBoth(): void
81
    {
Sergei Morozov's avatar
Sergei Morozov committed
82
        $expectedResult = [];
jeroendedauw's avatar
jeroendedauw committed
83
        foreach ($this->expectedResult as $v) {
84 85
            $expectedResult[] = array_merge($v, array_values($v));
        }
86

87
        $this->assertCacheNonCacheSelectSameFetchModeAreEqual($expectedResult, FetchMode::MIXED);
88
    }
89

90
    public function testFetchColumn(): void
91
    {
Sergei Morozov's avatar
Sergei Morozov committed
92
        $expectedResult = [];
jeroendedauw's avatar
jeroendedauw committed
93
        foreach ($this->expectedResult as $v) {
94 95
            $expectedResult[] = array_shift($v);
        }
96

97
        $this->assertCacheNonCacheSelectSameFetchModeAreEqual($expectedResult, FetchMode::COLUMN);
98
    }
99

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

107
        $stmt = $this->connection->executeQuery('SELECT * FROM caching ORDER BY test_int ASC', [], [], new QueryCacheProfile(0, 'testcachekey'));
108

109
        $data = $this->hydrateStmt($stmt, FetchMode::ASSOCIATIVE);
110

111
        self::assertEquals($this->expectedResult, $data);
112

113
        $stmt = $this->connection->executeQuery('SELECT * FROM caching ORDER BY test_int ASC', [], [], new QueryCacheProfile(0, 'testcachekey'));
114

115
        $data = $this->hydrateStmt($stmt, FetchMode::NUMERIC);
116

117
        self::assertEquals($numExpectedResult, $data);
118 119
    }

120
    public function testIteratorFetch(): void
121
    {
122 123 124
        self::assertStandardAndIteratorFetchAreEqual(FetchMode::MIXED);
        self::assertStandardAndIteratorFetchAreEqual(FetchMode::ASSOCIATIVE);
        self::assertStandardAndIteratorFetchAreEqual(FetchMode::NUMERIC);
125 126
    }

127
    private function assertStandardAndIteratorFetchAreEqual(int $fetchMode): void
128
    {
129
        $stmt = $this->connection->executeQuery('SELECT * FROM caching ORDER BY test_int ASC', [], [], new QueryCacheProfile(0, 'testcachekey'));
130
        $data = $this->hydrateStmt($stmt, $fetchMode);
131

132
        $stmt         = $this->connection->executeQuery('SELECT * FROM caching ORDER BY test_int ASC', [], [], new QueryCacheProfile(0, 'testcachekey'));
Grégoire Paris's avatar
Grégoire Paris committed
133
        $dataIterator = $this->hydrateStmtIterator($stmt, $fetchMode);
134

Grégoire Paris's avatar
Grégoire Paris committed
135
        self::assertEquals($data, $dataIterator);
136 137
    }

138
    public function testFetchAndFinishSavesCache(): void
139
    {
140
        $stmt = $this->connection->executeQuery('SELECT * FROM caching ORDER BY test_int ASC', [], [], new QueryCacheProfile(0, 'testcachekey'));
141

Sergei Morozov's avatar
Sergei Morozov committed
142
        $data = [];
143 144

        while ($row = $stmt->fetch(FetchMode::ASSOCIATIVE)) {
145 146 147
            $data[] = $row;
        }

148
        $stmt = $this->connection->executeQuery('SELECT * FROM caching ORDER BY test_int ASC', [], [], new QueryCacheProfile(0, 'testcachekey'));
149

Sergei Morozov's avatar
Sergei Morozov committed
150
        $data = [];
151 152

        while ($row = $stmt->fetch(FetchMode::NUMERIC)) {
153 154 155
            $data[] = $row;
        }

156
        self::assertCount(1, $this->sqlLogger->queries);
157 158
    }

159
    public function testDontFinishNoCache(): void
160
    {
161
        $stmt = $this->connection->executeQuery('SELECT * FROM caching ORDER BY test_int ASC', [], [], new QueryCacheProfile(0, 'testcachekey'));
162

163
        $stmt->fetch(FetchMode::ASSOCIATIVE);
164

165
        $stmt = $this->connection->executeQuery('SELECT * FROM caching ORDER BY test_int ASC', [], [], new QueryCacheProfile(0, 'testcachekey'));
166

167
        $this->hydrateStmt($stmt, FetchMode::NUMERIC);
168

Gabriel Caruso's avatar
Gabriel Caruso committed
169
        self::assertCount(2, $this->sqlLogger->queries);
170 171
    }

172
    public function testFetchAllSavesCache(): void
173 174
    {
        $layerCache = new ArrayCache();
175
        $stmt       = $this->connection->executeQuery('SELECT * FROM caching WHERE test_int > 500', [], [], new QueryCacheProfile(0, 'testcachekey', $layerCache));
176 177 178 179 180
        $stmt->fetchAll();

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

181
    public function testFetchAllColumn(): void
182 183 184 185 186 187 188 189 190 191 192 193 194 195
    {
        $query = $this->connection->getDatabasePlatform()
            ->getDummySelectSQL('1');

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

        $stmt = $this->connection->executeCacheQuery($query, [], [], $qcp);
        $stmt->fetchAll(FetchMode::COLUMN);

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

        self::assertEquals([1], $stmt->fetchAll(FetchMode::COLUMN));
    }

196
    /**
197
     * @param array<int, array<int, int|string>>|list<int> $expectedResult
198
     */
199
    private function assertCacheNonCacheSelectSameFetchModeAreEqual(array $expectedResult, int $fetchMode): void
200
    {
201
        $stmt = $this->connection->executeQuery('SELECT * FROM caching ORDER BY test_int ASC', [], [], new QueryCacheProfile(0, 'testcachekey'));
202

203
        self::assertEquals(2, $stmt->columnCount());
204
        $data = $this->hydrateStmt($stmt, $fetchMode);
205
        self::assertEquals($expectedResult, $data);
206

207
        $stmt = $this->connection->executeQuery('SELECT * FROM caching ORDER BY test_int ASC', [], [], new QueryCacheProfile(0, 'testcachekey'));
208

209
        self::assertEquals(2, $stmt->columnCount());
210
        $data = $this->hydrateStmt($stmt, $fetchMode);
211
        self::assertEquals($expectedResult, $data);
Sergei Morozov's avatar
Sergei Morozov committed
212
        self::assertCount(1, $this->sqlLogger->queries, 'just one dbal hit');
213 214
    }

215
    public function testEmptyResultCache(): void
216
    {
217 218
        $stmt = $this->connection->executeQuery('SELECT * FROM caching WHERE test_int > 500', [], [], new QueryCacheProfile(0, 'emptycachekey'));
        $this->hydrateStmt($stmt);
219

220 221
        $stmt = $this->connection->executeQuery('SELECT * FROM caching WHERE test_int > 500', [], [], new QueryCacheProfile(0, 'emptycachekey'));
        $this->hydrateStmt($stmt);
222

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

226
    public function testChangeCacheImpl(): void
227
    {
228 229
        $stmt = $this->connection->executeQuery('SELECT * FROM caching WHERE test_int > 500', [], [], new QueryCacheProfile(0, 'emptycachekey'));
        $this->hydrateStmt($stmt);
230

Sergei Morozov's avatar
Sergei Morozov committed
231
        $secondCache = new ArrayCache();
232 233 234

        $stmt = $this->connection->executeQuery('SELECT * FROM caching WHERE test_int > 500', [], [], new QueryCacheProfile(0, 'emptycachekey', $secondCache));
        $this->hydrateStmt($stmt);
235

Sergei Morozov's avatar
Sergei Morozov committed
236 237
        self::assertCount(2, $this->sqlLogger->queries, 'two hits');
        self::assertCount(1, $secondCache->fetch('emptycachekey'));
238 239
    }

240 241 242
    /**
     * @return array<int, mixed>
     */
243
    private function hydrateStmt(ResultStatement $stmt, int $fetchMode = FetchMode::ASSOCIATIVE): array
244
    {
Sergei Morozov's avatar
Sergei Morozov committed
245
        $data = [];
246 247

        foreach ($stmt->fetchAll($fetchMode) as $row) {
248
            $data[] = is_array($row) ? array_change_key_case($row, CASE_LOWER) : $row;
249
        }
Grégoire Paris's avatar
Grégoire Paris committed
250

251
        return $data;
252
    }
253

254 255 256
    /**
     * @return array<int, mixed>
     */
257
    private function hydrateStmtIterator(ResultStatement $stmt, int $fetchMode = FetchMode::ASSOCIATIVE): array
258
    {
Sergei Morozov's avatar
Sergei Morozov committed
259
        $data = [];
260
        $stmt->setFetchMode($fetchMode);
261
        foreach ($stmt as $row) {
262
            $data[] = is_array($row) ? array_change_key_case($row, CASE_LOWER) : $row;
263
        }
Grégoire Paris's avatar
Grégoire Paris committed
264

265 266
        return $data;
    }
267
}