├── LICENSE ├── README.md ├── composer.json ├── config └── migrations-generator.php ├── src ├── Database │ ├── DatabaseColumnType.php │ ├── DatabaseSchema.php │ ├── Models │ │ ├── Blueprint.php │ │ ├── DatabaseColumn.php │ │ ├── DatabaseForeignKey.php │ │ ├── DatabaseIndex.php │ │ ├── DatabaseProcedure.php │ │ ├── DatabaseTable.php │ │ ├── DatabaseUDTColumn.php │ │ ├── DatabaseView.php │ │ ├── MySQL │ │ │ ├── MySQLColumn.php │ │ │ ├── MySQLColumnType.php │ │ │ ├── MySQLForeignKey.php │ │ │ ├── MySQLIndex.php │ │ │ ├── MySQLProcedure.php │ │ │ ├── MySQLTable.php │ │ │ ├── MySQLUDTColumn.php │ │ │ └── MySQLView.php │ │ ├── PgSQL │ │ │ ├── PgSQLColumn.php │ │ │ ├── PgSQLColumnType.php │ │ │ ├── PgSQLForeignKey.php │ │ │ ├── PgSQLIndex.php │ │ │ ├── PgSQLParser.php │ │ │ ├── PgSQLProcedure.php │ │ │ ├── PgSQLTable.php │ │ │ ├── PgSQLUDTColumn.php │ │ │ └── PgSQLView.php │ │ ├── SQLSrv │ │ │ ├── SQLSrvColumn.php │ │ │ ├── SQLSrvColumnType.php │ │ │ ├── SQLSrvForeignKey.php │ │ │ ├── SQLSrvIndex.php │ │ │ ├── SQLSrvParser.php │ │ │ ├── SQLSrvProcedure.php │ │ │ ├── SQLSrvTable.php │ │ │ ├── SQLSrvUDTColumn.php │ │ │ └── SQLSrvView.php │ │ └── SQLite │ │ │ ├── SQLiteColumn.php │ │ │ ├── SQLiteColumnType.php │ │ │ ├── SQLiteForeignKey.php │ │ │ ├── SQLiteIndex.php │ │ │ ├── SQLiteTable.php │ │ │ ├── SQLiteUDTColumn.php │ │ │ └── SQLiteView.php │ ├── MySQLSchema.php │ ├── PgSQLSchema.php │ ├── SQLSrvSchema.php │ └── SQLiteSchema.php ├── Enum │ ├── Driver.php │ └── Migrations │ │ ├── ColumnName.php │ │ ├── Method │ │ ├── ColumnModifier.php │ │ ├── ColumnType.php │ │ ├── DBBuilder.php │ │ ├── Foreign.php │ │ ├── IndexType.php │ │ ├── MethodName.php │ │ ├── SchemaBuilder.php │ │ └── TableMethod.php │ │ └── Property │ │ ├── PropertyName.php │ │ └── TableProperty.php ├── MigrateGenerateCommand.php ├── Migration │ ├── Blueprint │ │ ├── DBStatementBlueprint.php │ │ ├── DBUnpreparedBlueprint.php │ │ ├── Method.php │ │ ├── Property.php │ │ ├── SchemaBlueprint.php │ │ ├── Support │ │ │ ├── MergeTimestamps.php │ │ │ ├── MethodStringHelper.php │ │ │ └── Stringable.php │ │ ├── TableBlueprint.php │ │ └── WritableBlueprint.php │ ├── Enum │ │ ├── MigrationFileType.php │ │ └── Space.php │ ├── ForeignKeyMigration.php │ ├── Generator │ │ ├── ColumnGenerator.php │ │ ├── Columns │ │ │ ├── BooleanColumn.php │ │ │ ├── ColumnTypeGenerator.php │ │ │ ├── DatetimeColumn.php │ │ │ ├── DecimalColumn.php │ │ │ ├── DoubleColumn.php │ │ │ ├── FloatColumn.php │ │ │ ├── IntegerColumn.php │ │ │ ├── MiscColumn.php │ │ │ ├── OmitNameColumn.php │ │ │ ├── PresetValuesColumn.php │ │ │ ├── SoftDeleteColumn.php │ │ │ ├── SpatialColumn.php │ │ │ └── StringColumn.php │ │ ├── ForeignKeyGenerator.php │ │ ├── IndexGenerator.php │ │ └── Modifiers │ │ │ ├── CharsetModifier.php │ │ │ ├── CollationModifier.php │ │ │ ├── CommentModifier.php │ │ │ ├── DefaultModifier.php │ │ │ ├── IndexModifier.php │ │ │ ├── Modifier.php │ │ │ ├── NullableModifier.php │ │ │ ├── StoredAsModifier.php │ │ │ └── VirtualAsModifier.php │ ├── Migrator │ │ └── Migrator.php │ ├── ProcedureMigration.php │ ├── Squash.php │ ├── TableMigration.php │ ├── ViewMigration.php │ └── Writer │ │ ├── MigrationStub.php │ │ ├── MigrationWriter.php │ │ └── SquashWriter.php ├── MigrationsGeneratorServiceProvider.php ├── Repositories │ ├── Entities │ │ ├── MariaDB │ │ │ └── CheckConstraint.php │ │ ├── MySQL │ │ │ └── ShowColumn.php │ │ ├── PgSQL │ │ │ └── IndexDefinition.php │ │ ├── ProcedureDefinition.php │ │ └── SQLSrv │ │ │ └── ColumnDefinition.php │ ├── MariaDBRepository.php │ ├── MySQLRepository.php │ ├── PgSQLRepository.php │ ├── Repository.php │ ├── SQLSrvRepository.php │ └── SQLiteRepository.php ├── Schema │ ├── Models │ │ ├── Column.php │ │ ├── ForeignKey.php │ │ ├── Index.php │ │ ├── Model.php │ │ ├── Procedure.php │ │ ├── Table.php │ │ ├── UDTColumn.php │ │ └── View.php │ ├── MySQLSchema.php │ ├── PgSQLSchema.php │ ├── SQLSrvSchema.php │ ├── SQLiteSchema.php │ └── Schema.php ├── Setting.php └── Support │ ├── AssetNameQuote.php │ ├── CheckLaravelVersion.php │ ├── IndexNameHelper.php │ ├── MigrationNameHelper.php │ ├── Regex.php │ └── TableName.php └── stubs └── migration.generate.stub /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2020 Kit Loong Liow 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://getcomposer.org/schema.json", 3 | "name": "kitloong/laravel-migrations-generator", 4 | "description": "Generates Laravel Migrations from an existing database", 5 | "keywords": [ 6 | "laravel", 7 | "lumen", 8 | "migration", 9 | "generator", 10 | "migrations", 11 | "artisan" 12 | ], 13 | "license": "MIT", 14 | "authors": [ 15 | { 16 | "name": "Kit Loong", 17 | "email": "kitloong1008@gmail.com" 18 | } 19 | ], 20 | "require": { 21 | "php": "^8.2", 22 | "illuminate/support": "^11.0|^12.0", 23 | "ext-pdo": "*" 24 | }, 25 | "require-dev": { 26 | "barryvdh/laravel-ide-helper": "^3.0", 27 | "friendsofphp/php-cs-fixer": "^3.1", 28 | "larastan/larastan": "^2.0|^3.0", 29 | "mockery/mockery": "^1.0", 30 | "orchestra/testbench": "^9.0|^10.0", 31 | "phpmd/phpmd": "^2.10", 32 | "phpstan/phpstan-mockery": "^1.0|^2.0", 33 | "slevomat/coding-standard": "^8.0", 34 | "squizlabs/php_codesniffer": "^3.5" 35 | }, 36 | "autoload": { 37 | "psr-4": { 38 | "KitLoong\\MigrationsGenerator\\": "src" 39 | } 40 | }, 41 | "autoload-dev": { 42 | "psr-4": { 43 | "KitLoong\\MigrationsGenerator\\Tests\\": "tests" 44 | } 45 | }, 46 | "extra": { 47 | "laravel": { 48 | "providers": [ 49 | "KitLoong\\MigrationsGenerator\\MigrationsGeneratorServiceProvider" 50 | ] 51 | } 52 | }, 53 | "minimum-stability": "dev", 54 | "prefer-stable": true, 55 | "scripts": { 56 | "action-env-setup": [ 57 | "@php -r \"file_exists('phpunit.xml') || copy('phpunit-action.xml', 'phpunit.xml');\"" 58 | ], 59 | "phpcs": [ 60 | "phpcs" 61 | ], 62 | "phpmd": [ 63 | "phpmd \"src,tests\" xml .phpmd.xml" 64 | ], 65 | "phpstan": [ 66 | "phpstan analyse" 67 | ] 68 | }, 69 | "config": { 70 | "sort-packages": true, 71 | "allow-plugins": { 72 | "dealerdirect/phpcodesniffer-composer-installer": true 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /config/migrations-generator.php: -------------------------------------------------------------------------------- 1 | __DIR__ . '/../stubs/migration.generate.stub', 6 | 7 | // Where the generated files will be saved. 8 | 'migration_target_path' => base_path('database/migrations'), 9 | 10 | // Migration filename pattern. 11 | 'filename_pattern' => [ 12 | 'table' => '[datetime]_create_[name]_table.php', 13 | 'view' => '[datetime]_create_[name]_view.php', 14 | 'procedure' => '[datetime]_create_[name]_proc.php', 15 | 'foreign_key' => '[datetime]_add_foreign_keys_to_[name]_table.php', 16 | ], 17 | ]; 18 | -------------------------------------------------------------------------------- /src/Database/DatabaseColumnType.php: -------------------------------------------------------------------------------- 1 | $map 16 | */ 17 | protected static function mapToColumnType(array $map, string $dbType): ColumnType 18 | { 19 | return $map[strtolower($dbType)] ?? ColumnType::STRING; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Database/Models/Blueprint.php: -------------------------------------------------------------------------------- 1 | atLeastLaravel12()) { 16 | parent::__construct(Schema::getConnection(), $table); 17 | 18 | return; 19 | } 20 | 21 | parent::__construct($table); // @phpstan-ignore-line 22 | } 23 | 24 | /** 25 | * @return string[] 26 | */ 27 | public function toSqlWithCompatible(): array 28 | { 29 | if ($this->atLeastLaravel12()) { 30 | return parent::toSql(); 31 | } 32 | 33 | return parent::toSql(Schema::getConnection(), Schema::getConnection()->getSchemaGrammar()); // @phpstan-ignore-line 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Database/Models/DatabaseForeignKey.php: -------------------------------------------------------------------------------- 1 | tableName = $table; 38 | $this->name = $foreignKey['name']; 39 | $this->localColumns = $foreignKey['columns']; 40 | $this->foreignColumns = $foreignKey['foreign_columns']; 41 | $this->foreignTableName = $foreignKey['foreign_table']; 42 | $this->onUpdate = $foreignKey['on_update']; 43 | $this->onDelete = $foreignKey['on_delete']; 44 | } 45 | 46 | /** 47 | * @inheritDoc 48 | */ 49 | public function getName(): ?string 50 | { 51 | return $this->name; 52 | } 53 | 54 | /** 55 | * @inheritDoc 56 | */ 57 | public function getTableName(): string 58 | { 59 | return $this->tableName; 60 | } 61 | 62 | /** 63 | * @inheritDoc 64 | */ 65 | public function getLocalColumns(): array 66 | { 67 | return $this->localColumns; 68 | } 69 | 70 | /** 71 | * @inheritDoc 72 | */ 73 | public function getForeignColumns(): array 74 | { 75 | return $this->foreignColumns; 76 | } 77 | 78 | /** 79 | * @inheritDoc 80 | */ 81 | public function getForeignTableName(): string 82 | { 83 | return $this->foreignTableName; 84 | } 85 | 86 | /** 87 | * @inheritDoc 88 | */ 89 | public function getOnUpdate(): ?string 90 | { 91 | return $this->onUpdate; 92 | } 93 | 94 | /** 95 | * @inheritDoc 96 | */ 97 | public function getOnDelete(): ?string 98 | { 99 | return $this->onDelete; 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/Database/Models/DatabaseIndex.php: -------------------------------------------------------------------------------- 1 | tableName = $table; 37 | $this->name = $index['name']; 38 | $this->columns = $index['columns']; 39 | $this->type = $this->getIndexType($index); 40 | $this->udtColumnSqls = []; 41 | } 42 | 43 | /** 44 | * @inheritDoc 45 | */ 46 | public function getName(): string 47 | { 48 | return $this->name; 49 | } 50 | 51 | /** 52 | * @inheritDoc 53 | */ 54 | public function getTableName(): string 55 | { 56 | return $this->tableName; 57 | } 58 | 59 | /** 60 | * @inheritDoc 61 | */ 62 | public function getColumns(): array 63 | { 64 | return $this->columns; 65 | } 66 | 67 | /** 68 | * @inheritDoc 69 | */ 70 | public function getType(): IndexType 71 | { 72 | return $this->type; 73 | } 74 | 75 | /** 76 | * @inheritDoc 77 | */ 78 | public function getUDTColumnSqls(): array 79 | { 80 | return $this->udtColumnSqls; 81 | } 82 | 83 | /** 84 | * @inheritDoc 85 | */ 86 | public function hasUDTColumn(): bool 87 | { 88 | return count($this->udtColumnSqls) > 0; 89 | } 90 | 91 | /** 92 | * Get the index type. 93 | * 94 | * @param SchemaIndex $index 95 | */ 96 | private function getIndexType(array $index): IndexType 97 | { 98 | if ($index['primary'] === true) { 99 | return IndexType::PRIMARY; 100 | } 101 | 102 | if ($index['unique'] === true) { 103 | return IndexType::UNIQUE; 104 | } 105 | 106 | // pgsql uses gist 107 | if ($index['type'] === 'spatial' || $index['type'] === 'gist') { 108 | return IndexType::SPATIAL_INDEX; 109 | } 110 | 111 | // pgsql uses gin 112 | if ($index['type'] === 'fulltext' || $index['type'] === 'gin') { 113 | return IndexType::FULLTEXT; 114 | } 115 | 116 | return IndexType::INDEX; 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/Database/Models/DatabaseProcedure.php: -------------------------------------------------------------------------------- 1 | dropDefinition = "DROP PROCEDURE IF EXISTS $name"; 14 | } 15 | 16 | /** 17 | * @inheritDoc 18 | */ 19 | public function getName(): string 20 | { 21 | return $this->name; 22 | } 23 | 24 | /** 25 | * @inheritDoc 26 | */ 27 | public function getDefinition(): string 28 | { 29 | return $this->definition; 30 | } 31 | 32 | /** 33 | * @inheritDoc 34 | */ 35 | public function getDropDefinition(): string 36 | { 37 | return $this->dropDefinition; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Database/Models/DatabaseTable.php: -------------------------------------------------------------------------------- 1 | 22 | */ 23 | protected Collection $columns; 24 | 25 | /** 26 | * @var \Illuminate\Support\Collection 27 | */ 28 | protected Collection $udtColumns; 29 | 30 | protected string $name; 31 | 32 | protected ?string $comment = null; 33 | 34 | /** 35 | * @var \Illuminate\Support\Collection 36 | */ 37 | protected Collection $indexes; 38 | 39 | /** 40 | * Make a Column instance. 41 | * 42 | * @param SchemaColumn $column 43 | */ 44 | abstract protected function makeColumn(string $table, array $column): Column; 45 | 46 | /** 47 | * Make a UDTColumn instance. 48 | * 49 | * @param SchemaColumn $column 50 | */ 51 | abstract protected function makeUDTColumn(string $table, array $column): UDTColumn; 52 | 53 | /** 54 | * Make an Index instance. 55 | * 56 | * @param SchemaIndex $index 57 | */ 58 | abstract protected function makeIndex(string $table, array $index, bool $hasUDTColumn): Index; 59 | 60 | /** 61 | * Create a new instance. 62 | * 63 | * @param SchemaTable $table 64 | * @param \Illuminate\Support\Collection $columns 65 | * @param \Illuminate\Support\Collection $indexes 66 | * @param \Illuminate\Support\Collection $userDefinedTypes 67 | */ 68 | public function __construct(array $table, Collection $columns, Collection $indexes, Collection $userDefinedTypes) 69 | { 70 | $this->name = $table['name']; 71 | $this->comment = $table['comment']; 72 | $this->collation = $table['collation']; 73 | 74 | $this->columns = $columns->reduce(function (Collection $columns, array $column) use ($userDefinedTypes) { 75 | if (!$userDefinedTypes->contains($column['type_name'])) { 76 | $columns->push($this->makeColumn($this->name, $column)); 77 | } 78 | 79 | return $columns; 80 | }, new Collection())->values(); 81 | 82 | $this->udtColumns = $columns->reduce(function (Collection $columns, array $column) use ($userDefinedTypes) { 83 | if ($userDefinedTypes->contains($column['type_name'])) { 84 | $columns->push($this->makeUDTColumn($this->name, $column)); 85 | } 86 | 87 | return $columns; 88 | }, new Collection())->values(); 89 | 90 | $this->indexes = $indexes->map(function (array $index) { 91 | $hasUdtColumn = $this->udtColumns 92 | ->map(static fn ($column) => $column->getName()) 93 | ->intersect($index['columns']) 94 | ->isNotEmpty(); 95 | 96 | return $this->makeIndex($this->name, $index, $hasUdtColumn); 97 | })->values(); 98 | } 99 | 100 | /** 101 | * @inheritDoc 102 | */ 103 | public function getName(): string 104 | { 105 | return $this->name; 106 | } 107 | 108 | /** 109 | * @inheritDoc 110 | */ 111 | public function getComment(): ?string 112 | { 113 | return $this->comment; 114 | } 115 | 116 | /** 117 | * @inheritDoc 118 | */ 119 | public function getColumns(): Collection 120 | { 121 | return $this->columns; 122 | } 123 | 124 | /** 125 | * @inheritDoc 126 | */ 127 | public function getUdtColumns(): Collection 128 | { 129 | return $this->udtColumns; 130 | } 131 | 132 | /** 133 | * @inheritDoc 134 | */ 135 | public function getIndexes(): Collection 136 | { 137 | return $this->indexes; 138 | } 139 | 140 | /** 141 | * @inheritDoc 142 | */ 143 | public function getCollation(): ?string 144 | { 145 | return $this->collation; 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /src/Database/Models/DatabaseUDTColumn.php: -------------------------------------------------------------------------------- 1 | name = $column['name']; 27 | $this->tableName = $table; 28 | } 29 | 30 | /** 31 | * @inheritDoc 32 | */ 33 | public function getName(): string 34 | { 35 | return $this->name; 36 | } 37 | 38 | /** 39 | * @inheritDoc 40 | */ 41 | public function getTableName(): string 42 | { 43 | return $this->tableName; 44 | } 45 | 46 | /** 47 | * @inheritDoc 48 | */ 49 | public function getSqls(): array 50 | { 51 | return $this->sqls; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/Database/Models/DatabaseView.php: -------------------------------------------------------------------------------- 1 | name = $view['name']; 29 | $this->quotedName = $this->quoteIdentifier($view['name']); 30 | $this->definition = ''; 31 | $this->dropDefinition = "DROP VIEW IF EXISTS $this->quotedName"; 32 | } 33 | 34 | /** 35 | * @inheritDoc 36 | */ 37 | public function getName(): string 38 | { 39 | return $this->name; 40 | } 41 | 42 | /** 43 | * @inheritDoc 44 | */ 45 | public function getDefinition(): string 46 | { 47 | return $this->definition; 48 | } 49 | 50 | /** 51 | * @inheritDoc 52 | */ 53 | public function getDropDefinition(): string 54 | { 55 | return $this->dropDefinition; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/Database/Models/MySQL/MySQLColumnType.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | protected static array $map = [ 14 | 'bigint' => ColumnType::BIG_INTEGER, 15 | 'binary' => ColumnType::BINARY, 16 | 'bit' => ColumnType::BOOLEAN, 17 | 'blob' => ColumnType::BINARY, 18 | 'char' => ColumnType::CHAR, 19 | 'date' => ColumnType::DATE, 20 | 'datetime' => ColumnType::DATETIME, 21 | 'decimal' => ColumnType::DECIMAL, 22 | 'double' => ColumnType::DOUBLE, 23 | 'enum' => ColumnType::ENUM, 24 | 'float' => ColumnType::FLOAT, 25 | 'geography' => ColumnType::GEOGRAPHY, 26 | 'geometry' => ColumnType::GEOMETRY, 27 | 'int' => ColumnType::INTEGER, 28 | 'integer' => ColumnType::INTEGER, 29 | 'json' => ColumnType::JSON, 30 | 'longblob' => ColumnType::BINARY, 31 | 'longtext' => ColumnType::LONG_TEXT, 32 | 'mediumblob' => ColumnType::BINARY, 33 | 'mediumint' => ColumnType::MEDIUM_INTEGER, 34 | 'mediumtext' => ColumnType::MEDIUM_TEXT, 35 | 'numeric' => ColumnType::DECIMAL, 36 | 'real' => ColumnType::FLOAT, 37 | 'set' => ColumnType::SET, 38 | 'smallint' => ColumnType::SMALL_INTEGER, 39 | 'string' => ColumnType::STRING, 40 | 'text' => ColumnType::TEXT, 41 | 'time' => ColumnType::TIME, 42 | 'timestamp' => ColumnType::TIMESTAMP, 43 | 'tinyblob' => ColumnType::BINARY, 44 | 'tinyint' => ColumnType::TINY_INTEGER, 45 | 'tinytext' => ColumnType::TINY_TEXT, 46 | 'varbinary' => ColumnType::BINARY, 47 | 'varchar' => ColumnType::STRING, 48 | 'year' => ColumnType::YEAR, 49 | 50 | 'geomcollection' => ColumnType::GEOMETRY, 51 | 'linestring' => ColumnType::GEOMETRY, 52 | 'multilinestring' => ColumnType::GEOMETRY, 53 | 'point' => ColumnType::GEOMETRY, 54 | 'multipoint' => ColumnType::GEOMETRY, 55 | 'polygon' => ColumnType::GEOMETRY, 56 | 'multipolygon' => ColumnType::GEOMETRY, 57 | 58 | // For MariaDB 59 | 'uuid' => ColumnType::UUID, 60 | 61 | // For MariaDB and MySQL57 62 | 'geometrycollection' => ColumnType::GEOMETRY, 63 | ]; 64 | 65 | /** 66 | * @inheritDoc 67 | */ 68 | public static function toColumnType(string $dbType): ColumnType 69 | { 70 | return self::mapToColumnType(self::$map, $dbType); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/Database/Models/MySQL/MySQLForeignKey.php: -------------------------------------------------------------------------------- 1 | type) { 18 | case IndexType::PRIMARY: 19 | // Reset name to empty to indicate use the database platform naming. 20 | $this->name = ''; 21 | break; 22 | 23 | default: 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Database/Models/MySQL/MySQLProcedure.php: -------------------------------------------------------------------------------- 1 | definition = 'CREATE VIEW ' . $this->quotedName . ' AS ' . $view['definition']; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Database/Models/PgSQL/PgSQLColumn.php: -------------------------------------------------------------------------------- 1 | default = $this->parseDefault($column['default'], $this->type); 25 | $this->default = $this->escapeDefault($this->default); 26 | 27 | $this->repository = app(PgSQLRepository::class); 28 | 29 | $this->setTypeToIncrements(false); 30 | 31 | switch ($this->type) { 32 | case ColumnType::DATE: 33 | case ColumnType::DATETIME: 34 | case ColumnType::DATETIME_TZ: 35 | case ColumnType::TIME: 36 | case ColumnType::TIME_TZ: 37 | case ColumnType::TIMESTAMP: 38 | case ColumnType::TIMESTAMP_TZ: 39 | case ColumnType::SOFT_DELETES: 40 | case ColumnType::SOFT_DELETES_TZ: 41 | $this->setRawDefault(); 42 | break; 43 | 44 | case ColumnType::GEOGRAPHY: 45 | case ColumnType::GEOMETRY: 46 | $this->setRealSpatialColumn($column['type']); 47 | break; 48 | 49 | case ColumnType::STRING: 50 | $this->presetValues = $this->getEnumPresetValues(); 51 | 52 | if (count($this->presetValues) > 0) { 53 | $this->type = ColumnType::ENUM; 54 | } 55 | 56 | break; 57 | 58 | default: 59 | } 60 | } 61 | 62 | /** 63 | * @inheritDoc 64 | */ 65 | protected function getColumnType(string $type): ColumnType 66 | { 67 | return PgSQLColumnType::toColumnType($type); 68 | } 69 | 70 | /** 71 | * @inheritDoc 72 | */ 73 | protected function escapeDefault(?string $default): ?string 74 | { 75 | if ($default === null) { 76 | return null; 77 | } 78 | 79 | if (preg_match('/\((.?)\)\)/', $default)) { 80 | return $default; 81 | } 82 | 83 | return parent::escapeDefault($default); 84 | } 85 | 86 | protected function setTypeToIncrements(bool $supportUnsigned): void 87 | { 88 | parent::setTypeToIncrements($supportUnsigned); 89 | 90 | if ($this->default === null) { 91 | return; 92 | } 93 | 94 | if (!Str::startsWith($this->default, 'nextval(') || !Str::endsWith($this->default, '::regclass)')) { 95 | return; 96 | } 97 | 98 | $this->default = null; 99 | } 100 | 101 | /** 102 | * Check and set to use raw default. 103 | * Raw default will be generated with DB::raw(). 104 | */ 105 | private function setRawDefault(): void 106 | { 107 | if ($this->default === null) { 108 | return; 109 | } 110 | 111 | if ($this->default === 'now()') { 112 | $this->rawDefault = true; 113 | return; 114 | } 115 | 116 | // If default value is expression, eg: timezone('Europe/Rome'::text, now()) 117 | if (!preg_match('/\((.?)\)/', $this->default)) { 118 | return; 119 | } 120 | 121 | $this->rawDefault = true; 122 | } 123 | 124 | /** 125 | * Set to geometry type base on geography map. 126 | */ 127 | private function setRealSpatialColumn(string $fullDefinitionType): void 128 | { 129 | $dataType = strtolower($fullDefinitionType); 130 | $dataType = preg_replace('/\s+/', '', $dataType); 131 | 132 | if ($dataType === null) { 133 | return; 134 | } 135 | 136 | $dotPosition = Str::position($dataType, '.'); 137 | 138 | if ($dotPosition !== false) { 139 | $dataType = Str::substr($dataType, $dotPosition + 1); 140 | } 141 | 142 | if ($dataType === 'geography' || $dataType === 'geometry') { 143 | return; 144 | } 145 | 146 | if (!preg_match('/(\w+)(?:\((\w+)(?:,\s*(\w+))?\))?/', $dataType, $matches) || !isset($matches[2])) { 147 | return; 148 | } 149 | 150 | $spatialSubType = $matches[2]; 151 | $spatialSrID = isset($matches[3]) ? (int) $matches[3] : null; 152 | 153 | $this->spatialSubType = $spatialSubType; 154 | $this->spatialSrID = $spatialSrID; 155 | } 156 | 157 | /** 158 | * Get the preset values if the column is "enum". 159 | * 160 | * @return string[] 161 | */ 162 | private function getEnumPresetValues(): array 163 | { 164 | $definition = $this->repository->getCheckConstraintDefinition($this->tableName, $this->name); 165 | 166 | if ($definition === null) { 167 | return []; 168 | } 169 | 170 | if ($definition === '') { 171 | return []; 172 | } 173 | 174 | $presetValues = Regex::getTextBetweenAll($definition, "'", "'::"); 175 | 176 | if ($presetValues === null) { 177 | return []; 178 | } 179 | 180 | return $presetValues; 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /src/Database/Models/PgSQL/PgSQLColumnType.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | protected static array $map = [ 14 | '_text' => ColumnType::TEXT, 15 | '_varchar' => ColumnType::STRING, 16 | 'bigint' => ColumnType::BIG_INTEGER, 17 | 'bigserial' => ColumnType::BIG_INTEGER, 18 | 'bool' => ColumnType::BOOLEAN, 19 | 'boolean' => ColumnType::BOOLEAN, 20 | 'bpchar' => ColumnType::CHAR, 21 | 'bytea' => ColumnType::BINARY, 22 | 'char' => ColumnType::CHAR, 23 | 'date' => ColumnType::DATE, 24 | 'datetime' => ColumnType::DATETIME, 25 | 'decimal' => ColumnType::DECIMAL, 26 | 'double precision' => ColumnType::FLOAT, 27 | 'double' => ColumnType::FLOAT, 28 | 'float' => ColumnType::FLOAT, 29 | 'float4' => ColumnType::FLOAT, 30 | 'float8' => ColumnType::FLOAT, 31 | 'geography' => ColumnType::GEOGRAPHY, 32 | 'geometry' => ColumnType::GEOMETRY, 33 | 'inet' => ColumnType::IP_ADDRESS, 34 | 'int' => ColumnType::INTEGER, 35 | 'int2' => ColumnType::SMALL_INTEGER, 36 | 'int4' => ColumnType::INTEGER, 37 | 'int8' => ColumnType::BIG_INTEGER, 38 | 'integer' => ColumnType::INTEGER, 39 | 'interval' => ColumnType::STRING, 40 | 'json' => ColumnType::JSON, 41 | 'jsonb' => ColumnType::JSONB, 42 | 'macaddr' => ColumnType::MAC_ADDRESS, 43 | 'money' => ColumnType::DECIMAL, 44 | 'numeric' => ColumnType::DECIMAL, 45 | 'real' => ColumnType::FLOAT, 46 | 'serial' => ColumnType::INTEGER, 47 | 'serial4' => ColumnType::INTEGER, 48 | 'serial8' => ColumnType::BIG_INTEGER, 49 | 'smallint' => ColumnType::SMALL_INTEGER, 50 | 'text' => ColumnType::TEXT, 51 | 'time' => ColumnType::TIME, 52 | 'timestamp' => ColumnType::TIMESTAMP, 53 | 'timestamptz' => ColumnType::TIMESTAMP_TZ, 54 | 'timetz' => ColumnType::TIME_TZ, 55 | 'tsvector' => ColumnType::TEXT, 56 | 'uuid' => ColumnType::UUID, 57 | 'varchar' => ColumnType::STRING, 58 | 'year' => ColumnType::INTEGER, 59 | ]; 60 | 61 | /** 62 | * @inheritDoc 63 | */ 64 | public static function toColumnType(string $dbType): ColumnType 65 | { 66 | return self::mapToColumnType(self::$map, $dbType); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/Database/Models/PgSQL/PgSQLForeignKey.php: -------------------------------------------------------------------------------- 1 | type) { 22 | case IndexType::PRIMARY: 23 | // Reset name to empty to indicate use the database platform naming. 24 | $this->name = ''; 25 | break; 26 | 27 | default: 28 | } 29 | 30 | if (!$hasUDTColumn) { 31 | return; 32 | } 33 | 34 | $blueprint = new Blueprint($this->stripTablePrefix($table)); 35 | 36 | // Generate the alter index statement. 37 | $blueprint->{$this->type->value}($this->columns, $this->name); 38 | 39 | $this->udtColumnSqls = $blueprint->toSqlWithCompatible(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Database/Models/PgSQL/PgSQLParser.php: -------------------------------------------------------------------------------- 1 | repository = app(PgSQLRepository::class); 26 | 27 | $this->updateFulltextIndexes(); 28 | 29 | $this->indexes = $this->indexes->sortBy(static fn (Index $index) => $index->getName())->values(); 30 | } 31 | 32 | /** 33 | * @inheritDoc 34 | */ 35 | protected function makeColumn(string $table, array $column): Column 36 | { 37 | return new PgSQLColumn($table, $column); 38 | } 39 | 40 | /** 41 | * @inheritDoc 42 | */ 43 | protected function makeUDTColumn(string $table, array $column): UDTColumn 44 | { 45 | return new PgSQLUDTColumn($table, $column); 46 | } 47 | 48 | /** 49 | * @inheritDoc 50 | */ 51 | protected function makeIndex(string $table, array $index, bool $hasUDTColumn): Index 52 | { 53 | return new PgSQLIndex($table, $index, $hasUDTColumn); 54 | } 55 | 56 | /** 57 | * The fulltext index column is empty by default. 58 | * This method query the DB to get the fulltext index columns and update the indexes collection. 59 | */ 60 | private function updateFulltextIndexes(): void 61 | { 62 | $tableName = $this->name; 63 | 64 | // Get fulltext indexes. 65 | $fulltextIndexes = $this->repository->getFulltextIndexes($tableName) 66 | ->keyBy(static fn (IndexDefinition $indexDefinition) => $indexDefinition->getIndexName()); 67 | 68 | $this->indexes = $this->indexes->map(static function (Index $index) use ($fulltextIndexes, $tableName) { 69 | if (!($index->getType() === IndexType::FULLTEXT)) { 70 | return $index; 71 | } 72 | 73 | if (!$fulltextIndexes->has($index->getName())) { 74 | return $index; 75 | } 76 | 77 | preg_match_all('/to_tsvector\((.*), \((.*)\)::text/U', $fulltextIndexes->get($index->getName())?->getIndexDef() ?? '', $matches); 78 | 79 | $columns = $matches[2]; 80 | 81 | return new PgSQLIndex( 82 | $tableName, 83 | [ 84 | 'name' => $index->getName(), 85 | 'columns' => $columns, 86 | 'type' => 'gin', 87 | 'unique' => false, 88 | 'primary' => false, 89 | ], 90 | false, 91 | ); 92 | }); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/Database/Models/PgSQL/PgSQLUDTColumn.php: -------------------------------------------------------------------------------- 1 | stripTablePrefix($table)); 25 | 26 | // Generate the add column statement with string column type. 27 | $blueprint->addColumn('string', $column['name'], [ 28 | 'autoIncrement' => $column['auto_increment'], 29 | 'collation' => $column['collation'], 30 | 'comment' => $column['comment'], 31 | 'default' => $this->parseDefault($column['default'], ColumnType::STRING), // Assume is string 32 | 'nullable' => $column['nullable'], 33 | ]); 34 | 35 | $sqls = $blueprint->toSqlWithCompatible(); 36 | 37 | // Replace the string column type with the user-defined type. 38 | $sqls[0] = Str::replaceFirst(' varchar ', ' ' . $column['type'] . ' ', $sqls[0]); 39 | 40 | $this->sqls = $sqls; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Database/Models/PgSQL/PgSQLView.php: -------------------------------------------------------------------------------- 1 | definition = 'CREATE VIEW ' . $this->quotedName . ' AS ' . trim($view['definition']); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Database/Models/SQLSrv/SQLSrvColumn.php: -------------------------------------------------------------------------------- 1 | default = $this->parseDefault($column['default']); 31 | $this->default = $this->escapeDefault($this->default); 32 | 33 | $this->repository = app(SQLSrvRepository::class); 34 | 35 | $this->setTypeToIncrements(false); 36 | $this->fixMoneyPrecision($column['type_name']); 37 | 38 | switch ($this->type) { 39 | case ColumnType::DATE: 40 | case ColumnType::DATETIME: 41 | case ColumnType::DATETIME_TZ: 42 | case ColumnType::TIME: 43 | case ColumnType::TIME_TZ: 44 | case ColumnType::TIMESTAMP: 45 | case ColumnType::TIMESTAMP_TZ: 46 | case ColumnType::SOFT_DELETES: 47 | case ColumnType::SOFT_DELETES_TZ: 48 | $this->length = $this->getDateTimeLength(); 49 | break; 50 | 51 | case ColumnType::CHAR: 52 | case ColumnType::STRING: 53 | case ColumnType::TEXT: 54 | $this->presetValues = $this->getEnumPresetValues(); 55 | 56 | if (count($this->presetValues) > 0) { 57 | $this->type = ColumnType::ENUM; 58 | break; 59 | } 60 | 61 | if ($this->isText($column['type'])) { 62 | $this->type = ColumnType::TEXT; 63 | $this->length = null; 64 | } 65 | 66 | $this->fixLength($column['type_name']); 67 | 68 | break; 69 | 70 | default: 71 | } 72 | } 73 | 74 | /** 75 | * @inheritDoc 76 | */ 77 | protected function getColumnType(string $type): ColumnType 78 | { 79 | return SQLSrvColumnType::toColumnType($type); 80 | } 81 | 82 | /** 83 | * Get the datetime column length. 84 | */ 85 | private function getDateTimeLength(): ?int 86 | { 87 | $columnDef = $this->repository->getColumnDefinition($this->tableName, $this->name); 88 | 89 | if ($columnDef === null) { 90 | return null; 91 | } 92 | 93 | switch ($this->type) { 94 | case ColumnType::DATETIME: 95 | if ( 96 | $columnDef->getScale() === self::DATETIME_EMPTY_SCALE 97 | && $columnDef->getLength() === self::DATETIME_EMPTY_LENGTH 98 | ) { 99 | return null; 100 | } 101 | 102 | return $columnDef->getScale(); 103 | 104 | case ColumnType::DATETIME_TZ: 105 | if ( 106 | $columnDef->getScale() === self::DATETIME_TZ_EMPTY_SCALE 107 | && $columnDef->getLength() === self::DATETIME_TZ_EMPTY_LENGTH 108 | ) { 109 | return null; 110 | } 111 | 112 | return $columnDef->getScale(); 113 | 114 | default: 115 | return $columnDef->getScale(); 116 | } 117 | } 118 | 119 | /** 120 | * Set precision to 19 and scale to 4. 121 | */ 122 | private function fixMoneyPrecision(string $dbType): void 123 | { 124 | if ($dbType === 'money') { 125 | $this->precision = 19; 126 | $this->scale = 4; 127 | return; 128 | } 129 | 130 | if ($dbType !== 'smallmoney') { 131 | return; 132 | } 133 | 134 | $this->precision = 10; 135 | $this->scale = 4; 136 | } 137 | 138 | /** 139 | * Check if the column type is "text". 140 | */ 141 | private function isText(string $fullDefinitionType): bool 142 | { 143 | return $fullDefinitionType === 'nvarchar(max)' || $fullDefinitionType === 'varchar(max)'; 144 | } 145 | 146 | /** 147 | * Get the preset values if the column is `enum`. 148 | * 149 | * @return string[] 150 | */ 151 | private function getEnumPresetValues(): array 152 | { 153 | return $this->repository->getEnumPresetValues( 154 | $this->tableName, 155 | $this->name, 156 | )->all(); 157 | } 158 | 159 | /** 160 | * Fix the unicode string length. 161 | */ 162 | private function fixLength(string $dbType): void 163 | { 164 | if ($this->length === null) { 165 | return; 166 | } 167 | 168 | switch ($dbType) { 169 | case 'nchar': 170 | case 'ntext': 171 | case 'nvarchar': 172 | // Unicode data requires 2 bytes per character 173 | $this->length /= 2; 174 | return; 175 | 176 | default: 177 | } 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /src/Database/Models/SQLSrv/SQLSrvColumnType.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | protected static array $map = [ 14 | 'bigint' => ColumnType::BIG_INTEGER, 15 | 'binary' => ColumnType::BINARY, 16 | 'bit' => ColumnType::BOOLEAN, 17 | 'blob' => ColumnType::BINARY, 18 | 'char' => ColumnType::STRING, 19 | 'date' => ColumnType::DATE, 20 | 'datetime' => ColumnType::DATETIME, 21 | 'datetime2' => ColumnType::DATETIME, 22 | 'datetimeoffset' => ColumnType::DATETIME_TZ, 23 | 'decimal' => ColumnType::DECIMAL, 24 | 'double precision' => ColumnType::FLOAT, 25 | 'double' => ColumnType::FLOAT, 26 | 'float' => ColumnType::FLOAT, 27 | 'geography' => ColumnType::GEOGRAPHY, 28 | 'geometry' => ColumnType::GEOMETRY, 29 | 'image' => ColumnType::BINARY, 30 | 'int' => ColumnType::INTEGER, 31 | 'money' => ColumnType::DECIMAL, 32 | 'nchar' => ColumnType::CHAR, 33 | 'ntext' => ColumnType::TEXT, 34 | 'numeric' => ColumnType::DECIMAL, 35 | 'nvarchar' => ColumnType::STRING, 36 | 'real' => ColumnType::FLOAT, 37 | 'smalldatetime' => ColumnType::DATETIME, 38 | 'smallint' => ColumnType::SMALL_INTEGER, 39 | 'smallmoney' => ColumnType::DECIMAL, 40 | 'text' => ColumnType::TEXT, 41 | 'time' => ColumnType::TIME, 42 | 'tinyint' => ColumnType::TINY_INTEGER, 43 | 'uniqueidentifier' => ColumnType::UUID, 44 | 'varbinary' => ColumnType::BINARY, 45 | 'varchar' => ColumnType::STRING, 46 | 'xml' => ColumnType::TEXT, 47 | ]; 48 | 49 | /** 50 | * @inheritDoc 51 | */ 52 | public static function toColumnType(string $dbType): ColumnType 53 | { 54 | return self::mapToColumnType(self::$map, $dbType); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/Database/Models/SQLSrv/SQLSrvForeignKey.php: -------------------------------------------------------------------------------- 1 | type) { 23 | case IndexType::PRIMARY: 24 | $this->resetPrimaryNameToEmptyIfIsDefaultName(); 25 | break; 26 | 27 | default: 28 | } 29 | 30 | if (!$hasUDTColumn) { 31 | return; 32 | } 33 | 34 | $blueprint = new Blueprint($this->stripTablePrefix($table)); 35 | 36 | // Generate the alter index statement. 37 | $blueprint->{$this->type->value}($this->columns, $this->name); 38 | 39 | $this->udtColumnSqls = $blueprint->toSqlWithCompatible(); 40 | } 41 | 42 | /** 43 | * Reset primary index name to empty if the name is using default naming convention. 44 | * 45 | * @see https://learnsql.com/cookbook/what-is-the-default-constraint-name-in-sql-server/ for default naming convention. 46 | */ 47 | private function resetPrimaryNameToEmptyIfIsDefaultName(): void 48 | { 49 | $prefix = 'pk__' . Str::substr($this->tableName, 0, 8) . '__'; 50 | 51 | // Can be improved by generate exact 16 characters of sequence number instead of `\w{16}` 52 | // if the rules of sequence number generation is known. 53 | if ($this->name !== Str::match('/' . $prefix . '\w{16}/', $this->name)) { 54 | return; 55 | } 56 | 57 | $this->name = ''; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/Database/Models/SQLSrv/SQLSrvParser.php: -------------------------------------------------------------------------------- 1 | stripTablePrefix($table)); 24 | 25 | // Generate the add column statement with string column type. 26 | $blueprint->addColumn('string', $column['name'], [ 27 | 'autoIncrement' => $column['auto_increment'], 28 | 'default' => $this->parseDefault($column['default']), 29 | 'nullable' => $column['nullable'], 30 | ]); 31 | 32 | $sqls = $blueprint->toSqlWithCompatible(); 33 | 34 | // Replace the string column type with the user-defined type. 35 | $sqls[0] = Str::replaceFirst(' nvarchar() ', ' ' . $column['type'] . ' ', $sqls[0]); 36 | 37 | $this->sqls = $sqls; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Database/Models/SQLSrv/SQLSrvView.php: -------------------------------------------------------------------------------- 1 | definition = $view['definition']; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Database/Models/SQLite/SQLiteColumn.php: -------------------------------------------------------------------------------- 1 | default = $this->parseDefault($column['default']); 23 | $this->default = $this->escapeDefault($this->default); 24 | 25 | $this->repository = app(SQLiteRepository::class); 26 | 27 | $this->setAutoincrement(); 28 | $this->setTypeToIncrements(false); 29 | 30 | switch ($this->type) { 31 | case ColumnType::INTEGER: 32 | if ($this->isBoolean($column['type'])) { 33 | $this->type = ColumnType::BOOLEAN; 34 | } 35 | 36 | break; 37 | 38 | case ColumnType::STRING: 39 | $this->presetValues = $this->getEnumPresetValues(); 40 | 41 | if (count($this->presetValues) > 0) { 42 | $this->type = ColumnType::ENUM; 43 | } 44 | 45 | break; 46 | 47 | case ColumnType::DATETIME: 48 | if ($this->default === 'CURRENT_TIMESTAMP') { 49 | $this->type = ColumnType::TIMESTAMP; 50 | } 51 | 52 | break; 53 | 54 | default: 55 | } 56 | } 57 | 58 | /** 59 | * @inheritDoc 60 | */ 61 | protected function getColumnType(string $type): ColumnType 62 | { 63 | return SQLiteColumnType::toColumnType($type); 64 | } 65 | 66 | /** 67 | * If column is integer and primary key, 68 | * The column is autoincrement, but it could be wrong. 69 | * Should check full sql statement from sqlite_master to ensure autoincrement is written correctly. 70 | */ 71 | private function setAutoincrement(): void 72 | { 73 | // We need to check if the autoincrement is set correctly. 74 | // Proceed only if this column is autoincrement. 75 | if (!$this->isAutoincrement()) { 76 | return; 77 | } 78 | 79 | // First, disable autoincrement 80 | $this->autoincrement = false; 81 | 82 | $sql = $this->repository->getSql($this->tableName); 83 | 84 | if (!Str::contains($sql, 'autoincrement')) { 85 | return; 86 | } 87 | 88 | $sqlColumn = Regex::getTextBetween($sql); 89 | 90 | if ($sqlColumn === null) { 91 | return; 92 | } 93 | 94 | $columns = explode(',', $sqlColumn); 95 | 96 | foreach ($columns as $column) { 97 | if (!Str::startsWith(trim($column), '"' . $this->name . '"')) { 98 | continue; 99 | } 100 | 101 | if (Str::contains($column, 'autoincrement')) { 102 | $this->autoincrement = true; 103 | } 104 | 105 | break; 106 | } 107 | } 108 | 109 | /** 110 | * Check if the column is "tinyint(1)". 111 | */ 112 | private function isBoolean(string $fullDefinitionType): bool 113 | { 114 | if ($this->autoincrement) { 115 | return false; 116 | } 117 | 118 | return $fullDefinitionType === 'tinyint(1)'; 119 | } 120 | 121 | /** 122 | * Get the preset values if the column is "enum". 123 | * 124 | * @return string[] 125 | */ 126 | private function getEnumPresetValues(): array 127 | { 128 | $sql = $this->repository->getSql($this->tableName); 129 | 130 | if (!preg_match('/\(\"' . $this->name . '\" in \((.*?)\)/', $sql, $matched)) { 131 | return []; 132 | } 133 | 134 | // Get content from (.*?) from index 1 135 | $explodes = explode(',', $matched[1]); 136 | $values = []; 137 | 138 | foreach ($explodes as $value) { 139 | $values[] = trim(trim($value), "'"); 140 | } 141 | 142 | return $values; 143 | } 144 | 145 | /** 146 | * Parse the default value. 147 | */ 148 | private function parseDefault(?string $default): ?string 149 | { 150 | if ($default === null) { 151 | return null; 152 | } 153 | 154 | while (preg_match('/^\'(.*)\'$/s', $default, $matches)) { 155 | $default = str_replace("''", "'", $matches[1]); 156 | } 157 | 158 | return $default; 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /src/Database/Models/SQLite/SQLiteColumnType.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | protected static array $map = [ 14 | 'bigint' => ColumnType::BIG_INTEGER, 15 | 'bigserial' => ColumnType::BIG_INTEGER, 16 | 'blob' => ColumnType::BINARY, 17 | 'boolean' => ColumnType::BOOLEAN, 18 | 'char' => ColumnType::STRING, 19 | 'clob' => ColumnType::TEXT, 20 | 'date' => ColumnType::DATE, 21 | 'datetime' => ColumnType::DATETIME, 22 | 'decimal' => ColumnType::DECIMAL, 23 | 'double' => ColumnType::DOUBLE, 24 | 'double precision' => ColumnType::DOUBLE, 25 | 'float' => ColumnType::FLOAT, 26 | 'image' => ColumnType::STRING, 27 | 'int' => ColumnType::INTEGER, 28 | 'integer' => ColumnType::INTEGER, 29 | 'geometry' => ColumnType::GEOMETRY, 30 | 'longtext' => ColumnType::TEXT, 31 | 'longvarchar' => ColumnType::STRING, 32 | 'mediumint' => ColumnType::INTEGER, 33 | 'mediumtext' => ColumnType::TEXT, 34 | 'ntext' => ColumnType::STRING, 35 | 'numeric' => ColumnType::DECIMAL, 36 | 'nvarchar' => ColumnType::STRING, 37 | 'real' => ColumnType::FLOAT, 38 | 'serial' => ColumnType::INTEGER, 39 | 'smallint' => ColumnType::INTEGER, 40 | 'string' => ColumnType::STRING, 41 | 'text' => ColumnType::TEXT, 42 | 'time' => ColumnType::TIME, 43 | 'timestamp' => ColumnType::DATETIME, 44 | 'tinyint' => ColumnType::INTEGER, 45 | 'tinytext' => ColumnType::TEXT, 46 | 'varchar' => ColumnType::STRING, 47 | 'varchar2' => ColumnType::STRING, 48 | ]; 49 | 50 | /** 51 | * @inheritDoc 52 | */ 53 | public static function toColumnType(string $dbType): ColumnType 54 | { 55 | return self::mapToColumnType(self::$map, $dbType); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/Database/Models/SQLite/SQLiteForeignKey.php: -------------------------------------------------------------------------------- 1 | type) { 18 | case IndexType::PRIMARY: 19 | // Reset name to empty to indicate use the database platform naming. 20 | $this->name = ''; 21 | break; 22 | 23 | default: 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Database/Models/SQLite/SQLiteTable.php: -------------------------------------------------------------------------------- 1 | definition = $view['definition']; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Database/MySQLSchema.php: -------------------------------------------------------------------------------- 1 | getSchemaTable($name), 29 | $this->getSchemaColumns($name), 30 | $this->getSchemaIndexes($name), 31 | new Collection(), 32 | ); 33 | } 34 | 35 | /** 36 | * @inheritDoc 37 | */ 38 | public function getViewNames(): Collection 39 | { 40 | return $this->getViews()->map(static fn (View $view) => $view->getName()); 41 | } 42 | 43 | /** 44 | * @inheritDoc 45 | */ 46 | public function getViews(): Collection 47 | { 48 | return $this->getSchemaViews()->map(static fn (array $view) => new MySQLView($view)); 49 | } 50 | 51 | /** 52 | * @inheritDoc 53 | */ 54 | public function getProcedures(): Collection 55 | { 56 | return $this->mySQLRepository->getProcedures() 57 | ->map(static fn (ProcedureDefinition $procedureDefinition) => new MySQLProcedure($procedureDefinition->getName(), $procedureDefinition->getDefinition())); 58 | } 59 | 60 | /** 61 | * @inheritDoc 62 | */ 63 | public function getForeignKeys(string $table): Collection 64 | { 65 | return $this->getSchemaForeignKeys($table) 66 | ->map(static fn (array $foreignKey) => new MySQLForeignKey($table, $foreignKey)); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/Database/PgSQLSchema.php: -------------------------------------------------------------------------------- 1 | 23 | */ 24 | private Collection $userDefinedTypes; 25 | 26 | private bool $ranGetUserDefinedTypes = false; 27 | 28 | public function __construct(private readonly PgSQLRepository $pgSQLRepository) 29 | { 30 | $this->userDefinedTypes = new Collection(); 31 | } 32 | 33 | /** 34 | * @inheritDoc 35 | */ 36 | public function getTableNames(): Collection 37 | { 38 | return (new Collection($this->getSchemaTables())) 39 | ->filter(static function (array $table): bool { 40 | if ($table['name'] === 'spatial_ref_sys') { 41 | return false; 42 | } 43 | 44 | // Schema name defined in the framework configuration. 45 | $searchPath = DB::connection()->getConfig('search_path') ?: DB::connection()->getConfig('schema'); 46 | 47 | return $table['schema'] === $searchPath; 48 | }) 49 | ->map(static fn (array $table): string => $table['name']) 50 | ->values(); 51 | } 52 | 53 | /** 54 | * @inheritDoc 55 | */ 56 | public function getTable(string $name): Table 57 | { 58 | return new PgSQLTable( 59 | $this->getSchemaTable($name), 60 | $this->getSchemaColumns($name), 61 | $this->getSchemaIndexes($name), 62 | $this->getUserDefinedTypes(), 63 | ); 64 | } 65 | 66 | /** 67 | * @inheritDoc 68 | */ 69 | public function getViewNames(): Collection 70 | { 71 | return $this->getViews()->map(static fn (View $view) => $view->getName()); 72 | } 73 | 74 | /** 75 | * @inheritDoc 76 | */ 77 | public function getViews(): Collection 78 | { 79 | return $this->getSchemaViews() 80 | ->filter(static function (array $view) { 81 | if (in_array($view['name'], ['geography_columns', 'geometry_columns'])) { 82 | return false; 83 | } 84 | 85 | // Start from Laravel 9, the `schema` configuration option used to configure Postgres connection search paths renamed to `search_path`. 86 | // Fallback to `schema` if Laravel version is older than 9. 87 | $searchPath = DB::connection()->getConfig('search_path') ?: DB::connection()->getConfig('schema'); 88 | 89 | return $view['schema'] === $searchPath; 90 | }) 91 | ->map(static fn (array $view) => new PgSQLView($view)) 92 | ->values(); 93 | } 94 | 95 | /** 96 | * @inheritDoc 97 | */ 98 | public function getProcedures(): Collection 99 | { 100 | return $this->pgSQLRepository->getProcedures() 101 | ->map(static fn (ProcedureDefinition $procedureDefinition) => new PgSQLProcedure($procedureDefinition->getName(), $procedureDefinition->getDefinition())); 102 | } 103 | 104 | /** 105 | * @inheritDoc 106 | */ 107 | public function getForeignKeys(string $table): Collection 108 | { 109 | return $this->getSchemaForeignKeys($table) 110 | ->map(static fn (array $foreignKey) => new PgSQLForeignKey($table, $foreignKey)); 111 | } 112 | 113 | /** 114 | * Get user defined types from the schema. 115 | * 116 | * @return \Illuminate\Support\Collection 117 | */ 118 | private function getUserDefinedTypes(): Collection 119 | { 120 | if (!$this->ranGetUserDefinedTypes) { 121 | $this->userDefinedTypes = new Collection(array_column($this->getSchemaTypes(), 'name')); 122 | $this->ranGetUserDefinedTypes = true; 123 | } 124 | 125 | return $this->userDefinedTypes; 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/Database/SQLSrvSchema.php: -------------------------------------------------------------------------------- 1 | 19 | */ 20 | private Collection $userDefinedTypes; 21 | 22 | private bool $ranGetUserDefinedTypes = false; 23 | 24 | public function __construct(private readonly SQLSrvRepository $sqlSrvRepository) 25 | { 26 | $this->userDefinedTypes = new Collection(); 27 | } 28 | 29 | /** 30 | * @inheritDoc 31 | */ 32 | public function getTable(string $name): Table 33 | { 34 | return new SQLSrvTable( 35 | $this->getSchemaTable($name), 36 | $this->getSchemaColumns($name), 37 | $this->getSchemaIndexes($name), 38 | $this->getUserDefinedTypes(), 39 | ); 40 | } 41 | 42 | /** 43 | * @inheritDoc 44 | */ 45 | public function getViewNames(): Collection 46 | { 47 | return $this->getViews()->map(static fn (View $view) => $view->getName()); 48 | } 49 | 50 | /** 51 | * @inheritDoc 52 | */ 53 | public function getViews(): Collection 54 | { 55 | return $this->getSchemaViews() 56 | ->map(static fn (array $view) => new SQLSrvView($view)) 57 | ->filter(static fn (SQLSrvView $view) => $view->getDefinition() !== ''); 58 | } 59 | 60 | /** 61 | * @inheritDoc 62 | */ 63 | public function getProcedures(): Collection 64 | { 65 | return $this->sqlSrvRepository->getProcedures() 66 | ->map(static fn (ProcedureDefinition $procedureDefinition) => new PgSQLProcedure($procedureDefinition->getName(), $procedureDefinition->getDefinition())); 67 | } 68 | 69 | /** 70 | * @inheritDoc 71 | */ 72 | public function getForeignKeys(string $table): Collection 73 | { 74 | return $this->getSchemaForeignKeys($table) 75 | ->map(static fn (array $foreignKey) => new SQLSrvForeignKey($table, $foreignKey)); 76 | } 77 | 78 | /** 79 | * Get user defined types from the database. 80 | * 81 | * @return \Illuminate\Support\Collection 82 | */ 83 | private function getUserDefinedTypes(): Collection 84 | { 85 | if (!$this->ranGetUserDefinedTypes) { 86 | $this->userDefinedTypes = $this->sqlSrvRepository->getUserDefinedTypes(); 87 | $this->ranGetUserDefinedTypes = true; 88 | } 89 | 90 | return $this->userDefinedTypes; 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/Database/SQLiteSchema.php: -------------------------------------------------------------------------------- 1 | getSchemaTable($name), 21 | $this->getSchemaColumns($name), 22 | $this->getSchemaIndexes($name), 23 | new Collection(), 24 | ); 25 | } 26 | 27 | /** 28 | * @inheritDoc 29 | */ 30 | public function getViewNames(): Collection 31 | { 32 | return $this->getViews()->map(static fn (View $view) => $view->getName()); 33 | } 34 | 35 | /** 36 | * @inheritDoc 37 | */ 38 | public function getViews(): Collection 39 | { 40 | return $this->getSchemaViews()->map(static fn (array $view) => new SQLiteView($view)); 41 | } 42 | 43 | /** 44 | * @inheritDoc 45 | */ 46 | public function getProcedures(): Collection 47 | { 48 | // Stored procedure does not available. 49 | // https://sqlite.org/forum/info/78a60bdeec7c1ee9 50 | return new Collection(); 51 | } 52 | 53 | /** 54 | * @inheritDoc 55 | */ 56 | public function getForeignKeys(string $table): Collection 57 | { 58 | return $this->getSchemaForeignKeys($table) 59 | ->map(static fn (array $foreignKey) => new SQLiteForeignKey($table, $foreignKey)); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/Enum/Driver.php: -------------------------------------------------------------------------------- 1 | statement("CREATE VIEW active_users AS select * from users where status = 1"); 20 | * ``` 21 | */ 22 | class DBStatementBlueprint implements WritableBlueprint 23 | { 24 | use Stringable; 25 | use MethodStringHelper; 26 | 27 | /** 28 | * DBStatementBlueprint constructor. 29 | * 30 | * @param string $sql The SQL statement. 31 | */ 32 | public function __construct(private readonly string $sql) 33 | { 34 | } 35 | 36 | /** 37 | * @inheritDoc 38 | */ 39 | public function toString(): string 40 | { 41 | $method = $this->connection('DB', DBBuilder::STATEMENT); 42 | $query = $this->escapeDoubleQuote($this->sql); 43 | return "$method(\"$query\");"; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Migration/Blueprint/DBUnpreparedBlueprint.php: -------------------------------------------------------------------------------- 1 | statement("CREATE PROCEDURE myProcedure() BEGIN SELECT * from table END"); 20 | * ``` 21 | */ 22 | class DBUnpreparedBlueprint implements WritableBlueprint 23 | { 24 | use Stringable; 25 | use MethodStringHelper; 26 | 27 | /** 28 | * DBStatementBlueprint constructor. 29 | * 30 | * @param string $sql The SQL statement. 31 | */ 32 | public function __construct(private readonly string $sql) 33 | { 34 | } 35 | 36 | /** 37 | * @inheritDoc 38 | */ 39 | public function toString(): string 40 | { 41 | $method = $this->connection('DB', DBBuilder::UNPREPARED); 42 | $query = $this->escapeDoubleQuote($this->sql); 43 | return "$method(\"$query\");"; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Migration/Blueprint/Method.php: -------------------------------------------------------------------------------- 1 | values = $values; 28 | $this->chains = []; 29 | } 30 | 31 | public function getName(): MethodName 32 | { 33 | return $this->name; 34 | } 35 | 36 | /** 37 | * @return mixed[] 38 | */ 39 | public function getValues(): array 40 | { 41 | return $this->values; 42 | } 43 | 44 | /** 45 | * Chain method. 46 | * 47 | * @param \KitLoong\MigrationsGenerator\Enum\Migrations\Method\MethodName $name Method name. 48 | * @param mixed ...$values Method arguments. 49 | * @return $this 50 | */ 51 | public function chain(MethodName $name, mixed ...$values): self 52 | { 53 | $this->chains[] = new self($name, ...$values); 54 | return $this; 55 | } 56 | 57 | /** 58 | * Checks if chain name exists. 59 | * 60 | * @param \KitLoong\MigrationsGenerator\Enum\Migrations\Method\MethodName $name Method name. 61 | */ 62 | public function hasChain(MethodName $name): bool 63 | { 64 | foreach ($this->chains as $chain) { 65 | if ($chain->getName() === $name) { 66 | return true; 67 | } 68 | } 69 | 70 | return false; 71 | } 72 | 73 | /** 74 | * Total chain. 75 | */ 76 | public function countChain(): int 77 | { 78 | return count($this->chains); 79 | } 80 | 81 | /** 82 | * Get a list of chained methods. 83 | * 84 | * @return \KitLoong\MigrationsGenerator\Migration\Blueprint\Method[] 85 | */ 86 | public function getChains(): array 87 | { 88 | return $this->chains; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/Migration/Blueprint/Property.php: -------------------------------------------------------------------------------- 1 | name; 19 | } 20 | 21 | public function getValue(): mixed 22 | { 23 | return $this->value; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Migration/Blueprint/SchemaBlueprint.php: -------------------------------------------------------------------------------- 1 | table('users', function (Blueprint $table) { 25 | * // ... 26 | * }); 27 | * ``` 28 | * 29 | * eg 3: 30 | * ``` 31 | * Schema::dropIfExists('users); 32 | * ``` 33 | */ 34 | class SchemaBlueprint implements WritableBlueprint 35 | { 36 | use Stringable; 37 | use MethodStringHelper; 38 | use TableName; 39 | 40 | /** 41 | * The table name without prefix. {@see \Illuminate\Support\Facades\DB::getTablePrefix()} 42 | */ 43 | private string $table; 44 | 45 | private ?TableBlueprint $blueprint = null; 46 | 47 | /** 48 | * SchemaBlueprint constructor. 49 | * 50 | * @param string $table Table name. 51 | * @param \KitLoong\MigrationsGenerator\Enum\Migrations\Method\SchemaBuilder $schemaBuilder SchemaBuilder name. 52 | */ 53 | public function __construct(string $table, private readonly SchemaBuilder $schemaBuilder) 54 | { 55 | $this->table = $this->stripTablePrefix($table); 56 | } 57 | 58 | public function setBlueprint(TableBlueprint $blueprint): void 59 | { 60 | $this->blueprint = $blueprint; 61 | } 62 | 63 | /** 64 | * @inheritDoc 65 | */ 66 | public function toString(): string 67 | { 68 | $lines = $this->getLines(); 69 | return $this->flattenLines($lines, 2); 70 | } 71 | 72 | /** 73 | * @return string[] 74 | */ 75 | private function getLines(): array 76 | { 77 | $schema = $this->connection('Schema', $this->schemaBuilder); 78 | 79 | if ($this->schemaBuilder === SchemaBuilder::DROP_IF_EXISTS) { 80 | return $this->getDropLines($schema); 81 | } 82 | 83 | $tableLines = $this->getTableLines($schema); 84 | 85 | if (!app(Setting::class)->isWithHasTable()) { 86 | return $tableLines; 87 | } 88 | 89 | $schemaHasTable = $this->connection('Schema', SchemaBuilder::HAS_TABLE); 90 | 91 | $lines = []; 92 | 93 | $lines[] = $this->getIfCondition($schemaHasTable, $this->table); 94 | 95 | foreach ($tableLines as $tableLine) { 96 | // Add another tabulation to indent(prettify) blueprint definition. 97 | $lines[] = Space::TAB->value . $tableLine; 98 | } 99 | 100 | $lines[] = "}"; 101 | 102 | return $lines; 103 | } 104 | 105 | /** 106 | * Get drop commands in array. 107 | * 108 | * @param string $schema The stringify `Schema::something` or `Schema::connection('db')->something`. 109 | * @return string[] 110 | */ 111 | private function getDropLines(string $schema): array 112 | { 113 | return [ 114 | "$schema('$this->table');", 115 | ]; 116 | } 117 | 118 | /** 119 | * Get table commands in array. 120 | * 121 | * @param string $schema The stringify `Schema::something` or `Schema::connection('db')->something`. 122 | * @return string[] 123 | */ 124 | private function getTableLines(string $schema): array 125 | { 126 | if ($this->blueprint === null) { 127 | return []; 128 | } 129 | 130 | $lines = []; 131 | $lines[] = "$schema('$this->table', function (Blueprint \$table) {"; 132 | 133 | if (app(Setting::class)->isWithHasTable()) { 134 | $this->blueprint->increaseNumberOfPrefixTab(); 135 | } 136 | 137 | // Add 1 tabulation to indent(prettify) blueprint definition. 138 | $lines[] = Space::TAB->value . $this->blueprint->toString(); 139 | $lines[] = "});"; 140 | 141 | return $lines; 142 | } 143 | 144 | /** 145 | * Generate `if` condition string. 146 | * 147 | * @param string $schemaHasTable The stringify `Schema::hasTable` or `Schema::connection('db')->hasTable`. 148 | */ 149 | private function getIfCondition(string $schemaHasTable, string $tableWithoutPrefix): string 150 | { 151 | if ($this->schemaBuilder === SchemaBuilder::TABLE) { 152 | return "if ($schemaHasTable('$tableWithoutPrefix')) {"; 153 | } 154 | 155 | return "if (!$schemaHasTable('$tableWithoutPrefix')) {"; 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /src/Migration/Blueprint/Support/MethodStringHelper.php: -------------------------------------------------------------------------------- 1 | getDefaultConnection()) { 18 | return "$class::$method->value"; 19 | } 20 | 21 | return "$class::" . SchemaBuilder::CONNECTION->value . "('" . DB::getName() . "')->$method->value"; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Migration/Blueprint/Support/Stringable.php: -------------------------------------------------------------------------------- 1 | $line) { 23 | // Skip tab if the line is first line or line break. 24 | if ($i === 0 || $line === Space::LINE_BREAK->value) { 25 | $content .= $line; 26 | continue; 27 | } 28 | 29 | $content .= Space::LINE_BREAK->value . str_repeat(Space::TAB->value, $numberOfPrefixTab) . $line; 30 | } 31 | 32 | return $content; 33 | } 34 | 35 | /** 36 | * Convert $value to printable string. 37 | */ 38 | public function convertFromAnyTypeToString(mixed $value): string 39 | { 40 | switch (gettype($value)) { 41 | case 'string': 42 | return "'" . $this->escapeSingleQuote($value) . "'"; 43 | 44 | case 'integer': 45 | case 'double': 46 | return (string) $value; 47 | 48 | case 'boolean': 49 | return $value ? 'true' : 'false'; 50 | 51 | case 'NULL': 52 | return 'null'; 53 | 54 | case 'array': 55 | return '[' . implode(', ', $this->mapArrayItemsToString($value)) . ']'; 56 | 57 | default: 58 | // Wrap with DB::raw(); 59 | if ($value instanceof Expression) { 60 | return 'DB::raw("' . $this->escapeDoubleQuote((string) DB::getQueryGrammar()->getValue($value)) . '")'; 61 | } 62 | 63 | if ($value instanceof Space) { 64 | return $value->value; 65 | } 66 | 67 | return (string) $value; 68 | } 69 | } 70 | 71 | /** 72 | * Escapes single quotes by adding backslash. 73 | */ 74 | public function escapeSingleQuote(string $string): string 75 | { 76 | return addcslashes($string, "'"); 77 | } 78 | 79 | /** 80 | * Escapes double quotes by adding backslash. 81 | */ 82 | public function escapeDoubleQuote(string $string): string 83 | { 84 | return addcslashes($string, '"'); 85 | } 86 | 87 | /** 88 | * Convert $list items to printable string. 89 | * 90 | * @param mixed[] $list 91 | * @return string[] 92 | */ 93 | public function mapArrayItemsToString(array $list): array 94 | { 95 | return (new Collection($list))->map(fn ($v) => $this->convertFromAnyTypeToString($v))->toArray(); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/Migration/Blueprint/WritableBlueprint.php: -------------------------------------------------------------------------------- 1 | $foreignKeys 34 | * @return string The migration file path. 35 | */ 36 | public function write(string $table, Collection $foreignKeys): string 37 | { 38 | $up = $this->up($table, $foreignKeys); 39 | $down = $this->down($table, $foreignKeys); 40 | 41 | $this->migrationWriter->writeTo( 42 | $path = $this->makeMigrationPath($table), 43 | $this->setting->getStubPath(), 44 | $this->makeMigrationClassName($table), 45 | new Collection([$up]), 46 | new Collection([$down]), 47 | MigrationFileType::FOREIGN_KEY, 48 | ); 49 | 50 | return $path; 51 | } 52 | 53 | /** 54 | * Write foreign key migration into temporary file. 55 | * 56 | * @param \Illuminate\Support\Collection $foreignKeys 57 | */ 58 | public function writeToTemp(string $table, Collection $foreignKeys): void 59 | { 60 | $up = $this->up($table, $foreignKeys); 61 | $down = $this->down($table, $foreignKeys); 62 | 63 | $this->squashWriter->writeToTemp(new Collection([$up]), new Collection([$down])); 64 | } 65 | 66 | /** 67 | * Generates `up` schema for foreign key. 68 | * 69 | * @param \Illuminate\Support\Collection $foreignKeys 70 | */ 71 | private function up(string $table, Collection $foreignKeys): SchemaBlueprint 72 | { 73 | $up = $this->getSchemaBlueprint($table); 74 | $upBlueprint = new TableBlueprint(); 75 | 76 | foreach ($foreignKeys as $foreignKey) { 77 | $method = $this->foreignKeyGenerator->generate($foreignKey); 78 | $upBlueprint->setMethod($method); 79 | } 80 | 81 | $up->setBlueprint($upBlueprint); 82 | 83 | return $up; 84 | } 85 | 86 | /** 87 | * Generates `down` schema for foreign key. 88 | * 89 | * @param \Illuminate\Support\Collection $foreignKeys 90 | */ 91 | private function down(string $table, Collection $foreignKeys): SchemaBlueprint 92 | { 93 | $down = $this->getSchemaBlueprint($table); 94 | $downBlueprint = new TableBlueprint(); 95 | 96 | foreach ($foreignKeys as $foreignKey) { 97 | $method = $this->foreignKeyGenerator->generateDrop($foreignKey); 98 | $downBlueprint->setMethod($method); 99 | } 100 | 101 | $down->setBlueprint($downBlueprint); 102 | 103 | return $down; 104 | } 105 | 106 | /** 107 | * Makes class name for foreign key migration. 108 | * 109 | * @param string $table Table name. 110 | */ 111 | private function makeMigrationClassName(string $table): string 112 | { 113 | $withoutPrefix = $this->stripTablePrefix($table); 114 | return $this->migrationNameHelper->makeClassName( 115 | $this->setting->getFkFilename(), 116 | $withoutPrefix, 117 | ); 118 | } 119 | 120 | /** 121 | * Makes file path for foreign key migration. 122 | * 123 | * @param string $table Table name. 124 | */ 125 | private function makeMigrationPath(string $table): string 126 | { 127 | $withoutPrefix = $this->stripTablePrefix($table); 128 | return $this->migrationNameHelper->makeFilename( 129 | $this->setting->getFkFilename(), 130 | $this->setting->getDateForMigrationFilename(), 131 | $withoutPrefix, 132 | ); 133 | } 134 | 135 | private function getSchemaBlueprint(string $table): SchemaBlueprint 136 | { 137 | return new SchemaBlueprint( 138 | $table, 139 | SchemaBuilder::TABLE, 140 | ); 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /src/Migration/Generator/ColumnGenerator.php: -------------------------------------------------------------------------------- 1 | $chainableIndexes 35 | */ 36 | public function generate(Table $table, Column $column, Collection $chainableIndexes): Method 37 | { 38 | $method = $this->createMethodFromColumn($table, $column); 39 | 40 | $method = $this->charsetModifier->chain($method, $table, $column); 41 | $method = $this->collationModifier->chain($method, $table, $column); 42 | $method = $this->nullableModifier->chain($method, $table, $column); 43 | $method = $this->defaultModifier->chain($method, $table, $column); 44 | $method = $this->virtualAsModifier->chain($method, $table, $column); 45 | $method = $this->storedAsModifier->chain($method, $table, $column); 46 | $method = $this->indexModifier->chain($method, $table, $column, $chainableIndexes); 47 | $method = $this->commentModifier->chain($method, $table, $column); 48 | 49 | return $method; 50 | } 51 | 52 | private function createMethodFromColumn(Table $table, Column $column): Method 53 | { 54 | /** @var \KitLoong\MigrationsGenerator\Migration\Generator\Columns\ColumnTypeGenerator $generator */ 55 | $generator = app(ColumnType::class . '\\' . $column->getType()->name); 56 | return $generator->generate($table, $column); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Migration/Generator/Columns/BooleanColumn.php: -------------------------------------------------------------------------------- 1 | getType(), $column->getName()); 18 | 19 | if ($column->isUnsigned()) { 20 | $method->chain(ColumnModifier::UNSIGNED); 21 | } 22 | 23 | return $method; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Migration/Generator/Columns/ColumnTypeGenerator.php: -------------------------------------------------------------------------------- 1 | makeMethod($column); 20 | 21 | if ($column->isOnUpdateCurrentTimestamp()) { 22 | $method->chain(ColumnModifier::USE_CURRENT_ON_UPDATE); 23 | } 24 | 25 | return $method; 26 | } 27 | 28 | /** 29 | * Create a Method instance. 30 | */ 31 | private function makeMethod(Column $column): Method 32 | { 33 | $length = $column->getLength() === self::DEFAULT_PRECISION ? null : $column->getLength(); 34 | 35 | if ($length !== null) { 36 | return new Method($column->getType(), $column->getName(), $length); 37 | } 38 | 39 | return new Method($column->getType(), $column->getName()); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Migration/Generator/Columns/DecimalColumn.php: -------------------------------------------------------------------------------- 1 | getDecimalPrecisions($column->getPrecision(), $column->getScale()); 22 | 23 | $method = new Method($column->getType(), $column->getName(), ...$precisions); 24 | 25 | if ($column->isUnsigned()) { 26 | $method->chain(ColumnModifier::UNSIGNED); 27 | } 28 | 29 | return $method; 30 | } 31 | 32 | /** 33 | * Default decimal precision and scale is (8, 2). 34 | * Return precision and scale if the column is not (8, 2). 35 | * 36 | * @return int[] "[]|[precision]|[precision, scale]" 37 | */ 38 | private function getDecimalPrecisions(?int $precision, int $scale): array 39 | { 40 | if ($precision === null) { 41 | return []; 42 | } 43 | 44 | if ($precision === self::DEFAULT_PRECISION && $scale === self::DEFAULT_SCALE) { 45 | return []; 46 | } 47 | 48 | if ($scale === self::DEFAULT_SCALE) { 49 | return [$precision]; 50 | } 51 | 52 | return [$precision, $scale]; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/Migration/Generator/Columns/DoubleColumn.php: -------------------------------------------------------------------------------- 1 | getType(), $column->getName()); 18 | 19 | if ($column->isUnsigned()) { 20 | $method->chain(ColumnModifier::UNSIGNED); 21 | } 22 | 23 | return $method; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Migration/Generator/Columns/FloatColumn.php: -------------------------------------------------------------------------------- 1 | getPrecisions($column); 20 | 21 | $method = new Method($column->getType(), $column->getName(), ...$precisions); 22 | 23 | if ($column->isUnsigned()) { 24 | $method->chain(ColumnModifier::UNSIGNED); 25 | } 26 | 27 | return $method; 28 | } 29 | 30 | /** 31 | * Get precision. 32 | * Return empty if precision = {@see self::DEFAULT_PRECISION}. 33 | * 34 | * @return array|array "[]|[precision]" 35 | */ 36 | private function getPrecisions(Column $column): array 37 | { 38 | if ($column->getPrecision() === null || $column->getPrecision() === self::DEFAULT_PRECISION) { 39 | return []; 40 | } 41 | 42 | return [$column->getPrecision()]; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Migration/Generator/Columns/IntegerColumn.php: -------------------------------------------------------------------------------- 1 | isAutoincrement()) { 17 | return new Method($column->getType(), $column->getName(), true); 18 | } 19 | 20 | return new Method($column->getType(), $column->getName()); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Migration/Generator/Columns/MiscColumn.php: -------------------------------------------------------------------------------- 1 | getType(), $column->getName()); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Migration/Generator/Columns/OmitNameColumn.php: -------------------------------------------------------------------------------- 1 | getType()); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Migration/Generator/Columns/PresetValuesColumn.php: -------------------------------------------------------------------------------- 1 | getType(), $column->getName(), $column->getPresetValues()); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Migration/Generator/Columns/SoftDeleteColumn.php: -------------------------------------------------------------------------------- 1 | makeMethod($column); 20 | 21 | if ($column->isOnUpdateCurrentTimestamp()) { 22 | $method->chain(ColumnModifier::USE_CURRENT_ON_UPDATE); 23 | } 24 | 25 | return $method; 26 | } 27 | 28 | /** 29 | * Create a Method instance. 30 | */ 31 | private function makeMethod(Column $column): Method 32 | { 33 | $length = $column->getLength() === self::DEFAULT_PRECISION ? null : $column->getLength(); 34 | 35 | if ($length !== null) { 36 | return new Method($column->getType(), $column->getName(), $length); 37 | } 38 | 39 | return new Method($column->getType()); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Migration/Generator/Columns/SpatialColumn.php: -------------------------------------------------------------------------------- 1 | getName()]; 22 | 23 | if ($column->getSpatialSubType() !== null) { 24 | $methodValues[] = $column->getSpatialSubType(); 25 | } 26 | 27 | $srID = $this->getSrIDArg($column); 28 | 29 | if ($srID !== null) { 30 | if (count($methodValues) === 1) { 31 | $methodValues[] = null; 32 | } 33 | 34 | $methodValues[] = $srID; 35 | } 36 | 37 | return new Method($column->getType(), ...$methodValues); 38 | } 39 | 40 | /** 41 | * Get the SRID argument for spatial column. 42 | * Return null if the SRID is null or it matches the default SRID. 43 | */ 44 | private function getSrIDArg(Column $column): ?int 45 | { 46 | if ($column->getSpatialSrID() === null) { 47 | return null; 48 | } 49 | 50 | switch ($column->getType()) { 51 | case ColumnType::GEOMETRY: 52 | if ($column->getSpatialSrID() !== self::GEOMETRY_DEFAULT_SRID) { 53 | return $column->getSpatialSrID(); 54 | } 55 | 56 | break; 57 | 58 | default: 59 | if ($column->getSpatialSrID() !== self::GEOGRAPHY_DEFAULT_SRID) { 60 | return $column->getSpatialSrID(); 61 | } 62 | } 63 | 64 | return null; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/Migration/Generator/Columns/StringColumn.php: -------------------------------------------------------------------------------- 1 | getLength() !== null && $column->getLength() !== Builder::$defaultStringLength) { 18 | return new Method($column->getType(), $column->getName(), $column->getLength()); 19 | } 20 | 21 | return new Method($column->getType(), $column->getName()); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Migration/Generator/ForeignKeyGenerator.php: -------------------------------------------------------------------------------- 1 | makeMethod($foreignKey); 21 | 22 | $method->chain(Foreign::REFERENCES, $foreignKey->getForeignColumns()) 23 | ->chain(Foreign::ON, $this->stripTablePrefix($foreignKey->getForeignTableName())); 24 | 25 | if ($foreignKey->getOnUpdate() !== null) { 26 | $method->chain(Foreign::ON_UPDATE, $foreignKey->getOnUpdate()); 27 | } 28 | 29 | if ($foreignKey->getOnDelete() !== null) { 30 | $method->chain(Foreign::ON_DELETE, $foreignKey->getOnDelete()); 31 | } 32 | 33 | return $method; 34 | } 35 | 36 | /** 37 | * Generates drop foreign migration method. 38 | */ 39 | public function generateDrop(ForeignKey $foreignKey): Method 40 | { 41 | if ($this->shouldSkipName($foreignKey)) { 42 | return new Method(Foreign::DROP_FOREIGN, $this->makeLaravelForeignKeyName($foreignKey)); 43 | } 44 | 45 | if ($foreignKey->getName() === null) { 46 | return new Method(Foreign::DROP_FOREIGN); 47 | } 48 | 49 | return new Method(Foreign::DROP_FOREIGN, $foreignKey->getName()); 50 | } 51 | 52 | /** 53 | * Create a new Method with foreignKey and columns. 54 | */ 55 | public function makeMethod(ForeignKey $foreignKey): Method 56 | { 57 | if ($this->shouldSkipName($foreignKey)) { 58 | return new Method(Foreign::FOREIGN, $foreignKey->getLocalColumns()); 59 | } 60 | 61 | return new Method(Foreign::FOREIGN, $foreignKey->getLocalColumns(), $foreignKey->getName()); 62 | } 63 | 64 | /** 65 | * Checks should skip current foreign key name from DB. 66 | */ 67 | private function shouldSkipName(ForeignKey $foreignKey): bool 68 | { 69 | if (app(Setting::class)->isIgnoreForeignKeyNames()) { 70 | return true; 71 | } 72 | 73 | return $this->makeLaravelForeignKeyName($foreignKey) === $foreignKey->getName(); 74 | } 75 | 76 | /** 77 | * Makes foreign key name with Laravel way. 78 | */ 79 | private function makeLaravelForeignKeyName(ForeignKey $foreignKey): string 80 | { 81 | $name = strtolower( 82 | $foreignKey->getTableName() . '_' . implode('_', $foreignKey->getLocalColumns()) . '_foreign', 83 | ); 84 | return str_replace(['-', '.'], '_', $name); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/Migration/Generator/IndexGenerator.php: -------------------------------------------------------------------------------- 1 | getColumns($index); 21 | 22 | if ($this->indexNameHelper->shouldSkipName($table->getName(), $index)) { 23 | return new Method($index->getType(), $columns); 24 | } 25 | 26 | return new Method($index->getType(), $columns, $index->getName()); 27 | } 28 | 29 | /** 30 | * Get a list of chainable indexes. 31 | * Chainable indexes are index with single column, and able be chained in column declaration. 32 | * eg: 33 | * $table->string('email')->index('chainable_index'); 34 | * $table->integer('id')->primary(); 35 | * 36 | * @param string $name Table name 37 | * @param \Illuminate\Support\Collection $indexes 38 | * @return \Illuminate\Support\Collection Key is the column name. 39 | */ 40 | public function getChainableIndexes(string $name, Collection $indexes): Collection 41 | { 42 | return $indexes->reduce(function (Collection $carry, Index $index) use ($name) { 43 | /** @var \Illuminate\Support\Collection $carry */ 44 | if (count($index->getColumns()) > 1) { 45 | return $carry; 46 | } 47 | 48 | // TODO Laravel 10 does not support `$table->index(DB::raw("with_length(16)"))` 49 | // if ($index->getLengths()[0] !== null) { 50 | // return $carry; 51 | // } 52 | 53 | $columnName = $index->getColumns()[0]; 54 | 55 | // Check if index is using framework default naming. 56 | // The older version "spatialIndex" modifier does not accept index name as argument. 57 | if ( 58 | $index->getType() === IndexType::SPATIAL_INDEX 59 | && !$this->indexNameHelper->shouldSkipName($name, $index) 60 | ) { 61 | return $carry; 62 | } 63 | 64 | // If name is not empty, primary name should be set explicitly. 65 | if ( 66 | $index->getType() === IndexType::PRIMARY 67 | && $index->getName() !== '' 68 | ) { 69 | return $carry; 70 | } 71 | 72 | // A column may have multiple indexes. 73 | // Set only first found index as chainable. 74 | if ($carry->has($columnName)) { 75 | return $carry; 76 | } 77 | 78 | $carry->put($columnName, $index); 79 | return $carry; 80 | }, new Collection()); 81 | } 82 | 83 | /** 84 | * Get a list of not chainable indexes. 85 | * Not chainable indexes are index with multi columns, or other indexes that must be explicitly defined in migration. 86 | * eg: 87 | * $table->index(['col1', 'col2'], 'not_chainable_index'); 88 | * $table->integer(['col1', 'col2'])->primary(); 89 | * 90 | * @param \Illuminate\Support\Collection $indexes 91 | * @param \Illuminate\Support\Collection $chainableIndexes Key is column name. 92 | * @return \Illuminate\Support\Collection 93 | */ 94 | public function getNotChainableIndexes(Collection $indexes, Collection $chainableIndexes): Collection 95 | { 96 | $chainableNames = $chainableIndexes->map(static fn (Index $index) => $index->getName()); 97 | 98 | return $indexes->filter(static fn (Index $index) => !$chainableNames->contains($index->getName())); 99 | } 100 | 101 | /** 102 | * Get column names with length. 103 | * 104 | * @return array 105 | */ 106 | private function getColumns(Index $index): array 107 | { 108 | $cols = []; 109 | 110 | foreach ($index->getColumns() as $column) { 111 | // TODO Laravel 10 does not support `$table->index(DB::raw("with_length(16)"))` 112 | // if ($index->getLengths()[$i] !== null) { 113 | // $cols[] = DB::raw($column . '(' . $index->getLengths()[$i] . ')'); 114 | // continue; 115 | // } 116 | $cols[] = $column; 117 | } 118 | 119 | return $cols; 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/Migration/Generator/Modifiers/CharsetModifier.php: -------------------------------------------------------------------------------- 1 | setting->isUseDBCollation()) { 24 | return $method; 25 | } 26 | 27 | // Collation is not set in PgSQL 28 | $tableCollation = $table->getCollation() ?? ''; 29 | $tableCharset = Str::before($tableCollation, '_'); 30 | 31 | $charset = $column->getCharset(); 32 | 33 | if ($charset !== null && $charset !== $tableCharset) { 34 | $method->chain(ColumnModifier::CHARSET, $charset); 35 | } 36 | 37 | return $method; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Migration/Generator/Modifiers/CollationModifier.php: -------------------------------------------------------------------------------- 1 | setting->isUseDBCollation()) { 23 | return $method; 24 | } 25 | 26 | // Collation is not set in PgSQL 27 | $tableCollation = $table->getCollation(); 28 | 29 | $collation = $column->getCollation(); 30 | 31 | if ($collation !== null && $collation !== $tableCollation) { 32 | $method->chain(ColumnModifier::COLLATION, $collation); 33 | } 34 | 35 | return $method; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Migration/Generator/Modifiers/CommentModifier.php: -------------------------------------------------------------------------------- 1 | getComment() !== null) { 18 | $method->chain(ColumnModifier::COMMENT, $column->getComment()); 19 | } 20 | 21 | return $method; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Migration/Generator/Modifiers/DefaultModifier.php: -------------------------------------------------------------------------------- 1 | 16 | */ 17 | private array $chainerMap = []; 18 | 19 | public function __construct() 20 | { 21 | foreach ( 22 | [ 23 | ColumnType::BIG_INTEGER, 24 | ColumnType::INTEGER, 25 | ColumnType::MEDIUM_INTEGER, 26 | ColumnType::SMALL_INTEGER, 27 | ColumnType::TINY_INTEGER, 28 | ColumnType::UNSIGNED_BIG_INTEGER, 29 | ColumnType::UNSIGNED_INTEGER, 30 | ColumnType::UNSIGNED_MEDIUM_INTEGER, 31 | ColumnType::UNSIGNED_SMALL_INTEGER, 32 | ColumnType::UNSIGNED_TINY_INTEGER, 33 | ] as $columnType 34 | ) { 35 | $this->chainerMap[$columnType->value] = fn (Method $method, Column $column): Method => call_user_func([$this, 'chainDefaultForInteger'], $method, $column); 36 | } 37 | 38 | foreach ( 39 | [ 40 | ColumnType::DECIMAL, 41 | ColumnType::FLOAT, 42 | ColumnType::DOUBLE, 43 | ] as $columnType 44 | ) { 45 | $this->chainerMap[$columnType->value] = fn (Method $method, Column $column): Method => call_user_func([$this, 'chainDefaultForDecimal'], $method, $column); 46 | } 47 | 48 | $this->chainerMap[ColumnType::BOOLEAN->value] = fn (Method $method, Column $column): Method => call_user_func([$this, 'chainDefaultForBoolean'], $method, $column); 49 | 50 | foreach ( 51 | [ 52 | ColumnType::SOFT_DELETES, 53 | ColumnType::SOFT_DELETES_TZ, 54 | ColumnType::DATE, 55 | ColumnType::DATETIME, 56 | ColumnType::DATETIME_TZ, 57 | ColumnType::TIME, 58 | ColumnType::TIME_TZ, 59 | ColumnType::TIMESTAMP, 60 | ColumnType::TIMESTAMP_TZ, 61 | ] as $columnType 62 | ) { 63 | $this->chainerMap[$columnType->value] = fn (Method $method, Column $column): Method => call_user_func([$this, 'chainDefaultForDatetime'], $method, $column); 64 | } 65 | } 66 | 67 | /** 68 | * @inheritDoc 69 | */ 70 | public function chain(Method $method, Table $table, Column $column, mixed ...$args): Method 71 | { 72 | if ($column->getDefault() === null) { 73 | return $method; 74 | } 75 | 76 | if (isset($this->chainerMap[$column->getType()->value])) { 77 | return $this->chainerMap[$column->getType()->value]($method, $column); 78 | } 79 | 80 | return $this->chainDefaultForString($method, $column); 81 | } 82 | 83 | /** 84 | * Set default value to method for integer column. 85 | */ 86 | protected function chainDefaultForInteger(Method $method, Column $column): Method 87 | { 88 | $method->chain(ColumnModifier::DEFAULT, (int) $column->getDefault()); 89 | return $method; 90 | } 91 | 92 | /** 93 | * Set default value to method for decimal column. 94 | */ 95 | protected function chainDefaultForDecimal(Method $method, Column $column): Method 96 | { 97 | $method->chain(ColumnModifier::DEFAULT, (float) $column->getDefault()); 98 | return $method; 99 | } 100 | 101 | /** 102 | * Set default value to method for boolean column. 103 | */ 104 | protected function chainDefaultForBoolean(Method $method, Column $column): Method 105 | { 106 | $default = match ($column->getDefault()) { 107 | 'true', '1' => true, 108 | default => false, 109 | }; 110 | 111 | $method->chain(ColumnModifier::DEFAULT, $default); 112 | return $method; 113 | } 114 | 115 | /** 116 | * Set default value to method for datetime column. 117 | */ 118 | protected function chainDefaultForDatetime(Method $method, Column $column): Method 119 | { 120 | switch ($column->getDefault()) { 121 | case 'CURRENT_TIMESTAMP': 122 | $method->chain(ColumnModifier::USE_CURRENT); 123 | break; 124 | 125 | default: 126 | $default = $column->getDefault(); 127 | 128 | if ($column->isRawDefault()) { 129 | // Set default with DB::raw(), which will return an instance of \Illuminate\Database\Query\Expression. 130 | // Writer will check for Expression instance and generate as DB::raw(). 131 | $default = DB::raw($default); 132 | } 133 | 134 | $method->chain(ColumnModifier::DEFAULT, $default); 135 | } 136 | 137 | return $method; 138 | } 139 | 140 | /** 141 | * Set default value to method, which support string. 142 | */ 143 | protected function chainDefaultForString(Method $method, Column $column): Method 144 | { 145 | $method->chain(ColumnModifier::DEFAULT, $column->getDefault()); 146 | 147 | return $method; 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /src/Migration/Generator/Modifiers/IndexModifier.php: -------------------------------------------------------------------------------- 1 | $chainableIndexes Key is column name. */ 23 | $chainableIndexes = $args[0]; 24 | 25 | if (!$chainableIndexes->has($column->getName())) { 26 | return $method; 27 | } 28 | 29 | /** @var \KitLoong\MigrationsGenerator\Schema\Models\Index $index */ 30 | $index = $chainableIndexes->get($column->getName()); 31 | 32 | // "increment" will add primary key by default. No need explicitly declare "primary" index here. 33 | if ($column->isAutoincrement() && $index->getType() === IndexType::PRIMARY) { 34 | return $method; 35 | } 36 | 37 | $indexType = $this->adjustIndexType($index->getType()); 38 | 39 | if ($this->indexNameHelper->shouldSkipName($table->getName(), $index)) { 40 | $method->chain($indexType); 41 | return $method; 42 | } 43 | 44 | $method->chain($indexType, $index->getName()); 45 | return $method; 46 | } 47 | 48 | /** 49 | * FULLTEXT index method name is `fullText` (camelCase) but changed to `fulltext` (lowercase) 50 | * when used for column chaining. 51 | */ 52 | private function adjustIndexType(IndexType $indexType): IndexType 53 | { 54 | if ($indexType === IndexType::FULLTEXT) { 55 | return IndexType::FULLTEXT_CHAIN; 56 | } 57 | 58 | return $indexType; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/Migration/Generator/Modifiers/Modifier.php: -------------------------------------------------------------------------------- 1 | isNotNull()) { 19 | if ($this->shouldAddNotNullModifier($column->getType())) { 20 | $method->chain(ColumnModifier::NULLABLE, false); 21 | } 22 | 23 | return $method; 24 | } 25 | 26 | if ($this->shouldAddNullableModifier($column->getType())) { 27 | $method->chain(ColumnModifier::NULLABLE); 28 | } 29 | 30 | return $method; 31 | } 32 | 33 | /** 34 | * Check if the column type should add nullable. 35 | * "softDeletes", "softDeletesTz", "rememberToken", and "timestamps" are skipped. 36 | */ 37 | private function shouldAddNullableModifier(ColumnType $columnType): bool 38 | { 39 | return !in_array( 40 | $columnType, 41 | [ 42 | ColumnType::SOFT_DELETES, 43 | ColumnType::SOFT_DELETES_TZ, 44 | ColumnType::REMEMBER_TOKEN, 45 | ColumnType::TIMESTAMPS, 46 | ], 47 | ); 48 | } 49 | 50 | /** 51 | * Check if the column type should add nullable(false). 52 | * Only check "softDeletes", "softDeletesTz", and "rememberToken". 53 | */ 54 | private function shouldAddNotNullModifier(ColumnType $columnType): bool 55 | { 56 | return in_array( 57 | $columnType, 58 | [ 59 | ColumnType::SOFT_DELETES, 60 | ColumnType::SOFT_DELETES_TZ, 61 | ColumnType::REMEMBER_TOKEN, 62 | ], 63 | ); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/Migration/Generator/Modifiers/StoredAsModifier.php: -------------------------------------------------------------------------------- 1 | getStoredDefinition() !== null) { 18 | $method->chain(ColumnModifier::STORED_AS, $column->getStoredDefinition()); 19 | } 20 | 21 | return $method; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Migration/Generator/Modifiers/VirtualAsModifier.php: -------------------------------------------------------------------------------- 1 | getVirtualDefinition() !== null) { 18 | $method->chain(ColumnModifier::VIRTUAL_AS, $column->getVirtualDefinition()); 19 | } 20 | 21 | return $method; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Migration/Migrator/Migrator.php: -------------------------------------------------------------------------------- 1 | 'sqlite', 30 | 'database' => ':memory:', 31 | ]); 32 | 33 | DB::setDefaultConnection('lgm_sqlite'); 34 | 35 | $vendorPaths = app('migrator')->paths(); 36 | 37 | foreach ($vendorPaths as $path) { 38 | $files = File::files($path); 39 | 40 | foreach ($files as $file) { 41 | $queries = $this->getMigrationQueries($file->getPathname()); 42 | 43 | foreach ($queries as $q) { 44 | $matched = Str::match('/^create table ["|`](.*?)["|`]/', $q['query']); 45 | 46 | if ($matched === '') { 47 | continue; 48 | } 49 | 50 | $tables[] = $matched; 51 | } 52 | } 53 | } 54 | } finally { 55 | // Restore backup DB connection. 56 | DB::setDefaultConnection($previousConnection); 57 | } 58 | 59 | return $tables; 60 | } 61 | 62 | /** 63 | * Resolve migration instance from `$path` and get all of the queries that would be run for a migration. 64 | * 65 | * @return array>, 'time': float|null}> 66 | */ 67 | protected function getMigrationQueries(string $path): array 68 | { 69 | $migration = $this->resolveMigration($path); 70 | 71 | return $this->getQueries($migration, 'up'); 72 | } 73 | 74 | /** 75 | * Resolve migration instance with backward compatibility. 76 | */ 77 | protected function resolveMigration(string $path): object 78 | { 79 | return $this->resolvePath($path); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/Migration/ProcedureMigration.php: -------------------------------------------------------------------------------- 1 | up($procedure); 32 | $down = $this->down($procedure); 33 | 34 | $this->migrationWriter->writeTo( 35 | $path = $this->makeMigrationPath($procedure->getName()), 36 | $this->setting->getStubPath(), 37 | $this->makeMigrationClassName($procedure->getName()), 38 | new Collection([$up]), 39 | new Collection([$down]), 40 | MigrationFileType::PROCEDURE, 41 | ); 42 | 43 | return $path; 44 | } 45 | 46 | /** 47 | * Write stored procedure migration into temporary file. 48 | */ 49 | public function writeToTemp(Procedure $procedure): void 50 | { 51 | $up = $this->up($procedure); 52 | $down = $this->down($procedure); 53 | 54 | $this->squashWriter->writeToTemp(new Collection([$up]), new Collection([$down])); 55 | } 56 | 57 | /** 58 | * Generates `up` db statement for stored procedure. 59 | */ 60 | private function up(Procedure $procedure): DBUnpreparedBlueprint 61 | { 62 | return new DBUnpreparedBlueprint($procedure->getDefinition()); 63 | } 64 | 65 | /** 66 | * Generates `down` db statement for stored procedure. 67 | */ 68 | private function down(Procedure $procedure): DBUnpreparedBlueprint 69 | { 70 | return new DBUnpreparedBlueprint($procedure->getDropDefinition()); 71 | } 72 | 73 | /** 74 | * Makes class name for stored procedure migration. 75 | * 76 | * @param string $procedure Stored procedure name. 77 | */ 78 | private function makeMigrationClassName(string $procedure): string 79 | { 80 | return $this->migrationNameHelper->makeClassName( 81 | $this->setting->getProcedureFilename(), 82 | $procedure, 83 | ); 84 | } 85 | 86 | /** 87 | * Makes file path for stored procedure migration. 88 | * 89 | * @param string $procedure Stored procedure name. 90 | */ 91 | private function makeMigrationPath(string $procedure): string 92 | { 93 | return $this->migrationNameHelper->makeFilename( 94 | $this->setting->getProcedureFilename(), 95 | $this->setting->getDateForMigrationFilename(), 96 | $procedure, 97 | ); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/Migration/Squash.php: -------------------------------------------------------------------------------- 1 | squashWriter->cleanTemps(); 26 | } 27 | 28 | /** 29 | * Squash temporary paths into single migration file. 30 | * 31 | * @return string Squashed migration file path. 32 | */ 33 | public function squashMigrations(): string 34 | { 35 | $path = $this->migrationNameHelper->makeFilename( 36 | $this->setting->getTableFilename(), 37 | $this->setting->getDateForMigrationFilename(), 38 | DB::getDatabaseName(), 39 | ); 40 | 41 | $className = $this->migrationNameHelper->makeClassName( 42 | $this->setting->getTableFilename(), 43 | DB::getDatabaseName(), 44 | ); 45 | $this->squashWriter->squashMigrations($path, $this->setting->getStubPath(), $className); 46 | return $path; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Migration/ViewMigration.php: -------------------------------------------------------------------------------- 1 | up($view); 35 | $down = $this->down($view); 36 | 37 | $this->migrationWriter->writeTo( 38 | $path = $this->makeMigrationPath($view->getName()), 39 | $this->setting->getStubPath(), 40 | $this->makeMigrationClassName($view->getName()), 41 | new Collection([$up]), 42 | new Collection([$down]), 43 | MigrationFileType::VIEW, 44 | ); 45 | 46 | return $path; 47 | } 48 | 49 | /** 50 | * Write view migration into temporary file. 51 | */ 52 | public function writeToTemp(View $view): void 53 | { 54 | $up = $this->up($view); 55 | $down = $this->down($view); 56 | 57 | $this->squashWriter->writeToTemp(new Collection([$up]), new Collection([$down])); 58 | } 59 | 60 | /** 61 | * Generates `up` db statement for view. 62 | */ 63 | private function up(View $view): DBStatementBlueprint 64 | { 65 | return new DBStatementBlueprint($view->getDefinition()); 66 | } 67 | 68 | /** 69 | * Generates `down` db statement for view. 70 | */ 71 | private function down(View $view): DBStatementBlueprint 72 | { 73 | return new DBStatementBlueprint($view->getDropDefinition()); 74 | } 75 | 76 | /** 77 | * Makes class name for view migration. 78 | * 79 | * @param string $view View name. 80 | */ 81 | private function makeMigrationClassName(string $view): string 82 | { 83 | $withoutPrefix = $this->stripTablePrefix($view); 84 | return $this->migrationNameHelper->makeClassName( 85 | $this->setting->getViewFilename(), 86 | $withoutPrefix, 87 | ); 88 | } 89 | 90 | /** 91 | * Makes file path for view migration. 92 | * 93 | * @param string $view View name. 94 | */ 95 | private function makeMigrationPath(string $view): string 96 | { 97 | $withoutPrefix = $this->stripTablePrefix($view); 98 | return $this->migrationNameHelper->makeFilename( 99 | $this->setting->getViewFilename(), 100 | $this->setting->getDateForMigrationFilename(), 101 | $withoutPrefix, 102 | ); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/Migration/Writer/MigrationStub.php: -------------------------------------------------------------------------------- 1 | $use, 37 | '{{ class }}' => $className, 38 | '{{ up }}' => $upContent, 39 | '{{ down }}' => $downContent, 40 | ]; 41 | return str_replace(array_keys($replace), $replace, $content); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Migration/Writer/MigrationWriter.php: -------------------------------------------------------------------------------- 1 | $up Blueprint of migration `up`. 25 | * @param \Illuminate\Support\Collection $down Blueprint of migration `down`. 26 | */ 27 | public function writeTo( 28 | string $path, 29 | string $stubPath, 30 | string $className, 31 | Collection $up, 32 | Collection $down, 33 | MigrationFileType $migrationFileType, 34 | ): void { 35 | try { 36 | $stub = $this->migrationStub->getStub($stubPath); 37 | 38 | $upString = $this->prettifyToString($up); 39 | $downString = $this->prettifyToString($down); 40 | 41 | $useDBFacade = false; 42 | 43 | if (Str::contains($upString . $downString, 'DB::')) { 44 | $useDBFacade = true; 45 | } 46 | 47 | $use = implode(Space::LINE_BREAK->value, $this->getNamespaces($migrationFileType, $useDBFacade)); 48 | 49 | // Create directory if it doesn't exist 50 | $directory = dirname($path); 51 | 52 | if (!File::exists($directory)) { 53 | File::makeDirectory($directory, 0755, true); 54 | } 55 | 56 | File::put( 57 | $path, 58 | $this->migrationStub->populateStub($stub, $use, $className, $upString, $downString), 59 | ); 60 | } catch (FileNotFoundException) { 61 | // Do nothing. 62 | } 63 | } 64 | 65 | /** 66 | * @return string[] 67 | */ 68 | private function getNamespaces(MigrationFileType $migrationFileType, bool $useDBFacade): array 69 | { 70 | if ( 71 | $migrationFileType === MigrationFileType::VIEW 72 | || $migrationFileType === MigrationFileType::PROCEDURE 73 | ) { 74 | return [ 75 | 'use Illuminate\Database\Migrations\Migration;', 76 | 'use Illuminate\Support\Facades\DB;', 77 | ]; 78 | } 79 | 80 | $imports = [ 81 | 'use Illuminate\Database\Migrations\Migration;', 82 | 'use Illuminate\Database\Schema\Blueprint;', 83 | ]; 84 | 85 | if ($useDBFacade) { 86 | $imports[] = 'use Illuminate\Support\Facades\DB;'; 87 | } 88 | 89 | // Push at the last to maintain alphabetically sort. 90 | $imports[] = 'use Illuminate\Support\Facades\Schema;'; 91 | 92 | return $imports; 93 | } 94 | 95 | /** 96 | * Convert collection of blueprints to string and prettify and tabular. 97 | * 98 | * @param \Illuminate\Support\Collection $blueprints 99 | */ 100 | private function prettifyToString(Collection $blueprints): string 101 | { 102 | return $blueprints->map(static fn (WritableBlueprint $blueprint) => $blueprint->toString())->implode(Space::LINE_BREAK->value . Space::TAB->value . Space::TAB->value); // Add tab to prettify 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/Migration/Writer/SquashWriter.php: -------------------------------------------------------------------------------- 1 | $upBlueprints 24 | * @param \Illuminate\Support\Collection $downBlueprints 25 | */ 26 | public function writeToTemp(Collection $upBlueprints, Collection $downBlueprints): void 27 | { 28 | $upTempPath = $this->migrationNameHelper->makeUpTempPath(); 29 | $prettySpace = $this->getSpaceIfFileExists($upTempPath); 30 | $upString = $upBlueprints->map(static fn (WritableBlueprint $up) => $up->toString())->implode(Space::LINE_BREAK->value . Space::TAB->value . Space::TAB->value); // Add tab to prettify 31 | File::append($upTempPath, $prettySpace . $upString); 32 | 33 | $downTempPath = $this->migrationNameHelper->makeDownTempPath(); 34 | $prettySpace = $this->getSpaceIfFileExists($downTempPath); 35 | $downString = $downBlueprints->map(static fn (WritableBlueprint $down) => $down->toString())->implode(Space::LINE_BREAK->value . Space::TAB->value . Space::TAB->value); // Add tab to prettify 36 | File::prepend($downTempPath, $downString . $prettySpace); 37 | } 38 | 39 | /** 40 | * Cleans all migration temporary paths. 41 | */ 42 | public function cleanTemps(): void 43 | { 44 | File::delete($this->migrationNameHelper->makeUpTempPath()); 45 | File::delete($this->migrationNameHelper->makeDownTempPath()); 46 | } 47 | 48 | /** 49 | * Squash temporary paths into single migration file. 50 | * 51 | * @param string $path Migration file destination path. 52 | * @param string $stubPath Migration stub file path. 53 | */ 54 | public function squashMigrations(string $path, string $stubPath, string $className): void 55 | { 56 | $use = implode(Space::LINE_BREAK->value, [ 57 | 'use Illuminate\Database\Migrations\Migration;', 58 | 'use Illuminate\Database\Schema\Blueprint;', 59 | 'use Illuminate\Support\Facades\DB;', 60 | 'use Illuminate\Support\Facades\Schema;', 61 | ]); 62 | 63 | $upTempPath = $this->migrationNameHelper->makeUpTempPath(); 64 | $downTempPath = $this->migrationNameHelper->makeDownTempPath(); 65 | 66 | try { 67 | File::put( 68 | $path, 69 | $this->migrationStub->populateStub( 70 | $this->migrationStub->getStub($stubPath), 71 | $use, 72 | $className, 73 | File::get($upTempPath), 74 | File::get($downTempPath), 75 | ), 76 | ); 77 | } catch (FileNotFoundException) { 78 | // Do nothing. 79 | } finally { 80 | File::delete($upTempPath); 81 | File::delete($downTempPath); 82 | } 83 | } 84 | 85 | private function getSpaceIfFileExists(string $path): string 86 | { 87 | if (!File::exists($path)) { 88 | return ''; 89 | } 90 | 91 | return Space::LINE_BREAK->value . Space::LINE_BREAK->value . Space::TAB->value . Space::TAB->value; 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/Repositories/Entities/MariaDB/CheckConstraint.php: -------------------------------------------------------------------------------- 1 | mapWithKeys(static fn ($item, $key) => [strtolower($key) => $item]); 34 | 35 | $this->constraintCatalog = $lowerKey['constraint_catalog']; 36 | $this->constraintSchema = $lowerKey['constraint_schema']; 37 | $this->tableName = $lowerKey['table_name']; 38 | $this->constraintName = $lowerKey['constraint_name']; 39 | 40 | if (isset($lowerKey['level'])) { 41 | $this->level = $lowerKey['level']; 42 | } 43 | 44 | $this->checkClause = $lowerKey['check_clause']; 45 | } 46 | 47 | /** 48 | * Always contains the string 'def'. 49 | */ 50 | public function getConstraintCatalog(): string 51 | { 52 | return $this->constraintCatalog; 53 | } 54 | 55 | /** 56 | * Database name. 57 | */ 58 | public function getConstraintSchema(): string 59 | { 60 | return $this->constraintSchema; 61 | } 62 | 63 | /** 64 | * Table name. 65 | */ 66 | public function getTableName(): string 67 | { 68 | return $this->tableName; 69 | } 70 | 71 | /** 72 | * Constraint name. 73 | */ 74 | public function getConstraintName(): string 75 | { 76 | return $this->constraintName; 77 | } 78 | 79 | /** 80 | * Type of the constraint ('Column' or 'Table'). From MariaDB 10.5.10 81 | * 82 | * @return string|null NULL if MariaDB < 10.5.10 83 | */ 84 | public function getLevel(): ?string 85 | { 86 | return $this->level; 87 | } 88 | 89 | /** 90 | * Constraint clause. 91 | */ 92 | public function getCheckClause(): string 93 | { 94 | return $this->checkClause; 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/Repositories/Entities/MySQL/ShowColumn.php: -------------------------------------------------------------------------------- 1 | mapWithKeys(static fn ($item, $key) => [strtolower($key) => $item]); 35 | 36 | $this->field = $lowerKey['field']; 37 | $this->type = $lowerKey['type']; 38 | $this->null = $lowerKey['null']; 39 | $this->key = $lowerKey['key']; 40 | $this->default = $lowerKey['default']; 41 | $this->extra = $lowerKey['extra']; 42 | } 43 | 44 | public function getField(): string 45 | { 46 | return $this->field; 47 | } 48 | 49 | public function getType(): string 50 | { 51 | return $this->type; 52 | } 53 | 54 | public function getNull(): string 55 | { 56 | return $this->null; 57 | } 58 | 59 | public function getKey(): string 60 | { 61 | return $this->key; 62 | } 63 | 64 | public function getDefault(): ?string 65 | { 66 | return $this->default; 67 | } 68 | 69 | public function getExtra(): string 70 | { 71 | return $this->extra; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/Repositories/Entities/PgSQL/IndexDefinition.php: -------------------------------------------------------------------------------- 1 | tableName; 17 | } 18 | 19 | public function getIndexName(): string 20 | { 21 | return $this->indexName; 22 | } 23 | 24 | public function getIndexDef(): string 25 | { 26 | return $this->indexDef; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Repositories/Entities/ProcedureDefinition.php: -------------------------------------------------------------------------------- 1 | name; 14 | } 15 | 16 | public function getDefinition(): string 17 | { 18 | return $this->definition; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Repositories/Entities/SQLSrv/ColumnDefinition.php: -------------------------------------------------------------------------------- 1 | mapWithKeys(static fn ($item, $key) => [strtolower($key) => $item]); 34 | 35 | $this->name = $lowerKey['name']; 36 | $this->type = $lowerKey['type']; 37 | $this->length = $lowerKey['length']; 38 | $this->notnull = $lowerKey['notnull']; 39 | $this->default = $lowerKey['default']; 40 | $this->scale = $lowerKey['scale']; 41 | $this->precision = $lowerKey['precision']; 42 | $this->autoincrement = $lowerKey['autoincrement']; 43 | $this->collation = $lowerKey['collation']; 44 | $this->comment = $lowerKey['comment']; 45 | } 46 | 47 | public function getName(): string 48 | { 49 | return $this->name; 50 | } 51 | 52 | public function getType(): string 53 | { 54 | return $this->type; 55 | } 56 | 57 | public function getLength(): int 58 | { 59 | return $this->length; 60 | } 61 | 62 | public function isNotnull(): bool 63 | { 64 | return $this->notnull; 65 | } 66 | 67 | public function getDefault(): ?string 68 | { 69 | return $this->default; 70 | } 71 | 72 | public function getScale(): int 73 | { 74 | return $this->scale; 75 | } 76 | 77 | public function getPrecision(): int 78 | { 79 | return $this->precision; 80 | } 81 | 82 | public function isAutoincrement(): bool 83 | { 84 | return $this->autoincrement; 85 | } 86 | 87 | public function getCollation(): ?string 88 | { 89 | return $this->collation; 90 | } 91 | 92 | public function getComment(): ?string 93 | { 94 | return $this->comment; 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/Repositories/MariaDBRepository.php: -------------------------------------------------------------------------------- 1 | 49 | */ 50 | public function getProcedures(): Collection 51 | { 52 | $list = new Collection(); 53 | $procedures = DB::select("SHOW PROCEDURE STATUS WHERE Db = '" . DB::getDatabaseName() . "'"); 54 | 55 | foreach ($procedures as $procedure) { 56 | // Change all keys to lowercase. 57 | $procedureArr = array_change_key_case((array) $procedure); 58 | $createProc = $this->getProcedure($procedureArr['name']); 59 | 60 | // Change all keys to lowercase. 61 | $createProcArr = array_change_key_case((array) $createProc); 62 | 63 | // https://mariadb.com/kb/en/show-create-procedure/ 64 | if ($createProcArr['create procedure'] === null || $createProcArr['create procedure'] === '') { 65 | continue; 66 | } 67 | 68 | $list->push(new ProcedureDefinition($procedureArr['name'], $createProcArr['create procedure'])); 69 | } 70 | 71 | return $list; 72 | } 73 | 74 | /** 75 | * Get the SRID by table and column name. 76 | */ 77 | public function getSrID(string $table, string $column): ?int 78 | { 79 | try { 80 | $srsID = DB::selectOne( 81 | "SELECT SRS_ID 82 | FROM information_schema.COLUMNS 83 | WHERE TABLE_SCHEMA = '" . DB::getDatabaseName() . "' 84 | AND TABLE_NAME = '" . $table . "' 85 | AND COLUMN_NAME = '" . $column . "'", 86 | ); 87 | } catch (QueryException $exception) { 88 | if ( 89 | // `SRS_ID` available since MySQL 8.0.3. 90 | // https://dev.mysql.com/doc/relnotes/mysql/8.0/en/news-8-0-3.html 91 | Str::contains( 92 | $exception->getMessage(), 93 | "SQLSTATE[42S22]: Column not found: 1054 Unknown column 'SRS_ID'", 94 | true, 95 | ) 96 | ) { 97 | return null; 98 | } 99 | 100 | throw $exception; 101 | } 102 | 103 | if ($srsID === null) { 104 | return null; 105 | } 106 | 107 | $srsIDArr = array_change_key_case((array) $srsID); 108 | return $srsIDArr['srs_id'] ?? null; 109 | } 110 | 111 | /** 112 | * Get single stored procedure by name. 113 | * 114 | * @param string $procedure Procedure name. 115 | */ 116 | private function getProcedure(string $procedure): mixed 117 | { 118 | return DB::selectOne("SHOW CREATE PROCEDURE $procedure"); 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/Repositories/PgSQLRepository.php: -------------------------------------------------------------------------------- 1 | getConfig('search_path') ?: DB::connection()->getConfig('schema'); 21 | 22 | $result = DB::selectOne( 23 | "SELECT pgc.conname AS constraint_name, 24 | pgc.contype, 25 | ccu.table_schema AS table_schema, 26 | ccu.table_name, 27 | ccu.column_name, 28 | pg_get_constraintdef(pgc.oid) AS definition 29 | FROM pg_constraint pgc 30 | JOIN pg_namespace nsp ON nsp.oid = pgc.connamespace 31 | JOIN pg_class cls ON pgc.conrelid = cls.oid 32 | LEFT JOIN information_schema.constraint_column_usage ccu 33 | ON pgc.conname = ccu.constraint_name 34 | AND nsp.nspname = ccu.constraint_schema 35 | WHERE contype ='c' 36 | AND nsp.nspname = '$searchPath' 37 | AND ccu.table_name='$table' 38 | AND ccu.column_name='$column'", 39 | ); 40 | return $result?->definition; 41 | } 42 | 43 | /** 44 | * Get a list of fulltext indexes. 45 | * 46 | * @param string $table Table name. 47 | * @return \Illuminate\Support\Collection 48 | */ 49 | public function getFulltextIndexes(string $table): Collection 50 | { 51 | $columns = DB::select( 52 | "SELECT tablename, 53 | indexname, 54 | indexdef 55 | FROM pg_indexes 56 | WHERE tablename = '$table' 57 | AND indexdef LIKE '%to_tsvector(%' 58 | ORDER BY indexname", 59 | ); 60 | $definitions = new Collection(); 61 | 62 | if (count($columns) > 0) { 63 | foreach ($columns as $column) { 64 | $definitions->push( 65 | new IndexDefinition( 66 | $column->tablename, 67 | $column->indexname, 68 | $column->indexdef, 69 | ), 70 | ); 71 | } 72 | } 73 | 74 | return $definitions; 75 | } 76 | 77 | /** 78 | * Get a list of stored procedures. 79 | * 80 | * @return \Illuminate\Support\Collection 81 | */ 82 | public function getProcedures(): Collection 83 | { 84 | $list = new Collection(); 85 | 86 | $searchPath = DB::connection()->getConfig('search_path') ?: DB::connection()->getConfig('schema'); 87 | 88 | $procedures = DB::select( 89 | "SELECT proname, pg_get_functiondef(pg_proc.oid) AS definition 90 | FROM pg_catalog.pg_proc 91 | JOIN pg_namespace ON pg_catalog.pg_proc.pronamespace = pg_namespace.oid 92 | WHERE prokind = 'p' 93 | AND pg_namespace.nspname = '$searchPath'", 94 | ); 95 | 96 | foreach ($procedures as $procedure) { 97 | if ($procedure->definition === null || $procedure->definition === '') { 98 | continue; 99 | } 100 | 101 | $definition = str_replace('$procedure', '$', $procedure->definition); 102 | $list->push(new ProcedureDefinition($procedure->proname, $definition)); 103 | } 104 | 105 | return $list; 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/Repositories/Repository.php: -------------------------------------------------------------------------------- 1 | getStringLiteralQuoteCharacter(); 19 | 20 | return $c . str_replace($c, $c . $c, $str) . $c; 21 | } 22 | 23 | /** 24 | * Gets the character used for string literal quoting. 25 | */ 26 | protected function getStringLiteralQuoteCharacter(): string 27 | { 28 | return "'"; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Repositories/SQLiteRepository.php: -------------------------------------------------------------------------------- 1 | sql; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Schema/Models/Column.php: -------------------------------------------------------------------------------- 1 | 23 | */ 24 | public function getColumns(): Collection; 25 | 26 | /** 27 | * Get a list of user-defined type columns. 28 | * 29 | * @return \Illuminate\Support\Collection 30 | */ 31 | public function getUdtColumns(): Collection; 32 | 33 | /** 34 | * Get a list of indexes. 35 | * 36 | * @return \Illuminate\Support\Collection 37 | */ 38 | public function getIndexes(): Collection; 39 | 40 | /** 41 | * Get the table collation. 42 | */ 43 | public function getCollation(): ?string; 44 | } 45 | -------------------------------------------------------------------------------- /src/Schema/Models/UDTColumn.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | public function getTableNames(): Collection; 16 | 17 | /** 18 | * Get a table by name. 19 | * 20 | * @param string $name Table name. 21 | */ 22 | public function getTable(string $name): Table; 23 | 24 | /** 25 | * Get a list of view names. 26 | * 27 | * @return \Illuminate\Support\Collection 28 | */ 29 | public function getViewNames(): Collection; 30 | 31 | /** 32 | * Get a list of views. 33 | * 34 | * @return \Illuminate\Support\Collection 35 | */ 36 | public function getViews(): Collection; 37 | 38 | /** 39 | * Get a list of foreign keys. 40 | * 41 | * @return \Illuminate\Support\Collection 42 | */ 43 | public function getForeignKeys(string $table): Collection; 44 | 45 | /** 46 | * Get a list of store procedures. 47 | * 48 | * @return \Illuminate\Support\Collection 49 | */ 50 | public function getProcedures(): Collection; 51 | } 52 | -------------------------------------------------------------------------------- /src/Setting.php: -------------------------------------------------------------------------------- 1 | defaultConnection; 42 | } 43 | 44 | public function setDefaultConnection(string $defaultConnection): void 45 | { 46 | $this->defaultConnection = $defaultConnection; 47 | } 48 | 49 | public function isUseDBCollation(): bool 50 | { 51 | return $this->useDBCollation; 52 | } 53 | 54 | public function setUseDBCollation(bool $useDBCollation): void 55 | { 56 | $this->useDBCollation = $useDBCollation; 57 | } 58 | 59 | public function isIgnoreIndexNames(): bool 60 | { 61 | return $this->ignoreIndexNames; 62 | } 63 | 64 | public function setIgnoreIndexNames(bool $ignoreIndexNames): void 65 | { 66 | $this->ignoreIndexNames = $ignoreIndexNames; 67 | } 68 | 69 | public function isIgnoreForeignKeyNames(): bool 70 | { 71 | return $this->ignoreForeignKeyNames; 72 | } 73 | 74 | public function setIgnoreForeignKeyNames(bool $ignoreForeignKeyNames): void 75 | { 76 | $this->ignoreForeignKeyNames = $ignoreForeignKeyNames; 77 | } 78 | 79 | public function getPath(): string 80 | { 81 | return $this->path; 82 | } 83 | 84 | public function setPath(string $path): void 85 | { 86 | $this->path = $path; 87 | } 88 | 89 | public function getStubPath(): string 90 | { 91 | return $this->stubPath; 92 | } 93 | 94 | public function setStubPath(string $stubPath): void 95 | { 96 | $this->stubPath = $stubPath; 97 | } 98 | 99 | public function isSquash(): bool 100 | { 101 | return $this->squash; 102 | } 103 | 104 | public function setSquash(bool $squash): void 105 | { 106 | $this->squash = $squash; 107 | } 108 | 109 | public function getTableFilename(): string 110 | { 111 | return $this->tableFilename; 112 | } 113 | 114 | public function setTableFilename(string $tableFilename): void 115 | { 116 | $this->tableFilename = $tableFilename; 117 | } 118 | 119 | public function getViewFilename(): string 120 | { 121 | return $this->viewFilename; 122 | } 123 | 124 | public function setViewFilename(string $viewFilename): void 125 | { 126 | $this->viewFilename = $viewFilename; 127 | } 128 | 129 | public function getProcedureFilename(): string 130 | { 131 | return $this->procedureFilename; 132 | } 133 | 134 | public function setProcedureFilename(string $procedureFilename): void 135 | { 136 | $this->procedureFilename = $procedureFilename; 137 | } 138 | 139 | public function getFkFilename(): string 140 | { 141 | return $this->fkFilename; 142 | } 143 | 144 | public function setFkFilename(string $fkFilename): void 145 | { 146 | $this->fkFilename = $fkFilename; 147 | } 148 | 149 | public function getDate(): Carbon 150 | { 151 | return $this->date; 152 | } 153 | 154 | public function getDateForMigrationFilename(): string 155 | { 156 | return $this->date->format('Y_m_d_His'); 157 | } 158 | 159 | public function setDate(Carbon $date): void 160 | { 161 | $this->date = $date; 162 | } 163 | 164 | public function isWithHasTable(): bool 165 | { 166 | return $this->withHasTable; 167 | } 168 | 169 | public function setWithHasTable(bool $withHasTable): void 170 | { 171 | $this->withHasTable = $withHasTable; 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /src/Support/AssetNameQuote.php: -------------------------------------------------------------------------------- 1 | value: 33 | return $value === '*' ? $value : '[' . str_replace(']', ']]', $value) . ']'; 34 | 35 | case Driver::MARIADB->value: 36 | case Driver::MYSQL->value: 37 | return $value === '*' ? $value : '`' . str_replace('`', '``', $value) . '`'; 38 | 39 | default: 40 | if ($value !== '*') { 41 | return '"' . str_replace('"', '""', $value) . '"'; 42 | } 43 | 44 | return $value; 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Support/CheckLaravelVersion.php: -------------------------------------------------------------------------------- 1 | atLeastLaravelVersion('12.0') || version_compare(App::version(), '12.x-dev', '=='); 12 | } 13 | 14 | private function atLeastLaravelVersion(string $version): bool 15 | { 16 | return version_compare(App::version(), $version, '>='); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Support/IndexNameHelper.php: -------------------------------------------------------------------------------- 1 | setting->isIgnoreIndexNames()) { 24 | return true; 25 | } 26 | 27 | if ( 28 | $index->getType() === IndexType::PRIMARY 29 | && $index->getName() === '' 30 | ) { 31 | return true; 32 | } 33 | 34 | $indexName = strtolower($table . '_' . implode('_', $index->getColumns()) . '_' . $index->getType()->value); 35 | $indexName = (string) str_replace(['-', '.'], '_', $indexName); 36 | return $indexName === $index->getName(); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Support/MigrationNameHelper.php: -------------------------------------------------------------------------------- 1 | setting->getPath(); 22 | return "$path/lmg-up-temp"; 23 | } 24 | 25 | /** 26 | * Makes file path for temporary `down` migration. 27 | */ 28 | public function makeDownTempPath(): string 29 | { 30 | $path = $this->setting->getPath(); 31 | return "$path/lmg-down-temp"; 32 | } 33 | 34 | /** 35 | * Makes migration filename by given naming pattern. 36 | * 37 | * @param string $pattern Naming pattern for migration filename. 38 | * @param string $datetime Current datetime for filename prefix. 39 | * @param string $name Name. 40 | */ 41 | public function makeFilename(string $pattern, string $datetime, string $name): string 42 | { 43 | $path = $this->setting->getPath(); 44 | $filename = $pattern; 45 | $replace = [ 46 | '[datetime]' => $datetime, 47 | '[name]' => $this->removeSpecialCharacters($name), 48 | ]; 49 | $filename = str_replace(array_keys($replace), $replace, $filename); 50 | return "$path/$filename"; 51 | } 52 | 53 | /** 54 | * Makes migration class name by given naming pattern. 55 | * 56 | * @param string $pattern Naming pattern for class. 57 | * @param string $name Name. 58 | */ 59 | public function makeClassName(string $pattern, string $name): string 60 | { 61 | $className = $pattern; 62 | $replace = [ 63 | '[datetime]_' => '', 64 | '[name]' => $this->removeSpecialCharacters($name), 65 | '.php' => '', 66 | ]; 67 | return Str::studly(str_replace(array_keys($replace), $replace, $className)); 68 | } 69 | 70 | /** 71 | * Remove special characters except `_`. 72 | * 73 | * @param string $table Table name. 74 | * @return string Table name without prefix. 75 | */ 76 | private function removeSpecialCharacters(string $table): string 77 | { 78 | return (string) preg_replace('/[^a-zA-Z0-9_]/', '_', $table); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/Support/Regex.php: -------------------------------------------------------------------------------- 1 | 0) { 60 | return $output[1]; 61 | } 62 | 63 | return null; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/Support/TableName.php: -------------------------------------------------------------------------------- 1 |