From 963f612fb929796298d95cbbdc1ca952a6272337 Mon Sep 17 00:00:00 2001 From: kitloong Date: Sun, 22 Nov 2020 18:55:46 +0800 Subject: [PATCH 01/65] Added table collation support --- .../Generators/EnumField.php | 14 ++++- .../Generators/FieldGenerator.php | 8 +-- .../Generators/Modifier/CollationModifier.php | 41 +++++++++++++++ .../Generators/OtherField.php | 18 ++++++- .../Generators/SetField.php | 14 ++++- .../Generators/StringField.php | 15 +++++- .../MigrationMethod/ColumnModifier.php | 1 + .../MigrationsGeneratorSetting.php | 15 ++++++ .../Repositories/MySQLRepository.php | 13 +++++ .../MigrationsGenerator/Syntax/Table.php | 51 ++++++++++++++++++- 10 files changed, 178 insertions(+), 12 deletions(-) create mode 100644 src/KitLoong/MigrationsGenerator/Generators/Modifier/CollationModifier.php diff --git a/src/KitLoong/MigrationsGenerator/Generators/EnumField.php b/src/KitLoong/MigrationsGenerator/Generators/EnumField.php index a728fee6..32c8dcf3 100644 --- a/src/KitLoong/MigrationsGenerator/Generators/EnumField.php +++ b/src/KitLoong/MigrationsGenerator/Generators/EnumField.php @@ -7,27 +7,37 @@ namespace KitLoong\MigrationsGenerator\Generators; +use Doctrine\DBAL\Schema\Column; +use KitLoong\MigrationsGenerator\Generators\Modifier\CollationModifier; use KitLoong\MigrationsGenerator\Repositories\MySQLRepository; class EnumField { + private $collationModifier; + private $decorator; private $mysqlRepository; - public function __construct(Decorator $decorator, MySQLRepository $mySQLRepository) + public function __construct(CollationModifier $collationModifier, Decorator $decorator, MySQLRepository $mySQLRepository) { + $this->collationModifier = $collationModifier; $this->decorator = $decorator; $this->mysqlRepository = $mySQLRepository; } - public function makeField(string $tableName, array $field): array + public function makeField(string $tableName, array $field, Column $column): array { $value = $this->mysqlRepository->getEnumPresetValues($tableName, $field['field']); if ($value !== null) { $field['args'][] = $value; } + $collation = $this->collationModifier->generate($tableName, $column); + if ($collation !== '') { + $field['decorators'][] = $collation; + } + return $field; } } diff --git a/src/KitLoong/MigrationsGenerator/Generators/FieldGenerator.php b/src/KitLoong/MigrationsGenerator/Generators/FieldGenerator.php index d6087120..62309c3b 100644 --- a/src/KitLoong/MigrationsGenerator/Generators/FieldGenerator.php +++ b/src/KitLoong/MigrationsGenerator/Generators/FieldGenerator.php @@ -169,15 +169,15 @@ private function makeLaravelFieldTypeMethod( case DBALTypes::DOUBLE: return $this->decimalField->makeField($field, $column); case DBALTypes::ENUM: - return $this->enumField->makeField($tableName, $field); + return $this->enumField->makeField($tableName, $field, $column); case DBALTypes::GEOMETRY: return $this->geometryField->makeField($tableName, $field); case DBALTypes::SET: - return $this->setField->makeField($tableName, $field); + return $this->setField->makeField($tableName, $field, $column); case DBALTypes::STRING: - return $this->stringField->makeField($field, $column); + return $this->stringField->makeField($tableName, $field, $column); default: - return $this->otherField->makeField($field); + return $this->otherField->makeField($tableName, $field, $column); } } } diff --git a/src/KitLoong/MigrationsGenerator/Generators/Modifier/CollationModifier.php b/src/KitLoong/MigrationsGenerator/Generators/Modifier/CollationModifier.php new file mode 100644 index 00000000..6d84c366 --- /dev/null +++ b/src/KitLoong/MigrationsGenerator/Generators/Modifier/CollationModifier.php @@ -0,0 +1,41 @@ +decorator = $decorator; + } + + public function generate(string $tableName, Column $column): string + { + $setting = app(MigrationsGeneratorSetting::class); + $tableCollation = $setting->getSchema()->listTableDetails($tableName)->getOptions()['collation'] ?? null; + + $columnCollation = $column->getPlatformOptions()['collation'] ?? null; + if (!empty($column->getPlatformOptions()['collation'])) { + if ($columnCollation !== $tableCollation) { + return $this->decorator->decorate( + ColumnModifier::COLLATION, + [$this->decorator->columnDefaultToString($columnCollation)] + ); + } + } + + return ''; + } +} diff --git a/src/KitLoong/MigrationsGenerator/Generators/OtherField.php b/src/KitLoong/MigrationsGenerator/Generators/OtherField.php index bde2ecfa..afbee258 100644 --- a/src/KitLoong/MigrationsGenerator/Generators/OtherField.php +++ b/src/KitLoong/MigrationsGenerator/Generators/OtherField.php @@ -7,13 +7,29 @@ namespace KitLoong\MigrationsGenerator\Generators; +use Doctrine\DBAL\Schema\Column; +use KitLoong\MigrationsGenerator\Generators\Modifier\CollationModifier; + class OtherField { - public function makeField(array $field): array + private $collationModifier; + + public function __construct(CollationModifier $collationModifier) + { + $this->collationModifier = $collationModifier; + } + + public function makeField(string $tableName, array $field, Column $column): array { if (isset(FieldGenerator::$fieldTypeMap[$field['type']])) { $field['type'] = FieldGenerator::$fieldTypeMap[$field['type']]; } + + $collation = $this->collationModifier->generate($tableName, $column); + if ($collation !== '') { + $field['decorators'][] = $collation; + } + return $field; } } diff --git a/src/KitLoong/MigrationsGenerator/Generators/SetField.php b/src/KitLoong/MigrationsGenerator/Generators/SetField.php index 4c235854..8322b06f 100644 --- a/src/KitLoong/MigrationsGenerator/Generators/SetField.php +++ b/src/KitLoong/MigrationsGenerator/Generators/SetField.php @@ -7,27 +7,37 @@ namespace KitLoong\MigrationsGenerator\Generators; +use Doctrine\DBAL\Schema\Column; +use KitLoong\MigrationsGenerator\Generators\Modifier\CollationModifier; use KitLoong\MigrationsGenerator\Repositories\MySQLRepository; class SetField { + private $collationModifier; + private $decorator; private $mysqlRepository; - public function __construct(Decorator $decorator, MySQLRepository $mySQLRepository) + public function __construct(CollationModifier $collationModifier, Decorator $decorator, MySQLRepository $mySQLRepository) { + $this->collationModifier = $collationModifier; $this->decorator = $decorator; $this->mysqlRepository = $mySQLRepository; } - public function makeField(string $tableName, array $field): array + public function makeField(string $tableName, array $field, Column $column): array { $value = $this->mysqlRepository->getSetPresetValues($tableName, $field['field']); if ($value !== null) { $field['args'][] = $value; } + $collation = $this->collationModifier->generate($tableName, $column); + if ($collation !== '') { + $field['decorators'][] = $collation; + } + return $field; } } diff --git a/src/KitLoong/MigrationsGenerator/Generators/StringField.php b/src/KitLoong/MigrationsGenerator/Generators/StringField.php index 62cafb39..c6edb4db 100644 --- a/src/KitLoong/MigrationsGenerator/Generators/StringField.php +++ b/src/KitLoong/MigrationsGenerator/Generators/StringField.php @@ -9,12 +9,20 @@ use Doctrine\DBAL\Schema\Column; use Illuminate\Database\Schema\Builder; +use KitLoong\MigrationsGenerator\Generators\Modifier\CollationModifier; use KitLoong\MigrationsGenerator\MigrationMethod\ColumnName; use KitLoong\MigrationsGenerator\MigrationMethod\ColumnType; class StringField { - public function makeField(array $field, Column $column): array + private $collationModifier; + + public function __construct(CollationModifier $collationModifier) + { + $this->collationModifier = $collationModifier; + } + + public function makeField(string $tableName, array $field, Column $column): array { if ($field['field'] === ColumnName::REMEMBER_TOKEN && $column->getLength() === 100 && !$column->getFixed()) { $field['type'] = ColumnType::REMEMBER_TOKEN; @@ -32,6 +40,11 @@ public function makeField(array $field, Column $column): array $field['args'][] = $column->getLength(); } + $collation = $this->collationModifier->generate($tableName, $column); + if ($collation !== '') { + $field['decorators'][] = $collation; + } + return $field; } } diff --git a/src/KitLoong/MigrationsGenerator/MigrationMethod/ColumnModifier.php b/src/KitLoong/MigrationsGenerator/MigrationMethod/ColumnModifier.php index d41d5c7c..96193d33 100644 --- a/src/KitLoong/MigrationsGenerator/MigrationMethod/ColumnModifier.php +++ b/src/KitLoong/MigrationsGenerator/MigrationMethod/ColumnModifier.php @@ -9,6 +9,7 @@ final class ColumnModifier { + const COLLATION = 'collation'; const COMMENT = 'comment'; const DEFAULT = 'default'; const NULLABLE = 'nullable'; diff --git a/src/KitLoong/MigrationsGenerator/MigrationsGeneratorSetting.php b/src/KitLoong/MigrationsGenerator/MigrationsGeneratorSetting.php index 18d30436..3616c845 100644 --- a/src/KitLoong/MigrationsGenerator/MigrationsGeneratorSetting.php +++ b/src/KitLoong/MigrationsGenerator/MigrationsGeneratorSetting.php @@ -7,6 +7,7 @@ namespace KitLoong\MigrationsGenerator; +use Doctrine\DBAL\Schema\AbstractSchemaManager; use Illuminate\Database\Connection; use Illuminate\Support\Facades\DB; use KitLoong\MigrationsGenerator\Generators\Platform; @@ -23,6 +24,11 @@ class MigrationsGeneratorSetting */ private $platform; + /** + * @var AbstractSchemaManager + */ + private $schema; + /** * @var boolean */ @@ -50,6 +56,7 @@ public function setConnection(string $connection): void /** @var \Doctrine\DBAL\Connection $doctConn */ $doctConn = $this->connection->getDoctrineConnection(); + $this->schema = $doctConn->getSchemaManager(); $classPath = explode('\\', get_class($doctConn->getDatabasePlatform())); $platform = end($classPath); @@ -111,4 +118,12 @@ public function setIgnoreForeignKeyNames(bool $ignoreForeignKeyNames): void { $this->ignoreForeignKeyNames = $ignoreForeignKeyNames; } + + /** + * @return AbstractSchemaManager + */ + public function getSchema(): AbstractSchemaManager + { + return $this->schema; + } } diff --git a/src/KitLoong/MigrationsGenerator/Repositories/MySQLRepository.php b/src/KitLoong/MigrationsGenerator/Repositories/MySQLRepository.php index 4726b7b3..0a0d6d55 100644 --- a/src/KitLoong/MigrationsGenerator/Repositories/MySQLRepository.php +++ b/src/KitLoong/MigrationsGenerator/Repositories/MySQLRepository.php @@ -11,6 +11,19 @@ class MySQLRepository { + /** + * @return array [ + * 'charset' => string, + * 'collation' => string + * ] + */ + public function getDatabaseCollation(): array + { + $setting = app(MigrationsGeneratorSetting::class); + $columns = $setting->getConnection()->select("SELECT @@character_set_database, @@collation_database"); + return ['charset' => $columns[0]->{'@@character_set_database'}, 'collation' => $columns[0]->{'@@collation_database'}]; + } + public function getEnumPresetValues(string $table, string $columnName): ?string { /** @var MigrationsGeneratorSetting $setting */ diff --git a/src/Xethron/MigrationsGenerator/Syntax/Table.php b/src/Xethron/MigrationsGenerator/Syntax/Table.php index 11a63511..9e70fedd 100644 --- a/src/Xethron/MigrationsGenerator/Syntax/Table.php +++ b/src/Xethron/MigrationsGenerator/Syntax/Table.php @@ -1,7 +1,11 @@ mySQLRepository = $mySQLRepository; + } + public function run(array $fields, string $table, string $connection, $method = 'table'): string { $table = $this->decorator->tableWithoutPrefix($table); @@ -24,7 +37,17 @@ public function run(array $fields, string $table, string $connection, $method = } $compiled = $this->compiler->compile($this->getTemplate($method), ['table' => $table, 'method' => $method]); - return $this->replaceFieldsWith($this->getItems($fields), $compiled); + + $content = $this->getItems($fields); + + if ($method === 'create') { + $tableCollation = $this->getTableCollation($table); + if (!empty($tableCollation)) { + $content = array_merge($tableCollation, $content); + } + } + + return $this->replaceFieldsWith($content, $compiled); } /** @@ -42,6 +65,30 @@ protected function getItems(array $items): array return $result; } + /** + * Get table collation migration lines if not equal to DB collation. + * + * @param string $tableName + * @return array|string[] + */ + protected function getTableCollation(string $tableName): array + { + $setting = app(MigrationsGeneratorSetting::class); + if ($setting->getPlatform() === Platform::MYSQL) { + $dbCollation = $this->mySQLRepository->getDatabaseCollation(); + $tableCollation = $setting->getSchema()->listTableDetails($tableName)->getOptions()['collation']; + + if ($tableCollation !== $dbCollation['collation']) { + $tableCharset = explode('_', $tableCollation)[0]; + return [ + '$table->charset = \''.$tableCharset.'\';', + '$table->collation = \''.$tableCollation.'\';' + ]; + } + } + return []; + } + /** * @param array $item * @return string From 87b850285d96db4361fcb6e67a2fc123fafb04d5 Mon Sep 17 00:00:00 2001 From: kitloong Date: Sun, 22 Nov 2020 18:56:00 +0800 Subject: [PATCH 02/65] Ignore storage directory --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 3c5cab82..f1859856 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ report composer.lock build .idea -.phpunit.result.cache \ No newline at end of file +.phpunit.result.cache +storage From aebf95eafb2bced8701b978f4f9d463f35da7378 Mon Sep 17 00:00:00 2001 From: kitloong Date: Sun, 22 Nov 2020 20:21:49 +0800 Subject: [PATCH 03/65] Add collation to rememberToken field --- .../Generators/StringField.php | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/src/KitLoong/MigrationsGenerator/Generators/StringField.php b/src/KitLoong/MigrationsGenerator/Generators/StringField.php index c6edb4db..4cad6af4 100644 --- a/src/KitLoong/MigrationsGenerator/Generators/StringField.php +++ b/src/KitLoong/MigrationsGenerator/Generators/StringField.php @@ -28,16 +28,14 @@ public function makeField(string $tableName, array $field, Column $column): arra $field['type'] = ColumnType::REMEMBER_TOKEN; $field['field'] = null; $field['args'] = []; - - return $field; - } - - if ($column->getFixed()) { - $field['type'] = ColumnType::CHAR; - } - - if ($column->getLength() && $column->getLength() !== Builder::$defaultStringLength) { - $field['args'][] = $column->getLength(); + } else { + if ($column->getFixed()) { + $field['type'] = ColumnType::CHAR; + } + + if ($column->getLength() && $column->getLength() !== Builder::$defaultStringLength) { + $field['args'][] = $column->getLength(); + } } $collation = $this->collationModifier->generate($tableName, $column); From 3fa00fa5ad646bc1c29774f7c5ac0ac42670e798 Mon Sep 17 00:00:00 2001 From: kitloong Date: Sun, 22 Nov 2020 20:22:49 +0800 Subject: [PATCH 04/65] Fixed failed tests --- .../MigrationGeneratorSettingTest.php | 34 +++++++-- .../Generators/EnumFieldTest.php | 40 ++++++++--- .../Generators/FieldGeneratorTest.php | 10 +-- .../Generators/OtherFieldTest.php | 22 +++++- .../Generators/SetFieldTest.php | 33 +++++++-- .../Generators/StringFieldTest.php | 69 ++++++++++++++----- 6 files changed, 162 insertions(+), 46 deletions(-) diff --git a/tests/KitLoong/MigrationGeneratorSettingTest.php b/tests/KitLoong/MigrationGeneratorSettingTest.php index 66d5c806..89f54ff8 100644 --- a/tests/KitLoong/MigrationGeneratorSettingTest.php +++ b/tests/KitLoong/MigrationGeneratorSettingTest.php @@ -12,6 +12,7 @@ use Doctrine\DBAL\Platforms\PostgreSQL100Platform; use Doctrine\DBAL\Platforms\SqlitePlatform; use Doctrine\DBAL\Platforms\SQLServer2012Platform; +use Doctrine\DBAL\Schema\AbstractSchemaManager; use Illuminate\Support\Facades\DB; use KitLoong\MigrationsGenerator\Generators\Platform; use KitLoong\MigrationsGenerator\MigrationsGeneratorSetting; @@ -21,9 +22,9 @@ class MigrationGeneratorSettingTest extends TestCase { public function testSetConnectionTryMysql() { - $dbconn = Mockery::mock(Connection::class); + $dbconn = $this->mockConnection(); $dbPlatform = Mockery::mock(MySqlPlatform::class); - $dbconn->shouldReceive('getDoctrineConnection->getDatabasePlatform') + $dbconn->shouldReceive('getDoctrineConnection->getSchemaManager->getDatabasePlatform') ->andReturn($dbPlatform); DB::shouldReceive('connection')->with('mysql')->andReturn($dbconn); @@ -36,8 +37,9 @@ public function testSetConnectionTryMysql() public function testSetConnectionTryPostreSql() { - $dbconn = Mockery::mock(Connection::class); + $dbconn = $this->mockConnection(); $dbPlatform = Mockery::mock(PostgreSQL100Platform::class); + $dbconn->shouldReceive('getDoctrineConnection->getDatabasePlatform') ->andReturn($dbPlatform); @@ -51,8 +53,9 @@ public function testSetConnectionTryPostreSql() public function testSetConnectionTrySqlServer() { - $dbconn = Mockery::mock(Connection::class); + $dbconn = $this->mockConnection(); $dbPlatform = Mockery::mock(SQLServer2012Platform::class); + $dbconn->shouldReceive('getDoctrineConnection->getDatabasePlatform') ->andReturn($dbPlatform); @@ -66,7 +69,7 @@ public function testSetConnectionTrySqlServer() public function testSetConnectionTrySqlite() { - $dbconn = Mockery::mock(Connection::class); + $dbconn = $this->mockConnection(); $dbPlatform = Mockery::mock(SqlitePlatform::class); $dbconn->shouldReceive('getDoctrineConnection->getDatabasePlatform') ->andReturn($dbPlatform); @@ -81,8 +84,9 @@ public function testSetConnectionTrySqlite() public function testSetConnectionTryOthers() { - $dbconn = Mockery::mock(Connection::class); + $dbconn = $this->mockConnection(); $dbPlatform = Mockery::mock(OraclePlatform::class); + $dbconn->shouldReceive('getDoctrineConnection->getDatabasePlatform') ->andReturn($dbPlatform); @@ -93,4 +97,22 @@ public function testSetConnectionTryOthers() $this->assertSame(Platform::OTHERS, $setting->getPlatform()); } + + /** + * @return \Doctrine\DBAL\Connection|\Mockery\Mock + */ + private function mockConnection(): Connection + { + $dbconn = Mockery::mock(Connection::class); + + $schemaManager = Mockery::mock(AbstractSchemaManager::class); + + $dbconn->shouldReceive('getDoctrineConnection') + ->andReturnSelf(); + + $dbconn->shouldReceive('getSchemaManager') + ->andReturn($schemaManager); + + return $dbconn; + } } diff --git a/tests/KitLoong/MigrationsGenerator/Generators/EnumFieldTest.php b/tests/KitLoong/MigrationsGenerator/Generators/EnumFieldTest.php index 76f69796..2d492baa 100644 --- a/tests/KitLoong/MigrationsGenerator/Generators/EnumFieldTest.php +++ b/tests/KitLoong/MigrationsGenerator/Generators/EnumFieldTest.php @@ -7,8 +7,11 @@ namespace Tests\KitLoong\MigrationsGenerator\Generators; +use Doctrine\DBAL\Schema\Column; use KitLoong\MigrationsGenerator\Generators\EnumField; +use KitLoong\MigrationsGenerator\Generators\Modifier\CollationModifier; use KitLoong\MigrationsGenerator\Repositories\MySQLRepository; +use Mockery; use Mockery\MockInterface; use Tests\KitLoong\TestCase; @@ -23,16 +26,27 @@ public function testMakeField() ->once(); }); - /** @var EnumField $enumField */ - $enumField = resolve(EnumField::class); - $field = [ 'field' => 'enum_field', 'args' => [] ]; - $field = $enumField->makeField('table', $field); - $this->assertSame(["['value1', 'value2' , 'value3']"], $field['args']); + $column = Mockery::mock(Column::class); + $this->mock(CollationModifier::class, function (MockInterface $mock) use ($column) { + $mock->shouldReceive('generate') + ->with('table', $column) + ->andReturn('collation') + ->once(); + }); + + /** @var EnumField $enumField */ + $enumField = resolve(EnumField::class); + $output = $enumField->makeField('table', $field, $column); + $this->assertSame([ + 'field' => 'enum_field', + 'args' => ["['value1', 'value2' , 'value3']"], + 'decorators' => ['collation'] + ], $output); } public function testMakeFieldValueIsEmpty() @@ -44,15 +58,23 @@ public function testMakeFieldValueIsEmpty() ->once(); }); - /** @var EnumField $enumField */ - $enumField = resolve(EnumField::class); - $field = [ 'field' => 'enum_field', 'args' => [] ]; - $field = $enumField->makeField('table', $field); + $column = Mockery::mock(Column::class); + $this->mock(CollationModifier::class, function (MockInterface $mock) use ($column) { + $mock->shouldReceive('generate') + ->with('table', $column) + ->andReturn('collation') + ->once(); + }); + + /** @var EnumField $enumField */ + $enumField = resolve(EnumField::class); + + $field = $enumField->makeField('table', $field, $column); $this->assertEmpty($field['args']); } } diff --git a/tests/KitLoong/MigrationsGenerator/Generators/FieldGeneratorTest.php b/tests/KitLoong/MigrationsGenerator/Generators/FieldGeneratorTest.php index ce20a4c0..8f630e4e 100644 --- a/tests/KitLoong/MigrationsGenerator/Generators/FieldGeneratorTest.php +++ b/tests/KitLoong/MigrationsGenerator/Generators/FieldGeneratorTest.php @@ -297,7 +297,7 @@ public function testGenerateEnum() $returnField = $field; $returnField['field'] = 'returned'; $mock->shouldReceive('makeField') - ->with('table', $field) + ->with('table', $field, $column) ->andReturn($returnField); }); @@ -397,7 +397,7 @@ public function testGenerateSet() $returnField = $field; $returnField['field'] = 'returned'; $mock->shouldReceive('makeField') - ->with('table', $field) + ->with('table', $field, $column) ->andReturn($returnField); }); @@ -447,7 +447,7 @@ public function testGenerateString() $returnField = $field; $returnField['field'] = 'returned'; $mock->shouldReceive('makeField') - ->with($field, $column) + ->with('table', $field, $column) ->andReturn($returnField); }); @@ -493,11 +493,11 @@ public function testGenerateOtherType() 'decorators' => [] ]; - $this->mock(OtherField::class, function (MockInterface $mock) use ($field) { + $this->mock(OtherField::class, function (MockInterface $mock) use ($field, $column) { $returnField = $field; $returnField['field'] = 'returned'; $mock->shouldReceive('makeField') - ->with($field) + ->with('table', $field, $column) ->andReturn($returnField); }); diff --git a/tests/KitLoong/MigrationsGenerator/Generators/OtherFieldTest.php b/tests/KitLoong/MigrationsGenerator/Generators/OtherFieldTest.php index 5b172dce..adc48c35 100644 --- a/tests/KitLoong/MigrationsGenerator/Generators/OtherFieldTest.php +++ b/tests/KitLoong/MigrationsGenerator/Generators/OtherFieldTest.php @@ -7,23 +7,39 @@ namespace Tests\KitLoong\MigrationsGenerator\Generators; +use Doctrine\DBAL\Schema\Column; +use KitLoong\MigrationsGenerator\Generators\Modifier\CollationModifier; use KitLoong\MigrationsGenerator\Generators\OtherField; use KitLoong\MigrationsGenerator\MigrationMethod\ColumnType; +use Mockery; +use Mockery\MockInterface; use Tests\KitLoong\TestCase; class OtherFieldTest extends TestCase { public function testMakeField() { + $column = Mockery::mock(Column::class); + $this->mock(CollationModifier::class, function (MockInterface $mock) use ($column) { + $mock->shouldReceive('generate') + ->with('table', $column) + ->andReturn('collation') + ->once(); + }); + /** @var OtherField $otherField */ - $otherField = resolve(OtherField::class); + $otherField = app(OtherField::class); $field = [ 'field' => 'field', 'type' => 'blob' ]; - $field = $otherField->makeField($field); - $this->assertSame(ColumnType::BINARY, $field['type']); + $output = $otherField->makeField('table', $field, $column); + $this->assertSame([ + 'field' => 'field', + 'type' => ColumnType::BINARY, + 'decorators' => ['collation'] + ], $output); } } diff --git a/tests/KitLoong/MigrationsGenerator/Generators/SetFieldTest.php b/tests/KitLoong/MigrationsGenerator/Generators/SetFieldTest.php index 56cbc601..8963221d 100644 --- a/tests/KitLoong/MigrationsGenerator/Generators/SetFieldTest.php +++ b/tests/KitLoong/MigrationsGenerator/Generators/SetFieldTest.php @@ -7,8 +7,11 @@ namespace Tests\KitLoong\MigrationsGenerator\Generators; +use Doctrine\DBAL\Schema\Column; +use KitLoong\MigrationsGenerator\Generators\Modifier\CollationModifier; use KitLoong\MigrationsGenerator\Generators\SetField; use KitLoong\MigrationsGenerator\Repositories\MySQLRepository; +use Mockery; use Mockery\MockInterface; use Tests\KitLoong\TestCase; @@ -23,16 +26,28 @@ public function testMakeField() ->once(); }); + $column = Mockery::mock(Column::class); + $this->mock(CollationModifier::class, function (MockInterface $mock) use ($column) { + $mock->shouldReceive('generate') + ->with('table', $column) + ->andReturn('collation') + ->once(); + }); + /** @var SetField $setField */ - $setField = resolve(SetField::class); + $setField = app(SetField::class); $field = [ 'field' => 'set_field', 'args' => [] ]; - $field = $setField->makeField('table', $field); - $this->assertSame(["['value1', 'value2' , 'value3']"], $field['args']); + $output = $setField->makeField('table', $field, $column); + $this->assertSame([ + 'field' => 'set_field', + 'args' => ["['value1', 'value2' , 'value3']"], + 'decorators' => ['collation'] + ], $output); } public function testMakeFieldValueIsEmpty() @@ -44,15 +59,23 @@ public function testMakeFieldValueIsEmpty() ->once(); }); + $column = Mockery::mock(Column::class); + $this->mock(CollationModifier::class, function (MockInterface $mock) use ($column) { + $mock->shouldReceive('generate') + ->with('table', $column) + ->andReturn('collation') + ->once(); + }); + /** @var SetField $setField */ - $setField = resolve(SetField::class); + $setField = app(SetField::class); $field = [ 'field' => 'set_field', 'args' => [] ]; - $field = $setField->makeField('table', $field); + $field = $setField->makeField('table', $field, $column); $this->assertEmpty($field['args']); } } diff --git a/tests/KitLoong/MigrationsGenerator/Generators/StringFieldTest.php b/tests/KitLoong/MigrationsGenerator/Generators/StringFieldTest.php index d5551db1..77bc3349 100644 --- a/tests/KitLoong/MigrationsGenerator/Generators/StringFieldTest.php +++ b/tests/KitLoong/MigrationsGenerator/Generators/StringFieldTest.php @@ -8,18 +8,17 @@ namespace Tests\KitLoong\MigrationsGenerator\Generators; use Doctrine\DBAL\Schema\Column; +use KitLoong\MigrationsGenerator\Generators\Modifier\CollationModifier; use KitLoong\MigrationsGenerator\Generators\StringField; use KitLoong\MigrationsGenerator\MigrationMethod\ColumnType; use Mockery; +use Mockery\MockInterface; use Tests\KitLoong\TestCase; class StringFieldTest extends TestCase { public function testMakeFieldIsChar() { - /** @var StringField $stringField */ - $stringField = resolve(StringField::class); - $field = [ 'field' => 'field', 'type' => 'string', @@ -32,19 +31,28 @@ public function testMakeFieldIsChar() $column->shouldReceive('getLength') ->andReturn(50); - $field = $stringField->makeField($field, $column); + $this->mock(CollationModifier::class, function (MockInterface $mock) use ($column) { + $mock->shouldReceive('generate') + ->with('table', $column) + ->andReturn('collation') + ->once(); + }); + + /** @var StringField $stringField */ + $stringField = app(StringField::class); + + $output = $stringField->makeField('table', $field, $column); + $this->assertSame([ 'field' => 'field', 'type' => ColumnType::CHAR, - 'args' => [50] - ], $field); + 'args' => [50], + 'decorators' => ['collation'] + ], $output); } public function testMakeFieldIsRememberToken() { - /** @var StringField $stringField */ - $stringField = resolve(StringField::class); - $field = [ 'field' => 'remember_token', 'type' => 'string', @@ -57,17 +65,25 @@ public function testMakeFieldIsRememberToken() $column->shouldReceive('getLength') ->andReturn(100); - $field = $stringField->makeField($field, $column); + $this->mock(CollationModifier::class, function (MockInterface $mock) use ($column) { + $mock->shouldReceive('generate') + ->with('table', $column) + ->andReturn('collation') + ->once(); + }); + + /** @var StringField $stringField */ + $stringField = app(StringField::class); + + $field = $stringField->makeField('table', $field, $column); $this->assertSame(ColumnType::REMEMBER_TOKEN, $field['type']); $this->assertNull($field['field']); $this->assertEmpty($field['args']); + $this->assertSame(['collation'], $field['decorators']); } public function testMakeFieldWith255Length() { - /** @var StringField $stringField */ - $stringField = resolve(StringField::class); - $field = [ 'field' => 'field', 'type' => 'string', @@ -80,7 +96,17 @@ public function testMakeFieldWith255Length() $column->shouldReceive('getLength') ->andReturn(255); - $field = $stringField->makeField($field, $column); + $this->mock(CollationModifier::class, function (MockInterface $mock) use ($column) { + $mock->shouldReceive('generate') + ->with('table', $column) + ->andReturn('collation') + ->once(); + }); + + /** @var StringField $stringField */ + $stringField = app(StringField::class); + + $field = $stringField->makeField('table', $field, $column); $this->assertSame('string', $field['type']); $this->assertSame('field', $field['field']); $this->assertEmpty($field['args']); @@ -88,9 +114,6 @@ public function testMakeFieldWith255Length() public function testMakeFieldWith100Length() { - /** @var StringField $stringField */ - $stringField = resolve(StringField::class); - $field = [ 'field' => 'field', 'type' => 'string', @@ -103,7 +126,17 @@ public function testMakeFieldWith100Length() $column->shouldReceive('getLength') ->andReturn(100); - $field = $stringField->makeField($field, $column); + $this->mock(CollationModifier::class, function (MockInterface $mock) use ($column) { + $mock->shouldReceive('generate') + ->with('table', $column) + ->andReturn('collation') + ->once(); + }); + + /** @var StringField $stringField */ + $stringField = app(StringField::class); + + $field = $stringField->makeField('table', $field, $column); $this->assertSame('string', $field['type']); $this->assertSame('field', $field['field']); $this->assertSame([100], $field['args']); From ba91ebc9d597c3131ecb6634c6128bcf25a9515a Mon Sep 17 00:00:00 2001 From: kitloong Date: Fri, 1 Jan 2021 11:57:07 +0800 Subject: [PATCH 05/65] Add useCurrentOnUpdate --- .../Generators/DatetimeField.php | 14 +++- .../Generators/FieldGenerator.php | 2 +- .../MigrationMethod/ColumnModifier.php | 1 + .../Repositories/MySQLRepository.php | 18 +++++ .../Generators/DatetimeFieldTest.php | 73 ++++++++++++++----- .../Generators/FieldGeneratorTest.php | 2 +- 6 files changed, 86 insertions(+), 24 deletions(-) diff --git a/src/KitLoong/MigrationsGenerator/Generators/DatetimeField.php b/src/KitLoong/MigrationsGenerator/Generators/DatetimeField.php index 792b53bd..a20e1637 100644 --- a/src/KitLoong/MigrationsGenerator/Generators/DatetimeField.php +++ b/src/KitLoong/MigrationsGenerator/Generators/DatetimeField.php @@ -11,17 +11,21 @@ use KitLoong\MigrationsGenerator\MigrationMethod\ColumnModifier; use KitLoong\MigrationsGenerator\MigrationMethod\ColumnName; use KitLoong\MigrationsGenerator\MigrationMethod\ColumnType; +use KitLoong\MigrationsGenerator\Repositories\MySQLRepository; +use KitLoong\MigrationsGenerator\Types\DBALTypes; class DatetimeField { private $decorator; + private $mySQLRepository; - public function __construct(Decorator $decorator) + public function __construct(Decorator $decorator, MySQLRepository $mySQLRepository) { $this->decorator = $decorator; + $this->mySQLRepository = $mySQLRepository; } - public function makeField(array $field, Column $column, bool $useTimestamps): array + public function makeField(string $table, array $field, Column $column, bool $useTimestamps): array { if ($useTimestamps) { if ($field['field'] === ColumnName::CREATED_AT) { @@ -47,6 +51,12 @@ public function makeField(array $field, Column $column, bool $useTimestamps): ar } $field['args'][] = $column->getLength(); } + + if ($column->getType()->getName() === DBALTypes::TIMESTAMP) { + if ($this->mySQLRepository->useOnUpdateCurrentTimestamp($table, $column->getName())) { + $field['decorators'][] = ColumnModifier::USE_CURRENT_ON_UPDATE; + } + } return $field; } diff --git a/src/KitLoong/MigrationsGenerator/Generators/FieldGenerator.php b/src/KitLoong/MigrationsGenerator/Generators/FieldGenerator.php index 62309c3b..5ac9090f 100644 --- a/src/KitLoong/MigrationsGenerator/Generators/FieldGenerator.php +++ b/src/KitLoong/MigrationsGenerator/Generators/FieldGenerator.php @@ -163,7 +163,7 @@ private function makeLaravelFieldTypeMethod( case DBALTypes::DATETIME_MUTABLE: case DBALTypes::TIMESTAMP: case DBALTypes::TIME_MUTABLE: - return $this->datetimeField->makeField($field, $column, $useTimestamps); + return $this->datetimeField->makeField($tableName, $field, $column, $useTimestamps); case DBALTypes::DECIMAL: case DBALTypes::FLOAT: case DBALTypes::DOUBLE: diff --git a/src/KitLoong/MigrationsGenerator/MigrationMethod/ColumnModifier.php b/src/KitLoong/MigrationsGenerator/MigrationMethod/ColumnModifier.php index 96193d33..f28febbe 100644 --- a/src/KitLoong/MigrationsGenerator/MigrationMethod/ColumnModifier.php +++ b/src/KitLoong/MigrationsGenerator/MigrationMethod/ColumnModifier.php @@ -15,4 +15,5 @@ final class ColumnModifier const NULLABLE = 'nullable'; const UNSIGNED = 'unsigned'; const USE_CURRENT = 'useCurrent'; + const USE_CURRENT_ON_UPDATE = 'useCurrentOnUpdate'; } diff --git a/src/KitLoong/MigrationsGenerator/Repositories/MySQLRepository.php b/src/KitLoong/MigrationsGenerator/Repositories/MySQLRepository.php index 0a0d6d55..740f3815 100644 --- a/src/KitLoong/MigrationsGenerator/Repositories/MySQLRepository.php +++ b/src/KitLoong/MigrationsGenerator/Repositories/MySQLRepository.php @@ -57,6 +57,24 @@ public function getSetPresetValues(string $table, string $columnName): ?string return null; } + public function useOnUpdateCurrentTimestamp(string $table, string $columnName): bool + { + $setting = app(MigrationsGeneratorSetting::class); + + $column = $setting->getConnection() + ->select( + "SHOW COLUMNS FROM `${table}` + WHERE Field = '${columnName}' + AND Type = 'timestamp' + AND EXTRA='on update CURRENT_TIMESTAMP'" + ); + if (count($column) > 0) { + return true; + } + + return false; + } + private function spaceAfterComma(string $value): string { return str_replace("','", "', '", $value); diff --git a/tests/KitLoong/MigrationsGenerator/Generators/DatetimeFieldTest.php b/tests/KitLoong/MigrationsGenerator/Generators/DatetimeFieldTest.php index 0d9672a6..774a40a6 100644 --- a/tests/KitLoong/MigrationsGenerator/Generators/DatetimeFieldTest.php +++ b/tests/KitLoong/MigrationsGenerator/Generators/DatetimeFieldTest.php @@ -12,13 +12,37 @@ use KitLoong\MigrationsGenerator\MigrationMethod\ColumnModifier; use KitLoong\MigrationsGenerator\MigrationMethod\ColumnName; use KitLoong\MigrationsGenerator\MigrationMethod\ColumnType; +use KitLoong\MigrationsGenerator\Repositories\MySQLRepository; +use KitLoong\MigrationsGenerator\Types\DBALTypes; use Mockery; +use Mockery\MockInterface; use Tests\KitLoong\TestCase; class DatetimeFieldTest extends TestCase { public function testMakeFieldIsSoftDeletes() { + $column = Mockery::mock(Column::class); + $column->shouldReceive('getNotnull') + ->andReturn(false) + ->once(); + $column->shouldReceive('getLength') + ->andReturn(2); + $column->shouldReceive('getType->getName') + ->andReturn(DBALTypes::TIMESTAMP) + ->once(); + $column->shouldReceive('getName') + ->withNoArgs() + ->andReturn(ColumnName::DELETED_AT) + ->once(); + + $this->mock(MySQLRepository::class, function (MockInterface $mock) { + $mock->shouldReceive('useOnUpdateCurrentTimestamp') + ->with('table', ColumnName::DELETED_AT) + ->andReturnFalse() + ->once(); + }); + /** @var DatetimeField $datetimeField */ $datetimeField = resolve(DatetimeField::class); @@ -28,14 +52,7 @@ public function testMakeFieldIsSoftDeletes() 'args' => [] ]; - $column = Mockery::mock(Column::class); - $column->shouldReceive('getNotnull') - ->andReturn(false) - ->once(); - $column->shouldReceive('getLength') - ->andReturn(2); - - $field = $datetimeField->makeField($field, $column, false); + $field = $datetimeField->makeField('table', $field, $column, false); $this->assertSame(ColumnType::SOFT_DELETES, $field['type']); $this->assertSame(ColumnName::DELETED_AT, $field['field']); $this->assertSame([2], $field['args']); @@ -43,20 +60,33 @@ public function testMakeFieldIsSoftDeletes() public function testMakeFieldIsTimestamps() { + $column = Mockery::mock(Column::class); + $column->shouldReceive('getLength') + ->andReturn(2); + $column->shouldReceive('getType->getName') + ->andReturn(DBALTypes::TIMESTAMP) + ->once(); + $column->shouldReceive('getName') + ->withNoArgs() + ->andReturn(ColumnName::UPDATED_AT) + ->once(); + + $this->mock(MySQLRepository::class, function (MockInterface $mock) { + $mock->shouldReceive('useOnUpdateCurrentTimestamp') + ->with('table', ColumnName::UPDATED_AT) + ->andReturnFalse() + ->once(); + }); + /** @var DatetimeField $datetimeField */ $datetimeField = resolve(DatetimeField::class); - $field = [ 'field' => ColumnName::UPDATED_AT, 'type' => 'timestamp', 'args' => [] ]; - $column = Mockery::mock(Column::class); - $column->shouldReceive('getLength') - ->andReturn(2); - - $field = $datetimeField->makeField($field, $column, true); + $field = $datetimeField->makeField('table', $field, $column, true); $this->assertSame(ColumnType::TIMESTAMPS, $field['type']); $this->assertNull($field['field']); $this->assertSame([2], $field['args']); @@ -64,6 +94,13 @@ public function testMakeFieldIsTimestamps() public function testMakeFieldIsDatetime() { + $column = Mockery::mock(Column::class); + $column->shouldReceive('getLength') + ->andReturn(2); + $column->shouldReceive('getType->getName') + ->andReturn(DBALTypes::DATETIME_MUTABLE) + ->once(); + /** @var DatetimeField $datetimeField */ $datetimeField = resolve(DatetimeField::class); @@ -73,11 +110,7 @@ public function testMakeFieldIsDatetime() 'args' => [] ]; - $column = Mockery::mock(Column::class); - $column->shouldReceive('getLength') - ->andReturn(2); - - $field = $datetimeField->makeField($field, $column, false); + $field = $datetimeField->makeField('table', $field, $column, false); $this->assertSame(ColumnType::DATETIME, $field['type']); $this->assertSame([2], $field['args']); } @@ -93,7 +126,7 @@ public function testMakeFieldSkipCreatedAtWhenIsTimestamps() 'args' => [] ]; $column = Mockery::mock(Column::class); - $field = $datetimeField->makeField($field, $column, true); + $field = $datetimeField->makeField('table', $field, $column, true); $this->assertEmpty($field); } diff --git a/tests/KitLoong/MigrationsGenerator/Generators/FieldGeneratorTest.php b/tests/KitLoong/MigrationsGenerator/Generators/FieldGeneratorTest.php index 8f630e4e..8a0efcd5 100644 --- a/tests/KitLoong/MigrationsGenerator/Generators/FieldGeneratorTest.php +++ b/tests/KitLoong/MigrationsGenerator/Generators/FieldGeneratorTest.php @@ -195,7 +195,7 @@ public function testGenerateDatetime() $returnField = $field; $returnField['field'] = 'returned'; $mock->shouldReceive('makeField') - ->with($field, $column, false) + ->with('table', $field, $column, false) ->andReturn($returnField); }); From cce0c2c072907bde55b28addc5be4adfc09ab718 Mon Sep 17 00:00:00 2001 From: kitloong Date: Fri, 1 Jan 2021 11:58:00 +0800 Subject: [PATCH 06/65] Rename to basePath --- tests/KitLoong/TestCase.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/KitLoong/TestCase.php b/tests/KitLoong/TestCase.php index 2f7e529f..a82fb7e5 100644 --- a/tests/KitLoong/TestCase.php +++ b/tests/KitLoong/TestCase.php @@ -36,7 +36,7 @@ protected function setUp(): void parent::setUp(); } - protected function packageBasePath(string $path): string + protected function basePath(string $path): string { return __DIR__.'/../../'.$path; } From 6cfdc56dc268624cd4d6e3a3f82f3a7533859a56 Mon Sep 17 00:00:00 2001 From: kitloong Date: Fri, 1 Jan 2021 14:37:04 +0800 Subject: [PATCH 07/65] Update mysql57 --- .github/workflows/tests.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 04e8c700..a9354a80 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -12,7 +12,8 @@ jobs: image: mysql:5.7 env: MYSQL_ALLOW_EMPTY_PASSWORD: yes - MYSQL_DATABASE: forge + MYSQL_DATABASE: migration + MYSQL_USER: user ports: - 33306:3306 options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 From d1348571d177470c82013335a69ab4bf938638fb Mon Sep 17 00:00:00 2001 From: kitloong Date: Fri, 1 Jan 2021 14:39:45 +0800 Subject: [PATCH 08/65] Added feature test --- .env.action | 5 + .env.example | 5 + .gitattributes | 4 +- .gitignore | 1 + composer.json | 3 +- tests/KitLoong/Feature/FeatureTestCase.php | 81 ++++++++++ .../KitLoong/Feature/MySQL57/CommandTest.php | 31 ++++ .../Feature/MySQL57/MySQL57TestCase.php | 63 ++++++++ tests/KitLoong/TestCase.php | 9 +- ...4_10_12_000000_from_create_users_table.php | 37 +++++ ...1_000000_from_create_all_columns_table.php | 147 ++++++++++++++++++ 11 files changed, 378 insertions(+), 8 deletions(-) create mode 100644 .env.action create mode 100644 .env.example create mode 100644 tests/KitLoong/Feature/FeatureTestCase.php create mode 100644 tests/KitLoong/Feature/MySQL57/CommandTest.php create mode 100644 tests/KitLoong/Feature/MySQL57/MySQL57TestCase.php create mode 100644 tests/KitLoong/resources/database/migrations/2014_10_12_000000_from_create_users_table.php create mode 100644 tests/KitLoong/resources/database/migrations/2020_03_21_000000_from_create_all_columns_table.php diff --git a/.env.action b/.env.action new file mode 100644 index 00000000..6ed8c1b0 --- /dev/null +++ b/.env.action @@ -0,0 +1,5 @@ +MYSQL57_HOST=mysql57 +MYSQL57_PORT=3306 +MYSQL57_DATABASE=migration +MYSQL57_USERNAME=user +MYSQL57_PASSWORD= diff --git a/.env.example b/.env.example new file mode 100644 index 00000000..7c994736 --- /dev/null +++ b/.env.example @@ -0,0 +1,5 @@ +MYSQL57_HOST=mysql57 +MYSQL57_PORT=3306 +MYSQL57_DATABASE=migration +MYSQL57_USERNAME= +MYSQL57_PASSWORD= diff --git a/.gitattributes b/.gitattributes index 48e26609..e9722b07 100644 --- a/.gitattributes +++ b/.gitattributes @@ -6,5 +6,7 @@ /.gitattributes export-ignore /.gitignore export-ignore /.travis.yml export-ignore -/phpunit.xml.dist export-ignore +/phpunit.xml export-ignore /tests export-ignore +/.env.* export-ignore +/phpcs.xml export-ignore diff --git a/.gitignore b/.gitignore index f1859856..183967d7 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ build .idea .phpunit.result.cache storage +.env diff --git a/composer.json b/composer.json index eb0b7833..fd154689 100644 --- a/composer.json +++ b/composer.json @@ -17,7 +17,8 @@ "require-dev": { "orchestra/testbench": "^3.6|^4.0|^5.0|^6.0", "squizlabs/php_codesniffer": "^3.5", - "mockery/mockery": "^1.0" + "mockery/mockery": "^1.0", + "ext-pdo": "*" }, "autoload": { "psr-4": { diff --git a/tests/KitLoong/Feature/FeatureTestCase.php b/tests/KitLoong/Feature/FeatureTestCase.php new file mode 100644 index 00000000..fcdcf57f --- /dev/null +++ b/tests/KitLoong/Feature/FeatureTestCase.php @@ -0,0 +1,81 @@ +loadDotenv(); + } catch (InvalidPathException $exception) { + $this->markTestSkipped('Skipped feature tests.'); + } + + $app['config']->set( + 'generators.config.migration_template_path', + base_path('src/Way/Generators/templates/migration.txt') + ); + + $app['config']->set('generators.config.migration_target_path', $this->migrationOutputPath()); + } + + protected function setUp(): void + { + parent::setUp(); + + $this->prepareStorage(); + $this->dropAllTables(); + } + + protected function loadDotenv() + { + $dotenv = Dotenv::createImmutable(base_path()); + $dotenv->load(); + } + + protected function prepareStorage() + { + File::deleteDirectory(storage_path()); + File::makeDirectory(config('generators.config.migration_target_path'), 0775, true); + File::makeDirectory($this->sqlOutputPath()); + } + + protected function migrationOutputPath(string $path = ''): string + { + return storage_path('migrations').($path ? DIRECTORY_SEPARATOR.$path : $path); + } + + protected function sqlOutputPath(string $path = ''): string + { + return storage_path('sql').($path ? DIRECTORY_SEPARATOR.$path : $path); + } + + protected function generateMigrations(): void + { + $this->artisan( + 'migrate:generate', + ['--no-interaction' => true] + )->run(); + } + + abstract protected function dropAllTables(): void; +} diff --git a/tests/KitLoong/Feature/MySQL57/CommandTest.php b/tests/KitLoong/Feature/MySQL57/CommandTest.php new file mode 100644 index 00000000..f7b6fe98 --- /dev/null +++ b/tests/KitLoong/Feature/MySQL57/CommandTest.php @@ -0,0 +1,31 @@ +loadMigrationsFrom(base_path('tests/KitLoong/resources/database/migrations')); + + $this->dumpSchemaAs($this->sqlOutputPath('expected.sql')); + + $this->generateMigrations(); + + $this->dropAllTables(); + + $this->loadMigrationsFrom($this->migrationOutputPath()); + + $this->dumpSchemaAs($this->sqlOutputPath('actual.sql')); + + $this->assertFileEquals( + $this->sqlOutputPath('expected.sql'), + $this->sqlOutputPath('actual.sql') + ); + } +} diff --git a/tests/KitLoong/Feature/MySQL57/MySQL57TestCase.php b/tests/KitLoong/Feature/MySQL57/MySQL57TestCase.php new file mode 100644 index 00000000..d18c144c --- /dev/null +++ b/tests/KitLoong/Feature/MySQL57/MySQL57TestCase.php @@ -0,0 +1,63 @@ +set('database.default', 'mysql'); + $app['config']->set('database.connections.mysql', [ + 'driver' => 'mysql', + 'url' => null, + 'host' => env('MYSQL57_HOST'), + 'port' => env('MYSQL57_PORT'), + 'database' => env('MYSQL57_DATABASE'), + 'username' => env('MYSQL57_USERNAME'), + 'password' => env('MYSQL57_PASSWORD'), + 'unix_socket' => env('DB_SOCKET', ''), + 'charset' => 'utf8mb4', + 'collation' => 'utf8mb4_general_ci', + 'prefix' => '', + 'prefix_indexes' => true, + 'strict' => true, + 'engine' => null, + 'options' => extension_loaded('pdo_mysql') ? array_filter([ + PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'), + ]) : [], + ]); + } + + protected function dumpSchemaAs(string $destination): void + { + $command = sprintf( + 'mysqldump -h %s -u %s -p\'%s\' %s --compact --no-data > %s', + config('database.connections.mysql.host'), + config('database.connections.mysql.username'), + config('database.connections.mysql.password'), + config('database.connections.mysql.database'), + $destination + ); + exec($command); + } + + protected function dropAllTables(): void + { + $tables = DB::select('SHOW TABLES'); + foreach ($tables as $table) { + Schema::drop($table->{'Tables_in_'.config('database.connections.mysql.database')}); + } + } +} diff --git a/tests/KitLoong/TestCase.php b/tests/KitLoong/TestCase.php index a82fb7e5..fb78ff41 100644 --- a/tests/KitLoong/TestCase.php +++ b/tests/KitLoong/TestCase.php @@ -31,13 +31,10 @@ public function __call(string $name, array $arguments) } } - protected function setUp(): void + protected function getEnvironmentSetUp($app) { - parent::setUp(); - } + parent::getEnvironmentSetUp($app); - protected function basePath(string $path): string - { - return __DIR__.'/../../'.$path; + app()->setBasePath(__DIR__.'/../../'); } } diff --git a/tests/KitLoong/resources/database/migrations/2014_10_12_000000_from_create_users_table.php b/tests/KitLoong/resources/database/migrations/2014_10_12_000000_from_create_users_table.php new file mode 100644 index 00000000..9db7d530 --- /dev/null +++ b/tests/KitLoong/resources/database/migrations/2014_10_12_000000_from_create_users_table.php @@ -0,0 +1,37 @@ +unsignedBigInteger('id'); + $table->string('name'); + $table->string('email')->unique(); + $table->timestamp('email_verified_at')->nullable(); + $table->string('password'); + $table->softDeletes(); + $table->rememberToken(); + $table->timestamps(); + }); + } + + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::drop('users'); + } +} diff --git a/tests/KitLoong/resources/database/migrations/2020_03_21_000000_from_create_all_columns_table.php b/tests/KitLoong/resources/database/migrations/2020_03_21_000000_from_create_all_columns_table.php new file mode 100644 index 00000000..0d9e1f2b --- /dev/null +++ b/tests/KitLoong/resources/database/migrations/2020_03_21_000000_from_create_all_columns_table.php @@ -0,0 +1,147 @@ +bigInteger('bigInteger'); + $table->bigInteger('bigInteger_default')->default(1080); + $table->binary('binary'); + $table->boolean('boolean'); + $table->boolean('boolean_default_false')->default(0); + $table->boolean('boolean_default_true')->default(1); + $table->boolean('boolean_unsigned')->unsigned(); + $table->char('char'); + $table->char('char_255', 255); + $table->char('char_100', 100); + $table->char('char_default')->default('default'); + $table->date('date'); + $table->date('date_default')->default('2020-10-08'); + $table->dateTime('dateTime'); + $table->dateTime('dateTime_0', 0); + $table->dateTime('dateTime_2', 2); + $table->dateTime('dateTime_default', 2)->default('2020-10-08 10:20:30'); + $table->dateTimeTz('dateTimeTz'); + $table->dateTimeTz('dateTimeTz_0', 0); + $table->dateTimeTz('dateTimeTz_2', 2); + $table->dateTimeTz('dateTimeTz_default')->default('2020-10-08 10:20:30'); + $table->decimal('decimal'); + $table->decimal('decimal_82', 8, 2); + $table->decimal('decimal_53', 5, 3); + $table->decimal('decimal_default')->default(10.8); + $table->double('double'); + $table->double('double_82', 8, 2); + $table->double('double_53', 5, 3); + $table->double('double_default')->default(10.8); + $table->enum('enum', ['easy', 'hard']); + $table->enum('enum_default', ['easy', 'hard'])->default('easy'); + $table->float('float'); + $table->float('float_82', 8, 2); + $table->float('float_53', 5, 3); + $table->float('float_default')->default(10.8); + $table->geometry('geometry'); + $table->geometryCollection('geometryCollection'); + $table->integer('integer'); + $table->integer('integer_default')->default(1080); + $table->ipAddress('ipAddress'); + $table->ipAddress('ipAddress_default')->default('10.0.0.8'); + $table->json('json'); + $table->jsonb('jsonb'); + $table->lineString('lineString'); + $table->longText('longText'); + $table->mediumInteger('mediumInteger'); + $table->mediumInteger('mediumInteger_default')->default(1080); + $table->mediumText('mediumText'); + $table->multiLineString('multiLineString'); + $table->multiPoint('multiPoint'); + $table->multiPolygon('multiPolygon'); + $table->point('point'); + $table->polygon('polygon'); + $table->smallInteger('smallInteger'); + $table->smallInteger('smallInteger_default')->default(1080); + $table->string('string'); + $table->string('string_255', 255); + $table->string('string_100', 100); + $table->string('default_single_quote')->default('string with \" !@#$%^^&*()_+ \\\' quotes'); + $table->string('comment_double_quote')->comment("string with \" ' quotes"); + $table->text('text'); + $table->time('time'); + $table->time('time_0', 0); + $table->time('time_2', 2); + $table->time('time_default')->default('10:20:30'); + $table->timeTz('timeTz'); + $table->timeTz('timeTz_0', 0); + $table->timeTz('timeTz_2', 2); + $table->timeTz('timeTz_default')->default('10:20:30'); + $table->timestamp('timestamp'); + $table->timestamp('timestamp_useCurrent')->useCurrent(); + $table->timestamp('timestamp_useCurrentOnUpdate')->useCurrent()->useCurrentOnUpdate(); + $table->timestamp('timestamp_0', 0)->nullable(); + $table->timestamp('timestamp_2', 2)->nullable(); + $table->timestamp('timestamp_default')->default('2020-10-08 10:20:30'); + $table->timestampTz('timestampTz')->nullable(); + $table->timestampTz('timestampTz_0', 0)->nullable(); + $table->timestampTz('timestampTz_2', 2)->nullable(); + $table->timestampTz('timestampTz_default')->default('2020-10-08 10:20:30'); + $table->tinyInteger('tinyInteger'); + $table->tinyInteger('tinyInteger_default')->default(10); + $table->unsignedBigInteger('unsignedBigInteger'); + $table->decimal('unsignedDecimal')->unsigned(); + $table->double('unsignedDouble')->unsigned(); + $table->float('unsignedFloat')->unsigned(); + $table->unsignedInteger('unsignedInteger'); + $table->unsignedMediumInteger('unsignedMediumInteger'); + $table->unsignedSmallInteger('unsignedSmallInteger'); + $table->unsignedTinyInteger('unsignedTinyInteger'); + $table->year('year')->default(2020); + + if (config('database.default') === 'mysql') { + $table->char('char_charset')->charset('utf8'); + $table->char('char_collation')->collation('utf8_unicode_ci'); + $table->enum('enum_charset', ['easy', 'hard'])->charset('utf8'); + $table->enum('enum_collation', ['easy', 'hard'])->collation('utf8_unicode_ci'); + $table->longText('longText_charset')->charset('utf8'); + $table->longText('longText_collation')->collation('utf8_unicode_ci'); + $table->mediumText('mediumText_charset')->charset('utf8'); + $table->mediumText('mediumText_collation')->collation('utf8_unicode_ci'); + $table->text('text_charset')->charset('utf8'); + $table->text('text_collation')->collation('utf8_unicode_ci'); + $table->set('set', ['strawberry', 'vanilla']); + $table->set('set_default', ['strawberry', 'vanilla'])->default('strawberry'); + $table->set('set_charset', ['strawberry', 'vanilla'])->charset('utf8'); + $table->set('set_collation', ['strawberry', 'vanilla'])->collation('utf8_unicode_ci'); + $table->string('string_charset')->charset('utf8'); + $table->string('string_collation')->collation('utf8_unicode_ci'); + } + + if (config('database.default') !== 'pgsql') { + $table->macAddress('macAddress'); + $table->macAddress('macAddress_default')->default('10.0.0.8'); + $table->uuid('uuid')->default('uuid'); + } + + $table->charset = 'utf8mb4'; + $table->collation = 'utf8mb4_general_ci'; + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::drop('all_columns'); + } +} From 45eb7066785c0a28ca84840dc5f9b89a2bc7922f Mon Sep 17 00:00:00 2001 From: kitloong Date: Fri, 1 Jan 2021 14:54:40 +0800 Subject: [PATCH 09/65] Fallback for v3.x --- tests/KitLoong/Feature/FeatureTestCase.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/KitLoong/Feature/FeatureTestCase.php b/tests/KitLoong/Feature/FeatureTestCase.php index fcdcf57f..d4189083 100644 --- a/tests/KitLoong/Feature/FeatureTestCase.php +++ b/tests/KitLoong/Feature/FeatureTestCase.php @@ -48,7 +48,12 @@ protected function setUp(): void protected function loadDotenv() { - $dotenv = Dotenv::createImmutable(base_path()); + if (method_exists(Dotenv::class, 'createImmutable')) { + $dotenv = Dotenv::createImmutable(base_path()); + } else { + /** @noinspection PhpParamsInspection */ + $dotenv = Dotenv::create(base_path()); + } $dotenv->load(); } From 72707363b59e93c9733d3d25049c5add8f2c6a21 Mon Sep 17 00:00:00 2001 From: kitloong Date: Fri, 1 Jan 2021 14:59:08 +0800 Subject: [PATCH 10/65] Fallback for v2.x --- tests/KitLoong/Feature/FeatureTestCase.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/KitLoong/Feature/FeatureTestCase.php b/tests/KitLoong/Feature/FeatureTestCase.php index d4189083..4b691cd3 100644 --- a/tests/KitLoong/Feature/FeatureTestCase.php +++ b/tests/KitLoong/Feature/FeatureTestCase.php @@ -50,9 +50,12 @@ protected function loadDotenv() { if (method_exists(Dotenv::class, 'createImmutable')) { $dotenv = Dotenv::createImmutable(base_path()); - } else { + } elseif (method_exists(Dotenv::class, 'create')) { /** @noinspection PhpParamsInspection */ $dotenv = Dotenv::create(base_path()); + } else { + /** @noinspection PhpParamsInspection */ + $dotenv = new Dotenv(base_path()); } $dotenv->load(); } From dd3544c33cf1903b5fa781e15bf4b4c711d27a82 Mon Sep 17 00:00:00 2001 From: kitloong Date: Fri, 1 Jan 2021 15:13:32 +0800 Subject: [PATCH 11/65] Setup .env for Github Action --- .github/workflows/tests.yml | 3 +++ composer.json | 9 ++++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index a9354a80..b9441811 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -53,5 +53,8 @@ jobs: composer require "laravel/framework:${{ matrix.laravel }}" --no-interaction --no-update composer update --${{ matrix.stability }} --prefer-dist --no-interaction --no-progress + - name: Setup .env + run: composer run action-env-setup + - name: Execute tests run: vendor/bin/phpunit diff --git a/composer.json b/composer.json index fd154689..f3bd07c6 100644 --- a/composer.json +++ b/composer.json @@ -38,5 +38,12 @@ "KitLoong\\MigrationsGenerator\\MigrationsGeneratorServiceProvider" ] } - } + }, + "minimum-stability": "dev", + "prefer-stable": true, + "scripts": { + "action-env-setup": [ + "@php -r \"file_exists('.env1') || copy('.env.action', '.env1');\"" + ] + } } From f4205847a2fceea72ff40f5f942006a66f67d0f3 Mon Sep 17 00:00:00 2001 From: kitloong Date: Fri, 1 Jan 2021 16:20:21 +0800 Subject: [PATCH 12/65] Fixed wrong name --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index f3bd07c6..ffb335f4 100644 --- a/composer.json +++ b/composer.json @@ -43,7 +43,7 @@ "prefer-stable": true, "scripts": { "action-env-setup": [ - "@php -r \"file_exists('.env1') || copy('.env.action', '.env1');\"" + "@php -r \"file_exists('.env') || copy('.env.action', '.env');\"" ] } } From bbbc30902a101c84f84f6eec2073abd24252a5df Mon Sep 17 00:00:00 2001 From: kitloong Date: Fri, 1 Jan 2021 16:54:50 +0800 Subject: [PATCH 13/65] Run test in container --- .github/workflows/tests.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index b9441811..8f26fecb 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -6,6 +6,7 @@ jobs: test: runs-on: ubuntu-latest + container: ubuntu services: mysql57: From 5409f6c9bcc687cc475597e737a77ca2c1a9efd2 Mon Sep 17 00:00:00 2001 From: kitloong Date: Fri, 1 Jan 2021 16:58:05 +0800 Subject: [PATCH 14/65] Update Github action mysql host --- .env.action | 2 +- .github/workflows/tests.yml | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/.env.action b/.env.action index 6ed8c1b0..610cdb49 100644 --- a/.env.action +++ b/.env.action @@ -1,4 +1,4 @@ -MYSQL57_HOST=mysql57 +MYSQL57_HOST=localhost MYSQL57_PORT=3306 MYSQL57_DATABASE=migration MYSQL57_USERNAME=user diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 8f26fecb..fd5f533d 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -6,7 +6,6 @@ jobs: test: runs-on: ubuntu-latest - container: ubuntu services: mysql57: @@ -16,7 +15,7 @@ jobs: MYSQL_DATABASE: migration MYSQL_USER: user ports: - - 33306:3306 + - 3306:3306 options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 strategy: From 664e9358a3d40636ce4c87874824f653dea6f51a Mon Sep 17 00:00:00 2001 From: kitloong Date: Fri, 1 Jan 2021 17:06:20 +0800 Subject: [PATCH 15/65] Use 127.0.0.1 --- .env.action | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.env.action b/.env.action index 610cdb49..005a56eb 100644 --- a/.env.action +++ b/.env.action @@ -1,4 +1,4 @@ -MYSQL57_HOST=localhost +MYSQL57_HOST=127.0.0.1 MYSQL57_PORT=3306 MYSQL57_DATABASE=migration MYSQL57_USERNAME=user From 15ad3ba371fef3fc3aeaf5d48488e6774106253e Mon Sep 17 00:00:00 2001 From: kitloong Date: Fri, 1 Jan 2021 17:11:29 +0800 Subject: [PATCH 16/65] Use root --- .env.action | 2 +- .github/workflows/tests.yml | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.env.action b/.env.action index 005a56eb..9fd29170 100644 --- a/.env.action +++ b/.env.action @@ -1,5 +1,5 @@ MYSQL57_HOST=127.0.0.1 MYSQL57_PORT=3306 MYSQL57_DATABASE=migration -MYSQL57_USERNAME=user +MYSQL57_USERNAME=root MYSQL57_PASSWORD= diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index fd5f533d..b7369c17 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -13,7 +13,6 @@ jobs: env: MYSQL_ALLOW_EMPTY_PASSWORD: yes MYSQL_DATABASE: migration - MYSQL_USER: user ports: - 3306:3306 options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 From 1b15035e23ab1b501b5af90642c3f664b85b3ae7 Mon Sep 17 00:00:00 2001 From: kitloong Date: Sat, 2 Jan 2021 00:15:57 +0800 Subject: [PATCH 17/65] Run set() if Laravel version is at least 5.8 --- .../KitLoong/Support/CheckLaravelVersion.php | 36 +++++++++++++++++++ ...1_000000_from_create_all_columns_table.php | 15 +++++--- 2 files changed, 47 insertions(+), 4 deletions(-) create mode 100644 tests/KitLoong/Support/CheckLaravelVersion.php diff --git a/tests/KitLoong/Support/CheckLaravelVersion.php b/tests/KitLoong/Support/CheckLaravelVersion.php new file mode 100644 index 00000000..d7c77992 --- /dev/null +++ b/tests/KitLoong/Support/CheckLaravelVersion.php @@ -0,0 +1,36 @@ +version(), '5.7.0', '>='); + } + + protected function atLeastLaravel5Dot8(): bool + { + return version_compare(app()->version(), '5.8.0', '>='); + } + + protected function atLeastLaravel6(): bool + { + return version_compare(app()->version(), '6.0', '>='); + } + + protected function atLeastLaravel7(): bool + { + return version_compare(app()->version(), '7.0', '>='); + } + + protected function atLeastLaravel8(): bool + { + return version_compare(app()->version(), '8.0', '>='); + } +} diff --git a/tests/KitLoong/resources/database/migrations/2020_03_21_000000_from_create_all_columns_table.php b/tests/KitLoong/resources/database/migrations/2020_03_21_000000_from_create_all_columns_table.php index 0d9e1f2b..e5be286c 100644 --- a/tests/KitLoong/resources/database/migrations/2020_03_21_000000_from_create_all_columns_table.php +++ b/tests/KitLoong/resources/database/migrations/2020_03_21_000000_from_create_all_columns_table.php @@ -3,9 +3,12 @@ use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; +use Tests\KitLoong\Support\CheckLaravelVersion; class FromCreateAllColumnsTable extends Migration { + use CheckLaravelVersion; + /** * Run the migrations. * @@ -116,10 +119,14 @@ public function up() $table->mediumText('mediumText_collation')->collation('utf8_unicode_ci'); $table->text('text_charset')->charset('utf8'); $table->text('text_collation')->collation('utf8_unicode_ci'); - $table->set('set', ['strawberry', 'vanilla']); - $table->set('set_default', ['strawberry', 'vanilla'])->default('strawberry'); - $table->set('set_charset', ['strawberry', 'vanilla'])->charset('utf8'); - $table->set('set_collation', ['strawberry', 'vanilla'])->collation('utf8_unicode_ci'); + + if ($this->atLeastLaravel5Dot8()) { + $table->set('set', ['strawberry', 'vanilla']); + $table->set('set_default', ['strawberry', 'vanilla'])->default('strawberry'); + $table->set('set_charset', ['strawberry', 'vanilla'])->charset('utf8'); + $table->set('set_collation', ['strawberry', 'vanilla'])->collation('utf8_unicode_ci'); + } + $table->string('string_charset')->charset('utf8'); $table->string('string_collation')->collation('utf8_unicode_ci'); } From 75117bc0b2c7623076ae09ea8ba11df313d5a621 Mon Sep 17 00:00:00 2001 From: kitloong Date: Sat, 2 Jan 2021 00:19:32 +0800 Subject: [PATCH 18/65] Added noinspection --- .../2020_03_21_000000_from_create_all_columns_table.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/KitLoong/resources/database/migrations/2020_03_21_000000_from_create_all_columns_table.php b/tests/KitLoong/resources/database/migrations/2020_03_21_000000_from_create_all_columns_table.php index e5be286c..b372307d 100644 --- a/tests/KitLoong/resources/database/migrations/2020_03_21_000000_from_create_all_columns_table.php +++ b/tests/KitLoong/resources/database/migrations/2020_03_21_000000_from_create_all_columns_table.php @@ -1,4 +1,6 @@ Date: Sat, 2 Jan 2021 00:55:26 +0800 Subject: [PATCH 19/65] Include dbal test --- .github/workflows/tests.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index b7369c17..cc8e46a2 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -21,10 +21,12 @@ jobs: matrix: php: [ 7.3, 7.4 ] laravel: [ 8.*, 7.*, 6.*, 5.8.*, 5.7.*, 5.6.* ] + dbal: [ 2.* ] stability: [ prefer-stable ] include: - laravel: 8.* php: 8.0 + dbal: 3.* stability: prefer-stable - laravel: 7.* php: 8.0 @@ -49,7 +51,7 @@ jobs: - name: Install dependencies run: | - composer require "laravel/framework:${{ matrix.laravel }}" --no-interaction --no-update + composer require "laravel/framework:${{ matrix.laravel }}" "doctrine/dbal:${{ matrix.dbal }}" --no-interaction --no-update composer update --${{ matrix.stability }} --prefer-dist --no-interaction --no-progress - name: Setup .env From dfda64c58f4b28a9bf6c10964084379533764011 Mon Sep 17 00:00:00 2001 From: kitloong Date: Sat, 2 Jan 2021 00:58:36 +0800 Subject: [PATCH 20/65] Add missing dbal for Laravel 6 and 7 --- .github/workflows/tests.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index cc8e46a2..89c08730 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -30,9 +30,11 @@ jobs: stability: prefer-stable - laravel: 7.* php: 8.0 + dbal: 2.* stability: prefer-stable - laravel: 6.* php: 8.0 + dbal: 2.* stability: prefer-stable name: PHP ${{ matrix.php }} - Laravel ${{ matrix.laravel }} From 3135a1bf70a5a2ac2dc7aa73c6cba73d744ea7f1 Mon Sep 17 00:00:00 2001 From: kitloong Date: Mon, 4 Jan 2021 18:47:15 +0800 Subject: [PATCH 21/65] Added useCurrentOnUpdate fallback for Laravel 7.x and below --- .../Generators/DatetimeField.php | 10 ++- .../Generators/FieldGenerator.php | 13 +++ .../Support/CheckLaravelVersion.php | 43 ++++++++++ .../KitLoong/Support/CheckLaravelVersion.php | 36 -------- .../Support/CheckLaravelVersionTest.php | 82 +++++++++++++++++++ ...1_000000_from_create_all_columns_table.php | 6 +- 6 files changed, 149 insertions(+), 41 deletions(-) create mode 100644 src/KitLoong/MigrationsGenerator/Support/CheckLaravelVersion.php delete mode 100644 tests/KitLoong/Support/CheckLaravelVersion.php create mode 100644 tests/KitLoong/Support/CheckLaravelVersionTest.php diff --git a/src/KitLoong/MigrationsGenerator/Generators/DatetimeField.php b/src/KitLoong/MigrationsGenerator/Generators/DatetimeField.php index a20e1637..cc67043f 100644 --- a/src/KitLoong/MigrationsGenerator/Generators/DatetimeField.php +++ b/src/KitLoong/MigrationsGenerator/Generators/DatetimeField.php @@ -11,6 +11,7 @@ use KitLoong\MigrationsGenerator\MigrationMethod\ColumnModifier; use KitLoong\MigrationsGenerator\MigrationMethod\ColumnName; use KitLoong\MigrationsGenerator\MigrationMethod\ColumnType; +use KitLoong\MigrationsGenerator\MigrationsGeneratorSetting; use KitLoong\MigrationsGenerator\Repositories\MySQLRepository; use KitLoong\MigrationsGenerator\Types\DBALTypes; @@ -52,11 +53,14 @@ public function makeField(string $table, array $field, Column $column, bool $use $field['args'][] = $column->getLength(); } - if ($column->getType()->getName() === DBALTypes::TIMESTAMP) { - if ($this->mySQLRepository->useOnUpdateCurrentTimestamp($table, $column->getName())) { - $field['decorators'][] = ColumnModifier::USE_CURRENT_ON_UPDATE; + if (app(MigrationsGeneratorSetting::class)->getPlatform() === Platform::MYSQL) { + if ($column->getType()->getName() === DBALTypes::TIMESTAMP) { + if ($this->mySQLRepository->useOnUpdateCurrentTimestamp($table, $column->getName())) { + $field['decorators'][] = ColumnModifier::USE_CURRENT_ON_UPDATE; + } } } + return $field; } diff --git a/src/KitLoong/MigrationsGenerator/Generators/FieldGenerator.php b/src/KitLoong/MigrationsGenerator/Generators/FieldGenerator.php index 5ac9090f..a0fc7546 100644 --- a/src/KitLoong/MigrationsGenerator/Generators/FieldGenerator.php +++ b/src/KitLoong/MigrationsGenerator/Generators/FieldGenerator.php @@ -15,10 +15,13 @@ use KitLoong\MigrationsGenerator\Generators\Modifier\NullableModifier; use KitLoong\MigrationsGenerator\MigrationMethod\ColumnModifier; use KitLoong\MigrationsGenerator\MigrationMethod\ColumnType; +use KitLoong\MigrationsGenerator\Support\CheckLaravelVersion; use KitLoong\MigrationsGenerator\Types\DBALTypes; class FieldGenerator { + use CheckLaravelVersion; + private $decorator; private $integerField; private $datetimeField; @@ -133,6 +136,16 @@ public function generate(string $table, $columns, Collection $indexes): array $field['decorators'][] = $this->commentModifier->generate($column->getComment()); } + if (!$this->atLeastLaravel8()) { + if ($field['type'] === DBALTypes::TIMESTAMP) { + if (($key1 = array_search(ColumnModifier::USE_CURRENT, $field['decorators'])) !== false && + ($key2 = array_search(ColumnModifier::USE_CURRENT_ON_UPDATE, $field['decorators'])) !== false) { + unset($field['decorators'][$key1]); + unset($field['decorators'][$key2]); + } + } + } + $fields[] = $field; } return $fields; diff --git a/src/KitLoong/MigrationsGenerator/Support/CheckLaravelVersion.php b/src/KitLoong/MigrationsGenerator/Support/CheckLaravelVersion.php new file mode 100644 index 00000000..d9117baf --- /dev/null +++ b/src/KitLoong/MigrationsGenerator/Support/CheckLaravelVersion.php @@ -0,0 +1,43 @@ +atLeastLaravelVersion('5.7.0'); + } + + public function atLeastLaravel5Dot8(): bool + { + return $this->atLeastLaravelVersion('5.8.0'); + } + + public function atLeastLaravel6(): bool + { + return $this->atLeastLaravelVersion('6.0'); + } + + public function atLeastLaravel7(): bool + { + return $this->atLeastLaravelVersion('7.0'); + } + + public function atLeastLaravel8(): bool + { + return $this->atLeastLaravelVersion('8.0'); + } + + private function atLeastLaravelVersion(string $version): bool + { + return version_compare(App::version(), $version, '>='); + } +} diff --git a/tests/KitLoong/Support/CheckLaravelVersion.php b/tests/KitLoong/Support/CheckLaravelVersion.php deleted file mode 100644 index d7c77992..00000000 --- a/tests/KitLoong/Support/CheckLaravelVersion.php +++ /dev/null @@ -1,36 +0,0 @@ -version(), '5.7.0', '>='); - } - - protected function atLeastLaravel5Dot8(): bool - { - return version_compare(app()->version(), '5.8.0', '>='); - } - - protected function atLeastLaravel6(): bool - { - return version_compare(app()->version(), '6.0', '>='); - } - - protected function atLeastLaravel7(): bool - { - return version_compare(app()->version(), '7.0', '>='); - } - - protected function atLeastLaravel8(): bool - { - return version_compare(app()->version(), '8.0', '>='); - } -} diff --git a/tests/KitLoong/Support/CheckLaravelVersionTest.php b/tests/KitLoong/Support/CheckLaravelVersionTest.php new file mode 100644 index 00000000..7790670f --- /dev/null +++ b/tests/KitLoong/Support/CheckLaravelVersionTest.php @@ -0,0 +1,82 @@ +andReturn('5.6.0')->once(); + $this->assertFalse($this->stubInstance()->atLeastLaravel5Dot7()); + + App::shouldReceive('version')->andReturn('5.7.0')->once(); + $this->assertTrue($this->stubInstance()->atLeastLaravel5Dot7()); + + App::shouldReceive('version')->andReturn('5.8.0')->once(); + $this->assertTrue($this->stubInstance()->atLeastLaravel5Dot7()); + } + + public function testAtLeastLaravel5Dot8() + { + App::shouldReceive('version')->andReturn('5.7.0')->once(); + $this->assertFalse($this->stubInstance()->atLeastLaravel5Dot8()); + + App::shouldReceive('version')->andReturn('5.8.0')->once(); + $this->assertTrue($this->stubInstance()->atLeastLaravel5Dot8()); + + App::shouldReceive('version')->andReturn('6.0.0')->once(); + $this->assertTrue($this->stubInstance()->atLeastLaravel5Dot8()); + } + + public function testAtLeastLaravel6() + { + App::shouldReceive('version')->andReturn('5.8.0')->once(); + $this->assertFalse($this->stubInstance()->atLeastLaravel6()); + + App::shouldReceive('version')->andReturn('6.0.0')->once(); + $this->assertTrue($this->stubInstance()->atLeastLaravel6()); + + App::shouldReceive('version')->andReturn('7.0.0')->once(); + $this->assertTrue($this->stubInstance()->atLeastLaravel6()); + } + + public function testAtLeastLaravel7() + { + App::shouldReceive('version')->andReturn('6.0.0')->once(); + $this->assertFalse($this->stubInstance()->atLeastLaravel7()); + + App::shouldReceive('version')->andReturn('7.0.0')->once(); + $this->assertTrue($this->stubInstance()->atLeastLaravel7()); + + App::shouldReceive('version')->andReturn('8.0.0')->once(); + $this->assertTrue($this->stubInstance()->atLeastLaravel7()); + } + + public function testAtLeastLaravel9() + { + App::shouldReceive('version')->andReturn('7.0.0')->once(); + $this->assertFalse($this->stubInstance()->atLeastLaravel8()); + + App::shouldReceive('version')->andReturn('8.0.0')->once(); + $this->assertTrue($this->stubInstance()->atLeastLaravel8()); + + App::shouldReceive('version')->andReturn('9.0.0')->once(); + $this->assertTrue($this->stubInstance()->atLeastLaravel8()); + } + + private function stubInstance() + { + return new class { + use CheckLaravelVersion; + }; + } +} diff --git a/tests/KitLoong/resources/database/migrations/2020_03_21_000000_from_create_all_columns_table.php b/tests/KitLoong/resources/database/migrations/2020_03_21_000000_from_create_all_columns_table.php index b372307d..ecfbe206 100644 --- a/tests/KitLoong/resources/database/migrations/2020_03_21_000000_from_create_all_columns_table.php +++ b/tests/KitLoong/resources/database/migrations/2020_03_21_000000_from_create_all_columns_table.php @@ -5,7 +5,7 @@ use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; -use Tests\KitLoong\Support\CheckLaravelVersion; +use KitLoong\MigrationsGenerator\Support\CheckLaravelVersion; class FromCreateAllColumnsTable extends Migration { @@ -90,7 +90,9 @@ public function up() $table->timeTz('timeTz_default')->default('10:20:30'); $table->timestamp('timestamp'); $table->timestamp('timestamp_useCurrent')->useCurrent(); - $table->timestamp('timestamp_useCurrentOnUpdate')->useCurrent()->useCurrentOnUpdate(); + if ($this->atLeastLaravel8()) { + $table->timestamp('timestamp_useCurrentOnUpdate')->useCurrent()->useCurrentOnUpdate(); + } $table->timestamp('timestamp_0', 0)->nullable(); $table->timestamp('timestamp_2', 2)->nullable(); $table->timestamp('timestamp_default')->default('2020-10-08 10:20:30'); From a60b97fa83dd25a1a5cc20a38b69bd9146a3a053 Mon Sep 17 00:00:00 2001 From: kitloong Date: Mon, 4 Jan 2021 18:47:39 +0800 Subject: [PATCH 22/65] Removed run() --- tests/KitLoong/Feature/FeatureTestCase.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/KitLoong/Feature/FeatureTestCase.php b/tests/KitLoong/Feature/FeatureTestCase.php index 4b691cd3..276c4781 100644 --- a/tests/KitLoong/Feature/FeatureTestCase.php +++ b/tests/KitLoong/Feature/FeatureTestCase.php @@ -82,7 +82,7 @@ protected function generateMigrations(): void $this->artisan( 'migrate:generate', ['--no-interaction' => true] - )->run(); + ); } abstract protected function dropAllTables(): void; From 4c11b0006e1bc84252716bea5d267e8e9581565a Mon Sep 17 00:00:00 2001 From: kitloong Date: Mon, 4 Jan 2021 18:55:55 +0800 Subject: [PATCH 23/65] Fixed failed tests --- .../Generators/DatetimeFieldTest.php | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/tests/KitLoong/MigrationsGenerator/Generators/DatetimeFieldTest.php b/tests/KitLoong/MigrationsGenerator/Generators/DatetimeFieldTest.php index 774a40a6..a14a5147 100644 --- a/tests/KitLoong/MigrationsGenerator/Generators/DatetimeFieldTest.php +++ b/tests/KitLoong/MigrationsGenerator/Generators/DatetimeFieldTest.php @@ -9,9 +9,11 @@ use Doctrine\DBAL\Schema\Column; use KitLoong\MigrationsGenerator\Generators\DatetimeField; +use KitLoong\MigrationsGenerator\Generators\Platform; use KitLoong\MigrationsGenerator\MigrationMethod\ColumnModifier; use KitLoong\MigrationsGenerator\MigrationMethod\ColumnName; use KitLoong\MigrationsGenerator\MigrationMethod\ColumnType; +use KitLoong\MigrationsGenerator\MigrationsGeneratorSetting; use KitLoong\MigrationsGenerator\Repositories\MySQLRepository; use KitLoong\MigrationsGenerator\Types\DBALTypes; use Mockery; @@ -43,6 +45,13 @@ public function testMakeFieldIsSoftDeletes() ->once(); }); + $this->mock(MigrationsGeneratorSetting::class, function (MockInterface $mock) { + $mock->shouldReceive('getPlatform') + ->withNoArgs() + ->andReturn(Platform::MYSQL) + ->once(); + }); + /** @var DatetimeField $datetimeField */ $datetimeField = resolve(DatetimeField::class); @@ -78,6 +87,13 @@ public function testMakeFieldIsTimestamps() ->once(); }); + $this->mock(MigrationsGeneratorSetting::class, function (MockInterface $mock) { + $mock->shouldReceive('getPlatform') + ->withNoArgs() + ->andReturn(Platform::MYSQL) + ->once(); + }); + /** @var DatetimeField $datetimeField */ $datetimeField = resolve(DatetimeField::class); $field = [ @@ -101,6 +117,13 @@ public function testMakeFieldIsDatetime() ->andReturn(DBALTypes::DATETIME_MUTABLE) ->once(); + $this->mock(MigrationsGeneratorSetting::class, function (MockInterface $mock) { + $mock->shouldReceive('getPlatform') + ->withNoArgs() + ->andReturn(Platform::MYSQL) + ->once(); + }); + /** @var DatetimeField $datetimeField */ $datetimeField = resolve(DatetimeField::class); From 2d5adc2e238e20e2bcedb16d4d6a93b528b98dbc Mon Sep 17 00:00:00 2001 From: kitloong Date: Tue, 5 Jan 2021 19:02:20 +0800 Subject: [PATCH 24/65] Use \Doctrine\DBAL\Exception --- src/KitLoong/MigrationsGenerator/MigrateGenerateCommand.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/KitLoong/MigrationsGenerator/MigrateGenerateCommand.php b/src/KitLoong/MigrationsGenerator/MigrateGenerateCommand.php index 31068099..a1eb8610 100644 --- a/src/KitLoong/MigrationsGenerator/MigrateGenerateCommand.php +++ b/src/KitLoong/MigrationsGenerator/MigrateGenerateCommand.php @@ -108,7 +108,7 @@ public function __construct( * Execute the console command. Added for Laravel 5.5 * * @return void - * @throws \Doctrine\DBAL\DBALException + * @throws \Doctrine\DBAL\Exception */ public function handle() { From 9e1e74ced7d5604fce6fa70de83bd33528b6fe28 Mon Sep 17 00:00:00 2001 From: kitloong Date: Tue, 5 Jan 2021 19:02:34 +0800 Subject: [PATCH 25/65] Update for MySQL8 --- .../MigrationsGenerator/Repositories/MySQLRepository.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/KitLoong/MigrationsGenerator/Repositories/MySQLRepository.php b/src/KitLoong/MigrationsGenerator/Repositories/MySQLRepository.php index 740f3815..949d065a 100644 --- a/src/KitLoong/MigrationsGenerator/Repositories/MySQLRepository.php +++ b/src/KitLoong/MigrationsGenerator/Repositories/MySQLRepository.php @@ -61,12 +61,14 @@ public function useOnUpdateCurrentTimestamp(string $table, string $columnName): { $setting = app(MigrationsGeneratorSetting::class); + // MySQL5.7 shows on update CURRENT_TIMESTAMP + // MySQL8 shows DEFAULT_GENERATED on update CURRENT_TIMESTAMP $column = $setting->getConnection() ->select( "SHOW COLUMNS FROM `${table}` WHERE Field = '${columnName}' AND Type = 'timestamp' - AND EXTRA='on update CURRENT_TIMESTAMP'" + AND EXTRA LIKE '%on update CURRENT_TIMESTAMP%'" ); if (count($column) > 0) { return true; From 7630475f4375f403ec8b097f4ad0768320a59d3b Mon Sep 17 00:00:00 2001 From: kitloong Date: Tue, 5 Jan 2021 21:33:54 +0800 Subject: [PATCH 26/65] Added more migration tests --- tests/KitLoong/Feature/FeatureTestCase.php | 32 ++++++++++++++ .../KitLoong/Feature/MySQL57/CommandTest.php | 6 ++- tests/KitLoong/TestCase.php | 25 +++++++++++ ..._000000_expected_create_users_db_table.php | 41 +++++++++++++++++ ..._expected_create_all_columns_db_table.php} | 6 +-- ...0_expected_create_failed_jobs_db_table.php | 35 +++++++++++++++ ...00_expected_create_test_index_db_table.php | 44 +++++++++++++++++++ ..._expected_create_user_profile_db_table.php | 36 +++++++++++++++ ...cted_create_composite_primary_db_table.php | 32 ++++++++++++++ 9 files changed, 252 insertions(+), 5 deletions(-) create mode 100644 tests/KitLoong/resources/database/migrations/2014_10_12_000000_expected_create_users_db_table.php rename tests/KitLoong/resources/database/migrations/{2020_03_21_000000_from_create_all_columns_table.php => 2020_03_21_000000_expected_create_all_columns_db_table.php} (97%) create mode 100644 tests/KitLoong/resources/database/migrations/2020_03_21_000000_expected_create_failed_jobs_db_table.php create mode 100644 tests/KitLoong/resources/database/migrations/2020_03_21_000000_expected_create_test_index_db_table.php create mode 100644 tests/KitLoong/resources/database/migrations/2020_03_21_000000_expected_create_user_profile_db_table.php create mode 100644 tests/KitLoong/resources/database/migrations/2020_03_21_000001_expected_create_composite_primary_db_table.php diff --git a/tests/KitLoong/Feature/FeatureTestCase.php b/tests/KitLoong/Feature/FeatureTestCase.php index 276c4781..7908acd2 100644 --- a/tests/KitLoong/Feature/FeatureTestCase.php +++ b/tests/KitLoong/Feature/FeatureTestCase.php @@ -9,6 +9,7 @@ use Dotenv\Dotenv; use Dotenv\Exception\InvalidPathException; +use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\File; use KitLoong\MigrationsGenerator\MigrationsGeneratorServiceProvider; use Tests\KitLoong\TestCase; @@ -64,6 +65,7 @@ protected function prepareStorage() { File::deleteDirectory(storage_path()); File::makeDirectory(config('generators.config.migration_target_path'), 0775, true); + File::makeDirectory($this->migrateFromPath()); File::makeDirectory($this->sqlOutputPath()); } @@ -72,11 +74,36 @@ protected function migrationOutputPath(string $path = ''): string return storage_path('migrations').($path ? DIRECTORY_SEPARATOR.$path : $path); } + protected function migrateFromPath(string $path = ''): string + { + return storage_path('from').($path ? DIRECTORY_SEPARATOR.$path : $path); + } + protected function sqlOutputPath(string $path = ''): string { return storage_path('sql').($path ? DIRECTORY_SEPARATOR.$path : $path); } + protected function migrateExpected(string $connection): void + { + File::copyDirectory(base_path('tests/KitLoong/resources/database/migrations'), $this->migrateFromPath()); + foreach (File::files($this->migrateFromPath()) as $file) { + $content = str_replace([ + '[db]', '_DB_Table' + ], [ + $connection, ucfirst("${connection}Table") + ], $file->getContents()); + + file_put_contents($this->migrateFromPath($file->getBasename()), $content); + File::move( + $this->migrateFromPath($file->getBasename()), + $this->migrateFromPath(str_replace('_db_', "_${connection}_", $file->getBasename())) + ); + } + + $this->loadMigrationsFrom($this->migrateFromPath()); + } + protected function generateMigrations(): void { $this->artisan( @@ -85,5 +112,10 @@ protected function generateMigrations(): void ); } + protected function truncateMigration() + { + DB::table('migrations')->truncate(); + } + abstract protected function dropAllTables(): void; } diff --git a/tests/KitLoong/Feature/MySQL57/CommandTest.php b/tests/KitLoong/Feature/MySQL57/CommandTest.php index f7b6fe98..b5da69c5 100644 --- a/tests/KitLoong/Feature/MySQL57/CommandTest.php +++ b/tests/KitLoong/Feature/MySQL57/CommandTest.php @@ -11,8 +11,9 @@ class CommandTest extends MySQL57TestCase { public function testRun() { - $this->loadMigrationsFrom(base_path('tests/KitLoong/resources/database/migrations')); + $this->migrateExpected('mysql57'); + $this->truncateMigration(); $this->dumpSchemaAs($this->sqlOutputPath('expected.sql')); $this->generateMigrations(); @@ -21,9 +22,10 @@ public function testRun() $this->loadMigrationsFrom($this->migrationOutputPath()); + $this->truncateMigration(); $this->dumpSchemaAs($this->sqlOutputPath('actual.sql')); - $this->assertFileEquals( + $this->assertFileEqualsIgnoringOrder( $this->sqlOutputPath('expected.sql'), $this->sqlOutputPath('actual.sql') ); diff --git a/tests/KitLoong/TestCase.php b/tests/KitLoong/TestCase.php index fb78ff41..4ec924c7 100644 --- a/tests/KitLoong/TestCase.php +++ b/tests/KitLoong/TestCase.php @@ -10,6 +10,7 @@ use Exception; use Mockery; use Orchestra\Testbench\TestCase as Testbench; +use PHPUnit\Framework\Constraint\IsEqual; abstract class TestCase extends Testbench { @@ -37,4 +38,28 @@ protected function getEnvironmentSetUp($app) app()->setBasePath(__DIR__.'/../../'); } + + /** + * Asserts that the contents of one file is equal to the contents of another + * file. + * + * @param string $expected + * @param string $actual + * @param string $message + */ + public static function assertFileEqualsIgnoringOrder(string $expected, string $actual, string $message = ''): void + { + static::assertFileExists($expected, $message); + static::assertFileExists($actual, $message); + + $expectedContent = file($expected); + sort($expectedContent); + + $constraint = new IsEqual($expectedContent); + + $actualContent = file($actual); + sort($actualContent); + + static::assertThat($actualContent, $constraint, $message); + } } diff --git a/tests/KitLoong/resources/database/migrations/2014_10_12_000000_expected_create_users_db_table.php b/tests/KitLoong/resources/database/migrations/2014_10_12_000000_expected_create_users_db_table.php new file mode 100644 index 00000000..2e48c7ec --- /dev/null +++ b/tests/KitLoong/resources/database/migrations/2014_10_12_000000_expected_create_users_db_table.php @@ -0,0 +1,41 @@ +unsignedBigInteger('id'); + $table->unsignedInteger('sub_id'); + $table->string('name'); + $table->string('email')->unique(); + $table->timestamp('email_verified_at')->nullable(); + $table->string('password'); + $table->softDeletes()->comment('Soft delete'); + $table->rememberToken()->comment('Remember token'); + $table->timestamps(); + + $table->primary(['id', 'sub_id']); + }); + } + + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('users_[db]'); + } +} diff --git a/tests/KitLoong/resources/database/migrations/2020_03_21_000000_from_create_all_columns_table.php b/tests/KitLoong/resources/database/migrations/2020_03_21_000000_expected_create_all_columns_db_table.php similarity index 97% rename from tests/KitLoong/resources/database/migrations/2020_03_21_000000_from_create_all_columns_table.php rename to tests/KitLoong/resources/database/migrations/2020_03_21_000000_expected_create_all_columns_db_table.php index ecfbe206..6f0b9473 100644 --- a/tests/KitLoong/resources/database/migrations/2020_03_21_000000_from_create_all_columns_table.php +++ b/tests/KitLoong/resources/database/migrations/2020_03_21_000000_expected_create_all_columns_db_table.php @@ -7,7 +7,7 @@ use Illuminate\Support\Facades\Schema; use KitLoong\MigrationsGenerator\Support\CheckLaravelVersion; -class FromCreateAllColumnsTable extends Migration +class ExpectedCreateAllColumns_DB_Table extends Migration { use CheckLaravelVersion; @@ -18,7 +18,7 @@ class FromCreateAllColumnsTable extends Migration */ public function up() { - Schema::create('all_columns', function (Blueprint $table) { + Schema::create('all_columns_[db]', function (Blueprint $table) { $table->bigInteger('bigInteger'); $table->bigInteger('bigInteger_default')->default(1080); $table->binary('binary'); @@ -153,6 +153,6 @@ public function up() */ public function down() { - Schema::drop('all_columns'); + Schema::dropIfExists('all_columns_[db]'); } } diff --git a/tests/KitLoong/resources/database/migrations/2020_03_21_000000_expected_create_failed_jobs_db_table.php b/tests/KitLoong/resources/database/migrations/2020_03_21_000000_expected_create_failed_jobs_db_table.php new file mode 100644 index 00000000..f94bfdea --- /dev/null +++ b/tests/KitLoong/resources/database/migrations/2020_03_21_000000_expected_create_failed_jobs_db_table.php @@ -0,0 +1,35 @@ +id(); + $table->text('connection'); + $table->text('queue'); + $table->longText('payload'); + $table->longText('exception'); + $table->timestamp('failed_at')->useCurrent(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('failed_jobs_[db]'); + } +} diff --git a/tests/KitLoong/resources/database/migrations/2020_03_21_000000_expected_create_test_index_db_table.php b/tests/KitLoong/resources/database/migrations/2020_03_21_000000_expected_create_test_index_db_table.php new file mode 100644 index 00000000..40b791a1 --- /dev/null +++ b/tests/KitLoong/resources/database/migrations/2020_03_21_000000_expected_create_test_index_db_table.php @@ -0,0 +1,44 @@ +integer('id'); + $table->string('code', 50); + $table->string('email')->unique(); + $table->enum('enum', ['PROGRESS', 'DONE']); + $table->geometry('geometry'); + $table->string('stri"\'helo')->index(); + $table->timestamps(2); + + $table->primary('id'); + + $table->index('enum', 'user_pro file"\'d'); + $table->index('code'); + $table->index(['code', 'enum']); + $table->index(['enum', 'code']); + $table->spatialIndex('geometry'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('test_index_[db]'); + } +} diff --git a/tests/KitLoong/resources/database/migrations/2020_03_21_000000_expected_create_user_profile_db_table.php b/tests/KitLoong/resources/database/migrations/2020_03_21_000000_expected_create_user_profile_db_table.php new file mode 100644 index 00000000..5e247a7d --- /dev/null +++ b/tests/KitLoong/resources/database/migrations/2020_03_21_000000_expected_create_user_profile_db_table.php @@ -0,0 +1,36 @@ +integer('id'); + $table->bigInteger('user_id')->unsigned(); + $table->unsignedInteger('sub_id'); + + $table->primary('id'); + $table->foreign('user_id')->references('id')->on('users_[db]'); + $table->foreign(['user_id', 'sub_id'])->references(['id', 'sub_id'])->on('users_[db]'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('user_profile_[db]'); + } +} diff --git a/tests/KitLoong/resources/database/migrations/2020_03_21_000001_expected_create_composite_primary_db_table.php b/tests/KitLoong/resources/database/migrations/2020_03_21_000001_expected_create_composite_primary_db_table.php new file mode 100644 index 00000000..c4a87719 --- /dev/null +++ b/tests/KitLoong/resources/database/migrations/2020_03_21_000001_expected_create_composite_primary_db_table.php @@ -0,0 +1,32 @@ +unsignedInteger('id'); + $table->unsignedInteger('sub_id'); + $table->primary(['id', 'sub_id']); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('composite_primary_[db]'); + } +} From 581283eaa572166dfb7155abc94e2581d4a0fbe0 Mon Sep 17 00:00:00 2001 From: kitloong Date: Tue, 5 Jan 2021 21:41:15 +0800 Subject: [PATCH 27/65] Use bigIncrements --- .../2020_03_21_000000_expected_create_failed_jobs_db_table.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/KitLoong/resources/database/migrations/2020_03_21_000000_expected_create_failed_jobs_db_table.php b/tests/KitLoong/resources/database/migrations/2020_03_21_000000_expected_create_failed_jobs_db_table.php index f94bfdea..257b433d 100644 --- a/tests/KitLoong/resources/database/migrations/2020_03_21_000000_expected_create_failed_jobs_db_table.php +++ b/tests/KitLoong/resources/database/migrations/2020_03_21_000000_expected_create_failed_jobs_db_table.php @@ -14,7 +14,7 @@ class ExpectedCreateFailedJobs_DB_Table extends Migration public function up() { Schema::create('failed_jobs_[db]', function (Blueprint $table) { - $table->id(); + $table->bigIncrements('id'); $table->text('connection'); $table->text('queue'); $table->longText('payload'); From fb098caefc5a1d3dfadd372d8c05c2cc22320885 Mon Sep 17 00:00:00 2001 From: kitloong Date: Tue, 5 Jan 2021 22:05:49 +0800 Subject: [PATCH 28/65] Test MySQL 8 --- .env.action | 6 ++ .github/workflows/tests.yml | 9 +++ tests/KitLoong/Feature/MySQL8/CommandTest.php | 33 ++++++++++ .../Feature/MySQL8/MySQL8TestCase.php | 63 +++++++++++++++++++ 4 files changed, 111 insertions(+) create mode 100644 tests/KitLoong/Feature/MySQL8/CommandTest.php create mode 100644 tests/KitLoong/Feature/MySQL8/MySQL8TestCase.php diff --git a/.env.action b/.env.action index 9fd29170..e8b86d94 100644 --- a/.env.action +++ b/.env.action @@ -3,3 +3,9 @@ MYSQL57_PORT=3306 MYSQL57_DATABASE=migration MYSQL57_USERNAME=root MYSQL57_PASSWORD= + +MYSQL8_HOST=127.0.0.1 +MYSQL8_PORT=33062 +MYSQL8_DATABASE=migration +MYSQL8_USERNAME=root +MYSQL8_PASSWORD= diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 89c08730..cfbef0ce 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -17,6 +17,15 @@ jobs: - 3306:3306 options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 + mysql8: + image: mysql:8 + env: + MYSQL_ALLOW_EMPTY_PASSWORD: yes + MYSQL_DATABASE: migration + ports: + - 33062:3306 + options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 + strategy: matrix: php: [ 7.3, 7.4 ] diff --git a/tests/KitLoong/Feature/MySQL8/CommandTest.php b/tests/KitLoong/Feature/MySQL8/CommandTest.php new file mode 100644 index 00000000..4d30cc0b --- /dev/null +++ b/tests/KitLoong/Feature/MySQL8/CommandTest.php @@ -0,0 +1,33 @@ +migrateExpected('mysql8'); + + $this->truncateMigration(); + $this->dumpSchemaAs($this->sqlOutputPath('expected.sql')); + + $this->generateMigrations(); + + $this->dropAllTables(); + + $this->loadMigrationsFrom($this->migrationOutputPath()); + + $this->truncateMigration(); + $this->dumpSchemaAs($this->sqlOutputPath('actual.sql')); + + $this->assertFileEqualsIgnoringOrder( + $this->sqlOutputPath('expected.sql'), + $this->sqlOutputPath('actual.sql') + ); + } +} diff --git a/tests/KitLoong/Feature/MySQL8/MySQL8TestCase.php b/tests/KitLoong/Feature/MySQL8/MySQL8TestCase.php new file mode 100644 index 00000000..447877ab --- /dev/null +++ b/tests/KitLoong/Feature/MySQL8/MySQL8TestCase.php @@ -0,0 +1,63 @@ +set('database.default', 'mysql'); + $app['config']->set('database.connections.mysql', [ + 'driver' => 'mysql', + 'url' => null, + 'host' => env('MYSQL8_HOST'), + 'port' => env('MYSQL8_PORT'), + 'database' => env('MYSQL8_DATABASE'), + 'username' => env('MYSQL8_USERNAME'), + 'password' => env('MYSQL8_PASSWORD'), + 'unix_socket' => env('DB_SOCKET', ''), + 'charset' => 'utf8mb4', + 'collation' => 'utf8mb4_general_ci', + 'prefix' => '', + 'prefix_indexes' => true, + 'strict' => true, + 'engine' => null, + 'options' => extension_loaded('pdo_mysql') ? array_filter([ + PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'), + ]) : [], + ]); + } + + protected function dumpSchemaAs(string $destination): void + { + $command = sprintf( + 'mysqldump -h %s -u %s -p\'%s\' %s --compact --no-data > %s', + config('database.connections.mysql.host'), + config('database.connections.mysql.username'), + config('database.connections.mysql.password'), + config('database.connections.mysql.database'), + $destination + ); + exec($command); + } + + protected function dropAllTables(): void + { + $tables = DB::select('SHOW TABLES'); + foreach ($tables as $table) { + Schema::drop($table->{'Tables_in_'.config('database.connections.mysql.database')}); + } + } +} From fbde4738e7e8d970f3bfa611b51c57473d57f803 Mon Sep 17 00:00:00 2001 From: kitloong Date: Wed, 6 Jan 2021 21:37:19 +0800 Subject: [PATCH 29/65] Delete --- ...4_10_12_000000_from_create_users_table.php | 37 ------------------- 1 file changed, 37 deletions(-) delete mode 100644 tests/KitLoong/resources/database/migrations/2014_10_12_000000_from_create_users_table.php diff --git a/tests/KitLoong/resources/database/migrations/2014_10_12_000000_from_create_users_table.php b/tests/KitLoong/resources/database/migrations/2014_10_12_000000_from_create_users_table.php deleted file mode 100644 index 9db7d530..00000000 --- a/tests/KitLoong/resources/database/migrations/2014_10_12_000000_from_create_users_table.php +++ /dev/null @@ -1,37 +0,0 @@ -unsignedBigInteger('id'); - $table->string('name'); - $table->string('email')->unique(); - $table->timestamp('email_verified_at')->nullable(); - $table->string('password'); - $table->softDeletes(); - $table->rememberToken(); - $table->timestamps(); - }); - } - - - /** - * Reverse the migrations. - * - * @return void - */ - public function down() - { - Schema::drop('users'); - } -} From cdd673e0a1ffe1fa9634c04d177de50a75bc0127 Mon Sep 17 00:00:00 2001 From: kitloong Date: Wed, 6 Jan 2021 21:37:38 +0800 Subject: [PATCH 30/65] Use -p --- tests/KitLoong/Feature/MySQL57/MySQL57TestCase.php | 4 +++- tests/KitLoong/Feature/MySQL8/MySQL8TestCase.php | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/KitLoong/Feature/MySQL57/MySQL57TestCase.php b/tests/KitLoong/Feature/MySQL57/MySQL57TestCase.php index d18c144c..6ab53795 100644 --- a/tests/KitLoong/Feature/MySQL57/MySQL57TestCase.php +++ b/tests/KitLoong/Feature/MySQL57/MySQL57TestCase.php @@ -43,7 +43,9 @@ protected function getEnvironmentSetUp($app) protected function dumpSchemaAs(string $destination): void { $command = sprintf( - 'mysqldump -h %s -u %s -p\'%s\' %s --compact --no-data > %s', + 'mysqldump -h %s -u %s ' + .(!empty(config('database.connections.mysql.password')) ? '-p\'%s\'' : ''). + ' %s --compact --no-data > %s', config('database.connections.mysql.host'), config('database.connections.mysql.username'), config('database.connections.mysql.password'), diff --git a/tests/KitLoong/Feature/MySQL8/MySQL8TestCase.php b/tests/KitLoong/Feature/MySQL8/MySQL8TestCase.php index 447877ab..32bd42a8 100644 --- a/tests/KitLoong/Feature/MySQL8/MySQL8TestCase.php +++ b/tests/KitLoong/Feature/MySQL8/MySQL8TestCase.php @@ -43,7 +43,9 @@ protected function getEnvironmentSetUp($app) protected function dumpSchemaAs(string $destination): void { $command = sprintf( - 'mysqldump -h %s -u %s -p\'%s\' %s --compact --no-data > %s', + 'mysqldump -h %s -u %s ' + .(!empty(config('database.connections.mysql.password')) ? '-p\'%s\'' : ''). + ' %s --compact --no-data > %s', config('database.connections.mysql.host'), config('database.connections.mysql.username'), config('database.connections.mysql.password'), From a5d5b327681b7bdc7c0f43162a7d7ea671c41dc1 Mon Sep 17 00:00:00 2001 From: kitloong Date: Wed, 6 Jan 2021 21:37:57 +0800 Subject: [PATCH 31/65] Alter root plugin --- .github/workflows/tests.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index cfbef0ce..55fbfad1 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -52,6 +52,11 @@ jobs: - name: Checkout code uses: actions/checkout@v2 + - name: Alter MySQL 8 root plugin + run: | + mysql --version + mysql --host 127.0.0.1 --port ${{ job.services.mysql8.ports['3306'] }} -u root -e "ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY ''" + - name: Setup PHP uses: shivammathur/setup-php@v2 with: From e2b2739740a446180e2d057a8d9a7298a266e754 Mon Sep 17 00:00:00 2001 From: kitloong Date: Wed, 6 Jan 2021 21:53:09 +0800 Subject: [PATCH 32/65] Fixed command --- tests/KitLoong/Feature/MySQL57/MySQL57TestCase.php | 8 ++++---- tests/KitLoong/Feature/MySQL8/MySQL8TestCase.php | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/KitLoong/Feature/MySQL57/MySQL57TestCase.php b/tests/KitLoong/Feature/MySQL57/MySQL57TestCase.php index 6ab53795..223ca218 100644 --- a/tests/KitLoong/Feature/MySQL57/MySQL57TestCase.php +++ b/tests/KitLoong/Feature/MySQL57/MySQL57TestCase.php @@ -42,13 +42,13 @@ protected function getEnvironmentSetUp($app) protected function dumpSchemaAs(string $destination): void { + $password = (!empty(config('database.connections.mysql.password')) ? + '-p\''.config('database.connections.mysql.password').'\'' : + ''); $command = sprintf( - 'mysqldump -h %s -u %s ' - .(!empty(config('database.connections.mysql.password')) ? '-p\'%s\'' : ''). - ' %s --compact --no-data > %s', + 'mysqldump -h %s -u %s '.$password.' %s --compact --no-data > %s', config('database.connections.mysql.host'), config('database.connections.mysql.username'), - config('database.connections.mysql.password'), config('database.connections.mysql.database'), $destination ); diff --git a/tests/KitLoong/Feature/MySQL8/MySQL8TestCase.php b/tests/KitLoong/Feature/MySQL8/MySQL8TestCase.php index 32bd42a8..e328e0cb 100644 --- a/tests/KitLoong/Feature/MySQL8/MySQL8TestCase.php +++ b/tests/KitLoong/Feature/MySQL8/MySQL8TestCase.php @@ -42,13 +42,13 @@ protected function getEnvironmentSetUp($app) protected function dumpSchemaAs(string $destination): void { + $password = (!empty(config('database.connections.mysql.password')) ? + '-p\''.config('database.connections.mysql.password').'\'' : + ''); $command = sprintf( - 'mysqldump -h %s -u %s ' - .(!empty(config('database.connections.mysql.password')) ? '-p\'%s\'' : ''). - ' %s --compact --no-data > %s', + 'mysqldump -h %s -u %s '.$password.' %s --compact --no-data > %s', config('database.connections.mysql.host'), config('database.connections.mysql.username'), - config('database.connections.mysql.password'), config('database.connections.mysql.database'), $destination ); From a12b72b65cce1037f05e2b2b1315483812272f4c Mon Sep 17 00:00:00 2001 From: kitloong Date: Fri, 18 Jun 2021 23:57:34 +0800 Subject: [PATCH 33/65] Improve Postgres timestamp generation --- .../Generators/DatetimeField.php | 16 +++++++++++++++- .../Generators/FieldGenerator.php | 3 +++ .../MigrationsGenerator/Support/Regex.php | 15 +++++++++++++++ .../MigrationsGenerator/Types/DBALTypes.php | 15 +++++++++++++++ 4 files changed, 48 insertions(+), 1 deletion(-) create mode 100644 src/KitLoong/MigrationsGenerator/Support/Regex.php diff --git a/src/KitLoong/MigrationsGenerator/Generators/DatetimeField.php b/src/KitLoong/MigrationsGenerator/Generators/DatetimeField.php index cc67043f..3f00ad41 100644 --- a/src/KitLoong/MigrationsGenerator/Generators/DatetimeField.php +++ b/src/KitLoong/MigrationsGenerator/Generators/DatetimeField.php @@ -13,17 +13,23 @@ use KitLoong\MigrationsGenerator\MigrationMethod\ColumnType; use KitLoong\MigrationsGenerator\MigrationsGeneratorSetting; use KitLoong\MigrationsGenerator\Repositories\MySQLRepository; +use KitLoong\MigrationsGenerator\Repositories\PgSQLRepository; +use KitLoong\MigrationsGenerator\Support\Regex; use KitLoong\MigrationsGenerator\Types\DBALTypes; class DatetimeField { private $decorator; private $mySQLRepository; + private $pgSQLRepository; + private $regex; - public function __construct(Decorator $decorator, MySQLRepository $mySQLRepository) + public function __construct(Decorator $decorator, MySQLRepository $mySQLRepository, PgSQLRepository $pgSQLRepository, Regex $regex) { $this->decorator = $decorator; $this->mySQLRepository = $mySQLRepository; + $this->pgSQLRepository = $pgSQLRepository; + $this->regex = $regex; } public function makeField(string $table, array $field, Column $column, bool $useTimestamps): array @@ -51,6 +57,14 @@ public function makeField(string $table, array $field, Column $column, bool $use $field['field'] = ColumnName::DELETED_AT; } $field['args'][] = $column->getLength(); + } else { + if (app(MigrationsGeneratorSetting::class)->getPlatform() === Platform::POSTGRESQL) { + $rawType = ($this->pgSQLRepository->getTypeByColumnName($table, $column->getName())); + $length = $this->regex->getTextBetween($rawType); + if ($length !== null) { + $field['args'][] = $length; + } + } } if (app(MigrationsGeneratorSetting::class)->getPlatform() === Platform::MYSQL) { diff --git a/src/KitLoong/MigrationsGenerator/Generators/FieldGenerator.php b/src/KitLoong/MigrationsGenerator/Generators/FieldGenerator.php index a0fc7546..35aa9a99 100644 --- a/src/KitLoong/MigrationsGenerator/Generators/FieldGenerator.php +++ b/src/KitLoong/MigrationsGenerator/Generators/FieldGenerator.php @@ -176,6 +176,9 @@ private function makeLaravelFieldTypeMethod( case DBALTypes::DATETIME_MUTABLE: case DBALTypes::TIMESTAMP: case DBALTypes::TIME_MUTABLE: + case DBALTypes::DATETIME_TZ: + case DBALTypes::TIME_TZ: + case DBALTypes::TIMESTAMP_TZ: return $this->datetimeField->makeField($tableName, $field, $column, $useTimestamps); case DBALTypes::DECIMAL: case DBALTypes::FLOAT: diff --git a/src/KitLoong/MigrationsGenerator/Support/Regex.php b/src/KitLoong/MigrationsGenerator/Support/Regex.php new file mode 100644 index 00000000..29d20c8f --- /dev/null +++ b/src/KitLoong/MigrationsGenerator/Support/Regex.php @@ -0,0 +1,15 @@ + Date: Fri, 18 Jun 2021 23:58:36 +0800 Subject: [PATCH 34/65] Format --- .../Repositories/PgSQLRepository.php | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/KitLoong/MigrationsGenerator/Repositories/PgSQLRepository.php b/src/KitLoong/MigrationsGenerator/Repositories/PgSQLRepository.php index 0b8e55cd..14812c77 100644 --- a/src/KitLoong/MigrationsGenerator/Repositories/PgSQLRepository.php +++ b/src/KitLoong/MigrationsGenerator/Repositories/PgSQLRepository.php @@ -18,20 +18,20 @@ public function getTypeByColumnName(string $table, string $columnName): ?string $column = $setting->getConnection() ->select("SELECT - pg_catalog.format_type(a.atttypid, a.atttypmod) as \"datatype\" -FROM - pg_catalog.pg_attribute a -WHERE - a.attnum > 0 - AND NOT a.attisdropped - AND a.attrelid = ( - SELECT c.oid - FROM pg_catalog.pg_class c - LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace - WHERE c.relname ~ '^(${table})$' - AND pg_catalog.pg_table_is_visible(c.oid) - ) - AND a.attname='${columnName}'"); + pg_catalog.format_type(a.atttypid, a.atttypmod) as datatype + FROM + pg_catalog.pg_attribute a + WHERE + a.attnum > 0 + AND NOT a.attisdropped + AND a.attrelid = ( + SELECT c.oid + FROM pg_catalog.pg_class c + LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace + WHERE c.relname ~ '^(${table})$' + AND pg_catalog.pg_table_is_visible(c.oid) + ) + AND a.attname='${columnName}'"); if (count($column) > 0) { return $column[0]->datatype; } From 657ca1861283deed81a690b981d74aaa47e012d6 Mon Sep 17 00:00:00 2001 From: kitloong Date: Sun, 20 Jun 2021 17:27:23 +0800 Subject: [PATCH 35/65] Generate PostgreSQL enum --- .../Generators/StringField.php | 46 +++++++++++---- .../Repositories/PgSQLRepository.php | 57 ++++++++++++++----- .../MigrationsGenerator/Support/Regex.php | 25 +++++++- 3 files changed, 101 insertions(+), 27 deletions(-) diff --git a/src/KitLoong/MigrationsGenerator/Generators/StringField.php b/src/KitLoong/MigrationsGenerator/Generators/StringField.php index 4cad6af4..f2b7fccd 100644 --- a/src/KitLoong/MigrationsGenerator/Generators/StringField.php +++ b/src/KitLoong/MigrationsGenerator/Generators/StringField.php @@ -12,29 +12,41 @@ use KitLoong\MigrationsGenerator\Generators\Modifier\CollationModifier; use KitLoong\MigrationsGenerator\MigrationMethod\ColumnName; use KitLoong\MigrationsGenerator\MigrationMethod\ColumnType; +use KitLoong\MigrationsGenerator\MigrationsGeneratorSetting; +use KitLoong\MigrationsGenerator\Repositories\PgSQLRepository; +use KitLoong\MigrationsGenerator\Support\Regex; class StringField { private $collationModifier; + private $pgSQLRepository; + private $regex; - public function __construct(CollationModifier $collationModifier) + public function __construct(CollationModifier $collationModifier, PgSQLRepository $pgSQLRepository, Regex $regex) { $this->collationModifier = $collationModifier; + $this->pgSQLRepository = $pgSQLRepository; + $this->regex = $regex; } public function makeField(string $tableName, array $field, Column $column): array { - if ($field['field'] === ColumnName::REMEMBER_TOKEN && $column->getLength() === 100 && !$column->getFixed()) { - $field['type'] = ColumnType::REMEMBER_TOKEN; - $field['field'] = null; - $field['args'] = []; + if (($pgSQLEnum = $this->getPgSQLEnumValue($tableName, $column->getName())) !== '') { + $field['type'] = ColumnType::ENUM; + $field['args'][] = $pgSQLEnum; } else { - if ($column->getFixed()) { - $field['type'] = ColumnType::CHAR; - } + if ($field['field'] === ColumnName::REMEMBER_TOKEN && $column->getLength() === 100 && !$column->getFixed()) { + $field['type'] = ColumnType::REMEMBER_TOKEN; + $field['field'] = null; + $field['args'] = []; + } else { + if ($column->getFixed()) { + $field['type'] = ColumnType::CHAR; + } - if ($column->getLength() && $column->getLength() !== Builder::$defaultStringLength) { - $field['args'][] = $column->getLength(); + if ($column->getLength() && $column->getLength() !== Builder::$defaultStringLength) { + $field['args'][] = $column->getLength(); + } } } @@ -45,4 +57,18 @@ public function makeField(string $tableName, array $field, Column $column): arra return $field; } + + private function getPgSQLEnumValue(string $tableName, string $column): string + { + if (app(MigrationsGeneratorSetting::class)->getPlatform() === Platform::POSTGRESQL) { + $definition = ($this->pgSQLRepository->getCheckConstraintDefinition($tableName, $column)); + if (!empty($definition)) { + $enumValues = $this->regex->getTextBetweenAll($definition, "'", "'::"); + if (!empty($enumValues)) { + return "['".implode("', '", $enumValues)."']"; + } + } + } + return ''; + } } diff --git a/src/KitLoong/MigrationsGenerator/Repositories/PgSQLRepository.php b/src/KitLoong/MigrationsGenerator/Repositories/PgSQLRepository.php index 14812c77..ba56f42b 100644 --- a/src/KitLoong/MigrationsGenerator/Repositories/PgSQLRepository.php +++ b/src/KitLoong/MigrationsGenerator/Repositories/PgSQLRepository.php @@ -17,24 +17,51 @@ public function getTypeByColumnName(string $table, string $columnName): ?string $setting = app(MigrationsGeneratorSetting::class); $column = $setting->getConnection() - ->select("SELECT - pg_catalog.format_type(a.atttypid, a.atttypmod) as datatype - FROM - pg_catalog.pg_attribute a - WHERE - a.attnum > 0 - AND NOT a.attisdropped - AND a.attrelid = ( - SELECT c.oid - FROM pg_catalog.pg_class c - LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace - WHERE c.relname ~ '^(${table})$' - AND pg_catalog.pg_table_is_visible(c.oid) - ) - AND a.attname='${columnName}'"); + ->select(" + SELECT pg_catalog.format_type(a.atttypid, a.atttypmod) as datatype + FROM + pg_catalog.pg_attribute a + WHERE + a.attnum > 0 + AND NOT a.attisdropped + AND a.attrelid = ( + SELECT c.oid + FROM pg_catalog.pg_class c + LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace + WHERE c.relname ~ '^(${table})$' + AND pg_catalog.pg_table_is_visible(c.oid) + ) + AND a.attname='${columnName}'"); if (count($column) > 0) { return $column[0]->datatype; } return null; } + + public function getCheckConstraintDefinition(string $table, string $column): ?string + { + $setting = app(MigrationsGeneratorSetting::class); + $column = $setting->getConnection() + ->select(" + SELECT pgc.conname AS constraint_name, + pgc.contype, + ccu.table_schema AS table_schema, + ccu.table_name, + ccu.column_name, + pgc.consrc AS definition + FROM pg_constraint pgc + JOIN pg_namespace nsp ON nsp.oid = pgc.connamespace + JOIN pg_class cls ON pgc.conrelid = cls.oid + LEFT JOIN information_schema.constraint_column_usage ccu + ON pgc.conname = ccu.constraint_name + AND nsp.nspname = ccu.constraint_schema + WHERE contype ='c' + AND ccu.table_name='${table}' + AND ccu.column_name='${column}'; + "); + if (count($column) > 0) { + return $column[0]->definition; + } + return null; + } } diff --git a/src/KitLoong/MigrationsGenerator/Support/Regex.php b/src/KitLoong/MigrationsGenerator/Support/Regex.php index 29d20c8f..122581bc 100644 --- a/src/KitLoong/MigrationsGenerator/Support/Regex.php +++ b/src/KitLoong/MigrationsGenerator/Support/Regex.php @@ -4,12 +4,33 @@ class Regex { - public function getTextBetween(string $text, string $left = '(', string $right = ')'): ?string + /** + * @param string $text + * @param string $left + * @param string $right + * @return string|null + */ + public function getTextBetween(string $text, string $left = '\(', string $right = '\)'): ?string { - $matched = preg_match('/\\'.$left.'(.*?)\\'.$right.'/', $text, $output); + $matched = preg_match('/'.$left.'(.*?)'.$right.'/', $text, $output); if ($matched === 1) { return $output[1]; } return null; } + + /** + * @param string $text + * @param string $left + * @param string $right + * @return array|null|string[] + */ + public function getTextBetweenAll(string $text, string $left = '\(', string $right = '\)'): ?array + { + $matched = preg_match_all('/'.$left.'(.*?)'.$right.'/', $text, $output); + if ($matched > 0) { + return $output[1]; + } + return null; + } } From 1cddfcf295e85742ffc7e32795964cd544d6a8f4 Mon Sep 17 00:00:00 2001 From: kitloong Date: Sun, 20 Jun 2021 17:27:58 +0800 Subject: [PATCH 36/65] Fixed wrong precision of type softDeletes --- .../Generators/DatetimeField.php | 36 ++++++++++++------- 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/src/KitLoong/MigrationsGenerator/Generators/DatetimeField.php b/src/KitLoong/MigrationsGenerator/Generators/DatetimeField.php index 3f00ad41..7d8f3939 100644 --- a/src/KitLoong/MigrationsGenerator/Generators/DatetimeField.php +++ b/src/KitLoong/MigrationsGenerator/Generators/DatetimeField.php @@ -24,8 +24,12 @@ class DatetimeField private $pgSQLRepository; private $regex; - public function __construct(Decorator $decorator, MySQLRepository $mySQLRepository, PgSQLRepository $pgSQLRepository, Regex $regex) - { + public function __construct( + Decorator $decorator, + MySQLRepository $mySQLRepository, + PgSQLRepository $pgSQLRepository, + Regex $regex + ) { $this->decorator = $decorator; $this->mySQLRepository = $mySQLRepository; $this->pgSQLRepository = $pgSQLRepository; @@ -52,19 +56,12 @@ public function makeField(string $table, array $field, Column $column, bool $use $field['type'] = FieldGenerator::$fieldTypeMap[$field['type']]; } - if ($column->getLength() !== null && $column->getLength() > 0) { + $length = $this->getLength($table, $column); + if ($length !== null && $length > 0) { if ($field['type'] === ColumnType::SOFT_DELETES) { $field['field'] = ColumnName::DELETED_AT; } - $field['args'][] = $column->getLength(); - } else { - if (app(MigrationsGeneratorSetting::class)->getPlatform() === Platform::POSTGRESQL) { - $rawType = ($this->pgSQLRepository->getTypeByColumnName($table, $column->getName())); - $length = $this->regex->getTextBetween($rawType); - if ($length !== null) { - $field['args'][] = $length; - } - } + $field['args'][] = $length; } if (app(MigrationsGeneratorSetting::class)->getPlatform() === Platform::MYSQL) { @@ -114,4 +111,19 @@ public function isUseTimestamps($columns): bool } return $useTimestamps; } + + private function getLength(string $table, Column $column): ?int + { + if (app(MigrationsGeneratorSetting::class)->getPlatform() === Platform::POSTGRESQL) { + $rawType = ($this->pgSQLRepository->getTypeByColumnName($table, $column->getName())); + $length = $this->regex->getTextBetween($rawType); + if ($length !== null) { + return (int) $length; + } else { + return null; + } + } else { + return $column->getLength(); + } + } } From 5e3446970f3b63181fca80791332fd2b4300f0c9 Mon Sep 17 00:00:00 2001 From: kitloong Date: Mon, 21 Jun 2021 09:08:41 +0800 Subject: [PATCH 37/65] Fixed PostgreSQL spatial index --- .../Generators/ForeignKeyGenerator.php | 4 ++-- .../Generators/IndexGenerator.php | 24 +++++++++++++++---- .../Repositories/PgSQLRepository.php | 21 ++++++++++++++++ 3 files changed, 42 insertions(+), 7 deletions(-) diff --git a/src/KitLoong/MigrationsGenerator/Generators/ForeignKeyGenerator.php b/src/KitLoong/MigrationsGenerator/Generators/ForeignKeyGenerator.php index d0bb3dbc..8a6556f2 100644 --- a/src/KitLoong/MigrationsGenerator/Generators/ForeignKeyGenerator.php +++ b/src/KitLoong/MigrationsGenerator/Generators/ForeignKeyGenerator.php @@ -38,8 +38,8 @@ public function generate(string $table, $foreignKeys, bool $ignoreForeignKeyName 'fields' => $foreignKey->getLocalColumns(), 'references' => $foreignKey->getForeignColumns(), 'on' => $this->decorator->tableWithoutPrefix($foreignKey->getForeignTableName()), - 'onUpdate' => $foreignKey->hasOption('onUpdate') ? $foreignKey->getOption('onUpdate') : 'RESTRICT', - 'onDelete' => $foreignKey->hasOption('onDelete') ? $foreignKey->getOption('onDelete') : 'RESTRICT', + 'onUpdate' => $foreignKey->hasOption('onUpdate') ? $foreignKey->getOption('onUpdate') : null, + 'onDelete' => $foreignKey->hasOption('onDelete') ? $foreignKey->getOption('onDelete') : null, ]; } return $fields; diff --git a/src/KitLoong/MigrationsGenerator/Generators/IndexGenerator.php b/src/KitLoong/MigrationsGenerator/Generators/IndexGenerator.php index fa75fb37..e71dc08c 100644 --- a/src/KitLoong/MigrationsGenerator/Generators/IndexGenerator.php +++ b/src/KitLoong/MigrationsGenerator/Generators/IndexGenerator.php @@ -10,14 +10,18 @@ use Doctrine\DBAL\Schema\Index; use Illuminate\Support\Collection; use KitLoong\MigrationsGenerator\MigrationMethod\IndexType; +use KitLoong\MigrationsGenerator\MigrationsGeneratorSetting; +use KitLoong\MigrationsGenerator\Repositories\PgSQLRepository; class IndexGenerator { private $decorator; + private $pgSQLRepository; - public function __construct(Decorator $decorator) + public function __construct(Decorator $decorator, PgSQLRepository $pgSQLRepository) { $this->decorator = $decorator; + $this->pgSQLRepository = $pgSQLRepository; } /** @@ -31,6 +35,13 @@ public function generate(string $table, $indexes, bool $ignoreIndexNames): array $singleColIndexes = collect([]); $multiColIndexes = collect([]); + // Doctrine/Dbal doesn't return spatial information from PostgreSQL + // Use raw SQL here to create $spatial index name list. + $spatials = collect([]); + if (app(MigrationsGeneratorSetting::class)->getPlatform() === Platform::POSTGRESQL) { + $spatials = $this->pgSQLRepository->getSpatialIndexNames($table); + } + foreach ($indexes as $index) { $indexField = [ 'field' => array_map([$this->decorator, 'addSlash'], $index->getColumns()), @@ -42,13 +53,16 @@ public function generate(string $table, $indexes, bool $ignoreIndexNames): array $indexField['type'] = IndexType::PRIMARY; } elseif ($index->isUnique()) { $indexField['type'] = IndexType::UNIQUE; - } elseif (count($index->getFlags()) > 0 && - in_array('spatial', $index->getFlags())) { + } elseif (( + count($index->getFlags()) > 0 && in_array('spatial', $index->getFlags()) + ) || $spatials->contains($index->getName())) { $indexField['type'] = IndexType::SPATIAL_INDEX; } - if (!$ignoreIndexNames && !$this->useLaravelStyleDefaultName($table, $index, $indexField['type'])) { - $indexField['args'][] = $this->decorateName($index->getName()); + if (!$index->isPrimary()) { + if (!$ignoreIndexNames && !$this->useLaravelStyleDefaultName($table, $index, $indexField['type'])) { + $indexField['args'][] = $this->decorateName($index->getName()); + } } if (count($index->getColumns()) === 1) { diff --git a/src/KitLoong/MigrationsGenerator/Repositories/PgSQLRepository.php b/src/KitLoong/MigrationsGenerator/Repositories/PgSQLRepository.php index ba56f42b..dbc246ff 100644 --- a/src/KitLoong/MigrationsGenerator/Repositories/PgSQLRepository.php +++ b/src/KitLoong/MigrationsGenerator/Repositories/PgSQLRepository.php @@ -7,6 +7,7 @@ namespace KitLoong\MigrationsGenerator\Repositories; +use Illuminate\Support\Collection; use KitLoong\MigrationsGenerator\MigrationsGeneratorSetting; class PgSQLRepository @@ -64,4 +65,24 @@ public function getCheckConstraintDefinition(string $table, string $column): ?st } return null; } + + public function getSpatialIndexNames(string $table): Collection + { + $setting = app(MigrationsGeneratorSetting::class); + $columns = $setting->getConnection() + ->select(" + SELECT tablename, + indexname, + indexdef + FROM pg_indexes + WHERE tablename = '${table}' + AND indexdef LIKE '% USING gist %'"); + $definitions = collect([]); + if (count($columns) > 0) { + foreach ($columns as $column) { + $definitions->push($column->indexname); + } + } + return $definitions; + } } From 2f6546acebf24102f43ef7d3a90775c9b5648f82 Mon Sep 17 00:00:00 2001 From: kitloong Date: Tue, 22 Jun 2021 20:58:20 +0800 Subject: [PATCH 38/65] Removed old tests --- .../Generators/DatetimeFieldTest.php | 276 ------------------ .../Generators/IndexGeneratorTest.php | 204 ------------- .../Generators/StringFieldTest.php | 144 --------- .../Repositories/PgSQLRepositoryTest.php | 69 ----- 4 files changed, 693 deletions(-) delete mode 100644 tests/KitLoong/MigrationsGenerator/Generators/DatetimeFieldTest.php delete mode 100644 tests/KitLoong/MigrationsGenerator/Generators/IndexGeneratorTest.php delete mode 100644 tests/KitLoong/MigrationsGenerator/Generators/StringFieldTest.php delete mode 100644 tests/KitLoong/MigrationsGenerator/Repositories/PgSQLRepositoryTest.php diff --git a/tests/KitLoong/MigrationsGenerator/Generators/DatetimeFieldTest.php b/tests/KitLoong/MigrationsGenerator/Generators/DatetimeFieldTest.php deleted file mode 100644 index a14a5147..00000000 --- a/tests/KitLoong/MigrationsGenerator/Generators/DatetimeFieldTest.php +++ /dev/null @@ -1,276 +0,0 @@ -shouldReceive('getNotnull') - ->andReturn(false) - ->once(); - $column->shouldReceive('getLength') - ->andReturn(2); - $column->shouldReceive('getType->getName') - ->andReturn(DBALTypes::TIMESTAMP) - ->once(); - $column->shouldReceive('getName') - ->withNoArgs() - ->andReturn(ColumnName::DELETED_AT) - ->once(); - - $this->mock(MySQLRepository::class, function (MockInterface $mock) { - $mock->shouldReceive('useOnUpdateCurrentTimestamp') - ->with('table', ColumnName::DELETED_AT) - ->andReturnFalse() - ->once(); - }); - - $this->mock(MigrationsGeneratorSetting::class, function (MockInterface $mock) { - $mock->shouldReceive('getPlatform') - ->withNoArgs() - ->andReturn(Platform::MYSQL) - ->once(); - }); - - /** @var DatetimeField $datetimeField */ - $datetimeField = resolve(DatetimeField::class); - - $field = [ - 'field' => ColumnName::DELETED_AT, - 'type' => 'timestamp', - 'args' => [] - ]; - - $field = $datetimeField->makeField('table', $field, $column, false); - $this->assertSame(ColumnType::SOFT_DELETES, $field['type']); - $this->assertSame(ColumnName::DELETED_AT, $field['field']); - $this->assertSame([2], $field['args']); - } - - public function testMakeFieldIsTimestamps() - { - $column = Mockery::mock(Column::class); - $column->shouldReceive('getLength') - ->andReturn(2); - $column->shouldReceive('getType->getName') - ->andReturn(DBALTypes::TIMESTAMP) - ->once(); - $column->shouldReceive('getName') - ->withNoArgs() - ->andReturn(ColumnName::UPDATED_AT) - ->once(); - - $this->mock(MySQLRepository::class, function (MockInterface $mock) { - $mock->shouldReceive('useOnUpdateCurrentTimestamp') - ->with('table', ColumnName::UPDATED_AT) - ->andReturnFalse() - ->once(); - }); - - $this->mock(MigrationsGeneratorSetting::class, function (MockInterface $mock) { - $mock->shouldReceive('getPlatform') - ->withNoArgs() - ->andReturn(Platform::MYSQL) - ->once(); - }); - - /** @var DatetimeField $datetimeField */ - $datetimeField = resolve(DatetimeField::class); - $field = [ - 'field' => ColumnName::UPDATED_AT, - 'type' => 'timestamp', - 'args' => [] - ]; - - $field = $datetimeField->makeField('table', $field, $column, true); - $this->assertSame(ColumnType::TIMESTAMPS, $field['type']); - $this->assertNull($field['field']); - $this->assertSame([2], $field['args']); - } - - public function testMakeFieldIsDatetime() - { - $column = Mockery::mock(Column::class); - $column->shouldReceive('getLength') - ->andReturn(2); - $column->shouldReceive('getType->getName') - ->andReturn(DBALTypes::DATETIME_MUTABLE) - ->once(); - - $this->mock(MigrationsGeneratorSetting::class, function (MockInterface $mock) { - $mock->shouldReceive('getPlatform') - ->withNoArgs() - ->andReturn(Platform::MYSQL) - ->once(); - }); - - /** @var DatetimeField $datetimeField */ - $datetimeField = resolve(DatetimeField::class); - - $field = [ - 'field' => 'date', - 'type' => 'datetime', - 'args' => [] - ]; - - $field = $datetimeField->makeField('table', $field, $column, false); - $this->assertSame(ColumnType::DATETIME, $field['type']); - $this->assertSame([2], $field['args']); - } - - public function testMakeFieldSkipCreatedAtWhenIsTimestamps() - { - /** @var DatetimeField $datetimeField */ - $datetimeField = resolve(DatetimeField::class); - - $field = [ - 'field' => ColumnName::CREATED_AT, - 'type' => 'timestamp', - 'args' => [] - ]; - $column = Mockery::mock(Column::class); - $field = $datetimeField->makeField('table', $field, $column, true); - $this->assertEmpty($field); - } - - public function testMakeDefaultIsUseCurrent() - { - /** @var DatetimeField $datetimeField */ - $datetimeField = resolve(DatetimeField::class); - $column = Mockery::mock(Column::class); - $column->shouldReceive('getDefault') - ->andReturn('CURRENT_TIMESTAMP') - ->once(); - - $result = $datetimeField->makeDefault($column); - $this->assertSame(ColumnModifier::USE_CURRENT, $result); - } - - public function testMakeDefaulNormal() - { - /** @var DatetimeField $datetimeField */ - $datetimeField = resolve(DatetimeField::class); - $column = Mockery::mock(Column::class); - $column->shouldReceive('getDefault') - ->andReturn('Default'); - - $result = $datetimeField->makeDefault($column); - $this->assertSame("default('Default')", $result); - } - - public function testIsUseTimestampsTrue() - { - /** @var DatetimeField $datetimeField */ - $datetimeField = resolve(DatetimeField::class); - - $columnCreatedAt = Mockery::mock(Column::class); - $columnCreatedAt->shouldReceive('getName') - ->andReturn(ColumnName::CREATED_AT); - $columnCreatedAt->shouldReceive('getNotnull') - ->andReturnFalse(); - $columnCreatedAt->shouldReceive('getDefault') - ->andReturnNull(); - - $columnUpdatedAt = Mockery::mock(Column::class); - $columnUpdatedAt->shouldReceive('getName') - ->andReturn(ColumnName::UPDATED_AT); - $columnUpdatedAt->shouldReceive('getNotnull') - ->andReturnFalse(); - $columnUpdatedAt->shouldReceive('getDefault') - ->andReturnNull(); - - $this->assertTrue($datetimeField->isUseTimestamps([$columnUpdatedAt, $columnCreatedAt])); - } - - public function testIsUseTimestampsWhenDefaultIsNotNull() - { - /** @var DatetimeField $datetimeField */ - $datetimeField = resolve(DatetimeField::class); - - $columnCreatedAt = Mockery::mock(Column::class); - $columnCreatedAt->shouldReceive('getName') - ->andReturn(ColumnName::CREATED_AT); - $columnCreatedAt->shouldReceive('getNotnull') - ->andReturnFalse(); - $columnCreatedAt->shouldReceive('getDefault') - ->andReturn('Default'); - - $columnUpdatedAt = Mockery::mock(Column::class); - $columnUpdatedAt->shouldReceive('getName') - ->andReturn(ColumnName::UPDATED_AT); - $columnUpdatedAt->shouldReceive('getNotnull') - ->andReturnFalse(); - $columnUpdatedAt->shouldReceive('getDefault') - ->andReturnNull(); - - $this->assertFalse($datetimeField->isUseTimestamps([$columnUpdatedAt, $columnCreatedAt])); - } - - public function testIsUseTimestampsOnlyHasCreatedAt() - { - /** @var DatetimeField $datetimeField */ - $datetimeField = resolve(DatetimeField::class); - - $columnCreatedAt = Mockery::mock(Column::class); - $columnCreatedAt->shouldReceive('getName') - ->andReturn(ColumnName::CREATED_AT); - $columnCreatedAt->shouldReceive('getNotnull') - ->andReturnFalse(); - $columnCreatedAt->shouldReceive('getDefault') - ->andReturnNull(); - - $columnUpdatedAt = Mockery::mock(Column::class); - $columnUpdatedAt->shouldReceive('getName') - ->andReturn('other'); - $columnUpdatedAt->shouldReceive('getNotnull') - ->andReturnFalse(); - $columnUpdatedAt->shouldReceive('getDefault') - ->andReturnNull(); - - $this->assertFalse($datetimeField->isUseTimestamps([$columnUpdatedAt, $columnCreatedAt])); - } - - public function testIsUseTimestampsOnlyHasUpdatedAt() - { - /** @var DatetimeField $datetimeField */ - $datetimeField = resolve(DatetimeField::class); - - $columnCreatedAt = Mockery::mock(Column::class); - $columnCreatedAt->shouldReceive('getName') - ->andReturn('other'); - $columnCreatedAt->shouldReceive('getNotnull') - ->andReturnFalse(); - $columnCreatedAt->shouldReceive('getDefault') - ->andReturnNull(); - - $columnUpdatedAt = Mockery::mock(Column::class); - $columnUpdatedAt->shouldReceive('getName') - ->andReturn(ColumnName::UPDATED_AT); - $columnUpdatedAt->shouldReceive('getNotnull') - ->andReturnFalse(); - $columnUpdatedAt->shouldReceive('getDefault') - ->andReturnNull(); - - $this->assertFalse($datetimeField->isUseTimestamps([$columnUpdatedAt, $columnCreatedAt])); - } -} diff --git a/tests/KitLoong/MigrationsGenerator/Generators/IndexGeneratorTest.php b/tests/KitLoong/MigrationsGenerator/Generators/IndexGeneratorTest.php deleted file mode 100644 index b02c9b80..00000000 --- a/tests/KitLoong/MigrationsGenerator/Generators/IndexGeneratorTest.php +++ /dev/null @@ -1,204 +0,0 @@ -shouldReceive('getColumns') - ->andReturn(['col1']); - $index->shouldReceive('isPrimary') - ->andReturnTrue(); - $index->shouldReceive('getName') - ->andReturn('name'); - - $result = $indexGenerator->generate('table', [$index], false); - $this->assertSame([ - 'col1' => [ - 'field' => ['col1'], - 'type' => IndexType::PRIMARY, - 'args' => ["'name'"] - ] - ], $result['single']->toArray()); - $this->assertEmpty($result['multi']); - } - - public function testGenerateIsUnique() - { - /** @var IndexGenerator $indexGenerator */ - $indexGenerator = resolve(IndexGenerator::class); - - $index = Mockery::mock(Index::class); - $index->shouldReceive('getColumns') - ->andReturn(['col1']); - $index->shouldReceive('isPrimary') - ->andReturnFalse(); - $index->shouldReceive('isUnique') - ->andReturnTrue(); - $index->shouldReceive('getName') - ->andReturn('name'); - - $result = $indexGenerator->generate('table', [$index], false); - $this->assertSame([ - 'col1' => [ - 'field' => ['col1'], - 'type' => IndexType::UNIQUE, - 'args' => ["'name'"] - ] - ], $result['single']->toArray()); - } - - public function testGenerateIsSpatialIndex() - { - /** @var IndexGenerator $indexGenerator */ - $indexGenerator = resolve(IndexGenerator::class); - - $index = Mockery::mock(Index::class); - $index->shouldReceive('getColumns') - ->andReturn(['col1']); - $index->shouldReceive('isPrimary') - ->andReturnFalse(); - $index->shouldReceive('isUnique') - ->andReturnFalse(); - $index->shouldReceive('getFlags') - ->andReturn(['spatial']); - $index->shouldReceive('getName') - ->andReturn('name'); - - $result = $indexGenerator->generate('table', [$index], false); - $this->assertSame([ - 'col1' => [ - 'field' => ['col1'], - 'type' => IndexType::SPATIAL_INDEX, - 'args' => ["'name'"] - ] - ], $result['single']->toArray()); - } - - public function testGenerateIsIndex() - { - /** @var IndexGenerator $indexGenerator */ - $indexGenerator = resolve(IndexGenerator::class); - - $index = Mockery::mock(Index::class); - $index->shouldReceive('getColumns') - ->andReturn(['col1']); - $index->shouldReceive('isPrimary') - ->andReturnFalse(); - $index->shouldReceive('isUnique') - ->andReturnFalse(); - $index->shouldReceive('getFlags') - ->andReturn([]); - $index->shouldReceive('getName') - ->andReturn('name'); - - $result = $indexGenerator->generate('table', [$index], false); - $this->assertSame([ - 'col1' => [ - 'field' => ['col1'], - 'type' => IndexType::INDEX, - 'args' => ["'name'"] - ] - ], $result['single']->toArray()); - } - - public function testGenerateIsMultiColumn() - { - /** @var IndexGenerator $indexGenerator */ - $indexGenerator = resolve(IndexGenerator::class); - - $index = Mockery::mock(Index::class); - $index->shouldReceive('getColumns') - ->andReturn(['col1', 'col2']); - $index->shouldReceive('isPrimary') - ->andReturnFalse(); - $index->shouldReceive('isUnique') - ->andReturnFalse(); - $index->shouldReceive('getFlags') - ->andReturn([]); - $index->shouldReceive('getName') - ->andReturn('name'); - - $result = $indexGenerator->generate('table', [$index], false); - $this->assertSame([ - 0 => [ - 'field' => ['col1', 'col2'], - 'type' => IndexType::INDEX, - 'args' => ["'name'"] - ] - ], $result['multi']->toArray()); - $this->assertEmpty($result['single']); - } - - public function testGenerateUseLaravelDefaultName() - { - /** @var IndexGenerator $indexGenerator */ - $indexGenerator = resolve(IndexGenerator::class); - - $index = Mockery::mock(Index::class); - $index->shouldReceive('getColumns') - ->andReturn(['col1', 'col2']); - $index->shouldReceive('isPrimary') - ->andReturnFalse(); - $index->shouldReceive('isUnique') - ->andReturnFalse(); - $index->shouldReceive('getFlags') - ->andReturn([]); - $index->shouldReceive('getName') - ->andReturn('table_col1_col2_index'); - - $result = $indexGenerator->generate('table', [$index], false); - $this->assertSame([ - 0 => [ - 'field' => ['col1', 'col2'], - 'type' => IndexType::INDEX, - 'args' => [] - ] - ], $result['multi']->toArray()); - $this->assertEmpty($result['single']); - } - - public function testGenerateIgnoreIndexName() - { - /** @var IndexGenerator $indexGenerator */ - $indexGenerator = resolve(IndexGenerator::class); - - $index = Mockery::mock(Index::class); - $index->shouldReceive('getColumns') - ->andReturn(['col1', 'col2']); - $index->shouldReceive('isPrimary') - ->andReturnFalse(); - $index->shouldReceive('isUnique') - ->andReturnFalse(); - $index->shouldReceive('getFlags') - ->andReturn([]); - $index->shouldReceive('getName') - ->andReturn('name'); - - $result = $indexGenerator->generate('table', [$index], true); - $this->assertSame([ - 0 => [ - 'field' => ['col1', 'col2'], - 'type' => IndexType::INDEX, - 'args' => [] - ] - ], $result['multi']->toArray()); - $this->assertEmpty($result['single']); - } -} diff --git a/tests/KitLoong/MigrationsGenerator/Generators/StringFieldTest.php b/tests/KitLoong/MigrationsGenerator/Generators/StringFieldTest.php deleted file mode 100644 index 77bc3349..00000000 --- a/tests/KitLoong/MigrationsGenerator/Generators/StringFieldTest.php +++ /dev/null @@ -1,144 +0,0 @@ - 'field', - 'type' => 'string', - 'args' => [] - ]; - - $column = Mockery::mock(Column::class); - $column->shouldReceive('getFixed') - ->andReturnTrue(); - $column->shouldReceive('getLength') - ->andReturn(50); - - $this->mock(CollationModifier::class, function (MockInterface $mock) use ($column) { - $mock->shouldReceive('generate') - ->with('table', $column) - ->andReturn('collation') - ->once(); - }); - - /** @var StringField $stringField */ - $stringField = app(StringField::class); - - $output = $stringField->makeField('table', $field, $column); - - $this->assertSame([ - 'field' => 'field', - 'type' => ColumnType::CHAR, - 'args' => [50], - 'decorators' => ['collation'] - ], $output); - } - - public function testMakeFieldIsRememberToken() - { - $field = [ - 'field' => 'remember_token', - 'type' => 'string', - 'args' => [] - ]; - - $column = Mockery::mock(Column::class); - $column->shouldReceive('getFixed') - ->andReturnFalse(); - $column->shouldReceive('getLength') - ->andReturn(100); - - $this->mock(CollationModifier::class, function (MockInterface $mock) use ($column) { - $mock->shouldReceive('generate') - ->with('table', $column) - ->andReturn('collation') - ->once(); - }); - - /** @var StringField $stringField */ - $stringField = app(StringField::class); - - $field = $stringField->makeField('table', $field, $column); - $this->assertSame(ColumnType::REMEMBER_TOKEN, $field['type']); - $this->assertNull($field['field']); - $this->assertEmpty($field['args']); - $this->assertSame(['collation'], $field['decorators']); - } - - public function testMakeFieldWith255Length() - { - $field = [ - 'field' => 'field', - 'type' => 'string', - 'args' => [] - ]; - - $column = Mockery::mock(Column::class); - $column->shouldReceive('getFixed') - ->andReturnFalse(); - $column->shouldReceive('getLength') - ->andReturn(255); - - $this->mock(CollationModifier::class, function (MockInterface $mock) use ($column) { - $mock->shouldReceive('generate') - ->with('table', $column) - ->andReturn('collation') - ->once(); - }); - - /** @var StringField $stringField */ - $stringField = app(StringField::class); - - $field = $stringField->makeField('table', $field, $column); - $this->assertSame('string', $field['type']); - $this->assertSame('field', $field['field']); - $this->assertEmpty($field['args']); - } - - public function testMakeFieldWith100Length() - { - $field = [ - 'field' => 'field', - 'type' => 'string', - 'args' => [] - ]; - - $column = Mockery::mock(Column::class); - $column->shouldReceive('getFixed') - ->andReturnFalse(); - $column->shouldReceive('getLength') - ->andReturn(100); - - $this->mock(CollationModifier::class, function (MockInterface $mock) use ($column) { - $mock->shouldReceive('generate') - ->with('table', $column) - ->andReturn('collation') - ->once(); - }); - - /** @var StringField $stringField */ - $stringField = app(StringField::class); - - $field = $stringField->makeField('table', $field, $column); - $this->assertSame('string', $field['type']); - $this->assertSame('field', $field['field']); - $this->assertSame([100], $field['args']); - } -} diff --git a/tests/KitLoong/MigrationsGenerator/Repositories/PgSQLRepositoryTest.php b/tests/KitLoong/MigrationsGenerator/Repositories/PgSQLRepositoryTest.php deleted file mode 100644 index 446bf3a8..00000000 --- a/tests/KitLoong/MigrationsGenerator/Repositories/PgSQLRepositoryTest.php +++ /dev/null @@ -1,69 +0,0 @@ -mock(MigrationsGeneratorSetting::class, function (MockInterface $mock) { - $mock->shouldReceive('getConnection->select') - ->with($this->getTypeSql('table', 'column')) - ->andReturn([ - (object) ['datatype' => "type"] - ]) - ->once(); - }); - - /** @var PgSQLRepository $repository */ - $repository = app(PgSQLRepository::class); - $type = $repository->getTypeByColumnName('table', 'column'); - - $this->assertSame('type', $type); - } - - public function testGetTypeByColumnNameReturnNull() - { - $this->mock(MigrationsGeneratorSetting::class, function (MockInterface $mock) { - $mock->shouldReceive('getConnection->select') - ->with($this->getTypeSql('table', 'column')) - ->andReturn([]) - ->once(); - }); - - /** @var PgSQLRepository $repository */ - $repository = app(PgSQLRepository::class); - $type = $repository->getTypeByColumnName('table', 'column'); - - $this->assertNull($type); - } - - private function getTypeSql(string $table, string $column): string - { - return "SELECT - pg_catalog.format_type(a.atttypid, a.atttypmod) as \"datatype\" -FROM - pg_catalog.pg_attribute a -WHERE - a.attnum > 0 - AND NOT a.attisdropped - AND a.attrelid = ( - SELECT c.oid - FROM pg_catalog.pg_class c - LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace - WHERE c.relname ~ '^(${table})$' - AND pg_catalog.pg_table_is_visible(c.oid) - ) - AND a.attname='${column}'"; - } -} From ec142300bd5e5be80f51594368230e733cdc8c66 Mon Sep 17 00:00:00 2001 From: kitloong Date: Wed, 23 Jun 2021 23:55:21 +0800 Subject: [PATCH 39/65] Added new option `followCollation` to use DB charset and collation --- .../Generators/EnumField.php | 17 +++- .../Generators/Modifier/CharsetModifier.php | 33 ++++++++ .../Generators/Modifier/CollationModifier.php | 16 ++-- .../Generators/OtherField.php | 10 ++- .../Generators/SetField.php | 17 +++- .../Generators/StringField.php | 16 +++- .../MigrateGenerateCommand.php | 2 + .../MigrationMethod/ColumnModifier.php | 1 + .../MigrationsGeneratorSetting.php | 21 +++++ .../MigrationsGenerator/Syntax/Table.php | 21 +++-- tests/KitLoong/Feature/FeatureTestCase.php | 49 +++++++---- .../KitLoong/Feature/MySQL57/CommandTest.php | 40 +++++++-- tests/KitLoong/Feature/MySQL8/CommandTest.php | 41 ++++++++-- tests/KitLoong/Feature/PgSQL/CommandTest.php | 72 +++++++++++++++++ .../KitLoong/Feature/PgSQL/PgSQLTestCase.php | 65 +++++++++++++++ .../Generators/EnumFieldTest.php | 80 ------------------ .../Generators/OtherFieldTest.php | 45 ----------- .../Generators/SetFieldTest.php | 81 ------------------- ...00_expected_create_collations_db_table.php | 72 +++++++++++++++++ ..._000000_expected_create_users_db_table.php | 0 ...0_expected_create_all_columns_db_table.php | 39 +++------ ...0_expected_create_failed_jobs_db_table.php | 0 ...00_expected_create_test_index_db_table.php | 3 +- ..._expected_create_user_profile_db_table.php | 2 +- ...cted_create_composite_primary_db_table.php | 0 25 files changed, 447 insertions(+), 296 deletions(-) create mode 100644 src/KitLoong/MigrationsGenerator/Generators/Modifier/CharsetModifier.php create mode 100644 tests/KitLoong/Feature/PgSQL/CommandTest.php create mode 100644 tests/KitLoong/Feature/PgSQL/PgSQLTestCase.php delete mode 100644 tests/KitLoong/MigrationsGenerator/Generators/EnumFieldTest.php delete mode 100644 tests/KitLoong/MigrationsGenerator/Generators/OtherFieldTest.php delete mode 100644 tests/KitLoong/MigrationsGenerator/Generators/SetFieldTest.php create mode 100644 tests/KitLoong/resources/database/migrations/collation/2020_03_21_000000_expected_create_collations_db_table.php rename tests/KitLoong/resources/database/migrations/{ => general}/2014_10_12_000000_expected_create_users_db_table.php (100%) rename tests/KitLoong/resources/database/migrations/{ => general}/2020_03_21_000000_expected_create_all_columns_db_table.php (76%) rename tests/KitLoong/resources/database/migrations/{ => general}/2020_03_21_000000_expected_create_failed_jobs_db_table.php (100%) rename tests/KitLoong/resources/database/migrations/{ => general}/2020_03_21_000000_expected_create_test_index_db_table.php (90%) rename tests/KitLoong/resources/database/migrations/{ => general}/2020_03_21_000000_expected_create_user_profile_db_table.php (91%) rename tests/KitLoong/resources/database/migrations/{ => general}/2020_03_21_000001_expected_create_composite_primary_db_table.php (100%) diff --git a/src/KitLoong/MigrationsGenerator/Generators/EnumField.php b/src/KitLoong/MigrationsGenerator/Generators/EnumField.php index 32c8dcf3..4e09364f 100644 --- a/src/KitLoong/MigrationsGenerator/Generators/EnumField.php +++ b/src/KitLoong/MigrationsGenerator/Generators/EnumField.php @@ -8,6 +8,7 @@ namespace KitLoong\MigrationsGenerator\Generators; use Doctrine\DBAL\Schema\Column; +use KitLoong\MigrationsGenerator\Generators\Modifier\CharsetModifier; use KitLoong\MigrationsGenerator\Generators\Modifier\CollationModifier; use KitLoong\MigrationsGenerator\Repositories\MySQLRepository; @@ -15,14 +16,17 @@ class EnumField { private $collationModifier; - private $decorator; + private $charsetModifier; private $mysqlRepository; - public function __construct(CollationModifier $collationModifier, Decorator $decorator, MySQLRepository $mySQLRepository) - { + public function __construct( + CollationModifier $collationModifier, + CharsetModifier $charsetModifier, + MySQLRepository $mySQLRepository + ) { $this->collationModifier = $collationModifier; - $this->decorator = $decorator; + $this->charsetModifier = $charsetModifier; $this->mysqlRepository = $mySQLRepository; } @@ -33,6 +37,11 @@ public function makeField(string $tableName, array $field, Column $column): arra $field['args'][] = $value; } + $charset = $this->charsetModifier->generate($tableName, $column); + if ($charset !== '') { + $field['decorators'][] = $charset; + } + $collation = $this->collationModifier->generate($tableName, $column); if ($collation !== '') { $field['decorators'][] = $collation; diff --git a/src/KitLoong/MigrationsGenerator/Generators/Modifier/CharsetModifier.php b/src/KitLoong/MigrationsGenerator/Generators/Modifier/CharsetModifier.php new file mode 100644 index 00000000..e7a742bf --- /dev/null +++ b/src/KitLoong/MigrationsGenerator/Generators/Modifier/CharsetModifier.php @@ -0,0 +1,33 @@ +decorator = $decorator; + } + + public function generate(string $tableName, Column $column): string + { + if (app(MigrationsGeneratorSetting::class)->isFollowCollation()) { + $charset = $column->getPlatformOptions()['charset'] ?? null; + if ($charset != null) { + return $this->decorator->decorate( + ColumnModifier::CHARSET, + [$this->decorator->columnDefaultToString($charset)] + ); + } + } + + return ''; + } +} diff --git a/src/KitLoong/MigrationsGenerator/Generators/Modifier/CollationModifier.php b/src/KitLoong/MigrationsGenerator/Generators/Modifier/CollationModifier.php index 6d84c366..f0657527 100644 --- a/src/KitLoong/MigrationsGenerator/Generators/Modifier/CollationModifier.php +++ b/src/KitLoong/MigrationsGenerator/Generators/Modifier/CollationModifier.php @@ -23,18 +23,22 @@ public function __construct(Decorator $decorator) public function generate(string $tableName, Column $column): string { - $setting = app(MigrationsGeneratorSetting::class); - $tableCollation = $setting->getSchema()->listTableDetails($tableName)->getOptions()['collation'] ?? null; +// $setting = app(MigrationsGeneratorSetting::class); +// $tableCollation = $setting->getSchema()->listTableDetails($tableName)->getOptions()['collation'] ?? null; - $columnCollation = $column->getPlatformOptions()['collation'] ?? null; - if (!empty($column->getPlatformOptions()['collation'])) { - if ($columnCollation !== $tableCollation) { + if (app(MigrationsGeneratorSetting::class)->isFollowCollation()) { + $collation = $column->getPlatformOptions()['collation'] ?? null; + // if (!empty($column->getPlatformOptions()['collation'])) { + // if ($columnCollation !== $tableCollation) { + if ($collation != null) { return $this->decorator->decorate( ColumnModifier::COLLATION, - [$this->decorator->columnDefaultToString($columnCollation)] + [$this->decorator->columnDefaultToString($collation)] ); } } +// } +// } return ''; } diff --git a/src/KitLoong/MigrationsGenerator/Generators/OtherField.php b/src/KitLoong/MigrationsGenerator/Generators/OtherField.php index afbee258..e259454f 100644 --- a/src/KitLoong/MigrationsGenerator/Generators/OtherField.php +++ b/src/KitLoong/MigrationsGenerator/Generators/OtherField.php @@ -8,15 +8,18 @@ namespace KitLoong\MigrationsGenerator\Generators; use Doctrine\DBAL\Schema\Column; +use KitLoong\MigrationsGenerator\Generators\Modifier\CharsetModifier; use KitLoong\MigrationsGenerator\Generators\Modifier\CollationModifier; class OtherField { private $collationModifier; + private $charsetModifier; - public function __construct(CollationModifier $collationModifier) + public function __construct(CollationModifier $collationModifier, CharsetModifier $charsetModifier) { $this->collationModifier = $collationModifier; + $this->charsetModifier = $charsetModifier; } public function makeField(string $tableName, array $field, Column $column): array @@ -25,6 +28,11 @@ public function makeField(string $tableName, array $field, Column $column): arra $field['type'] = FieldGenerator::$fieldTypeMap[$field['type']]; } + $charset = $this->charsetModifier->generate($tableName, $column); + if ($charset !== '') { + $field['decorators'][] = $charset; + } + $collation = $this->collationModifier->generate($tableName, $column); if ($collation !== '') { $field['decorators'][] = $collation; diff --git a/src/KitLoong/MigrationsGenerator/Generators/SetField.php b/src/KitLoong/MigrationsGenerator/Generators/SetField.php index 8322b06f..07dab1b8 100644 --- a/src/KitLoong/MigrationsGenerator/Generators/SetField.php +++ b/src/KitLoong/MigrationsGenerator/Generators/SetField.php @@ -8,6 +8,7 @@ namespace KitLoong\MigrationsGenerator\Generators; use Doctrine\DBAL\Schema\Column; +use KitLoong\MigrationsGenerator\Generators\Modifier\CharsetModifier; use KitLoong\MigrationsGenerator\Generators\Modifier\CollationModifier; use KitLoong\MigrationsGenerator\Repositories\MySQLRepository; @@ -15,14 +16,17 @@ class SetField { private $collationModifier; - private $decorator; + private $charsetModifier; private $mysqlRepository; - public function __construct(CollationModifier $collationModifier, Decorator $decorator, MySQLRepository $mySQLRepository) - { + public function __construct( + CollationModifier $collationModifier, + CharsetModifier $charsetModifier, + MySQLRepository $mySQLRepository + ) { $this->collationModifier = $collationModifier; - $this->decorator = $decorator; + $this->charsetModifier = $charsetModifier; $this->mysqlRepository = $mySQLRepository; } @@ -33,6 +37,11 @@ public function makeField(string $tableName, array $field, Column $column): arra $field['args'][] = $value; } + $charset = $this->charsetModifier->generate($tableName, $column); + if ($charset !== '') { + $field['decorators'][] = $charset; + } + $collation = $this->collationModifier->generate($tableName, $column); if ($collation !== '') { $field['decorators'][] = $collation; diff --git a/src/KitLoong/MigrationsGenerator/Generators/StringField.php b/src/KitLoong/MigrationsGenerator/Generators/StringField.php index f2b7fccd..420d3db5 100644 --- a/src/KitLoong/MigrationsGenerator/Generators/StringField.php +++ b/src/KitLoong/MigrationsGenerator/Generators/StringField.php @@ -9,6 +9,7 @@ use Doctrine\DBAL\Schema\Column; use Illuminate\Database\Schema\Builder; +use KitLoong\MigrationsGenerator\Generators\Modifier\CharsetModifier; use KitLoong\MigrationsGenerator\Generators\Modifier\CollationModifier; use KitLoong\MigrationsGenerator\MigrationMethod\ColumnName; use KitLoong\MigrationsGenerator\MigrationMethod\ColumnType; @@ -19,12 +20,18 @@ class StringField { private $collationModifier; + private $charsetModifier; private $pgSQLRepository; private $regex; - public function __construct(CollationModifier $collationModifier, PgSQLRepository $pgSQLRepository, Regex $regex) - { + public function __construct( + CollationModifier $collationModifier, + CharsetModifier $charsetModifier, + PgSQLRepository $pgSQLRepository, + Regex $regex + ) { $this->collationModifier = $collationModifier; + $this->charsetModifier = $charsetModifier; $this->pgSQLRepository = $pgSQLRepository; $this->regex = $regex; } @@ -50,6 +57,11 @@ public function makeField(string $tableName, array $field, Column $column): arra } } + $charset = $this->charsetModifier->generate($tableName, $column); + if ($charset !== '') { + $field['decorators'][] = $charset; + } + $collation = $this->collationModifier->generate($tableName, $column); if ($collation !== '') { $field['decorators'][] = $collation; diff --git a/src/KitLoong/MigrationsGenerator/MigrateGenerateCommand.php b/src/KitLoong/MigrationsGenerator/MigrateGenerateCommand.php index a1eb8610..f0e82983 100644 --- a/src/KitLoong/MigrationsGenerator/MigrateGenerateCommand.php +++ b/src/KitLoong/MigrationsGenerator/MigrateGenerateCommand.php @@ -26,6 +26,7 @@ class MigrateGenerateCommand extends GeneratorCommand {--i|ignore= : A list of Tables you wish to ignore, separated by a comma: users,posts,comments} {--p|path= : Where should the file be created?} {--tp|templatePath= : The location of the template for this generator} + {--followCollation : Follow db collations for migrations} {--defaultIndexNames : Don\'t use db index names for migrations} {--defaultFKNames : Don\'t use db foreign key names for migrations}'; @@ -133,6 +134,7 @@ protected function setup(string $connection): void /** @var MigrationsGeneratorSetting $setting */ $setting = app(MigrationsGeneratorSetting::class); $setting->setConnection($connection); + $setting->setFollowCollation($this->option('followCollation')); $setting->setIgnoreIndexNames($this->option('defaultIndexNames')); $setting->setIgnoreForeignKeyNames($this->option('defaultFKNames')); } diff --git a/src/KitLoong/MigrationsGenerator/MigrationMethod/ColumnModifier.php b/src/KitLoong/MigrationsGenerator/MigrationMethod/ColumnModifier.php index f28febbe..6478580e 100644 --- a/src/KitLoong/MigrationsGenerator/MigrationMethod/ColumnModifier.php +++ b/src/KitLoong/MigrationsGenerator/MigrationMethod/ColumnModifier.php @@ -9,6 +9,7 @@ final class ColumnModifier { + const CHARSET = 'charset'; const COLLATION = 'collation'; const COMMENT = 'comment'; const DEFAULT = 'default'; diff --git a/src/KitLoong/MigrationsGenerator/MigrationsGeneratorSetting.php b/src/KitLoong/MigrationsGenerator/MigrationsGeneratorSetting.php index 3616c845..062f024a 100644 --- a/src/KitLoong/MigrationsGenerator/MigrationsGeneratorSetting.php +++ b/src/KitLoong/MigrationsGenerator/MigrationsGeneratorSetting.php @@ -29,6 +29,11 @@ class MigrationsGeneratorSetting */ private $schema; + /** + * @var boolean + */ + private $followCollation; + /** * @var boolean */ @@ -87,6 +92,22 @@ public function getPlatform(): string return $this->platform; } + /** + * @return bool + */ + public function isFollowCollation(): bool + { + return $this->followCollation; + } + + /** + * @param bool $followCollation + */ + public function setFollowCollation(bool $followCollation): void + { + $this->followCollation = $followCollation; + } + /** * @return bool */ diff --git a/src/Xethron/MigrationsGenerator/Syntax/Table.php b/src/Xethron/MigrationsGenerator/Syntax/Table.php index 9e70fedd..077fb17e 100644 --- a/src/Xethron/MigrationsGenerator/Syntax/Table.php +++ b/src/Xethron/MigrationsGenerator/Syntax/Table.php @@ -4,7 +4,6 @@ use KitLoong\MigrationsGenerator\Generators\Decorator; use KitLoong\MigrationsGenerator\Generators\Platform; use KitLoong\MigrationsGenerator\MigrationsGeneratorSetting; -use KitLoong\MigrationsGenerator\Repositories\MySQLRepository; use Way\Generators\Compilers\TemplateCompiler; use Way\Generators\Syntax\Table as WayTable; @@ -19,13 +18,9 @@ abstract class Table extends WayTable */ protected $table; - private $mySQLRepository; - - public function __construct(MySQLRepository $mySQLRepository, TemplateCompiler $compiler, Decorator $decorator) + public function __construct(TemplateCompiler $compiler, Decorator $decorator) { parent::__construct($compiler, $decorator); - - $this->mySQLRepository = $mySQLRepository; } public function run(array $fields, string $table, string $connection, $method = 'table'): string @@ -43,7 +38,11 @@ public function run(array $fields, string $table, string $connection, $method = if ($method === 'create') { $tableCollation = $this->getTableCollation($table); if (!empty($tableCollation)) { - $content = array_merge($tableCollation, $content); + $content = array_merge( + $tableCollation, + [''], // New line + $content + ); } } @@ -75,14 +74,12 @@ protected function getTableCollation(string $tableName): array { $setting = app(MigrationsGeneratorSetting::class); if ($setting->getPlatform() === Platform::MYSQL) { - $dbCollation = $this->mySQLRepository->getDatabaseCollation(); - $tableCollation = $setting->getSchema()->listTableDetails($tableName)->getOptions()['collation']; - - if ($tableCollation !== $dbCollation['collation']) { + if ($setting->isFollowCollation()) { + $tableCollation = $setting->getSchema()->listTableDetails($tableName)->getOptions()['collation']; $tableCharset = explode('_', $tableCollation)[0]; return [ '$table->charset = \''.$tableCharset.'\';', - '$table->collation = \''.$tableCollation.'\';' + '$table->collation = \''.$tableCollation.'\';', ]; } } diff --git a/tests/KitLoong/Feature/FeatureTestCase.php b/tests/KitLoong/Feature/FeatureTestCase.php index 7908acd2..a4a29720 100644 --- a/tests/KitLoong/Feature/FeatureTestCase.php +++ b/tests/KitLoong/Feature/FeatureTestCase.php @@ -36,7 +36,7 @@ protected function getEnvironmentSetUp($app) base_path('src/Way/Generators/templates/migration.txt') ); - $app['config']->set('generators.config.migration_target_path', $this->migrationOutputPath()); +// $app['config']->set('generators.config.migration_target_path', $this->storageMigrations()); } protected function setUp(): void @@ -64,51 +64,68 @@ protected function loadDotenv() protected function prepareStorage() { File::deleteDirectory(storage_path()); - File::makeDirectory(config('generators.config.migration_target_path'), 0775, true); - File::makeDirectory($this->migrateFromPath()); - File::makeDirectory($this->sqlOutputPath()); + File::makeDirectory($this->storageMigrations(), 0775, true); + File::makeDirectory($this->storageFrom()); + File::makeDirectory($this->storageSql()); } - protected function migrationOutputPath(string $path = ''): string + protected function storageMigrations(string $path = ''): string { return storage_path('migrations').($path ? DIRECTORY_SEPARATOR.$path : $path); } - protected function migrateFromPath(string $path = ''): string + protected function storageFrom(string $path = ''): string { return storage_path('from').($path ? DIRECTORY_SEPARATOR.$path : $path); } - protected function sqlOutputPath(string $path = ''): string + protected function storageSql(string $path = ''): string { return storage_path('sql').($path ? DIRECTORY_SEPARATOR.$path : $path); } - protected function migrateExpected(string $connection): void + protected function migrateGeneral(string $connection): void { - File::copyDirectory(base_path('tests/KitLoong/resources/database/migrations'), $this->migrateFromPath()); - foreach (File::files($this->migrateFromPath()) as $file) { + $this->migrateFromTemplate($connection, base_path('tests/KitLoong/resources/database/migrations/general')); + } + + protected function migrateCollation(string $connection): void + { + $this->migrateFromTemplate($connection, base_path('tests/KitLoong/resources/database/migrations/collation')); + } + + private function migrateFromTemplate(string $connection, string $templatePath): void + { + File::copyDirectory($templatePath, $this->storageFrom()); + foreach (File::files($this->storageFrom()) as $file) { $content = str_replace([ '[db]', '_DB_Table' ], [ $connection, ucfirst("${connection}Table") ], $file->getContents()); - file_put_contents($this->migrateFromPath($file->getBasename()), $content); + file_put_contents($this->storageFrom($file->getBasename()), $content); File::move( - $this->migrateFromPath($file->getBasename()), - $this->migrateFromPath(str_replace('_db_', "_${connection}_", $file->getBasename())) + $this->storageFrom($file->getBasename()), + $this->storageFrom(str_replace('_db_', "_${connection}_", $file->getBasename())) ); } - $this->loadMigrationsFrom($this->migrateFromPath()); + $this->loadMigrationsFrom($this->storageFrom()); } - protected function generateMigrations(): void + /** + * Generate migration files to $this->storageMigrations() + * @see \Tests\KitLoong\Feature\FeatureTestCase::getEnvironmentSetUp() + */ + protected function generateMigrations(array $options = []): void { $this->artisan( 'migrate:generate', - ['--no-interaction' => true] + array_merge($options, [ + '--path' => $this->storageMigrations(), + '--no-interaction' => true, + ]) ); } diff --git a/tests/KitLoong/Feature/MySQL57/CommandTest.php b/tests/KitLoong/Feature/MySQL57/CommandTest.php index b5da69c5..07627345 100644 --- a/tests/KitLoong/Feature/MySQL57/CommandTest.php +++ b/tests/KitLoong/Feature/MySQL57/CommandTest.php @@ -11,23 +11,49 @@ class CommandTest extends MySQL57TestCase { public function testRun() { - $this->migrateExpected('mysql57'); + $migrateTemplates = function () { + $this->migrateGeneral('mysql57'); + }; + + $generateMigrations = function () { + $this->generateMigrations(); + }; + + $this->verify($migrateTemplates, $generateMigrations); + } + + public function testCollation() + { + $migrateTemplates = function () { + $this->migrateCollation('mysql57'); + }; + + $generateMigrations = function () { + $this->generateMigrations(['--followCollation' => true]); + }; + + $this->verify($migrateTemplates, $generateMigrations); + } + + private function verify(callable $migrateTemplates, callable $generateMigrations) + { + $migrateTemplates(); $this->truncateMigration(); - $this->dumpSchemaAs($this->sqlOutputPath('expected.sql')); + $this->dumpSchemaAs($this->storageSql('expected.sql')); - $this->generateMigrations(); + $generateMigrations(); $this->dropAllTables(); - $this->loadMigrationsFrom($this->migrationOutputPath()); + $this->loadMigrationsFrom($this->storageMigrations()); $this->truncateMigration(); - $this->dumpSchemaAs($this->sqlOutputPath('actual.sql')); + $this->dumpSchemaAs($this->storageSql('actual.sql')); $this->assertFileEqualsIgnoringOrder( - $this->sqlOutputPath('expected.sql'), - $this->sqlOutputPath('actual.sql') + $this->storageSql('expected.sql'), + $this->storageSql('actual.sql') ); } } diff --git a/tests/KitLoong/Feature/MySQL8/CommandTest.php b/tests/KitLoong/Feature/MySQL8/CommandTest.php index 4d30cc0b..dc82411b 100644 --- a/tests/KitLoong/Feature/MySQL8/CommandTest.php +++ b/tests/KitLoong/Feature/MySQL8/CommandTest.php @@ -11,23 +11,50 @@ class CommandTest extends MySQL8TestCase { public function testRun() { - $this->migrateExpected('mysql8'); + $migrateTemplates = function () { + $this->migrateGeneral('mysql8'); + }; + + $generateMigrations = function () { + $this->generateMigrations(); + }; + + $this->verify($migrateTemplates, $generateMigrations); + } + + public function testCollation() + { + $this->markTestSkipped(); + $migrateTemplates = function () { + $this->migrateCollation('mysql8'); + }; + + $generateMigrations = function () { + $this->generateMigrations(['--followCollation' => true]); + }; + + $this->verify($migrateTemplates, $generateMigrations); + } + + private function verify(callable $migrateTemplates, callable $generateMigrations) + { + $migrateTemplates(); $this->truncateMigration(); - $this->dumpSchemaAs($this->sqlOutputPath('expected.sql')); + $this->dumpSchemaAs($this->storageSql('expected.sql')); - $this->generateMigrations(); + $generateMigrations(); $this->dropAllTables(); - $this->loadMigrationsFrom($this->migrationOutputPath()); + $this->loadMigrationsFrom($this->storageMigrations()); $this->truncateMigration(); - $this->dumpSchemaAs($this->sqlOutputPath('actual.sql')); + $this->dumpSchemaAs($this->storageSql('actual.sql')); $this->assertFileEqualsIgnoringOrder( - $this->sqlOutputPath('expected.sql'), - $this->sqlOutputPath('actual.sql') + $this->storageSql('expected.sql'), + $this->storageSql('actual.sql') ); } } diff --git a/tests/KitLoong/Feature/PgSQL/CommandTest.php b/tests/KitLoong/Feature/PgSQL/CommandTest.php new file mode 100644 index 00000000..c9c37aad --- /dev/null +++ b/tests/KitLoong/Feature/PgSQL/CommandTest.php @@ -0,0 +1,72 @@ +migrateGeneral('pgsql'); + }; + + $generateMigrations = function () { + $this->generateMigrations(); + }; + + $this->verify($migrateTemplates, $generateMigrations); + } + + public function testCollation() + { + $migrateTemplates = function () { + $this->migrateCollation('pgsql'); + }; + + $generateMigrations = function () { + $this->generateMigrations(['--followCollation' => true]); + }; + + $this->verify($migrateTemplates, $generateMigrations); + } + + public function verify(callable $migrateTemplates, callable $generateMigrations) + { + $migrateTemplates(); + + $this->truncateMigration(); + $this->dumpSchemaAs($this->storageSql('expected.sql')); + + $generateMigrations(); + + foreach (File::files($this->storageMigrations()) as $file) { + if (Str::contains($file->getBasename(), 'tiger')) { + File::delete($file); + } + + if (Str::contains($file->getBasename(), 'topology')) { + File::delete($file); + } + } + + $this->dropAllTables(); + + $this->loadMigrationsFrom($this->storageMigrations()); + + $this->truncateMigration(); + $this->dumpSchemaAs($this->storageSql('actual.sql')); + + $this->assertFileEqualsIgnoringOrder( + $this->storageSql('expected.sql'), + $this->storageSql('actual.sql') + ); + } +} diff --git a/tests/KitLoong/Feature/PgSQL/PgSQLTestCase.php b/tests/KitLoong/Feature/PgSQL/PgSQLTestCase.php new file mode 100644 index 00000000..0a1dc90e --- /dev/null +++ b/tests/KitLoong/Feature/PgSQL/PgSQLTestCase.php @@ -0,0 +1,65 @@ +set('database.default', 'pgsql'); + $app['config']->set('database.connections.pgsql', [ + 'driver' => 'pgsql', + 'url' => env('DATABASE_URL'), + 'host' => env('POSTGRES_HOST'), + 'port' => env('POSTGRES_PORT'), + 'database' => env('POSTGRES_DATABASE'), + 'username' => env('POSTGRES_USERNAME'), + 'password' => env('POSTGRES_PASSWORD'), + 'charset' => 'utf8', + 'prefix' => '', + 'prefix_indexes' => true, + 'schema' => 'public', + 'sslmode' => 'prefer', + ]); + } + + protected function dumpSchemaAs(string $destination): void + { + $command = sprintf( + 'PGPASSWORD="%s" pg_dump -h %s -U %s %s -f %s --schema-only', + config('database.connections.pgsql.password'), + config('database.connections.pgsql.host'), + config('database.connections.pgsql.username'), + config('database.connections.pgsql.database'), + $destination + ); + exec($command); + } + + protected function dropAllTables(): void + { + $tables = DB::connection()->getDoctrineSchemaManager()->listTableNames(); + foreach ($tables as $table) { + if (Str::startsWith($table, 'tiger.')) { + continue; + } + + if (Str::startsWith($table, 'topology.')) { + continue; + } + + DB::statement("DROP TABLE if exists $table cascade"); + } + } +} diff --git a/tests/KitLoong/MigrationsGenerator/Generators/EnumFieldTest.php b/tests/KitLoong/MigrationsGenerator/Generators/EnumFieldTest.php deleted file mode 100644 index 2d492baa..00000000 --- a/tests/KitLoong/MigrationsGenerator/Generators/EnumFieldTest.php +++ /dev/null @@ -1,80 +0,0 @@ -mock(MySQLRepository::class, function (MockInterface $mock) { - $mock->shouldReceive('getEnumPresetValues') - ->with('table', 'enum_field') - ->andReturn("['value1', 'value2' , 'value3']") - ->once(); - }); - - $field = [ - 'field' => 'enum_field', - 'args' => [] - ]; - - $column = Mockery::mock(Column::class); - $this->mock(CollationModifier::class, function (MockInterface $mock) use ($column) { - $mock->shouldReceive('generate') - ->with('table', $column) - ->andReturn('collation') - ->once(); - }); - - /** @var EnumField $enumField */ - $enumField = resolve(EnumField::class); - $output = $enumField->makeField('table', $field, $column); - $this->assertSame([ - 'field' => 'enum_field', - 'args' => ["['value1', 'value2' , 'value3']"], - 'decorators' => ['collation'] - ], $output); - } - - public function testMakeFieldValueIsEmpty() - { - $this->mock(MySQLRepository::class, function (MockInterface $mock) { - $mock->shouldReceive('getEnumPresetValues') - ->with('table', 'enum_field') - ->andReturnNull() - ->once(); - }); - - $field = [ - 'field' => 'enum_field', - 'args' => [] - ]; - - $column = Mockery::mock(Column::class); - $this->mock(CollationModifier::class, function (MockInterface $mock) use ($column) { - $mock->shouldReceive('generate') - ->with('table', $column) - ->andReturn('collation') - ->once(); - }); - - /** @var EnumField $enumField */ - $enumField = resolve(EnumField::class); - - $field = $enumField->makeField('table', $field, $column); - $this->assertEmpty($field['args']); - } -} diff --git a/tests/KitLoong/MigrationsGenerator/Generators/OtherFieldTest.php b/tests/KitLoong/MigrationsGenerator/Generators/OtherFieldTest.php deleted file mode 100644 index adc48c35..00000000 --- a/tests/KitLoong/MigrationsGenerator/Generators/OtherFieldTest.php +++ /dev/null @@ -1,45 +0,0 @@ -mock(CollationModifier::class, function (MockInterface $mock) use ($column) { - $mock->shouldReceive('generate') - ->with('table', $column) - ->andReturn('collation') - ->once(); - }); - - /** @var OtherField $otherField */ - $otherField = app(OtherField::class); - - $field = [ - 'field' => 'field', - 'type' => 'blob' - ]; - - $output = $otherField->makeField('table', $field, $column); - $this->assertSame([ - 'field' => 'field', - 'type' => ColumnType::BINARY, - 'decorators' => ['collation'] - ], $output); - } -} diff --git a/tests/KitLoong/MigrationsGenerator/Generators/SetFieldTest.php b/tests/KitLoong/MigrationsGenerator/Generators/SetFieldTest.php deleted file mode 100644 index 8963221d..00000000 --- a/tests/KitLoong/MigrationsGenerator/Generators/SetFieldTest.php +++ /dev/null @@ -1,81 +0,0 @@ -mock(MySQLRepository::class, function (MockInterface $mock) { - $mock->shouldReceive('getSetPresetValues') - ->with('table', 'set_field') - ->andReturn("['value1', 'value2' , 'value3']") - ->once(); - }); - - $column = Mockery::mock(Column::class); - $this->mock(CollationModifier::class, function (MockInterface $mock) use ($column) { - $mock->shouldReceive('generate') - ->with('table', $column) - ->andReturn('collation') - ->once(); - }); - - /** @var SetField $setField */ - $setField = app(SetField::class); - - $field = [ - 'field' => 'set_field', - 'args' => [] - ]; - - $output = $setField->makeField('table', $field, $column); - $this->assertSame([ - 'field' => 'set_field', - 'args' => ["['value1', 'value2' , 'value3']"], - 'decorators' => ['collation'] - ], $output); - } - - public function testMakeFieldValueIsEmpty() - { - $this->mock(MySQLRepository::class, function (MockInterface $mock) { - $mock->shouldReceive('getSetPresetValues') - ->with('table', 'set_field') - ->andReturnNull() - ->once(); - }); - - $column = Mockery::mock(Column::class); - $this->mock(CollationModifier::class, function (MockInterface $mock) use ($column) { - $mock->shouldReceive('generate') - ->with('table', $column) - ->andReturn('collation') - ->once(); - }); - - /** @var SetField $setField */ - $setField = app(SetField::class); - - $field = [ - 'field' => 'set_field', - 'args' => [] - ]; - - $field = $setField->makeField('table', $field, $column); - $this->assertEmpty($field['args']); - } -} diff --git a/tests/KitLoong/resources/database/migrations/collation/2020_03_21_000000_expected_create_collations_db_table.php b/tests/KitLoong/resources/database/migrations/collation/2020_03_21_000000_expected_create_collations_db_table.php new file mode 100644 index 00000000..4f46e683 --- /dev/null +++ b/tests/KitLoong/resources/database/migrations/collation/2020_03_21_000000_expected_create_collations_db_table.php @@ -0,0 +1,72 @@ +charset = 'utf8mb4'; + $table->collation = 'utf8mb4_general_ci'; + + switch (config('database.default')) { + case 'pgsql': + $collation = 'en_US.utf8'; + break; + default: + $collation = 'utf8_unicode_ci'; + } + + $table->char('char'); + $table->char('char_charset')->charset('utf8'); + $table->enum('enum', ['easy', 'hard']); + $table->enum('enum_charset', ['easy', 'hard'])->charset('utf8'); + $table->enum('enum_collation', ['easy', 'hard'])->collation($collation); + $table->longText('longText'); + $table->longText('longText_charset')->charset('utf8'); + $table->longText('longText_collation')->collation($collation); + $table->mediumText('mediumText'); + $table->mediumText('mediumText_charset')->charset('utf8'); + $table->mediumText('mediumText_collation')->collation($collation); + $table->text('text'); + $table->text('text_charset')->charset('utf8'); + $table->text('text_collation')->collation($collation); + + if (config('database.default') === 'mysql') { + if ($this->atLeastLaravel5Dot8()) { + $table->set('set', ['strawberry', 'vanilla']); + $table->set('set_default', ['strawberry', 'vanilla'])->default('strawberry'); + $table->set('set_charset', ['strawberry', 'vanilla'])->charset('utf8'); + $table->set('set_collation', ['strawberry', 'vanilla'])->collation($collation); + } + } + + $table->string('string'); + $table->string('string_charset')->charset('utf8'); + $table->string('string_collation')->collation($collation); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('all_columns_[db]'); + } +} diff --git a/tests/KitLoong/resources/database/migrations/2014_10_12_000000_expected_create_users_db_table.php b/tests/KitLoong/resources/database/migrations/general/2014_10_12_000000_expected_create_users_db_table.php similarity index 100% rename from tests/KitLoong/resources/database/migrations/2014_10_12_000000_expected_create_users_db_table.php rename to tests/KitLoong/resources/database/migrations/general/2014_10_12_000000_expected_create_users_db_table.php diff --git a/tests/KitLoong/resources/database/migrations/2020_03_21_000000_expected_create_all_columns_db_table.php b/tests/KitLoong/resources/database/migrations/general/2020_03_21_000000_expected_create_all_columns_db_table.php similarity index 76% rename from tests/KitLoong/resources/database/migrations/2020_03_21_000000_expected_create_all_columns_db_table.php rename to tests/KitLoong/resources/database/migrations/general/2020_03_21_000000_expected_create_all_columns_db_table.php index 6f0b9473..fd7cae4a 100644 --- a/tests/KitLoong/resources/database/migrations/2020_03_21_000000_expected_create_all_columns_db_table.php +++ b/tests/KitLoong/resources/database/migrations/general/2020_03_21_000000_expected_create_all_columns_db_table.php @@ -77,8 +77,13 @@ public function up() $table->string('string'); $table->string('string_255', 255); $table->string('string_100', 100); - $table->string('default_single_quote')->default('string with \" !@#$%^^&*()_+ \\\' quotes'); - $table->string('comment_double_quote')->comment("string with \" ' quotes"); + if (config('database.default') === 'pgsql') { + $table->string('default_single_quote')->default('string with \" !@#$%^^&*()_+ quotes'); + $table->string('comment_double_quote')->comment("string with ' quotes"); + } else { + $table->string('default_single_quote')->default('string with \" !@#$%^^&*()_+ \\\' quotes'); + $table->string('comment_double_quote')->comment("string with \" ' quotes"); + } $table->text('text'); $table->time('time'); $table->time('time_0', 0); @@ -113,36 +118,14 @@ public function up() $table->year('year')->default(2020); if (config('database.default') === 'mysql') { - $table->char('char_charset')->charset('utf8'); - $table->char('char_collation')->collation('utf8_unicode_ci'); - $table->enum('enum_charset', ['easy', 'hard'])->charset('utf8'); - $table->enum('enum_collation', ['easy', 'hard'])->collation('utf8_unicode_ci'); - $table->longText('longText_charset')->charset('utf8'); - $table->longText('longText_collation')->collation('utf8_unicode_ci'); - $table->mediumText('mediumText_charset')->charset('utf8'); - $table->mediumText('mediumText_collation')->collation('utf8_unicode_ci'); - $table->text('text_charset')->charset('utf8'); - $table->text('text_collation')->collation('utf8_unicode_ci'); - if ($this->atLeastLaravel5Dot8()) { $table->set('set', ['strawberry', 'vanilla']); - $table->set('set_default', ['strawberry', 'vanilla'])->default('strawberry'); - $table->set('set_charset', ['strawberry', 'vanilla'])->charset('utf8'); - $table->set('set_collation', ['strawberry', 'vanilla'])->collation('utf8_unicode_ci'); } - - $table->string('string_charset')->charset('utf8'); - $table->string('string_collation')->collation('utf8_unicode_ci'); } - - if (config('database.default') !== 'pgsql') { - $table->macAddress('macAddress'); - $table->macAddress('macAddress_default')->default('10.0.0.8'); - $table->uuid('uuid')->default('uuid'); - } - - $table->charset = 'utf8mb4'; - $table->collation = 'utf8mb4_general_ci'; + $table->macAddress('macAddress'); + $table->macAddress('macAddress_default')->default('00:0a:95:9d:68:16'); + $table->uuid('uuid'); + $table->uuid('uuid_default')->default('f6a16ff7-4a31-11eb-be7b-8344edc8f36b'); }); } diff --git a/tests/KitLoong/resources/database/migrations/2020_03_21_000000_expected_create_failed_jobs_db_table.php b/tests/KitLoong/resources/database/migrations/general/2020_03_21_000000_expected_create_failed_jobs_db_table.php similarity index 100% rename from tests/KitLoong/resources/database/migrations/2020_03_21_000000_expected_create_failed_jobs_db_table.php rename to tests/KitLoong/resources/database/migrations/general/2020_03_21_000000_expected_create_failed_jobs_db_table.php diff --git a/tests/KitLoong/resources/database/migrations/2020_03_21_000000_expected_create_test_index_db_table.php b/tests/KitLoong/resources/database/migrations/general/2020_03_21_000000_expected_create_test_index_db_table.php similarity index 90% rename from tests/KitLoong/resources/database/migrations/2020_03_21_000000_expected_create_test_index_db_table.php rename to tests/KitLoong/resources/database/migrations/general/2020_03_21_000000_expected_create_test_index_db_table.php index 40b791a1..a6f8c85f 100644 --- a/tests/KitLoong/resources/database/migrations/2020_03_21_000000_expected_create_test_index_db_table.php +++ b/tests/KitLoong/resources/database/migrations/general/2020_03_21_000000_expected_create_test_index_db_table.php @@ -19,12 +19,11 @@ public function up() $table->string('email')->unique(); $table->enum('enum', ['PROGRESS', 'DONE']); $table->geometry('geometry'); - $table->string('stri"\'helo')->index(); $table->timestamps(2); $table->primary('id'); - $table->index('enum', 'user_pro file"\'d'); + $table->index('enum', 'user_pro file\'d'); $table->index('code'); $table->index(['code', 'enum']); $table->index(['enum', 'code']); diff --git a/tests/KitLoong/resources/database/migrations/2020_03_21_000000_expected_create_user_profile_db_table.php b/tests/KitLoong/resources/database/migrations/general/2020_03_21_000000_expected_create_user_profile_db_table.php similarity index 91% rename from tests/KitLoong/resources/database/migrations/2020_03_21_000000_expected_create_user_profile_db_table.php rename to tests/KitLoong/resources/database/migrations/general/2020_03_21_000000_expected_create_user_profile_db_table.php index 5e247a7d..b6ae772c 100644 --- a/tests/KitLoong/resources/database/migrations/2020_03_21_000000_expected_create_user_profile_db_table.php +++ b/tests/KitLoong/resources/database/migrations/general/2020_03_21_000000_expected_create_user_profile_db_table.php @@ -19,7 +19,7 @@ public function up() $table->unsignedInteger('sub_id'); $table->primary('id'); - $table->foreign('user_id')->references('id')->on('users_[db]'); +// $table->foreign('user_id')->references('id')->on('users_[db]'); $table->foreign(['user_id', 'sub_id'])->references(['id', 'sub_id'])->on('users_[db]'); }); } diff --git a/tests/KitLoong/resources/database/migrations/2020_03_21_000001_expected_create_composite_primary_db_table.php b/tests/KitLoong/resources/database/migrations/general/2020_03_21_000001_expected_create_composite_primary_db_table.php similarity index 100% rename from tests/KitLoong/resources/database/migrations/2020_03_21_000001_expected_create_composite_primary_db_table.php rename to tests/KitLoong/resources/database/migrations/general/2020_03_21_000001_expected_create_composite_primary_db_table.php From 650079f49c08a286b697be13ef73573f585ac98e Mon Sep 17 00:00:00 2001 From: kitloong Date: Wed, 23 Jun 2021 23:55:32 +0800 Subject: [PATCH 40/65] Fixed wrong name --- tests/KitLoong/Support/CheckLaravelVersionTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/KitLoong/Support/CheckLaravelVersionTest.php b/tests/KitLoong/Support/CheckLaravelVersionTest.php index 7790670f..a7102d8a 100644 --- a/tests/KitLoong/Support/CheckLaravelVersionTest.php +++ b/tests/KitLoong/Support/CheckLaravelVersionTest.php @@ -61,7 +61,7 @@ public function testAtLeastLaravel7() $this->assertTrue($this->stubInstance()->atLeastLaravel7()); } - public function testAtLeastLaravel9() + public function testAtLeastLaravel8() { App::shouldReceive('version')->andReturn('7.0.0')->once(); $this->assertFalse($this->stubInstance()->atLeastLaravel8()); From 660f66db7b785492580f01032ab0320d8c2d673a Mon Sep 17 00:00:00 2001 From: kitloong Date: Wed, 23 Jun 2021 23:56:07 +0800 Subject: [PATCH 41/65] Ignore .phpstorm.meta.php --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 183967d7..3e499ecc 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ build .phpunit.result.cache storage .env +.phpstorm.meta.php From f9cbd71af8e245854427cd02e0d4b1d2112fa5c4 Mon Sep 17 00:00:00 2001 From: kitloong Date: Wed, 23 Jun 2021 23:57:19 +0800 Subject: [PATCH 42/65] Updated env --- .env.action | 6 ++++++ .env.example | 12 ++++++++++++ 2 files changed, 18 insertions(+) diff --git a/.env.action b/.env.action index e8b86d94..470b2700 100644 --- a/.env.action +++ b/.env.action @@ -9,3 +9,9 @@ MYSQL8_PORT=33062 MYSQL8_DATABASE=migration MYSQL8_USERNAME=root MYSQL8_PASSWORD= + +POSTGRES_HOST=127.0.0.1 +POSTGRES_PORT=5432 +POSTGRES_DATABASE=migration +POSTGRES_USERNAME=root +POSTGRES_PASSWORD= diff --git a/.env.example b/.env.example index 7c994736..67b3954f 100644 --- a/.env.example +++ b/.env.example @@ -3,3 +3,15 @@ MYSQL57_PORT=3306 MYSQL57_DATABASE=migration MYSQL57_USERNAME= MYSQL57_PASSWORD= + +MYSQL8_HOST=mysql8 +MYSQL8_PORT=3306 +MYSQL8_DATABASE=migration +MYSQL8_USERNAME= +MYSQL8_PASSWORD= + +POSTGRES_HOST=pgsql +POSTGRES_PORT=5432 +POSTGRES_DATABASE=migration +POSTGRES_USERNAME= +POSTGRES_PASSWORD= From 8f337abed615441f2a3c356ced1fb8b1769ae5d2 Mon Sep 17 00:00:00 2001 From: kitloong Date: Thu, 24 Jun 2021 00:00:16 +0800 Subject: [PATCH 43/65] Exclude migration --- phpcs.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/phpcs.xml b/phpcs.xml index 9b258e38..863f972b 100644 --- a/phpcs.xml +++ b/phpcs.xml @@ -8,4 +8,5 @@ tests features/bootstrap/FeatureContext.php + tests/KitLoong/resources/database/migrations/*.php From 7d249ba869c4cbbce3cffa49e25a8fd236e76f72 Mon Sep 17 00:00:00 2001 From: kitloong Date: Thu, 24 Jun 2021 00:06:24 +0800 Subject: [PATCH 44/65] Add pgsql --- .github/workflows/tests.yml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 55fbfad1..602995c5 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -26,6 +26,20 @@ jobs: - 33062:3306 options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 + postgres: + image: mdillon/postgis:11 + env: + POSTGRES_DB: migration + POSTGRES_USER: root + POSTGRES_PASSWORD: "!QAZ2wsx" + ports: + - 5432:5432 + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + strategy: matrix: php: [ 7.3, 7.4 ] From b053aa0a90c1cc91b0e72f3ef1453e6468ff32dc Mon Sep 17 00:00:00 2001 From: kitloong Date: Thu, 24 Jun 2021 00:09:30 +0800 Subject: [PATCH 45/65] Update password --- .env.action | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.env.action b/.env.action index 470b2700..f8cf3845 100644 --- a/.env.action +++ b/.env.action @@ -14,4 +14,4 @@ POSTGRES_HOST=127.0.0.1 POSTGRES_PORT=5432 POSTGRES_DATABASE=migration POSTGRES_USERNAME=root -POSTGRES_PASSWORD= +POSTGRES_PASSWORD=!QAZ2wsx From 167dbf285b24ae73a981e561cb625e3ce5955d9f Mon Sep 17 00:00:00 2001 From: kitloong Date: Thu, 24 Jun 2021 00:25:56 +0800 Subject: [PATCH 46/65] Geometry not available for pgsql below Laravel 5.7 --- ..._21_000000_expected_create_all_columns_db_table.php | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/tests/KitLoong/resources/database/migrations/general/2020_03_21_000000_expected_create_all_columns_db_table.php b/tests/KitLoong/resources/database/migrations/general/2020_03_21_000000_expected_create_all_columns_db_table.php index fd7cae4a..fe02bae5 100644 --- a/tests/KitLoong/resources/database/migrations/general/2020_03_21_000000_expected_create_all_columns_db_table.php +++ b/tests/KitLoong/resources/database/migrations/general/2020_03_21_000000_expected_create_all_columns_db_table.php @@ -1,5 +1,6 @@ float('float_82', 8, 2); $table->float('float_53', 5, 3); $table->float('float_default')->default(10.8); - $table->geometry('geometry'); - $table->geometryCollection('geometryCollection'); + + if ((config('database.default') === 'pgsql' && $this->atLeastLaravel5Dot7()) + || config('database.default') !== 'pgsql') { + $table->geometry('geometry'); + $table->geometryCollection('geometryCollection'); + } + $table->integer('integer'); $table->integer('integer_default')->default(1080); $table->ipAddress('ipAddress'); From 3b40b8c76950c0ccd34e2e31a31b5b8efdda17b7 Mon Sep 17 00:00:00 2001 From: kitloong Date: Thu, 24 Jun 2021 00:31:59 +0800 Subject: [PATCH 47/65] Test spatialIndex with lineString --- ...2020_03_21_000000_expected_create_all_columns_db_table.php | 2 +- .../2020_03_21_000000_expected_create_test_index_db_table.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/KitLoong/resources/database/migrations/general/2020_03_21_000000_expected_create_all_columns_db_table.php b/tests/KitLoong/resources/database/migrations/general/2020_03_21_000000_expected_create_all_columns_db_table.php index fe02bae5..5feda621 100644 --- a/tests/KitLoong/resources/database/migrations/general/2020_03_21_000000_expected_create_all_columns_db_table.php +++ b/tests/KitLoong/resources/database/migrations/general/2020_03_21_000000_expected_create_all_columns_db_table.php @@ -61,7 +61,7 @@ public function up() $table->geometry('geometry'); $table->geometryCollection('geometryCollection'); } - + $table->integer('integer'); $table->integer('integer_default')->default(1080); $table->ipAddress('ipAddress'); diff --git a/tests/KitLoong/resources/database/migrations/general/2020_03_21_000000_expected_create_test_index_db_table.php b/tests/KitLoong/resources/database/migrations/general/2020_03_21_000000_expected_create_test_index_db_table.php index a6f8c85f..5c01fd44 100644 --- a/tests/KitLoong/resources/database/migrations/general/2020_03_21_000000_expected_create_test_index_db_table.php +++ b/tests/KitLoong/resources/database/migrations/general/2020_03_21_000000_expected_create_test_index_db_table.php @@ -18,7 +18,7 @@ public function up() $table->string('code', 50); $table->string('email')->unique(); $table->enum('enum', ['PROGRESS', 'DONE']); - $table->geometry('geometry'); + $table->lineString('lineString'); $table->timestamps(2); $table->primary('id'); @@ -27,7 +27,7 @@ public function up() $table->index('code'); $table->index(['code', 'enum']); $table->index(['enum', 'code']); - $table->spatialIndex('geometry'); + $table->spatialIndex('lineString'); }); } From e6dc2e437a1045733ed1cf05fcf459ec610b089d Mon Sep 17 00:00:00 2001 From: kitloong Date: Sun, 4 Jul 2021 10:42:36 +0800 Subject: [PATCH 48/65] Generate sqlsrv guid as uuid --- .../MigrationsGenerator/Generators/FieldGenerator.php | 1 + src/KitLoong/MigrationsGenerator/Types/DBALTypes.php | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/src/KitLoong/MigrationsGenerator/Generators/FieldGenerator.php b/src/KitLoong/MigrationsGenerator/Generators/FieldGenerator.php index 35aa9a99..d623f6ed 100644 --- a/src/KitLoong/MigrationsGenerator/Generators/FieldGenerator.php +++ b/src/KitLoong/MigrationsGenerator/Generators/FieldGenerator.php @@ -75,6 +75,7 @@ public function __construct( DBALTypes::BIGINT => ColumnType::BIG_INTEGER, DBALTypes::DATETIME_MUTABLE => ColumnType::DATETIME, DBALTypes::BLOB => ColumnType::BINARY, + DBALTypes::GUID => ColumnType::UUID, ]; /** diff --git a/src/KitLoong/MigrationsGenerator/Types/DBALTypes.php b/src/KitLoong/MigrationsGenerator/Types/DBALTypes.php index fdcfb262..0cfa7ad0 100644 --- a/src/KitLoong/MigrationsGenerator/Types/DBALTypes.php +++ b/src/KitLoong/MigrationsGenerator/Types/DBALTypes.php @@ -41,6 +41,11 @@ final class DBALTypes */ const FLOAT = 'float'; + /** + * @see \Doctrine\DBAL\Types\Types::GUID + */ + const GUID = 'guid'; + /** * @see \Doctrine\DBAL\Types\Types::INTEGER */ From 37ffe10db4936d62363a7f40de9e194cdde45b90 Mon Sep 17 00:00:00 2001 From: kitloong Date: Sun, 4 Jul 2021 10:45:27 +0800 Subject: [PATCH 49/65] Generate sqlsrv spatial index --- .../Generators/IndexGenerator.php | 30 +++++-- .../Repositories/SQLSrvRepository.php | 82 +++++++++++++++++++ 2 files changed, 106 insertions(+), 6 deletions(-) create mode 100644 src/KitLoong/MigrationsGenerator/Repositories/SQLSrvRepository.php diff --git a/src/KitLoong/MigrationsGenerator/Generators/IndexGenerator.php b/src/KitLoong/MigrationsGenerator/Generators/IndexGenerator.php index e71dc08c..3679dd74 100644 --- a/src/KitLoong/MigrationsGenerator/Generators/IndexGenerator.php +++ b/src/KitLoong/MigrationsGenerator/Generators/IndexGenerator.php @@ -12,16 +12,19 @@ use KitLoong\MigrationsGenerator\MigrationMethod\IndexType; use KitLoong\MigrationsGenerator\MigrationsGeneratorSetting; use KitLoong\MigrationsGenerator\Repositories\PgSQLRepository; +use KitLoong\MigrationsGenerator\Repositories\SQLSrvRepository; class IndexGenerator { private $decorator; private $pgSQLRepository; + private $sqlSrvRepository; - public function __construct(Decorator $decorator, PgSQLRepository $pgSQLRepository) + public function __construct(Decorator $decorator, PgSQLRepository $pgSQLRepository, SQLSrvRepository $sqlSrvRepository) { $this->decorator = $decorator; $this->pgSQLRepository = $pgSQLRepository; + $this->sqlSrvRepository = $sqlSrvRepository; } /** @@ -37,10 +40,7 @@ public function generate(string $table, $indexes, bool $ignoreIndexNames): array // Doctrine/Dbal doesn't return spatial information from PostgreSQL // Use raw SQL here to create $spatial index name list. - $spatials = collect([]); - if (app(MigrationsGeneratorSetting::class)->getPlatform() === Platform::POSTGRESQL) { - $spatials = $this->pgSQLRepository->getSpatialIndexNames($table); - } + $spatials = $this->getSpatialList($table); foreach ($indexes as $index) { $indexField = [ @@ -54,7 +54,7 @@ public function generate(string $table, $indexes, bool $ignoreIndexNames): array } elseif ($index->isUnique()) { $indexField['type'] = IndexType::UNIQUE; } elseif (( - count($index->getFlags()) > 0 && in_array('spatial', $index->getFlags()) + count($index->getFlags()) > 0 && $index->hasFlag('spatial') ) || $spatials->contains($index->getName())) { $indexField['type'] = IndexType::SPATIAL_INDEX; } @@ -94,4 +94,22 @@ private function decorateName(string $name): string { return "'".$this->decorator->addSlash($name)."'"; } + + /** + * Doctrine/Dbal doesn't return spatial information from PostgreSQL + * Use raw SQL here to create $spatial index name list. + * @param string $table + * @return \Illuminate\Support\Collection Spatial index name list + */ + private function getSpatialList(string $table): Collection + { + switch (app(MigrationsGeneratorSetting::class)->getPlatform()) { + case Platform::POSTGRESQL: + return $this->pgSQLRepository->getSpatialIndexNames($table); + case Platform::SQLSERVER: + return $this->sqlSrvRepository->getSpatialIndexNames($table); + default: + return collect([]); + } + } } diff --git a/src/KitLoong/MigrationsGenerator/Repositories/SQLSrvRepository.php b/src/KitLoong/MigrationsGenerator/Repositories/SQLSrvRepository.php new file mode 100644 index 00000000..a189a97a --- /dev/null +++ b/src/KitLoong/MigrationsGenerator/Repositories/SQLSrvRepository.php @@ -0,0 +1,82 @@ +getConnection() + ->select(" + SELECT idx.name AS indexname + FROM sys.tables AS tbl + JOIN sys.schemas AS scm ON tbl.schema_id = scm.schema_id + JOIN sys.indexes AS idx ON tbl.object_id = idx.object_id + JOIN sys.index_columns AS idxcol ON idx.object_id = idxcol.object_id AND idx.index_id = idxcol.index_id + JOIN sys.columns AS col ON idxcol.object_id = col.object_id AND idxcol.column_id = col.column_id + WHERE " . $this->getTableWhereClause($table, 'scm.name', 'tbl.name') . " + AND idx.type = 4 + "); + $definitions = collect([]); + if (count($columns) > 0) { + foreach ($columns as $column) { + $definitions->push($column->indexname); + } + } + return $definitions; + } + + /** + * Returns the where clause to filter schema and table name in a query. + * + * @param string $table The full qualified name of the table. + * @param string $schemaColumn The name of the column to compare the schema to in the where clause. + * @param string $tableColumn The name of the column to compare the table to in the where clause. + * + * @return string + */ + private function getTableWhereClause(string $table, string $schemaColumn, string $tableColumn): string + { + if (strpos($table, '.') !== false) { + [$schema, $table] = explode('.', $table); + $schema = $this->quoteStringLiteral($schema); + $table = $this->quoteStringLiteral($table); + } else { + $schema = 'SCHEMA_NAME()'; + $table = $this->quoteStringLiteral($table); + } + + return sprintf('(%s = %s AND %s = %s)', $tableColumn, $table, $schemaColumn, $schema); + } + + /** + * Quotes a literal string. + * This method is NOT meant to fix SQL injections! + * It is only meant to escape this platform's string literal + * quote character inside the given literal string. + * + * @param string $str The literal string to be quoted. + * + * @return string The quoted literal string. + */ + private function quoteStringLiteral(string $str): string + { + $c = $this->getStringLiteralQuoteCharacter(); + + return $c . str_replace($c, $c . $c, $str) . $c; + } + + /** + * Gets the character used for string literal quoting. + * + * @return string + */ + private function getStringLiteralQuoteCharacter(): string + { + return "'"; + } +} From de05a38f27c6bb23e4f2720246492e9a009abce2 Mon Sep 17 00:00:00 2001 From: kitloong Date: Sun, 4 Jul 2021 10:46:14 +0800 Subject: [PATCH 50/65] Ignore \' default value test for sqlsrv --- .../2020_03_21_000000_expected_create_all_columns_db_table.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/KitLoong/resources/database/migrations/general/2020_03_21_000000_expected_create_all_columns_db_table.php b/tests/KitLoong/resources/database/migrations/general/2020_03_21_000000_expected_create_all_columns_db_table.php index 5feda621..03d3774c 100644 --- a/tests/KitLoong/resources/database/migrations/general/2020_03_21_000000_expected_create_all_columns_db_table.php +++ b/tests/KitLoong/resources/database/migrations/general/2020_03_21_000000_expected_create_all_columns_db_table.php @@ -83,7 +83,7 @@ public function up() $table->string('string'); $table->string('string_255', 255); $table->string('string_100', 100); - if (config('database.default') === 'pgsql') { + if (config('database.default') === 'pgsql' || config('database.default') === 'sqlsrv') { $table->string('default_single_quote')->default('string with \" !@#$%^^&*()_+ quotes'); $table->string('comment_double_quote')->comment("string with ' quotes"); } else { From 09ebab7a0029b1ea83c2cc64f5e9d3fbed35588a Mon Sep 17 00:00:00 2001 From: kitloong Date: Sun, 4 Jul 2021 13:34:19 +0800 Subject: [PATCH 51/65] Move quoteStringLiteral and getStringLiteralQuoteCharacter to abstract Repository --- .../Repositories/MySQLRepository.php | 2 +- .../Repositories/PgSQLRepository.php | 2 +- .../Repositories/Repository.php | 33 +++++++++++++ .../Repositories/SQLSrvRepository.php | 46 ++++--------------- 4 files changed, 45 insertions(+), 38 deletions(-) create mode 100644 src/KitLoong/MigrationsGenerator/Repositories/Repository.php diff --git a/src/KitLoong/MigrationsGenerator/Repositories/MySQLRepository.php b/src/KitLoong/MigrationsGenerator/Repositories/MySQLRepository.php index 949d065a..406469cd 100644 --- a/src/KitLoong/MigrationsGenerator/Repositories/MySQLRepository.php +++ b/src/KitLoong/MigrationsGenerator/Repositories/MySQLRepository.php @@ -9,7 +9,7 @@ use KitLoong\MigrationsGenerator\MigrationsGeneratorSetting; -class MySQLRepository +class MySQLRepository extends Repository { /** * @return array [ diff --git a/src/KitLoong/MigrationsGenerator/Repositories/PgSQLRepository.php b/src/KitLoong/MigrationsGenerator/Repositories/PgSQLRepository.php index dbc246ff..ff0d6e25 100644 --- a/src/KitLoong/MigrationsGenerator/Repositories/PgSQLRepository.php +++ b/src/KitLoong/MigrationsGenerator/Repositories/PgSQLRepository.php @@ -10,7 +10,7 @@ use Illuminate\Support\Collection; use KitLoong\MigrationsGenerator\MigrationsGeneratorSetting; -class PgSQLRepository +class PgSQLRepository extends Repository { public function getTypeByColumnName(string $table, string $columnName): ?string { diff --git a/src/KitLoong/MigrationsGenerator/Repositories/Repository.php b/src/KitLoong/MigrationsGenerator/Repositories/Repository.php new file mode 100644 index 00000000..26f308ab --- /dev/null +++ b/src/KitLoong/MigrationsGenerator/Repositories/Repository.php @@ -0,0 +1,33 @@ +getStringLiteralQuoteCharacter(); + + return $c.str_replace($c, $c.$c, $str).$c; + } + + /** + * Gets the character used for string literal quoting. + * + * @return string + */ + protected function getStringLiteralQuoteCharacter(): string + { + return "'"; + } +} diff --git a/src/KitLoong/MigrationsGenerator/Repositories/SQLSrvRepository.php b/src/KitLoong/MigrationsGenerator/Repositories/SQLSrvRepository.php index a189a97a..90470a8b 100644 --- a/src/KitLoong/MigrationsGenerator/Repositories/SQLSrvRepository.php +++ b/src/KitLoong/MigrationsGenerator/Repositories/SQLSrvRepository.php @@ -5,8 +5,10 @@ use Illuminate\Support\Collection; use KitLoong\MigrationsGenerator\MigrationsGeneratorSetting; -class SQLSrvRepository +class SQLSrvRepository extends Repository { + const INDEX_TYPE_SPATIAL = 4; + public function getSpatialIndexNames(string $table): Collection { $setting = app(MigrationsGeneratorSetting::class); @@ -18,8 +20,8 @@ public function getSpatialIndexNames(string $table): Collection JOIN sys.indexes AS idx ON tbl.object_id = idx.object_id JOIN sys.index_columns AS idxcol ON idx.object_id = idxcol.object_id AND idx.index_id = idxcol.index_id JOIN sys.columns AS col ON idxcol.object_id = col.object_id AND idxcol.column_id = col.column_id - WHERE " . $this->getTableWhereClause($table, 'scm.name', 'tbl.name') . " - AND idx.type = 4 + WHERE ".$this->getTableWhereClause($table, 'scm.name', 'tbl.name')." + AND idx.type = ".self::INDEX_TYPE_SPATIAL." "); $definitions = collect([]); if (count($columns) > 0) { @@ -33,9 +35,9 @@ public function getSpatialIndexNames(string $table): Collection /** * Returns the where clause to filter schema and table name in a query. * - * @param string $table The full qualified name of the table. - * @param string $schemaColumn The name of the column to compare the schema to in the where clause. - * @param string $tableColumn The name of the column to compare the table to in the where clause. + * @param string $table The full qualified name of the table. + * @param string $schemaColumn The name of the column to compare the schema to in the where clause. + * @param string $tableColumn The name of the column to compare the table to in the where clause. * * @return string */ @@ -43,40 +45,12 @@ private function getTableWhereClause(string $table, string $schemaColumn, string { if (strpos($table, '.') !== false) { [$schema, $table] = explode('.', $table); - $schema = $this->quoteStringLiteral($schema); - $table = $this->quoteStringLiteral($table); + $schema = $this->quoteStringLiteral($schema); } else { $schema = 'SCHEMA_NAME()'; - $table = $this->quoteStringLiteral($table); } + $table = $this->quoteStringLiteral($table); return sprintf('(%s = %s AND %s = %s)', $tableColumn, $table, $schemaColumn, $schema); } - - /** - * Quotes a literal string. - * This method is NOT meant to fix SQL injections! - * It is only meant to escape this platform's string literal - * quote character inside the given literal string. - * - * @param string $str The literal string to be quoted. - * - * @return string The quoted literal string. - */ - private function quoteStringLiteral(string $str): string - { - $c = $this->getStringLiteralQuoteCharacter(); - - return $c . str_replace($c, $c . $c, $str) . $c; - } - - /** - * Gets the character used for string literal quoting. - * - * @return string - */ - private function getStringLiteralQuoteCharacter(): string - { - return "'"; - } } From 861f4dfcfdaa4e1485338098ba18b03292bc521d Mon Sep 17 00:00:00 2001 From: kitloong Date: Sun, 4 Jul 2021 15:40:08 +0800 Subject: [PATCH 52/65] Fixed sqlsrv datetime precision --- .../Generators/DatetimeField.php | 83 ++++++++-- .../Generators/FieldGenerator.php | 10 +- .../Repositories/SQLSrvRepository.php | 57 +++++++ .../Schema/SQLSrv/Column.php | 153 ++++++++++++++++++ .../MigrationsGenerator/Types/DBALTypes.php | 76 ++------- 5 files changed, 303 insertions(+), 76 deletions(-) create mode 100644 src/KitLoong/MigrationsGenerator/Schema/SQLSrv/Column.php diff --git a/src/KitLoong/MigrationsGenerator/Generators/DatetimeField.php b/src/KitLoong/MigrationsGenerator/Generators/DatetimeField.php index 7d8f3939..3eb9bd23 100644 --- a/src/KitLoong/MigrationsGenerator/Generators/DatetimeField.php +++ b/src/KitLoong/MigrationsGenerator/Generators/DatetimeField.php @@ -1,38 +1,47 @@ decorator = $decorator; $this->mySQLRepository = $mySQLRepository; $this->pgSQLRepository = $pgSQLRepository; + $this->sqlSrvRepository = $sqlSrvRepository; $this->regex = $regex; } @@ -89,7 +98,7 @@ public function makeDefault(Column $column): string * @param Column[] $columns * @return bool */ - public function isUseTimestamps($columns): bool + public function isUseTimestamps(array $columns): bool { /** @var Column[] $timestampsColumns */ $timestampsColumns = []; @@ -114,16 +123,60 @@ public function isUseTimestamps($columns): bool private function getLength(string $table, Column $column): ?int { - if (app(MigrationsGeneratorSetting::class)->getPlatform() === Platform::POSTGRESQL) { - $rawType = ($this->pgSQLRepository->getTypeByColumnName($table, $column->getName())); - $length = $this->regex->getTextBetween($rawType); - if ($length !== null) { - return (int) $length; - } else { - return null; - } + switch (app(MigrationsGeneratorSetting::class)->getPlatform()) { + case Platform::POSTGRESQL: + return $this->getPgSQLLength($table, $column); + case Platform::SQLSERVER: + return $this->getSQLSrvLength($table, $column); + default: + return $column->getLength(); + } + } + + /** + * @param string $table + * @param \Doctrine\DBAL\Schema\Column $column + * @return int|null + */ + private function getPgSQLLength(string $table, Column $column): ?int + { + $rawType = ($this->pgSQLRepository->getTypeByColumnName($table, $column->getName())); + $length = $this->regex->getTextBetween($rawType); + if ($length !== null) { + return (int) $length; } else { - return $column->getLength(); + return null; + } + } + + /** + * @param string $table + * @param \Doctrine\DBAL\Schema\Column $column + * @return int|null + */ + private function getSQLSrvLength(string $table, Column $column): ?int + { + $colDef = $this->sqlSrvRepository->getColumnDefinition($table, $column->getName()); + + switch (get_class($column->getType())) { + case DateTimeType::class: + case DateTimeImmutableType::class: + if ($colDef->getScale() === self::SQLSRV_DATETIME_DEFAULT_SCALE && + $colDef->getLength() === self::SQLSRV_DATETIME_DEFAULT_LENGTH) { + return null; + } else { + return $column->getScale(); + } + case DateTimeTzType::class: + case DateTimeTzImmutableType::class: + if ($colDef->getScale() === self::SQLSRV_DATETIME_TZ_DEFAULT_SCALE && + $colDef->getLength() === self::SQLSRV_DATETIME_TZ_DEFAULT_LENGTH) { + return null; + } else { + return $column->getScale(); + } + default: + return $column->getScale(); } } } diff --git a/src/KitLoong/MigrationsGenerator/Generators/FieldGenerator.php b/src/KitLoong/MigrationsGenerator/Generators/FieldGenerator.php index d623f6ed..dfc66fcd 100644 --- a/src/KitLoong/MigrationsGenerator/Generators/FieldGenerator.php +++ b/src/KitLoong/MigrationsGenerator/Generators/FieldGenerator.php @@ -74,6 +74,9 @@ public function __construct( DBALTypes::SMALLINT => ColumnType::SMALL_INTEGER, DBALTypes::BIGINT => ColumnType::BIG_INTEGER, DBALTypes::DATETIME_MUTABLE => ColumnType::DATETIME, + DBALTypes::DATETIME_IMMUTABLE => ColumnType::DATETIME, + DBALTypes::DATETIMETZ_MUTABLE => ColumnType::DATETIME_TZ, + DBALTypes::DATETIMETZ_IMMUTABLE => ColumnType::DATETIME_TZ, DBALTypes::BLOB => ColumnType::BINARY, DBALTypes::GUID => ColumnType::UUID, ]; @@ -175,11 +178,14 @@ private function makeLaravelFieldTypeMethod( case DBALTypes::TINYINT: return $this->integerField->makeField($tableName, $field, $column, $indexes); case DBALTypes::DATETIME_MUTABLE: + case DBALTypes::DATETIME_IMMUTABLE: + case DBALTypes::DATETIMETZ_MUTABLE: + case DBALTypes::DATETIMETZ_IMMUTABLE: case DBALTypes::TIMESTAMP: + case DBALTypes::TIMESTAMP_TZ: case DBALTypes::TIME_MUTABLE: - case DBALTypes::DATETIME_TZ: + case DBALTypes::TIME_IMMUTABLE: case DBALTypes::TIME_TZ: - case DBALTypes::TIMESTAMP_TZ: return $this->datetimeField->makeField($tableName, $field, $column, $useTimestamps); case DBALTypes::DECIMAL: case DBALTypes::FLOAT: diff --git a/src/KitLoong/MigrationsGenerator/Repositories/SQLSrvRepository.php b/src/KitLoong/MigrationsGenerator/Repositories/SQLSrvRepository.php index 90470a8b..24fa2a3b 100644 --- a/src/KitLoong/MigrationsGenerator/Repositories/SQLSrvRepository.php +++ b/src/KitLoong/MigrationsGenerator/Repositories/SQLSrvRepository.php @@ -4,6 +4,7 @@ use Illuminate\Support\Collection; use KitLoong\MigrationsGenerator\MigrationsGeneratorSetting; +use KitLoong\MigrationsGenerator\Schema\SQLSrv\Column; class SQLSrvRepository extends Repository { @@ -32,6 +33,62 @@ public function getSpatialIndexNames(string $table): Collection return $definitions; } + /** + * @param string $table + * @param string $column + * @return \KitLoong\MigrationsGenerator\Schema\SQLSrv\Column|null + */ + public function getColumnDefinition(string $table, string $column): ?Column + { + $setting = app(MigrationsGeneratorSetting::class); + $columns = $setting->getConnection() + ->select(" + SELECT col.name, + type.name AS type, + col.max_length AS length, + ~col.is_nullable AS notnull, + def.definition AS [default], + col.scale, + col.precision, + col.is_identity AS autoincrement, + col.collation_name AS collation, + CAST(prop.value AS NVARCHAR(MAX)) AS comment -- CAST avoids driver error for sql_variant type + FROM sys.columns AS col + JOIN sys.types AS type + ON col.user_type_id = type.user_type_id + JOIN sys.objects AS obj + ON col.object_id = obj.object_id + JOIN sys.schemas AS scm + ON obj.schema_id = scm.schema_id + LEFT JOIN sys.default_constraints def + ON col.default_object_id = def.object_id + AND col.object_id = def.parent_object_id + LEFT JOIN sys.extended_properties AS prop + ON obj.object_id = prop.major_id + AND col.column_id = prop.minor_id + AND prop.name = 'MS_Description' + WHERE obj.type = 'U' + AND ".$this->getTableWhereClause($table, 'scm.name', 'obj.name')." + AND col.name = ".$this->quoteStringLiteral($column)." + "); + if (count($columns) > 0) { + $column = $columns[0]; + return new Column( + $column->name, + $column->type, + $column->length, + $column->notnull, + $column->scale, + $column->precision, + $column->autoincrement, + $column->default, + $column->collation, + $column->comment + ); + } + return null; + } + /** * Returns the where clause to filter schema and table name in a query. * diff --git a/src/KitLoong/MigrationsGenerator/Schema/SQLSrv/Column.php b/src/KitLoong/MigrationsGenerator/Schema/SQLSrv/Column.php new file mode 100644 index 00000000..24b2f540 --- /dev/null +++ b/src/KitLoong/MigrationsGenerator/Schema/SQLSrv/Column.php @@ -0,0 +1,153 @@ +name = $name; + $this->type = $type; + $this->length = $length; + $this->notnull = $notnull; + $this->default = $default; + $this->scale = $scale; + $this->precision = $precision; + $this->autoincrement = $autoincrement; + $this->collation = $collation; + $this->comment = $comment; + } + + /** + * @return string + */ + public function getName(): string + { + return $this->name; + } + + /** + * @return string + */ + public function getType(): string + { + return $this->type; + } + + /** + * @return int + */ + public function getLength(): int + { + return $this->length; + } + + /** + * @return bool + */ + public function isNotnull(): bool + { + return $this->notnull; + } + + /** + * @return string|null + */ + public function getDefault(): ?string + { + return $this->default; + } + + /** + * @return int + */ + public function getScale(): int + { + return $this->scale; + } + + /** + * @return int + */ + public function getPrecision(): int + { + return $this->precision; + } + + /** + * @return bool + */ + public function isAutoincrement(): bool + { + return $this->autoincrement; + } + + /** + * @return string|null + */ + public function getCollation(): ?string + { + return $this->collation; + } + + /** + * @return string|null + */ + public function getComment(): ?string + { + return $this->comment; + } +} diff --git a/src/KitLoong/MigrationsGenerator/Types/DBALTypes.php b/src/KitLoong/MigrationsGenerator/Types/DBALTypes.php index 0cfa7ad0..1dc147f2 100644 --- a/src/KitLoong/MigrationsGenerator/Types/DBALTypes.php +++ b/src/KitLoong/MigrationsGenerator/Types/DBALTypes.php @@ -7,64 +7,27 @@ namespace KitLoong\MigrationsGenerator\Types; +use Doctrine\DBAL\Types\Types; use KitLoong\MigrationsGenerator\MigrationMethod\ColumnType; final class DBALTypes { - /** - * @see \Doctrine\DBAL\Types\Types::BIGINT - */ - const BIGINT = 'bigint'; - - /** - * @see \Doctrine\DBAL\Types\Types::BLOB - */ - const BLOB = 'blob'; - - /** - * @see \Doctrine\DBAL\Types\Types::BOOLEAN - */ - const BOOLEAN = 'boolean'; - - /** - * @see \Doctrine\DBAL\Types\Types::DATETIME_MUTABLE - */ - const DATETIME_MUTABLE = 'datetime'; - - /** - * @see \Doctrine\DBAL\Types\Types::DECIMAL - */ - const DECIMAL = 'decimal'; - - /** - * @see \Doctrine\DBAL\Types\Types::FLOAT - */ - const FLOAT = 'float'; - - /** - * @see \Doctrine\DBAL\Types\Types::GUID - */ - const GUID = 'guid'; - - /** - * @see \Doctrine\DBAL\Types\Types::INTEGER - */ - const INTEGER = 'integer'; - - /** - * @see \Doctrine\DBAL\Types\Types::SMALLINT - */ - const SMALLINT = 'smallint'; - - /** - * @see \Doctrine\DBAL\Types\Types::STRING - */ - const STRING = 'string'; - - /** - * @see \Doctrine\DBAL\Types\Types::TIME_MUTABLE - */ - const TIME_MUTABLE = 'time'; + const BIGINT = Types::BIGINT; + const BLOB = Types::BLOB; + const BOOLEAN = Types::BOOLEAN; + const DATETIME_MUTABLE = Types::DATETIME_MUTABLE; + const DATETIME_IMMUTABLE = Types::DATETIME_IMMUTABLE; + const DATETIMETZ_MUTABLE = Types::DATETIMETZ_MUTABLE; + const DATETIMETZ_IMMUTABLE = Types::DATETIMETZ_IMMUTABLE; + const DATE = Types::DATETIMETZ_MUTABLE; + const DECIMAL = Types::DECIMAL; + const FLOAT = Types::FLOAT; + const GUID = Types::GUID; + const INTEGER = Types::INTEGER; + const SMALLINT = Types::SMALLINT; + const STRING = Types::STRING; + const TIME_MUTABLE = Types::TIME_MUTABLE; + const TIME_IMMUTABLE = Types::TIME_IMMUTABLE; // Custom types, should identical with CustomDoctrineType name @@ -104,11 +67,6 @@ final class DBALTypes */ const TINYINT = ColumnType::TINY_INTEGER; - /** - * @see DateTimeTzType::getName() - */ - const DATETIME_TZ = ColumnType::DATETIME_TZ; - /** * @see TimeTzType::getName() */ From f560e6eb9f7e9f42fc969a1eb25756cbfb191e4e Mon Sep 17 00:00:00 2001 From: kitloong Date: Sun, 4 Jul 2021 16:28:12 +0800 Subject: [PATCH 53/65] Fixed sqlsrv text generation --- .../Generators/StringField.php | 44 ++++++++++++++----- .../MigrationMethod/ColumnType.php | 2 + 2 files changed, 35 insertions(+), 11 deletions(-) diff --git a/src/KitLoong/MigrationsGenerator/Generators/StringField.php b/src/KitLoong/MigrationsGenerator/Generators/StringField.php index 420d3db5..6461e340 100644 --- a/src/KitLoong/MigrationsGenerator/Generators/StringField.php +++ b/src/KitLoong/MigrationsGenerator/Generators/StringField.php @@ -15,33 +15,57 @@ use KitLoong\MigrationsGenerator\MigrationMethod\ColumnType; use KitLoong\MigrationsGenerator\MigrationsGeneratorSetting; use KitLoong\MigrationsGenerator\Repositories\PgSQLRepository; +use KitLoong\MigrationsGenerator\Repositories\SQLSrvRepository; use KitLoong\MigrationsGenerator\Support\Regex; class StringField { + const SQLSRV_TEXT_TYPE = 'nvarchar'; + const SQLSRV_TEXT_LENGTH = -1; + private $collationModifier; private $charsetModifier; private $pgSQLRepository; + private $sqlSrvRepository; private $regex; public function __construct( CollationModifier $collationModifier, CharsetModifier $charsetModifier, PgSQLRepository $pgSQLRepository, + SQLSrvRepository $sqlSrvRepository, Regex $regex ) { $this->collationModifier = $collationModifier; $this->charsetModifier = $charsetModifier; $this->pgSQLRepository = $pgSQLRepository; + $this->sqlSrvRepository = $sqlSrvRepository; $this->regex = $regex; } public function makeField(string $tableName, array $field, Column $column): array { - if (($pgSQLEnum = $this->getPgSQLEnumValue($tableName, $column->getName())) !== '') { - $field['type'] = ColumnType::ENUM; - $field['args'][] = $pgSQLEnum; - } else { + switch (app(MigrationsGeneratorSetting::class)->getPlatform()) { + // It could be pgsql enum + case Platform::POSTGRESQL: + if (($pgSQLEnum = $this->getPgSQLEnumValue($tableName, $column->getName())) !== '') { + $field['type'] = ColumnType::ENUM; + $field['args'][] = $pgSQLEnum; + } + break; + // It could be sqlsrv text + case Platform::SQLSERVER: + $colDef = $this->sqlSrvRepository->getColumnDefinition($tableName, $column->getName()); + if ($colDef->getType() === self::SQLSRV_TEXT_TYPE && + $colDef->getLength() === self::SQLSRV_TEXT_LENGTH) { + $field['type'] = ColumnType::TEXT; + } + break; + default: + } + + // Continue if type is `string` + if ($field['type'] === ColumnType::STRING) { if ($field['field'] === ColumnName::REMEMBER_TOKEN && $column->getLength() === 100 && !$column->getFixed()) { $field['type'] = ColumnType::REMEMBER_TOKEN; $field['field'] = null; @@ -72,13 +96,11 @@ public function makeField(string $tableName, array $field, Column $column): arra private function getPgSQLEnumValue(string $tableName, string $column): string { - if (app(MigrationsGeneratorSetting::class)->getPlatform() === Platform::POSTGRESQL) { - $definition = ($this->pgSQLRepository->getCheckConstraintDefinition($tableName, $column)); - if (!empty($definition)) { - $enumValues = $this->regex->getTextBetweenAll($definition, "'", "'::"); - if (!empty($enumValues)) { - return "['".implode("', '", $enumValues)."']"; - } + $definition = ($this->pgSQLRepository->getCheckConstraintDefinition($tableName, $column)); + if (!empty($definition)) { + $enumValues = $this->regex->getTextBetweenAll($definition, "'", "'::"); + if (!empty($enumValues)) { + return "['".implode("', '", $enumValues)."']"; } } return ''; diff --git a/src/KitLoong/MigrationsGenerator/MigrationMethod/ColumnType.php b/src/KitLoong/MigrationsGenerator/MigrationMethod/ColumnType.php index b3927132..83cfa6a8 100644 --- a/src/KitLoong/MigrationsGenerator/MigrationMethod/ColumnType.php +++ b/src/KitLoong/MigrationsGenerator/MigrationMethod/ColumnType.php @@ -42,6 +42,8 @@ final class ColumnType const SMALL_INCREMENTS = 'smallIncrements'; const SMALL_INTEGER = 'smallInteger'; const SOFT_DELETES = 'softDeletes'; + const STRING = 'string'; + const TEXT = 'text'; const TIME_TZ = 'timeTz'; const TIMESTAMP = 'timestamp'; const TIMESTAMPS = 'timestamps'; From 35613bad066469bf724afcc5c5afdda00e40947d Mon Sep 17 00:00:00 2001 From: kitloong Date: Sun, 4 Jul 2021 20:19:32 +0800 Subject: [PATCH 54/65] Update test template --- ..._000000_expected_create_collations_db_table.php | 14 +++++++++++--- ...000000_expected_create_all_columns_db_table.php | 7 +++++++ 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/tests/KitLoong/resources/database/migrations/collation/2020_03_21_000000_expected_create_collations_db_table.php b/tests/KitLoong/resources/database/migrations/collation/2020_03_21_000000_expected_create_collations_db_table.php index 4f46e683..dcb80c55 100644 --- a/tests/KitLoong/resources/database/migrations/collation/2020_03_21_000000_expected_create_collations_db_table.php +++ b/tests/KitLoong/resources/database/migrations/collation/2020_03_21_000000_expected_create_collations_db_table.php @@ -26,15 +26,23 @@ public function up() case 'pgsql': $collation = 'en_US.utf8'; break; + case 'sqlsrv': + $collation = 'Latin1_General_100_CI_AI_SC_UTF8'; + break; default: $collation = 'utf8_unicode_ci'; } $table->char('char'); $table->char('char_charset')->charset('utf8'); - $table->enum('enum', ['easy', 'hard']); - $table->enum('enum_charset', ['easy', 'hard'])->charset('utf8'); - $table->enum('enum_collation', ['easy', 'hard'])->collation($collation); + + // sqlsrv does not support collation with enum + if (config('database.default') !== 'sqlsrv') { + $table->enum('enum', ['easy', 'hard']); + $table->enum('enum_charset', ['easy', 'hard'])->charset('utf8'); + $table->enum('enum_collation', ['easy', 'hard'])->collation($collation); + } + $table->longText('longText'); $table->longText('longText_charset')->charset('utf8'); $table->longText('longText_collation')->collation($collation); diff --git a/tests/KitLoong/resources/database/migrations/general/2020_03_21_000000_expected_create_all_columns_db_table.php b/tests/KitLoong/resources/database/migrations/general/2020_03_21_000000_expected_create_all_columns_db_table.php index 03d3774c..9d22a668 100644 --- a/tests/KitLoong/resources/database/migrations/general/2020_03_21_000000_expected_create_all_columns_db_table.php +++ b/tests/KitLoong/resources/database/migrations/general/2020_03_21_000000_expected_create_all_columns_db_table.php @@ -35,11 +35,18 @@ public function up() $table->date('date_default')->default('2020-10-08'); $table->dateTime('dateTime'); $table->dateTime('dateTime_0', 0); + $table->dateTime('dateTime_1', 1); $table->dateTime('dateTime_2', 2); + $table->dateTime('dateTime_3', 3); + $table->dateTime('dateTime_4', 4); + $table->dateTime('dateTime_5', 5); $table->dateTime('dateTime_default', 2)->default('2020-10-08 10:20:30'); $table->dateTimeTz('dateTimeTz'); $table->dateTimeTz('dateTimeTz_0', 0); + $table->dateTimeTz('dateTimeTz_1', 1); $table->dateTimeTz('dateTimeTz_2', 2); + $table->dateTimeTz('dateTimeTz_3', 3); + $table->dateTimeTz('dateTimeTz_4', 4); $table->dateTimeTz('dateTimeTz_default')->default('2020-10-08 10:20:30'); $table->decimal('decimal'); $table->decimal('decimal_82', 8, 2); From aa5fdfc8b147f6e2dc961d0a8347be6dd0d0186e Mon Sep 17 00:00:00 2001 From: kitloong Date: Sun, 4 Jul 2021 20:22:26 +0800 Subject: [PATCH 55/65] Remove old test --- .../Generators/FieldGeneratorTest.php | 597 ------------------ 1 file changed, 597 deletions(-) delete mode 100644 tests/KitLoong/MigrationsGenerator/Generators/FieldGeneratorTest.php diff --git a/tests/KitLoong/MigrationsGenerator/Generators/FieldGeneratorTest.php b/tests/KitLoong/MigrationsGenerator/Generators/FieldGeneratorTest.php deleted file mode 100644 index 8a0efcd5..00000000 --- a/tests/KitLoong/MigrationsGenerator/Generators/FieldGeneratorTest.php +++ /dev/null @@ -1,597 +0,0 @@ -assertSame([ - DBALTypes::SMALLINT => ColumnType::SMALL_INTEGER, - DBALTypes::BIGINT => ColumnType::BIG_INTEGER, - DBALTypes::DATETIME_MUTABLE => ColumnType::DATETIME, - DBALTypes::BLOB => ColumnType::BINARY - ], $fieldGenerator::$fieldTypeMap); - } - - public function testGenerateEmptyColumn() - { - /** @var FieldGenerator $fieldGenerator */ - $fieldGenerator = resolve(FieldGenerator::class); - - $index = ['index']; - $indexes = collect([$index]); - - $fields = $fieldGenerator->generate('table', [], $indexes); - $this->assertEmpty($fields); - } - - public function testGenerateEmptyField() - { - $this->mock(DatetimeField::class, function (MockInterface $mock) { - $mock->shouldReceive('isUseTimestamps') - ->andReturnFalse(); - }); - - $types = [ - DBALTypes::INTEGER, - ]; - - foreach ($types as $type) { - $index = ['index']; - $column = Mockery::mock(Column::class); - $indexes = collect([$index]); - - $column->shouldReceive('getName') - ->andReturn('name'); - $column->shouldReceive('getNotnull') - ->andReturnTrue(); - $column->shouldReceive('getDefault') - ->andReturnNull(); - $column->shouldReceive('getComment') - ->andReturnNull(); - $column->shouldReceive('getType->getName') - ->andReturn($type); - - $field = [ - 'field' => 'name', - 'type' => $type, - 'args' => [], - 'decorators' => [] - ]; - - $this->mock(IntegerField::class, function (MockInterface $mock) use ($field, $column, $indexes) { - $mock->shouldReceive('makeField') - ->with('table', $field, $column, $indexes) - ->andReturn([]); - }); - - /** @var FieldGenerator $fieldGenerator */ - $fieldGenerator = resolve(FieldGenerator::class); - - $fields = $fieldGenerator->generate('table', [$column], $indexes); - $this->assertEmpty($fields); - } - } - - public function testGenerateInteger() - { - $this->mock(DatetimeField::class, function (MockInterface $mock) { - $mock->shouldReceive('isUseTimestamps') - ->andReturnFalse(); - }); - - $types = [ - DBALTypes::INTEGER, - DBALTypes::BIGINT, - DBALTypes::MEDIUMINT, - DBALTypes::SMALLINT, - DBALTypes::TINYINT - ]; - - foreach ($types as $type) { - $index = ['index']; - $column = Mockery::mock(Column::class); - $indexes = collect([$index]); - - $column->shouldReceive('getName') - ->andReturn('name'); - $column->shouldReceive('getNotnull') - ->andReturnTrue(); - $column->shouldReceive('getDefault') - ->andReturnNull(); - $column->shouldReceive('getComment') - ->andReturnNull(); - $column->shouldReceive('getType->getName') - ->andReturn($type); - - $field = [ - 'field' => 'name', - 'type' => $type, - 'args' => [], - 'decorators' => [] - ]; - - $this->mock(IntegerField::class, function (MockInterface $mock) use ($field, $column, $indexes) { - $returnField = $field; - $returnField['field'] = 'returned'; - $mock->shouldReceive('makeField') - ->with('table', $field, $column, $indexes) - ->andReturn($returnField); - }); - - /** @var FieldGenerator $fieldGenerator */ - $fieldGenerator = resolve(FieldGenerator::class); - - $fields = $fieldGenerator->generate('table', [$column], $indexes); - $this->assertSame('returned', $fields[0]['field']); - } - } - - public function testGenerateDatetime() - { - $types = [ - DBALTypes::DATETIME_MUTABLE, - DBALTypes::TIMESTAMP, - DBALTypes::TIME_MUTABLE - ]; - - foreach ($types as $type) { - $index = ['index']; - $column = Mockery::mock(Column::class); - $indexes = collect([$index]); - - $column->shouldReceive('getName') - ->andReturn('name'); - $column->shouldReceive('getNotnull') - ->andReturnTrue(); - $column->shouldReceive('getDefault') - ->andReturnNull(); - $column->shouldReceive('getComment') - ->andReturnNull(); - $column->shouldReceive('getType->getName') - ->andReturn($type); - - $field = [ - 'field' => 'name', - 'type' => $type, - 'args' => [], - 'decorators' => [] - ]; - - $this->mock(DatetimeField::class, function (MockInterface $mock) use ($field, $column, $indexes) { - $mock->shouldReceive('isUseTimestamps') - ->andReturnFalse(); - - $returnField = $field; - $returnField['field'] = 'returned'; - $mock->shouldReceive('makeField') - ->with('table', $field, $column, false) - ->andReturn($returnField); - }); - - /** @var FieldGenerator $fieldGenerator */ - $fieldGenerator = resolve(FieldGenerator::class); - - $fields = $fieldGenerator->generate('table', [$column], $indexes); - $this->assertSame('returned', $fields[0]['field']); - } - } - - public function testGenerateDecimal() - { - $this->mock(DatetimeField::class, function (MockInterface $mock) { - $mock->shouldReceive('isUseTimestamps') - ->andReturnFalse(); - }); - - $types = [ - DBALTypes::DECIMAL, - DBALTypes::FLOAT, - DBALTypes::DOUBLE - ]; - - foreach ($types as $type) { - $index = ['index']; - $column = Mockery::mock(Column::class); - $indexes = collect([$index]); - - $column->shouldReceive('getName') - ->andReturn('name'); - $column->shouldReceive('getNotnull') - ->andReturnTrue(); - $column->shouldReceive('getDefault') - ->andReturnNull(); - $column->shouldReceive('getComment') - ->andReturnNull(); - $column->shouldReceive('getType->getName') - ->andReturn($type); - - $field = [ - 'field' => 'name', - 'type' => $type, - 'args' => [], - 'decorators' => [] - ]; - - $this->mock(DecimalField::class, function (MockInterface $mock) use ($field, $column, $indexes) { - $returnField = $field; - $returnField['field'] = 'returned'; - $mock->shouldReceive('makeField') - ->with($field, $column) - ->andReturn($returnField); - }); - - /** @var FieldGenerator $fieldGenerator */ - $fieldGenerator = resolve(FieldGenerator::class); - - $fields = $fieldGenerator->generate('table', [$column], $indexes); - $this->assertSame('returned', $fields[0]['field']); - } - } - - public function testGenerateEnum() - { - $this->mock(DatetimeField::class, function (MockInterface $mock) { - $mock->shouldReceive('isUseTimestamps') - ->andReturnFalse(); - }); - - $types = [ - DBALTypes::ENUM - ]; - - foreach ($types as $type) { - $index = ['index']; - $column = Mockery::mock(Column::class); - $indexes = collect([$index]); - - $column->shouldReceive('getName') - ->andReturn('name'); - $column->shouldReceive('getNotnull') - ->andReturnTrue(); - $column->shouldReceive('getDefault') - ->andReturnNull(); - $column->shouldReceive('getComment') - ->andReturnNull(); - $column->shouldReceive('getType->getName') - ->andReturn($type); - - $field = [ - 'field' => 'name', - 'type' => $type, - 'args' => [], - 'decorators' => [] - ]; - - $this->mock(EnumField::class, function (MockInterface $mock) use ($field, $column, $indexes) { - $returnField = $field; - $returnField['field'] = 'returned'; - $mock->shouldReceive('makeField') - ->with('table', $field, $column) - ->andReturn($returnField); - }); - - /** @var FieldGenerator $fieldGenerator */ - $fieldGenerator = resolve(FieldGenerator::class); - - $fields = $fieldGenerator->generate('table', [$column], $indexes); - $this->assertSame('returned', $fields[0]['field']); - } - } - - public function testGenerateGeometry() - { - $this->mock(DatetimeField::class, function (MockInterface $mock) { - $mock->shouldReceive('isUseTimestamps') - ->andReturnFalse(); - }); - - $types = [ - DBALTypes::GEOMETRY - ]; - - foreach ($types as $type) { - $index = ['index']; - $column = Mockery::mock(Column::class); - $indexes = collect([$index]); - - $column->shouldReceive('getName') - ->andReturn('name'); - $column->shouldReceive('getNotnull') - ->andReturnTrue(); - $column->shouldReceive('getDefault') - ->andReturnNull(); - $column->shouldReceive('getComment') - ->andReturnNull(); - $column->shouldReceive('getType->getName') - ->andReturn($type); - - $field = [ - 'field' => 'name', - 'type' => $type, - 'args' => [], - 'decorators' => [] - ]; - - $this->mock(GeometryField::class, function (MockInterface $mock) use ($field) { - $returnField = $field; - $returnField['field'] = 'returned'; - $mock->shouldReceive('makeField') - ->with('table', $field) - ->andReturn($returnField); - }); - - /** @var FieldGenerator $fieldGenerator */ - $fieldGenerator = resolve(FieldGenerator::class); - - $fields = $fieldGenerator->generate('table', [$column], $indexes); - $this->assertSame('returned', $fields[0]['field']); - } - } - - public function testGenerateSet() - { - $this->mock(DatetimeField::class, function (MockInterface $mock) { - $mock->shouldReceive('isUseTimestamps') - ->andReturnFalse(); - }); - - $types = [ - DBALTypes::SET - ]; - - foreach ($types as $type) { - $index = ['index']; - $column = Mockery::mock(Column::class); - $indexes = collect([$index]); - - $column->shouldReceive('getName') - ->andReturn('name'); - $column->shouldReceive('getNotnull') - ->andReturnTrue(); - $column->shouldReceive('getDefault') - ->andReturnNull(); - $column->shouldReceive('getComment') - ->andReturnNull(); - $column->shouldReceive('getType->getName') - ->andReturn($type); - - $field = [ - 'field' => 'name', - 'type' => $type, - 'args' => [], - 'decorators' => [] - ]; - - $this->mock(SetField::class, function (MockInterface $mock) use ($field, $column, $indexes) { - $returnField = $field; - $returnField['field'] = 'returned'; - $mock->shouldReceive('makeField') - ->with('table', $field, $column) - ->andReturn($returnField); - }); - - /** @var FieldGenerator $fieldGenerator */ - $fieldGenerator = resolve(FieldGenerator::class); - - $fields = $fieldGenerator->generate('table', [$column], $indexes); - $this->assertSame('returned', $fields[0]['field']); - } - } - - public function testGenerateString() - { - $this->mock(DatetimeField::class, function (MockInterface $mock) { - $mock->shouldReceive('isUseTimestamps') - ->andReturnFalse(); - }); - - $types = [ - DBALTypes::STRING - ]; - - foreach ($types as $type) { - $index = ['index']; - $column = Mockery::mock(Column::class); - $indexes = collect([$index]); - - $column->shouldReceive('getName') - ->andReturn('name'); - $column->shouldReceive('getNotnull') - ->andReturnTrue(); - $column->shouldReceive('getDefault') - ->andReturnNull(); - $column->shouldReceive('getComment') - ->andReturnNull(); - $column->shouldReceive('getType->getName') - ->andReturn($type); - - $field = [ - 'field' => 'name', - 'type' => $type, - 'args' => [], - 'decorators' => [] - ]; - - $this->mock(StringField::class, function (MockInterface $mock) use ($field, $column, $indexes) { - $returnField = $field; - $returnField['field'] = 'returned'; - $mock->shouldReceive('makeField') - ->with('table', $field, $column) - ->andReturn($returnField); - }); - - /** @var FieldGenerator $fieldGenerator */ - $fieldGenerator = resolve(FieldGenerator::class); - - $fields = $fieldGenerator->generate('table', [$column], $indexes); - $this->assertSame('returned', $fields[0]['field']); - } - } - - public function testGenerateOtherType() - { - $this->mock(DatetimeField::class, function (MockInterface $mock) { - $mock->shouldReceive('isUseTimestamps') - ->andReturnFalse(); - }); - - $types = [ - 'json' - ]; - - foreach ($types as $type) { - $index = ['index']; - $column = Mockery::mock(Column::class); - $indexes = collect([$index]); - - $column->shouldReceive('getName') - ->andReturn('name'); - $column->shouldReceive('getNotnull') - ->andReturnTrue(); - $column->shouldReceive('getDefault') - ->andReturnNull(); - $column->shouldReceive('getComment') - ->andReturnNull(); - $column->shouldReceive('getType->getName') - ->andReturn($type); - - $field = [ - 'field' => 'name', - 'type' => $type, - 'args' => [], - 'decorators' => [] - ]; - - $this->mock(OtherField::class, function (MockInterface $mock) use ($field, $column) { - $returnField = $field; - $returnField['field'] = 'returned'; - $mock->shouldReceive('makeField') - ->with('table', $field, $column) - ->andReturn($returnField); - }); - - /** @var FieldGenerator $fieldGenerator */ - $fieldGenerator = resolve(FieldGenerator::class); - - $fields = $fieldGenerator->generate('table', [$column], $indexes); - $this->assertSame('returned', $fields[0]['field']); - } - } - - public function testGenerateWithAllModifier() - { - $this->mock(DatetimeField::class, function (MockInterface $mock) { - $mock->shouldReceive('isUseTimestamps') - ->andReturnFalse(); - }); - - $types = [ - DBALTypes::INTEGER, - ]; - - foreach ($types as $type) { - $index = ['index']; - $column = Mockery::mock(Column::class); - $indexes = collect(['returned' => $index]); - - $column->shouldReceive('getName') - ->andReturn('name'); - $column->shouldReceive('getNotnull') - ->andReturnFalse(); - $column->shouldReceive('getDefault') - ->andReturn('default'); - $column->shouldReceive('getComment') - ->andReturn('comment'); - $column->shouldReceive('getType->getName') - ->andReturn($type); - - $field = [ - 'field' => 'name', - 'type' => $type, - 'args' => [], - 'decorators' => [] - ]; - - $this->mock(IntegerField::class, function (MockInterface $mock) use ($field, $column, $indexes) { - $returnField = $field; - $returnField['field'] = 'returned'; - $mock->shouldReceive('makeField') - ->with('table', $field, $column, $indexes) - ->andReturn($returnField); - }); - - $this->mock(NullableModifier::class, function (MockInterface $mock) { - $mock->shouldReceive('shouldAddNullableModifier') - ->with(ColumnType::INTEGER) - ->andReturnTrue(); - }); - - $this->mock(DefaultModifier::class, function (MockInterface $mock) use ($column) { - $mock->shouldReceive('generate') - ->with(DBALTypes::INTEGER, $column) - ->andReturn('default(default)'); - }); - - $this->mock(IndexModifier::class, function (MockInterface $mock) use ($index) { - $mock->shouldReceive('generate') - ->with($index) - ->andReturn('index'); - }); - - $this->mock(CommentModifier::class, function (MockInterface $mock) use ($column) { - $mock->shouldReceive('generate') - ->with('comment') - ->andReturn("comment('comment')"); - }); - - /** @var FieldGenerator $fieldGenerator */ - $fieldGenerator = resolve(FieldGenerator::class); - - $fields = $fieldGenerator->generate('table', [$column], $indexes); - $this->assertSame([ - [ - 'field' => 'returned', - 'type' => ColumnType::INTEGER, - 'args' => [], - 'decorators' => [ - ColumnModifier::NULLABLE, - 'default(default)', - 'index', - "comment('comment')" - ] - ] - ], $fields); - } - } -} From 103b9193c78b6dd8838c8527e9c916cc507b74c9 Mon Sep 17 00:00:00 2001 From: Kit Loong Date: Sun, 4 Jul 2021 23:26:20 +0800 Subject: [PATCH 56/65] Add sqlsrv UT (#40) * Add sqlsrv service * Skip sqlsrv test for Laravel 5.6 and 5.7 --- .env.action | 6 ++ .github/docker/sqlsrv/db-init.sh | 7 +++ .github/docker/sqlsrv/entrypoint.sh | 3 + .github/workflows/tests.yml | 21 ++++++- tests/KitLoong/Feature/SQLSrv/CommandTest.php | 54 ++++++++++++++++ .../Feature/SQLSrv/SQLSrvTestCase.php | 63 +++++++++++++++++++ 6 files changed, 153 insertions(+), 1 deletion(-) create mode 100755 .github/docker/sqlsrv/db-init.sh create mode 100644 .github/docker/sqlsrv/entrypoint.sh create mode 100644 tests/KitLoong/Feature/SQLSrv/CommandTest.php create mode 100644 tests/KitLoong/Feature/SQLSrv/SQLSrvTestCase.php diff --git a/.env.action b/.env.action index f8cf3845..15ff3877 100644 --- a/.env.action +++ b/.env.action @@ -15,3 +15,9 @@ POSTGRES_PORT=5432 POSTGRES_DATABASE=migration POSTGRES_USERNAME=root POSTGRES_PASSWORD=!QAZ2wsx + +SQLSRV_HOST=127.0.0.1 +SQLSRV_PORT=1433 +SQLSRV_DATABASE=migration +SQLSRV_USERNAME=sa +SQLSRV_PASSWORD=!QAZ2wsx diff --git a/.github/docker/sqlsrv/db-init.sh b/.github/docker/sqlsrv/db-init.sh new file mode 100755 index 00000000..e2837794 --- /dev/null +++ b/.github/docker/sqlsrv/db-init.sh @@ -0,0 +1,7 @@ +echo "Wait for the SQL Server to come up" +sleep 30s +echo "running set up script" +# Run the setup script to create the DB and the schema in the DB +/opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P $SA_PASSWORD -Q 'CREATE DATABASE migration' + +echo "DB created" diff --git a/.github/docker/sqlsrv/entrypoint.sh b/.github/docker/sqlsrv/entrypoint.sh new file mode 100644 index 00000000..06335b1b --- /dev/null +++ b/.github/docker/sqlsrv/entrypoint.sh @@ -0,0 +1,3 @@ +# Start SQL Server, start the script to create/setup the DB +chmod +x /db-init.sh +/db-init.sh & /opt/mssql/bin/sqlservr diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 602995c5..3b51f5f4 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -40,6 +40,14 @@ jobs: --health-timeout 5s --health-retries 5 + sqlsrv: + image: mcr.microsoft.com/mssql/server:2019-latest + env: + SA_PASSWORD: "!QAZ2wsx" + ACCEPT_EULA: "Y" + ports: + - 1433:1433 + strategy: matrix: php: [ 7.3, 7.4 ] @@ -71,16 +79,27 @@ jobs: mysql --version mysql --host 127.0.0.1 --port ${{ job.services.mysql8.ports['3306'] }} -u root -e "ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY ''" + - name: SQL Server Create Database + run: | + sleep 10s + + # Test connection + sqlcmd -S 127.0.0.1 -U sa -P '!QAZ2wsx' -Q 'SELECT 1' -b + + # Create DB + sqlcmd -S 127.0.0.1 -U sa -P '!QAZ2wsx' -Q 'CREATE DATABASE migration' -b + - name: Setup PHP uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php }} - extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, gd, redis, memcached + extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, gd, redis, memcached, pdo_sqlsrv tools: composer:v2 coverage: none - name: Install dependencies run: | + php -m composer require "laravel/framework:${{ matrix.laravel }}" "doctrine/dbal:${{ matrix.dbal }}" --no-interaction --no-update composer update --${{ matrix.stability }} --prefer-dist --no-interaction --no-progress diff --git a/tests/KitLoong/Feature/SQLSrv/CommandTest.php b/tests/KitLoong/Feature/SQLSrv/CommandTest.php new file mode 100644 index 00000000..aa672180 --- /dev/null +++ b/tests/KitLoong/Feature/SQLSrv/CommandTest.php @@ -0,0 +1,54 @@ +migrateGeneral('sqlsrv'); + }; + + $generateMigrations = function () { + $this->generateMigrations(); + }; + + $this->verify($migrateTemplates, $generateMigrations); + } + + public function testCollation() + { + $migrateTemplates = function () { + $this->migrateCollation('sqlsrv'); + }; + + $generateMigrations = function () { + $this->generateMigrations(['--followCollation' => true]); + }; + + $this->verify($migrateTemplates, $generateMigrations); + } + + public function verify(callable $migrateTemplates, callable $generateMigrations) + { + $migrateTemplates(); + + $this->truncateMigration(); + $this->dumpSchemaAs($this->storageSql('expected.sql')); + + $generateMigrations(); + + $this->dropAllTables(); + + $this->loadMigrationsFrom($this->storageMigrations()); + + $this->truncateMigration(); + $this->dumpSchemaAs($this->storageSql('actual.sql')); + + $this->assertFileEqualsIgnoringOrder( + $this->storageSql('expected.sql'), + $this->storageSql('actual.sql') + ); + } +} diff --git a/tests/KitLoong/Feature/SQLSrv/SQLSrvTestCase.php b/tests/KitLoong/Feature/SQLSrv/SQLSrvTestCase.php new file mode 100644 index 00000000..db18a313 --- /dev/null +++ b/tests/KitLoong/Feature/SQLSrv/SQLSrvTestCase.php @@ -0,0 +1,63 @@ +set('database.default', 'sqlsrv'); + $app['config']->set('database.connections.sqlsrv', [ + 'driver' => 'sqlsrv', + 'url' => env('DATABASE_URL'), + 'host' => env('SQLSRV_HOST'), + 'port' => env('SQLSRV_PORT'), + 'database' => env('SQLSRV_DATABASE'), + 'username' => env('SQLSRV_USERNAME'), + 'password' => env('SQLSRV_PASSWORD'), + 'charset' => 'utf8', + 'prefix' => '', + 'prefix_indexes' => true, + ]); + } + + protected function dumpSchemaAs(string $destination): void + { + $tables = Schema::connection('sqlsrv')->getConnection()->getDoctrineSchemaManager()->listTableNames(); + $sqls = []; + foreach ($tables as $table) { + $sqls[] = "EXEC sp_columns $table;"; + } + + $command = sprintf( + 'sqlcmd -S %s -U %s -P \'%s\' -d %s -Q "%s" -o "%s"', + config('database.connections.sqlsrv.host'), + config('database.connections.sqlsrv.username'), + config('database.connections.sqlsrv.password'), + config('database.connections.sqlsrv.database'), + implode('', $sqls), + $destination + ); + exec($command); + } + + protected function dropAllTables(): void + { + if (!$this->atLeastLaravel5Dot8()) { + $this->markTestSkipped(); + } + + $tables = Schema::connection('sqlsrv')->getConnection()->getDoctrineSchemaManager()->listTableNames(); + foreach ($tables as $table) { + Schema::drop($table); + } + } +} From a5a05bc887a5579abe34f4e07756b5508fc27f5a Mon Sep 17 00:00:00 2001 From: kitloong Date: Sun, 4 Jul 2021 23:49:04 +0800 Subject: [PATCH 57/65] Removed --- .github/docker/sqlsrv/db-init.sh | 7 ------- .github/docker/sqlsrv/entrypoint.sh | 3 --- 2 files changed, 10 deletions(-) delete mode 100755 .github/docker/sqlsrv/db-init.sh delete mode 100644 .github/docker/sqlsrv/entrypoint.sh diff --git a/.github/docker/sqlsrv/db-init.sh b/.github/docker/sqlsrv/db-init.sh deleted file mode 100755 index e2837794..00000000 --- a/.github/docker/sqlsrv/db-init.sh +++ /dev/null @@ -1,7 +0,0 @@ -echo "Wait for the SQL Server to come up" -sleep 30s -echo "running set up script" -# Run the setup script to create the DB and the schema in the DB -/opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P $SA_PASSWORD -Q 'CREATE DATABASE migration' - -echo "DB created" diff --git a/.github/docker/sqlsrv/entrypoint.sh b/.github/docker/sqlsrv/entrypoint.sh deleted file mode 100644 index 06335b1b..00000000 --- a/.github/docker/sqlsrv/entrypoint.sh +++ /dev/null @@ -1,3 +0,0 @@ -# Start SQL Server, start the script to create/setup the DB -chmod +x /db-init.sh -/db-init.sh & /opt/mssql/bin/sqlservr From ac1f88f024053c7cacea345aaad2d3fade6fc22c Mon Sep 17 00:00:00 2001 From: kitloong Date: Sun, 4 Jul 2021 23:49:56 +0800 Subject: [PATCH 58/65] Rename followCollation => useDBCollation --- .../Generators/Modifier/CharsetModifier.php | 2 +- .../Generators/Modifier/CollationModifier.php | 2 +- .../MigrationsGenerator/MigrateGenerateCommand.php | 4 ++-- .../MigrationsGeneratorSetting.php | 12 ++++++------ src/Xethron/MigrationsGenerator/Syntax/Table.php | 2 +- tests/KitLoong/Feature/MySQL57/CommandTest.php | 2 +- tests/KitLoong/Feature/MySQL8/CommandTest.php | 2 +- tests/KitLoong/Feature/PgSQL/CommandTest.php | 2 +- tests/KitLoong/Feature/SQLSrv/CommandTest.php | 2 +- 9 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/KitLoong/MigrationsGenerator/Generators/Modifier/CharsetModifier.php b/src/KitLoong/MigrationsGenerator/Generators/Modifier/CharsetModifier.php index e7a742bf..9a62b36a 100644 --- a/src/KitLoong/MigrationsGenerator/Generators/Modifier/CharsetModifier.php +++ b/src/KitLoong/MigrationsGenerator/Generators/Modifier/CharsetModifier.php @@ -18,7 +18,7 @@ public function __construct(Decorator $decorator) public function generate(string $tableName, Column $column): string { - if (app(MigrationsGeneratorSetting::class)->isFollowCollation()) { + if (app(MigrationsGeneratorSetting::class)->isUseDBCollation()) { $charset = $column->getPlatformOptions()['charset'] ?? null; if ($charset != null) { return $this->decorator->decorate( diff --git a/src/KitLoong/MigrationsGenerator/Generators/Modifier/CollationModifier.php b/src/KitLoong/MigrationsGenerator/Generators/Modifier/CollationModifier.php index f0657527..54fb101a 100644 --- a/src/KitLoong/MigrationsGenerator/Generators/Modifier/CollationModifier.php +++ b/src/KitLoong/MigrationsGenerator/Generators/Modifier/CollationModifier.php @@ -26,7 +26,7 @@ public function generate(string $tableName, Column $column): string // $setting = app(MigrationsGeneratorSetting::class); // $tableCollation = $setting->getSchema()->listTableDetails($tableName)->getOptions()['collation'] ?? null; - if (app(MigrationsGeneratorSetting::class)->isFollowCollation()) { + if (app(MigrationsGeneratorSetting::class)->isUseDBCollation()) { $collation = $column->getPlatformOptions()['collation'] ?? null; // if (!empty($column->getPlatformOptions()['collation'])) { // if ($columnCollation !== $tableCollation) { diff --git a/src/KitLoong/MigrationsGenerator/MigrateGenerateCommand.php b/src/KitLoong/MigrationsGenerator/MigrateGenerateCommand.php index f0e82983..e7fbbd77 100644 --- a/src/KitLoong/MigrationsGenerator/MigrateGenerateCommand.php +++ b/src/KitLoong/MigrationsGenerator/MigrateGenerateCommand.php @@ -26,7 +26,7 @@ class MigrateGenerateCommand extends GeneratorCommand {--i|ignore= : A list of Tables you wish to ignore, separated by a comma: users,posts,comments} {--p|path= : Where should the file be created?} {--tp|templatePath= : The location of the template for this generator} - {--followCollation : Follow db collations for migrations} + {--useDBCollation : Follow db collations for migrations} {--defaultIndexNames : Don\'t use db index names for migrations} {--defaultFKNames : Don\'t use db foreign key names for migrations}'; @@ -134,7 +134,7 @@ protected function setup(string $connection): void /** @var MigrationsGeneratorSetting $setting */ $setting = app(MigrationsGeneratorSetting::class); $setting->setConnection($connection); - $setting->setFollowCollation($this->option('followCollation')); + $setting->setUseDBCollation($this->option('useDBCollation')); $setting->setIgnoreIndexNames($this->option('defaultIndexNames')); $setting->setIgnoreForeignKeyNames($this->option('defaultFKNames')); } diff --git a/src/KitLoong/MigrationsGenerator/MigrationsGeneratorSetting.php b/src/KitLoong/MigrationsGenerator/MigrationsGeneratorSetting.php index 062f024a..a7465774 100644 --- a/src/KitLoong/MigrationsGenerator/MigrationsGeneratorSetting.php +++ b/src/KitLoong/MigrationsGenerator/MigrationsGeneratorSetting.php @@ -32,7 +32,7 @@ class MigrationsGeneratorSetting /** * @var boolean */ - private $followCollation; + private $useDBCollation; /** * @var boolean @@ -95,17 +95,17 @@ public function getPlatform(): string /** * @return bool */ - public function isFollowCollation(): bool + public function isUseDBCollation(): bool { - return $this->followCollation; + return $this->useDBCollation; } /** - * @param bool $followCollation + * @param bool $useDBCollation */ - public function setFollowCollation(bool $followCollation): void + public function setUseDBCollation(bool $useDBCollation): void { - $this->followCollation = $followCollation; + $this->useDBCollation = $useDBCollation; } /** diff --git a/src/Xethron/MigrationsGenerator/Syntax/Table.php b/src/Xethron/MigrationsGenerator/Syntax/Table.php index 077fb17e..112bfabd 100644 --- a/src/Xethron/MigrationsGenerator/Syntax/Table.php +++ b/src/Xethron/MigrationsGenerator/Syntax/Table.php @@ -74,7 +74,7 @@ protected function getTableCollation(string $tableName): array { $setting = app(MigrationsGeneratorSetting::class); if ($setting->getPlatform() === Platform::MYSQL) { - if ($setting->isFollowCollation()) { + if ($setting->isUseDBCollation()) { $tableCollation = $setting->getSchema()->listTableDetails($tableName)->getOptions()['collation']; $tableCharset = explode('_', $tableCollation)[0]; return [ diff --git a/tests/KitLoong/Feature/MySQL57/CommandTest.php b/tests/KitLoong/Feature/MySQL57/CommandTest.php index 07627345..685303e0 100644 --- a/tests/KitLoong/Feature/MySQL57/CommandTest.php +++ b/tests/KitLoong/Feature/MySQL57/CommandTest.php @@ -29,7 +29,7 @@ public function testCollation() }; $generateMigrations = function () { - $this->generateMigrations(['--followCollation' => true]); + $this->generateMigrations(['--useDBCollation' => true]); }; $this->verify($migrateTemplates, $generateMigrations); diff --git a/tests/KitLoong/Feature/MySQL8/CommandTest.php b/tests/KitLoong/Feature/MySQL8/CommandTest.php index dc82411b..9f8b44a5 100644 --- a/tests/KitLoong/Feature/MySQL8/CommandTest.php +++ b/tests/KitLoong/Feature/MySQL8/CommandTest.php @@ -30,7 +30,7 @@ public function testCollation() }; $generateMigrations = function () { - $this->generateMigrations(['--followCollation' => true]); + $this->generateMigrations(['--useDBCollation' => true]); }; $this->verify($migrateTemplates, $generateMigrations); diff --git a/tests/KitLoong/Feature/PgSQL/CommandTest.php b/tests/KitLoong/Feature/PgSQL/CommandTest.php index c9c37aad..070c2601 100644 --- a/tests/KitLoong/Feature/PgSQL/CommandTest.php +++ b/tests/KitLoong/Feature/PgSQL/CommandTest.php @@ -32,7 +32,7 @@ public function testCollation() }; $generateMigrations = function () { - $this->generateMigrations(['--followCollation' => true]); + $this->generateMigrations(['--useDBCollation' => true]); }; $this->verify($migrateTemplates, $generateMigrations); diff --git a/tests/KitLoong/Feature/SQLSrv/CommandTest.php b/tests/KitLoong/Feature/SQLSrv/CommandTest.php index aa672180..d2655fe8 100644 --- a/tests/KitLoong/Feature/SQLSrv/CommandTest.php +++ b/tests/KitLoong/Feature/SQLSrv/CommandTest.php @@ -24,7 +24,7 @@ public function testCollation() }; $generateMigrations = function () { - $this->generateMigrations(['--followCollation' => true]); + $this->generateMigrations(['--useDBCollation' => true]); }; $this->verify($migrateTemplates, $generateMigrations); From 3822b034f30b793446cc1a08aa528c062694c671 Mon Sep 17 00:00:00 2001 From: kitloong Date: Sun, 4 Jul 2021 23:52:37 +0800 Subject: [PATCH 59/65] Add option `useDBCollation` to generate migrations with existing charset and collation #23 --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 0df62b97..ced661a2 100644 --- a/README.md +++ b/README.md @@ -87,6 +87,7 @@ Run `php artisan help migrate:generate` for a list of options. |-t, --tables[=TABLES]|A list of Tables you wish to Generate Migrations for separated by a comma: users,posts,comments| |-i, --ignore[=IGNORE]|A list of Tables you wish to ignore, separated by a comma: users,posts,comments| |-p, --path[=PATH]|Where should the file be created?| +| --useDBCollation|Follow db collations for migrations| | --defaultIndexNames|Don't use db index names for migrations| | --defaultFKNames|Don't use db foreign key names for migrations| |-tp, --templatePath[=TEMPLATEPATH]|The location of the template for this generator| From 349cb7c0cd25d8ba8c8c37a361dcbe12c874c618 Mon Sep 17 00:00:00 2001 From: kitloong Date: Mon, 5 Jul 2021 21:06:31 +0800 Subject: [PATCH 60/65] Drop old test --- .../Generators/SchemaGeneratorTest.php | 311 ------------------ 1 file changed, 311 deletions(-) delete mode 100644 tests/KitLoong/MigrationsGenerator/Generators/SchemaGeneratorTest.php diff --git a/tests/KitLoong/MigrationsGenerator/Generators/SchemaGeneratorTest.php b/tests/KitLoong/MigrationsGenerator/Generators/SchemaGeneratorTest.php deleted file mode 100644 index eab627b5..00000000 --- a/tests/KitLoong/MigrationsGenerator/Generators/SchemaGeneratorTest.php +++ /dev/null @@ -1,311 +0,0 @@ -mock(MigrationsGeneratorSetting::class, function (MockInterface $mock) { - $this->mockShouldReceivedCustomType($mock); - - $mock->shouldReceive('getPlatform') - ->andReturn(Platform::POSTGRESQL) - ->once(); - - $this->mockShouldReceivedDoctrineType($mock); - - $mock->shouldReceive('getConnection->getDoctrineConnection->getSchemaManager') - ->andReturn(Mockery::mock(AbstractSchemaManager::class)) - ->once(); - }); - - $schemaGenerator = resolve(SchemaGenerator::class); - - $schemaGenerator->initialize(); - } - - /** - * @throws \Doctrine\DBAL\Exception - */ - public function testGetTables() - { - $schemaGenerator = resolve(SubSchemaGenerator::class); - - $schemaGenerator->mockSchema() - ->shouldReceive('listTableNames') - ->andReturn(['result']) - ->once(); - - $this->assertSame(['result'], $schemaGenerator->getTables()); - } - - /** - * @throws \Doctrine\DBAL\Exception - */ - public function testGetIndexes() - { - $mockIndexes = [Mockery::mock(Index::class)]; - - $this->mock(IndexGenerator::class, function (MockInterface $mock) use ($mockIndexes) { - $mock->shouldReceive('generate') - ->with('table', $mockIndexes, true) - ->andReturn(['result']) - ->once(); - }); - - $schemaGenerator = resolve(SubSchemaGenerator::class); - - $schemaGenerator->mockSchema() - ->shouldReceive('listTableIndexes') - ->with('table') - ->andReturn($mockIndexes) - ->once(); - - $this->mock(MigrationsGeneratorSetting::class, function (MockInterface $mock) { - $mock->shouldReceive('isIgnoreIndexNames') - ->andReturnTrue() - ->once(); - }); - - $this->assertSame( - ['result'], - $schemaGenerator->getIndexes('table') - ); - } - - /** - * @throws \Doctrine\DBAL\Exception - */ - public function testGetFields() - { - $mockColumns = [Mockery::mock(Column::class)]; - - $collection = collect(); - $this->mock(FieldGenerator::class, function (MockInterface $mock) use ($mockColumns, $collection) { - $mock->shouldReceive('generate') - ->with('table', $mockColumns, $collection) - ->andReturn(['result']) - ->once(); - }); - - $schemaGenerator = resolve(SubSchemaGenerator::class); - - $schemaGenerator->mockSchema() - ->shouldReceive('listTableColumns') - ->with('table') - ->andReturn($mockColumns) - ->once(); - - $this->assertSame( - ['result'], - $schemaGenerator->getFields('table', $collection) - ); - } - - /** - * @throws \Doctrine\DBAL\Exception - */ - public function testGetForeignKeyConstraints() - { - $mockForeignKeys = [Mockery::mock(ForeignKeyConstraint::class)]; - - $this->mock(ForeignKeyGenerator::class, function (MockInterface $mock) use ($mockForeignKeys) { - $mock->shouldReceive('generate') - ->with('table', $mockForeignKeys, true) - ->andReturn(['result']) - ->once(); - }); - - $schemaGenerator = resolve(SubSchemaGenerator::class); - - $schemaGenerator->mockSchema() - ->shouldReceive('listTableForeignKeys') - ->with('table') - ->andReturn($mockForeignKeys) - ->once(); - - $this->mock(MigrationsGeneratorSetting::class, function (MockInterface $mock) { - $mock->shouldReceive('isIgnoreForeignKeyNames') - ->andReturnTrue() - ->once(); - }); - - $this->assertSame( - ['result'], - $schemaGenerator->getForeignKeyConstraints('table') - ); - } - - /** - * @throws \Doctrine\DBAL\Exception - */ - public function testRegisterCustomDoctrineType() - { - $this->mock(MigrationsGeneratorSetting::class, function (MockInterface $mock) { - $mock->shouldReceive('getConnection->getDoctrineConnection->getDatabasePlatform->registerDoctrineTypeMapping') - ->with('inet', 'ipaddress') - ->twice(); - }); - - $schemaGenerator = resolve(SubSchemaGenerator::class); - - $schemaGenerator->registerCustomDoctrineType(IpAddressType::class, 'ipaddress', 'inet'); - - $this->assertSame(ColumnType::IP_ADDRESS, Type::getType('ipaddress')->getName()); - - // Register same type should not throw type exists exception - $schemaGenerator->registerCustomDoctrineType(IpAddressType::class, 'ipaddress', 'inet'); - } - - /** - * @throws \Doctrine\DBAL\Exception - */ - public function testAddNewDoctrineType() - { - $this->mock(MigrationsGeneratorSetting::class, function (MockInterface $mock) { - $mock->shouldReceive('getConnection->getDoctrineConnection->getDatabasePlatform->registerDoctrineTypeMapping') - ->with('inet', 'ipaddress') - ->once(); - }); - - resolve(SubSchemaGenerator::class)->addNewDoctrineType('inet', 'ipaddress'); - } - - private function mockShouldReceivedCustomType(MockInterface $mock) - { - foreach ($this->getTypes() as $type) { - $mock->shouldReceive('getConnection->getDoctrineConnection->getDatabasePlatform->registerDoctrineTypeMapping') - ->with($type[1], $type[0]) - ->once(); - } - } - - private function mockShouldReceivedDoctrineType(MockInterface $mock) - { - $types = [ - 'bit' => 'boolean', - 'json' => 'json', - - '_text' => 'text', - '_int4' => 'integer', - '_numeric' => 'float', - 'cidr' => 'string', - 'oid' => 'string', - ]; - - foreach ($types as $dbType => $doctrineType) { - $mock->shouldReceive('getConnection->getDoctrineConnection->getDatabasePlatform->registerDoctrineTypeMapping') - ->with($dbType, $doctrineType) - ->once(); - } - } - - /** - * @return string[][] - */ - private function getTypes(): array - { - return [ - DoubleType::class => ['double', 'double'], - EnumType::class => ['enum', 'enum'], - GeometryType::class => ['geometry', 'geometry'], - GeomCollectionType::class => ['geomcollection', 'geomcollection'], - GeometryCollectionType::class => ['geometrycollection', 'geometrycollection'], - LineStringType::class => ['linestring', 'linestring'], - LongTextType::class => ['longtext', 'longtext'], - MediumIntegerType::class => ['mediumint', 'mediumint'], - MediumTextType::class => ['mediumtext', 'mediumtext'], - MultiLineStringType::class => ['multilinestring', 'multilinestring'], - MultiPointType::class => ['multipoint', 'multipoint'], - MultiPolygonType::class => ['multipolygon', 'multipolygon'], - PointType::class => ['point', 'point'], - PolygonType::class => ['polygon', 'polygon'], - SetType::class => ['set', 'set'], - TimestampType::class => ['timestamp', 'timestamp'], - TinyIntegerType::class => ['tinyint', 'tinyint'], - UUIDType::class => ['uuid', 'uuid'], - YearType::class => ['year', 'year'], - - // Postgres types - GeographyType::class => ['geography', 'geography'], - IpAddressType::class => ['ipaddress', 'inet'], - JsonbType::class => ['jsonb', 'jsonb'], - MacAddressType::class => ['macaddress', 'macaddr'], - TimeTzType::class => ['timetz', 'timetz'], - TimestampTzType::class => ['timestamptz', 'timestamptz'] - ]; - } -} - - -// phpcs:ignore -class SubSchemaGenerator extends SchemaGenerator -{ - public function registerCustomDoctrineType(string $class, string $name, string $type): void - { - parent::registerCustomDoctrineType($class, $name, $type); - } - - public function addNewDoctrineType(string $type, string $name): void - { - parent::addNewDoctrineType($type, $name); - } - - /** - * @return AbstractSchemaManager|Mockery\LegacyMockInterface|MockInterface - */ - public function mockSchema() - { - return $this->schema = Mockery::mock(AbstractSchemaManager::class); - } -} From e13747f07c7f35835aac76414f383bf668718fec Mon Sep 17 00:00:00 2001 From: kitloong Date: Mon, 5 Jul 2021 21:06:54 +0800 Subject: [PATCH 61/65] Generate _int4 as text #37 --- src/KitLoong/MigrationsGenerator/Generators/SchemaGenerator.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/KitLoong/MigrationsGenerator/Generators/SchemaGenerator.php b/src/KitLoong/MigrationsGenerator/Generators/SchemaGenerator.php index a97b4405..4410ddfa 100644 --- a/src/KitLoong/MigrationsGenerator/Generators/SchemaGenerator.php +++ b/src/KitLoong/MigrationsGenerator/Generators/SchemaGenerator.php @@ -118,7 +118,7 @@ public function initialize() switch ($setting->getPlatform()) { case Platform::POSTGRESQL: $this->addNewDoctrineType('_text', 'text'); - $this->addNewDoctrineType('_int4', 'integer'); + $this->addNewDoctrineType('_int4', 'text'); $this->addNewDoctrineType('_numeric', 'float'); $this->addNewDoctrineType('cidr', 'string'); $this->addNewDoctrineType('oid', 'string'); From ee0c41a26cebe81827a176a83292e42f904d9a92 Mon Sep 17 00:00:00 2001 From: kitloong Date: Mon, 5 Jul 2021 22:01:46 +0800 Subject: [PATCH 62/65] Generate `now()` as `useCurrent()` #39 --- .../Generators/DatetimeField.php | 2 ++ tests/KitLoong/Feature/PgSQL/CommandTest.php | 36 +++++++++++++++++-- 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/src/KitLoong/MigrationsGenerator/Generators/DatetimeField.php b/src/KitLoong/MigrationsGenerator/Generators/DatetimeField.php index 3eb9bd23..5e41a69a 100644 --- a/src/KitLoong/MigrationsGenerator/Generators/DatetimeField.php +++ b/src/KitLoong/MigrationsGenerator/Generators/DatetimeField.php @@ -88,6 +88,8 @@ public function makeDefault(Column $column): string { if (in_array($column->getDefault(), ['CURRENT_TIMESTAMP'], true)) { return ColumnModifier::USE_CURRENT; + } elseif ($column->getDefault() === 'now()') { // For PgSQL + return ColumnModifier::USE_CURRENT; } else { $default = $this->decorator->columnDefaultToString($column->getDefault()); return $this->decorator->decorate(ColumnModifier::DEFAULT, [$default]); diff --git a/tests/KitLoong/Feature/PgSQL/CommandTest.php b/tests/KitLoong/Feature/PgSQL/CommandTest.php index 070c2601..3bcb45b4 100644 --- a/tests/KitLoong/Feature/PgSQL/CommandTest.php +++ b/tests/KitLoong/Feature/PgSQL/CommandTest.php @@ -7,6 +7,7 @@ namespace Tests\KitLoong\Feature\PgSQL; +use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\File; use Illuminate\Support\Str; @@ -16,13 +17,28 @@ public function testRun() { $migrateTemplates = function () { $this->migrateGeneral('pgsql'); + + // Test timestamp default now() + DB::statement("ALTER TABLE all_columns_pgsql ADD COLUMN timestamp_defaultnow timestamp(0) without time zone DEFAULT now() NOT NULL"); }; $generateMigrations = function () { $this->generateMigrations(); }; - $this->verify($migrateTemplates, $generateMigrations); + $beforeVerify = function () { + $this->assertLineExistsThenReplace( + $this->storageSql('actual.sql'), + 'timestamp_defaultnow timestamp(0) without time zone DEFAULT CURRENT_TIMESTAMP NOT NULL' + ); + + $this->assertLineExistsThenReplace( + $this->storageSql('expected.sql'), + 'timestamp_defaultnow timestamp(0) without time zone DEFAULT now() NOT NULL' + ); + }; + + $this->verify($migrateTemplates, $generateMigrations, $beforeVerify); } public function testCollation() @@ -38,7 +54,7 @@ public function testCollation() $this->verify($migrateTemplates, $generateMigrations); } - public function verify(callable $migrateTemplates, callable $generateMigrations) + public function verify(callable $migrateTemplates, callable $generateMigrations, callable $beforeVerify = null) { $migrateTemplates(); @@ -64,9 +80,25 @@ public function verify(callable $migrateTemplates, callable $generateMigrations) $this->truncateMigration(); $this->dumpSchemaAs($this->storageSql('actual.sql')); + $beforeVerify === null ?: $beforeVerify(); + $this->assertFileEqualsIgnoringOrder( $this->storageSql('expected.sql'), $this->storageSql('actual.sql') ); } + + private function assertLineExistsThenReplace(string $file, string $line) + { + $this->assertTrue(str_contains( + file_get_contents($file), + $line + )); + + File::put($file, str_replace( + $line, + 'replaced', + file_get_contents($file) + )); + } } From 34d4f8bf5c8b853a288ca2fb94e25882bf849eca Mon Sep 17 00:00:00 2001 From: kitloong Date: Tue, 6 Jul 2021 20:46:10 +0800 Subject: [PATCH 63/65] Sleep 5s --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 3b51f5f4..03dfe919 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -81,7 +81,7 @@ jobs: - name: SQL Server Create Database run: | - sleep 10s + sleep 5s # Test connection sqlcmd -S 127.0.0.1 -U sa -P '!QAZ2wsx' -Q 'SELECT 1' -b From 47b86057bb87346810653008bf265ae6071c6c87 Mon Sep 17 00:00:00 2001 From: kitloong Date: Tue, 6 Jul 2021 21:37:22 +0800 Subject: [PATCH 64/65] Removed old test --- .../Repositories/MySQLRepositoryTest.php | 84 ------------------- 1 file changed, 84 deletions(-) delete mode 100644 tests/KitLoong/MigrationsGenerator/Repositories/MySQLRepositoryTest.php diff --git a/tests/KitLoong/MigrationsGenerator/Repositories/MySQLRepositoryTest.php b/tests/KitLoong/MigrationsGenerator/Repositories/MySQLRepositoryTest.php deleted file mode 100644 index 7b608c28..00000000 --- a/tests/KitLoong/MigrationsGenerator/Repositories/MySQLRepositoryTest.php +++ /dev/null @@ -1,84 +0,0 @@ -mock(MigrationsGeneratorSetting::class, function (MockInterface $mock) { - $mock->shouldReceive('getConnection->select') - ->with("SHOW COLUMNS FROM `table` where Field = 'column' AND Type LIKE 'enum(%'") - ->andReturn([ - (object) ['Type' => "enum('value1', 'value2' , 'value3')"] - ]) - ->once(); - }); - - /** @var MySQLRepository $repository */ - $repository = app(MySQLRepository::class); - - $value = $repository->getEnumPresetValues('table', 'column'); - $this->assertSame("['value1', 'value2' , 'value3']", $value); - } - - public function testGetEnumPresetValuesIsNull() - { - $this->mock(MigrationsGeneratorSetting::class, function (MockInterface $mock) { - $mock->shouldReceive('getConnection->select') - ->with("SHOW COLUMNS FROM `table` where Field = 'column' AND Type LIKE 'enum(%'") - ->andReturn([]) - ->once(); - }); - - /** @var MySQLRepository $repository */ - $repository = app(MySQLRepository::class); - - $value = $repository->getEnumPresetValues('table', 'column'); - $this->assertNull($value); - } - - public function testGetSetPresetValues() - { - $this->mock(MigrationsGeneratorSetting::class, function (MockInterface $mock) { - $mock->shouldReceive('getConnection->select') - ->with("SHOW COLUMNS FROM `table` where Field = 'column' AND Type LIKE 'set(%'") - ->andReturn([ - (object) ['Type' => "set('value1', 'value2' , 'value3')"] - ]) - ->once(); - }); - - /** @var MySQLRepository $repository */ - $repository = app(MySQLRepository::class); - - $value = $repository->getSetPresetValues('table', 'column'); - $this->assertSame("['value1', 'value2' , 'value3']", $value); - } - - public function testGetSetPresetValuesIsNull() - { - $this->mock(MigrationsGeneratorSetting::class, function (MockInterface $mock) { - $mock->shouldReceive('getConnection->select') - ->with("SHOW COLUMNS FROM `table` where Field = 'column' AND Type LIKE 'set(%'") - ->andReturn([]) - ->once(); - }); - - /** @var MySQLRepository $repository */ - $repository = app(MySQLRepository::class); - - $value = $repository->getSetPresetValues('table', 'column'); - $this->assertNull($value); - } -} From dd5a468e16145cf54942faa43b683e8591a87be5 Mon Sep 17 00:00:00 2001 From: kitloong Date: Tue, 6 Jul 2021 21:37:54 +0800 Subject: [PATCH 65/65] Convert column property to case insensitive #34 --- .../Repositories/MySQLRepository.php | 19 ++-- .../Schema/MySQL/ShowColumn.php | 100 ++++++++++++++++++ 2 files changed, 112 insertions(+), 7 deletions(-) create mode 100644 src/KitLoong/MigrationsGenerator/Schema/MySQL/ShowColumn.php diff --git a/src/KitLoong/MigrationsGenerator/Repositories/MySQLRepository.php b/src/KitLoong/MigrationsGenerator/Repositories/MySQLRepository.php index 406469cd..a0b17c96 100644 --- a/src/KitLoong/MigrationsGenerator/Repositories/MySQLRepository.php +++ b/src/KitLoong/MigrationsGenerator/Repositories/MySQLRepository.php @@ -8,6 +8,7 @@ namespace KitLoong\MigrationsGenerator\Repositories; use KitLoong\MigrationsGenerator\MigrationsGeneratorSetting; +use KitLoong\MigrationsGenerator\Schema\MySQL\ShowColumn; class MySQLRepository extends Repository { @@ -21,7 +22,9 @@ public function getDatabaseCollation(): array { $setting = app(MigrationsGeneratorSetting::class); $columns = $setting->getConnection()->select("SELECT @@character_set_database, @@collation_database"); - return ['charset' => $columns[0]->{'@@character_set_database'}, 'collation' => $columns[0]->{'@@collation_database'}]; + return [ + 'charset' => $columns[0]->{'@@character_set_database'}, 'collation' => $columns[0]->{'@@collation_database'} + ]; } public function getEnumPresetValues(string $table, string $columnName): ?string @@ -29,10 +32,11 @@ public function getEnumPresetValues(string $table, string $columnName): ?string /** @var MigrationsGeneratorSetting $setting */ $setting = app(MigrationsGeneratorSetting::class); - $column = $setting->getConnection()->select("SHOW COLUMNS FROM `${table}` where Field = '${columnName}' AND Type LIKE 'enum(%'"); - if (count($column) > 0) { + $columns = $setting->getConnection()->select("SHOW COLUMNS FROM `${table}` where Field = '${columnName}' AND Type LIKE 'enum(%'"); + if (count($columns) > 0) { + $showColumn = new ShowColumn($columns[0]); return substr( - str_replace('enum(', '[', $this->spaceAfterComma($column[0]->Type)), + str_replace('enum(', '[', $this->spaceAfterComma($showColumn->getType())), 0, -1 ).']'; @@ -45,10 +49,11 @@ public function getSetPresetValues(string $table, string $columnName): ?string /** @var MigrationsGeneratorSetting $setting */ $setting = app(MigrationsGeneratorSetting::class); - $column = $setting->getConnection()->select("SHOW COLUMNS FROM `${table}` where Field = '${columnName}' AND Type LIKE 'set(%'"); - if (count($column) > 0) { + $columns = $setting->getConnection()->select("SHOW COLUMNS FROM `${table}` where Field = '${columnName}' AND Type LIKE 'set(%'"); + if (count($columns) > 0) { + $showColumn = new ShowColumn($columns[0]); return substr( - str_replace('set(', '[', $this->spaceAfterComma($column[0]->Type)), + str_replace('set(', '[', $this->spaceAfterComma($showColumn->getType())), 0, -1 ).']'; diff --git a/src/KitLoong/MigrationsGenerator/Schema/MySQL/ShowColumn.php b/src/KitLoong/MigrationsGenerator/Schema/MySQL/ShowColumn.php new file mode 100644 index 00000000..33e20584 --- /dev/null +++ b/src/KitLoong/MigrationsGenerator/Schema/MySQL/ShowColumn.php @@ -0,0 +1,100 @@ +mapWithKeys(function ($item, $key) { + return [strtolower($key) => $item]; + }); + + $this->field = $lowerKey['field']; + $this->type = $lowerKey['type']; + $this->null = $lowerKey['null']; + $this->key = $lowerKey['key']; + $this->default = $lowerKey['default']; + $this->extra = $lowerKey['extra']; + } + + /** + * @return string + */ + public function getField(): string + { + return $this->field; + } + + /** + * @return string + */ + public function getType(): string + { + return $this->type; + } + + /** + * @return string + */ + public function getNull(): string + { + return $this->null; + } + + /** + * @return string + */ + public function getKey(): string + { + return $this->key; + } + + /** + * @return string|null + */ + public function getDefault(): ?string + { + return $this->default; + } + + /** + * @return string + */ + public function getExtra(): string + { + return $this->extra; + } +}