ResultCacheTest.php 9.21 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 const CASE_LOWER;
use function array_change_key_case;
use function array_merge;
use function array_shift;
use function array_values;
use function is_array;
18 19 20 21

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

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

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

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

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

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

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

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

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

        parent::tearDown();
    }

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

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

75
        $this->assertCacheNonCacheSelectSameFetchModeAreEqual($expectedResult, FetchMode::NUMERIC);
76 77
    }

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

85
        $this->assertCacheNonCacheSelectSameFetchModeAreEqual($expectedResult, FetchMode::MIXED);
86
    }
87

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

95
        $this->assertCacheNonCacheSelectSameFetchModeAreEqual($expectedResult, FetchMode::COLUMN);
96
    }
97

98
    public function testMixingFetch() : void
99
    {
Sergei Morozov's avatar
Sergei Morozov committed
100
        $numExpectedResult = [];
jeroendedauw's avatar
jeroendedauw committed
101
        foreach ($this->expectedResult as $v) {
102 103
            $numExpectedResult[] = array_values($v);
        }
Sergei Morozov's avatar
Sergei Morozov committed
104
        $stmt = $this->connection->executeQuery('SELECT * FROM caching ORDER BY test_int ASC', [], [], new QueryCacheProfile(10, 'testcachekey'));
105

106
        $data = $this->hydrateStmt($stmt, FetchMode::ASSOCIATIVE);
107

108
        self::assertEquals($this->expectedResult, $data);
109

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

112
        $data = $this->hydrateStmt($stmt, FetchMode::NUMERIC);
113

114
        self::assertEquals($numExpectedResult, $data);
115 116
    }

117
    public function testIteratorFetch() : void
118
    {
119 120 121
        self::assertStandardAndIteratorFetchAreEqual(FetchMode::MIXED);
        self::assertStandardAndIteratorFetchAreEqual(FetchMode::ASSOCIATIVE);
        self::assertStandardAndIteratorFetchAreEqual(FetchMode::NUMERIC);
122 123
    }

124
    private function assertStandardAndIteratorFetchAreEqual(int $fetchMode) : void
125
    {
Sergei Morozov's avatar
Sergei Morozov committed
126
        $stmt = $this->connection->executeQuery('SELECT * FROM caching ORDER BY test_int ASC', [], [], new QueryCacheProfile(10, 'testcachekey'));
127
        $data = $this->hydrateStmt($stmt, $fetchMode);
128

Sergei Morozov's avatar
Sergei Morozov committed
129
        $stmt          = $this->connection->executeQuery('SELECT * FROM caching ORDER BY test_int ASC', [], [], new QueryCacheProfile(10, 'testcachekey'));
130
        $data_iterator = $this->hydrateStmtIterator($stmt, $fetchMode);
131

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

135
    public function testDontCloseNoCache() : void
136
    {
Sergei Morozov's avatar
Sergei Morozov committed
137
        $stmt = $this->connection->executeQuery('SELECT * FROM caching ORDER BY test_int ASC', [], [], new QueryCacheProfile(10, 'testcachekey'));
138

Sergei Morozov's avatar
Sergei Morozov committed
139
        $data = [];
140 141

        while ($row = $stmt->fetch(FetchMode::ASSOCIATIVE)) {
142 143 144
            $data[] = $row;
        }

Sergei Morozov's avatar
Sergei Morozov committed
145
        $stmt = $this->connection->executeQuery('SELECT * FROM caching ORDER BY test_int ASC', [], [], new QueryCacheProfile(10, 'testcachekey'));
146

Sergei Morozov's avatar
Sergei Morozov committed
147
        $data = [];
148 149

        while ($row = $stmt->fetch(FetchMode::NUMERIC)) {
150 151 152
            $data[] = $row;
        }

Gabriel Caruso's avatar
Gabriel Caruso committed
153
        self::assertCount(2, $this->sqlLogger->queries);
154 155
    }

156
    public function testDontFinishNoCache() : void
157
    {
Sergei Morozov's avatar
Sergei Morozov committed
158
        $stmt = $this->connection->executeQuery('SELECT * FROM caching ORDER BY test_int ASC', [], [], new QueryCacheProfile(10, 'testcachekey'));
159

160
        $stmt->fetch(FetchMode::ASSOCIATIVE);
161 162
        $stmt->closeCursor();

Sergei Morozov's avatar
Sergei Morozov committed
163
        $stmt = $this->connection->executeQuery('SELECT * FROM caching ORDER BY test_int ASC', [], [], new QueryCacheProfile(10, 'testcachekey'));
164

165
        $this->hydrateStmt($stmt, FetchMode::NUMERIC);
166

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

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

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

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

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

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

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

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

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

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

Sergei Morozov's avatar
Sergei Morozov committed
207
        $stmt = $this->connection->executeQuery('SELECT * FROM caching ORDER BY test_int ASC', [], [], new QueryCacheProfile(10, '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
    {
Sergei Morozov's avatar
Sergei Morozov committed
217
        $stmt = $this->connection->executeQuery('SELECT * FROM caching WHERE test_int > 500', [], [], new QueryCacheProfile(10, 'emptycachekey'));
218 219
        $data = $this->hydrateStmt($stmt);

Sergei Morozov's avatar
Sergei Morozov committed
220
        $stmt = $this->connection->executeQuery('SELECT * FROM caching WHERE test_int > 500', [], [], new QueryCacheProfile(10, 'emptycachekey'));
221 222
        $data = $this->hydrateStmt($stmt);

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
    {
Sergei Morozov's avatar
Sergei Morozov committed
228
        $stmt = $this->connection->executeQuery('SELECT * FROM caching WHERE test_int > 500', [], [], new QueryCacheProfile(10, 'emptycachekey'));
229 230
        $data = $this->hydrateStmt($stmt);

Sergei Morozov's avatar
Sergei Morozov committed
231
        $secondCache = new ArrayCache();
Sergei Morozov's avatar
Sergei Morozov committed
232
        $stmt        = $this->connection->executeQuery('SELECT * FROM caching WHERE test_int > 500', [], [], new QueryCacheProfile(10, 'emptycachekey', $secondCache));
Sergei Morozov's avatar
Sergei Morozov committed
233
        $data        = $this->hydrateStmt($stmt);
234

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

239 240 241 242
    /**
     * @return array<int, mixed>
     */
    private function hydrateStmt(ResultStatement $stmt, int $fetchMode = FetchMode::ASSOCIATIVE) : array
243
    {
Sergei Morozov's avatar
Sergei Morozov committed
244
        $data = [];
245
        while ($row = $stmt->fetch($fetchMode)) {
246
            $data[] = is_array($row) ? array_change_key_case($row, CASE_LOWER) : $row;
247 248
        }
        $stmt->closeCursor();
249

250
        return $data;
251
    }
252

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

265 266
        return $data;
    }
267
}