diff --git a/src/Platforms/AbstractPlatform.php b/src/Platforms/AbstractPlatform.php index f29f8fef703..f66c8b9d325 100644 --- a/src/Platforms/AbstractPlatform.php +++ b/src/Platforms/AbstractPlatform.php @@ -948,6 +948,26 @@ private function buildCreateTableSQL(Table $table, bool $createForeignKeys): arr } } + if ($createForeignKeys && $table->hasOption(Table::OPTION_EXTRA_CREATE_SQL)) { + if (! is_array($table->getOption(Table::OPTION_EXTRA_CREATE_SQL))) { + throw new InvalidArgumentException(sprintf( + 'Table option "%s" must be an array', + Table::OPTION_EXTRA_CREATE_SQL, + )); + } + + foreach ($table->getOption(Table::OPTION_EXTRA_CREATE_SQL) as $extraSql) { + if (! is_string($extraSql)) { + throw new InvalidArgumentException(sprintf( + 'Table option "%s" must be an array of strings', + Table::OPTION_EXTRA_CREATE_SQL, + )); + } + + $sql[] = $extraSql; + } + } + return $sql; } @@ -971,6 +991,28 @@ public function getCreateTablesSQL(array $tables): array $table->getQuotedName($this), ); } + + if (! $table->hasOption(Table::OPTION_EXTRA_CREATE_SQL)) { + continue; + } + + if (! is_array($table->getOption(Table::OPTION_EXTRA_CREATE_SQL))) { + throw new InvalidArgumentException(sprintf( + 'Table option "%s" must be an array', + Table::OPTION_EXTRA_CREATE_SQL, + )); + } + + foreach ($table->getOption(Table::OPTION_EXTRA_CREATE_SQL) as $extraSql) { + if (! is_string($extraSql)) { + throw new InvalidArgumentException(sprintf( + 'Table option "%s" must be an array of strings', + Table::OPTION_EXTRA_CREATE_SQL, + )); + } + + $sql[] = $extraSql; + } } return $sql; @@ -986,6 +1028,26 @@ public function getDropTablesSQL(array $tables): array $sql = []; foreach ($tables as $table) { + if ($table->hasOption(Table::OPTION_EXTRA_DROP_SQL)) { + if (! is_array($table->getOption(Table::OPTION_EXTRA_DROP_SQL))) { + throw new InvalidArgumentException(sprintf( + 'Table option "%s" must be an array', + Table::OPTION_EXTRA_DROP_SQL, + )); + } + + foreach ($table->getOption(Table::OPTION_EXTRA_DROP_SQL) as $extraSql) { + if (! is_string($extraSql)) { + throw new InvalidArgumentException(sprintf( + 'Table option "%s" must be an array of strings', + Table::OPTION_EXTRA_DROP_SQL, + )); + } + + $sql[] = $extraSql; + } + } + foreach ($table->getForeignKeys() as $foreignKey) { $sql[] = $this->getDropForeignKeySQL( $foreignKey->getQuotedName($this), diff --git a/src/Platforms/SQLitePlatform.php b/src/Platforms/SQLitePlatform.php index 6fcce9a285a..8d9b8a5a994 100644 --- a/src/Platforms/SQLitePlatform.php +++ b/src/Platforms/SQLitePlatform.php @@ -35,6 +35,8 @@ use function count; use function explode; use function implode; +use function is_array; +use function is_string; use function sprintf; use function str_replace; use function strpos; @@ -593,6 +595,30 @@ public function getDropTablesSQL(array $tables): array { $sql = []; + foreach ($tables as $table) { + if (! $table->hasOption(Table::OPTION_EXTRA_DROP_SQL)) { + continue; + } + + if (! is_array($table->getOption(Table::OPTION_EXTRA_DROP_SQL))) { + throw new InvalidArgumentException(sprintf( + 'Table option "%s" must be an array', + Table::OPTION_EXTRA_DROP_SQL, + )); + } + + foreach ($table->getOption(Table::OPTION_EXTRA_DROP_SQL) as $extraSql) { + if (! is_string($extraSql)) { + throw new InvalidArgumentException(sprintf( + 'Table option "%s" must be an array of strings', + Table::OPTION_EXTRA_DROP_SQL, + )); + } + + $sql[] = $extraSql; + } + } + foreach ($tables as $table) { $sql[] = $this->getDropTableSQL($table->getQuotedName($this)); } diff --git a/src/Schema/Table.php b/src/Schema/Table.php index e41fcc6a5f1..0079ea822d3 100644 --- a/src/Schema/Table.php +++ b/src/Schema/Table.php @@ -43,6 +43,9 @@ */ class Table extends AbstractNamedObject { + public const OPTION_EXTRA_CREATE_SQL = 'extra_create_sql'; + public const OPTION_EXTRA_DROP_SQL = 'extra_drop_sql'; + /** @var Column[] */ protected array $_columns = []; diff --git a/tests/Platforms/AbstractPlatformTestCase.php b/tests/Platforms/AbstractPlatformTestCase.php index c8c698d85ee..9bc734d0782 100644 --- a/tests/Platforms/AbstractPlatformTestCase.php +++ b/tests/Platforms/AbstractPlatformTestCase.php @@ -1279,4 +1279,209 @@ public static function getEnumDeclarationSQLWithInvalidValuesProvider(): array "field 'values' is an empty array" => [['values' => []]], ]; } + + public function testGetCreateTableSQLWithExtraCreateSQL(): void + { + $table = Table::editor() + ->setUnquotedName('test') + ->setColumns( + Column::editor() + ->setUnquotedName('id') + ->setTypeName(Types::INTEGER) + ->setAutoincrement(true) + ->create(), + ) + ->setPrimaryKeyConstraint( + PrimaryKeyConstraint::editor() + ->setUnquotedColumnNames('id') + ->create(), + ) + ->setOptions([ + Table::OPTION_EXTRA_CREATE_SQL => [ + 'CREATE INDEX idx_test ON test (id)', + 'CREATE VIEW test_view AS SELECT * FROM test', + ], + ]) + ->create(); + + $sql = $this->platform->getCreateTableSQL($table); + + // Should contain the CREATE TABLE statement + self::assertNotEmpty($sql); + self::assertStringContainsString('CREATE TABLE', $sql[0]); + // Should contain the extra SQL statements + self::assertContains('CREATE INDEX idx_test ON test (id)', $sql); + self::assertContains('CREATE VIEW test_view AS SELECT * FROM test', $sql); + } + + public function testGetCreateTableSQLWithExtraCreateSQLNonArrayThrowsException(): void + { + $table = Table::editor() + ->setUnquotedName('test') + ->setColumns( + Column::editor() + ->setUnquotedName('id') + ->setTypeName(Types::INTEGER) + ->create(), + ) + ->setPrimaryKeyConstraint( + PrimaryKeyConstraint::editor() + ->setUnquotedColumnNames('id') + ->create(), + ) + ->setOptions([Table::OPTION_EXTRA_CREATE_SQL => 'not an array']) + ->create(); + + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage(sprintf('Table option "%s" must be an array', Table::OPTION_EXTRA_CREATE_SQL)); + + $this->platform->getCreateTableSQL($table); + } + + public function testGetCreateTablesSQLWithExtraCreateSQL(): void + { + $table1 = Table::editor() + ->setUnquotedName('test1') + ->setColumns( + Column::editor() + ->setUnquotedName('id') + ->setTypeName(Types::INTEGER) + ->setAutoincrement(true) + ->create(), + ) + ->setPrimaryKeyConstraint( + PrimaryKeyConstraint::editor() + ->setUnquotedColumnNames('id') + ->create(), + ) + ->setOptions([Table::OPTION_EXTRA_CREATE_SQL => ['CREATE INDEX idx_test1 ON test1 (id)']]) + ->create(); + + $table2 = Table::editor() + ->setUnquotedName('test2') + ->setColumns( + Column::editor() + ->setUnquotedName('id') + ->setTypeName(Types::INTEGER) + ->setAutoincrement(true) + ->create(), + ) + ->setPrimaryKeyConstraint( + PrimaryKeyConstraint::editor() + ->setUnquotedColumnNames('id') + ->create(), + ) + ->setOptions([Table::OPTION_EXTRA_CREATE_SQL => ['CREATE INDEX idx_test2 ON test2 (id)']]) + ->create(); + + $sql = $this->platform->getCreateTablesSQL([$table1, $table2]); + + self::assertContains('CREATE INDEX idx_test1 ON test1 (id)', $sql); + self::assertContains('CREATE INDEX idx_test2 ON test2 (id)', $sql); + } + + public function testGetCreateTablesSQLWithExtraCreateSQLNonArrayThrowsException(): void + { + $table = Table::editor() + ->setUnquotedName('test') + ->setColumns( + Column::editor() + ->setUnquotedName('id') + ->setTypeName(Types::INTEGER) + ->create(), + ) + ->setPrimaryKeyConstraint( + PrimaryKeyConstraint::editor() + ->setUnquotedColumnNames('id') + ->create(), + ) + ->setOptions([Table::OPTION_EXTRA_CREATE_SQL => 'not an array']) + ->create(); + + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage(sprintf('Table option "%s" must be an array', Table::OPTION_EXTRA_CREATE_SQL)); + + $this->platform->getCreateTablesSQL([$table]); + } + + public function testGetDropTablesSQLWithExtraDropSQL(): void + { + $table1 = Table::editor() + ->setUnquotedName('test1') + ->setColumns( + Column::editor() + ->setUnquotedName('id') + ->setTypeName(Types::INTEGER) + ->create(), + ) + ->setOptions([ + Table::OPTION_EXTRA_DROP_SQL => [ + 'DROP VIEW IF EXISTS test1_view', + 'DROP TRIGGER IF EXISTS test1_trigger', + ], + ]) + ->create(); + + $table2 = Table::editor() + ->setUnquotedName('test2') + ->setColumns( + Column::editor() + ->setUnquotedName('id') + ->setTypeName(Types::INTEGER) + ->create(), + ) + ->setOptions([Table::OPTION_EXTRA_DROP_SQL => ['DROP VIEW IF EXISTS test2_view']]) + ->create(); + + $sql = $this->platform->getDropTablesSQL([$table1, $table2]); + + // Verify extra drop SQL is present + self::assertContains('DROP VIEW IF EXISTS test1_view', $sql); + self::assertContains('DROP TRIGGER IF EXISTS test1_trigger', $sql); + self::assertContains('DROP VIEW IF EXISTS test2_view', $sql); + + // Verify DROP TABLE statements are present + $dropTable1Sql = 'DROP TABLE ' . $table1->getQuotedName($this->platform); + $dropTable2Sql = 'DROP TABLE ' . $table2->getQuotedName($this->platform); + self::assertContains($dropTable1Sql, $sql); + self::assertContains($dropTable2Sql, $sql); + + // Verify all expected SQL statements are present + // The implementation guarantees extra drop SQL comes before DROP TABLE statements + $hasDropTable1 = false; + $hasDropTable2 = false; + foreach ($sql as $statement) { + if ($statement === $dropTable1Sql) { + $hasDropTable1 = true; + } + + if ($statement !== $dropTable2Sql) { + continue; + } + + $hasDropTable2 = true; + } + + self::assertTrue($hasDropTable1); + self::assertTrue($hasDropTable2); + } + + public function testGetDropTablesSQLWithExtraDropSQLNonArrayThrowsException(): void + { + $table = Table::editor() + ->setUnquotedName('test') + ->setColumns( + Column::editor() + ->setUnquotedName('id') + ->setTypeName(Types::INTEGER) + ->create(), + ) + ->setOptions([Table::OPTION_EXTRA_DROP_SQL => 'not an array']) + ->create(); + + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage(sprintf('Table option "%s" must be an array', Table::OPTION_EXTRA_DROP_SQL)); + + $this->platform->getDropTablesSQL([$table]); + } }