|
6 | 6 | use InvalidArgumentException;
|
7 | 7 | use Phinx\Db\Adapter\SQLiteAdapter;
|
8 | 8 | use Phinx\Db\Table\Column;
|
| 9 | +use Phinx\Db\Table\ForeignKey; |
9 | 10 | use Phinx\Util\Expression;
|
10 | 11 | use Phinx\Util\Literal;
|
11 | 12 | use Symfony\Component\Console\Input\ArrayInput;
|
@@ -1408,6 +1409,208 @@ public function testAlterTableWithConstraints()
|
1408 | 1409 | }
|
1409 | 1410 | }
|
1410 | 1411 |
|
| 1412 | + /** |
| 1413 | + * Tests that operations that trigger implicit table drops will not cause |
| 1414 | + * a foreign key constraint violation error. |
| 1415 | + */ |
| 1416 | + public function testAlterTableDoesNotViolateRestrictedForeignKeyConstraint() |
| 1417 | + { |
| 1418 | + $this->adapter->execute('PRAGMA foreign_keys = ON'); |
| 1419 | + |
| 1420 | + $articlesTable = new \Phinx\Db\Table('articles', [], $this->adapter); |
| 1421 | + $articlesTable |
| 1422 | + ->insert(['id' => 1]) |
| 1423 | + ->save(); |
| 1424 | + |
| 1425 | + $commentsTable = new \Phinx\Db\Table('comments', [], $this->adapter); |
| 1426 | + $commentsTable |
| 1427 | + ->addColumn('article_id', 'integer') |
| 1428 | + ->addForeignKey('article_id', 'articles', 'id', [ |
| 1429 | + 'update' => ForeignKey::RESTRICT, |
| 1430 | + 'delete' => ForeignKey::RESTRICT, |
| 1431 | + ]) |
| 1432 | + ->insert(['id' => 1, 'article_id' => 1]) |
| 1433 | + ->save(); |
| 1434 | + |
| 1435 | + $this->assertTrue($this->adapter->hasForeignKey('comments', ['article_id'])); |
| 1436 | + |
| 1437 | + $articlesTable |
| 1438 | + ->addColumn('new_column', 'integer') |
| 1439 | + ->update(); |
| 1440 | + |
| 1441 | + $articlesTable |
| 1442 | + ->renameColumn('new_column', 'new_column_renamed') |
| 1443 | + ->update(); |
| 1444 | + |
| 1445 | + $articlesTable |
| 1446 | + ->changeColumn('new_column_renamed', 'integer', [ |
| 1447 | + 'default' => 1, |
| 1448 | + ]) |
| 1449 | + ->update(); |
| 1450 | + |
| 1451 | + $articlesTable |
| 1452 | + ->removeColumn('new_column_renamed') |
| 1453 | + ->update(); |
| 1454 | + |
| 1455 | + $articlesTable |
| 1456 | + ->addIndex('id', ['name' => 'ID_IDX']) |
| 1457 | + ->update(); |
| 1458 | + |
| 1459 | + $articlesTable |
| 1460 | + ->removeIndex('id') |
| 1461 | + ->update(); |
| 1462 | + |
| 1463 | + $articlesTable |
| 1464 | + ->addForeignKey('id', 'comments', 'id') |
| 1465 | + ->update(); |
| 1466 | + |
| 1467 | + $articlesTable |
| 1468 | + ->dropForeignKey('id') |
| 1469 | + ->update(); |
| 1470 | + |
| 1471 | + $articlesTable |
| 1472 | + ->addColumn('id2', 'integer') |
| 1473 | + ->addIndex('id', ['unique' => true]) |
| 1474 | + ->changePrimaryKey('id2') |
| 1475 | + ->update(); |
| 1476 | + } |
| 1477 | + |
| 1478 | + /** |
| 1479 | + * Tests that foreign key constraint violations introduced around the table |
| 1480 | + * alteration process (being it implicitly by the process itself or by the user) |
| 1481 | + * will trigger an error accordingly. |
| 1482 | + */ |
| 1483 | + public function testAlterTableDoesViolateForeignKeyConstraintOnTargetTableChange() |
| 1484 | + { |
| 1485 | + $articlesTable = new \Phinx\Db\Table('articles', [], $this->adapter); |
| 1486 | + $articlesTable |
| 1487 | + ->insert(['id' => 1]) |
| 1488 | + ->save(); |
| 1489 | + |
| 1490 | + $commentsTable = new \Phinx\Db\Table('comments', [], $this->adapter); |
| 1491 | + $commentsTable |
| 1492 | + ->addColumn('article_id', 'integer') |
| 1493 | + ->addForeignKey('article_id', 'articles', 'id', [ |
| 1494 | + 'update' => ForeignKey::RESTRICT, |
| 1495 | + 'delete' => ForeignKey::RESTRICT, |
| 1496 | + ]) |
| 1497 | + ->insert(['id' => 1, 'article_id' => 1]) |
| 1498 | + ->save(); |
| 1499 | + |
| 1500 | + $this->assertTrue($this->adapter->hasForeignKey('comments', ['article_id'])); |
| 1501 | + |
| 1502 | + $this->adapter->execute('PRAGMA foreign_keys = OFF'); |
| 1503 | + $this->adapter->execute('DELETE FROM articles'); |
| 1504 | + $this->adapter->execute('PRAGMA foreign_keys = ON'); |
| 1505 | + |
| 1506 | + $this->expectException(\RuntimeException::class); |
| 1507 | + $this->expectExceptionMessage('Integrity constraint violation: FOREIGN KEY constraint on `comments` failed.'); |
| 1508 | + |
| 1509 | + $articlesTable |
| 1510 | + ->addColumn('new_column', 'integer') |
| 1511 | + ->update(); |
| 1512 | + } |
| 1513 | + |
| 1514 | + /** |
| 1515 | + * Tests that foreign key constraint violations introduced around the table |
| 1516 | + * alteration process (being it implicitly by the process itself or by the user) |
| 1517 | + * will trigger an error accordingly. |
| 1518 | + */ |
| 1519 | + public function testAlterTableDoesViolateForeignKeyConstraintOnSourceTableChange() |
| 1520 | + { |
| 1521 | + $adapter = $this |
| 1522 | + ->getMockBuilder(SQLiteAdapter::class) |
| 1523 | + ->setConstructorArgs([SQLITE_DB_CONFIG, new ArrayInput([]), new NullOutput()]) |
| 1524 | + ->onlyMethods(['validateForeignKeys']) |
| 1525 | + ->getMock(); |
| 1526 | + |
| 1527 | + $articlesTable = new \Phinx\Db\Table('articles', [], $adapter); |
| 1528 | + $articlesTable |
| 1529 | + ->insert(['id' => 1]) |
| 1530 | + ->save(); |
| 1531 | + |
| 1532 | + $commentsTable = new \Phinx\Db\Table('comments', [], $adapter); |
| 1533 | + $commentsTable |
| 1534 | + ->addColumn('article_id', 'integer') |
| 1535 | + ->addForeignKey('article_id', 'articles', 'id', [ |
| 1536 | + 'update' => ForeignKey::RESTRICT, |
| 1537 | + 'delete' => ForeignKey::RESTRICT, |
| 1538 | + ]) |
| 1539 | + ->insert(['id' => 1, 'article_id' => 1]) |
| 1540 | + ->save(); |
| 1541 | + |
| 1542 | + $this->assertTrue($adapter->hasForeignKey('comments', ['article_id'])); |
| 1543 | + |
| 1544 | + $adapterReflection = new \ReflectionObject($adapter); |
| 1545 | + $validateForeignKeysReflection = $adapterReflection->getParentClass()->getMethod('validateForeignKeys'); |
| 1546 | + $validateForeignKeysReflection->setAccessible(true); |
| 1547 | + |
| 1548 | + $adapter |
| 1549 | + ->expects($this->once()) |
| 1550 | + ->method('validateForeignKeys') |
| 1551 | + ->willReturnCallback(function (string $tableName) use ($adapter, $validateForeignKeysReflection) { |
| 1552 | + $adapter->execute('PRAGMA foreign_keys = OFF'); |
| 1553 | + $adapter->execute('DELETE FROM articles'); |
| 1554 | + $adapter->execute('PRAGMA foreign_keys = ON'); |
| 1555 | + |
| 1556 | + $validateForeignKeysReflection->invoke($adapter, $tableName); |
| 1557 | + }); |
| 1558 | + |
| 1559 | + $this->expectException(\RuntimeException::class); |
| 1560 | + $this->expectExceptionMessage('Integrity constraint violation: FOREIGN KEY constraint on `comments` failed.'); |
| 1561 | + |
| 1562 | + $commentsTable |
| 1563 | + ->addColumn('new_column', 'integer') |
| 1564 | + ->update(); |
| 1565 | + } |
| 1566 | + |
| 1567 | + /** |
| 1568 | + * Tests that the adapter's foreign key validation does not apply when |
| 1569 | + * the `foreign_keys` pragma is set to `OFF`. |
| 1570 | + */ |
| 1571 | + public function testAlterTableForeignKeyConstraintValidationNotRunningWithDisabledForeignKeys() |
| 1572 | + { |
| 1573 | + $articlesTable = new \Phinx\Db\Table('articles', [], $this->adapter); |
| 1574 | + $articlesTable |
| 1575 | + ->insert(['id' => 1]) |
| 1576 | + ->save(); |
| 1577 | + |
| 1578 | + $commentsTable = new \Phinx\Db\Table('comments', [], $this->adapter); |
| 1579 | + $commentsTable |
| 1580 | + ->addColumn('article_id', 'integer') |
| 1581 | + ->addForeignKey('article_id', 'articles', 'id', [ |
| 1582 | + 'update' => ForeignKey::RESTRICT, |
| 1583 | + 'delete' => ForeignKey::RESTRICT, |
| 1584 | + ]) |
| 1585 | + ->insert(['id' => 1, 'article_id' => 1]) |
| 1586 | + ->save(); |
| 1587 | + |
| 1588 | + $this->assertTrue($this->adapter->hasForeignKey('comments', ['article_id'])); |
| 1589 | + |
| 1590 | + $this->adapter->execute('PRAGMA foreign_keys = OFF'); |
| 1591 | + $this->adapter->execute('DELETE FROM articles'); |
| 1592 | + |
| 1593 | + $noException = false; |
| 1594 | + try { |
| 1595 | + $articlesTable |
| 1596 | + ->addColumn('new_column1', 'integer') |
| 1597 | + ->update(); |
| 1598 | + |
| 1599 | + $noException = true; |
| 1600 | + } finally { |
| 1601 | + $this->assertTrue($noException); |
| 1602 | + } |
| 1603 | + |
| 1604 | + $this->adapter->execute('PRAGMA foreign_keys = ON'); |
| 1605 | + |
| 1606 | + $this->expectException(\RuntimeException::class); |
| 1607 | + $this->expectExceptionMessage('Integrity constraint violation: FOREIGN KEY constraint on `comments` failed.'); |
| 1608 | + |
| 1609 | + $articlesTable |
| 1610 | + ->addColumn('new_column2', 'integer') |
| 1611 | + ->update(); |
| 1612 | + } |
| 1613 | + |
1411 | 1614 | public function testLiteralSupport()
|
1412 | 1615 | {
|
1413 | 1616 | $createQuery = <<<'INPUT'
|
@@ -2366,7 +2569,10 @@ public function testForeignKeyReferenceCorrectAfterChangePrimaryKey()
|
2366 | 2569 | $table->addForeignKey($refTableColumnId, $refTable->getName(), 'id');
|
2367 | 2570 | $table->save();
|
2368 | 2571 |
|
2369 |
| - $refTable->changePrimaryKey($refTableColumnAdditionalId)->save(); |
| 2572 | + $refTable |
| 2573 | + ->addIndex('id', ['unique' => true]) |
| 2574 | + ->changePrimaryKey($refTableColumnAdditionalId) |
| 2575 | + ->save(); |
2370 | 2576 |
|
2371 | 2577 | $this->assertTrue($this->adapter->hasForeignKey($table->getName(), [$refTableColumnId]));
|
2372 | 2578 | $this->assertFalse($this->adapter->hasTable("tmp_{$refTable->getName()}"));
|
|
0 commit comments