MySqlSchemaManagerTest.php 19.1 KB
Newer Older
1 2 3 4
<?php

namespace Doctrine\Tests\DBAL\Functional\Schema;

5
use Doctrine\DBAL\Platforms\MariaDb1027Platform;
6
use Doctrine\DBAL\Platforms\MySqlPlatform;
7
use Doctrine\DBAL\Schema\Comparator;
8
use Doctrine\DBAL\Schema\Schema;
jeroendedauw's avatar
jeroendedauw committed
9
use Doctrine\DBAL\Schema\Table;
10
use Doctrine\DBAL\Types\Type;
11
use Doctrine\Tests\Types\MySqlPointType;
12

romanb's avatar
romanb committed
13
class MySqlSchemaManagerTest extends SchemaManagerFunctionalTestCase
14
{
15 16 17 18
    protected function setUp()
    {
        parent::setUp();

19
        if ( ! Type::hasType('point')) {
20
            Type::addType('point', MySqlPointType::class);
21 22 23
        }
    }

24 25 26 27 28 29 30 31
    public function testSwitchPrimaryKeyColumns()
    {
        $tableOld = new Table("switch_primary_key_columns");
        $tableOld->addColumn('foo_id', 'integer');
        $tableOld->addColumn('bar_id', 'integer');

        $this->_sm->createTable($tableOld);
        $tableFetched = $this->_sm->listTableDetails("switch_primary_key_columns");
belgattitude's avatar
belgattitude committed
32
        $tableNew = clone $tableFetched;
33 34
        $tableNew->setPrimaryKey(array('bar_id', 'foo_id'));

35
        $comparator = new Comparator;
36
        $this->_sm->alterTable($comparator->diffTable($tableFetched, $tableNew));
Luís Cobucci's avatar
Luís Cobucci committed
37 38 39 40 41 42 43

        $table      = $this->_sm->listTableDetails('switch_primary_key_columns');
        $primaryKey = $table->getPrimaryKeyColumns();

        self::assertCount(2, $primaryKey);
        self::assertContains('bar_id', $primaryKey);
        self::assertContains('foo_id', $primaryKey);
44 45 46 47 48
    }

    public function testDiffTableBug()
    {
        $schema = new Schema();
belgattitude's avatar
belgattitude committed
49
        $table = $schema->createTable('diffbug_routing_translations');
50 51 52 53 54 55 56 57 58 59 60 61
        $table->addColumn('id', 'integer');
        $table->addColumn('route', 'string');
        $table->addColumn('locale', 'string');
        $table->addColumn('attribute', 'string');
        $table->addColumn('localized_value', 'string');
        $table->addColumn('original_value', 'string');
        $table->setPrimaryKey(array('id'));
        $table->addUniqueIndex(array('route', 'locale', 'attribute'));
        $table->addIndex(array('localized_value')); // this is much more selective than the unique index

        $this->_sm->createTable($table);
        $tableFetched = $this->_sm->listTableDetails("diffbug_routing_translations");
62

63
        $comparator = new Comparator;
belgattitude's avatar
belgattitude committed
64
        $diff = $comparator->diffTable($tableFetched, $table);
65

66
        self::assertFalse($diff, "no changes expected.");
67
    }
68 69 70 71 72 73 74 75 76 77 78 79 80 81

    public function testFulltextIndex()
    {
        $table = new Table('fulltext_index');
        $table->addColumn('text', 'text');
        $table->addIndex(array('text'), 'f_index');
        $table->addOption('engine', 'MyISAM');

        $index = $table->getIndex('f_index');
        $index->addFlag('fulltext');

        $this->_sm->dropAndCreateTable($table);

        $indexes = $this->_sm->listTableIndexes('fulltext_index');
82 83
        self::assertArrayHasKey('f_index', $indexes);
        self::assertTrue($indexes['f_index']->hasFlag('fulltext'));
84
    }
85

86 87 88 89 90 91 92 93 94 95 96 97 98
    public function testSpatialIndex()
    {
        $table = new Table('spatial_index');
        $table->addColumn('point', 'point');
        $table->addIndex(array('point'), 's_index');
        $table->addOption('engine', 'MyISAM');

        $index = $table->getIndex('s_index');
        $index->addFlag('spatial');

        $this->_sm->dropAndCreateTable($table);

        $indexes = $this->_sm->listTableIndexes('spatial_index');
99 100
        self::assertArrayHasKey('s_index', $indexes);
        self::assertTrue($indexes['s_index']->hasFlag('spatial'));
101 102
    }

103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124
    /**
     * @group DBAL-400
     */
    public function testAlterTableAddPrimaryKey()
    {
        $table = new Table('alter_table_add_pk');
        $table->addColumn('id', 'integer');
        $table->addColumn('foo', 'integer');
        $table->addIndex(array('id'), 'idx_id');

        $this->_sm->createTable($table);

        $comparator = new Comparator();
        $diffTable  = clone $table;

        $diffTable->dropIndex('idx_id');
        $diffTable->setPrimaryKey(array('id'));

        $this->_sm->alterTable($comparator->diffTable($table, $diffTable));

        $table = $this->_sm->listTableDetails("alter_table_add_pk");

125 126
        self::assertFalse($table->hasIndex('idx_id'));
        self::assertTrue($table->hasPrimaryKey());
127
    }
128 129

    /**
Steve Müller's avatar
Steve Müller committed
130
     * @group DBAL-464
131 132 133 134
     */
    public function testDropPrimaryKeyWithAutoincrementColumn()
    {
        $table = new Table("drop_primary_key");
135 136
        $table->addColumn('id', 'integer', array('autoincrement' => true));
        $table->addColumn('foo', 'integer');
137 138 139 140 141 142 143 144 145 146 147
        $table->setPrimaryKey(array('id', 'foo'));

        $this->_sm->dropAndCreateTable($table);

        $diffTable = clone $table;

        $diffTable->dropPrimaryKey();

        $comparator = new Comparator();

        $this->_sm->alterTable($comparator->diffTable($table, $diffTable));
Steve Müller's avatar
Steve Müller committed
148 149 150

        $table = $this->_sm->listTableDetails("drop_primary_key");

151 152
        self::assertFalse($table->hasPrimaryKey());
        self::assertFalse($table->getColumn('id')->getAutoincrement());
153
    }
154 155 156 157 158 159

    /**
     * @group DBAL-789
     */
    public function testDoesNotPropagateDefaultValuesForUnsupportedColumnTypes()
    {
160
        if ($this->_sm->getDatabasePlatform() instanceof MariaDb1027Platform) {
belgattitude's avatar
belgattitude committed
161 162 163
            $this->markTestSkipped(
                'MariaDb102Platform supports default values for BLOB and TEXT columns and will propagate values'
            );
164 165
        }

166
        $table = new Table("text_blob_default_value");
167 168 169 170
        $table->addColumn('def_text', 'text', ['default' => 'def']);
        $table->addColumn('def_text_null', 'text', ['notnull' => false, 'default' => 'def']);
        $table->addColumn('def_blob', 'blob', ['default' => 'def']);
        $table->addColumn('def_blob_null', 'blob', ['notnull' => false, 'default' => 'def']);
171 172 173 174 175

        $this->_sm->dropAndCreateTable($table);

        $onlineTable = $this->_sm->listTableDetails("text_blob_default_value");

176 177 178 179 180 181
        self::assertNull($onlineTable->getColumn('def_text')->getDefault());
        self::assertNull($onlineTable->getColumn('def_text_null')->getDefault());
        self::assertFalse($onlineTable->getColumn('def_text_null')->getNotnull());
        self::assertNull($onlineTable->getColumn('def_blob')->getDefault());
        self::assertNull($onlineTable->getColumn('def_blob_null')->getDefault());
        self::assertFalse($onlineTable->getColumn('def_blob_null')->getNotnull());
182 183 184 185 186 187 188

        $comparator = new Comparator();

        $this->_sm->alterTable($comparator->diffTable($table, $onlineTable));

        $onlineTable = $this->_sm->listTableDetails("text_blob_default_value");

189 190 191 192 193 194
        self::assertNull($onlineTable->getColumn('def_text')->getDefault());
        self::assertNull($onlineTable->getColumn('def_text_null')->getDefault());
        self::assertFalse($onlineTable->getColumn('def_text_null')->getNotnull());
        self::assertNull($onlineTable->getColumn('def_blob')->getDefault());
        self::assertNull($onlineTable->getColumn('def_blob_null')->getDefault());
        self::assertFalse($onlineTable->getColumn('def_blob_null')->getNotnull());
195
    }
196 197 198

    public function testColumnCollation()
    {
belgattitude's avatar
belgattitude committed
199
        $table = new Table('test_collation');
200 201 202 203 204 205 206 207 208 209
        $table->addOption('collate', $collation = 'latin1_swedish_ci');
        $table->addOption('charset', 'latin1');
        $table->addColumn('id', 'integer');
        $table->addColumn('text', 'text');
        $table->addColumn('foo', 'text')->setPlatformOption('collation', 'latin1_swedish_ci');
        $table->addColumn('bar', 'text')->setPlatformOption('collation', 'utf8_general_ci');
        $this->_sm->dropAndCreateTable($table);

        $columns = $this->_sm->listTableColumns('test_collation');

210 211 212 213
        self::assertArrayNotHasKey('collation', $columns['id']->getPlatformOptions());
        self::assertEquals('latin1_swedish_ci', $columns['text']->getPlatformOption('collation'));
        self::assertEquals('latin1_swedish_ci', $columns['foo']->getPlatformOption('collation'));
        self::assertEquals('utf8_general_ci', $columns['bar']->getPlatformOption('collation'));
214
    }
215 216 217 218 219 220 221

    /**
     * @group DBAL-843
     */
    public function testListLobTypeColumns()
    {
        $tableName = 'lob_type_columns';
belgattitude's avatar
belgattitude committed
222
        $table = new Table($tableName);
223 224 225 226 227 228 229 230 231 232 233 234 235

        $table->addColumn('col_tinytext', 'text', array('length' => MySqlPlatform::LENGTH_LIMIT_TINYTEXT));
        $table->addColumn('col_text', 'text', array('length' => MySqlPlatform::LENGTH_LIMIT_TEXT));
        $table->addColumn('col_mediumtext', 'text', array('length' => MySqlPlatform::LENGTH_LIMIT_MEDIUMTEXT));
        $table->addColumn('col_longtext', 'text');

        $table->addColumn('col_tinyblob', 'text', array('length' => MySqlPlatform::LENGTH_LIMIT_TINYBLOB));
        $table->addColumn('col_blob', 'blob', array('length' => MySqlPlatform::LENGTH_LIMIT_BLOB));
        $table->addColumn('col_mediumblob', 'blob', array('length' => MySqlPlatform::LENGTH_LIMIT_MEDIUMBLOB));
        $table->addColumn('col_longblob', 'blob');

        $this->_sm->dropAndCreateTable($table);

belgattitude's avatar
belgattitude committed
236
        $platform = $this->_sm->getDatabasePlatform();
237
        $offlineColumns = $table->getColumns();
belgattitude's avatar
belgattitude committed
238
        $onlineColumns = $this->_sm->listTableColumns($tableName);
239

240
        self::assertSame(
241 242 243
            $platform->getClobTypeDeclarationSQL($offlineColumns['col_tinytext']->toArray()),
            $platform->getClobTypeDeclarationSQL($onlineColumns['col_tinytext']->toArray())
        );
244
        self::assertSame(
245 246 247
            $platform->getClobTypeDeclarationSQL($offlineColumns['col_text']->toArray()),
            $platform->getClobTypeDeclarationSQL($onlineColumns['col_text']->toArray())
        );
248
        self::assertSame(
249 250 251
            $platform->getClobTypeDeclarationSQL($offlineColumns['col_mediumtext']->toArray()),
            $platform->getClobTypeDeclarationSQL($onlineColumns['col_mediumtext']->toArray())
        );
252
        self::assertSame(
253 254 255 256
            $platform->getClobTypeDeclarationSQL($offlineColumns['col_longtext']->toArray()),
            $platform->getClobTypeDeclarationSQL($onlineColumns['col_longtext']->toArray())
        );

257
        self::assertSame(
258 259 260
            $platform->getBlobTypeDeclarationSQL($offlineColumns['col_tinyblob']->toArray()),
            $platform->getBlobTypeDeclarationSQL($onlineColumns['col_tinyblob']->toArray())
        );
261
        self::assertSame(
262 263 264
            $platform->getBlobTypeDeclarationSQL($offlineColumns['col_blob']->toArray()),
            $platform->getBlobTypeDeclarationSQL($onlineColumns['col_blob']->toArray())
        );
265
        self::assertSame(
266 267 268
            $platform->getBlobTypeDeclarationSQL($offlineColumns['col_mediumblob']->toArray()),
            $platform->getBlobTypeDeclarationSQL($onlineColumns['col_mediumblob']->toArray())
        );
269
        self::assertSame(
270 271 272 273
            $platform->getBlobTypeDeclarationSQL($offlineColumns['col_longblob']->toArray()),
            $platform->getBlobTypeDeclarationSQL($onlineColumns['col_longblob']->toArray())
        );
    }
274 275 276 277 278 279 280 281 282 283 284 285 286 287 288

    /**
     * @group DBAL-423
     */
    public function testDiffListGuidTableColumn()
    {
        $offlineTable = new Table('list_guid_table_column');
        $offlineTable->addColumn('col_guid', 'guid');

        $this->_sm->dropAndCreateTable($offlineTable);

        $onlineTable = $this->_sm->listTableDetails('list_guid_table_column');

        $comparator = new Comparator();

289
        self::assertFalse(
290 291 292 293
            $comparator->diffTable($offlineTable, $onlineTable),
            "No differences should be detected with the offline vs online schema."
        );
    }
294 295 296 297 298 299 300

    /**
     * @group DBAL-1082
     */
    public function testListDecimalTypeColumns()
    {
        $tableName = 'test_list_decimal_columns';
belgattitude's avatar
belgattitude committed
301
        $table = new Table($tableName);
302 303 304 305 306 307 308 309

        $table->addColumn('col', 'decimal');
        $table->addColumn('col_unsigned', 'decimal', array('unsigned' => true));

        $this->_sm->dropAndCreateTable($table);

        $columns = $this->_sm->listTableColumns($tableName);

310 311 312 313
        self::assertArrayHasKey('col', $columns);
        self::assertArrayHasKey('col_unsigned', $columns);
        self::assertFalse($columns['col']->getUnsigned());
        self::assertTrue($columns['col_unsigned']->getUnsigned());
314 315 316 317 318 319 320 321
    }

    /**
     * @group DBAL-1082
     */
    public function testListFloatTypeColumns()
    {
        $tableName = 'test_list_float_columns';
belgattitude's avatar
belgattitude committed
322
        $table = new Table($tableName);
323 324 325 326 327 328 329 330

        $table->addColumn('col', 'float');
        $table->addColumn('col_unsigned', 'float', array('unsigned' => true));

        $this->_sm->dropAndCreateTable($table);

        $columns = $this->_sm->listTableColumns($tableName);

331 332 333 334
        self::assertArrayHasKey('col', $columns);
        self::assertArrayHasKey('col_unsigned', $columns);
        self::assertFalse($columns['col']->getUnsigned());
        self::assertTrue($columns['col_unsigned']->getUnsigned());
335
    }
336

337
    public function testJsonColumnType() : void
338
    {
339 340 341 342 343
        $table = new Table('test_mysql_json');
        $table->addColumn('col_json', 'json');
        $this->_sm->dropAndCreateTable($table);

        $columns = $this->_sm->listTableColumns('test_mysql_json');
344

345 346
        self::assertSame(TYPE::JSON, $columns['col_json']->getType()->getName());
    }
347

348
    public function testColumnDefaultCurrentTimestamp() : void
349
    {
belgattitude's avatar
belgattitude committed
350
        $platform = $this->_sm->getDatabasePlatform();
351

belgattitude's avatar
belgattitude committed
352
        $table = new Table("test_column_defaults_current_timestamp");
353

belgattitude's avatar
belgattitude committed
354
        $currentTimeStampSql = $platform->getCurrentTimestampSQL();
355

belgattitude's avatar
belgattitude committed
356 357
        $table->addColumn('col_datetime', 'datetime', ['notnull' => true, 'default' => $currentTimeStampSql]);
        $table->addColumn('col_datetime_nullable', 'datetime', ['default' => $currentTimeStampSql]);
358 359 360

        $this->_sm->dropAndCreateTable($table);

belgattitude's avatar
belgattitude committed
361 362 363
        $onlineTable = $this->_sm->listTableDetails("test_column_defaults_current_timestamp");
        self::assertSame($currentTimeStampSql, $onlineTable->getColumn('col_datetime')->getDefault());
        self::assertSame($currentTimeStampSql, $onlineTable->getColumn('col_datetime_nullable')->getDefault());
364 365 366

        $comparator = new Comparator();

belgattitude's avatar
belgattitude committed
367 368 369 370
        $diff = $comparator->diffTable($table, $onlineTable);
        self::assertFalse($diff, "Tables should be identical with column defaults.");
    }

belgattitude's avatar
belgattitude committed
371 372
    public function testColumnDefaultsAreValid()
    {
belgattitude's avatar
belgattitude committed
373 374 375 376 377 378
        $table = new Table("test_column_defaults_are_valid");

        $currentTimeStampSql = $this->_sm->getDatabasePlatform()->getCurrentTimestampSQL();
        $table->addColumn('col_datetime', 'datetime', ['default' => $currentTimeStampSql]);
        $table->addColumn('col_datetime_null', 'datetime', ['notnull' => false, 'default' => null]);
        $table->addColumn('col_int', 'integer', ['default' => 1]);
379
        $table->addColumn('col_neg_int', 'integer', ['default' => -1]);
belgattitude's avatar
belgattitude committed
380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396
        $table->addColumn('col_string', 'string', ['default' => 'A']);
        $table->addColumn('col_decimal', 'decimal', ['scale' => 3, 'precision' => 6, 'default' => -2.3]);
        $table->addColumn('col_date', 'date', ['default' => '2012-12-12']);

        $this->_sm->dropAndCreateTable($table);

        $this->_conn->executeUpdate(
            "INSERT INTO test_column_defaults_are_valid () VALUES()"
        );

        $row = $this->_conn->fetchAssoc(
            'SELECT *, DATEDIFF(CURRENT_TIMESTAMP(), col_datetime) as diff_seconds FROM test_column_defaults_are_valid'
        );

        self::assertInstanceOf(\DateTime::class, \DateTime::createFromFormat('Y-m-d H:i:s', $row['col_datetime']));
        self::assertNull($row['col_datetime_null']);
        self::assertSame('2012-12-12', $row['col_date']);
397 398 399 400
        self::assertSame('A', $row['col_string']);
        self::assertEquals(1, $row['col_int']);
        self::assertEquals(-1, $row['col_neg_int']);
        self::assertEquals('-2.300', $row['col_decimal']);
belgattitude's avatar
belgattitude committed
401
        self::assertLessThan(5, $row['diff_seconds']);
402 403 404
    }

    /**
405 406 407 408 409 410 411 412
     * MariaDB 10.2+ does support CURRENT_TIME and CURRENT_DATE as
     * column default values for time and date columns.
     * (Not supported on Mysql as of 5.7.19)
     *
     * Note that MariaDB 10.2+, when storing default in information_schema,
     * silently change CURRENT_TIMESTAMP as 'current_timestamp()',
     * CURRENT_TIME as 'currtime()' and CURRENT_DATE as 'currdate()'.
     * This test also ensure proper aliasing to not trigger a table diff.
413
     */
414
    public function testColumnDefaultValuesCurrentTimeAndDate() : void
415
    {
belgattitude's avatar
belgattitude committed
416
        if ( ! $this->_sm->getDatabasePlatform() instanceof MariaDb1027Platform) {
417 418 419 420 421 422 423
            $this->markTestSkipped('Only relevant for MariaDb102Platform.');
        }

        $platform = $this->_sm->getDatabasePlatform();

        $table = new Table("test_column_defaults_current_time_and_date");

424
        $currentTimestampSql = $platform->getCurrentTimestampSQL();
belgattitude's avatar
belgattitude committed
425 426
        $currentTimeSql      = $platform->getCurrentTimeSQL();
        $currentDateSql      = $platform->getCurrentDateSQL();
427

428 429 430
        $table->addColumn('col_datetime', 'datetime', ['default' => $currentTimestampSql]);
        $table->addColumn('col_date', 'date', ['default' => $currentDateSql]);
        $table->addColumn('col_time', 'time', ['default' => $currentTimeSql]);
431 432 433 434 435

        $this->_sm->dropAndCreateTable($table);

        $onlineTable = $this->_sm->listTableDetails("test_column_defaults_current_time_and_date");

436
        self::assertSame($currentTimestampSql, $onlineTable->getColumn('col_datetime')->getDefault());
437 438 439 440 441 442
        self::assertSame($currentDateSql, $onlineTable->getColumn('col_date')->getDefault());
        self::assertSame($currentTimeSql, $onlineTable->getColumn('col_time')->getDefault());

        $comparator = new Comparator();

        $diff = $comparator->diffTable($table, $onlineTable);
443
        self::assertFalse($diff, "Tables should be identical with column defauts time and date.");
444
    }
445 446 447 448 449

    /**
     * Ensure default values (un-)escaping is properly done by mysql platforms.
     * The test is voluntarily relying on schema introspection due to current
     * doctrine limitations. Once #2850 is landed, this test can be removed.
450
     * @see https://dev.mysql.com/doc/refman/5.7/en/string-literals.html
451 452 453 454 455 456
     */
    public function testEnsureDefaultsAreUnescapedFromSchemaIntrospection() : void
    {
        $platform = $this->_sm->getDatabasePlatform();
        $this->_conn->query('DROP TABLE IF EXISTS test_column_defaults_with_create');

457 458 459 460 461 462 463 464 465 466 467 468 469 470
        $escapeSequences = [
            "\\0",          // An ASCII NUL (X'00') character
            "\\'", "''",    // Single quote
            '\\"', '""',    // Double quote
            '\\b',          // A backspace character
            '\\n',          // A new-line character
            '\\r',          // A carriage return character
            '\\t',          // A tab character
            '\\Z',          // ASCII 26 (Control+Z)
            '\\\\',         // A backslash (\) character
            '\\%',          // A percent (%) character
            '\\_',          // An underscore (_) character
        ];

belgattitude's avatar
belgattitude committed
471
        $default = implode('+', $escapeSequences);
472

belgattitude's avatar
belgattitude committed
473 474 475
        $sql = "CREATE TABLE test_column_defaults_with_create(
                    col1 VARCHAR(255) NULL DEFAULT {$platform->quoteStringLiteral($default)} 
                )";
476 477 478 479
        $this->_conn->query($sql);
        $onlineTable = $this->_sm->listTableDetails("test_column_defaults_with_create");
        self::assertSame($default, $onlineTable->getColumn('col1')->getDefault());
    }
480
}