ResultCacheTest.php 9.18 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
use function array_change_key_case;
use function array_merge;
use function array_shift;
use function array_values;
use function is_array;
Grégoire Paris's avatar
Grégoire Paris committed
17
use const CASE_LOWER;
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
        }

Grégoire Paris's avatar
Grégoire Paris 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);
        }
Grégoire Paris's avatar
Grégoire Paris committed
104

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

171
    public function testFetchAllAndFinishSavesCache() : void
172 173 174 175 176 177 178 179 180
    {
        $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'));
    }

181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196
    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));
    }

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

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

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

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

216
    public function testEmptyResultCache() : void
217
    {
Sergei Morozov's avatar
Sergei Morozov committed
218
        $stmt = $this->connection->executeQuery('SELECT * FROM caching WHERE test_int > 500', [], [], new QueryCacheProfile(10, 'emptycachekey'));
219 220
        $data = $this->hydrateStmt($stmt);

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

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

227
    public function testChangeCacheImpl() : void
228
    {
Sergei Morozov's avatar
Sergei Morozov committed
229
        $stmt = $this->connection->executeQuery('SELECT * FROM caching WHERE test_int > 500', [], [], new QueryCacheProfile(10, 'emptycachekey'));
230 231
        $data = $this->hydrateStmt($stmt);

Sergei Morozov's avatar
Sergei Morozov committed
232
        $secondCache = new ArrayCache();
Sergei Morozov's avatar
Sergei Morozov committed
233
        $stmt        = $this->connection->executeQuery('SELECT * FROM caching WHERE test_int > 500', [], [], new QueryCacheProfile(10, 'emptycachekey', $secondCache));
Sergei Morozov's avatar
Sergei Morozov committed
234
        $data        = $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 243
    /**
     * @return array<int, mixed>
     */
    private function hydrateStmt(ResultStatement $stmt, int $fetchMode = FetchMode::ASSOCIATIVE) : array
244
    {
Sergei Morozov's avatar
Sergei Morozov committed
245
        $data = [];
246
        while ($row = $stmt->fetch($fetchMode)) {
247
            $data[] = is_array($row) ? array_change_key_case($row, CASE_LOWER) : $row;
248
        }
Grégoire Paris's avatar
Grégoire Paris committed
249

250
        $stmt->closeCursor();
251

252
        return $data;
253
    }
254

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

266
        $stmt->closeCursor();
267

268 269
        return $data;
    }
270
}