[TASK] dbal: skip update suggestions for equivalent fields in Install Tool
[Packages/TYPO3.CMS.git] / typo3 / sysext / dbal / Tests / Unit / Database / DatabaseConnectionTest.php
1 <?php
2 namespace TYPO3\CMS\Dbal\Tests\Unit\Database;
3
4 /*
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17 use TYPO3\CMS\Core\Utility\GeneralUtility;
18
19 /**
20 * Test case
21 */
22 class DatabaseConnectionTest extends AbstractTestCase {
23
24 /**
25 * @var \TYPO3\CMS\Dbal\Database\DatabaseConnection|\PHPUnit_Framework_MockObject_MockObject|\TYPO3\CMS\Core\Tests\AccessibleObjectInterface
26 */
27 protected $subject;
28
29 /**
30 * Set up
31 */
32 protected function setUp() {
33 $GLOBALS['TYPO3_LOADED_EXT'] = array();
34
35 /** @var \TYPO3\CMS\Dbal\Database\DatabaseConnection|\PHPUnit_Framework_MockObject_MockObject|\TYPO3\CMS\Core\Tests\AccessibleObjectInterface $subject */
36 $subject = $this->getAccessibleMock(\TYPO3\CMS\Dbal\Database\DatabaseConnection::class, array('getFieldInfoCache'), array(), '', FALSE);
37
38 // Disable caching
39 $mockCacheFrontend = $this->getMock(\TYPO3\CMS\Core\Cache\Frontend\PhpFrontend::class, array(), array(), '', FALSE);
40 $subject->expects($this->any())->method('getFieldInfoCache')->will($this->returnValue($mockCacheFrontend));
41
42 // Inject SqlParser - Its logic is tested with the tests, too.
43 $sqlParser = $this->getAccessibleMock(\TYPO3\CMS\Dbal\Database\SqlParser::class, array('dummy'), array(), '', FALSE);
44 $sqlParser->_set('databaseConnection', $subject);
45 $subject->SQLparser = $sqlParser;
46
47 // Mock away schema migration service from install tool
48 $installerSqlMock = $this->getMock(\TYPO3\CMS\Install\Service\SqlSchemaMigrationService::class, array('getFieldDefinitions_fileContent'), array(), '', FALSE);
49 $installerSqlMock->expects($this->any())->method('getFieldDefinitions_fileContent')->will($this->returnValue(array()));
50 $subject->_set('installerSql', $installerSqlMock);
51
52 // Inject DBMS specifics
53 $subject->_set('dbmsSpecifics', GeneralUtility::makeInstance(\TYPO3\CMS\Dbal\Database\Specifics\NullSpecifics::class));
54
55 $subject->initialize();
56 $subject->lastHandlerKey = '_DEFAULT';
57
58 $this->subject = $subject;
59 }
60
61 /**
62 * Creates a fake extension with a given table definition.
63 *
64 * @param string $tableDefinition SQL script to create the extension's tables
65 * @throws \RuntimeException
66 * @return void
67 */
68 protected function createFakeExtension($tableDefinition) {
69 // Prepare a fake extension configuration
70 $ext_tables = GeneralUtility::tempnam('ext_tables');
71 if (!GeneralUtility::writeFile($ext_tables, $tableDefinition)) {
72 throw new \RuntimeException('Can\'t write temporary ext_tables file.');
73 }
74 $this->testFilesToDelete[] = $ext_tables;
75 $GLOBALS['TYPO3_LOADED_EXT'] = array(
76 'test_dbal' => array(
77 'ext_tables.sql' => $ext_tables
78 )
79 );
80 // Append our test table to the list of existing tables
81 $this->subject->initialize();
82 }
83
84 /**
85 * @test
86 */
87 public function tableWithMappingIsDetected() {
88 $dbalConfiguration = array(
89 'mapping' => array(
90 'cf_cache_hash' => array(),
91 ),
92 );
93
94 /** @var \TYPO3\CMS\Dbal\Database\DatabaseConnection|\PHPUnit_Framework_MockObject_MockObject|\TYPO3\CMS\Core\Tests\AccessibleObjectInterface $subject */
95 $subject = $this->getAccessibleMock(\TYPO3\CMS\Dbal\Database\DatabaseConnection::class, array('getFieldInfoCache'), array(), '', FALSE);
96
97 $mockCacheFrontend = $this->getMock(\TYPO3\CMS\Core\Cache\Frontend\PhpFrontend::class, array(), array(), '', FALSE);
98 $subject->expects($this->any())->method('getFieldInfoCache')->will($this->returnValue($mockCacheFrontend));
99
100 $sqlParser = $this->getAccessibleMock(\TYPO3\CMS\Dbal\Database\SqlParser::class, array('dummy'), array(), '', FALSE);
101 $sqlParser->_set('databaseConnection', $subject);
102 $subject->SQLparser = $sqlParser;
103
104 $installerSqlMock = $this->getMock(\TYPO3\CMS\Install\Service\SqlSchemaMigrationService::class, array(), array(), '', FALSE);
105 $subject->_set('installerSql', $installerSqlMock);
106 $schemaMigrationResult = array(
107 'cf_cache_pages' => array(),
108 );
109 $installerSqlMock->expects($this->once())->method('getFieldDefinitions_fileContent')->will($this->returnValue($schemaMigrationResult));
110
111 $subject->conf = $dbalConfiguration;
112 $subject->initialize();
113 $subject->lastHandlerKey = '_DEFAULT';
114
115 $this->assertFalse($subject->_call('map_needMapping', 'cf_cache_pages'));
116 $cfCacheHashNeedsMapping = $subject->_call('map_needMapping', 'cf_cache_hash');
117 $this->assertEquals('cf_cache_hash', $cfCacheHashNeedsMapping[0]['table']);
118 }
119
120 /**
121 * @test
122 * @see https://forge.typo3.org/issues/67067
123 */
124 public function adminGetTablesReturnsArrayWithNameKey() {
125 $handlerMock = $this->getMock('\ADODB_mock', array('MetaTables'), array(), '', FALSE);
126 $handlerMock->expects($this->any())->method('MetaTables')->will($this->returnValue(array('cf_cache_hash')));
127 $this->subject->handlerCfg['_DEFAULT']['type'] = 'adodb';
128 $this->subject->handlerInstance['_DEFAULT'] = $handlerMock;
129
130 $actual = $this->subject->admin_get_tables();
131 $expected = array('cf_cache_hash' => array('Name' => 'cf_cache_hash'));
132 $this->assertSame($expected, $actual);
133 }
134
135 /**
136 * @test
137 * @see http://forge.typo3.org/issues/21502
138 */
139 public function concatCanBeParsedAfterLikeOperator() {
140 $result = $this->subject->SELECTquery('*', 'sys_refindex, tx_dam_file_tracking', 'sys_refindex.tablename = \'tx_dam_file_tracking\'' . ' AND sys_refindex.ref_string LIKE CONCAT(tx_dam_file_tracking.file_path, tx_dam_file_tracking.file_name)');
141 $expected = 'SELECT * FROM sys_refindex, tx_dam_file_tracking WHERE sys_refindex.tablename = \'tx_dam_file_tracking\'';
142 $expected .= ' AND sys_refindex.ref_string LIKE CONCAT(tx_dam_file_tracking.file_path, tx_dam_file_tracking.file_name)';
143 $this->assertEquals($expected, $this->cleanSql($result));
144 }
145
146 /**
147 * @test
148 * @see http://forge.typo3.org/issues/20346
149 */
150 public function floatNumberCanBeStoredInDatabase() {
151 $this->createFakeExtension('
152 CREATE TABLE tx_test_dbal (
153 foo double default \'0\',
154 foobar int default \'0\'
155 );
156 ');
157 $data = array(
158 'foo' => 99.12,
159 'foobar' => -120
160 );
161 $result = $this->subject->INSERTquery('tx_test_dbal', $data);
162 $expected = 'INSERT INTO tx_test_dbal ( foo, foobar ) VALUES ( \'99.12\', \'-120\' )';
163 $this->assertEquals($expected, $this->cleanSql($result));
164 }
165
166 /**
167 * @test
168 * @see http://forge.typo3.org/issues/20427
169 */
170 public function positive64BitIntegerIsSupported() {
171 if (!is_int(9223372036854775806)) {
172 $this->markTestSkipped('Test skipped because running on 32 bit system.');
173 }
174 $this->createFakeExtension('
175 CREATE TABLE tx_test_dbal (
176 foo int default \'0\',
177 foobar bigint default \'0\'
178 );
179 ');
180 $data = array(
181 'foo' => 9223372036854775807,
182 'foobar' => 9223372036854775807
183 );
184 $result = $this->subject->INSERTquery('tx_test_dbal', $data);
185 $expected = 'INSERT INTO tx_test_dbal ( foo, foobar ) VALUES ( \'9223372036854775807\', \'9223372036854775807\' )';
186 $this->assertEquals($expected, $this->cleanSql($result));
187 }
188
189 /**
190 * @test
191 */
192 public function sqlForInsertWithMultipleRowsIsValid() {
193 $fields = array('uid', 'pid', 'title', 'body');
194 $rows = array(
195 array('1', '2', 'Title #1', 'Content #1'),
196 array('3', '4', 'Title #2', 'Content #2'),
197 array('5', '6', 'Title #3', 'Content #3')
198 );
199 $result = $this->subject->INSERTmultipleRows('tt_content', $fields, $rows);
200 $expected = 'INSERT INTO tt_content (uid, pid, title, body) VALUES ';
201 $expected .= '(\'1\', \'2\', \'Title #1\', \'Content #1\'), ';
202 $expected .= '(\'3\', \'4\', \'Title #2\', \'Content #2\'), ';
203 $expected .= '(\'5\', \'6\', \'Title #3\', \'Content #3\')';
204 $this->assertEquals($expected, $this->cleanSql($result));
205 }
206
207 /**
208 * @test
209 * @see http://forge.typo3.org/issues/16708
210 */
211 public function minFunctionAndInOperatorCanBeParsed() {
212 $result = $this->subject->SELECTquery('*', 'pages', 'MIN(uid) IN (1,2,3,4)');
213 $expected = 'SELECT * FROM pages WHERE MIN(uid) IN (1,2,3,4)';
214 $this->assertEquals($expected, $this->cleanSql($result));
215 }
216
217 /**
218 * @test
219 * @see http://forge.typo3.org/issues/16708
220 */
221 public function maxFunctionAndInOperatorCanBeParsed() {
222 $result = $this->subject->SELECTquery('*', 'pages', 'MAX(uid) IN (1,2,3,4)');
223 $expected = 'SELECT * FROM pages WHERE MAX(uid) IN (1,2,3,4)';
224 $this->assertEquals($expected, $this->cleanSql($result));
225 }
226
227 /**
228 * @test
229 * @see http://forge.typo3.org/issues/21514
230 */
231 public function likeBinaryOperatorIsKept() {
232 $result = $this->cleanSql($this->subject->SELECTquery('*', 'tt_content', 'bodytext LIKE BINARY \'test\''));
233 $expected = 'SELECT * FROM tt_content WHERE bodytext LIKE BINARY \'test\'';
234 $this->assertEquals($expected, $this->cleanSql($result));
235 }
236
237 /**
238 * @test
239 * @see http://forge.typo3.org/issues/21514
240 */
241 public function notLikeBinaryOperatorIsKept() {
242 $result = $this->cleanSql($this->subject->SELECTquery('*', 'tt_content', 'bodytext NOT LIKE BINARY \'test\''));
243 $expected = 'SELECT * FROM tt_content WHERE bodytext NOT LIKE BINARY \'test\'';
244 $this->assertEquals($expected, $this->cleanSql($result));
245 }
246
247 ///////////////////////////////////////
248 // Tests concerning prepared queries
249 ///////////////////////////////////////
250 /**
251 * @test
252 * @see http://forge.typo3.org/issues/23374
253 */
254 public function similarNamedParametersAreProperlyReplaced() {
255 $sql = 'SELECT * FROM cache WHERE tag = :tag1 OR tag = :tag10 OR tag = :tag100';
256 $parameterValues = array(
257 ':tag1' => 'tag-one',
258 ':tag10' => 'tag-two',
259 ':tag100' => 'tag-three'
260 );
261 $className = self::buildAccessibleProxy(\TYPO3\CMS\Core\Database\PreparedStatement::class);
262 $query = $sql;
263 $precompiledQueryParts = array();
264 $statement = new $className($sql, 'cache');
265 $statement->bindValues($parameterValues);
266 $parameters = $statement->_get('parameters');
267 $statement->_callRef('convertNamedPlaceholdersToQuestionMarks', $query, $parameters, $precompiledQueryParts);
268 $expectedQuery = 'SELECT * FROM cache WHERE tag = ? OR tag = ? OR tag = ?';
269 $expectedParameterValues = array(
270 0 => array(
271 'type' => \TYPO3\CMS\Core\Database\PreparedStatement::PARAM_STR,
272 'value' => 'tag-one',
273 ),
274 1 => array(
275 'type' => \TYPO3\CMS\Core\Database\PreparedStatement::PARAM_STR,
276 'value' => 'tag-two',
277 ),
278 2 => array(
279 'type' => \TYPO3\CMS\Core\Database\PreparedStatement::PARAM_STR,
280 'value' => 'tag-three',
281 ),
282 );
283 $this->assertEquals($expectedQuery, $query);
284 $this->assertEquals($expectedParameterValues, $parameters);
285 }
286
287 ///////////////////////////////////////
288 // Tests concerning indexes
289 ///////////////////////////////////////
290 /**
291 * @test
292 * @param string $indexSQL
293 * @param string $expected
294 * @dataProvider equivalentIndexDefinitionDataProvider
295 */
296 public function equivalentIndexDefinitionRemovesLengthInformation($indexSQL, $expected) {
297 $result = $this->subject->getEquivalentIndexDefinition($indexSQL);
298 $this->assertSame($expected, $result);
299 }
300
301 /**
302 * @return array
303 */
304 public function equivalentIndexDefinitionDataProvider() {
305 return array(
306 array('KEY (foo,bar(199))', 'KEY (foo,bar)'),
307 array('KEY (foo(199), bar)', 'KEY (foo, bar)'),
308 array('KEY (foo(199),bar(199))', 'KEY (foo,bar)'),
309 );
310 }
311 }