diff --git a/src/Illuminate/Database/Schema/Blueprint.php b/src/Illuminate/Database/Schema/Blueprint.php index ee0988d5273a..d97f842f1d51 100755 --- a/src/Illuminate/Database/Schema/Blueprint.php +++ b/src/Illuminate/Database/Schema/Blueprint.php @@ -129,6 +129,15 @@ public function toSql(Connection $connection, Grammar $grammar) $this->ensureCommandsAreValid($connection); foreach ($this->commands as $command) { + if (in_array($command->name, $grammar->getFluentCommands()) + && (! $command->column->change && ! isset($command->column->{lcfirst($command->name)}) + || ($command->column->change && ! $connection->usingNativeSchemaOperations()))) { + // Doctrine DBAL handles fluent commands on change. Also, there is no need to + // compile them when creating or adding a column when its value is not set + // but we should always compile them when using native column modifying. + continue; + } + $method = 'compile'.ucfirst($command->name); if (method_exists($grammar, $method) || $grammar::hasMacro($method)) { @@ -188,6 +197,10 @@ protected function commandsNamed(array $names) */ protected function addImpliedCommands(Grammar $grammar) { + if ($this->hasAutoIncrementColumn() && ! $this->creating()) { + array_unshift($this->commands, $this->createCommand('autoIncrementStartingValues')); + } + if (count($this->getAddedColumns()) > 0 && ! $this->creating()) { array_unshift($this->commands, $this->createCommand('add')); } @@ -243,13 +256,7 @@ public function addFluentCommands(Grammar $grammar) { foreach ($this->columns as $column) { foreach ($grammar->getFluentCommands() as $commandName) { - $attributeName = lcfirst($commandName); - - if (! isset($column->{$attributeName})) { - continue; - } - - $value = $column->{$attributeName}; + $value = $column->{lcfirst($commandName)}; $this->addCommand( $commandName, compact('value', 'column') @@ -1794,7 +1801,7 @@ public function getChangedColumns() */ public function hasAutoIncrementColumn() { - return ! is_null(collect($this->getAddedColumns())->first(function ($column) { + return ! is_null(collect($this->columns)->first(function ($column) { return $column->autoIncrement === true; })); } @@ -1810,7 +1817,7 @@ public function autoIncrementingStartingValues() return []; } - return collect($this->getAddedColumns())->mapWithKeys(function ($column) { + return collect($this->columns)->mapWithKeys(function ($column) { return $column->autoIncrement === true ? [$column->name => $column->get('startingValue', $column->get('from'))] : [$column->name => null]; diff --git a/src/Illuminate/Database/Schema/Grammars/Grammar.php b/src/Illuminate/Database/Schema/Grammars/Grammar.php index ea8333e40436..1fc44af94c86 100755 --- a/src/Illuminate/Database/Schema/Grammars/Grammar.php +++ b/src/Illuminate/Database/Schema/Grammars/Grammar.php @@ -77,7 +77,7 @@ public function compileRenameColumn(Blueprint $blueprint, Fluent $command, Conne * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command * @param \Illuminate\Database\Connection $connection - * @return array + * @return array|string * * @throws \RuntimeException */ @@ -155,7 +155,7 @@ public function compileForeign(Blueprint $blueprint, Fluent $command) } /** - * Compile the blueprint's column definitions. + * Compile the blueprint's added column definitions. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @return array diff --git a/src/Illuminate/Database/Schema/Grammars/MySqlGrammar.php b/src/Illuminate/Database/Schema/Grammars/MySqlGrammar.php index 40ee3c8b873d..fb11ce2ddf7b 100755 --- a/src/Illuminate/Database/Schema/Grammars/MySqlGrammar.php +++ b/src/Illuminate/Database/Schema/Grammars/MySqlGrammar.php @@ -3,6 +3,7 @@ namespace Illuminate\Database\Schema\Grammars; use Illuminate\Database\Connection; +use Illuminate\Database\Query\Expression; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Fluent; use RuntimeException; @@ -16,7 +17,7 @@ class MySqlGrammar extends Grammar */ protected $modifiers = [ 'Unsigned', 'Charset', 'Collate', 'VirtualAs', 'StoredAs', 'Nullable', 'Invisible', - 'Srid', 'Default', 'Increment', 'Comment', 'After', 'First', + 'Srid', 'Default', 'OnUpdate', 'Increment', 'Comment', 'After', 'First', ]; /** @@ -178,16 +179,13 @@ protected function compileCreateEngine($sql, Connection $connection, Blueprint $ * * @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', $this->getColumns($blueprint)); - return array_values(array_merge( - ['alter table '.$this->wrapTable($blueprint).' '.implode(', ', $columns)], - $this->compileAutoIncrementStartingValues($blueprint) - )); + return 'alter table '.$this->wrapTable($blueprint).' '.implode(', ', $columns); } /** @@ -200,7 +198,7 @@ public function compileAutoIncrementStartingValues(Blueprint $blueprint) { return collect($blueprint->autoIncrementingStartingValues())->map(function ($value, $column) use ($blueprint) { return 'alter table '.$this->wrapTable($blueprint->getTable()).' auto_increment = '.$value; - })->all(); + })->values()->all(); } /** @@ -222,6 +220,33 @@ public function compileRenameColumn(Blueprint $blueprint, Fluent $command, Conne : parent::compileRenameColumn($blueprint, $command, $connection); } + /** + * 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) + { + if ($connection->usingNativeSchemaOperations()) { + $columns = []; + + foreach ($blueprint->getChangedColumns() as $column) { + $sql = 'modify '.$this->wrap($column).' '.$this->getType($column); + + $columns[] = $this->addModifiers($sql, $blueprint, $column); + } + + return 'alter table '.$this->wrapTable($blueprint).' '.implode(', ', $columns); + } + + return parent::compileChange($blueprint, $command, $connection); + } + /** * Compile a primary key command. * @@ -758,13 +783,17 @@ protected function typeDate(Fluent $column) */ protected function typeDateTime(Fluent $column) { - $columnType = $column->precision ? "datetime($column->precision)" : 'datetime'; - $current = $column->precision ? "CURRENT_TIMESTAMP($column->precision)" : 'CURRENT_TIMESTAMP'; - $columnType = $column->useCurrent ? "$columnType default $current" : $columnType; + if ($column->useCurrent) { + $column->default(new Expression($current)); + } + + if ($column->useCurrentOnUpdate) { + $column->onUpdate(new Expression($current)); + } - return $column->useCurrentOnUpdate ? "$columnType on update $current" : $columnType; + return $column->precision ? "datetime($column->precision)" : 'datetime'; } /** @@ -808,13 +837,17 @@ protected function typeTimeTz(Fluent $column) */ protected function typeTimestamp(Fluent $column) { - $columnType = $column->precision ? "timestamp($column->precision)" : 'timestamp'; - $current = $column->precision ? "CURRENT_TIMESTAMP($column->precision)" : 'CURRENT_TIMESTAMP'; - $columnType = $column->useCurrent ? "$columnType default $current" : $columnType; + if ($column->useCurrent) { + $column->default(new Expression($current)); + } + + if ($column->useCurrentOnUpdate) { + $column->onUpdate(new Expression($current)); + } - return $column->useCurrentOnUpdate ? "$columnType on update $current" : $columnType; + return $column->precision ? "timestamp($column->precision)" : 'timestamp'; } /** @@ -1175,6 +1208,20 @@ protected function modifyComment(Blueprint $blueprint, Fluent $column) } } + /** + * Get the SQL for an "on update" column modifier. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $column + * @return string|null + */ + protected function modifyOnUpdate(Blueprint $blueprint, Fluent $column) + { + if (! is_null($column->onUpdate)) { + return ' on update '.$column->onUpdate; + } + } + /** * Get the SQL for a SRID column modifier. * diff --git a/src/Illuminate/Database/Schema/Grammars/PostgresGrammar.php b/src/Illuminate/Database/Schema/Grammars/PostgresGrammar.php index ef60d0ff820f..8f486b5aa5e2 100755 --- a/src/Illuminate/Database/Schema/Grammars/PostgresGrammar.php +++ b/src/Illuminate/Database/Schema/Grammars/PostgresGrammar.php @@ -3,8 +3,10 @@ namespace Illuminate\Database\Schema\Grammars; use Illuminate\Database\Connection; +use Illuminate\Database\Query\Expression; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Fluent; +use LogicException; class PostgresGrammar extends Grammar { @@ -20,7 +22,7 @@ class PostgresGrammar extends Grammar * * @var string[] */ - protected $modifiers = ['Collate', 'Increment', 'Nullable', 'Default', 'VirtualAs', 'StoredAs']; + protected $modifiers = ['Collate', 'Increment', 'Nullable', 'Default', 'VirtualAs', 'StoredAs', 'GeneratedAs']; /** * The columns available as serials. @@ -111,10 +113,10 @@ public function compileCreate(Blueprint $blueprint, Fluent $command) */ public function compileAdd(Blueprint $blueprint, Fluent $command) { - return array_values(array_filter(array_merge([sprintf('alter table %s %s', + return sprintf('alter table %s %s', $this->wrapTable($blueprint), implode(', ', $this->prefixArray('add column', $this->getColumns($blueprint))) - )], $this->compileAutoIncrementStartingValues($blueprint)))); + ); } /** @@ -127,7 +129,7 @@ public function compileAutoIncrementStartingValues(Blueprint $blueprint) { return collect($blueprint->autoIncrementingStartingValues())->map(function ($value, $column) use ($blueprint) { return 'alter sequence '.$blueprint->getTable().'_'.$column.'_seq restart with '.$value; - })->all(); + })->values()->all(); } /** @@ -149,6 +151,48 @@ public function compileRenameColumn(Blueprint $blueprint, Fluent $command, Conne : parent::compileRenameColumn($blueprint, $command, $connection); } + /** + * 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) + { + if ($connection->usingNativeSchemaOperations()) { + $columns = []; + + foreach ($blueprint->getChangedColumns() as $column) { + $changes = ['type '.$this->getType($column).$this->modifyCollate($blueprint, $column)]; + + foreach ($this->modifiers as $modifier) { + if (in_array($modifier, ['Collate', 'Increment'])) { + // Already handled + continue; + } + + if (method_exists($this, $method = "modify{$modifier}")) { + $constraints = (array) $this->{$method}($blueprint, $column); + + 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 parent::compileChange($blueprint, $command, $connection); + } + /** * Compile a primary key command. * @@ -512,7 +556,7 @@ public function compileComment(Blueprint $blueprint, Fluent $command) return sprintf('comment on column %s.%s is %s', $this->wrapTable($blueprint), $this->wrap($command->column->name), - "'".str_replace("'", "''", $command->value)."'" + is_null($command->value) ? 'NULL' : "'".str_replace("'", "''", $command->value)."'" ); } @@ -527,7 +571,7 @@ public function compileTableComment(Blueprint $blueprint, Fluent $command) { return sprintf('comment on table %s is %s', $this->wrapTable($blueprint), - "'".str_replace("'", "''", $command->comment)."'" + is_null($command->comment) ? 'NULL' : "'".str_replace("'", "''", $command->comment)."'" ); } @@ -628,7 +672,7 @@ protected function typeLongText(Fluent $column) */ protected function typeInteger(Fluent $column) { - return $this->generatableColumn('integer', $column); + return $column->autoIncrement && is_null($column->generatedAs) ? 'serial' : 'integer'; } /** @@ -639,7 +683,7 @@ protected function typeInteger(Fluent $column) */ protected function typeBigInteger(Fluent $column) { - return $this->generatableColumn('bigint', $column); + return $column->autoIncrement && is_null($column->generatedAs) ? 'bigserial' : 'bigint'; } /** @@ -650,7 +694,7 @@ protected function typeBigInteger(Fluent $column) */ protected function typeMediumInteger(Fluent $column) { - return $this->generatableColumn('integer', $column); + return $this->typeInteger($column); } /** @@ -661,7 +705,7 @@ protected function typeMediumInteger(Fluent $column) */ protected function typeTinyInteger(Fluent $column) { - return $this->generatableColumn('smallint', $column); + return $this->typeSmallInteger($column); } /** @@ -672,42 +716,7 @@ protected function typeTinyInteger(Fluent $column) */ protected function typeSmallInteger(Fluent $column) { - return $this->generatableColumn('smallint', $column); - } - - /** - * Create the column definition for a generatable column. - * - * @param string $type - * @param \Illuminate\Support\Fluent $column - * @return string - */ - protected function generatableColumn($type, Fluent $column) - { - if (! $column->autoIncrement && is_null($column->generatedAs)) { - return $type; - } - - if ($column->autoIncrement && is_null($column->generatedAs)) { - return with([ - 'integer' => 'serial', - 'bigint' => 'bigserial', - 'smallint' => 'smallserial', - ])[$type]; - } - - $options = ''; - - if (! is_bool($column->generatedAs) && ! empty($column->generatedAs)) { - $options = sprintf(' (%s)', $column->generatedAs); - } - - return sprintf( - '%s generated %s as identity%s', - $type, - $column->always ? 'always' : 'by default', - $options - ); + return $column->autoIncrement && is_null($column->generatedAs) ? 'smallserial' : 'smallint'; } /** @@ -865,9 +874,11 @@ protected function typeTimeTz(Fluent $column) */ protected function typeTimestamp(Fluent $column) { - $columnType = 'timestamp'.(is_null($column->precision) ? '' : "($column->precision)").' without time zone'; + if ($column->useCurrent) { + $column->default(new Expression('CURRENT_TIMESTAMP')); + } - return $column->useCurrent ? "$columnType default CURRENT_TIMESTAMP" : $columnType; + return 'timestamp'.(is_null($column->precision) ? '' : "($column->precision)").' without time zone'; } /** @@ -878,9 +889,11 @@ protected function typeTimestamp(Fluent $column) */ protected function typeTimestampTz(Fluent $column) { - $columnType = 'timestamp'.(is_null($column->precision) ? '' : "($column->precision)").' with time zone'; + if ($column->useCurrent) { + $column->default(new Expression('CURRENT_TIMESTAMP')); + } - return $column->useCurrent ? "$columnType default CURRENT_TIMESTAMP" : $columnType; + return 'timestamp'.(is_null($column->precision) ? '' : "($column->precision)").' with time zone'; } /** @@ -1080,6 +1093,10 @@ protected function modifyCollate(Blueprint $blueprint, Fluent $column) */ protected function modifyNullable(Blueprint $blueprint, Fluent $column) { + if ($column->change) { + return $column->nullable ? 'drop not null' : 'set not null'; + } + return $column->nullable ? ' null' : ' not null'; } @@ -1092,6 +1109,10 @@ protected function modifyNullable(Blueprint $blueprint, Fluent $column) */ protected function modifyDefault(Blueprint $blueprint, Fluent $column) { + if ($column->change) { + return is_null($column->default) ? 'drop default' : 'set default '.$this->getDefaultValue($column->default); + } + if (! is_null($column->default)) { return ' default '.$this->getDefaultValue($column->default); } @@ -1120,7 +1141,17 @@ protected function modifyIncrement(Blueprint $blueprint, Fluent $column) */ protected function modifyVirtualAs(Blueprint $blueprint, Fluent $column) { - if ($column->virtualAs !== null) { + if ($column->change) { + if (array_key_exists('virtualAs', $column->getAttributes())) { + return is_null($column->virtualAs) + ? 'drop expression if exists' + : throw new LogicException('This database driver does not support modifying generated columns.'); + } + + return null; + } + + if (! is_null($column->virtualAs)) { return " generated always as ({$column->virtualAs})"; } } @@ -1134,8 +1165,56 @@ protected function modifyVirtualAs(Blueprint $blueprint, Fluent $column) */ protected function modifyStoredAs(Blueprint $blueprint, Fluent $column) { - if ($column->storedAs !== null) { + if ($column->change) { + if (array_key_exists('storedAs', $column->getAttributes())) { + return is_null($column->storedAs) + ? 'drop expression if exists' + : throw new LogicException('This database driver does not support modifying generated columns.'); + } + + return null; + } + + if (! is_null($column->storedAs)) { return " generated always as ({$column->storedAs}) stored"; } } + + /** + * Get the SQL for an identity column modifier. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $column + * @return string|array|null + */ + protected function modifyGeneratedAs(Blueprint $blueprint, Fluent $column) + { + $sql = null; + + if (! is_null($column->generatedAs)) { + $options = ''; + + if (! is_bool($column->generatedAs) && ! empty($column->generatedAs)) { + $options = sprintf(' (%s)', $column->generatedAs); + } + + $sql = sprintf( + ' generated %s as identity%s', + $column->always ? 'always' : 'by default', + $options + ); + } + + if ($column->change) { + $changes = ['drop identity if exists']; + + if (! is_null($sql)) { + $changes[] = 'add '.$sql; + } + + return $changes; + } + + return $sql; + } } diff --git a/src/Illuminate/Database/Schema/Grammars/SQLiteGrammar.php b/src/Illuminate/Database/Schema/Grammars/SQLiteGrammar.php index c9d1c5503aaa..6d3f53f0eb15 100755 --- a/src/Illuminate/Database/Schema/Grammars/SQLiteGrammar.php +++ b/src/Illuminate/Database/Schema/Grammars/SQLiteGrammar.php @@ -4,6 +4,7 @@ use Doctrine\DBAL\Schema\Index; use Illuminate\Database\Connection; +use Illuminate\Database\Query\Expression; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Arr; use Illuminate\Support\Fluent; @@ -727,7 +728,11 @@ protected function typeTimeTz(Fluent $column) */ protected function typeTimestamp(Fluent $column) { - return $column->useCurrent ? 'datetime default CURRENT_TIMESTAMP' : 'datetime'; + if ($column->useCurrent) { + $column->default(new Expression('CURRENT_TIMESTAMP')); + } + + return 'datetime'; } /** diff --git a/src/Illuminate/Database/Schema/Grammars/SqlServerGrammar.php b/src/Illuminate/Database/Schema/Grammars/SqlServerGrammar.php index 4d7271ca3308..5437f068917e 100755 --- a/src/Illuminate/Database/Schema/Grammars/SqlServerGrammar.php +++ b/src/Illuminate/Database/Schema/Grammars/SqlServerGrammar.php @@ -3,6 +3,7 @@ namespace Illuminate\Database\Schema\Grammars; use Illuminate\Database\Connection; +use Illuminate\Database\Query\Expression; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Fluent; @@ -29,6 +30,13 @@ class SqlServerGrammar extends Grammar */ protected $serials = ['tinyInteger', 'smallInteger', 'mediumInteger', 'integer', 'bigInteger']; + /** + * The commands to be executed outside of create or alter command. + * + * @var string[] + */ + protected $fluentCommands = ['Default']; + /** * Compile a create database command. * @@ -126,6 +134,47 @@ public function compileRenameColumn(Blueprint $blueprint, Fluent $command, Conne : parent::compileRenameColumn($blueprint, $command, $connection); } + /** + * 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) + { + if ($connection->usingNativeSchemaOperations()) { + $columns = []; + + foreach ($blueprint->getChangedColumns() as $column) { + $sql = sprintf('alter table %s alter column %s %s', + $this->wrapTable($blueprint), + $this->wrap($column), + $this->getType($column) + ); + + foreach ($this->modifiers as $modifier) { + if (in_array($modifier, ['Increment', 'Default'])) { + continue; + } + + if (method_exists($this, $method = "modify{$modifier}")) { + $sql .= $this->{$method}($blueprint, $column); + } + } + + $columns[] = $sql; + } + + return $columns; + } + + return parent::compileChange($blueprint, $command, $connection); + } + /** * Compile a primary key command. * @@ -190,6 +239,28 @@ public function compileSpatialIndex(Blueprint $blueprint, Fluent $command) ); } + /** + * Compile a default command. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $command + * @return string|null + */ + public function compileDefault(Blueprint $blueprint, Fluent $command) + { + if ($command->column->change) { + $dropDefaultConstraintSql = $this->compileDropDefaultConstraint($blueprint, $command); + + return is_null($command->column->default) + ? $dropDefaultConstraintSql + : $dropDefaultConstraintSql.sprintf('alter table %s add default %s for %s', + $this->wrapTable($blueprint), + $this->getDefaultValue($command->column->default), + $this->wrap($command->column) + ); + } + } + /** * Compile a drop table command. * @@ -238,7 +309,7 @@ public function compileDropColumn(Blueprint $blueprint, Fluent $command) { $columns = $this->wrapArray($command->columns); - $dropExistingConstraintsSql = $this->compileDropDefaultConstraint($blueprint, $command).';'; + $dropExistingConstraintsSql = $this->compileDropDefaultConstraint($blueprint, $command); return $dropExistingConstraintsSql.'alter table '.$this->wrapTable($blueprint).' drop column '.implode(', ', $columns); } @@ -252,7 +323,7 @@ public function compileDropColumn(Blueprint $blueprint, Fluent $command) */ public function compileDropDefaultConstraint(Blueprint $blueprint, Fluent $command) { - $columns = "'".implode("','", $command->columns)."'"; + $columns = "'".implode("','", $command->columns ?? [$command->column->name])."'"; $tableName = $this->getTablePrefix().$blueprint->getTable(); @@ -260,7 +331,7 @@ public function compileDropDefaultConstraint(Blueprint $blueprint, Fluent $comma $sql .= "SELECT @sql += 'ALTER TABLE [dbo].[{$tableName}] DROP CONSTRAINT ' + OBJECT_NAME([default_object_id]) + ';' "; $sql .= 'FROM sys.columns '; $sql .= "WHERE [object_id] = OBJECT_ID('[dbo].[{$tableName}]') AND [name] in ({$columns}) AND [default_object_id] <> 0;"; - $sql .= 'EXEC(@sql)'; + $sql .= 'EXEC(@sql);'; return $sql; } @@ -697,9 +768,11 @@ protected function typeTimeTz(Fluent $column) */ protected function typeTimestamp(Fluent $column) { - $columnType = $column->precision ? "datetime2($column->precision)" : 'datetime'; + if ($column->useCurrent) { + $column->default(new Expression('CURRENT_TIMESTAMP')); + } - return $column->useCurrent ? "$columnType default CURRENT_TIMESTAMP" : $columnType; + return $column->precision ? "datetime2($column->precision)" : 'datetime'; } /** @@ -712,9 +785,11 @@ protected function typeTimestamp(Fluent $column) */ protected function typeTimestampTz(Fluent $column) { - $columnType = $column->precision ? "datetimeoffset($column->precision)" : 'datetimeoffset'; + if ($column->useCurrent) { + $column->default(new Expression('CURRENT_TIMESTAMP')); + } - return $column->useCurrent ? "$columnType default CURRENT_TIMESTAMP" : $columnType; + return $column->precision ? "datetimeoffset($column->precision)" : 'datetimeoffset'; } /** @@ -936,6 +1011,14 @@ protected function modifyIncrement(Blueprint $blueprint, Fluent $column) */ protected function modifyPersisted(Blueprint $blueprint, Fluent $column) { + if ($column->change) { + if ($column->type === 'computed') { + return $column->persisted ? ' add persisted' : ' drop persisted'; + } + + return null; + } + if ($column->persisted) { return ' persisted'; } diff --git a/tests/Database/DatabaseMySqlSchemaGrammarTest.php b/tests/Database/DatabaseMySqlSchemaGrammarTest.php index 61ac2224caf8..1a64125870b6 100755 --- a/tests/Database/DatabaseMySqlSchemaGrammarTest.php +++ b/tests/Database/DatabaseMySqlSchemaGrammarTest.php @@ -837,7 +837,7 @@ public function testAddingDateTimeWithDefaultCurrent() $blueprint->dateTime('foo')->useCurrent(); $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); $this->assertCount(1, $statements); - $this->assertSame('alter table `users` add `foo` datetime default CURRENT_TIMESTAMP not null', $statements[0]); + $this->assertSame('alter table `users` add `foo` datetime not null default CURRENT_TIMESTAMP', $statements[0]); } public function testAddingDateTimeWithOnUpdateCurrent() @@ -846,7 +846,7 @@ public function testAddingDateTimeWithOnUpdateCurrent() $blueprint->dateTime('foo')->useCurrentOnUpdate(); $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); $this->assertCount(1, $statements); - $this->assertSame('alter table `users` add `foo` datetime on update CURRENT_TIMESTAMP not null', $statements[0]); + $this->assertSame('alter table `users` add `foo` datetime not null on update CURRENT_TIMESTAMP', $statements[0]); } public function testAddingDateTimeWithDefaultCurrentAndOnUpdateCurrent() @@ -855,7 +855,7 @@ public function testAddingDateTimeWithDefaultCurrentAndOnUpdateCurrent() $blueprint->dateTime('foo')->useCurrent()->useCurrentOnUpdate(); $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); $this->assertCount(1, $statements); - $this->assertSame('alter table `users` add `foo` datetime default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP not null', $statements[0]); + $this->assertSame('alter table `users` add `foo` datetime not null default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP', $statements[0]); } public function testAddingDateTimeWithDefaultCurrentOnUpdateCurrentAndPrecision() @@ -864,7 +864,7 @@ public function testAddingDateTimeWithDefaultCurrentOnUpdateCurrentAndPrecision( $blueprint->dateTime('foo', 3)->useCurrent()->useCurrentOnUpdate(); $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); $this->assertCount(1, $statements); - $this->assertSame('alter table `users` add `foo` datetime(3) default CURRENT_TIMESTAMP(3) on update CURRENT_TIMESTAMP(3) not null', $statements[0]); + $this->assertSame('alter table `users` add `foo` datetime(3) not null default CURRENT_TIMESTAMP(3) on update CURRENT_TIMESTAMP(3)', $statements[0]); } public function testAddingDateTimeTz() @@ -951,7 +951,7 @@ public function testAddingTimestampWithDefaultCurrentSpecifyingPrecision() $blueprint->timestamp('created_at', 1)->useCurrent(); $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); $this->assertCount(1, $statements); - $this->assertSame('alter table `users` add `created_at` timestamp(1) default CURRENT_TIMESTAMP(1) not null', $statements[0]); + $this->assertSame('alter table `users` add `created_at` timestamp(1) not null default CURRENT_TIMESTAMP(1)', $statements[0]); } public function testAddingTimestampWithOnUpdateCurrentSpecifyingPrecision() @@ -960,7 +960,7 @@ public function testAddingTimestampWithOnUpdateCurrentSpecifyingPrecision() $blueprint->timestamp('created_at', 1)->useCurrentOnUpdate(); $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); $this->assertCount(1, $statements); - $this->assertSame('alter table `users` add `created_at` timestamp(1) on update CURRENT_TIMESTAMP(1) not null', $statements[0]); + $this->assertSame('alter table `users` add `created_at` timestamp(1) not null on update CURRENT_TIMESTAMP(1)', $statements[0]); } public function testAddingTimestampWithDefaultCurrentAndOnUpdateCurrentSpecifyingPrecision() @@ -969,7 +969,7 @@ public function testAddingTimestampWithDefaultCurrentAndOnUpdateCurrentSpecifyin $blueprint->timestamp('created_at', 1)->useCurrent()->useCurrentOnUpdate(); $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); $this->assertCount(1, $statements); - $this->assertSame('alter table `users` add `created_at` timestamp(1) default CURRENT_TIMESTAMP(1) on update CURRENT_TIMESTAMP(1) not null', $statements[0]); + $this->assertSame('alter table `users` add `created_at` timestamp(1) not null default CURRENT_TIMESTAMP(1) on update CURRENT_TIMESTAMP(1)', $statements[0]); } public function testAddingTimestampTz() diff --git a/tests/Database/DatabasePostgresSchemaGrammarTest.php b/tests/Database/DatabasePostgresSchemaGrammarTest.php index 98066f4f90eb..aee18467e888 100755 --- a/tests/Database/DatabasePostgresSchemaGrammarTest.php +++ b/tests/Database/DatabasePostgresSchemaGrammarTest.php @@ -902,25 +902,25 @@ public function testAddingGeneratedAs() $blueprint->increments('foo')->generatedAs(); $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); $this->assertCount(1, $statements); - $this->assertSame('alter table "users" add column "foo" integer generated by default as identity primary key not null', $statements[0]); + $this->assertSame('alter table "users" add column "foo" integer primary key not null generated by default as identity', $statements[0]); // With always modifier $blueprint = new Blueprint('users'); $blueprint->increments('foo')->generatedAs()->always(); $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); $this->assertCount(1, $statements); - $this->assertSame('alter table "users" add column "foo" integer generated always as identity primary key not null', $statements[0]); + $this->assertSame('alter table "users" add column "foo" integer primary key not null generated always as identity', $statements[0]); // With sequence options $blueprint = new Blueprint('users'); $blueprint->increments('foo')->generatedAs('increment by 10 start with 100'); $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); $this->assertCount(1, $statements); - $this->assertSame('alter table "users" add column "foo" integer generated by default as identity (increment by 10 start with 100) primary key not null', $statements[0]); + $this->assertSame('alter table "users" add column "foo" integer primary key not null generated by default as identity (increment by 10 start with 100)', $statements[0]); // Not a primary key $blueprint = new Blueprint('users'); $blueprint->integer('foo')->generatedAs(); $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); $this->assertCount(1, $statements); - $this->assertSame('alter table "users" add column "foo" integer generated by default as identity not null', $statements[0]); + $this->assertSame('alter table "users" add column "foo" integer not null generated by default as identity', $statements[0]); } public function testAddingVirtualAs() diff --git a/tests/Database/DatabaseSchemaBlueprintIntegrationTest.php b/tests/Database/DatabaseSchemaBlueprintIntegrationTest.php index b6b081496be4..5829f382c65c 100644 --- a/tests/Database/DatabaseSchemaBlueprintIntegrationTest.php +++ b/tests/Database/DatabaseSchemaBlueprintIntegrationTest.php @@ -154,6 +154,145 @@ public function testDroppingColumnsWithoutDoctrineWorks() $this->assertEquals(['alter table "users" drop column "name"'], $blueprint->toSql($connection, new SQLiteGrammar)); } + public function testNativeColumnModifyingOnMySql() + { + $connection = $this->db->connection(); + $schema = $connection->getSchemaBuilder(); + + $schema->useNativeSchemaOperationsIfPossible(); + + $blueprint = new Blueprint('users', function ($table) { + $table->double('amount', 6, 2)->nullable()->invisible()->after('name')->change(); + $table->timestamp('added_at', 4)->nullable(false)->useCurrent()->useCurrentOnUpdate()->change(); + $table->enum('difficulty', ['easy', 'hard'])->default('easy')->charset('utf8mb4')->collation('unicode')->change(); + $table->multiPolygon('positions')->srid(1234)->storedAs('expression')->change(); + $table->bigIncrements('id')->first()->from(10)->comment('my comment')->change(); + }); + + $this->assertEquals([ + 'alter table `users` ' + .'modify `amount` double(6, 2) 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 as (expression) stored srid 1234, ' + ."modify `id` bigint unsigned not null auto_increment primary key comment 'my comment' first", + 'alter table `users` auto_increment = 10', + ], $blueprint->toSql($connection, new MySqlGrammar)); + } + + public function testNativeColumnModifyingOnPostgreSql() + { + $connection = $this->db->connection(); + $schema = $connection->getSchemaBuilder(); + + $schema->useNativeSchemaOperationsIfPossible(); + + $blueprint = new Blueprint('users', function ($table) { + $table->integer('code')->autoIncrement()->from(10)->comment('my comment')->change(); + }); + + $this->assertEquals([ + 'alter table "users" ' + .'alter column "code" type serial, ' + .'alter column "code" set not null, ' + .'alter column "code" drop default, ' + .'alter column "code" drop identity if exists', + 'alter sequence users_code_seq restart with 10', + 'comment on column "users"."code" is \'my comment\'', + ], $blueprint->toSql($connection, new PostgresGrammar)); + + $blueprint = new Blueprint('users', function ($table) { + $table->char('name', 40)->nullable()->default('easy')->collation('unicode')->change(); + }); + + $this->assertEquals([ + 'alter table "users" ' + .'alter column "name" type char(40) collate "unicode", ' + .'alter column "name" drop not null, ' + .'alter column "name" set default \'easy\', ' + .'alter column "name" drop identity if exists', + 'comment on column "users"."name" is NULL', + ], $blueprint->toSql($connection, new PostgresGrammar)); + + $blueprint = new Blueprint('users', function ($table) { + $table->integer('foo')->generatedAs('expression')->always()->change(); + }); + + $this->assertEquals([ + 'alter table "users" ' + .'alter column "foo" type integer, ' + .'alter column "foo" set not null, ' + .'alter column "foo" drop default, ' + .'alter column "foo" drop identity if exists, ' + .'alter column "foo" add generated always as identity (expression)', + 'comment on column "users"."foo" is NULL', + ], $blueprint->toSql($connection, new PostgresGrammar)); + + $blueprint = new Blueprint('users', function ($table) { + $table->point('foo')->isGeometry()->projection(1234)->change(); + }); + + $this->assertEquals([ + 'alter table "users" ' + .'alter column "foo" type geometry(point, 1234), ' + .'alter column "foo" set not null, ' + .'alter column "foo" drop default, ' + .'alter column "foo" drop identity if exists', + 'comment on column "users"."foo" is NULL', + ], $blueprint->toSql($connection, new PostgresGrammar)); + + $blueprint = new Blueprint('users', function ($table) { + $table->timestamp('added_at', 2)->useCurrent()->storedAs(null)->change(); + }); + + $this->assertEquals([ + 'alter table "users" ' + .'alter column "added_at" type timestamp(2) without time zone, ' + .'alter column "added_at" set not null, ' + .'alter column "added_at" set default CURRENT_TIMESTAMP, ' + .'alter column "added_at" drop expression if exists, ' + .'alter column "added_at" drop identity if exists', + 'comment on column "users"."added_at" is NULL', + ], $blueprint->toSql($connection, new PostgresGrammar)); + } + + public function testNativeColumnModifyingOnSqlServer() + { + $connection = $this->db->connection(); + $schema = $connection->getSchemaBuilder(); + + $schema->useNativeSchemaOperationsIfPossible(); + + $blueprint = new Blueprint('users', function ($table) { + $table->timestamp('added_at', 4)->nullable(false)->useCurrent()->change(); + }); + + $this->assertEquals([ + 'alter table "users" alter column "added_at" datetime2(4) not null', + "DECLARE @sql NVARCHAR(MAX) = '';SELECT @sql += 'ALTER TABLE [dbo].[users] DROP CONSTRAINT ' + OBJECT_NAME([default_object_id]) + ';' FROM sys.columns WHERE [object_id] = OBJECT_ID('[dbo].[users]') AND [name] in ('added_at') AND [default_object_id] <> 0;EXEC(@sql);" + .'alter table "users" add default CURRENT_TIMESTAMP for "added_at"', + ], $blueprint->toSql($connection, new SqlServerGrammar)); + + $blueprint = new Blueprint('users', function ($table) { + $table->char('name', 40)->nullable()->default('easy')->collation('unicode')->change(); + }); + + $this->assertEquals([ + 'alter table "users" alter column "name" nchar(40) collate unicode null', + "DECLARE @sql NVARCHAR(MAX) = '';SELECT @sql += 'ALTER TABLE [dbo].[users] DROP CONSTRAINT ' + OBJECT_NAME([default_object_id]) + ';' FROM sys.columns WHERE [object_id] = OBJECT_ID('[dbo].[users]') AND [name] in ('name') AND [default_object_id] <> 0;EXEC(@sql);" + .'alter table "users" add default \'easy\' for "name"', + ], $blueprint->toSql($connection, new SqlServerGrammar)); + + $blueprint = new Blueprint('users', function ($table) { + $table->integer('foo')->change(); + }); + + $this->assertEquals([ + 'alter table "users" alter column "foo" int not null', + "DECLARE @sql NVARCHAR(MAX) = '';SELECT @sql += 'ALTER TABLE [dbo].[users] DROP CONSTRAINT ' + OBJECT_NAME([default_object_id]) + ';' FROM sys.columns WHERE [object_id] = OBJECT_ID('[dbo].[users]') AND [name] in ('foo') AND [default_object_id] <> 0;EXEC(@sql);", + ], $blueprint->toSql($connection, new SqlServerGrammar)); + } + public function testChangingColumnWithCollationWorks() { $this->db->connection()->getSchemaBuilder()->create('users', function ($table) { diff --git a/tests/Database/DatabaseSchemaBlueprintTest.php b/tests/Database/DatabaseSchemaBlueprintTest.php index 3e6ce17f5a43..45a72191faa6 100755 --- a/tests/Database/DatabaseSchemaBlueprintTest.php +++ b/tests/Database/DatabaseSchemaBlueprintTest.php @@ -113,16 +113,16 @@ public function testDefaultCurrentDateTime() $connection = m::mock(Connection::class); $blueprint = clone $base; - $this->assertEquals(['alter table `users` add `created` datetime default CURRENT_TIMESTAMP not null'], $blueprint->toSql($connection, new MySqlGrammar)); + $this->assertEquals(['alter table `users` add `created` datetime not null default CURRENT_TIMESTAMP'], $blueprint->toSql($connection, new MySqlGrammar)); $blueprint = clone $base; - $this->assertEquals(['alter table "users" add column "created" timestamp(0) without time zone default CURRENT_TIMESTAMP not null'], $blueprint->toSql($connection, new PostgresGrammar)); + $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; - $this->assertEquals(['alter table "users" add column "created" datetime default CURRENT_TIMESTAMP not null'], $blueprint->toSql($connection, new SQLiteGrammar)); + $this->assertEquals(['alter table "users" add column "created" datetime not null default CURRENT_TIMESTAMP'], $blueprint->toSql($connection, new SQLiteGrammar)); $blueprint = clone $base; - $this->assertEquals(['alter table "users" add "created" datetime default CURRENT_TIMESTAMP not null'], $blueprint->toSql($connection, new SqlServerGrammar)); + $this->assertEquals(['alter table "users" add "created" datetime not null default CURRENT_TIMESTAMP'], $blueprint->toSql($connection, new SqlServerGrammar)); } public function testDefaultCurrentTimestamp() @@ -134,16 +134,16 @@ public function testDefaultCurrentTimestamp() $connection = m::mock(Connection::class); $blueprint = clone $base; - $this->assertEquals(['alter table `users` add `created` timestamp default CURRENT_TIMESTAMP not null'], $blueprint->toSql($connection, new MySqlGrammar)); + $this->assertEquals(['alter table `users` add `created` timestamp not null default CURRENT_TIMESTAMP'], $blueprint->toSql($connection, new MySqlGrammar)); $blueprint = clone $base; - $this->assertEquals(['alter table "users" add column "created" timestamp(0) without time zone default CURRENT_TIMESTAMP not null'], $blueprint->toSql($connection, new PostgresGrammar)); + $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; - $this->assertEquals(['alter table "users" add column "created" datetime default CURRENT_TIMESTAMP not null'], $blueprint->toSql($connection, new SQLiteGrammar)); + $this->assertEquals(['alter table "users" add column "created" datetime not null default CURRENT_TIMESTAMP'], $blueprint->toSql($connection, new SQLiteGrammar)); $blueprint = clone $base; - $this->assertEquals(['alter table "users" add "created" datetime default CURRENT_TIMESTAMP not null'], $blueprint->toSql($connection, new SqlServerGrammar)); + $this->assertEquals(['alter table "users" add "created" datetime not null default CURRENT_TIMESTAMP'], $blueprint->toSql($connection, new SqlServerGrammar)); } public function testUnsignedDecimalTable()