Passed
Pull Request — master (#2412)
by Benoît
14:05
created

testGenerateAnIndexWithPartialColumnLength()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 16
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 10
dl 0
loc 16
rs 9.9332
c 0
b 0
f 0
cc 1
nc 1
nop 0
1
<?php
2
3
namespace Doctrine\Tests\DBAL\Functional\Schema;
4
5
use Doctrine\DBAL\Platforms\MariaDb1027Platform;
6
use Doctrine\DBAL\Platforms\MySqlPlatform;
7
use Doctrine\DBAL\Schema\Comparator;
8
use Doctrine\DBAL\Schema\Schema;
9
use Doctrine\DBAL\Schema\Table;
10
use Doctrine\DBAL\Types\Type;
11
use Doctrine\Tests\Types\MySqlPointType;
12
use function implode;
13
14
class MySqlSchemaManagerTest extends SchemaManagerFunctionalTestCase
15
{
16
    protected function setUp()
17
    {
18
        parent::setUp();
19
20
        if ( ! Type::hasType('point')) {
21
            Type::addType('point', MySqlPointType::class);
22
        }
23
    }
24
25
    public function testSwitchPrimaryKeyColumns()
26
    {
27
        $tableOld = new Table("switch_primary_key_columns");
28
        $tableOld->addColumn('foo_id', 'integer');
29
        $tableOld->addColumn('bar_id', 'integer');
30
31
        $this->_sm->createTable($tableOld);
32
        $tableFetched = $this->_sm->listTableDetails("switch_primary_key_columns");
33
        $tableNew = clone $tableFetched;
34
        $tableNew->setPrimaryKey(array('bar_id', 'foo_id'));
35
36
        $comparator = new Comparator;
37
        $this->_sm->alterTable($comparator->diffTable($tableFetched, $tableNew));
38
39
        $table      = $this->_sm->listTableDetails('switch_primary_key_columns');
40
        $primaryKey = $table->getPrimaryKeyColumns();
41
42
        self::assertCount(2, $primaryKey);
43
        self::assertContains('bar_id', $primaryKey);
44
        self::assertContains('foo_id', $primaryKey);
45
    }
46
47
    public function testDiffTableBug()
48
    {
49
        $schema = new Schema();
50
        $table = $schema->createTable('diffbug_routing_translations');
51
        $table->addColumn('id', 'integer');
52
        $table->addColumn('route', 'string');
53
        $table->addColumn('locale', 'string');
54
        $table->addColumn('attribute', 'string');
55
        $table->addColumn('localized_value', 'string');
56
        $table->addColumn('original_value', 'string');
57
        $table->setPrimaryKey(array('id'));
58
        $table->addUniqueIndex(array('route', 'locale', 'attribute'));
59
        $table->addIndex(array('localized_value')); // this is much more selective than the unique index
60
61
        $this->_sm->createTable($table);
62
        $tableFetched = $this->_sm->listTableDetails("diffbug_routing_translations");
63
64
        $comparator = new Comparator;
65
        $diff = $comparator->diffTable($tableFetched, $table);
66
67
        self::assertFalse($diff, "no changes expected.");
68
    }
69
70
    public function testFulltextIndex()
71
    {
72
        $table = new Table('fulltext_index');
73
        $table->addColumn('text', 'text');
74
        $table->addIndex(array('text'), 'f_index');
75
        $table->addOption('engine', 'MyISAM');
76
77
        $index = $table->getIndex('f_index');
78
        $index->addFlag('fulltext');
79
80
        $this->_sm->dropAndCreateTable($table);
81
82
        $indexes = $this->_sm->listTableIndexes('fulltext_index');
83
        self::assertArrayHasKey('f_index', $indexes);
84
        self::assertTrue($indexes['f_index']->hasFlag('fulltext'));
85
    }
86
87
    public function testSpatialIndex()
88
    {
89
        $table = new Table('spatial_index');
90
        $table->addColumn('point', 'point');
91
        $table->addIndex(array('point'), 's_index');
92
        $table->addOption('engine', 'MyISAM');
93
94
        $index = $table->getIndex('s_index');
95
        $index->addFlag('spatial');
96
97
        $this->_sm->dropAndCreateTable($table);
98
99
        $indexes = $this->_sm->listTableIndexes('spatial_index');
100
        self::assertArrayHasKey('s_index', $indexes);
101
        self::assertTrue($indexes['s_index']->hasFlag('spatial'));
102
    }
103
104
    /**
105
     * @group DBAL-400
106
     */
107
    public function testAlterTableAddPrimaryKey()
108
    {
109
        $table = new Table('alter_table_add_pk');
110
        $table->addColumn('id', 'integer');
111
        $table->addColumn('foo', 'integer');
112
        $table->addIndex(array('id'), 'idx_id');
113
114
        $this->_sm->createTable($table);
115
116
        $comparator = new Comparator();
117
        $diffTable  = clone $table;
118
119
        $diffTable->dropIndex('idx_id');
120
        $diffTable->setPrimaryKey(array('id'));
121
122
        $this->_sm->alterTable($comparator->diffTable($table, $diffTable));
0 ignored issues
show
Bug introduced by
It seems like $comparator->diffTable($table, $diffTable) can also be of type false; however, parameter $tableDiff of Doctrine\DBAL\Schema\Abs...maManager::alterTable() does only seem to accept Doctrine\DBAL\Schema\TableDiff, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

122
        $this->_sm->alterTable(/** @scrutinizer ignore-type */ $comparator->diffTable($table, $diffTable));
Loading history...
123
124
        $table = $this->_sm->listTableDetails("alter_table_add_pk");
125
126
        self::assertFalse($table->hasIndex('idx_id'));
127
        self::assertTrue($table->hasPrimaryKey());
128
    }
129
130
    /**
131
     * @group DBAL-464
132
     */
133
    public function testDropPrimaryKeyWithAutoincrementColumn()
134
    {
135
        $table = new Table("drop_primary_key");
136
        $table->addColumn('id', 'integer', array('autoincrement' => true));
137
        $table->addColumn('foo', 'integer');
138
        $table->setPrimaryKey(array('id', 'foo'));
139
140
        $this->_sm->dropAndCreateTable($table);
141
142
        $diffTable = clone $table;
143
144
        $diffTable->dropPrimaryKey();
145
146
        $comparator = new Comparator();
147
148
        $this->_sm->alterTable($comparator->diffTable($table, $diffTable));
0 ignored issues
show
Bug introduced by
It seems like $comparator->diffTable($table, $diffTable) can also be of type false; however, parameter $tableDiff of Doctrine\DBAL\Schema\Abs...maManager::alterTable() does only seem to accept Doctrine\DBAL\Schema\TableDiff, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

148
        $this->_sm->alterTable(/** @scrutinizer ignore-type */ $comparator->diffTable($table, $diffTable));
Loading history...
149
150
        $table = $this->_sm->listTableDetails("drop_primary_key");
151
152
        self::assertFalse($table->hasPrimaryKey());
153
        self::assertFalse($table->getColumn('id')->getAutoincrement());
154
    }
155
156
    /**
157
     * @group DBAL-789
158
     */
159
    public function testDoesNotPropagateDefaultValuesForUnsupportedColumnTypes()
160
    {
161
        if ($this->_sm->getDatabasePlatform() instanceof MariaDb1027Platform) {
162
            $this->markTestSkipped(
163
                'MariaDb102Platform supports default values for BLOB and TEXT columns and will propagate values'
164
            );
165
        }
166
167
        $table = new Table("text_blob_default_value");
168
        $table->addColumn('def_text', 'text', ['default' => 'def']);
169
        $table->addColumn('def_text_null', 'text', ['notnull' => false, 'default' => 'def']);
170
        $table->addColumn('def_blob', 'blob', ['default' => 'def']);
171
        $table->addColumn('def_blob_null', 'blob', ['notnull' => false, 'default' => 'def']);
172
173
        $this->_sm->dropAndCreateTable($table);
174
175
        $onlineTable = $this->_sm->listTableDetails("text_blob_default_value");
176
177
        self::assertNull($onlineTable->getColumn('def_text')->getDefault());
178
        self::assertNull($onlineTable->getColumn('def_text_null')->getDefault());
179
        self::assertFalse($onlineTable->getColumn('def_text_null')->getNotnull());
180
        self::assertNull($onlineTable->getColumn('def_blob')->getDefault());
181
        self::assertNull($onlineTable->getColumn('def_blob_null')->getDefault());
182
        self::assertFalse($onlineTable->getColumn('def_blob_null')->getNotnull());
183
184
        $comparator = new Comparator();
185
186
        $this->_sm->alterTable($comparator->diffTable($table, $onlineTable));
187
188
        $onlineTable = $this->_sm->listTableDetails("text_blob_default_value");
189
190
        self::assertNull($onlineTable->getColumn('def_text')->getDefault());
191
        self::assertNull($onlineTable->getColumn('def_text_null')->getDefault());
192
        self::assertFalse($onlineTable->getColumn('def_text_null')->getNotnull());
193
        self::assertNull($onlineTable->getColumn('def_blob')->getDefault());
194
        self::assertNull($onlineTable->getColumn('def_blob_null')->getDefault());
195
        self::assertFalse($onlineTable->getColumn('def_blob_null')->getNotnull());
196
    }
197
198
    public function testColumnCollation()
199
    {
200
        $table = new Table('test_collation');
201
        $table->addOption('collate', $collation = 'latin1_swedish_ci');
202
        $table->addOption('charset', 'latin1');
203
        $table->addColumn('id', 'integer');
204
        $table->addColumn('text', 'text');
205
        $table->addColumn('foo', 'text')->setPlatformOption('collation', 'latin1_swedish_ci');
206
        $table->addColumn('bar', 'text')->setPlatformOption('collation', 'utf8_general_ci');
207
        $this->_sm->dropAndCreateTable($table);
208
209
        $columns = $this->_sm->listTableColumns('test_collation');
210
211
        self::assertArrayNotHasKey('collation', $columns['id']->getPlatformOptions());
212
        self::assertEquals('latin1_swedish_ci', $columns['text']->getPlatformOption('collation'));
213
        self::assertEquals('latin1_swedish_ci', $columns['foo']->getPlatformOption('collation'));
214
        self::assertEquals('utf8_general_ci', $columns['bar']->getPlatformOption('collation'));
215
    }
216
217
    /**
218
     * @group DBAL-843
219
     */
220
    public function testListLobTypeColumns()
221
    {
222
        $tableName = 'lob_type_columns';
223
        $table = new Table($tableName);
224
225
        $table->addColumn('col_tinytext', 'text', array('length' => MySqlPlatform::LENGTH_LIMIT_TINYTEXT));
226
        $table->addColumn('col_text', 'text', array('length' => MySqlPlatform::LENGTH_LIMIT_TEXT));
227
        $table->addColumn('col_mediumtext', 'text', array('length' => MySqlPlatform::LENGTH_LIMIT_MEDIUMTEXT));
228
        $table->addColumn('col_longtext', 'text');
229
230
        $table->addColumn('col_tinyblob', 'text', array('length' => MySqlPlatform::LENGTH_LIMIT_TINYBLOB));
231
        $table->addColumn('col_blob', 'blob', array('length' => MySqlPlatform::LENGTH_LIMIT_BLOB));
232
        $table->addColumn('col_mediumblob', 'blob', array('length' => MySqlPlatform::LENGTH_LIMIT_MEDIUMBLOB));
233
        $table->addColumn('col_longblob', 'blob');
234
235
        $this->_sm->dropAndCreateTable($table);
236
237
        $platform = $this->_sm->getDatabasePlatform();
238
        $offlineColumns = $table->getColumns();
239
        $onlineColumns = $this->_sm->listTableColumns($tableName);
240
241
        self::assertSame(
242
            $platform->getClobTypeDeclarationSQL($offlineColumns['col_tinytext']->toArray()),
243
            $platform->getClobTypeDeclarationSQL($onlineColumns['col_tinytext']->toArray())
244
        );
245
        self::assertSame(
246
            $platform->getClobTypeDeclarationSQL($offlineColumns['col_text']->toArray()),
247
            $platform->getClobTypeDeclarationSQL($onlineColumns['col_text']->toArray())
248
        );
249
        self::assertSame(
250
            $platform->getClobTypeDeclarationSQL($offlineColumns['col_mediumtext']->toArray()),
251
            $platform->getClobTypeDeclarationSQL($onlineColumns['col_mediumtext']->toArray())
252
        );
253
        self::assertSame(
254
            $platform->getClobTypeDeclarationSQL($offlineColumns['col_longtext']->toArray()),
255
            $platform->getClobTypeDeclarationSQL($onlineColumns['col_longtext']->toArray())
256
        );
257
258
        self::assertSame(
259
            $platform->getBlobTypeDeclarationSQL($offlineColumns['col_tinyblob']->toArray()),
260
            $platform->getBlobTypeDeclarationSQL($onlineColumns['col_tinyblob']->toArray())
261
        );
262
        self::assertSame(
263
            $platform->getBlobTypeDeclarationSQL($offlineColumns['col_blob']->toArray()),
264
            $platform->getBlobTypeDeclarationSQL($onlineColumns['col_blob']->toArray())
265
        );
266
        self::assertSame(
267
            $platform->getBlobTypeDeclarationSQL($offlineColumns['col_mediumblob']->toArray()),
268
            $platform->getBlobTypeDeclarationSQL($onlineColumns['col_mediumblob']->toArray())
269
        );
270
        self::assertSame(
271
            $platform->getBlobTypeDeclarationSQL($offlineColumns['col_longblob']->toArray()),
272
            $platform->getBlobTypeDeclarationSQL($onlineColumns['col_longblob']->toArray())
273
        );
274
    }
275
276
    /**
277
     * @group DBAL-423
278
     */
279
    public function testDiffListGuidTableColumn()
280
    {
281
        $offlineTable = new Table('list_guid_table_column');
282
        $offlineTable->addColumn('col_guid', 'guid');
283
284
        $this->_sm->dropAndCreateTable($offlineTable);
285
286
        $onlineTable = $this->_sm->listTableDetails('list_guid_table_column');
287
288
        $comparator = new Comparator();
289
290
        self::assertFalse(
291
            $comparator->diffTable($offlineTable, $onlineTable),
292
            "No differences should be detected with the offline vs online schema."
293
        );
294
    }
295
296
    /**
297
     * @group DBAL-1082
298
     */
299
    public function testListDecimalTypeColumns()
300
    {
301
        $tableName = 'test_list_decimal_columns';
302
        $table = new Table($tableName);
303
304
        $table->addColumn('col', 'decimal');
305
        $table->addColumn('col_unsigned', 'decimal', array('unsigned' => true));
306
307
        $this->_sm->dropAndCreateTable($table);
308
309
        $columns = $this->_sm->listTableColumns($tableName);
310
311
        self::assertArrayHasKey('col', $columns);
312
        self::assertArrayHasKey('col_unsigned', $columns);
313
        self::assertFalse($columns['col']->getUnsigned());
314
        self::assertTrue($columns['col_unsigned']->getUnsigned());
315
    }
316
317
    /**
318
     * @group DBAL-1082
319
     */
320
    public function testListFloatTypeColumns()
321
    {
322
        $tableName = 'test_list_float_columns';
323
        $table = new Table($tableName);
324
325
        $table->addColumn('col', 'float');
326
        $table->addColumn('col_unsigned', 'float', array('unsigned' => true));
327
328
        $this->_sm->dropAndCreateTable($table);
329
330
        $columns = $this->_sm->listTableColumns($tableName);
331
332
        self::assertArrayHasKey('col', $columns);
333
        self::assertArrayHasKey('col_unsigned', $columns);
334
        self::assertFalse($columns['col']->getUnsigned());
335
        self::assertTrue($columns['col_unsigned']->getUnsigned());
336
    }
337
338
    public function testJsonColumnType() : void
339
    {
340
        $table = new Table('test_mysql_json');
341
        $table->addColumn('col_json', 'json');
342
        $this->_sm->dropAndCreateTable($table);
343
344
        $columns = $this->_sm->listTableColumns('test_mysql_json');
345
346
        self::assertSame(TYPE::JSON, $columns['col_json']->getType()->getName());
347
    }
348
349
    public function testColumnDefaultCurrentTimestamp() : void
350
    {
351
        $platform = $this->_sm->getDatabasePlatform();
352
353
        $table = new Table("test_column_defaults_current_timestamp");
354
355
        $currentTimeStampSql = $platform->getCurrentTimestampSQL();
356
357
        $table->addColumn('col_datetime', 'datetime', ['notnull' => true, 'default' => $currentTimeStampSql]);
358
        $table->addColumn('col_datetime_nullable', 'datetime', ['default' => $currentTimeStampSql]);
359
360
        $this->_sm->dropAndCreateTable($table);
361
362
        $onlineTable = $this->_sm->listTableDetails("test_column_defaults_current_timestamp");
363
        self::assertSame($currentTimeStampSql, $onlineTable->getColumn('col_datetime')->getDefault());
364
        self::assertSame($currentTimeStampSql, $onlineTable->getColumn('col_datetime_nullable')->getDefault());
365
366
        $comparator = new Comparator();
367
368
        $diff = $comparator->diffTable($table, $onlineTable);
369
        self::assertFalse($diff, "Tables should be identical with column defaults.");
370
    }
371
372
    public function testColumnDefaultsAreValid()
373
    {
374
        $table = new Table("test_column_defaults_are_valid");
375
376
        $currentTimeStampSql = $this->_sm->getDatabasePlatform()->getCurrentTimestampSQL();
377
        $table->addColumn('col_datetime', 'datetime', ['default' => $currentTimeStampSql]);
378
        $table->addColumn('col_datetime_null', 'datetime', ['notnull' => false, 'default' => null]);
379
        $table->addColumn('col_int', 'integer', ['default' => 1]);
380
        $table->addColumn('col_neg_int', 'integer', ['default' => -1]);
381
        $table->addColumn('col_string', 'string', ['default' => 'A']);
382
        $table->addColumn('col_decimal', 'decimal', ['scale' => 3, 'precision' => 6, 'default' => -2.3]);
383
        $table->addColumn('col_date', 'date', ['default' => '2012-12-12']);
384
385
        $this->_sm->dropAndCreateTable($table);
386
387
        $this->_conn->executeUpdate(
388
            "INSERT INTO test_column_defaults_are_valid () VALUES()"
389
        );
390
391
        $row = $this->_conn->fetchAssoc(
392
            'SELECT *, DATEDIFF(CURRENT_TIMESTAMP(), col_datetime) as diff_seconds FROM test_column_defaults_are_valid'
393
        );
394
395
        self::assertInstanceOf(\DateTime::class, \DateTime::createFromFormat('Y-m-d H:i:s', $row['col_datetime']));
396
        self::assertNull($row['col_datetime_null']);
397
        self::assertSame('2012-12-12', $row['col_date']);
398
        self::assertSame('A', $row['col_string']);
399
        self::assertEquals(1, $row['col_int']);
400
        self::assertEquals(-1, $row['col_neg_int']);
401
        self::assertEquals('-2.300', $row['col_decimal']);
402
        self::assertLessThan(5, $row['diff_seconds']);
403
    }
404
405
    /**
406
     * MariaDB 10.2+ does support CURRENT_TIME and CURRENT_DATE as
407
     * column default values for time and date columns.
408
     * (Not supported on Mysql as of 5.7.19)
409
     *
410
     * Note that MariaDB 10.2+, when storing default in information_schema,
411
     * silently change CURRENT_TIMESTAMP as 'current_timestamp()',
412
     * CURRENT_TIME as 'currtime()' and CURRENT_DATE as 'currdate()'.
413
     * This test also ensure proper aliasing to not trigger a table diff.
414
     */
415
    public function testColumnDefaultValuesCurrentTimeAndDate() : void
416
    {
417
        if ( ! $this->_sm->getDatabasePlatform() instanceof MariaDb1027Platform) {
418
            $this->markTestSkipped('Only relevant for MariaDb102Platform.');
419
        }
420
421
        $platform = $this->_sm->getDatabasePlatform();
422
423
        $table = new Table("test_column_defaults_current_time_and_date");
424
425
        $currentTimestampSql = $platform->getCurrentTimestampSQL();
426
        $currentTimeSql      = $platform->getCurrentTimeSQL();
427
        $currentDateSql      = $platform->getCurrentDateSQL();
428
429
        $table->addColumn('col_datetime', 'datetime', ['default' => $currentTimestampSql]);
430
        $table->addColumn('col_date', 'date', ['default' => $currentDateSql]);
431
        $table->addColumn('col_time', 'time', ['default' => $currentTimeSql]);
432
433
        $this->_sm->dropAndCreateTable($table);
434
435
        $onlineTable = $this->_sm->listTableDetails("test_column_defaults_current_time_and_date");
436
437
        self::assertSame($currentTimestampSql, $onlineTable->getColumn('col_datetime')->getDefault());
438
        self::assertSame($currentDateSql, $onlineTable->getColumn('col_date')->getDefault());
439
        self::assertSame($currentTimeSql, $onlineTable->getColumn('col_time')->getDefault());
440
441
        $comparator = new Comparator();
442
443
        $diff = $comparator->diffTable($table, $onlineTable);
444
        self::assertFalse($diff, "Tables should be identical with column defauts time and date.");
445
    }
446
447
    /**
448
     * Ensure default values (un-)escaping is properly done by mysql platforms.
449
     * The test is voluntarily relying on schema introspection due to current
450
     * doctrine limitations. Once #2850 is landed, this test can be removed.
451
     * @see https://dev.mysql.com/doc/refman/5.7/en/string-literals.html
452
     */
453
    public function testEnsureDefaultsAreUnescapedFromSchemaIntrospection() : void
454
    {
455
        $platform = $this->_sm->getDatabasePlatform();
456
        $this->_conn->query('DROP TABLE IF EXISTS test_column_defaults_with_create');
457
458
        $escapeSequences = [
459
            "\\0",          // An ASCII NUL (X'00') character
460
            "\\'", "''",    // Single quote
461
            '\\"', '""',    // Double quote
462
            '\\b',          // A backspace character
463
            '\\n',          // A new-line character
464
            '\\r',          // A carriage return character
465
            '\\t',          // A tab character
466
            '\\Z',          // ASCII 26 (Control+Z)
467
            '\\\\',         // A backslash (\) character
468
            '\\%',          // A percent (%) character
469
            '\\_',          // An underscore (_) character
470
        ];
471
472
        $default = implode('+', $escapeSequences);
473
474
        $sql = "CREATE TABLE test_column_defaults_with_create(
475
                    col1 VARCHAR(255) NULL DEFAULT {$platform->quoteStringLiteral($default)} 
476
                )";
477
        $this->_conn->query($sql);
478
        $onlineTable = $this->_sm->listTableDetails("test_column_defaults_with_create");
479
        self::assertSame($default, $onlineTable->getColumn('col1')->getDefault());
480
    }
481
482
    public function testGenerateAnIndexWithPartialColumnLength() : void
483
    {
484
        $this->_conn->query('DROP TABLE IF EXISTS test_partial_column_index');
485
486
        $table = new Table('test_partial_column_index');
487
        $table->addColumn('long_column', 'string', ['length' => 40]);
488
        $table->addColumn('standard_column', 'integer');
489
        $table->addIndex(['long_column'], 'partial_long_column_idx', [], ['lengths' => [4]]);
490
        $table->addIndex(['standard_column', 'long_column'], 'standard_and_partial_idx', [], ['lengths' => [null, 2]]);
491
492
        $expected = $table->getIndexes();
493
494
        $this->_sm->dropAndCreateTable($table);
495
496
        $onlineTable = $this->_sm->listTableDetails('test_partial_column_index');
497
        self::assertEquals($expected, $onlineTable->getIndexes());
498
    }
499
}
500