From 15c0e70d547bb20c38f92ef3de9af4a76d3ea850 Mon Sep 17 00:00:00 2001 From: Kit Loong Date: Thu, 27 Feb 2025 20:53:09 +0800 Subject: [PATCH 01/14] Remove unsupported spatial column types --- src/Database/Models/MySQL/MySQLColumn.php | 28 ++++++++----------- src/Database/Models/MySQL/MySQLColumnType.php | 21 +++++++------- src/Enum/Migrations/Method/ColumnType.php | 7 ----- src/MigrationsGeneratorServiceProvider.php | 7 ----- 4 files changed, 21 insertions(+), 42 deletions(-) diff --git a/src/Database/Models/MySQL/MySQLColumn.php b/src/Database/Models/MySQL/MySQLColumn.php index 3026ae26..1ba00a4f 100644 --- a/src/Database/Models/MySQL/MySQLColumn.php +++ b/src/Database/Models/MySQL/MySQLColumn.php @@ -87,14 +87,7 @@ public function __construct(string $table, array $column) case ColumnType::GEOGRAPHY: case ColumnType::GEOMETRY: - case ColumnType::GEOMETRY_COLLECTION: - case ColumnType::LINE_STRING: - case ColumnType::MULTI_LINE_STRING: - case ColumnType::POINT: - case ColumnType::MULTI_POINT: - case ColumnType::POLYGON: - case ColumnType::MULTI_POLYGON: - $this->setRealSpatialColumn(); + $this->setRealSpatialColumn($column['type_name']); break; default: @@ -248,34 +241,35 @@ private function setStoredDefinition(): void /** * Set to geometry or geography. */ - private function setRealSpatialColumn(): void + private function setRealSpatialColumn(string $typeName): void { - switch ($this->type) { - case ColumnType::GEOMETRY_COLLECTION: + switch ($typeName) { + case 'geometrycollection': + case 'geomcollection': $this->spatialSubType = 'geometryCollection'; break; - case ColumnType::LINE_STRING: + case 'linestring': $this->spatialSubType = 'lineString'; break; - case ColumnType::MULTI_LINE_STRING: + case 'multilinestring': $this->spatialSubType = 'multiLineString'; break; - case ColumnType::POINT: + case 'point': $this->spatialSubType = 'point'; break; - case ColumnType::MULTI_POINT: + case 'multipoint': $this->spatialSubType = 'multiPoint'; break; - case ColumnType::POLYGON: + case 'polygon': $this->spatialSubType = 'polygon'; break; - case ColumnType::MULTI_POLYGON: + case 'multipolygon': $this->spatialSubType = 'multiPolygon'; break; diff --git a/src/Database/Models/MySQL/MySQLColumnType.php b/src/Database/Models/MySQL/MySQLColumnType.php index d749a1b4..e5be9da8 100644 --- a/src/Database/Models/MySQL/MySQLColumnType.php +++ b/src/Database/Models/MySQL/MySQLColumnType.php @@ -47,20 +47,19 @@ class MySQLColumnType extends DatabaseColumnType 'varchar' => ColumnType::STRING, 'year' => ColumnType::YEAR, + 'geomcollection' => ColumnType::GEOMETRY, + 'linestring' => ColumnType::GEOMETRY, + 'multilinestring' => ColumnType::GEOMETRY, + 'point' => ColumnType::GEOMETRY, + 'multipoint' => ColumnType::GEOMETRY, + 'polygon' => ColumnType::GEOMETRY, + 'multipolygon' => ColumnType::GEOMETRY, + // For MariaDB 'uuid' => ColumnType::UUID, - // Removed from Laravel v11 - 'geomcollection' => ColumnType::GEOMETRY_COLLECTION, - 'linestring' => ColumnType::LINE_STRING, - 'multilinestring' => ColumnType::MULTI_LINE_STRING, - 'point' => ColumnType::POINT, - 'multipoint' => ColumnType::MULTI_POINT, - 'polygon' => ColumnType::POLYGON, - 'multipolygon' => ColumnType::MULTI_POLYGON, - - // For MariaDB - 'geometrycollection' => ColumnType::GEOMETRY_COLLECTION, + // For MariaDB and MySQL57 + 'geometrycollection' => ColumnType::GEOMETRY, ]; /** diff --git a/src/Enum/Migrations/Method/ColumnType.php b/src/Enum/Migrations/Method/ColumnType.php index a1f6b1e2..9f5883e3 100644 --- a/src/Enum/Migrations/Method/ColumnType.php +++ b/src/Enum/Migrations/Method/ColumnType.php @@ -23,23 +23,16 @@ enum ColumnType: string implements MethodName case FLOAT = 'float'; case GEOGRAPHY = 'geography'; case GEOMETRY = 'geometry'; - case GEOMETRY_COLLECTION = 'geometryCollection'; case INCREMENTS = 'increments'; case INTEGER = 'integer'; case IP_ADDRESS = 'ipAddress'; case JSON = 'json'; case JSONB = 'jsonb'; - case LINE_STRING = 'lineString'; case LONG_TEXT = 'longText'; case MAC_ADDRESS = 'macAddress'; case MEDIUM_INCREMENTS = 'mediumIncrements'; case MEDIUM_INTEGER = 'mediumInteger'; case MEDIUM_TEXT = 'mediumText'; - case MULTI_LINE_STRING = 'multiLineString'; - case MULTI_POINT = 'multiPoint'; - case MULTI_POLYGON = 'multiPolygon'; - case POINT = 'point'; - case POLYGON = 'polygon'; case REMEMBER_TOKEN = 'rememberToken'; case SET = 'set'; case SMALL_INCREMENTS = 'smallIncrements'; diff --git a/src/MigrationsGeneratorServiceProvider.php b/src/MigrationsGeneratorServiceProvider.php index e8d3195d..7687c202 100644 --- a/src/MigrationsGeneratorServiceProvider.php +++ b/src/MigrationsGeneratorServiceProvider.php @@ -189,13 +189,6 @@ protected function registerColumnTypeGenerator(): void [ ColumnType::GEOGRAPHY, ColumnType::GEOMETRY, - ColumnType::GEOMETRY_COLLECTION, - ColumnType::LINE_STRING, - ColumnType::MULTI_LINE_STRING, - ColumnType::POINT, - ColumnType::MULTI_POINT, - ColumnType::MULTI_POLYGON, - ColumnType::POLYGON, ] as $columnType ) { $this->columnTypeSingleton($columnType, SpatialColumn::class); From f6a162bb49f4e0652786cb44c9e779ffd88fe5ea Mon Sep 17 00:00:00 2001 From: Kit Loong Date: Thu, 27 Feb 2025 20:53:20 +0800 Subject: [PATCH 02/14] Fix version compare --- .../2020_03_21_000000_expected_create_all_columns_table.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/resources/database/migrations/general/2020_03_21_000000_expected_create_all_columns_table.php b/tests/resources/database/migrations/general/2020_03_21_000000_expected_create_all_columns_table.php index 99219cf3..fab17780 100644 --- a/tests/resources/database/migrations/general/2020_03_21_000000_expected_create_all_columns_table.php +++ b/tests/resources/database/migrations/general/2020_03_21_000000_expected_create_all_columns_table.php @@ -80,7 +80,7 @@ public function up() if ( !in_array(DB::getDriverName(), [Driver::MARIADB->value, Driver::MYSQL->value]) || - version_compare(DB::getPdo()->getAttribute(PDO::ATTR_SERVER_VERSION), '5.7', 'eq') + version_compare(DB::getPdo()->getAttribute(PDO::ATTR_SERVER_VERSION), '5.8', '>') ) { $table->geography('geography'); $table->geography('geographyGeometryCollection', 'geometryCollection'); From 6bb1f520041598786986b619b160cb167732c32a Mon Sep 17 00:00:00 2001 From: Kit Loong Date: Thu, 27 Feb 2025 21:10:01 +0800 Subject: [PATCH 03/14] Remove unused methods --- src/Repositories/MySQLRepository.php | 65 ---------------------------- src/Repositories/PgSQLRepository.php | 25 ----------- 2 files changed, 90 deletions(-) diff --git a/src/Repositories/MySQLRepository.php b/src/Repositories/MySQLRepository.php index cbdc9d6f..549a84e7 100644 --- a/src/Repositories/MySQLRepository.php +++ b/src/Repositories/MySQLRepository.php @@ -42,30 +42,6 @@ public function isOnUpdateCurrentTimestamp(string $table, string $column): bool return !($result === null); } - /** - * Get the virtual column definition by table and column name. - * - * @param string $table Table name. - * @param string $column Column name. - * @return string|null The virtual column definition. NULL if not found. - */ - public function getVirtualDefinition(string $table, string $column): ?string - { - return $this->getGenerationExpression($table, $column, 'VIRTUAL GENERATED'); - } - - /** - * Get the stored column definition by table and column name. - * - * @param string $table Table name. - * @param string $column Column name. - * @return string|null The stored column definition. NULL if not found. - */ - public function getStoredDefinition(string $table, string $column): ?string - { - return $this->getGenerationExpression($table, $column, 'STORED GENERATED'); - } - /** * Get a list of stored procedures. * @@ -141,45 +117,4 @@ private function getProcedure(string $procedure): mixed { return DB::selectOne("SHOW CREATE PROCEDURE $procedure"); } - - /** - * Get the column GENERATION_EXPRESSION when EXTRA is 'VIRTUAL GENERATED' or 'STORED GENERATED'. - * - * @param 'VIRTUAL GENERATED'|'STORED GENERATED' $extra - */ - private function getGenerationExpression(string $table, string $column, string $extra): ?string - { - try { - $definition = DB::selectOne( - "SELECT GENERATION_EXPRESSION - FROM information_schema.COLUMNS - WHERE TABLE_NAME = '$table' - AND COLUMN_NAME = '$column' - AND EXTRA = '$extra'", - ); - } catch (QueryException $exception) { - // Check if error caused by missing column 'GENERATION_EXPRESSION'. - // The column is introduced since MySQL 5.7 and MariaDB 10.2.5. - // @see https://mariadb.com/kb/en/information-schema-columns-table/ - // @see https://dev.mysql.com/doc/refman/5.7/en/information-schema-columns-table.html - if ( - Str::contains( - $exception->getMessage(), - "SQLSTATE[42S22]: Column not found: 1054 Unknown column 'GENERATION_EXPRESSION'", - true, - ) - ) { - return null; - } - - throw $exception; - } - - if ($definition === null) { - return null; - } - - $definitionArr = array_change_key_case((array) $definition); - return $definitionArr['generation_expression'] !== '' ? $definitionArr['generation_expression'] : null; - } } diff --git a/src/Repositories/PgSQLRepository.php b/src/Repositories/PgSQLRepository.php index 5d479925..695215a2 100644 --- a/src/Repositories/PgSQLRepository.php +++ b/src/Repositories/PgSQLRepository.php @@ -104,29 +104,4 @@ public function getProcedures(): Collection return $list; } - - /** - * Get the stored column definition by table and column name. - * - * @param string $table Table name. - * @param string $column Column name. - * @return string|null The stored column definition. NULL if not found. - */ - public function getStoredDefinition(string $table, string $column): ?string - { - $definition = DB::selectOne( - "SELECT generation_expression - FROM information_schema.columns - WHERE table_name = '$table' - AND column_name = '$column' - AND is_generated = 'ALWAYS'", - ); - - if ($definition === null) { - return null; - } - - $definitionArr = array_change_key_case((array) $definition); - return $definitionArr['generation_expression'] !== '' ? $definitionArr['generation_expression'] : null; - } } From ae63d601e6c1ece7556e3e2318f7dc23ddb59645 Mon Sep 17 00:00:00 2001 From: Kit Loong Date: Thu, 27 Feb 2025 21:20:50 +0800 Subject: [PATCH 04/14] Use `defineEnvironment` --- tests/Feature/FeatureTestCase.php | 2 +- tests/Feature/MariaDB/MariaDBTestCase.php | 4 ++-- tests/Feature/MariaDB/TablePrefixTest.php | 4 ++-- tests/Feature/MySQL57/DBConnectionTest.php | 4 ++-- tests/Feature/MySQL57/MySQL57TestCase.php | 4 ++-- tests/Feature/MySQL57/StackedCommandTest.php | 4 ++-- tests/Feature/MySQL57/TablePrefixTest.php | 4 ++-- tests/Feature/MySQL8/MySQL8TestCase.php | 4 ++-- tests/Feature/MySQL8/TablePrefixTest.php | 4 ++-- tests/Feature/PgSQL/PgSQLTestCase.php | 4 ++-- tests/Feature/PgSQL/TablePrefixTest.php | 4 ++-- tests/Feature/SQLSrv/SQLSrvTestCase.php | 4 ++-- tests/Feature/SQLSrv/TablePrefixTest.php | 4 ++-- tests/Feature/SQLite/SQLiteTestCase.php | 4 ++-- tests/TestCase.php | 4 ++-- 15 files changed, 29 insertions(+), 29 deletions(-) diff --git a/tests/Feature/FeatureTestCase.php b/tests/Feature/FeatureTestCase.php index 2446f5b6..77b29cff 100644 --- a/tests/Feature/FeatureTestCase.php +++ b/tests/Feature/FeatureTestCase.php @@ -107,7 +107,7 @@ protected function rollbackMigrationsFrom(string $connection, string $path): voi * Generate migration files to $this->storageMigrations() * * @param array $options - * @see \KitLoong\MigrationsGenerator\Tests\Feature\FeatureTestCase::getEnvironmentSetUp() + * @see \KitLoong\MigrationsGenerator\Tests\Feature\FeatureTestCase::defineEnvironment() */ protected function generateMigrations(array $options = []): void { diff --git a/tests/Feature/MariaDB/MariaDBTestCase.php b/tests/Feature/MariaDB/MariaDBTestCase.php index 6833a12f..db1a542a 100644 --- a/tests/Feature/MariaDB/MariaDBTestCase.php +++ b/tests/Feature/MariaDB/MariaDBTestCase.php @@ -12,9 +12,9 @@ abstract class MariaDBTestCase extends FeatureTestCase /** * @inheritDoc */ - protected function getEnvironmentSetUp($app): void + protected function defineEnvironment($app): void { - parent::getEnvironmentSetUp($app); + parent::defineEnvironment($app); $app['config']->set('database.default', 'mariadb'); $app['config']->set('database.connections.mariadb', [ diff --git a/tests/Feature/MariaDB/TablePrefixTest.php b/tests/Feature/MariaDB/TablePrefixTest.php index 107a1e02..23b8e5b1 100644 --- a/tests/Feature/MariaDB/TablePrefixTest.php +++ b/tests/Feature/MariaDB/TablePrefixTest.php @@ -20,9 +20,9 @@ public function testTablePrefix(): void /** * @inheritDoc */ - protected function getEnvironmentSetUp($app): void + protected function defineEnvironment($app): void { - parent::getEnvironmentSetUp($app); + parent::defineEnvironment($app); $app['config']->set('database.connections.mariadb.prefix', 'prefix_'); } diff --git a/tests/Feature/MySQL57/DBConnectionTest.php b/tests/Feature/MySQL57/DBConnectionTest.php index f8a64bd3..bd14a6c4 100644 --- a/tests/Feature/MySQL57/DBConnectionTest.php +++ b/tests/Feature/MySQL57/DBConnectionTest.php @@ -81,9 +81,9 @@ public function testLogMigrationToAnotherSource(): void /** * @inheritDoc */ - protected function getEnvironmentSetUp($app): void + protected function defineEnvironment($app): void { - parent::getEnvironmentSetUp($app); + parent::defineEnvironment($app); $app['config']->set('database.default', 'mysql8'); $app['config']->set('database.connections.mysql8', [ diff --git a/tests/Feature/MySQL57/MySQL57TestCase.php b/tests/Feature/MySQL57/MySQL57TestCase.php index 0fe435c6..a88820bc 100644 --- a/tests/Feature/MySQL57/MySQL57TestCase.php +++ b/tests/Feature/MySQL57/MySQL57TestCase.php @@ -12,9 +12,9 @@ abstract class MySQL57TestCase extends FeatureTestCase /** * @inheritDoc */ - protected function getEnvironmentSetUp($app): void + protected function defineEnvironment($app): void { - parent::getEnvironmentSetUp($app); + parent::defineEnvironment($app); $app['config']->set('database.default', 'mysql57'); $app['config']->set('database.connections.mysql57', [ diff --git a/tests/Feature/MySQL57/StackedCommandTest.php b/tests/Feature/MySQL57/StackedCommandTest.php index 9aa0df31..484a2b0e 100644 --- a/tests/Feature/MySQL57/StackedCommandTest.php +++ b/tests/Feature/MySQL57/StackedCommandTest.php @@ -60,9 +60,9 @@ public function testRunAsCall(): void /** * @inheritDoc */ - protected function getEnvironmentSetUp($app): void + protected function defineEnvironment($app): void { - parent::getEnvironmentSetUp($app); + parent::defineEnvironment($app); $app['config']->set('database.connections.migration2', [ 'driver' => 'mysql', diff --git a/tests/Feature/MySQL57/TablePrefixTest.php b/tests/Feature/MySQL57/TablePrefixTest.php index a72a8d66..1d40a4e6 100644 --- a/tests/Feature/MySQL57/TablePrefixTest.php +++ b/tests/Feature/MySQL57/TablePrefixTest.php @@ -20,9 +20,9 @@ public function testTablePrefix(): void /** * @inheritDoc */ - protected function getEnvironmentSetUp($app): void + protected function defineEnvironment($app): void { - parent::getEnvironmentSetUp($app); + parent::defineEnvironment($app); $app['config']->set('database.connections.mysql57.prefix', 'prefix_'); } diff --git a/tests/Feature/MySQL8/MySQL8TestCase.php b/tests/Feature/MySQL8/MySQL8TestCase.php index c2963c99..134bff00 100644 --- a/tests/Feature/MySQL8/MySQL8TestCase.php +++ b/tests/Feature/MySQL8/MySQL8TestCase.php @@ -12,9 +12,9 @@ abstract class MySQL8TestCase extends FeatureTestCase /** * @inheritDoc */ - protected function getEnvironmentSetUp($app): void + protected function defineEnvironment($app): void { - parent::getEnvironmentSetUp($app); + parent::defineEnvironment($app); $app['config']->set('database.default', 'mysql8'); $app['config']->set('database.connections.mysql8', [ diff --git a/tests/Feature/MySQL8/TablePrefixTest.php b/tests/Feature/MySQL8/TablePrefixTest.php index 2b13639b..04222f64 100644 --- a/tests/Feature/MySQL8/TablePrefixTest.php +++ b/tests/Feature/MySQL8/TablePrefixTest.php @@ -20,9 +20,9 @@ public function testTablePrefix(): void /** * @inheritDoc */ - protected function getEnvironmentSetUp($app): void + protected function defineEnvironment($app): void { - parent::getEnvironmentSetUp($app); + parent::defineEnvironment($app); $app['config']->set('database.connections.mysql8.prefix', 'prefix_'); } diff --git a/tests/Feature/PgSQL/PgSQLTestCase.php b/tests/Feature/PgSQL/PgSQLTestCase.php index f4419b81..b5b6dcc5 100644 --- a/tests/Feature/PgSQL/PgSQLTestCase.php +++ b/tests/Feature/PgSQL/PgSQLTestCase.php @@ -12,9 +12,9 @@ abstract class PgSQLTestCase extends FeatureTestCase /** * @inheritDoc */ - protected function getEnvironmentSetUp($app): void + protected function defineEnvironment($app): void { - parent::getEnvironmentSetUp($app); + parent::defineEnvironment($app); $app['config']->set('database.default', 'pgsql'); $app['config']->set('database.connections.pgsql', [ diff --git a/tests/Feature/PgSQL/TablePrefixTest.php b/tests/Feature/PgSQL/TablePrefixTest.php index 83f529aa..9f615734 100644 --- a/tests/Feature/PgSQL/TablePrefixTest.php +++ b/tests/Feature/PgSQL/TablePrefixTest.php @@ -20,9 +20,9 @@ public function testTablePrefix(): void /** * @inheritDoc */ - protected function getEnvironmentSetUp($app): void + protected function defineEnvironment($app): void { - parent::getEnvironmentSetUp($app); + parent::defineEnvironment($app); $app['config']->set('database.connections.pgsql.prefix', 'prefix_'); } diff --git a/tests/Feature/SQLSrv/SQLSrvTestCase.php b/tests/Feature/SQLSrv/SQLSrvTestCase.php index 9c94435a..9800cf0e 100644 --- a/tests/Feature/SQLSrv/SQLSrvTestCase.php +++ b/tests/Feature/SQLSrv/SQLSrvTestCase.php @@ -12,9 +12,9 @@ abstract class SQLSrvTestCase extends FeatureTestCase /** * @inheritDoc */ - protected function getEnvironmentSetUp($app): void + protected function defineEnvironment($app): void { - parent::getEnvironmentSetUp($app); + parent::defineEnvironment($app); $app['config']->set('database.default', 'sqlsrv'); $app['config']->set('database.connections.sqlsrv', [ diff --git a/tests/Feature/SQLSrv/TablePrefixTest.php b/tests/Feature/SQLSrv/TablePrefixTest.php index 1b8489fc..d7d390c6 100644 --- a/tests/Feature/SQLSrv/TablePrefixTest.php +++ b/tests/Feature/SQLSrv/TablePrefixTest.php @@ -20,9 +20,9 @@ public function testTablePrefix(): void /** * @inheritDoc */ - protected function getEnvironmentSetUp($app): void + protected function defineEnvironment($app): void { - parent::getEnvironmentSetUp($app); + parent::defineEnvironment($app); $app['config']->set('database.connections.sqlsrv.prefix', 'prefix_'); } diff --git a/tests/Feature/SQLite/SQLiteTestCase.php b/tests/Feature/SQLite/SQLiteTestCase.php index 0dc6bdf7..e2ec6beb 100644 --- a/tests/Feature/SQLite/SQLiteTestCase.php +++ b/tests/Feature/SQLite/SQLiteTestCase.php @@ -11,9 +11,9 @@ abstract class SQLiteTestCase extends FeatureTestCase /** * @inheritDoc */ - protected function getEnvironmentSetUp($app): void + protected function defineEnvironment($app): void { - parent::getEnvironmentSetUp($app); + parent::defineEnvironment($app); touch((string) env('SQLITE_DATABASE')); diff --git a/tests/TestCase.php b/tests/TestCase.php index cdb5697c..9fa7eea7 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -49,9 +49,9 @@ protected function getPackageProviders($app) /** * @inheritDoc */ - protected function getEnvironmentSetUp($app): void + protected function defineEnvironment($app): void { - parent::getEnvironmentSetUp($app); + parent::defineEnvironment($app); app()->setBasePath(__DIR__ . '/../'); } From 0a55612d23563381d77f9b0d663a7c5199196077 Mon Sep 17 00:00:00 2001 From: Kit Loong Date: Thu, 27 Feb 2025 21:44:19 +0800 Subject: [PATCH 05/14] Add tests --- .../Entities/MariaDB/CheckConstraintTest.php | 28 +++++++++++++++ .../Entities/MySQL/ShowColumnTest.php | 29 +++++++++++++++ .../Entities/PgSQL/IndexDefinitionTest.php | 19 ++++++++++ .../Entities/ProcedureDefinitionTest.php | 17 +++++++++ .../Entities/SQLSrv/ColumnDefinitionTest.php | 36 +++++++++++++++++++ 5 files changed, 129 insertions(+) create mode 100644 tests/Unit/Repositories/Entities/MariaDB/CheckConstraintTest.php create mode 100644 tests/Unit/Repositories/Entities/MySQL/ShowColumnTest.php create mode 100644 tests/Unit/Repositories/Entities/PgSQL/IndexDefinitionTest.php create mode 100644 tests/Unit/Repositories/Entities/ProcedureDefinitionTest.php create mode 100644 tests/Unit/Repositories/Entities/SQLSrv/ColumnDefinitionTest.php diff --git a/tests/Unit/Repositories/Entities/MariaDB/CheckConstraintTest.php b/tests/Unit/Repositories/Entities/MariaDB/CheckConstraintTest.php new file mode 100644 index 00000000..2093d765 --- /dev/null +++ b/tests/Unit/Repositories/Entities/MariaDB/CheckConstraintTest.php @@ -0,0 +1,28 @@ + 'def', // should convert to lowercase + 'constraint_schema' => 'schema', + 'table_name' => 'table', + 'constraint_name' => 'name', + 'level' => 'level', + 'check_clause' => 'clause', + ]); + + $this->assertEquals('def', $checkConstraint->getConstraintCatalog()); + $this->assertEquals('schema', $checkConstraint->getConstraintSchema()); + $this->assertEquals('table', $checkConstraint->getTableName()); + $this->assertEquals('name', $checkConstraint->getConstraintName()); + $this->assertEquals('level', $checkConstraint->getLevel()); + $this->assertEquals('clause', $checkConstraint->getCheckClause()); + } +} diff --git a/tests/Unit/Repositories/Entities/MySQL/ShowColumnTest.php b/tests/Unit/Repositories/Entities/MySQL/ShowColumnTest.php new file mode 100644 index 00000000..8a0cf99b --- /dev/null +++ b/tests/Unit/Repositories/Entities/MySQL/ShowColumnTest.php @@ -0,0 +1,29 @@ + 'field', + 'Type' => 'type', + 'Null' => 'null', + 'Key' => 'key', + 'Default' => 'default', + 'Extra' => 'extra', + ]); + + $this->assertEquals('field', $showColumn->getField()); + $this->assertEquals('type', $showColumn->getType()); + $this->assertEquals('null', $showColumn->getNull()); + $this->assertEquals('key', $showColumn->getKey()); + $this->assertEquals('default', $showColumn->getDefault()); + $this->assertEquals('extra', $showColumn->getExtra()); + } +} diff --git a/tests/Unit/Repositories/Entities/PgSQL/IndexDefinitionTest.php b/tests/Unit/Repositories/Entities/PgSQL/IndexDefinitionTest.php new file mode 100644 index 00000000..96747fcc --- /dev/null +++ b/tests/Unit/Repositories/Entities/PgSQL/IndexDefinitionTest.php @@ -0,0 +1,19 @@ +assertEquals('table', $indexDefinition->getTableName()); + $this->assertEquals('name', $indexDefinition->getIndexName()); + $this->assertEquals('def', $indexDefinition->getIndexDef()); + } +} diff --git a/tests/Unit/Repositories/Entities/ProcedureDefinitionTest.php b/tests/Unit/Repositories/Entities/ProcedureDefinitionTest.php new file mode 100644 index 00000000..b19956f2 --- /dev/null +++ b/tests/Unit/Repositories/Entities/ProcedureDefinitionTest.php @@ -0,0 +1,17 @@ +assertEquals('name', $procedureDefinition->getName()); + $this->assertEquals('definition', $procedureDefinition->getDefinition()); + } +} diff --git a/tests/Unit/Repositories/Entities/SQLSrv/ColumnDefinitionTest.php b/tests/Unit/Repositories/Entities/SQLSrv/ColumnDefinitionTest.php new file mode 100644 index 00000000..91b65623 --- /dev/null +++ b/tests/Unit/Repositories/Entities/SQLSrv/ColumnDefinitionTest.php @@ -0,0 +1,36 @@ + 'id', + 'type' => 'int', + 'length' => 11, + 'notnull' => true, + 'default' => 'default', + 'scale' => 0, + 'precision' => 10, + 'autoincrement' => true, + 'collation' => 'collation', + 'comment' => 'Primary key', + ]); + + $this->assertEquals('id', $columnDefinition->getName()); + $this->assertEquals('int', $columnDefinition->getType()); + $this->assertEquals(11, $columnDefinition->getLength()); + $this->assertTrue($columnDefinition->isNotnull()); + $this->assertEquals('default', $columnDefinition->getDefault()); + $this->assertEquals(0, $columnDefinition->getScale()); + $this->assertEquals(10, $columnDefinition->getPrecision()); + $this->assertTrue($columnDefinition->isAutoincrement()); + $this->assertEquals('collation', $columnDefinition->getCollation()); + $this->assertEquals('Primary key', $columnDefinition->getComment()); + } +} From f955e64bb528702478be11ea81b7a20eb3baa601 Mon Sep 17 00:00:00 2001 From: Kit Loong Date: Thu, 5 Dec 2024 00:05:03 +0800 Subject: [PATCH 06/14] Create documentation --- .gitattributes | 1 + .github/workflows/docs.yml | 39 +++++++ .gitignore | 2 + README.md | 76 +++++-------- docs/index.md | 62 +++++++++++ docs/options/connection.md | 36 +++++++ docs/options/date.md | 25 +++++ docs/options/default-fk-names.md | 31 ++++++ docs/options/default-index-names.md | 31 ++++++ docs/options/ignore.md | 15 +++ docs/options/log-with-batch.md | 38 +++++++ docs/options/path.md | 19 ++++ docs/options/skip-log.md | 7 ++ docs/options/skip-proc.md | 7 ++ docs/options/skip-vendor.md | 7 ++ docs/options/skip-views.md | 7 ++ docs/options/squash.md | 61 +++++++++++ docs/options/table-filename.md | 24 +++++ docs/options/tables.md | 17 +++ docs/options/template-path.md | 25 +++++ docs/options/use-db-collation.md | 43 ++++++++ docs/options/with-has-table.md | 24 +++++ docs/sqlite.md | 6 ++ docs/udt-columns.md | 20 ++++ docs/usage.md | 160 ++++++++++++++++++++++++++++ mkdocs.yml | 32 ++++++ 26 files changed, 763 insertions(+), 52 deletions(-) create mode 100644 .github/workflows/docs.yml create mode 100644 docs/index.md create mode 100644 docs/options/connection.md create mode 100644 docs/options/date.md create mode 100644 docs/options/default-fk-names.md create mode 100644 docs/options/default-index-names.md create mode 100644 docs/options/ignore.md create mode 100644 docs/options/log-with-batch.md create mode 100644 docs/options/path.md create mode 100644 docs/options/skip-log.md create mode 100644 docs/options/skip-proc.md create mode 100644 docs/options/skip-vendor.md create mode 100644 docs/options/skip-views.md create mode 100644 docs/options/squash.md create mode 100644 docs/options/table-filename.md create mode 100644 docs/options/tables.md create mode 100644 docs/options/template-path.md create mode 100644 docs/options/use-db-collation.md create mode 100644 docs/options/with-has-table.md create mode 100644 docs/sqlite.md create mode 100644 docs/udt-columns.md create mode 100644 docs/usage.md create mode 100644 mkdocs.yml diff --git a/.gitattributes b/.gitattributes index f65391ea..19361764 100644 --- a/.gitattributes +++ b/.gitattributes @@ -3,6 +3,7 @@ # Ignore all test and documentation with "export-ignore". /.github export-ignore +/docs export-ignore /tests export-ignore /.editorconfig export-ignore /.gitattributes export-ignore diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 00000000..478de806 --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,39 @@ +name: "Generate documentation" + +on: + push: + branches: + - 7.x + +# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages +permissions: + contents: write + pages: write + +# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. +# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. +concurrency: + group: "pages" + cancel-in-progress: false + +jobs: + deploy: + + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.x' + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install mkdocs + + - name: Deploy + run: mkdocs gh-deploy --force diff --git a/.gitignore b/.gitignore index b36a8a5a..9b8ac626 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,8 @@ /migrations /storage /vendor +/venv +/site .php-cs-fixer.cache .php_cs.cache .phpstorm.meta.php diff --git a/README.md b/README.md index 00f59b6a..edd70cb7 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ Generate Laravel Migrations from an existing database, including indexes and foreign keys! -This package is a modified version of https://github.com/Xethron/migrations-generator that has been updated to support Laravel 5.6 and beyond, along with additional features. +Checkout the [documentation](https://kitloong.github.io/laravel-migrations-generator/usage/) for more details. ## Supported Database @@ -80,7 +80,7 @@ You can also ignore tables with: php artisan migrate:generate --ignore="table3,table4,table5" ``` -Laravel Migrations Generator will first generate all the tables, columns and indexes, and afterwards setup all the foreign key constraints. +Laravel Migrations Generator will first generate all the tables, columns and indexes, and afterward setup all the foreign key constraints. So make sure you include all the tables listed in the foreign keys so that they are present when the foreign keys are created. @@ -104,56 +104,28 @@ php artisan migrate:generate --squash Run `php artisan help migrate:generate` for a list of options. -| Options | Description | -|--------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------| -| -c, --connection[=CONNECTION] | The database connection to use | -| -t, --tables[=TABLES] | A list of tables or views you wish to generate migrations for separated by a comma: users,posts,comments | -| -i, --ignore[=IGNORE] | A list of tables or views you wish to ignore, separated by a comma: users,posts,comments | -| -p, --path[=PATH] | Where should the file be created? | -| -tp, --template-path[=TEMPLATE-PATH] | The location of the template for this generator | -| --date[=DATE] | Migrations will be created with specified date. Views and foreign keys will be created with + 1 second. Date should be in format supported by `Carbon::parse` | -| --table-filename[=TABLE-FILENAME] | Define table migration filename, default pattern: `[datetime]\_create_[name]_table.php` | -| --view-filename[=VIEW-FILENAME] | Define view migration filename, default pattern: `[datetime]\_create_[name]_view.php` | -| --proc-filename[=PROC-FILENAME] | Define stored procedure filename, default pattern: `[datetime]\_create_[name]_proc.php` | -| --fk-filename[=FK-FILENAME] | Define foreign key migration filename, default pattern: `[datetime]\_add_foreign_keys_to_[name]_table.php` | -| --log-with-batch[=LOG-WITH-BATCH] | Log migrations with given batch number. We recommend using batch number 0 so that it becomes the first migration | -| --default-index-names | Don\'t use DB index names for migrations | -| --default-fk-names | Don\'t use DB foreign key names for migrations | -| --use-db-collation | Generate migrations with existing DB collation | -| --skip-log | Don\'t log into migrations table | -| --skip-vendor | Don\'t generate vendor migrations | -| --skip-views | Don\'t generate views | -| --skip-proc | Don\'t generate stored procedures | -| --squash | Generate all migrations into a single file | -| --with-has-table | Check for the existence of a table using `hasTable` | - -## SQLite Alter Foreign Key - -The generator first generates all tables and then adds foreign keys to existing tables. - -However, SQLite only supports foreign keys upon creation of the table and not when tables are altered. -*_add_foreign_keys_* migrations will still be generated, however will get omitted if migrate to SQLite type database. - -## User-Defined Type Columns - -The generator will recognize user-defined type from the schema, and then generate migration as - -```php -public function up() -{ - Schema::create('table', function (Blueprint $table) { - ... - }); - DB::statement("ALTER TABLE table ADD column custom_type NOT NULL"); -} -``` - -Note that the new `column` is always added at the end of the created `table` which means the ordering of the column generated in migration will differ from what we have from the schema. - -Supported database with user-defined types: - -- [x] PostgreSQL -- [x] SQL Server +| Options | Description | +|------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------| +| [-c, --connection[=CONNECTION]](https://kitloong.github.io/laravel-migrations-generator/options/connection/) | The database connection to use | +| [-t, --tables[=TABLES]](https://kitloong.github.io/laravel-migrations-generator/options/tables/) | A list of tables or views you wish to generate migrations for separated by a comma: users,posts,comments | +| [-i, --ignore[=IGNORE]](https://kitloong.github.io/laravel-migrations-generator/options/ignore/) | A list of tables or views you wish to ignore, separated by a comma: users,posts,comments | +| [-p, --path[=PATH]](https://kitloong.github.io/laravel-migrations-generator/options/path/) | Where should the file be created? | +| [-tp, --template-path[=TEMPLATE-PATH]](https://kitloong.github.io/laravel-migrations-generator/options/template-path/) | The location of the template for this generator | +| [--date[=DATE]](https://kitloong.github.io/laravel-migrations-generator/options/date/) | Migrations will be created with specified date. Views and foreign keys will be created with + 1 second. Date should be in format supported by `Carbon::parse` | +| [--table-filename[=TABLE-FILENAME]](https://kitloong.github.io/laravel-migrations-generator/options/table-filename/) | Define table migration filename, default pattern: `[datetime]\_create_[name]_table.php` | +| [--view-filename[=VIEW-FILENAME]](https://kitloong.github.io/laravel-migrations-generator/options/table-filename/) | Define view migration filename, default pattern: `[datetime]\_create_[name]_view.php` | +| [--proc-filename[=PROC-FILENAME]](https://kitloong.github.io/laravel-migrations-generator/options/table-filename/) | Define stored procedure filename, default pattern: `[datetime]\_create_[name]_proc.php` | +| [--fk-filename[=FK-FILENAME]](https://kitloong.github.io/laravel-migrations-generator/options/table-filename/) | Define foreign key migration filename, default pattern: `[datetime]\_add_foreign_keys_to_[name]_table.php` | +| [--log-with-batch[=LOG-WITH-BATCH]](https://kitloong.github.io/laravel-migrations-generator/options/log-with-batch/) | Log migrations with given batch number. We recommend using batch number 0 so that it becomes the first migration | +| [--default-index-names](https://kitloong.github.io/laravel-migrations-generator/options/default-index-names/) | Don\'t use DB index names for migrations | +| [--default-fk-names](https://kitloong.github.io/laravel-migrations-generator/options/default-fk-names/) | Don\'t use DB foreign key names for migrations | +| [--use-db-collation](https://kitloong.github.io/laravel-migrations-generator/options/use-db-collation/) | Generate migrations with existing DB collation | +| [--skip-log](https://kitloong.github.io/laravel-migrations-generator/options/skip-log/) | Don\'t log into migrations table | +| [--skip-vendor](https://kitloong.github.io/laravel-migrations-generator/options/skip-vendor/) | Don\'t generate vendor migrations | +| [--skip-views](https://kitloong.github.io/laravel-migrations-generator/options/skip-views/) | Don\'t generate views | +| [--skip-proc](https://kitloong.github.io/laravel-migrations-generator/options/skip-proc/) | Don\'t generate stored procedures | +| [--squash](https://kitloong.github.io/laravel-migrations-generator/options/squash/) | Generate all migrations into a single file | +| [--with-has-table](https://kitloong.github.io/laravel-migrations-generator/options/with-has-table/) | Check for the existence of a table using `hasTable` | ## Thank You diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 00000000..f34bbd17 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,62 @@ +# Getting Started + +Generate Laravel Migrations from an existing database, including indexes and foreign keys! + +## Supported Database + +Laravel Migrations Generator supports all five Laravel first-party support databases: + +- MariaDB +- MySQL +- PostgreSQL +- SQL Server +- SQLite + +## Install + +The recommended way to install this is through composer: + +```bash +composer require --dev kitloong/laravel-migrations-generator +``` + +## Version Compatibility + +| Laravel | Version | +|--------------------|-------------------------------------------------| +| 12.x | 7.x | +| 11.x | 7.x | +| \>= 10.43.x | 7.x | +| 10.x \| <= 10.42.x | 6.x | +| 9.x | 6.x | +| 8.x | 6.x | +| 7.x | 6.x | +| 6.x | 6.x | +| 5.8.x | 6.x | +| 5.7.x | 6.x | +| 5.6.x | 6.x | +| <= 5.5.x | https://github.com/Xethron/migrations-generator | + +### Laravel Setup + +Laravel will automatically register service provider for you. + +### Lumen Setup + +Auto-discovery is not available in Lumen, you need some modification on `bootstrap/app.php`. + +#### Enable Facade + +Uncomment the following line. + +```php +$app->withFacades(); +``` + +#### Register Provider + +Add following line into the `Register Service Providers` section. + +```php +$app->register(\KitLoong\MigrationsGenerator\MigrationsGeneratorServiceProvider::class); +``` diff --git a/docs/options/connection.md b/docs/options/connection.md new file mode 100644 index 00000000..ef0fec43 --- /dev/null +++ b/docs/options/connection.md @@ -0,0 +1,36 @@ +# Connection + +```bash +-c, --connection[=CONNECTION] +``` + +The `--connection` option allows you to specify the database connection to use. If you don't specify a connection, the default connection as defined in your Laravel's `config/database.php` will be used. + +This option is particularly useful when you have multiple database connections defined in your Laravel application and you want to generate migrations for a specific connection. + +### Example + +Suppose you have a connection named `secondary` defined in your `config/database.php`. You can generate migrations for this connection by running: + +```bash +php artisan migrate:generate --connection="secondary" +``` + +This command will generate migrations for the tables in the `secondary` database connection. + +```php +// Up +Schema::connection('secondary')->create('users', function (Blueprint $table) { + $table->bigIncrements('id'); + ... +}); + +// Down +Schema::connection('secondary')->dropIfExists('users'); +``` + +If you are not sure what is the name of the connection you want to use, you can check the `connections` array in your `config/database.php` file. + +```php +array_keys(config('database.connections')) +``` diff --git a/docs/options/date.md b/docs/options/date.md new file mode 100644 index 00000000..6f188190 --- /dev/null +++ b/docs/options/date.md @@ -0,0 +1,25 @@ +# Date + +```bash +--date[=DATE] +``` + +The `--date` option allows you to specify the date for the generated migration files. + +Migrations will be created with the specified date. Views, procedures, and foreign keys will be created with an additional 1 second. + +The date should be in a format supported by [`Carbon::parse`](https://github.com/briannesbitt/Carbon/blob/d481d8d69a94dc00e5c1b147ec1f7e7492626b12/src/Carbon/Traits/Creator.php#L207). + +### Example + +```bash +php artisan migrate:generate --date="2024-10-08 12:30:00" +``` + +Will generate the following migrations: + +```bash +2024_10_08_123000_create_comments_table.php +2024_10_08_123000_create_users_table.php +2024_10_08_123001_add_foreign_keys_to_comments_table.php +``` diff --git a/docs/options/default-fk-names.md b/docs/options/default-fk-names.md new file mode 100644 index 00000000..5aa1510d --- /dev/null +++ b/docs/options/default-fk-names.md @@ -0,0 +1,31 @@ +# Default Foreign Key Names + +```bash +--default-fk-names +``` + +Use Laravel's default naming convention for foreign keys instead of the database foreign key names. + +### Example + +```bash +php artisan migrate:generate --default-fk-names +``` + +Schema with foreign key: + +```sql +CREATE TABLE `comments` ( + ... + CONSTRAINT `user_id_fk` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) +); +``` + +Will generate as: + +```php +Schema::table('comments', function (Blueprint $table) { + ... + $table->foreign('user_id')->references('id')->on('users'); +}); +``` diff --git a/docs/options/default-index-names.md b/docs/options/default-index-names.md new file mode 100644 index 00000000..497f6865 --- /dev/null +++ b/docs/options/default-index-names.md @@ -0,0 +1,31 @@ +# Default Index Names + +```bash +--default-index-names +``` + +Use Laravel's default naming convention for indexes instead of the database index names. + +### Example + +```bash +php artisan migrate:generate --default-index-names +``` + +Schema with index: + +```sql +CREATE TABLE `comments` ( + ... + INDEX `user_id_idx` (`user_id`) +); +``` + +Will generate as: + +```php +Schema::create('comments', function (Blueprint $table) { + ... + $table->index('user_id'); +}); +``` diff --git a/docs/options/ignore.md b/docs/options/ignore.md new file mode 100644 index 00000000..4ab8ddba --- /dev/null +++ b/docs/options/ignore.md @@ -0,0 +1,15 @@ +# Ignore + +```bash +-i, --ignore[=IGNORE] +``` + +The `--ignore` option allows you to specify which tables you want to exclude when generating migrations. This is useful when you want to generate migrations for all tables in your database except for a few specific ones. + +### Example + +To use the `--ignore` option, you need to provide a comma-separated list of table names. For example: + +```bash +php artisan migrate:generate --ignore="users,comments" +``` diff --git a/docs/options/log-with-batch.md b/docs/options/log-with-batch.md new file mode 100644 index 00000000..003716f7 --- /dev/null +++ b/docs/options/log-with-batch.md @@ -0,0 +1,38 @@ +# Log With Batch + +```bash +--log-with-batch[=LOG-WITH-BATCH] +``` + +By default, the command will ask for the batch number to log the generated migrations. + +```bash +Do you want to log these migrations in the migrations table? (yes/no) [yes]: + > yes + + Next Batch Number is: 11. We recommend using Batch Number 0 so that it becomes the "first" migration. [Default: 0] [0]: +``` + +The `--log-with-batch` option allows you to specify the batch number for logging the generated migrations. + +By using `--log-with-batch`, you can skip the prompt for the batch number and directly log the migrations with the specified batch number. + +### Example + +```bash +php artisan migrate:generate --log-with-batch=0 +``` + +Will generate the migration files and log them in the migrations table with `0` as the batch number. + +```sql +SELECT migration FROM `migrations` WHERE batch = 0; + ++----------------------------------------------------------+ +| migration | ++----------------------------------------------------------+ +| 2024_10_08_083231_create_users_table.php | +| 2024_10_08_083231_create_comments_table.php | +| 2024_10_08_083232_add_foreign_keys_to_comments_table.php | ++----------------------------------------------------------+ +``` diff --git a/docs/options/path.md b/docs/options/path.md new file mode 100644 index 00000000..8b81ab9f --- /dev/null +++ b/docs/options/path.md @@ -0,0 +1,19 @@ +# Path + +```bash +-p, --path[=PATH] +``` + +The `--path` option allows you to specify the directory where the generated migration files should be created. + +By default, migrations are created in the `database/migrations` directory. + +## Example + +To use the `--path` option, you need to provide the desired directory path. For example: + +```bash +php artisan migrate:generate --path="custom/migrations" +``` + +This command will generate the migration files in the `custom/migrations` directory instead of the default `database/migrations` directory. diff --git a/docs/options/skip-log.md b/docs/options/skip-log.md new file mode 100644 index 00000000..79b1853c --- /dev/null +++ b/docs/options/skip-log.md @@ -0,0 +1,7 @@ +# Skip Log + +```bash +--skip-log +``` + +Skip the confirmation prompt and generate the migrations without log into `migrations` table. diff --git a/docs/options/skip-proc.md b/docs/options/skip-proc.md new file mode 100644 index 00000000..41f7e346 --- /dev/null +++ b/docs/options/skip-proc.md @@ -0,0 +1,7 @@ +# Skip Procedures + +```bash +--skip-proc +``` + +Excludes store procedures from the generated migrations. diff --git a/docs/options/skip-vendor.md b/docs/options/skip-vendor.md new file mode 100644 index 00000000..3f0c7a5d --- /dev/null +++ b/docs/options/skip-vendor.md @@ -0,0 +1,7 @@ +# Skip Vendor + +```bash +--skip-vendor +``` + +Prevents the generated migrations from including tables created by vendor packages such as Nova. diff --git a/docs/options/skip-views.md b/docs/options/skip-views.md new file mode 100644 index 00000000..7c8648a2 --- /dev/null +++ b/docs/options/skip-views.md @@ -0,0 +1,7 @@ +# Skip Views + +```bash +--skip-views +``` + +Excludes database views from the generated migrations. diff --git a/docs/options/squash.md b/docs/options/squash.md new file mode 100644 index 00000000..731b4e12 --- /dev/null +++ b/docs/options/squash.md @@ -0,0 +1,61 @@ +# Squash Migrations + +```bash +--squash +``` + +The `--squash` option combines all generated migrations into a single file. + +### Example + +```bash +php artisan migrate:generate --squash +``` + +This command will generate a single migration file containing all the migrations. + +```php +bigIncrements('id'); + ... + }); + + Schema::create('users', function (Blueprint $table) { + $table->bigIncrements('id'); + ... + }); + + Schema::table('comments', function (Blueprint $table) { + $table->foreign(['user_id'])->references(['id'])->on('users')->onUpdate('restrict')->onDelete('restrict'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('comments', function (Blueprint $table) { + $table->dropForeign('comments_user_id_foreign'); + }); + + Schema::dropIfExists('users'); + + Schema::dropIfExists('comments'); + } +}; +``` diff --git a/docs/options/table-filename.md b/docs/options/table-filename.md new file mode 100644 index 00000000..35681686 --- /dev/null +++ b/docs/options/table-filename.md @@ -0,0 +1,24 @@ +# Table Filename + +```bash +--table-filename[=TABLE-FILENAME] +``` + +The `--table-filename` option allows you to define the filename pattern for table migrations. + +By default, the pattern is `[datetime]_create_[name]_table.php`. + +### Example + +```bash +php artisan migrate:generate --table-filename="[datetime]_create_mysql_[name]_table.php" +``` + +Will generate the migration files with the specified pattern: + +```bash +2024_10_08_083231_create_mysql_comments_table.php +2024_10_08_083231_create_mysql_users_table.php +``` + +`--table-filename`, `--view-filename`, `--proc-filename`, and `--fk-filename` share the similar usage and purpose. diff --git a/docs/options/tables.md b/docs/options/tables.md new file mode 100644 index 00000000..82347342 --- /dev/null +++ b/docs/options/tables.md @@ -0,0 +1,17 @@ +# Tables + +```bash +-t, --tables[=TABLES] +``` + +By default, the `migrate:generate` command will generate migrations for all tables and views in your database. + +The `--tables` option allows you to specify which tables you want to generate migrations for. This is useful when you only want to generate migrations for specific tables or views in your database. + +### Example + +To use the `--tables` option, you need to provide a comma-separated list of table / view names. + +```bash +php artisan migrate:generate --tables="users,posts,comments" +``` diff --git a/docs/options/template-path.md b/docs/options/template-path.md new file mode 100644 index 00000000..683873da --- /dev/null +++ b/docs/options/template-path.md @@ -0,0 +1,25 @@ +# Template Path + +```bash +-tp, --template-path[=TEMPLATE-PATH] +``` + +The `--template-path` option allows you to specify the location of the template for this generator. This is useful when you want to use your own template for generating migrations. + +Customize your template using the [default stub](https://github.com/kitloong/laravel-migrations-generator/blob/7.x/stubs/migration.generate.stub), and make sure to include the following placeholders: + +1. `{{ use }}` +2. `{{ up }}` +3. `{{ down }}` + +The placeholder should be self-explanatory in [default stub](https://github.com/kitloong/laravel-migrations-generator/blob/7.x/stubs/migration.generate.stub). + +### Example + +To use the `--template-path` option, you need to provide the path to the directory containing the template files. For example: + +```bash +php artisan migrate:generate --template-path="custom/templates" +``` + +This command will use the templates located in the `custom/templates` directory for generating the migration files. diff --git a/docs/options/use-db-collation.md b/docs/options/use-db-collation.md new file mode 100644 index 00000000..cf12c34a --- /dev/null +++ b/docs/options/use-db-collation.md @@ -0,0 +1,43 @@ +# Use Database Collation + +```bash +--use-db-collation +``` + +By default, migration files are generated without collation setting. + +This means `php artisan migrate` will create tables with the collation settings defined in the `config/database.php` file. + +```php +// config/database.php + +return [ + 'connections' => [ + 'mysql' => [ + ... + 'charset' => 'utf8mb4', + 'collation' => 'utf8mb4_unicode_ci', + ], + ], +]; +``` + +The `--use-db-collation` option will always generates migrations with the existing database collation settings. + +### Example + +```bash +php artisan migrate:generate --use-db-collation +``` + +Will generate: + +```php +Schema::create('users', function (Blueprint $table) { + $table->collation = 'utf8mb4_unicode_ci'; + $table->charset = 'utf8mb4'; + + $table->bigIncrements('id'); + ... +}); +``` diff --git a/docs/options/with-has-table.md b/docs/options/with-has-table.md new file mode 100644 index 00000000..4741553d --- /dev/null +++ b/docs/options/with-has-table.md @@ -0,0 +1,24 @@ +# With Has Table + +```bash +--with-has-table +``` + +Checks for the existence of a table using the `Schema::hasTable` method before creating it. + +### Example + +```bash +php artisan migrate:generate --with-has-table +``` + +Will generate: + +```php +if (!Schema::hasTable('users')) { + Schema::create('users', function (Blueprint $table) { + $table->bigIncrements('id'); + ... + }); +} +``` diff --git a/docs/sqlite.md b/docs/sqlite.md new file mode 100644 index 00000000..c2c75a82 --- /dev/null +++ b/docs/sqlite.md @@ -0,0 +1,6 @@ +# SQLite Alter Foreign Key + +The generator first generates all tables and then adds foreign keys to existing tables. + +However, SQLite only supports foreign keys upon creation of the table and not when tables are altered. +*_add_foreign_keys_* migrations will still be generated, however will get omitted if migrate to SQLite type database. diff --git a/docs/udt-columns.md b/docs/udt-columns.md new file mode 100644 index 00000000..9a09c093 --- /dev/null +++ b/docs/udt-columns.md @@ -0,0 +1,20 @@ +# User-Defined Type Columns + +The generator will recognize user-defined type from the schema, and then generate migration as + +```php +public function up() +{ + Schema::create('table', function (Blueprint $table) { + ... + }); + DB::statement("ALTER TABLE table ADD column custom_type NOT NULL"); +} +``` + +Note that the new `column` is always added at the end of the created `table` which means the ordering of the column generated in migration will differ from what we have from the schema. + +Supported database with user-defined types: + +- [x] PostgreSQL +- [x] SQL Server diff --git a/docs/usage.md b/docs/usage.md new file mode 100644 index 00000000..d5279bd9 --- /dev/null +++ b/docs/usage.md @@ -0,0 +1,160 @@ +# Usage + +To create migrations for all the tables, run: + +```bash +php artisan migrate:generate +``` + +It will first ask if you want to log the migrations in the migrations table. + +```bash +Using connection: mysql + +Generating migrations for: comments,users + + Do you want to log these migrations in the migrations table? (yes/no) [yes]: +``` + +If you choose `yes`, it will prompt another question to confirm the batch number: + +```bash + Next Batch Number is: 1. We recommend using Batch Number 0 so that it becomes the "first" migration. [Default: 0] [0]: +``` + +Enter your desired batch number or press `Enter` to use the default value. + +The tables from your schema: + +```sql +CREATE TABLE `users` ( + `id` BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, + `name` VARCHAR(255) COMMENT 'comment', + `email` VARCHAR(255) UNIQUE, + `email_verified_at` TIMESTAMP NULL, + `password` VARCHAR(255), + `remember_token` VARCHAR(100), + `created_at` TIMESTAMP NULL DEFAULT NULL, + `updated_at` TIMESTAMP NULL DEFAULT NULL +); + +CREATE TABLE `comments` ( + `id` BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, + `user_id` BIGINT UNSIGNED, + `title` VARCHAR(255), + `comment` VARCHAR(255) UNIQUE, + `created_at` TIMESTAMP NULL DEFAULT NULL, + `updated_at` TIMESTAMP NULL DEFAULT NULL, + INDEX `comments_user_id_index` (`user_id`), + CONSTRAINT `comments_user_id_foreign` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) +); +``` + +Will generate the following migration files. + +Please note that the migration file for adding foreign keys is always generated last. This is because the tables referenced by the foreign keys need to exist before the foreign keys can be created. + +* `2000_10_08_145641_create_comments_table.php` + +```php +bigIncrements('id'); + $table->string('name')->comment('comment'); + $table->string('email')->unique(); + $table->timestamp('email_verified_at')->nullable(); + $table->string('password'); + $table->rememberToken(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('users'); + } +}; +``` + +* `2000_10_08_145641_create_users_table.php` + +```php +bigIncrements('id'); + $table->unsignedBigInteger('user_id')->index(); + $table->string('title'); + $table->string('comment')->unique(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('comments'); + } +}; +``` + +* `2000_10_08_145644_add_foreign_keys_to_comments_table.php` + +```php +foreign(['user_id'])->references(['id'])->on('users')->onUpdate('restrict')->onDelete('restrict'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('comments', function (Blueprint $table) { + $table->dropForeign('comments_user_id_foreign'); + }); + } +}; +``` diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 00000000..87a3179f --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,32 @@ +site_name: Laravel Migrations Generator +repo_url: https://github.com/kitloong/laravel-migrations-generator +edit_uri: tree/7.x/docs/ +nav: + - index.md + - usage.md + - Options: + - options/connection.md + - options/date.md + - options/default-fk-names.md + - options/default-index-names.md + - options/ignore.md + - options/log-with-batch.md + - options/path.md + - options/skip-log.md + - options/skip-proc.md + - options/skip-vendor.md + - options/skip-views.md + - options/squash.md + - options/table-filename.md + - options/tables.md + - options/template-path.md + - options/use-db-collation.md + - options/with-has-table.md + - sqlite.md + - udt-columns.md +theme: + name: readthedocs +# custom_dir: docs/overrides + color_mode: auto + user_color_mode_toggle: true + locale: en From d47f6bcbbd43b38098b4879f2a5ab06d61772fce Mon Sep 17 00:00:00 2001 From: Kit Loong Date: Sat, 1 Mar 2025 21:25:24 +0800 Subject: [PATCH 07/14] Ignore mkdocs.yml --- .gitattributes | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitattributes b/.gitattributes index 19361764..c9e27427 100644 --- a/.gitattributes +++ b/.gitattributes @@ -10,6 +10,7 @@ /.gitignore export-ignore /.phpmd.xml export-ignore /check_migrations.sh export-ignore +/mkdocs.yml export-ignore /phpcs.xml export-ignore /phpstan.neon export-ignore /phpunit.xml.dist export-ignore From 698e053193f4c55e89a817a1924ce4e856de4d8c Mon Sep 17 00:00:00 2001 From: Kit Loong Date: Sat, 1 Mar 2025 21:30:21 +0800 Subject: [PATCH 08/14] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index edd70cb7..15582f28 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,8 @@ Generate Laravel Migrations from an existing database, including indexes and foreign keys! +## Documentation + Checkout the [documentation](https://kitloong.github.io/laravel-migrations-generator/usage/) for more details. ## Supported Database From 34f696c9e4463fae409d116560d5fe0a0a6e2de5 Mon Sep 17 00:00:00 2001 From: Kit Loong Date: Sun, 2 Mar 2025 01:13:34 +0800 Subject: [PATCH 09/14] Update gtag --- mkdocs.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mkdocs.yml b/mkdocs.yml index 87a3179f..9fcfb596 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -26,6 +26,8 @@ nav: - udt-columns.md theme: name: readthedocs + analytics: + gtag: G-Q55TH3KLPX # custom_dir: docs/overrides color_mode: auto user_color_mode_toggle: true From 4a955661fc6e6406f4a027cbc64cec13b0a7d43a Mon Sep 17 00:00:00 2001 From: Kit Loong Date: Mon, 3 Mar 2025 21:28:50 +0800 Subject: [PATCH 10/14] Add meta description --- mkdocs.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mkdocs.yml b/mkdocs.yml index 9fcfb596..977be9a9 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -1,5 +1,7 @@ site_name: Laravel Migrations Generator repo_url: https://github.com/kitloong/laravel-migrations-generator +site_description: Generate Laravel Migrations from an existing database, including indexes and foreign keys! +site_author: 'Kit Loong' edit_uri: tree/7.x/docs/ nav: - index.md @@ -28,7 +30,6 @@ theme: name: readthedocs analytics: gtag: G-Q55TH3KLPX -# custom_dir: docs/overrides color_mode: auto user_color_mode_toggle: true locale: en From 08245506b77dce9630d0d8682ba2b127754d0b83 Mon Sep 17 00:00:00 2001 From: Kit Loong Date: Mon, 3 Mar 2025 22:03:07 +0800 Subject: [PATCH 11/14] Add stale action --- .github/workflows/stale.yml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 .github/workflows/stale.yml diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml new file mode 100644 index 00000000..e8ecc190 --- /dev/null +++ b/.github/workflows/stale.yml @@ -0,0 +1,15 @@ +name: "Mark stale issues and pull requests" + +on: + schedule: + - cron: '0 0 * * *' + +permissions: + issues: write + pull-requests: write + +jobs: + stale: + runs-on: ubuntu-latest + steps: + - uses: actions/stale@v8 From cd787e27af4cc7276df410a9cf2ff69eddee6ba7 Mon Sep 17 00:00:00 2001 From: Kit Loong Date: Tue, 11 Mar 2025 23:09:34 +0800 Subject: [PATCH 12/14] Add sponsor --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index 15582f28..6f3874d0 100644 --- a/README.md +++ b/README.md @@ -133,6 +133,16 @@ Run `php artisan help migrate:generate` for a list of options. Thanks to Bernhard Breytenbach for his great work. This package is based on https://github.com/Xethron/migrations-generator. +## Our Amazing Sponsor 🎉 + +A huge thank you to our sponsor for supporting this project! + + + Vemto + + +Want to support this project? [Become a sponsor](https://github.com/sponsors/kitloong) today! + ## Contributors [![Contributors](https://contrib.rocks/image?repo=kitloong/laravel-migrations-generator)](https://github.com/kitloong/laravel-migrations-generator/graphs/contributors) From 660d64de8476f5666c389618aa907ceb08a50bb4 Mon Sep 17 00:00:00 2001 From: Kit Loong Date: Tue, 22 Jul 2025 02:02:53 +0800 Subject: [PATCH 13/14] Default value is now escaped properly in Laravel v12.20.0 https://github.com/laravel/framework/pull/56158 --- src/Database/Models/DatabaseColumn.php | 7 +- .../Database/Models/DatabaseColumnTest.php | 153 ++++++++++++++++++ .../Database/Models/TestDatabaseColumn.php | 25 +++ 3 files changed, 184 insertions(+), 1 deletion(-) create mode 100644 tests/Unit/Database/Models/DatabaseColumnTest.php create mode 100644 tests/Unit/Database/Models/TestDatabaseColumn.php diff --git a/src/Database/Models/DatabaseColumn.php b/src/Database/Models/DatabaseColumn.php index 45741c7a..424b0a76 100644 --- a/src/Database/Models/DatabaseColumn.php +++ b/src/Database/Models/DatabaseColumn.php @@ -2,6 +2,7 @@ namespace KitLoong\MigrationsGenerator\Database\Models; +use Illuminate\Support\Facades\App; use KitLoong\MigrationsGenerator\Enum\Migrations\ColumnName; use KitLoong\MigrationsGenerator\Enum\Migrations\Method\ColumnType; use KitLoong\MigrationsGenerator\Schema\Models\Column; @@ -293,7 +294,11 @@ protected function escapeDefault(?string $default): ?string return null; } - $default = str_replace("'", "''", $default); + // For Laravel versions lower than 12.20.0, escape single quotes + if (version_compare(App::version(), '12.20.0', '<')) { + $default = str_replace("'", "''", $default); + } + return addcslashes($default, '\\'); } diff --git a/tests/Unit/Database/Models/DatabaseColumnTest.php b/tests/Unit/Database/Models/DatabaseColumnTest.php new file mode 100644 index 00000000..bc643c22 --- /dev/null +++ b/tests/Unit/Database/Models/DatabaseColumnTest.php @@ -0,0 +1,153 @@ +andReturn($laravelVersion); + + $column = new TestDatabaseColumn('test_table', [ + 'name' => 'test_column', + 'type_name' => 'varchar', + 'type' => 'varchar(255)', + 'collation' => null, + 'nullable' => true, + 'default' => null, + 'auto_increment' => false, + 'comment' => null, + 'generation' => null, + ]); + + $result = $column->testEscapeDefault($input); + $this->assertSame($expected, $result); + } + + public function testEscapeDefaultWithComplexStrings(): void + { + App::shouldReceive('version')->andReturn('11.0.0'); // Old version + + $column = new TestDatabaseColumn('test_table', [ + 'name' => 'test_column', + 'type_name' => 'varchar', + 'type' => 'varchar(255)', + 'collation' => null, + 'nullable' => true, + 'default' => null, + 'auto_increment' => false, + 'comment' => null, + 'generation' => null, + ]); + + // Test complex string with multiple characters to escape + $input = "string !@#$%^^&*()_+-=[]{};:,./<>?~`| \\ \\' \\\\\' \" \\\" \\\\\""; + $result = $column->testEscapeDefault($input); + + // Should escape both single quotes and backslashes for old Laravel versions + $expected = "string !@#$%^^&*()_+-=[]{};:,./<>?~`| \\\\ \\\\'' \\\\\\\\\\\\'' \" \\\\\" \\\\\\\\\""; + $this->assertSame($expected, $result); + } + + public function testEscapeDefaultWithNewLaravelVersionComplexString(): void + { + App::shouldReceive('version')->andReturn('12.20.0'); // New version + + $column = new TestDatabaseColumn('test_table', [ + 'name' => 'test_column', + 'type_name' => 'varchar', + 'type' => 'varchar(255)', + 'collation' => null, + 'nullable' => true, + 'default' => null, + 'auto_increment' => false, + 'comment' => null, + 'generation' => null, + ]); + + // Test complex string - should only escape backslashes, not quotes + $input = "string !@#$%^^&*()_+-=[]{};:,./<>?~`| \\ \\' \\\\\' \" \\\" \\\\\""; + $result = $column->testEscapeDefault($input); + + // Should only escape backslashes for new Laravel versions + $expected = "string !@#$%^^&*()_+-=[]{};:,./<>?~`| \\\\ \\\\' \\\\\\\\\\\\' \" \\\\\" \\\\\\\\\""; + $this->assertSame($expected, $result); + } + + /** + * @return array + */ + public static function escapeDefaultVersionProvider(): array + { + return [ + // Laravel < 12.20.0 cases - should escape single quotes + 'Laravel 11.x.x with single quotes' => [ + '11.5.0', + "test'value", + "test''value", + ], + 'Laravel 12.19.x with single quotes' => [ + '12.19.9', + "value with 'quotes' inside", + "value with ''quotes'' inside", + ], + 'Laravel 12.19.x with backslashes' => [ + '12.19.9', + 'path\\to\\file', + 'path\\\\to\\\\file', + ], + 'Laravel 12.19.x with both quotes and backslashes' => [ + '12.19.9', + "path\\to'file'", + "path\\\\to''file''", + ], + 'Laravel 12.19.x with null input' => [ + '12.19.9', + null, + null, + ], + + // Laravel >= 12.20.0 cases - should NOT escape single quotes + 'Laravel 12.20.0 with single quotes' => [ + '12.20.0', + "test'value", + "test'value", + ], + 'Laravel 12.21.x with single quotes' => [ + '12.21.5', + "value with 'quotes' inside", + "value with 'quotes' inside", + ], + 'Laravel 13.x.x with single quotes' => [ + '13.0.0', + "test'value", + "test'value", + ], + 'Laravel 12.20.0 with backslashes' => [ + '12.20.0', + 'path\\to\\file', + 'path\\\\to\\\\file', + ], + 'Laravel 12.20.0 with both quotes and backslashes' => [ + '12.20.0', + "path\\to'file'", + "path\\\\to'file'", + ], + 'Laravel 12.20.0 with null input' => [ + '12.20.0', + null, + null, + ], + ]; + } +} diff --git a/tests/Unit/Database/Models/TestDatabaseColumn.php b/tests/Unit/Database/Models/TestDatabaseColumn.php new file mode 100644 index 00000000..2d773e85 --- /dev/null +++ b/tests/Unit/Database/Models/TestDatabaseColumn.php @@ -0,0 +1,25 @@ +escapeDefault($default); + } + + protected function getColumnType(string $type): ColumnType + { + return ColumnType::STRING; + } +} From 0b99e764577f3e8eb15c283f63ea50ad30d4a251 Mon Sep 17 00:00:00 2001 From: Kit Loong Date: Tue, 5 Aug 2025 22:45:03 +0800 Subject: [PATCH 14/14] Fix #276 Parse enum constraint correctly --- src/Database/Models/PgSQL/PgSQLColumn.php | 51 +++++-- .../Database/Models/PgSQL/PgSQLColumnTest.php | 133 ++++++++++++++++++ ...0000_expected_create_all_columns_table.php | 1 + 3 files changed, 177 insertions(+), 8 deletions(-) diff --git a/src/Database/Models/PgSQL/PgSQLColumn.php b/src/Database/Models/PgSQL/PgSQLColumn.php index 4a92be8d..6106e968 100644 --- a/src/Database/Models/PgSQL/PgSQLColumn.php +++ b/src/Database/Models/PgSQL/PgSQLColumn.php @@ -6,7 +6,6 @@ use KitLoong\MigrationsGenerator\Database\Models\DatabaseColumn; use KitLoong\MigrationsGenerator\Enum\Migrations\Method\ColumnType; use KitLoong\MigrationsGenerator\Repositories\PgSQLRepository; -use KitLoong\MigrationsGenerator\Support\Regex; class PgSQLColumn extends DatabaseColumn { @@ -163,20 +162,56 @@ private function getEnumPresetValues(): array { $definition = $this->repository->getCheckConstraintDefinition($this->tableName, $this->name); - if ($definition === null) { + if ($definition === null || $definition === '') { return []; } - if ($definition === '') { - return []; + $enumValues = $this->parseEnumValuesFromConstraint($definition); + + if (count($enumValues) > 0) { + return array_values(array_unique($enumValues)); } - $presetValues = Regex::getTextBetweenAll($definition, "'", "'::"); + return []; + } - if ($presetValues === null) { - return []; + /** + * This method handles various PostgreSQL check constraint patterns: + * + * 1. ANY ARRAY: CHECK ((status)::text = ANY ((ARRAY['active'::character varying, 'inactive'::character varying])::text[])) + * 2. OR conditions: CHECK (((status)::text = 'active'::text) OR ((status)::text = 'inactive'::text)) + * 3. IN clause: CHECK (status IN ('active', 'inactive', 'pending')) + * + * @return string[] + */ + private function parseEnumValuesFromConstraint(string $definition): array + { + // Pattern 1: ANY with ARRAY pattern (most common in PostgreSQL) + // Example: CHECK ((status)::text = ANY ((ARRAY['active'::character varying, 'inactive'::character varying])::text[])) + if (preg_match('/ARRAY\[(.*?)\]/i', $definition, $matches)) { + $arrayContent = $matches[1]; + + if (preg_match_all('/\'([^\']+)\'/i', $arrayContent, $valueMatches)) { + return $valueMatches[1]; + } + } + + // Pattern 2: Multiple OR conditions + // Example: CHECK (((status)::text = 'active'::text) OR ((status)::text = 'inactive'::text)) + if (preg_match_all('/\(\(' . preg_quote($this->name, '/') . '\)[^=]*=\s*\'([^\']+)\'/i', $definition, $matches)) { + return array_unique($matches[1]); + } + + // Pattern 3: Simple IN clause + // Example: CHECK (status IN ('active', 'inactive', 'pending')) + if (preg_match('/' . preg_quote($this->name, '/') . '\s+IN\s*\(\s*(.*?)\s*\)/i', $definition, $matches)) { + $inContent = $matches[1]; + + if (preg_match_all('/\'([^\']+)\'/i', $inContent, $valueMatches)) { + return $valueMatches[1]; + } } - return $presetValues; + return []; } } diff --git a/tests/Unit/Database/Models/PgSQL/PgSQLColumnTest.php b/tests/Unit/Database/Models/PgSQL/PgSQLColumnTest.php index 8d9c1198..ee487b05 100644 --- a/tests/Unit/Database/Models/PgSQL/PgSQLColumnTest.php +++ b/tests/Unit/Database/Models/PgSQL/PgSQLColumnTest.php @@ -8,6 +8,7 @@ use KitLoong\MigrationsGenerator\Tests\TestCase; use Mockery\MockInterface; use PHPUnit\Framework\Attributes\DataProvider; +use ReflectionClass; class PgSQLColumnTest extends TestCase { @@ -35,6 +36,38 @@ public function testSpatialTypeName(string $type): void $this->assertSame(4326, $column->getSpatialSrID()); } + /** + * @param string[] $expectedValues + */ + #[DataProvider('parseEnumValuesFromConstraintProvider')] + public function testParseEnumValuesFromConstraint(string $constraintDefinition, array $expectedValues): void + { + $this->mock(PgSQLRepository::class, static function (MockInterface $mock): void { + $mock->shouldReceive('getCheckConstraintDefinition')->andReturn(''); + }); + + $column = new PgSQLColumn('test_table', [ + 'name' => 'status', + 'type_name' => 'varchar', + 'type' => 'character varying(255)', + 'collation' => null, + 'nullable' => false, + 'default' => null, + 'auto_increment' => false, + 'comment' => null, + 'generation' => null, + ]); + + // Use reflection to access the private method + $reflection = new ReflectionClass($column); + $method = $reflection->getMethod('parseEnumValuesFromConstraint'); + $method->setAccessible(true); + + $result = $method->invoke($column, $constraintDefinition); + + $this->assertSame($expectedValues, $result); + } + /** * @return array */ @@ -45,4 +78,104 @@ public static function spatialTypeNameProvider(): array 'without dot' => ['geography(Point,4326)'], ]; } + + /** + * @return array + */ + public static function parseEnumValuesFromConstraintProvider(): array + { + return [ + // Pattern 1: ANY with ARRAY pattern (most common in PostgreSQL) + 'ANY ARRAY with character varying' => [ + "CHECK ((status)::text = ANY ((ARRAY['active'::character varying, 'inactive'::character varying, 'pending'::character varying])::text[]))", + ['active', 'inactive', 'pending'], + ], + 'ANY ARRAY with text' => [ + "CHECK ((status)::text = ANY ((ARRAY['draft'::text, 'published'::text])::text[]))", + ['draft', 'published'], + ], + 'ANY ARRAY with mixed types' => [ + "CHECK ((priority)::text = ANY ((ARRAY['low'::character varying, 'medium'::text, 'high'::character varying])::text[]))", + ['low', 'medium', 'high'], + ], + 'ANY ARRAY case insensitive' => [ + "check ((status)::text = any ((array['Active'::character varying, 'INACTIVE'::character varying])::text[]))", + ['Active', 'INACTIVE'], + ], + 'ANY ARRAY with extra spaces' => [ + "CHECK ( ( status )::text = ANY ( ( ARRAY[ 'option1'::character varying , 'option2'::character varying ] )::text[] ) )", + ['option1', 'option2'], + ], + + // Pattern 2: Multiple OR conditions + 'OR conditions basic' => [ + "CHECK (((status)::text = 'active'::text) OR ((status)::text = 'inactive'::text))", + ['active', 'inactive'], + ], + 'OR conditions with more values' => [ + "CHECK (((status)::text = 'draft'::text) OR ((status)::text = 'review'::text) OR ((status)::text = 'published'::text))", + ['draft', 'review', 'published'], + ], + 'OR conditions case insensitive' => [ + "check (((status)::text = 'ACTIVE'::text) or ((status)::text = 'inactive'::text))", + ['ACTIVE', 'inactive'], + ], + 'OR conditions with character varying' => [ + "CHECK (((status)::character varying = 'yes'::character varying) OR ((status)::character varying = 'no'::character varying))", + ['yes', 'no'], + ], + 'OR conditions with duplicates' => [ + "CHECK (((status)::text = 'active'::text) OR ((status)::text = 'inactive'::text) OR ((status)::text = 'active'::text))", + ['active', 'inactive'], // Should remove duplicates + ], + + // Pattern 3: Simple IN clause + 'IN clause basic' => [ + "CHECK (status IN ('active', 'inactive', 'pending'))", + ['active', 'inactive', 'pending'], + ], + 'IN clause case insensitive' => [ + "check (status in ('YES', 'no', 'Maybe'))", + ['YES', 'no', 'Maybe'], + ], + 'IN clause with extra spaces' => [ + "CHECK ( status IN ( 'option1' , 'option2' , 'option3' ) )", + ['option1', 'option2', 'option3'], + ], + 'IN clause single value' => [ + "CHECK (status IN ('single'))", + ['single'], + ], + + // Edge cases and invalid patterns + "CHECK (((string IS NULL) OR ((string)::text ~ '^O\.[0-9]+$'::text)))" => [ + '', + [], + ], + 'empty constraint' => [ + '', + [], + ], + 'non-enum constraint' => [ + "CHECK (age > 18)", + [], + ], + 'malformed ARRAY pattern' => [ + "CHECK ((status)::text = ANY (ARRAY[missing quotes]))", + [], + ], + 'different column name in OR' => [ + "CHECK (((other_column)::text = 'value1'::text) OR ((other_column)::text = 'value2'::text))", + [], + ], + 'different column name in IN' => [ + "CHECK (other_column IN ('value1', 'value2'))", + [], + ], + 'no values in ARRAY' => [ + "CHECK ((status)::text = ANY ((ARRAY[])::text[]))", + [], + ], + ]; + } } diff --git a/tests/resources/database/migrations/general/2020_03_21_000000_expected_create_all_columns_table.php b/tests/resources/database/migrations/general/2020_03_21_000000_expected_create_all_columns_table.php index fab17780..aa4e0963 100644 --- a/tests/resources/database/migrations/general/2020_03_21_000000_expected_create_all_columns_table.php +++ b/tests/resources/database/migrations/general/2020_03_21_000000_expected_create_all_columns_table.php @@ -56,6 +56,7 @@ public function up() $table->double('double_default')->default(10.8); $table->enum('enum', ['easy', 'hard']); $table->enum('enum_default', ['easy', 'hard'])->default('easy'); + $table->enum('enum_special', ['IN', 'ANY', 'OR', 'BETWEEN', 'value::character']); $table->float('float'); $table->float('float_default')->default(10.8); $table->integer('integer');