From 980e8d5cd0bfe72f2cd878a785e8d2c22eefaf18 Mon Sep 17 00:00:00 2001 From: Hafez Divandari Date: Thu, 4 Jul 2024 18:01:28 +0330 Subject: [PATCH] [11.x] Enhance database migrations (#51373) * enhance migrations * formatting * support drop column on legacy sqlite * formatting * fix tests * force re-run tests * formatting * fix altering a table that has a column with zero as default fixes #51747 * formatting * formatting * formatting * formatting * formatting * Formatting --------- Co-authored-by: Taylor Otwell --- src/Illuminate/Database/Schema/Blueprint.php | 106 +++++++- .../Database/Schema/BlueprintState.php | 254 ++++++++++++++++++ .../Database/Schema/Grammars/Grammar.php | 24 +- .../Database/Schema/Grammars/MySqlGrammar.php | 28 +- .../Schema/Grammars/PostgresGrammar.php | 33 ++- .../Schema/Grammars/SQLiteGrammar.php | 189 +++++++------ .../Schema/Grammars/SqlServerGrammar.php | 28 +- .../DatabaseMariaDbSchemaGrammarTest.php | 76 ++++-- .../DatabaseMySqlSchemaGrammarTest.php | 76 ++++-- .../DatabasePostgresSchemaGrammarTest.php | 65 +++-- .../DatabaseSQLiteSchemaGrammarTest.php | 84 +++++- .../Database/DatabaseSchemaBlueprintTest.php | 23 +- .../DatabaseSqlServerSchemaGrammarTest.php | 49 +++- .../Database/DatabaseSchemaBlueprintTest.php | 33 +-- .../Database/SchemaBuilderTest.php | 129 ++++++++- 15 files changed, 946 insertions(+), 251 deletions(-) create mode 100644 src/Illuminate/Database/Schema/BlueprintState.php diff --git a/src/Illuminate/Database/Schema/Blueprint.php b/src/Illuminate/Database/Schema/Blueprint.php index 3dc3e24ab343..a3cddae4bd28 100755 --- a/src/Illuminate/Database/Schema/Blueprint.php +++ b/src/Illuminate/Database/Schema/Blueprint.php @@ -8,6 +8,7 @@ use Illuminate\Database\Query\Expression; use Illuminate\Database\Schema\Grammars\Grammar; use Illuminate\Database\Schema\Grammars\MySqlGrammar; +use Illuminate\Database\Schema\Grammars\SQLiteGrammar; use Illuminate\Support\Fluent; use Illuminate\Support\Traits\Macroable; @@ -78,6 +79,13 @@ class Blueprint */ public $after; + /** + * The blueprint state instance. + * + * @var \Illuminate\Database\Schema\BlueprintState|null + */ + protected $state; + /** * Create a new schema blueprint. * @@ -136,6 +144,10 @@ public function toSql(Connection $connection, Grammar $grammar) $method = 'compile'.ucfirst($command->name); if (method_exists($grammar, $method) || $grammar::hasMacro($method)) { + if ($this->hasState()) { + $this->state->update($command); + } + if (! is_null($sql = $grammar->$method($this, $command, $connection))) { $statements = array_merge($statements, (array) $sql); } @@ -161,6 +173,8 @@ protected function ensureCommandsAreValid(Connection $connection) /** * Get all of the commands matching the given names. * + * @deprecated Will be removed in a future Laravel version. + * * @param array $names * @return \Illuminate\Support\Collection */ @@ -180,17 +194,20 @@ protected function commandsNamed(array $names) */ protected function addImpliedCommands(Connection $connection, Grammar $grammar) { - if (count($this->getAddedColumns()) > 0 && ! $this->creating()) { - array_unshift($this->commands, $this->createCommand('add')); - } - - if (count($this->getChangedColumns()) > 0 && ! $this->creating()) { - array_unshift($this->commands, $this->createCommand('change')); - } - $this->addFluentIndexes($connection, $grammar); $this->addFluentCommands($connection, $grammar); + + if (! $this->creating()) { + $this->commands = array_map( + fn ($command) => $command instanceof ColumnDefinition + ? $this->createCommand($command->change ? 'change' : 'add', ['column' => $command]) + : $command, + $this->commands + ); + + $this->addAlterCommands($connection, $grammar); + } } /** @@ -260,6 +277,48 @@ public function addFluentCommands(Connection $connection, Grammar $grammar) } } + /** + * Add the alter commands if whenever needed. + * + * @param \Illuminate\Database\Connection $connection + * @param \Illuminate\Database\Schema\Grammars\Grammar $grammar + * @return void + */ + public function addAlterCommands(Connection $connection, Grammar $grammar) + { + if (! $grammar instanceof SQLiteGrammar) { + return; + } + + $alterCommands = $grammar->getAlterCommands($connection); + + [$commands, $lastCommandWasAlter, $hasAlterCommand] = [ + [], false, false, + ]; + + foreach ($this->commands as $command) { + if (in_array($command->name, $alterCommands)) { + $hasAlterCommand = true; + $lastCommandWasAlter = true; + } elseif ($lastCommandWasAlter) { + $commands[] = $this->createCommand('alter'); + $lastCommandWasAlter = false; + } + + $commands[] = $command; + } + + if ($lastCommandWasAlter) { + $commands[] = $this->createCommand('alter'); + } + + if ($hasAlterCommand) { + $this->state = new BlueprintState($this, $connection, $grammar); + } + + $this->commands = $commands; + } + /** * Determine if the blueprint has a create command. * @@ -1634,6 +1693,10 @@ protected function addColumnDefinition($definition) { $this->columns[] = $definition; + if (! $this->creating()) { + $this->commands[] = $definition; + } + if ($this->after) { $definition->after($this->after); @@ -1671,6 +1734,10 @@ public function removeColumn($name) return $c['name'] != $name; })); + $this->commands = array_values(array_filter($this->commands, function ($c) use ($name) { + return ! $c instanceof ColumnDefinition || $c['name'] != $name; + })); + return $this; } @@ -1740,6 +1807,27 @@ public function getCommands() return $this->commands; } + /* + * Determine if the blueprint has state. + * + * @param mixed $name + * @return bool + */ + private function hasState(): bool + { + return ! is_null($this->state); + } + + /** + * Get the state of the blueprint. + * + * @return \Illuminate\Database\Schema\BlueprintState + */ + public function getState() + { + return $this->state; + } + /** * Get the columns on the blueprint that should be added. * @@ -1755,6 +1843,8 @@ public function getAddedColumns() /** * Get the columns on the blueprint that should be changed. * + * @deprecated Will be removed in a future Laravel version. + * * @return \Illuminate\Database\Schema\ColumnDefinition[] */ public function getChangedColumns() diff --git a/src/Illuminate/Database/Schema/BlueprintState.php b/src/Illuminate/Database/Schema/BlueprintState.php new file mode 100644 index 000000000000..d617f58fac41 --- /dev/null +++ b/src/Illuminate/Database/Schema/BlueprintState.php @@ -0,0 +1,254 @@ +blueprint = $blueprint; + $this->connection = $connection; + $this->grammar = $grammar; + + $schema = $connection->getSchemaBuilder(); + $table = $blueprint->getTable(); + + $this->columns = collect($schema->getColumns($table))->map(fn ($column) => new ColumnDefinition([ + 'name' => $column['name'], + 'type' => $column['type_name'], + 'full_type_definition' => $column['type'], + 'nullable' => $column['nullable'], + 'default' => is_null($column['default']) ? null : new Expression($column['default']), + 'autoIncrement' => $column['auto_increment'], + 'collation' => $column['collation'], + 'comment' => $column['comment'], + 'virtualAs' => ! is_null($column['generation']) && $column['generation']['type'] === 'virtual' + ? $column['generation']['expression'] : null, + 'storedAs' => ! is_null($column['generation']) && $column['generation']['type'] === 'stored' + ? $column['generation']['expression'] : null, + ]))->all(); + + [$primary, $indexes] = collect($schema->getIndexes($table))->map(fn ($index) => new IndexDefinition([ + 'name' => match (true) { + $index['primary'] => 'primary', + $index['unique'] => 'unique', + default => 'index', + }, + 'index' => $index['name'], + 'columns' => $index['columns'], + ]))->partition(fn ($index) => $index->name === 'primary'); + + $this->indexes = $indexes->all(); + $this->primaryKey = $primary->first(); + + $this->foreignKeys = collect($schema->getForeignKeys($table))->map(fn ($foreignKey) => new ForeignKeyDefinition([ + 'columns' => $foreignKey['columns'], + 'on' => $foreignKey['foreign_table'], + 'references' => $foreignKey['foreign_columns'], + 'onUpdate' => $foreignKey['on_update'], + 'onDelete' => $foreignKey['on_delete'], + ]))->all(); + } + + /** + * Get the primary key. + * + * @return \Illuminate\Database\Schema\IndexDefinition|null + */ + public function getPrimaryKey() + { + return $this->primaryKey; + } + + /** + * Get the columns. + * + * @return \Illuminate\Database\Schema\ColumnDefinition[] + */ + public function getColumns() + { + return $this->columns; + } + + /** + * Get the indexes. + * + * @return \Illuminate\Database\Schema\IndexDefinition[] + */ + public function getIndexes() + { + return $this->indexes; + } + + /** + * Get the foreign keys. + * + * @return \Illuminate\Database\Schema\ForeignKeyDefinition[] + */ + public function getForeignKeys() + { + return $this->foreignKeys; + } + + /* + * Update the blueprint's state. + * + * @param \Illuminate\Support\Fluent $command + * @return void + */ + public function update(Fluent $command) + { + switch ($command->name) { + case 'alter': + // Already handled... + break; + + case 'add': + $this->columns[] = $command->column; + break; + + case 'change': + foreach ($this->columns as &$column) { + if ($column->name === $command->column->name) { + $column = $command->column; + break; + } + } + + break; + + case 'renameColumn': + foreach ($this->columns as $column) { + if ($column->name === $command->from) { + $column->name = $command->to; + break; + } + } + + if ($this->primaryKey) { + $this->primaryKey->columns = str_replace($command->from, $command->to, $this->primaryKey->columns); + } + + foreach ($this->indexes as $index) { + $index->columns = str_replace($command->from, $command->to, $index->columns); + } + + foreach ($this->foreignKeys as $foreignKey) { + $foreignKey->columns = str_replace($command->from, $command->to, $foreignKey->columns); + } + + break; + + case 'dropColumn': + $this->columns = array_values( + array_filter($this->columns, fn ($column) => ! in_array($column->name, $command->columns)) + ); + + break; + + case 'primary': + $this->primaryKey = $command; + break; + + case 'unique': + case 'index': + $this->indexes[] = $command; + break; + + case 'renameIndex': + foreach ($this->indexes as $index) { + if ($index->index === $command->from) { + $index->index = $command->to; + break; + } + } + + break; + + case 'foreign': + $this->foreignKeys[] = $command; + break; + + case 'dropPrimary': + $this->primaryKey = null; + break; + + case 'dropIndex': + case 'dropUnique': + $this->indexes = array_values( + array_filter($this->indexes, fn ($index) => $index->index !== $command->index) + ); + + break; + + case 'dropForeign': + $this->foreignKeys = array_values( + array_filter($this->foreignKeys, fn ($fk) => $fk->columns !== $command->columns) + ); + + break; + } + } +} diff --git a/src/Illuminate/Database/Schema/Grammars/Grammar.php b/src/Illuminate/Database/Schema/Grammars/Grammar.php index 86f4290b39d8..8efc92592988 100755 --- a/src/Illuminate/Database/Schema/Grammars/Grammar.php +++ b/src/Illuminate/Database/Schema/Grammars/Grammar.php @@ -187,17 +187,29 @@ protected function getColumns(Blueprint $blueprint) $columns = []; foreach ($blueprint->getAddedColumns() as $column) { - // Each of the column types has their own compiler functions, which are tasked - // with turning the column definition into its SQL format for this platform - // used by the connection. The column's modifiers are compiled and added. - $sql = $this->wrap($column).' '.$this->getType($column); - - $columns[] = $this->addModifiers($sql, $blueprint, $column); + $columns[] = $this->getColumn($blueprint, $column); } return $columns; } + /** + * Compile the column definition. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Database\Schema\ColumnDefinition $column + * @return string + */ + protected function getColumn(Blueprint $blueprint, $column) + { + // Each of the column types has their own compiler functions, which are tasked + // with turning the column definition into its SQL format for this platform + // used by the connection. The column's modifiers are compiled and added. + $sql = $this->wrap($column).' '.$this->getType($column); + + return $this->addModifiers($sql, $blueprint, $column); + } + /** * Get the SQL for the column data type. * diff --git a/src/Illuminate/Database/Schema/Grammars/MySqlGrammar.php b/src/Illuminate/Database/Schema/Grammars/MySqlGrammar.php index c61426d03fe6..f66317d619a6 100755 --- a/src/Illuminate/Database/Schema/Grammars/MySqlGrammar.php +++ b/src/Illuminate/Database/Schema/Grammars/MySqlGrammar.php @@ -290,9 +290,10 @@ protected function compileCreateEngine($sql, Connection $connection, Blueprint $ */ public function compileAdd(Blueprint $blueprint, Fluent $command) { - $columns = $this->prefixArray('add', $this->getColumns($blueprint)); - - return 'alter table '.$this->wrapTable($blueprint).' '.implode(', ', $columns); + return sprintf('alter table %s add %s', + $this->wrapTable($blueprint), + $this->getColumn($blueprint, $command->column) + ); } /** @@ -386,20 +387,17 @@ protected function compileLegacyRenameColumn(Blueprint $blueprint, Fluent $comma */ public function compileChange(Blueprint $blueprint, Fluent $command, Connection $connection) { - $columns = []; - - foreach ($blueprint->getChangedColumns() as $column) { - $sql = sprintf('%s %s%s %s', - is_null($column->renameTo) ? 'modify' : 'change', - $this->wrap($column), - is_null($column->renameTo) ? '' : ' '.$this->wrap($column->renameTo), - $this->getType($column) - ); + $column = $command->column; - $columns[] = $this->addModifiers($sql, $blueprint, $column); - } + $sql = sprintf('alter table %s %s %s%s %s', + $this->wrapTable($blueprint), + is_null($column->renameTo) ? 'modify' : 'change', + $this->wrap($column), + is_null($column->renameTo) ? '' : ' '.$this->wrap($column->renameTo), + $this->getType($column) + ); - return 'alter table '.$this->wrapTable($blueprint).' '.implode(', ', $columns); + return $this->addModifiers($sql, $blueprint, $column); } /** diff --git a/src/Illuminate/Database/Schema/Grammars/PostgresGrammar.php b/src/Illuminate/Database/Schema/Grammars/PostgresGrammar.php index db487e1509eb..a8f2249e91ce 100755 --- a/src/Illuminate/Database/Schema/Grammars/PostgresGrammar.php +++ b/src/Illuminate/Database/Schema/Grammars/PostgresGrammar.php @@ -214,9 +214,9 @@ public function compileCreate(Blueprint $blueprint, Fluent $command) */ public function compileAdd(Blueprint $blueprint, Fluent $command) { - return sprintf('alter table %s %s', + return sprintf('alter table %s add column %s', $this->wrapTable($blueprint), - implode(', ', $this->prefixArray('add column', $this->getColumns($blueprint))) + $this->getColumn($blueprint, $command->column) ); } @@ -249,29 +249,28 @@ public function compileAutoIncrementStartingValues(Blueprint $blueprint, Fluent */ public function compileChange(Blueprint $blueprint, Fluent $command, Connection $connection) { - $columns = []; + $column = $command->column; - foreach ($blueprint->getChangedColumns() as $column) { - $changes = ['type '.$this->getType($column).$this->modifyCollate($blueprint, $column)]; + $changes = ['type '.$this->getType($column).$this->modifyCollate($blueprint, $column)]; - foreach ($this->modifiers as $modifier) { - if ($modifier === 'Collate') { - continue; - } + foreach ($this->modifiers as $modifier) { + if ($modifier === 'Collate') { + continue; + } - if (method_exists($this, $method = "modify{$modifier}")) { - $constraints = (array) $this->{$method}($blueprint, $column); + if (method_exists($this, $method = "modify{$modifier}")) { + $constraints = (array) $this->{$method}($blueprint, $column); - foreach ($constraints as $constraint) { - $changes[] = $constraint; - } + foreach ($constraints as $constraint) { + $changes[] = $constraint; } } - - $columns[] = implode(', ', $this->prefixArray('alter column '.$this->wrap($column), $changes)); } - return 'alter table '.$this->wrapTable($blueprint).' '.implode(', ', $columns); + return sprintf('alter table %s %s', + $this->wrapTable($blueprint), + implode(', ', $this->prefixArray('alter column '.$this->wrap($column), $changes)) + ); } /** diff --git a/src/Illuminate/Database/Schema/Grammars/SQLiteGrammar.php b/src/Illuminate/Database/Schema/Grammars/SQLiteGrammar.php index 19e32fab63d5..67622327eaf3 100644 --- a/src/Illuminate/Database/Schema/Grammars/SQLiteGrammar.php +++ b/src/Illuminate/Database/Schema/Grammars/SQLiteGrammar.php @@ -5,8 +5,6 @@ use Illuminate\Database\Connection; use Illuminate\Database\Query\Expression; use Illuminate\Database\Schema\Blueprint; -use Illuminate\Database\Schema\ColumnDefinition; -use Illuminate\Database\Schema\ForeignKeyDefinition; use Illuminate\Database\Schema\IndexDefinition; use Illuminate\Support\Arr; use Illuminate\Support\Fluent; @@ -28,6 +26,23 @@ class SQLiteGrammar extends Grammar */ protected $serials = ['bigInteger', 'integer', 'mediumInteger', 'smallInteger', 'tinyInteger']; + /** + * Get the commands to be compiled on the alter command. + * + * @param \Illuminate\Database\Connection $connection + * @return array + */ + public function getAlterCommands(Connection $connection) + { + $alterCommands = ['change', 'primary', 'dropPrimary', 'foreign', 'dropForeign']; + + if (version_compare($connection->getServerVersion(), '3.35', '<')) { + $alterCommands[] = 'dropColumn'; + } + + return $alterCommands; + } + /** * Compile the query to determine the SQL text that describes the given object. * @@ -214,19 +229,18 @@ protected function addPrimaryKeys($primary) * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command - * @return array + * @return string */ public function compileAdd(Blueprint $blueprint, Fluent $command) { - $columns = $this->prefixArray('add column', $this->getColumns($blueprint)); - - return collect($columns)->map(function ($column) use ($blueprint) { - return 'alter table '.$this->wrapTable($blueprint).' '.$column; - })->all(); + return sprintf('alter table %s add column %s', + $this->wrapTable($blueprint), + $this->getColumn($blueprint, $command->column) + ); } /** - * Compile a change column command into a series of SQL statements. + * Compile alter table command into a series of SQL statements. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command @@ -235,79 +249,35 @@ public function compileAdd(Blueprint $blueprint, Fluent $command) * * @throws \RuntimeException */ - public function compileChange(Blueprint $blueprint, Fluent $command, Connection $connection) + public function compileAlter(Blueprint $blueprint, Fluent $command, Connection $connection) { - $schema = $connection->getSchemaBuilder(); - $table = $blueprint->getTable(); - - $changedColumns = collect($blueprint->getChangedColumns()); $columnNames = []; $autoIncrementColumn = null; - $columns = collect($schema->getColumns($table)) - ->map(function ($column) use ($blueprint, $changedColumns, &$columnNames, &$autoIncrementColumn) { - $column = $changedColumns->first(fn ($col) => $col->name === $column['name'], $column); - - if ($column instanceof Fluent) { - $name = $this->wrap($column); - $autoIncrementColumn = $column->autoIncrement ? $column->name : $autoIncrementColumn; - - if (is_null($column->virtualAs) && is_null($column->virtualAsJson) && - is_null($column->storedAs) && is_null($column->storedAsJson)) { - $columnNames[] = $name; - } - - return $this->addModifiers($name.' '.$this->getType($column), $blueprint, $column); - } else { - $name = $this->wrap($column['name']); - $autoIncrementColumn = $column['auto_increment'] ? $column['name'] : $autoIncrementColumn; - $isGenerated = ! is_null($column['generation']); - - if (! $isGenerated) { - $columnNames[] = $name; - } - - return $this->addModifiers($name.' '.$column['type'], $blueprint, - new ColumnDefinition([ - 'change' => true, - 'type' => $column['type_name'], - 'nullable' => $column['nullable'], - 'default' => is_null($column['default']) ? null : new Expression($column['default']), - 'autoIncrement' => $column['auto_increment'], - 'collation' => $column['collation'], - 'comment' => $column['comment'], - 'virtualAs' => $isGenerated && $column['generation']['type'] === 'virtual' - ? $column['generation']['expression'] : null, - 'storedAs' => $isGenerated && $column['generation']['type'] === 'stored' - ? $column['generation']['expression'] : null, - ]) - ); + $columns = collect($blueprint->getState()->getColumns()) + ->map(function ($column) use ($blueprint, &$columnNames, &$autoIncrementColumn) { + $name = $this->wrap($column); + + $autoIncrementColumn = $column->autoIncrement ? $column->name : $autoIncrementColumn; + + if (is_null($column->virtualAs) && is_null($column->virtualAsJson) && + is_null($column->storedAs) && is_null($column->storedAsJson)) { + $columnNames[] = $name; } + + return $this->addModifiers( + $this->wrap($column).' '.($column->full_type_definition ?? $this->getType($column)), + $blueprint, + $column + ); })->all(); - $foreignKeys = collect($schema->getForeignKeys($table))->map(fn ($foreignKey) => new ForeignKeyDefinition([ - 'columns' => $foreignKey['columns'], - 'on' => $foreignKey['foreign_table'], - 'references' => $foreignKey['foreign_columns'], - 'onUpdate' => $foreignKey['on_update'], - 'onDelete' => $foreignKey['on_delete'], - ]))->all(); - - [$primary, $indexes] = collect($schema->getIndexes($table))->map(fn ($index) => new IndexDefinition([ - 'name' => match (true) { - $index['primary'] => 'primary', - $index['unique'] => 'unique', - default => 'index', - }, - 'index' => $index['name'], - 'columns' => $index['columns'], - ]))->partition(fn ($index) => $index->name === 'primary'); - - $indexes = collect($indexes)->reject(fn ($index) => str_starts_with('sqlite_', $index->index))->map( - fn ($index) => $this->{'compile'.ucfirst($index->name)}($blueprint, $index) - )->all(); - - $tempTable = $this->wrap('__temp__'.$blueprint->getPrefix().$table); + $indexes = collect($blueprint->getState()->getIndexes()) + ->reject(fn ($index) => str_starts_with('sqlite_', $index->index)) + ->map(fn ($index) => $this->{'compile'.ucfirst($index->name)}($blueprint, $index)) + ->all(); + + $tempTable = $this->wrap('__temp__'.$blueprint->getPrefix().$blueprint->getTable()); $table = $this->wrapTable($blueprint); $columnNames = implode(', ', $columnNames); @@ -318,8 +288,8 @@ public function compileChange(Blueprint $blueprint, Fluent $command, Connection sprintf('create table %s (%s%s%s)', $tempTable, implode(', ', $columns), - $this->addForeignKeys($foreignKeys), - $autoIncrementColumn ? '' : $this->addPrimaryKeys($primary->first()) + $this->addForeignKeys($blueprint->getState()->getForeignKeys()), + $autoIncrementColumn ? '' : $this->addPrimaryKeys($blueprint->getState()->getPrimaryKey()) ), sprintf('insert into %s (%s) select %s from %s', $tempTable, $columnNames, $columnNames, $table), sprintf('drop table %s', $table), @@ -327,6 +297,33 @@ public function compileChange(Blueprint $blueprint, Fluent $command, Connection ], $indexes, [$foreignKeyConstraintsEnabled ? $this->compileEnableForeignKeyConstraints() : null])); } + /** + * Compile a change column command into a series of SQL statements. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @param \Illuminate\Database\Connection $connection + * @return array|string + * + * @throws \RuntimeException + */ + public function compileChange(Blueprint $blueprint, Fluent $command, Connection $connection) + { + // Handled on table alteration... + } + + /** + * Compile a primary key command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compilePrimary(Blueprint $blueprint, Fluent $command) + { + // Handled on table creation or alteration... + } + /** * Compile a unique key command. * @@ -382,7 +379,7 @@ public function compileSpatialIndex(Blueprint $blueprint, Fluent $command) */ public function compileForeign(Blueprint $blueprint, Fluent $command) { - // Handled on table creation... + // Handled on table creation or alteration... } /** @@ -445,10 +442,16 @@ public function compileRebuild() * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command * @param \Illuminate\Database\Connection $connection - * @return array + * @return array|null */ public function compileDropColumn(Blueprint $blueprint, Fluent $command, Connection $connection) { + if (version_compare($connection->getServerVersion(), '3.35', '<')) { + // Handled on table alteration... + + return null; + } + $table = $this->wrapTable($blueprint); $columns = $this->prefixArray('drop column', $this->wrapArray($command->columns)); @@ -456,6 +459,18 @@ public function compileDropColumn(Blueprint $blueprint, Fluent $command, Connect return collect($columns)->map(fn ($column) => 'alter table '.$table.' '.$column)->all(); } + /** + * Compile a drop primary key command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string + */ + public function compileDropPrimary(Blueprint $blueprint, Fluent $command) + { + // Handled on table alteration... + } + /** * Compile a drop unique key command. * @@ -498,6 +513,22 @@ public function compileDropSpatialIndex(Blueprint $blueprint, Fluent $command) throw new RuntimeException('The database driver in use does not support spatial indexes.'); } + /** + * Compile a drop foreign key command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return array + */ + public function compileDropForeign(Blueprint $blueprint, Fluent $command) + { + if (empty($command->columns)) { + throw new RuntimeException('This database driver does not support dropping foreign keys by name.'); + } + + // Handled on table alteration... + } + /** * Compile a rename table command. * diff --git a/src/Illuminate/Database/Schema/Grammars/SqlServerGrammar.php b/src/Illuminate/Database/Schema/Grammars/SqlServerGrammar.php index 9d811153829d..fe8121b4f4fc 100755 --- a/src/Illuminate/Database/Schema/Grammars/SqlServerGrammar.php +++ b/src/Illuminate/Database/Schema/Grammars/SqlServerGrammar.php @@ -213,7 +213,7 @@ public function compileAdd(Blueprint $blueprint, Fluent $command) { return sprintf('alter table %s add %s', $this->wrapTable($blueprint), - implode(', ', $this->getColumns($blueprint)) + $this->getColumn($blueprint, $command->column) ); } @@ -245,25 +245,13 @@ public function compileRenameColumn(Blueprint $blueprint, Fluent $command, Conne */ public function compileChange(Blueprint $blueprint, Fluent $command, Connection $connection) { - $changes = [$this->compileDropDefaultConstraint($blueprint, $command)]; - - foreach ($blueprint->getChangedColumns() as $column) { - $sql = sprintf('alter table %s alter column %s %s', + return [ + $this->compileDropDefaultConstraint($blueprint, $command), + sprintf('alter table %s alter column %s', $this->wrapTable($blueprint), - $this->wrap($column), - $this->getType($column) - ); - - foreach ($this->modifiers as $modifier) { - if (method_exists($this, $method = "modify{$modifier}")) { - $sql .= $this->{$method}($blueprint, $column); - } - } - - $changes[] = $sql; - } - - return $changes; + $this->getColumn($blueprint, $command->column), + ), + ]; } /** @@ -411,7 +399,7 @@ public function compileDropColumn(Blueprint $blueprint, Fluent $command) public function compileDropDefaultConstraint(Blueprint $blueprint, Fluent $command) { $columns = $command->name === 'change' - ? "'".collect($blueprint->getChangedColumns())->pluck('name')->implode("','")."'" + ? "'".$command->column->name."'" : "'".implode("','", $command->columns)."'"; $table = $this->wrapTable($blueprint); diff --git a/tests/Database/DatabaseMariaDbSchemaGrammarTest.php b/tests/Database/DatabaseMariaDbSchemaGrammarTest.php index c78355e4f768..85d0de26f311 100755 --- a/tests/Database/DatabaseMariaDbSchemaGrammarTest.php +++ b/tests/Database/DatabaseMariaDbSchemaGrammarTest.php @@ -44,8 +44,11 @@ public function testBasicCreateTable() $statements = $blueprint->toSql($conn, $this->getGrammar()); - $this->assertCount(1, $statements); - $this->assertSame('alter table `users` add `id` int unsigned not null auto_increment primary key, add `email` varchar(255) not null', $statements[0]); + $this->assertCount(2, $statements); + $this->assertSame([ + 'alter table `users` add `id` int unsigned not null auto_increment primary key', + 'alter table `users` add `email` varchar(255) not null', + ], $statements); $blueprint = new Blueprint('users'); $blueprint->create(); @@ -87,7 +90,8 @@ public function testAddColumnsWithMultipleAutoIncrementStartingValue() $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); $this->assertEquals([ - 'alter table `users` add `id` bigint unsigned not null auto_increment primary key, add `name` varchar(255) not null', + 'alter table `users` add `id` bigint unsigned not null auto_increment primary key', + 'alter table `users` add `name` varchar(255) not null', 'alter table `users` auto_increment = 100', ], $statements); } @@ -502,10 +506,14 @@ public function testAddingForeignID() $this->assertInstanceOf(ForeignIdColumnDefinition::class, $foreignId); $this->assertSame([ - 'alter table `users` add `foo` bigint unsigned not null, add `company_id` bigint unsigned not null, add `laravel_idea_id` bigint unsigned not null, add `team_id` bigint unsigned not null, add `team_column_id` bigint unsigned not null', + 'alter table `users` add `foo` bigint unsigned not null', + 'alter table `users` add `company_id` bigint unsigned not null', 'alter table `users` add constraint `users_company_id_foreign` foreign key (`company_id`) references `companies` (`id`)', + 'alter table `users` add `laravel_idea_id` bigint unsigned not null', 'alter table `users` add constraint `users_laravel_idea_id_foreign` foreign key (`laravel_idea_id`) references `laravel_ideas` (`id`)', + 'alter table `users` add `team_id` bigint unsigned not null', 'alter table `users` add constraint `users_team_id_foreign` foreign key (`team_id`) references `teams` (`id`)', + 'alter table `users` add `team_column_id` bigint unsigned not null', 'alter table `users` add constraint `users_team_column_id_foreign` foreign key (`team_column_id`) references `teams` (`id`)', ], $statements); } @@ -560,8 +568,12 @@ public function testAddingMultipleColumnsAfterAnotherColumn() }); $blueprint->string('three'); $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); - $this->assertCount(1, $statements); - $this->assertSame('alter table `users` add `one` varchar(255) not null after `foo`, add `two` varchar(255) not null after `one`, add `three` varchar(255) not null', $statements[0]); + $this->assertCount(3, $statements); + $this->assertSame([ + 'alter table `users` add `one` varchar(255) not null after `foo`', + 'alter table `users` add `two` varchar(255) not null after `one`', + 'alter table `users` add `three` varchar(255) not null', + ], $statements); } public function testAddingGeneratedColumn() @@ -572,8 +584,12 @@ public function testAddingGeneratedColumn() $blueprint->integer('discounted_stored')->storedAs('price - 5'); $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); - $this->assertCount(1, $statements); - $this->assertSame('alter table `products` add `price` int not null, add `discounted_virtual` int as (price - 5), add `discounted_stored` int as (price - 5) stored', $statements[0]); + $this->assertCount(3, $statements); + $this->assertSame([ + 'alter table `products` add `price` int not null', + 'alter table `products` add `discounted_virtual` int as (price - 5)', + 'alter table `products` add `discounted_stored` int as (price - 5) stored', + ], $statements); $blueprint = new Blueprint('products'); $blueprint->integer('price'); @@ -581,8 +597,12 @@ public function testAddingGeneratedColumn() $blueprint->integer('discounted_stored')->storedAs('price - 5')->nullable(false); $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); - $this->assertCount(1, $statements); - $this->assertSame('alter table `products` add `price` int not null, add `discounted_virtual` int as (price - 5) not null, add `discounted_stored` int as (price - 5) stored not null', $statements[0]); + $this->assertCount(3, $statements); + $this->assertSame([ + 'alter table `products` add `price` int not null', + 'alter table `products` add `discounted_virtual` int as (price - 5) not null', + 'alter table `products` add `discounted_stored` int as (price - 5) stored not null', + ], $statements); } public function testAddingGeneratedColumnWithCharset() @@ -593,8 +613,12 @@ public function testAddingGeneratedColumnWithCharset() $blueprint->string('url_hash_stored', 64)->storedAs('sha2(url, 256)')->charset('ascii'); $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); - $this->assertCount(1, $statements); - $this->assertSame('alter table `links` add `url` varchar(2083) character set ascii not null, add `url_hash_virtual` varchar(64) character set ascii as (sha2(url, 256)), add `url_hash_stored` varchar(64) character set ascii as (sha2(url, 256)) stored', $statements[0]); + $this->assertCount(3, $statements); + $this->assertSame([ + 'alter table `links` add `url` varchar(2083) character set ascii not null', + 'alter table `links` add `url_hash_virtual` varchar(64) character set ascii as (sha2(url, 256))', + 'alter table `links` add `url_hash_stored` varchar(64) character set ascii as (sha2(url, 256)) stored', + ], $statements); } public function testAddingGeneratedColumnByExpression() @@ -605,8 +629,12 @@ public function testAddingGeneratedColumnByExpression() $blueprint->integer('discounted_stored')->storedAs(new Expression('price - 5')); $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); - $this->assertCount(1, $statements); - $this->assertSame('alter table `products` add `price` int not null, add `discounted_virtual` int as (price - 5), add `discounted_stored` int as (price - 5) stored', $statements[0]); + $this->assertCount(3, $statements); + $this->assertSame([ + 'alter table `products` add `price` int not null', + 'alter table `products` add `discounted_virtual` int as (price - 5)', + 'alter table `products` add `discounted_stored` int as (price - 5) stored', + ], $statements); } public function testAddingInvisibleColumn() @@ -1050,8 +1078,11 @@ public function testAddingTimestamps() $blueprint = new Blueprint('users'); $blueprint->timestamps(); $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); - $this->assertCount(1, $statements); - $this->assertSame('alter table `users` add `created_at` timestamp null, add `updated_at` timestamp null', $statements[0]); + $this->assertCount(2, $statements); + $this->assertSame([ + 'alter table `users` add `created_at` timestamp null', + 'alter table `users` add `updated_at` timestamp null', + ], $statements); } public function testAddingTimestampsTz() @@ -1059,8 +1090,11 @@ public function testAddingTimestampsTz() $blueprint = new Blueprint('users'); $blueprint->timestampsTz(); $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); - $this->assertCount(1, $statements); - $this->assertSame('alter table `users` add `created_at` timestamp null, add `updated_at` timestamp null', $statements[0]); + $this->assertCount(2, $statements); + $this->assertSame([ + 'alter table `users` add `created_at` timestamp null', + 'alter table `users` add `updated_at` timestamp null', + ], $statements); } public function testAddingRememberToken() @@ -1116,10 +1150,14 @@ public function testAddingForeignUuid() $this->assertInstanceOf(ForeignIdColumnDefinition::class, $foreignUuid); $this->assertSame([ - 'alter table `users` add `foo` uuid not null, add `company_id` uuid not null, add `laravel_idea_id` uuid not null, add `team_id` uuid not null, add `team_column_id` uuid not null', + 'alter table `users` add `foo` uuid not null', + 'alter table `users` add `company_id` uuid not null', 'alter table `users` add constraint `users_company_id_foreign` foreign key (`company_id`) references `companies` (`id`)', + 'alter table `users` add `laravel_idea_id` uuid not null', 'alter table `users` add constraint `users_laravel_idea_id_foreign` foreign key (`laravel_idea_id`) references `laravel_ideas` (`id`)', + 'alter table `users` add `team_id` uuid not null', 'alter table `users` add constraint `users_team_id_foreign` foreign key (`team_id`) references `teams` (`id`)', + 'alter table `users` add `team_column_id` uuid not null', 'alter table `users` add constraint `users_team_column_id_foreign` foreign key (`team_column_id`) references `teams` (`id`)', ], $statements); } diff --git a/tests/Database/DatabaseMySqlSchemaGrammarTest.php b/tests/Database/DatabaseMySqlSchemaGrammarTest.php index e5eb104605ea..e959b66d8831 100755 --- a/tests/Database/DatabaseMySqlSchemaGrammarTest.php +++ b/tests/Database/DatabaseMySqlSchemaGrammarTest.php @@ -44,8 +44,11 @@ public function testBasicCreateTable() $statements = $blueprint->toSql($conn, $this->getGrammar()); - $this->assertCount(1, $statements); - $this->assertSame('alter table `users` add `id` int unsigned not null auto_increment primary key, add `email` varchar(255) not null', $statements[0]); + $this->assertCount(2, $statements); + $this->assertSame([ + 'alter table `users` add `id` int unsigned not null auto_increment primary key', + 'alter table `users` add `email` varchar(255) not null', + ], $statements); $blueprint = new Blueprint('users'); $blueprint->create(); @@ -87,7 +90,8 @@ public function testAddColumnsWithMultipleAutoIncrementStartingValue() $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); $this->assertEquals([ - 'alter table `users` add `id` bigint unsigned not null auto_increment primary key, add `name` varchar(255) not null', + 'alter table `users` add `id` bigint unsigned not null auto_increment primary key', + 'alter table `users` add `name` varchar(255) not null', 'alter table `users` auto_increment = 100', ], $statements); } @@ -502,10 +506,14 @@ public function testAddingForeignID() $this->assertInstanceOf(ForeignIdColumnDefinition::class, $foreignId); $this->assertSame([ - 'alter table `users` add `foo` bigint unsigned not null, add `company_id` bigint unsigned not null, add `laravel_idea_id` bigint unsigned not null, add `team_id` bigint unsigned not null, add `team_column_id` bigint unsigned not null', + 'alter table `users` add `foo` bigint unsigned not null', + 'alter table `users` add `company_id` bigint unsigned not null', 'alter table `users` add constraint `users_company_id_foreign` foreign key (`company_id`) references `companies` (`id`)', + 'alter table `users` add `laravel_idea_id` bigint unsigned not null', 'alter table `users` add constraint `users_laravel_idea_id_foreign` foreign key (`laravel_idea_id`) references `laravel_ideas` (`id`)', + 'alter table `users` add `team_id` bigint unsigned not null', 'alter table `users` add constraint `users_team_id_foreign` foreign key (`team_id`) references `teams` (`id`)', + 'alter table `users` add `team_column_id` bigint unsigned not null', 'alter table `users` add constraint `users_team_column_id_foreign` foreign key (`team_column_id`) references `teams` (`id`)', ], $statements); } @@ -560,8 +568,12 @@ public function testAddingMultipleColumnsAfterAnotherColumn() }); $blueprint->string('three'); $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); - $this->assertCount(1, $statements); - $this->assertSame('alter table `users` add `one` varchar(255) not null after `foo`, add `two` varchar(255) not null after `one`, add `three` varchar(255) not null', $statements[0]); + $this->assertCount(3, $statements); + $this->assertSame([ + 'alter table `users` add `one` varchar(255) not null after `foo`', + 'alter table `users` add `two` varchar(255) not null after `one`', + 'alter table `users` add `three` varchar(255) not null', + ], $statements); } public function testAddingGeneratedColumn() @@ -572,8 +584,12 @@ public function testAddingGeneratedColumn() $blueprint->integer('discounted_stored')->storedAs('price - 5'); $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); - $this->assertCount(1, $statements); - $this->assertSame('alter table `products` add `price` int not null, add `discounted_virtual` int as (price - 5), add `discounted_stored` int as (price - 5) stored', $statements[0]); + $this->assertCount(3, $statements); + $this->assertSame([ + 'alter table `products` add `price` int not null', + 'alter table `products` add `discounted_virtual` int as (price - 5)', + 'alter table `products` add `discounted_stored` int as (price - 5) stored', + ], $statements); $blueprint = new Blueprint('products'); $blueprint->integer('price'); @@ -581,8 +597,12 @@ public function testAddingGeneratedColumn() $blueprint->integer('discounted_stored')->storedAs('price - 5')->nullable(false); $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); - $this->assertCount(1, $statements); - $this->assertSame('alter table `products` add `price` int not null, add `discounted_virtual` int as (price - 5) not null, add `discounted_stored` int as (price - 5) stored not null', $statements[0]); + $this->assertCount(3, $statements); + $this->assertSame([ + 'alter table `products` add `price` int not null', + 'alter table `products` add `discounted_virtual` int as (price - 5) not null', + 'alter table `products` add `discounted_stored` int as (price - 5) stored not null', + ], $statements); } public function testAddingGeneratedColumnWithCharset() @@ -593,8 +613,12 @@ public function testAddingGeneratedColumnWithCharset() $blueprint->string('url_hash_stored', 64)->storedAs('sha2(url, 256)')->charset('ascii'); $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); - $this->assertCount(1, $statements); - $this->assertSame('alter table `links` add `url` varchar(2083) character set ascii not null, add `url_hash_virtual` varchar(64) character set ascii as (sha2(url, 256)), add `url_hash_stored` varchar(64) character set ascii as (sha2(url, 256)) stored', $statements[0]); + $this->assertCount(3, $statements); + $this->assertSame([ + 'alter table `links` add `url` varchar(2083) character set ascii not null', + 'alter table `links` add `url_hash_virtual` varchar(64) character set ascii as (sha2(url, 256))', + 'alter table `links` add `url_hash_stored` varchar(64) character set ascii as (sha2(url, 256)) stored', + ], $statements); } public function testAddingGeneratedColumnByExpression() @@ -605,8 +629,12 @@ public function testAddingGeneratedColumnByExpression() $blueprint->integer('discounted_stored')->storedAs(new Expression('price - 5')); $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); - $this->assertCount(1, $statements); - $this->assertSame('alter table `products` add `price` int not null, add `discounted_virtual` int as (price - 5), add `discounted_stored` int as (price - 5) stored', $statements[0]); + $this->assertCount(3, $statements); + $this->assertSame([ + 'alter table `products` add `price` int not null', + 'alter table `products` add `discounted_virtual` int as (price - 5)', + 'alter table `products` add `discounted_stored` int as (price - 5) stored', + ], $statements); } public function testAddingInvisibleColumn() @@ -1050,8 +1078,11 @@ public function testAddingTimestamps() $blueprint = new Blueprint('users'); $blueprint->timestamps(); $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); - $this->assertCount(1, $statements); - $this->assertSame('alter table `users` add `created_at` timestamp null, add `updated_at` timestamp null', $statements[0]); + $this->assertCount(2, $statements); + $this->assertSame([ + 'alter table `users` add `created_at` timestamp null', + 'alter table `users` add `updated_at` timestamp null', + ], $statements); } public function testAddingTimestampsTz() @@ -1059,8 +1090,11 @@ public function testAddingTimestampsTz() $blueprint = new Blueprint('users'); $blueprint->timestampsTz(); $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); - $this->assertCount(1, $statements); - $this->assertSame('alter table `users` add `created_at` timestamp null, add `updated_at` timestamp null', $statements[0]); + $this->assertCount(2, $statements); + $this->assertSame([ + 'alter table `users` add `created_at` timestamp null', + 'alter table `users` add `updated_at` timestamp null', + ], $statements); } public function testAddingRememberToken() @@ -1116,10 +1150,14 @@ public function testAddingForeignUuid() $this->assertInstanceOf(ForeignIdColumnDefinition::class, $foreignUuid); $this->assertSame([ - 'alter table `users` add `foo` char(36) not null, add `company_id` char(36) not null, add `laravel_idea_id` char(36) not null, add `team_id` char(36) not null, add `team_column_id` char(36) not null', + 'alter table `users` add `foo` char(36) not null', + 'alter table `users` add `company_id` char(36) not null', 'alter table `users` add constraint `users_company_id_foreign` foreign key (`company_id`) references `companies` (`id`)', + 'alter table `users` add `laravel_idea_id` char(36) not null', 'alter table `users` add constraint `users_laravel_idea_id_foreign` foreign key (`laravel_idea_id`) references `laravel_ideas` (`id`)', + 'alter table `users` add `team_id` char(36) not null', 'alter table `users` add constraint `users_team_id_foreign` foreign key (`team_id`) references `teams` (`id`)', + 'alter table `users` add `team_column_id` char(36) not null', 'alter table `users` add constraint `users_team_column_id_foreign` foreign key (`team_column_id`) references `teams` (`id`)', ], $statements); } diff --git a/tests/Database/DatabasePostgresSchemaGrammarTest.php b/tests/Database/DatabasePostgresSchemaGrammarTest.php index e74ed9ab91a8..db13ec2c5c44 100755 --- a/tests/Database/DatabasePostgresSchemaGrammarTest.php +++ b/tests/Database/DatabasePostgresSchemaGrammarTest.php @@ -35,8 +35,11 @@ public function testBasicCreateTable() $blueprint->string('email'); $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); - $this->assertCount(1, $statements); - $this->assertSame('alter table "users" add column "id" serial not null primary key, add column "email" varchar(255) not null', $statements[0]); + $this->assertCount(2, $statements); + $this->assertSame([ + 'alter table "users" add column "id" serial not null primary key', + 'alter table "users" add column "email" varchar(255) not null', + ], $statements); } public function testCreateTableWithAutoIncrementStartingValue() @@ -62,7 +65,9 @@ public function testAddColumnsWithMultipleAutoIncrementStartingValue() $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); $this->assertEquals([ - 'alter table "users" add column "id" bigserial not null primary key, add column "code" serial not null primary key, add column "name" varchar(255) not null', + 'alter table "users" add column "id" bigserial not null primary key', + 'alter table "users" add column "code" serial not null primary key', + 'alter table "users" add column "name" varchar(255) not null', 'alter sequence users_id_seq restart with 100', 'alter sequence users_code_seq restart with 200', ], $statements); @@ -409,10 +414,14 @@ public function testAddingForeignID() $this->assertInstanceOf(ForeignIdColumnDefinition::class, $foreignId); $this->assertSame([ - 'alter table "users" add column "foo" bigint not null, add column "company_id" bigint not null, add column "laravel_idea_id" bigint not null, add column "team_id" bigint not null, add column "team_column_id" bigint not null', + 'alter table "users" add column "foo" bigint not null', + 'alter table "users" add column "company_id" bigint not null', 'alter table "users" add constraint "users_company_id_foreign" foreign key ("company_id") references "companies" ("id")', + 'alter table "users" add column "laravel_idea_id" bigint not null', 'alter table "users" add constraint "users_laravel_idea_id_foreign" foreign key ("laravel_idea_id") references "laravel_ideas" ("id")', + 'alter table "users" add column "team_id" bigint not null', 'alter table "users" add constraint "users_team_id_foreign" foreign key ("team_id") references "teams" ("id")', + 'alter table "users" add column "team_column_id" bigint not null', 'alter table "users" add constraint "users_team_column_id_foreign" foreign key ("team_column_id") references "teams" ("id")', ], $statements); } @@ -859,8 +868,11 @@ public function testAddingTimestamps() $blueprint = new Blueprint('users'); $blueprint->timestamps(); $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); - $this->assertCount(1, $statements); - $this->assertSame('alter table "users" add column "created_at" timestamp(0) without time zone null, add column "updated_at" timestamp(0) without time zone null', $statements[0]); + $this->assertCount(2, $statements); + $this->assertSame([ + 'alter table "users" add column "created_at" timestamp(0) without time zone null', + 'alter table "users" add column "updated_at" timestamp(0) without time zone null', + ], $statements); } public function testAddingTimestampsTz() @@ -868,8 +880,11 @@ public function testAddingTimestampsTz() $blueprint = new Blueprint('users'); $blueprint->timestampsTz(); $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); - $this->assertCount(1, $statements); - $this->assertSame('alter table "users" add column "created_at" timestamp(0) with time zone null, add column "updated_at" timestamp(0) with time zone null', $statements[0]); + $this->assertCount(2, $statements); + $this->assertSame([ + 'alter table "users" add column "created_at" timestamp(0) with time zone null', + 'alter table "users" add column "updated_at" timestamp(0) with time zone null', + ], $statements); } public function testAddingBinary() @@ -915,10 +930,14 @@ public function testAddingForeignUuid() $this->assertInstanceOf(ForeignIdColumnDefinition::class, $foreignUuid); $this->assertSame([ - 'alter table "users" add column "foo" uuid not null, add column "company_id" uuid not null, add column "laravel_idea_id" uuid not null, add column "team_id" uuid not null, add column "team_column_id" uuid not null', + 'alter table "users" add column "foo" uuid not null', + 'alter table "users" add column "company_id" uuid not null', 'alter table "users" add constraint "users_company_id_foreign" foreign key ("company_id") references "companies" ("id")', + 'alter table "users" add column "laravel_idea_id" uuid not null', 'alter table "users" add constraint "users_laravel_idea_id_foreign" foreign key ("laravel_idea_id") references "laravel_ideas" ("id")', + 'alter table "users" add column "team_id" uuid not null', 'alter table "users" add constraint "users_team_id_foreign" foreign key ("team_id") references "teams" ("id")', + 'alter table "users" add column "team_column_id" uuid not null', 'alter table "users" add constraint "users_team_column_id_foreign" foreign key ("team_column_id") references "teams" ("id")', ], $statements); } @@ -956,15 +975,21 @@ public function testAddingVirtualAs() $blueprint->integer('foo')->nullable(); $blueprint->boolean('bar')->virtualAs('foo is not null'); $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); - $this->assertCount(1, $statements); - $this->assertSame('alter table "users" add column "foo" integer null, add column "bar" boolean not null generated always as (foo is not null)', $statements[0]); + $this->assertCount(2, $statements); + $this->assertSame([ + 'alter table "users" add column "foo" integer null', + 'alter table "users" add column "bar" boolean not null generated always as (foo is not null)', + ], $statements); $blueprint = new Blueprint('users'); $blueprint->integer('foo')->nullable(); $blueprint->boolean('bar')->virtualAs(new Expression('foo is not null')); $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); - $this->assertCount(1, $statements); - $this->assertSame('alter table "users" add column "foo" integer null, add column "bar" boolean not null generated always as (foo is not null)', $statements[0]); + $this->assertCount(2, $statements); + $this->assertSame([ + 'alter table "users" add column "foo" integer null', + 'alter table "users" add column "bar" boolean not null generated always as (foo is not null)', + ], $statements); } public function testAddingStoredAs() @@ -973,15 +998,21 @@ public function testAddingStoredAs() $blueprint->integer('foo')->nullable(); $blueprint->boolean('bar')->storedAs('foo is not null'); $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); - $this->assertCount(1, $statements); - $this->assertSame('alter table "users" add column "foo" integer null, add column "bar" boolean not null generated always as (foo is not null) stored', $statements[0]); + $this->assertCount(2, $statements); + $this->assertSame([ + 'alter table "users" add column "foo" integer null', + 'alter table "users" add column "bar" boolean not null generated always as (foo is not null) stored', + ], $statements); $blueprint = new Blueprint('users'); $blueprint->integer('foo')->nullable(); $blueprint->boolean('bar')->storedAs(new Expression('foo is not null')); $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); - $this->assertCount(1, $statements); - $this->assertSame('alter table "users" add column "foo" integer null, add column "bar" boolean not null generated always as (foo is not null) stored', $statements[0]); + $this->assertCount(2, $statements); + $this->assertSame([ + 'alter table "users" add column "foo" integer null', + 'alter table "users" add column "bar" boolean not null generated always as (foo is not null) stored', + ], $statements); } public function testAddingIpAddress() diff --git a/tests/Database/DatabaseSQLiteSchemaGrammarTest.php b/tests/Database/DatabaseSQLiteSchemaGrammarTest.php index ece08b8ae087..6454a9d82c60 100755 --- a/tests/Database/DatabaseSQLiteSchemaGrammarTest.php +++ b/tests/Database/DatabaseSQLiteSchemaGrammarTest.php @@ -5,9 +5,11 @@ use Illuminate\Database\Capsule\Manager; use Illuminate\Database\Connection; use Illuminate\Database\Query\Expression; +use Illuminate\Database\Query\Processors\SQLiteProcessor; use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Schema\ForeignIdColumnDefinition; use Illuminate\Database\Schema\Grammars\SQLiteGrammar; +use Illuminate\Database\Schema\SQLiteBuilder; use Mockery as m; use PHPUnit\Framework\TestCase; use RuntimeException; @@ -309,15 +311,39 @@ public function testAddingForeignID() $blueprint->foreignId('team_id')->references('id')->on('teams'); $blueprint->foreignId('team_column_id')->constrained('teams'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $grammar = $this->getGrammar(); + $connection = $this->getConnection(); + $connection->shouldReceive('getSchemaGrammar')->andReturn($grammar); + $connection->shouldReceive('getSchemaBuilder')->andReturn(new SQLiteBuilder($connection)); + $connection->shouldReceive('getTablePrefix')->andReturn(''); + $connection->shouldReceive('getPostProcessor')->andReturn(new SQliteProcessor); + $connection->shouldReceive('selectFromWriteConnection')->andReturn([]); + $connection->shouldReceive('scalar')->andReturn(''); + $statements = $blueprint->toSql($connection, $grammar); $this->assertInstanceOf(ForeignIdColumnDefinition::class, $foreignId); $this->assertSame([ 'alter table "users" add column "foo" integer not null', 'alter table "users" add column "company_id" integer not null', + 'create table "__temp__users" ("foo" integer not null, "company_id" integer not null, foreign key("company_id") references "companies"("id"))', + 'insert into "__temp__users" ("foo", "company_id") select "foo", "company_id" from "users"', + 'drop table "users"', + 'alter table "__temp__users" rename to "users"', 'alter table "users" add column "laravel_idea_id" integer not null', + 'create table "__temp__users" ("foo" integer not null, "company_id" integer not null, "laravel_idea_id" integer not null, foreign key("company_id") references "companies"("id"), foreign key("laravel_idea_id") references "laravel_ideas"("id"))', + 'insert into "__temp__users" ("foo", "company_id", "laravel_idea_id") select "foo", "company_id", "laravel_idea_id" from "users"', + 'drop table "users"', + 'alter table "__temp__users" rename to "users"', 'alter table "users" add column "team_id" integer not null', + 'create table "__temp__users" ("foo" integer not null, "company_id" integer not null, "laravel_idea_id" integer not null, "team_id" integer not null, foreign key("company_id") references "companies"("id"), foreign key("laravel_idea_id") references "laravel_ideas"("id"), foreign key("team_id") references "teams"("id"))', + 'insert into "__temp__users" ("foo", "company_id", "laravel_idea_id", "team_id") select "foo", "company_id", "laravel_idea_id", "team_id" from "users"', + 'drop table "users"', + 'alter table "__temp__users" rename to "users"', 'alter table "users" add column "team_column_id" integer not null', + 'create table "__temp__users" ("foo" integer not null, "company_id" integer not null, "laravel_idea_id" integer not null, "team_id" integer not null, "team_column_id" integer not null, foreign key("company_id") references "companies"("id"), foreign key("laravel_idea_id") references "laravel_ideas"("id"), foreign key("team_id") references "teams"("id"), foreign key("team_column_id") references "teams"("id"))', + 'insert into "__temp__users" ("foo", "company_id", "laravel_idea_id", "team_id", "team_column_id") select "foo", "company_id", "laravel_idea_id", "team_id", "team_column_id" from "users"', + 'drop table "users"', + 'alter table "__temp__users" rename to "users"', ], $statements); } @@ -325,9 +351,23 @@ public function testAddingForeignIdSpecifyingIndexNameInConstraint() { $blueprint = new Blueprint('users'); $blueprint->foreignId('company_id')->constrained(indexName: 'my_index'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + + $grammar = $this->getGrammar(); + $connection = $this->getConnection(); + $connection->shouldReceive('getSchemaGrammar')->andReturn($grammar); + $connection->shouldReceive('getSchemaBuilder')->andReturn(new SQLiteBuilder($connection)); + $connection->shouldReceive('getTablePrefix')->andReturn(''); + $connection->shouldReceive('getPostProcessor')->andReturn(new SQliteProcessor); + $connection->shouldReceive('selectFromWriteConnection')->andReturn([]); + $connection->shouldReceive('scalar')->andReturn(''); + $statements = $blueprint->toSql($connection, $grammar); + $this->assertSame([ 'alter table "users" add column "company_id" integer not null', + 'create table "__temp__users" ("company_id" integer not null, foreign key("company_id") references "companies"("id"))', + 'insert into "__temp__users" ("company_id") select "company_id" from "users"', + 'drop table "users"', + 'alter table "__temp__users" rename to "users"', ], $statements); } @@ -730,15 +770,39 @@ public function testAddingForeignUuid() $blueprint->foreignUuid('team_id')->references('id')->on('teams'); $blueprint->foreignUuid('team_column_id')->constrained('teams'); - $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $grammar = $this->getGrammar(); + $connection = $this->getConnection(); + $connection->shouldReceive('getSchemaGrammar')->andReturn($grammar); + $connection->shouldReceive('getSchemaBuilder')->andReturn(new SQLiteBuilder($connection)); + $connection->shouldReceive('getTablePrefix')->andReturn(''); + $connection->shouldReceive('getPostProcessor')->andReturn(new SQliteProcessor); + $connection->shouldReceive('selectFromWriteConnection')->andReturn([]); + $connection->shouldReceive('scalar')->andReturn(''); + $statements = $blueprint->toSql($connection, $grammar); $this->assertInstanceOf(ForeignIdColumnDefinition::class, $foreignUuid); $this->assertSame([ 'alter table "users" add column "foo" varchar not null', 'alter table "users" add column "company_id" varchar not null', + 'create table "__temp__users" ("foo" varchar not null, "company_id" varchar not null, foreign key("company_id") references "companies"("id"))', + 'insert into "__temp__users" ("foo", "company_id") select "foo", "company_id" from "users"', + 'drop table "users"', + 'alter table "__temp__users" rename to "users"', 'alter table "users" add column "laravel_idea_id" varchar not null', + 'create table "__temp__users" ("foo" varchar not null, "company_id" varchar not null, "laravel_idea_id" varchar not null, foreign key("company_id") references "companies"("id"), foreign key("laravel_idea_id") references "laravel_ideas"("id"))', + 'insert into "__temp__users" ("foo", "company_id", "laravel_idea_id") select "foo", "company_id", "laravel_idea_id" from "users"', + 'drop table "users"', + 'alter table "__temp__users" rename to "users"', 'alter table "users" add column "team_id" varchar not null', + 'create table "__temp__users" ("foo" varchar not null, "company_id" varchar not null, "laravel_idea_id" varchar not null, "team_id" varchar not null, foreign key("company_id") references "companies"("id"), foreign key("laravel_idea_id") references "laravel_ideas"("id"), foreign key("team_id") references "teams"("id"))', + 'insert into "__temp__users" ("foo", "company_id", "laravel_idea_id", "team_id") select "foo", "company_id", "laravel_idea_id", "team_id" from "users"', + 'drop table "users"', + 'alter table "__temp__users" rename to "users"', 'alter table "users" add column "team_column_id" varchar not null', + 'create table "__temp__users" ("foo" varchar not null, "company_id" varchar not null, "laravel_idea_id" varchar not null, "team_id" varchar not null, "team_column_id" varchar not null, foreign key("company_id") references "companies"("id"), foreign key("laravel_idea_id") references "laravel_ideas"("id"), foreign key("team_id") references "teams"("id"), foreign key("team_column_id") references "teams"("id"))', + 'insert into "__temp__users" ("foo", "company_id", "laravel_idea_id", "team_id", "team_column_id") select "foo", "company_id", "laravel_idea_id", "team_id", "team_column_id" from "users"', + 'drop table "users"', + 'alter table "__temp__users" rename to "users"', ], $statements); } @@ -925,9 +989,21 @@ public function testCreateTableWithStoredAsColumn() $this->assertSame('create table "users" ("my_json_column" varchar not null, "my_other_column" varchar as (json_extract("my_json_column", \'$."some_attribute"."nested"\')) stored)', $statements[0]); } + public function testDroppingColumnsWorks() + { + $blueprint = new Blueprint('users', function ($table) { + $table->dropColumn('name'); + }); + + $this->assertEquals(['alter table "users" drop column "name"'], $blueprint->toSql($this->getConnection(), $this->getGrammar())); + } + protected function getConnection() { - return m::mock(Connection::class); + $connection = m::mock(Connection::class); + $connection->shouldReceive('getServerVersion')->andReturn('3.35'); + + return $connection; } public function getGrammar() diff --git a/tests/Database/DatabaseSchemaBlueprintTest.php b/tests/Database/DatabaseSchemaBlueprintTest.php index eb80ee6b2a56..b9048e2a2bed 100755 --- a/tests/Database/DatabaseSchemaBlueprintTest.php +++ b/tests/Database/DatabaseSchemaBlueprintTest.php @@ -120,6 +120,7 @@ public function testDefaultCurrentDateTime() $this->assertEquals(['alter table "users" add column "created" timestamp(0) without time zone not null default CURRENT_TIMESTAMP'], $blueprint->toSql($connection, new PostgresGrammar)); $blueprint = clone $base; + $connection->shouldReceive('getServerVersion')->andReturn('3.35'); $this->assertEquals(['alter table "users" add column "created" datetime not null default CURRENT_TIMESTAMP'], $blueprint->toSql($connection, new SQLiteGrammar)); $blueprint = clone $base; @@ -141,6 +142,7 @@ public function testDefaultCurrentTimestamp() $this->assertEquals(['alter table "users" add column "created" timestamp(0) without time zone not null default CURRENT_TIMESTAMP'], $blueprint->toSql($connection, new PostgresGrammar)); $blueprint = clone $base; + $connection->shouldReceive('getServerVersion')->andReturn('3.35'); $this->assertEquals(['alter table "users" add column "created" datetime not null default CURRENT_TIMESTAMP'], $blueprint->toSql($connection, new SQLiteGrammar)); $blueprint = clone $base; @@ -251,6 +253,7 @@ public function testDropColumn() $this->assertEquals(['alter table "users" drop column "foo"'], $blueprint->toSql($connection, new PostgresGrammar)); $blueprint = clone $base; + $connection->shouldReceive('getServerVersion')->andReturn('3.35'); $this->assertEquals(['alter table "users" drop column "foo"'], $blueprint->toSql($connection, new SQLiteGrammar)); $blueprint = clone $base; @@ -287,7 +290,8 @@ public function testDefaultUsingIdMorph() $blueprint = clone $base; $this->assertEquals([ - 'alter table `comments` add `commentable_type` varchar(255) not null, add `commentable_id` bigint unsigned not null', + 'alter table `comments` add `commentable_type` varchar(255) not null', + 'alter table `comments` add `commentable_id` bigint unsigned not null', 'alter table `comments` add index `comments_commentable_type_commentable_id_index`(`commentable_type`, `commentable_id`)', ], $blueprint->toSql($connection, new MySqlGrammar)); } @@ -303,7 +307,8 @@ public function testDefaultUsingNullableIdMorph() $blueprint = clone $base; $this->assertEquals([ - 'alter table `comments` add `commentable_type` varchar(255) null, add `commentable_id` bigint unsigned null', + 'alter table `comments` add `commentable_type` varchar(255) null', + 'alter table `comments` add `commentable_id` bigint unsigned null', 'alter table `comments` add index `comments_commentable_type_commentable_id_index`(`commentable_type`, `commentable_id`)', ], $blueprint->toSql($connection, new MySqlGrammar)); } @@ -321,7 +326,8 @@ public function testDefaultUsingUuidMorph() $blueprint = clone $base; $this->assertEquals([ - 'alter table `comments` add `commentable_type` varchar(255) not null, add `commentable_id` char(36) not null', + 'alter table `comments` add `commentable_type` varchar(255) not null', + 'alter table `comments` add `commentable_id` char(36) not null', 'alter table `comments` add index `comments_commentable_type_commentable_id_index`(`commentable_type`, `commentable_id`)', ], $blueprint->toSql($connection, new MySqlGrammar)); } @@ -339,7 +345,8 @@ public function testDefaultUsingNullableUuidMorph() $blueprint = clone $base; $this->assertEquals([ - 'alter table `comments` add `commentable_type` varchar(255) null, add `commentable_id` char(36) null', + 'alter table `comments` add `commentable_type` varchar(255) null', + 'alter table `comments` add `commentable_id` char(36) null', 'alter table `comments` add index `comments_commentable_type_commentable_id_index`(`commentable_type`, `commentable_id`)', ], $blueprint->toSql($connection, new MySqlGrammar)); } @@ -357,7 +364,8 @@ public function testDefaultUsingUlidMorph() $blueprint = clone $base; $this->assertEquals([ - 'alter table `comments` add `commentable_type` varchar(255) not null, add `commentable_id` char(26) not null', + 'alter table `comments` add `commentable_type` varchar(255) not null', + 'alter table `comments` add `commentable_id` char(26) not null', 'alter table `comments` add index `comments_commentable_type_commentable_id_index`(`commentable_type`, `commentable_id`)', ], $blueprint->toSql($connection, new MySqlGrammar)); } @@ -375,7 +383,8 @@ public function testDefaultUsingNullableUlidMorph() $blueprint = clone $base; $this->assertEquals([ - 'alter table `comments` add `commentable_type` varchar(255) null, add `commentable_id` char(26) null', + 'alter table `comments` add `commentable_type` varchar(255) null', + 'alter table `comments` add `commentable_id` char(26) null', 'alter table `comments` add index `comments_commentable_type_commentable_id_index`(`commentable_type`, `commentable_id`)', ], $blueprint->toSql($connection, new MySqlGrammar)); } @@ -515,6 +524,7 @@ public function testTinyTextColumn() ], $blueprint->toSql($connection, new MySqlGrammar)); $blueprint = clone $base; + $connection->shouldReceive('getServerVersion')->andReturn('3.35'); $this->assertEquals([ 'alter table "posts" add column "note" text not null', ], $blueprint->toSql($connection, new SQLiteGrammar)); @@ -544,6 +554,7 @@ public function testTinyTextNullableColumn() ], $blueprint->toSql($connection, new MySqlGrammar)); $blueprint = clone $base; + $connection->shouldReceive('getServerVersion')->andReturn('3.35'); $this->assertEquals([ 'alter table "posts" add column "note" text', ], $blueprint->toSql($connection, new SQLiteGrammar)); diff --git a/tests/Database/DatabaseSqlServerSchemaGrammarTest.php b/tests/Database/DatabaseSqlServerSchemaGrammarTest.php index 7fb9a89f08fc..81c15f7aa79d 100755 --- a/tests/Database/DatabaseSqlServerSchemaGrammarTest.php +++ b/tests/Database/DatabaseSqlServerSchemaGrammarTest.php @@ -33,8 +33,11 @@ public function testBasicCreateTable() $blueprint->string('email'); $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); - $this->assertCount(1, $statements); - $this->assertSame('alter table "users" add "id" int not null identity primary key, "email" nvarchar(255) not null', $statements[0]); + $this->assertCount(2, $statements); + $this->assertSame([ + 'alter table "users" add "id" int not null identity primary key', + 'alter table "users" add "email" nvarchar(255) not null', + ], $statements); $blueprint = new Blueprint('users'); $blueprint->create(); @@ -359,10 +362,14 @@ public function testAddingForeignID() $this->assertInstanceOf(ForeignIdColumnDefinition::class, $foreignId); $this->assertSame([ - 'alter table "users" add "foo" bigint not null, "company_id" bigint not null, "laravel_idea_id" bigint not null, "team_id" bigint not null, "team_column_id" bigint not null', + 'alter table "users" add "foo" bigint not null', + 'alter table "users" add "company_id" bigint not null', 'alter table "users" add constraint "users_company_id_foreign" foreign key ("company_id") references "companies" ("id")', + 'alter table "users" add "laravel_idea_id" bigint not null', 'alter table "users" add constraint "users_laravel_idea_id_foreign" foreign key ("laravel_idea_id") references "laravel_ideas" ("id")', + 'alter table "users" add "team_id" bigint not null', 'alter table "users" add constraint "users_team_id_foreign" foreign key ("team_id") references "teams" ("id")', + 'alter table "users" add "team_column_id" bigint not null', 'alter table "users" add constraint "users_team_column_id_foreign" foreign key ("team_column_id") references "teams" ("id")', ], $statements); } @@ -709,8 +716,11 @@ public function testAddingTimestamps() $blueprint = new Blueprint('users'); $blueprint->timestamps(); $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); - $this->assertCount(1, $statements); - $this->assertSame('alter table "users" add "created_at" datetime null, "updated_at" datetime null', $statements[0]); + $this->assertCount(2, $statements); + $this->assertSame([ + 'alter table "users" add "created_at" datetime null', + 'alter table "users" add "updated_at" datetime null', + ], $statements); } public function testAddingTimestampsTz() @@ -718,8 +728,11 @@ public function testAddingTimestampsTz() $blueprint = new Blueprint('users'); $blueprint->timestampsTz(); $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); - $this->assertCount(1, $statements); - $this->assertSame('alter table "users" add "created_at" datetimeoffset null, "updated_at" datetimeoffset null', $statements[0]); + $this->assertCount(2, $statements); + $this->assertSame([ + 'alter table "users" add "created_at" datetimeoffset null', + 'alter table "users" add "updated_at" datetimeoffset null', + ], $statements); } public function testAddingRememberToken() @@ -775,10 +788,14 @@ public function testAddingForeignUuid() $this->assertInstanceOf(ForeignIdColumnDefinition::class, $foreignId); $this->assertSame([ - 'alter table "users" add "foo" uniqueidentifier not null, "company_id" uniqueidentifier not null, "laravel_idea_id" uniqueidentifier not null, "team_id" uniqueidentifier not null, "team_column_id" uniqueidentifier not null', + 'alter table "users" add "foo" uniqueidentifier not null', + 'alter table "users" add "company_id" uniqueidentifier not null', 'alter table "users" add constraint "users_company_id_foreign" foreign key ("company_id") references "companies" ("id")', + 'alter table "users" add "laravel_idea_id" uniqueidentifier not null', 'alter table "users" add constraint "users_laravel_idea_id_foreign" foreign key ("laravel_idea_id") references "laravel_ideas" ("id")', + 'alter table "users" add "team_id" uniqueidentifier not null', 'alter table "users" add constraint "users_team_id_foreign" foreign key ("team_id") references "teams" ("id")', + 'alter table "users" add "team_column_id" uniqueidentifier not null', 'alter table "users" add constraint "users_team_column_id_foreign" foreign key ("team_column_id") references "teams" ("id")', ], $statements); } @@ -850,16 +867,24 @@ public function testAddingGeneratedColumn() $blueprint->computed('discounted_virtual', 'price - 5'); $blueprint->computed('discounted_stored', 'price - 5')->persisted(); $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); - $this->assertCount(1, $statements); - $this->assertSame('alter table "products" add "price" int not null, "discounted_virtual" as (price - 5), "discounted_stored" as (price - 5) persisted', $statements[0]); + $this->assertCount(3, $statements); + $this->assertSame([ + 'alter table "products" add "price" int not null', + 'alter table "products" add "discounted_virtual" as (price - 5)', + 'alter table "products" add "discounted_stored" as (price - 5) persisted', + ], $statements); $blueprint = new Blueprint('products'); $blueprint->integer('price'); $blueprint->computed('discounted_virtual', new Expression('price - 5')); $blueprint->computed('discounted_stored', new Expression('price - 5'))->persisted(); $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); - $this->assertCount(1, $statements); - $this->assertSame('alter table "products" add "price" int not null, "discounted_virtual" as (price - 5), "discounted_stored" as (price - 5) persisted', $statements[0]); + $this->assertCount(3, $statements); + $this->assertSame([ + 'alter table "products" add "price" int not null', + 'alter table "products" add "discounted_virtual" as (price - 5)', + 'alter table "products" add "discounted_stored" as (price - 5) persisted', + ], $statements); } public function testGrammarsAreMacroable() diff --git a/tests/Integration/Database/DatabaseSchemaBlueprintTest.php b/tests/Integration/Database/DatabaseSchemaBlueprintTest.php index 42fe4de937d7..6ef517094e84 100644 --- a/tests/Integration/Database/DatabaseSchemaBlueprintTest.php +++ b/tests/Integration/Database/DatabaseSchemaBlueprintTest.php @@ -41,11 +41,11 @@ public function testRenamingAndChangingColumnsWork() $queries = $blueprint->toSql(DB::connection(), new SQLiteGrammar); $expected = [ - 'create table "__temp__users" ("name" varchar not null, "age" integer not null)', - 'insert into "__temp__users" ("name", "age") select "name", "age" from "users"', + 'alter table "users" rename column "name" to "first_name"', + 'create table "__temp__users" ("first_name" varchar not null, "age" integer not null)', + 'insert into "__temp__users" ("first_name", "age") select "first_name", "age" from "users"', 'drop table "users"', 'alter table "__temp__users" rename to "users"', - 'alter table "users" rename column "name" to "first_name"', ]; $this->assertEquals($expected, $queries); @@ -70,18 +70,6 @@ public function testRenamingColumnsWorks() $this->assertTrue($schema->hasColumns('test', ['bar', 'qux'])); } - public function testDroppingColumnsWorks() - { - $connection = DB::connection(); - $schema = $connection->getSchemaBuilder(); - - $blueprint = new Blueprint('users', function ($table) { - $table->dropColumn('name'); - }); - - $this->assertEquals(['alter table "users" drop column "name"'], $blueprint->toSql($connection, new SQLiteGrammar)); - } - public function testNativeColumnModifyingOnMySql() { $connection = DB::connection(); @@ -97,13 +85,12 @@ public function testNativeColumnModifyingOnMySql() }); $this->assertEquals([ - 'alter table `users` ' - .'modify `amount` double null invisible after `name`, ' - .'modify `added_at` timestamp(4) not null default CURRENT_TIMESTAMP(4) on update CURRENT_TIMESTAMP(4), ' - ."modify `difficulty` enum('easy', 'hard') character set utf8mb4 collate 'unicode' not null default 'easy', " - .'modify `positions` multipolygon srid 1234 as (expression) stored, ' - .'change `old_name` `new_name` varchar(50) not null, ' - ."modify `id` bigint unsigned not null auto_increment comment 'my comment' first", + 'alter table `users` modify `amount` double null invisible after `name`', + 'alter table `users` modify `added_at` timestamp(4) not null default CURRENT_TIMESTAMP(4) on update CURRENT_TIMESTAMP(4)', + "alter table `users` modify `difficulty` enum('easy', 'hard') character set utf8mb4 collate 'unicode' not null default 'easy'", + 'alter table `users` modify `positions` multipolygon srid 1234 as (expression) stored', + 'alter table `users` change `old_name` `new_name` varchar(50) not null', + "alter table `users` modify `id` bigint unsigned not null auto_increment comment 'my comment' first", 'alter table `users` auto_increment = 10', ], $blueprint->toSql($connection, new MySqlGrammar)); } @@ -589,7 +576,7 @@ public function testItDoesNotSetPrecisionHigherThanSupportedWhenRenamingTimestam public function testItEnsuresDroppingForeignKeyIsAvailable() { $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('This database driver does not support dropping foreign keys.'); + $this->expectExceptionMessage('This database driver does not support dropping foreign keys by name.'); Schema::table('users', function (Blueprint $table) { $table->dropForeign('something'); diff --git a/tests/Integration/Database/SchemaBuilderTest.php b/tests/Integration/Database/SchemaBuilderTest.php index 2112cc295a28..9022277aee63 100644 --- a/tests/Integration/Database/SchemaBuilderTest.php +++ b/tests/Integration/Database/SchemaBuilderTest.php @@ -135,11 +135,10 @@ public function testModifyNullableColumn() $queries = $blueprint->toSql($this->getConnection(), $this->getConnection()->getSchemaGrammar()); $expected = [ - 'alter table `test` ' - .'modify `not_null_column_to_not_null` text not null, ' - .'modify `not_null_column_to_nullable` text null, ' - .'modify `nullable_column_to_nullable` text null, ' - .'modify `nullable_column_to_not_null` text not null', + 'alter table `test` modify `not_null_column_to_not_null` text not null', + 'alter table `test` modify `not_null_column_to_nullable` text null', + 'alter table `test` modify `nullable_column_to_nullable` text null', + 'alter table `test` modify `nullable_column_to_not_null` text not null', ]; $this->assertEquals($expected, $queries); @@ -287,7 +286,7 @@ public function testAddingAutoIncrementColumn() }); Schema::table('test', function (Blueprint $table) { - $table->bigIncrements('id')->primary; + $table->bigIncrements('id'); }); $this->assertTrue(collect(Schema::getColumns('test'))->firstWhere('name', 'id')['auto_increment']); @@ -714,6 +713,124 @@ public function testGettingGeneratedColumns() )); } + public function testAddForeignKeysOnSqlite() + { + if ($this->driver !== 'sqlite') { + $this->markTestSkipped('Test requires a SQLite connection.'); + } + + Schema::create('users', function (Blueprint $table) { + $table->id(); + $table->string('name'); + }); + + Schema::create('posts', function (Blueprint $table) { + $table->string('title')->unique(); + }); + + Schema::table('posts', function (Blueprint $table) { + $table->foreignId('user_id')->nullable()->index()->constrained(); + $table->string('user_name'); + $table->foreign('user_name')->references('name')->on('users'); + }); + + $foreignKeys = Schema::getForeignKeys('posts'); + $this->assertCount(2, $foreignKeys); + $this->assertTrue(collect($foreignKeys)->contains(fn ($foreign) => $foreign['columns'] === ['user_id'] && $foreign['foreign_table'] === 'users' && $foreign['foreign_columns'] === ['id'])); + $this->assertTrue(collect($foreignKeys)->contains(fn ($foreign) => $foreign['columns'] === ['user_name'] && $foreign['foreign_table'] === 'users' && $foreign['foreign_columns'] === ['name'])); + $this->assertTrue(Schema::hasColumns('posts', ['title', 'user_id', 'user_name'])); + $this->assertTrue(Schema::hasIndex('posts', ['user_id'])); + $this->assertTrue(Schema::hasIndex('posts', ['title'], 'unique')); + } + + public function testDropForeignKeysOnSqlite() + { + if ($this->driver !== 'sqlite') { + $this->markTestSkipped('Test requires a SQLite connection.'); + } + + Schema::create('users', function (Blueprint $table) { + $table->id(); + $table->string('name'); + }); + + Schema::create('posts', function (Blueprint $table) { + $table->id(); + $table->foreignId('user_id')->nullable()->index()->constrained(); + $table->string('user_name')->unique(); + $table->foreign('user_name')->references('name')->on('users'); + }); + + $foreignKeys = Schema::getForeignKeys('posts'); + $this->assertCount(2, $foreignKeys); + $this->assertTrue(collect($foreignKeys)->contains(fn ($foreign) => $foreign['columns'] === ['user_id'] && $foreign['foreign_table'] === 'users' && $foreign['foreign_columns'] === ['id'])); + $this->assertTrue(collect($foreignKeys)->contains(fn ($foreign) => $foreign['columns'] === ['user_name'] && $foreign['foreign_table'] === 'users' && $foreign['foreign_columns'] === ['name'])); + $this->assertTrue(Schema::hasIndex('posts', ['id'], 'primary')); + + Schema::table('posts', function (Blueprint $table) { + $table->string('title')->unique(); + $table->dropIndex(['user_id']); + $table->dropForeign(['user_id']); + $table->dropColumn('user_id'); + }); + + $foreignKeys = Schema::getForeignKeys('posts'); + $this->assertCount(1, $foreignKeys); + $this->assertTrue(collect($foreignKeys)->contains(fn ($foreign) => $foreign['columns'] === ['user_name'] && $foreign['foreign_table'] === 'users' && $foreign['foreign_columns'] === ['name'])); + $this->assertTrue(Schema::hasColumns('posts', ['user_name', 'title'])); + $this->assertTrue(Schema::hasIndex('posts', ['id'], 'primary')); + $this->assertTrue(Schema::hasIndex('posts', ['title'], 'unique')); + $this->assertTrue(Schema::hasIndex('posts', ['user_name'], 'unique')); + $this->assertFalse(Schema::hasColumn('posts', 'user_id')); + $this->assertFalse(Schema::hasIndex('posts', ['user_id'])); + } + + public function testAddAndDropPrimaryOnSqlite() + { + if ($this->driver !== 'sqlite') { + $this->markTestSkipped('Test requires a SQLite connection.'); + } + + Schema::create('users', function (Blueprint $table) { + $table->id(); + $table->string('name'); + }); + + Schema::create('posts', function (Blueprint $table) { + $table->foreignId('user_id')->nullable()->index()->constrained(); + $table->string('user_name')->unique(); + $table->foreign('user_name')->references('name')->on('users'); + }); + + Schema::table('posts', function (Blueprint $table) { + $table->string('title')->primary(); + $table->dropIndex(['user_id']); + $table->dropForeign(['user_id']); + $table->dropColumn('user_id'); + }); + + $foreignKeys = Schema::getForeignKeys('posts'); + $this->assertCount(1, $foreignKeys); + $this->assertTrue(collect($foreignKeys)->contains(fn ($foreign) => $foreign['columns'] === ['user_name'] && $foreign['foreign_table'] === 'users' && $foreign['foreign_columns'] === ['name'])); + $this->assertTrue(Schema::hasColumns('posts', ['user_name', 'title'])); + $this->assertTrue(Schema::hasIndex('posts', ['title'], 'primary')); + $this->assertTrue(Schema::hasIndex('posts', ['user_name'], 'unique')); + $this->assertFalse(Schema::hasColumn('posts', 'user_id')); + $this->assertFalse(Schema::hasIndex('posts', ['user_id'])); + + Schema::table('posts', function (Blueprint $table) { + $table->dropPrimary(); + $table->integer('votes'); + }); + + $foreignKeys = Schema::getForeignKeys('posts'); + $this->assertCount(1, $foreignKeys); + $this->assertTrue(collect($foreignKeys)->contains(fn ($foreign) => $foreign['columns'] === ['user_name'] && $foreign['foreign_table'] === 'users' && $foreign['foreign_columns'] === ['name'])); + $this->assertTrue(Schema::hasColumns('posts', ['user_name', 'title', 'votes'])); + $this->assertFalse(Schema::hasIndex('posts', ['title'], 'primary')); + $this->assertTrue(Schema::hasIndex('posts', ['user_name'], 'unique')); + } + public function testAddingMacros() { Schema::macro('foo', fn () => 'foo');