Skip to content

Commit 72f6e37

Browse files
[9.x] Add support for native rename/drop column commands (#45258)
* add `compileRenameColumn` to DB grammar classes * add tests * add a static property to schema builder * add native drop column for SQLite * formatting * add the optional column keyword to drop command * fix php 8.0 windows tests * merge previously added schema methods into one * better test * formatting and renaming * formatting * fix tests Co-authored-by: Taylor Otwell <[email protected]>
1 parent 26c476d commit 72f6e37

File tree

11 files changed

+224
-10
lines changed

11 files changed

+224
-10
lines changed

src/Illuminate/Database/Connection.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1076,6 +1076,16 @@ public function isDoctrineAvailable()
10761076
return class_exists('Doctrine\DBAL\Connection');
10771077
}
10781078

1079+
/**
1080+
* Indicates whether native alter operations will be used when dropping or renaming columns, even if Doctrine DBAL is installed.
1081+
*
1082+
* @return bool
1083+
*/
1084+
public function usingNativeSchemaOperations()
1085+
{
1086+
return ! $this->isDoctrineAvailable() || SchemaBuilder::$alwaysUsesNativeSchemaOperationsIfPossible;
1087+
}
1088+
10791089
/**
10801090
* Get a Doctrine Schema Column instance.
10811091
*

src/Illuminate/Database/Schema/Blueprint.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,8 @@ public function toSql(Connection $connection, Grammar $grammar)
152152
protected function ensureCommandsAreValid(Connection $connection)
153153
{
154154
if ($connection instanceof SQLiteConnection) {
155-
if ($this->commandsNamed(['dropColumn', 'renameColumn'])->count() > 1) {
155+
if ($this->commandsNamed(['dropColumn', 'renameColumn'])->count() > 1
156+
&& ! $connection->usingNativeSchemaOperations()) {
156157
throw new BadMethodCallException(
157158
"SQLite doesn't support multiple calls to dropColumn / renameColumn in a single modification."
158159
);

src/Illuminate/Database/Schema/Builder.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,13 @@ class Builder
4545
*/
4646
public static $defaultMorphKeyType = 'int';
4747

48+
/**
49+
* Indicates whether Doctrine DBAL usage will be prevented if possible when dropping and renaming columns.
50+
*
51+
* @var bool
52+
*/
53+
public static $alwaysUsesNativeSchemaOperationsIfPossible = false;
54+
4855
/**
4956
* Create a new database Schema manager.
5057
*
@@ -105,6 +112,17 @@ public static function morphUsingUlids()
105112
return static::defaultMorphKeyType('ulid');
106113
}
107114

115+
/**
116+
* Attempt to use native schema operations for dropping and renaming columns, even if Doctrine DBAL is installed.
117+
*
118+
* @param bool $value
119+
* @return void
120+
*/
121+
public static function useNativeSchemaOperationsIfPossible(bool $value = true)
122+
{
123+
static::$alwaysUsesNativeSchemaOperationsIfPossible = $value;
124+
}
125+
108126
/**
109127
* Create a database in the schema.
110128
*

src/Illuminate/Database/Schema/Grammars/Grammar.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ public function compileDropDatabaseIfExists($name)
6464
* @param \Illuminate\Database\Schema\Blueprint $blueprint
6565
* @param \Illuminate\Support\Fluent $command
6666
* @param \Illuminate\Database\Connection $connection
67-
* @return array
67+
* @return array|string
6868
*/
6969
public function compileRenameColumn(Blueprint $blueprint, Fluent $command, Connection $connection)
7070
{

src/Illuminate/Database/Schema/Grammars/MySqlGrammar.php

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,25 @@ public function compileAutoIncrementStartingValues(Blueprint $blueprint)
203203
})->all();
204204
}
205205

206+
/**
207+
* Compile a rename column command.
208+
*
209+
* @param \Illuminate\Database\Schema\Blueprint $blueprint
210+
* @param \Illuminate\Support\Fluent $command
211+
* @param \Illuminate\Database\Connection $connection
212+
* @return array|string
213+
*/
214+
public function compileRenameColumn(Blueprint $blueprint, Fluent $command, Connection $connection)
215+
{
216+
return $connection->usingNativeSchemaOperations()
217+
? sprintf('alter table %s rename column %s to %s',
218+
$this->wrapTable($blueprint),
219+
$this->wrap($command->from),
220+
$this->wrap($command->to)
221+
)
222+
: parent::compileRenameColumn($blueprint, $command, $connection);
223+
}
224+
206225
/**
207226
* Compile a primary key command.
208227
*

src/Illuminate/Database/Schema/Grammars/PostgresGrammar.php

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace Illuminate\Database\Schema\Grammars;
44

5+
use Illuminate\Database\Connection;
56
use Illuminate\Database\Schema\Blueprint;
67
use Illuminate\Support\Fluent;
78

@@ -129,6 +130,25 @@ public function compileAutoIncrementStartingValues(Blueprint $blueprint)
129130
})->all();
130131
}
131132

133+
/**
134+
* Compile a rename column command.
135+
*
136+
* @param \Illuminate\Database\Schema\Blueprint $blueprint
137+
* @param \Illuminate\Support\Fluent $command
138+
* @param \Illuminate\Database\Connection $connection
139+
* @return array|string
140+
*/
141+
public function compileRenameColumn(Blueprint $blueprint, Fluent $command, Connection $connection)
142+
{
143+
return $connection->usingNativeSchemaOperations()
144+
? sprintf('alter table %s rename column %s to %s',
145+
$this->wrapTable($blueprint),
146+
$this->wrap($command->from),
147+
$this->wrap($command->to)
148+
)
149+
: parent::compileRenameColumn($blueprint, $command, $connection);
150+
}
151+
132152
/**
133153
* Compile a primary key command.
134154
*

src/Illuminate/Database/Schema/Grammars/SQLiteGrammar.php

Lines changed: 37 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,25 @@ public function compileAdd(Blueprint $blueprint, Fluent $command)
144144
})->all();
145145
}
146146

147+
/**
148+
* Compile a rename column command.
149+
*
150+
* @param \Illuminate\Database\Schema\Blueprint $blueprint
151+
* @param \Illuminate\Support\Fluent $command
152+
* @param \Illuminate\Database\Connection $connection
153+
* @return array|string
154+
*/
155+
public function compileRenameColumn(Blueprint $blueprint, Fluent $command, Connection $connection)
156+
{
157+
return $connection->usingNativeSchemaOperations()
158+
? sprintf('alter table %s rename column %s to %s',
159+
$this->wrapTable($blueprint),
160+
$this->wrap($command->from),
161+
$this->wrap($command->to)
162+
)
163+
: parent::compileRenameColumn($blueprint, $command, $connection);
164+
}
165+
147166
/**
148167
* Compile a unique key command.
149168
*
@@ -286,17 +305,27 @@ public function compileRebuild()
286305
*/
287306
public function compileDropColumn(Blueprint $blueprint, Fluent $command, Connection $connection)
288307
{
289-
$tableDiff = $this->getDoctrineTableDiff(
290-
$blueprint, $schema = $connection->getDoctrineSchemaManager()
291-
);
308+
if ($connection->usingNativeSchemaOperations()) {
309+
$table = $this->wrapTable($blueprint);
292310

293-
foreach ($command->columns as $name) {
294-
$tableDiff->removedColumns[$name] = $connection->getDoctrineColumn(
295-
$this->getTablePrefix().$blueprint->getTable(), $name
311+
$columns = $this->prefixArray('drop column', $this->wrapArray($command->columns));
312+
313+
return collect($columns)->map(fn ($column) =>
314+
'alter table '.$table.' '.$column
315+
)->all();
316+
} else {
317+
$tableDiff = $this->getDoctrineTableDiff(
318+
$blueprint, $schema = $connection->getDoctrineSchemaManager()
296319
);
297-
}
298320

299-
return (array) $schema->getDatabasePlatform()->getAlterTableSQL($tableDiff);
321+
foreach ($command->columns as $name) {
322+
$tableDiff->removedColumns[$name] = $connection->getDoctrineColumn(
323+
$this->getTablePrefix().$blueprint->getTable(), $name
324+
);
325+
}
326+
327+
return (array) $schema->getDatabasePlatform()->getAlterTableSQL($tableDiff);
328+
}
300329
}
301330

302331
/**

src/Illuminate/Database/Schema/Grammars/SqlServerGrammar.php

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace Illuminate\Database\Schema\Grammars;
44

5+
use Illuminate\Database\Connection;
56
use Illuminate\Database\Schema\Blueprint;
67
use Illuminate\Support\Fluent;
78

@@ -107,6 +108,24 @@ public function compileAdd(Blueprint $blueprint, Fluent $command)
107108
);
108109
}
109110

111+
/**
112+
* Compile a rename column command.
113+
*
114+
* @param \Illuminate\Database\Schema\Blueprint $blueprint
115+
* @param \Illuminate\Support\Fluent $command
116+
* @param \Illuminate\Database\Connection $connection
117+
* @return array|string
118+
*/
119+
public function compileRenameColumn(Blueprint $blueprint, Fluent $command, Connection $connection)
120+
{
121+
return $connection->usingNativeSchemaOperations()
122+
? sprintf("sp_rename '%s', %s, 'COLUMN'",
123+
$this->wrap($blueprint->getTable().'.'.$command->from),
124+
$this->wrap($command->to)
125+
)
126+
: parent::compileRenameColumn($blueprint, $command, $connection);
127+
}
128+
110129
/**
111130
* Compile a primary key command.
112131
*

src/Illuminate/Support/Facades/Schema.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
* @method static void defaultMorphKeyType(string $type)
88
* @method static void morphUsingUuids()
99
* @method static void morphUsingUlids()
10+
* @method static void useNativeSchemaOperationsIfPossible(bool $value = true)
1011
* @method static bool createDatabase(string $name)
1112
* @method static bool dropDatabaseIfExists(string $name)
1213
* @method static bool hasTable(string $table)

tests/Database/DatabaseSchemaBlueprintIntegrationTest.php

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ protected function tearDown(): void
4242
{
4343
Facade::clearResolvedInstances();
4444
Facade::setFacadeApplication(null);
45+
$this->db->connection()->getSchemaBuilder()->useNativeSchemaOperationsIfPossible(false);
4546
}
4647

4748
public function testRenamingAndChangingColumnsWork()
@@ -101,6 +102,58 @@ public function testRenamingAndChangingColumnsWork()
101102
$this->assertContains($queries, $expected);
102103
}
103104

105+
public function testRenamingColumnsWithoutDoctrineWorks()
106+
{
107+
$connection = $this->db->connection();
108+
$schema = $connection->getSchemaBuilder();
109+
110+
$schema->useNativeSchemaOperationsIfPossible();
111+
112+
$base = new Blueprint('users', function ($table) {
113+
$table->renameColumn('name', 'new_name');
114+
});
115+
116+
$blueprint = clone $base;
117+
$this->assertEquals(['alter table `users` rename column `name` to `new_name`'], $blueprint->toSql($connection, new MySqlGrammar));
118+
119+
$blueprint = clone $base;
120+
$this->assertEquals(['alter table "users" rename column "name" to "new_name"'], $blueprint->toSql($connection, new PostgresGrammar));
121+
122+
$blueprint = clone $base;
123+
$this->assertEquals(['alter table "users" rename column "name" to "new_name"'], $blueprint->toSql($connection, new SQLiteGrammar));
124+
125+
$blueprint = clone $base;
126+
$this->assertEquals(['sp_rename \'"users"."name"\', "new_name", \'COLUMN\''], $blueprint->toSql($connection, new SqlServerGrammar));
127+
128+
$schema->create('test', function (Blueprint $table) {
129+
$table->string('foo');
130+
$table->string('baz');
131+
});
132+
133+
$schema->table('test', function (Blueprint $table) {
134+
$table->renameColumn('foo', 'bar');
135+
$table->renameColumn('baz', 'qux');
136+
});
137+
138+
$this->assertFalse($schema->hasColumn('test', 'foo'));
139+
$this->assertFalse($schema->hasColumn('test', 'baz'));
140+
$this->assertTrue($schema->hasColumns('test', ['bar', 'qux']));
141+
}
142+
143+
public function testDroppingColumnsWithoutDoctrineWorks()
144+
{
145+
$connection = $this->db->connection();
146+
$schema = $connection->getSchemaBuilder();
147+
148+
$schema->useNativeSchemaOperationsIfPossible();
149+
150+
$blueprint = new Blueprint('users', function ($table) {
151+
$table->dropColumn('name');
152+
});
153+
154+
$this->assertEquals(['alter table "users" drop column "name"'], $blueprint->toSql($connection, new SQLiteGrammar));
155+
}
156+
104157
public function testChangingColumnWithCollationWorks()
105158
{
106159
$this->db->connection()->getSchemaBuilder()->create('users', function ($table) {

0 commit comments

Comments
 (0)