TableTest.php 28.7 KB
Newer Older
1 2 3 4
<?php

namespace Doctrine\Tests\DBAL\Schema;

Luís Cobucci's avatar
Luís Cobucci committed
5
use Doctrine\DBAL\DBALException;
Sergei Morozov's avatar
Sergei Morozov committed
6 7
use Doctrine\DBAL\Platforms\MySqlPlatform;
use Doctrine\DBAL\Platforms\SqlitePlatform;
8 9
use Doctrine\DBAL\Schema\Column;
use Doctrine\DBAL\Schema\ForeignKeyConstraint;
jeroendedauw's avatar
jeroendedauw committed
10
use Doctrine\DBAL\Schema\Index;
Sergei Morozov's avatar
Sergei Morozov committed
11
use Doctrine\DBAL\Schema\SchemaException;
jeroendedauw's avatar
jeroendedauw committed
12
use Doctrine\DBAL\Schema\Table;
13
use Doctrine\DBAL\Types\Type;
Sergei Morozov's avatar
Sergei Morozov committed
14
use Doctrine\Tests\DbalTestCase;
15

16 17
use function array_shift;
use function current;
18

Sergei Morozov's avatar
Sergei Morozov committed
19
class TableTest extends DbalTestCase
20
{
21
    public function testCreateWithInvalidTableName(): void
22
    {
Luís Cobucci's avatar
Luís Cobucci committed
23 24
        $this->expectException(DBALException::class);

Sergei Morozov's avatar
Sergei Morozov committed
25
        new Table('');
26 27
    }

28
    public function testGetName(): void
29
    {
Sergei Morozov's avatar
Sergei Morozov committed
30 31
        $table =  new Table('foo', [], [], []);
        self::assertEquals('foo', $table->getName());
32 33
    }

34
    public function testColumns(): void
35
    {
Sergei Morozov's avatar
Sergei Morozov committed
36 37 38 39 40
        $type      = Type::getType('integer');
        $columns   = [];
        $columns[] = new Column('foo', $type);
        $columns[] = new Column('bar', $type);
        $table     = new Table('foo', $columns, [], []);
41

Sergei Morozov's avatar
Sergei Morozov committed
42 43 44
        self::assertTrue($table->hasColumn('foo'));
        self::assertTrue($table->hasColumn('bar'));
        self::assertFalse($table->hasColumn('baz'));
45

Sergei Morozov's avatar
Sergei Morozov committed
46 47
        self::assertInstanceOf(Column::class, $table->getColumn('foo'));
        self::assertInstanceOf(Column::class, $table->getColumn('bar'));
48

Gabriel Caruso's avatar
Gabriel Caruso committed
49
        self::assertCount(2, $table->getColumns());
50 51
    }

52
    public function testColumnsCaseInsensitive(): void
53
    {
Sergei Morozov's avatar
Sergei Morozov committed
54
        $table  = new Table('foo');
55
        $column = $table->addColumn('Foo', 'integer');
56

57 58 59
        self::assertTrue($table->hasColumn('Foo'));
        self::assertTrue($table->hasColumn('foo'));
        self::assertTrue($table->hasColumn('FOO'));
60

61 62 63
        self::assertSame($column, $table->getColumn('Foo'));
        self::assertSame($column, $table->getColumn('foo'));
        self::assertSame($column, $table->getColumn('FOO'));
64 65
    }

66
    public function testCreateColumn(): void
67 68 69
    {
        $type = Type::getType('integer');

Sergei Morozov's avatar
Sergei Morozov committed
70
        $table = new Table('foo');
71

Sergei Morozov's avatar
Sergei Morozov committed
72 73 74 75
        self::assertFalse($table->hasColumn('bar'));
        $table->addColumn('bar', 'integer');
        self::assertTrue($table->hasColumn('bar'));
        self::assertSame($type, $table->getColumn('bar')->getType());
76 77
    }

78
    public function testDropColumn(): void
79
    {
Sergei Morozov's avatar
Sergei Morozov committed
80 81 82 83 84
        $type      = Type::getType('integer');
        $columns   = [];
        $columns[] = new Column('foo', $type);
        $columns[] = new Column('bar', $type);
        $table     = new Table('foo', $columns, [], []);
85

Sergei Morozov's avatar
Sergei Morozov committed
86 87
        self::assertTrue($table->hasColumn('foo'));
        self::assertTrue($table->hasColumn('bar'));
88

Sergei Morozov's avatar
Sergei Morozov committed
89
        $table->dropColumn('foo')->dropColumn('bar');
90

Sergei Morozov's avatar
Sergei Morozov committed
91 92
        self::assertFalse($table->hasColumn('foo'));
        self::assertFalse($table->hasColumn('bar'));
93 94
    }

95
    public function testGetUnknownColumnThrowsException(): void
96
    {
Sergei Morozov's avatar
Sergei Morozov committed
97
        $this->expectException(SchemaException::class);
98

Sergei Morozov's avatar
Sergei Morozov committed
99
        $table = new Table('foo', [], [], []);
100 101 102
        $table->getColumn('unknown');
    }

103
    public function testAddColumnTwiceThrowsException(): void
104
    {
Sergei Morozov's avatar
Sergei Morozov committed
105
        $this->expectException(SchemaException::class);
106

Sergei Morozov's avatar
Sergei Morozov committed
107 108 109 110 111
        $type      = Type::getType('integer');
        $columns   = [];
        $columns[] = new Column('foo', $type);
        $columns[] = new Column('foo', $type);
        $table     = new Table('foo', $columns, [], []);
112 113
    }

114
    public function testCreateIndex(): void
115
    {
Sergei Morozov's avatar
Sergei Morozov committed
116 117 118
        $type    = Type::getType('integer');
        $columns = [new Column('foo', $type), new Column('bar', $type), new Column('baz', $type)];
        $table   = new Table('foo', $columns);
119

Sergei Morozov's avatar
Sergei Morozov committed
120 121
        $table->addIndex(['foo', 'bar'], 'foo_foo_bar_idx');
        $table->addUniqueIndex(['bar', 'baz'], 'foo_bar_baz_uniq');
122

Sergei Morozov's avatar
Sergei Morozov committed
123 124
        self::assertTrue($table->hasIndex('foo_foo_bar_idx'));
        self::assertTrue($table->hasIndex('foo_bar_baz_uniq'));
125 126
    }

127
    public function testIndexCaseInsensitive(): void
128
    {
Sergei Morozov's avatar
Sergei Morozov committed
129 130 131 132 133 134 135
        $type    = Type::getType('integer');
        $columns = [
            new Column('foo', $type),
            new Column('bar', $type),
            new Column('baz', $type),
        ];
        $table   = new Table('foo', $columns);
136

Sergei Morozov's avatar
Sergei Morozov committed
137
        $table->addIndex(['foo', 'bar', 'baz'], 'Foo_Idx');
138

139 140 141
        self::assertTrue($table->hasIndex('foo_idx'));
        self::assertTrue($table->hasIndex('Foo_Idx'));
        self::assertTrue($table->hasIndex('FOO_IDX'));
142 143
    }

144
    public function testAddIndexes(): void
145
    {
Sergei Morozov's avatar
Sergei Morozov committed
146 147 148 149 150 151 152 153 154 155 156 157 158 159
        $type    = Type::getType('integer');
        $columns = [
            new Column('foo', $type),
            new Column('bar', $type),
        ];
        $indexes = [
            new Index('the_primary', ['foo'], true, true),
            new Index('bar_idx', ['bar'], false, false),
        ];
        $table   = new Table('foo', $columns, $indexes, []);

        self::assertTrue($table->hasIndex('the_primary'));
        self::assertTrue($table->hasIndex('bar_idx'));
        self::assertFalse($table->hasIndex('some_idx'));
160

Sergei Morozov's avatar
Sergei Morozov committed
161 162 163
        self::assertInstanceOf(Index::class, $table->getPrimaryKey());
        self::assertInstanceOf(Index::class, $table->getIndex('the_primary'));
        self::assertInstanceOf(Index::class, $table->getIndex('bar_idx'));
164 165
    }

166
    public function testGetUnknownIndexThrowsException(): void
167
    {
Sergei Morozov's avatar
Sergei Morozov committed
168
        $this->expectException(SchemaException::class);
169

Sergei Morozov's avatar
Sergei Morozov committed
170 171
        $table = new Table('foo', [], [], []);
        $table->getIndex('unknownIndex');
172 173
    }

174
    public function testAddTwoPrimaryThrowsException(): void
175
    {
Sergei Morozov's avatar
Sergei Morozov committed
176
        $this->expectException(SchemaException::class);
177

Sergei Morozov's avatar
Sergei Morozov committed
178 179 180 181 182 183 184
        $type    = Type::getType('integer');
        $columns = [new Column('foo', $type), new Column('bar', $type)];
        $indexes = [
            new Index('the_primary', ['foo'], true, true),
            new Index('other_primary', ['bar'], true, true),
        ];
        $table   = new Table('foo', $columns, $indexes, []);
185 186
    }

187
    public function testAddTwoIndexesWithSameNameThrowsException(): void
188
    {
Sergei Morozov's avatar
Sergei Morozov committed
189
        $this->expectException(SchemaException::class);
190

Sergei Morozov's avatar
Sergei Morozov committed
191 192 193 194 195 196 197
        $type    = Type::getType('integer');
        $columns = [new Column('foo', $type), new Column('bar', $type)];
        $indexes = [
            new Index('an_idx', ['foo'], false, false),
            new Index('an_idx', ['bar'], false, false),
        ];
        $table   = new Table('foo', $columns, $indexes, []);
198 199
    }

200
    public function testConstraints(): void
201
    {
Sergei Morozov's avatar
Sergei Morozov committed
202
        $constraint = new ForeignKeyConstraint([], 'foo', []);
203

Sergei Morozov's avatar
Sergei Morozov committed
204
        $tableA      = new Table('foo', [], [], [$constraint]);
205
        $constraints = $tableA->getForeignKeys();
206

Gabriel Caruso's avatar
Gabriel Caruso committed
207
        self::assertCount(1, $constraints);
208
        self::assertSame($constraint, array_shift($constraints));
209 210
    }

211
    public function testOptions(): void
212
    {
Sergei Morozov's avatar
Sergei Morozov committed
213
        $table = new Table('foo', [], [], [], false, ['foo' => 'bar']);
214

Sergei Morozov's avatar
Sergei Morozov committed
215 216
        self::assertTrue($table->hasOption('foo'));
        self::assertEquals('bar', $table->getOption('foo'));
217 218
    }

219
    public function testBuilderSetPrimaryKey(): void
220
    {
Sergei Morozov's avatar
Sergei Morozov committed
221
        $table = new Table('foo');
222

Sergei Morozov's avatar
Sergei Morozov committed
223 224
        $table->addColumn('bar', 'integer');
        $table->setPrimaryKey(['bar']);
225

Sergei Morozov's avatar
Sergei Morozov committed
226
        self::assertTrue($table->hasIndex('primary'));
Sergei Morozov's avatar
Sergei Morozov committed
227
        self::assertInstanceOf(Index::class, $table->getPrimaryKey());
Sergei Morozov's avatar
Sergei Morozov committed
228 229
        self::assertTrue($table->getIndex('primary')->isUnique());
        self::assertTrue($table->getIndex('primary')->isPrimary());
230 231
    }

232
    public function testBuilderAddUniqueIndex(): void
233
    {
Sergei Morozov's avatar
Sergei Morozov committed
234
        $table = new Table('foo');
235

Sergei Morozov's avatar
Sergei Morozov committed
236 237
        $table->addColumn('bar', 'integer');
        $table->addUniqueIndex(['bar'], 'my_idx');
238

Sergei Morozov's avatar
Sergei Morozov committed
239 240 241
        self::assertTrue($table->hasIndex('my_idx'));
        self::assertTrue($table->getIndex('my_idx')->isUnique());
        self::assertFalse($table->getIndex('my_idx')->isPrimary());
242 243
    }

244
    public function testBuilderAddIndex(): void
245
    {
Sergei Morozov's avatar
Sergei Morozov committed
246
        $table = new Table('foo');
247

Sergei Morozov's avatar
Sergei Morozov committed
248 249
        $table->addColumn('bar', 'integer');
        $table->addIndex(['bar'], 'my_idx');
250

Sergei Morozov's avatar
Sergei Morozov committed
251 252 253
        self::assertTrue($table->hasIndex('my_idx'));
        self::assertFalse($table->getIndex('my_idx')->isUnique());
        self::assertFalse($table->getIndex('my_idx')->isPrimary());
254 255
    }

256
    public function testBuilderAddIndexWithInvalidNameThrowsException(): void
257
    {
Sergei Morozov's avatar
Sergei Morozov committed
258
        $this->expectException(SchemaException::class);
259

Sergei Morozov's avatar
Sergei Morozov committed
260 261 262
        $table = new Table('foo');
        $table->addColumn('bar', 'integer');
        $table->addIndex(['bar'], 'invalid name %&/');
263 264
    }

265
    public function testBuilderAddIndexWithUnknownColumnThrowsException(): void
266
    {
Sergei Morozov's avatar
Sergei Morozov committed
267
        $this->expectException(SchemaException::class);
268

Sergei Morozov's avatar
Sergei Morozov committed
269 270
        $table = new Table('foo');
        $table->addIndex(['bar'], 'invalidName');
271 272
    }

273
    public function testBuilderOptions(): void
274
    {
Sergei Morozov's avatar
Sergei Morozov committed
275 276 277 278
        $table = new Table('foo');
        $table->addOption('foo', 'bar');
        self::assertTrue($table->hasOption('foo'));
        self::assertEquals('bar', $table->getOption('foo'));
279 280
    }

281
    public function testAddForeignKeyConstraintUnknownLocalColumnThrowsException(): void
282
    {
Sergei Morozov's avatar
Sergei Morozov committed
283
        $this->expectException(SchemaException::class);
284

Sergei Morozov's avatar
Sergei Morozov committed
285 286
        $table = new Table('foo');
        $table->addColumn('id', 'integer');
287

Sergei Morozov's avatar
Sergei Morozov committed
288 289
        $foreignTable = new Table('bar');
        $foreignTable->addColumn('id', 'integer');
290

Sergei Morozov's avatar
Sergei Morozov committed
291
        $table->addForeignKeyConstraint($foreignTable, ['foo'], ['id']);
292 293
    }

294
    public function testAddForeignKeyConstraintUnknownForeignColumnThrowsException(): void
295
    {
Sergei Morozov's avatar
Sergei Morozov committed
296
        $this->expectException(SchemaException::class);
297

Sergei Morozov's avatar
Sergei Morozov committed
298 299
        $table = new Table('foo');
        $table->addColumn('id', 'integer');
300

Sergei Morozov's avatar
Sergei Morozov committed
301 302
        $foreignTable = new Table('bar');
        $foreignTable->addColumn('id', 'integer');
303

Sergei Morozov's avatar
Sergei Morozov committed
304
        $table->addForeignKeyConstraint($foreignTable, ['id'], ['foo']);
305 306
    }

307
    public function testAddForeignKeyConstraint(): void
308
    {
Sergei Morozov's avatar
Sergei Morozov committed
309 310
        $table = new Table('foo');
        $table->addColumn('id', 'integer');
311

Sergei Morozov's avatar
Sergei Morozov committed
312 313
        $foreignTable = new Table('bar');
        $foreignTable->addColumn('id', 'integer');
314

Sergei Morozov's avatar
Sergei Morozov committed
315
        $table->addForeignKeyConstraint($foreignTable, ['id'], ['id'], ['foo' => 'bar']);
316

317
        $constraints = $table->getForeignKeys();
Gabriel Caruso's avatar
Gabriel Caruso committed
318
        self::assertCount(1, $constraints);
319
        $constraint = current($constraints);
320

Sergei Morozov's avatar
Sergei Morozov committed
321
        self::assertInstanceOf(ForeignKeyConstraint::class, $constraint);
322

Sergei Morozov's avatar
Sergei Morozov committed
323 324
        self::assertTrue($constraint->hasOption('foo'));
        self::assertEquals('bar', $constraint->getOption('foo'));
325
    }
326

327
    public function testAddIndexWithCaseSensitiveColumnProblem(): void
328
    {
Sergei Morozov's avatar
Sergei Morozov committed
329 330
        $table = new Table('foo');
        $table->addColumn('id', 'integer');
331

Sergei Morozov's avatar
Sergei Morozov committed
332
        $table->addIndex(['ID'], 'my_idx');
333

334
        self::assertTrue($table->hasIndex('my_idx'));
Sergei Morozov's avatar
Sergei Morozov committed
335 336
        self::assertEquals(['ID'], $table->getIndex('my_idx')->getColumns());
        self::assertTrue($table->getIndex('my_idx')->spansColumns(['id']));
337
    }
338

339
    public function testAddPrimaryKeyColumnsAreExplicitlySetToNotNull(): void
340
    {
Sergei Morozov's avatar
Sergei Morozov committed
341 342
        $table  = new Table('foo');
        $column = $table->addColumn('id', 'integer', ['notnull' => false]);
343

344
        self::assertFalse($column->getNotnull());
345

Sergei Morozov's avatar
Sergei Morozov committed
346
        $table->setPrimaryKey(['id']);
347

348
        self::assertTrue($column->getNotnull());
349
    }
350

351
    public function testAllowImplicitSchemaTableInAutogeneratedIndexNames(): void
352
    {
Sergei Morozov's avatar
Sergei Morozov committed
353 354 355
        $table = new Table('foo.bar');
        $table->addColumn('baz', 'integer', []);
        $table->addIndex(['baz']);
356

Gabriel Caruso's avatar
Gabriel Caruso committed
357
        self::assertCount(1, $table->getIndexes());
358
    }
359

360
    public function testAddForeignKeyIndexImplicitly(): void
361
    {
Sergei Morozov's avatar
Sergei Morozov committed
362 363
        $table = new Table('foo');
        $table->addColumn('id', 'integer');
364

Sergei Morozov's avatar
Sergei Morozov committed
365 366
        $foreignTable = new Table('bar');
        $foreignTable->addColumn('id', 'integer');
367

Sergei Morozov's avatar
Sergei Morozov committed
368
        $table->addForeignKeyConstraint($foreignTable, ['id'], ['id'], ['foo' => 'bar']);
369

370
        $indexes = $table->getIndexes();
Gabriel Caruso's avatar
Gabriel Caruso committed
371
        self::assertCount(1, $indexes);
372 373
        $index = current($indexes);

374
        self::assertTrue($table->hasIndex($index->getName()));
Sergei Morozov's avatar
Sergei Morozov committed
375
        self::assertEquals(['id'], $index->getColumns());
376 377
    }

378
    public function testAddForeignKeyDoesNotCreateDuplicateIndex(): void
379 380 381
    {
        $table = new Table('foo');
        $table->addColumn('bar', 'integer');
Sergei Morozov's avatar
Sergei Morozov committed
382
        $table->addIndex(['bar'], 'bar_idx');
383 384 385 386

        $foreignTable = new Table('bar');
        $foreignTable->addColumn('foo', 'integer');

Sergei Morozov's avatar
Sergei Morozov committed
387
        $table->addForeignKeyConstraint($foreignTable, ['bar'], ['foo']);
388

389 390
        self::assertCount(1, $table->getIndexes());
        self::assertTrue($table->hasIndex('bar_idx'));
Sergei Morozov's avatar
Sergei Morozov committed
391
        self::assertSame(['bar'], $table->getIndex('bar_idx')->getColumns());
392 393
    }

394
    public function testAddForeignKeyAddsImplicitIndexIfIndexColumnsDoNotSpan(): void
395 396 397 398 399
    {
        $table = new Table('foo');
        $table->addColumn('bar', 'integer');
        $table->addColumn('baz', 'string');
        $table->addColumn('bloo', 'string');
Sergei Morozov's avatar
Sergei Morozov committed
400 401
        $table->addIndex(['baz', 'bar'], 'composite_idx');
        $table->addIndex(['bar', 'baz', 'bloo'], 'full_idx');
402 403 404 405 406

        $foreignTable = new Table('bar');
        $foreignTable->addColumn('foo', 'integer');
        $foreignTable->addColumn('baz', 'string');

Sergei Morozov's avatar
Sergei Morozov committed
407
        $table->addForeignKeyConstraint($foreignTable, ['bar', 'baz'], ['foo', 'baz']);
408

409 410 411 412
        self::assertCount(3, $table->getIndexes());
        self::assertTrue($table->hasIndex('composite_idx'));
        self::assertTrue($table->hasIndex('full_idx'));
        self::assertTrue($table->hasIndex('idx_8c73652176ff8caa78240498'));
Sergei Morozov's avatar
Sergei Morozov committed
413 414 415
        self::assertSame(['baz', 'bar'], $table->getIndex('composite_idx')->getColumns());
        self::assertSame(['bar', 'baz', 'bloo'], $table->getIndex('full_idx')->getColumns());
        self::assertSame(['bar', 'baz'], $table->getIndex('idx_8c73652176ff8caa78240498')->getColumns());
416 417
    }

418
    public function testOverrulingIndexDoesNotDropOverruledIndex(): void
419
    {
Sergei Morozov's avatar
Sergei Morozov committed
420 421 422
        $table = new Table('bar');
        $table->addColumn('baz', 'integer', []);
        $table->addIndex(['baz']);
423 424

        $indexes = $table->getIndexes();
Gabriel Caruso's avatar
Gabriel Caruso committed
425
        self::assertCount(1, $indexes);
426
        $index = current($indexes);
427

Sergei Morozov's avatar
Sergei Morozov committed
428
        $table->addUniqueIndex(['baz']);
Gabriel Caruso's avatar
Gabriel Caruso committed
429
        self::assertCount(2, $table->getIndexes());
430
        self::assertTrue($table->hasIndex($index->getName()));
431 432
    }

433
    public function testAllowsAddingDuplicateIndexesBasedOnColumns(): void
434 435 436
    {
        $table = new Table('foo');
        $table->addColumn('bar', 'integer');
Sergei Morozov's avatar
Sergei Morozov committed
437 438
        $table->addIndex(['bar'], 'bar_idx');
        $table->addIndex(['bar'], 'duplicate_idx');
439

440 441 442
        self::assertCount(2, $table->getIndexes());
        self::assertTrue($table->hasIndex('bar_idx'));
        self::assertTrue($table->hasIndex('duplicate_idx'));
Sergei Morozov's avatar
Sergei Morozov committed
443 444
        self::assertSame(['bar'], $table->getIndex('bar_idx')->getColumns());
        self::assertSame(['bar'], $table->getIndex('duplicate_idx')->getColumns());
445
    }
446

447
    public function testAllowsAddingFulfillingIndexesBasedOnColumns(): void
448 449 450 451
    {
        $table = new Table('foo');
        $table->addColumn('bar', 'integer');
        $table->addColumn('baz', 'string');
Sergei Morozov's avatar
Sergei Morozov committed
452 453
        $table->addIndex(['bar'], 'bar_idx');
        $table->addIndex(['bar', 'baz'], 'fulfilling_idx');
454

455 456 457
        self::assertCount(2, $table->getIndexes());
        self::assertTrue($table->hasIndex('bar_idx'));
        self::assertTrue($table->hasIndex('fulfilling_idx'));
Sergei Morozov's avatar
Sergei Morozov committed
458 459
        self::assertSame(['bar'], $table->getIndex('bar_idx')->getColumns());
        self::assertSame(['bar', 'baz'], $table->getIndex('fulfilling_idx')->getColumns());
460 461
    }

462
    public function testPrimaryKeyOverrulingUniqueIndexDoesNotDropUniqueIndex(): void
463
    {
Sergei Morozov's avatar
Sergei Morozov committed
464 465 466
        $table = new Table('bar');
        $table->addColumn('baz', 'integer', []);
        $table->addUniqueIndex(['baz'], 'idx_unique');
467

Sergei Morozov's avatar
Sergei Morozov committed
468
        $table->setPrimaryKey(['baz']);
469 470

        $indexes = $table->getIndexes();
Sergei Morozov's avatar
Sergei Morozov committed
471
        self::assertCount(2, $indexes, 'Table should only contain both the primary key table index and the unique one, even though it was overruled.');
472

473 474
        self::assertTrue($table->hasPrimaryKey());
        self::assertTrue($table->hasIndex('idx_unique'));
475
    }
476

477
    public function testAddingFulfillingRegularIndexOverridesImplicitForeignKeyConstraintIndex(): void
478 479 480 481 482 483
    {
        $foreignTable = new Table('foreign');
        $foreignTable->addColumn('id', 'integer');

        $localTable = new Table('local');
        $localTable->addColumn('id', 'integer');
Sergei Morozov's avatar
Sergei Morozov committed
484
        $localTable->addForeignKeyConstraint($foreignTable, ['id'], ['id']);
485

486
        self::assertCount(1, $localTable->getIndexes());
487

Sergei Morozov's avatar
Sergei Morozov committed
488
        $localTable->addIndex(['id'], 'explicit_idx');
489

490 491
        self::assertCount(1, $localTable->getIndexes());
        self::assertTrue($localTable->hasIndex('explicit_idx'));
492 493
    }

494
    public function testAddingFulfillingUniqueIndexOverridesImplicitForeignKeyConstraintIndex(): void
495 496 497 498 499 500
    {
        $foreignTable = new Table('foreign');
        $foreignTable->addColumn('id', 'integer');

        $localTable = new Table('local');
        $localTable->addColumn('id', 'integer');
Sergei Morozov's avatar
Sergei Morozov committed
501
        $localTable->addForeignKeyConstraint($foreignTable, ['id'], ['id']);
502

503
        self::assertCount(1, $localTable->getIndexes());
504

Sergei Morozov's avatar
Sergei Morozov committed
505
        $localTable->addUniqueIndex(['id'], 'explicit_idx');
506

507 508
        self::assertCount(1, $localTable->getIndexes());
        self::assertTrue($localTable->hasIndex('explicit_idx'));
509 510
    }

511
    public function testAddingFulfillingPrimaryKeyOverridesImplicitForeignKeyConstraintIndex(): void
512 513 514 515 516 517
    {
        $foreignTable = new Table('foreign');
        $foreignTable->addColumn('id', 'integer');

        $localTable = new Table('local');
        $localTable->addColumn('id', 'integer');
Sergei Morozov's avatar
Sergei Morozov committed
518
        $localTable->addForeignKeyConstraint($foreignTable, ['id'], ['id']);
519

520
        self::assertCount(1, $localTable->getIndexes());
521

Sergei Morozov's avatar
Sergei Morozov committed
522
        $localTable->setPrimaryKey(['id'], 'explicit_idx');
523

524 525
        self::assertCount(1, $localTable->getIndexes());
        self::assertTrue($localTable->hasIndex('explicit_idx'));
526 527
    }

528
    public function testAddingFulfillingExplicitIndexOverridingImplicitForeignKeyConstraintIndexWithSameNameDoesNotThrowException(): void
529 530 531 532 533 534
    {
        $foreignTable = new Table('foreign');
        $foreignTable->addColumn('id', 'integer');

        $localTable = new Table('local');
        $localTable->addColumn('id', 'integer');
Sergei Morozov's avatar
Sergei Morozov committed
535
        $localTable->addForeignKeyConstraint($foreignTable, ['id'], ['id']);
536

537 538
        self::assertCount(1, $localTable->getIndexes());
        self::assertTrue($localTable->hasIndex('IDX_8BD688E8BF396750'));
539 540 541

        $implicitIndex = $localTable->getIndex('IDX_8BD688E8BF396750');

Sergei Morozov's avatar
Sergei Morozov committed
542
        $localTable->addIndex(['id'], 'IDX_8BD688E8BF396750');
543

544 545 546
        self::assertCount(1, $localTable->getIndexes());
        self::assertTrue($localTable->hasIndex('IDX_8BD688E8BF396750'));
        self::assertNotSame($implicitIndex, $localTable->getIndex('IDX_8BD688E8BF396750'));
547 548
    }

549
    public function testQuotedTableName(): void
550
    {
Sergei Morozov's avatar
Sergei Morozov committed
551
        $table = new Table('`bar`');
552

Sergei Morozov's avatar
Sergei Morozov committed
553 554
        $mysqlPlatform  = new MySqlPlatform();
        $sqlitePlatform = new SqlitePlatform();
555

556 557 558
        self::assertEquals('bar', $table->getName());
        self::assertEquals('`bar`', $table->getQuotedName($mysqlPlatform));
        self::assertEquals('"bar"', $table->getQuotedName($sqlitePlatform));
559
    }
560

561
    public function testTableHasPrimaryKey(): void
562
    {
Sergei Morozov's avatar
Sergei Morozov committed
563
        $table = new Table('test');
564

565
        self::assertFalse($table->hasPrimaryKey());
566

Sergei Morozov's avatar
Sergei Morozov committed
567 568
        $table->addColumn('foo', 'integer');
        $table->setPrimaryKey(['foo']);
569

570
        self::assertTrue($table->hasPrimaryKey());
571
    }
572

573
    public function testAddIndexWithQuotedColumns(): void
574
    {
Sergei Morozov's avatar
Sergei Morozov committed
575
        $table = new Table('test');
576 577
        $table->addColumn('"foo"', 'integer');
        $table->addColumn('bar', 'integer');
Luís Cobucci's avatar
Luís Cobucci committed
578 579 580
        $table->addIndex(['"foo"', '"bar"']);

        self::assertTrue($table->columnsAreIndexed(['"foo"', '"bar"']));
581 582
    }

583
    public function testAddForeignKeyWithQuotedColumnsAndTable(): void
584
    {
Sergei Morozov's avatar
Sergei Morozov committed
585
        $table = new Table('test');
586 587
        $table->addColumn('"foo"', 'integer');
        $table->addColumn('bar', 'integer');
Sergei Morozov's avatar
Sergei Morozov committed
588
        $table->addForeignKeyConstraint('"boing"', ['"foo"', '"bar"'], ['id']);
Luís Cobucci's avatar
Luís Cobucci committed
589 590

        self::assertCount(1, $table->getForeignKeys());
591
    }
592

593
    public function testQuoteSchemaPrefixed(): void
594
    {
Sergei Morozov's avatar
Sergei Morozov committed
595 596 597
        $table = new Table('`test`.`test`');
        self::assertEquals('test.test', $table->getName());
        self::assertEquals('`test`.`test`', $table->getQuotedName(new MySqlPlatform()));
598
    }
599

600
    public function testFullQualifiedTableName(): void
601
    {
Sergei Morozov's avatar
Sergei Morozov committed
602 603 604
        $table = new Table('`test`.`test`');
        self::assertEquals('test.test', $table->getFullQualifiedName('test'));
        self::assertEquals('test.test', $table->getFullQualifiedName('other'));
605

Sergei Morozov's avatar
Sergei Morozov committed
606 607 608
        $table = new Table('test');
        self::assertEquals('test.test', $table->getFullQualifiedName('test'));
        self::assertEquals('other.test', $table->getFullQualifiedName('other'));
609
    }
610

611
    public function testDropIndex(): void
612
    {
Sergei Morozov's avatar
Sergei Morozov committed
613
        $table = new Table('test');
614
        $table->addColumn('id', 'integer');
Sergei Morozov's avatar
Sergei Morozov committed
615
        $table->addIndex(['id'], 'idx');
616

617
        self::assertTrue($table->hasIndex('idx'));
618 619

        $table->dropIndex('idx');
620
        self::assertFalse($table->hasIndex('idx'));
621 622
    }

623
    public function testDropPrimaryKey(): void
624
    {
Sergei Morozov's avatar
Sergei Morozov committed
625
        $table = new Table('test');
626
        $table->addColumn('id', 'integer');
Sergei Morozov's avatar
Sergei Morozov committed
627
        $table->setPrimaryKey(['id']);
628

629
        self::assertTrue($table->hasPrimaryKey());
630 631

        $table->dropPrimaryKey();
632
        self::assertFalse($table->hasPrimaryKey());
633
    }
634

635
    public function testRenameIndex(): void
636
    {
Sergei Morozov's avatar
Sergei Morozov committed
637
        $table = new Table('test');
638 639 640 641
        $table->addColumn('id', 'integer');
        $table->addColumn('foo', 'integer');
        $table->addColumn('bar', 'integer');
        $table->addColumn('baz', 'integer');
Sergei Morozov's avatar
Sergei Morozov committed
642 643 644
        $table->setPrimaryKey(['id'], 'pk');
        $table->addIndex(['foo'], 'idx', ['flag']);
        $table->addUniqueIndex(['bar', 'baz'], 'uniq');
645 646

        // Rename to custom name.
647 648 649 650 651 652 653 654 655 656 657 658 659
        self::assertSame($table, $table->renameIndex('pk', 'pk_new'));
        self::assertSame($table, $table->renameIndex('idx', 'idx_new'));
        self::assertSame($table, $table->renameIndex('uniq', 'uniq_new'));

        self::assertTrue($table->hasPrimaryKey());
        self::assertTrue($table->hasIndex('pk_new'));
        self::assertTrue($table->hasIndex('idx_new'));
        self::assertTrue($table->hasIndex('uniq_new'));

        self::assertFalse($table->hasIndex('pk'));
        self::assertFalse($table->hasIndex('idx'));
        self::assertFalse($table->hasIndex('uniq'));

Sergei Morozov's avatar
Sergei Morozov committed
660 661
        self::assertEquals(new Index('pk_new', ['id'], true, true), $table->getPrimaryKey());
        self::assertEquals(new Index('pk_new', ['id'], true, true), $table->getIndex('pk_new'));
662
        self::assertEquals(
Sergei Morozov's avatar
Sergei Morozov committed
663
            new Index('idx_new', ['foo'], false, false, ['flag']),
664 665
            $table->getIndex('idx_new')
        );
Sergei Morozov's avatar
Sergei Morozov committed
666
        self::assertEquals(new Index('uniq_new', ['bar', 'baz'], true), $table->getIndex('uniq_new'));
667 668

        // Rename to auto-generated name.
669 670 671 672 673 674 675 676 677 678 679 680 681
        self::assertSame($table, $table->renameIndex('pk_new', null));
        self::assertSame($table, $table->renameIndex('idx_new', null));
        self::assertSame($table, $table->renameIndex('uniq_new', null));

        self::assertTrue($table->hasPrimaryKey());
        self::assertTrue($table->hasIndex('primary'));
        self::assertTrue($table->hasIndex('IDX_D87F7E0C8C736521'));
        self::assertTrue($table->hasIndex('UNIQ_D87F7E0C76FF8CAA78240498'));

        self::assertFalse($table->hasIndex('pk_new'));
        self::assertFalse($table->hasIndex('idx_new'));
        self::assertFalse($table->hasIndex('uniq_new'));

Sergei Morozov's avatar
Sergei Morozov committed
682 683
        self::assertEquals(new Index('primary', ['id'], true, true), $table->getPrimaryKey());
        self::assertEquals(new Index('primary', ['id'], true, true), $table->getIndex('primary'));
684
        self::assertEquals(
Sergei Morozov's avatar
Sergei Morozov committed
685
            new Index('IDX_D87F7E0C8C736521', ['foo'], false, false, ['flag']),
686 687
            $table->getIndex('IDX_D87F7E0C8C736521')
        );
688
        self::assertEquals(
Sergei Morozov's avatar
Sergei Morozov committed
689
            new Index('UNIQ_D87F7E0C76FF8CAA78240498', ['bar', 'baz'], true),
690 691 692 693
            $table->getIndex('UNIQ_D87F7E0C76FF8CAA78240498')
        );

        // Rename to same name (changed case).
694 695 696
        self::assertSame($table, $table->renameIndex('primary', 'PRIMARY'));
        self::assertSame($table, $table->renameIndex('IDX_D87F7E0C8C736521', 'idx_D87F7E0C8C736521'));
        self::assertSame($table, $table->renameIndex('UNIQ_D87F7E0C76FF8CAA78240498', 'uniq_D87F7E0C76FF8CAA78240498'));
697

698 699 700 701
        self::assertTrue($table->hasPrimaryKey());
        self::assertTrue($table->hasIndex('primary'));
        self::assertTrue($table->hasIndex('IDX_D87F7E0C8C736521'));
        self::assertTrue($table->hasIndex('UNIQ_D87F7E0C76FF8CAA78240498'));
702 703
    }

704
    public function testKeepsIndexOptionsOnRenamingRegularIndex(): void
705 706 707
    {
        $table = new Table('foo');
        $table->addColumn('id', 'integer');
Sergei Morozov's avatar
Sergei Morozov committed
708
        $table->addIndex(['id'], 'idx_bar', [], ['where' => '1 = 1']);
709 710 711

        $table->renameIndex('idx_bar', 'idx_baz');

Sergei Morozov's avatar
Sergei Morozov committed
712
        self::assertSame(['where' => '1 = 1'], $table->getIndex('idx_baz')->getOptions());
713 714
    }

715
    public function testKeepsIndexOptionsOnRenamingUniqueIndex(): void
716 717 718
    {
        $table = new Table('foo');
        $table->addColumn('id', 'integer');
Sergei Morozov's avatar
Sergei Morozov committed
719
        $table->addUniqueIndex(['id'], 'idx_bar', ['where' => '1 = 1']);
720 721 722

        $table->renameIndex('idx_bar', 'idx_baz');

Sergei Morozov's avatar
Sergei Morozov committed
723
        self::assertSame(['where' => '1 = 1'], $table->getIndex('idx_baz')->getOptions());
724 725
    }

726
    public function testThrowsExceptionOnRenamingNonExistingIndex(): void
727
    {
Sergei Morozov's avatar
Sergei Morozov committed
728
        $table = new Table('test');
729
        $table->addColumn('id', 'integer');
Sergei Morozov's avatar
Sergei Morozov committed
730
        $table->addIndex(['id'], 'idx');
731

732 733
        $this->expectException(SchemaException::class);

734 735 736
        $table->renameIndex('foo', 'bar');
    }

737
    public function testThrowsExceptionOnRenamingToAlreadyExistingIndex(): void
738
    {
Sergei Morozov's avatar
Sergei Morozov committed
739
        $table = new Table('test');
740 741
        $table->addColumn('id', 'integer');
        $table->addColumn('foo', 'integer');
Sergei Morozov's avatar
Sergei Morozov committed
742 743
        $table->addIndex(['id'], 'idx_id');
        $table->addIndex(['foo'], 'idx_foo');
744

745 746
        $this->expectException(SchemaException::class);

747 748
        $table->renameIndex('idx_id', 'idx_foo');
    }
749 750 751 752

    /**
     * @dataProvider getNormalizesAssetNames
     */
753
    public function testNormalizesColumnNames(string $assetName): void
754 755 756 757
    {
        $table = new Table('test');

        $table->addColumn($assetName, 'integer');
Sergei Morozov's avatar
Sergei Morozov committed
758 759
        $table->addIndex([$assetName], $assetName);
        $table->addForeignKeyConstraint('test', [$assetName], [$assetName], [], $assetName);
760

761 762
        self::assertTrue($table->hasColumn($assetName));
        self::assertTrue($table->hasColumn('foo'));
Sergei Morozov's avatar
Sergei Morozov committed
763 764
        self::assertInstanceOf(Column::class, $table->getColumn($assetName));
        self::assertInstanceOf(Column::class, $table->getColumn('foo'));
765

766 767
        self::assertTrue($table->hasIndex($assetName));
        self::assertTrue($table->hasIndex('foo'));
Sergei Morozov's avatar
Sergei Morozov committed
768 769
        self::assertInstanceOf(Index::class, $table->getIndex($assetName));
        self::assertInstanceOf(Index::class, $table->getIndex('foo'));
770

771 772
        self::assertTrue($table->hasForeignKey($assetName));
        self::assertTrue($table->hasForeignKey('foo'));
Sergei Morozov's avatar
Sergei Morozov committed
773 774
        self::assertInstanceOf(ForeignKeyConstraint::class, $table->getForeignKey($assetName));
        self::assertInstanceOf(ForeignKeyConstraint::class, $table->getForeignKey('foo'));
775 776

        $table->renameIndex($assetName, $assetName);
777 778
        self::assertTrue($table->hasIndex($assetName));
        self::assertTrue($table->hasIndex('foo'));
779 780

        $table->renameIndex($assetName, 'foo');
781 782
        self::assertTrue($table->hasIndex($assetName));
        self::assertTrue($table->hasIndex('foo'));
783 784

        $table->renameIndex('foo', $assetName);
785 786
        self::assertTrue($table->hasIndex($assetName));
        self::assertTrue($table->hasIndex('foo'));
787 788

        $table->renameIndex($assetName, 'bar');
789 790 791
        self::assertFalse($table->hasIndex($assetName));
        self::assertFalse($table->hasIndex('foo'));
        self::assertTrue($table->hasIndex('bar'));
792 793 794 795 796 797 798

        $table->renameIndex('bar', $assetName);

        $table->dropColumn($assetName);
        $table->dropIndex($assetName);
        $table->removeForeignKey($assetName);

799 800 801 802 803 804
        self::assertFalse($table->hasColumn($assetName));
        self::assertFalse($table->hasColumn('foo'));
        self::assertFalse($table->hasIndex($assetName));
        self::assertFalse($table->hasIndex('foo'));
        self::assertFalse($table->hasForeignKey($assetName));
        self::assertFalse($table->hasForeignKey('foo'));
805 806
    }

807 808 809
    /**
     * @return mixed[][]
     */
810
    public static function getNormalizesAssetNames(): iterable
811
    {
Sergei Morozov's avatar
Sergei Morozov committed
812 813 814 815 816 817 818 819 820 821
        return [
            ['foo'],
            ['FOO'],
            ['`foo`'],
            ['`FOO`'],
            ['"foo"'],
            ['"FOO"'],
            ['"foo"'],
            ['"FOO"'],
        ];
822
    }
823

824
    public function testTableComment(): void
825 826 827 828 829 830 831
    {
        $table = new Table('bar');
        self::assertNull($table->getComment());

        $table->setComment('foo');
        self::assertEquals('foo', $table->getComment());
    }
832
}