-
Notifications
You must be signed in to change notification settings - Fork 11.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[5.7] Sqlite & dropForeign #25475
Comments
Just use the new |
We have been using the builtin This is the way to do it according to the 5.7 documentation. I am not even dropping tables yet, just migrating upwards. It happens quite often that a table is removed, renamed after creation, hence there are plenty of It seems messy to rewrite all existing migrations to add database driver checks: |
The best/easiest option would probably be you just extend |
@laurencei That is indeed a reasonable option. I understand there are conflicting interests here, if you are deliberately trying to work with foreign keys on a production SQLite database, you would be glad to be informed by an exception. I do think this should be added to the 5.7 upgrade guide for clarity though, so I have created a PR on the docs repository. |
Can you close the issue? |
You can also just catch that exception instead of extending
|
@chris-rs That was my first thought as well, but in many scenarios you have Catching exceptions silently suppresses the error and continues within your custom |
@chris-rs Overriding classes turned out to be quite hard for migrations as well, so I opted to just make a macro for now:
... And just do a full search-replace on the migrations directory. This seems to work fine for to restore Laravel 5.6 behavior for now. I might later expand it with some logic which truly drops the foreign key by fully copying & swapping out the underlying SQLite tables. |
I found a way to replace sqlite connection, don't update any migration. Hopefully can help. abstract class TestCase extends BaseTestCase
{
public function __construct(?string $name = null, array $data = [], string $dataName = '')
{
parent::__construct($name, $data, $dataName);
$this->hotfixSqlite();
}
/**
*
*/
public function hotfixSqlite()
{
\Illuminate\Database\Connection::resolverFor('sqlite', function ($connection, $database, $prefix, $config) {
return new class($connection, $database, $prefix, $config) extends SQLiteConnection {
public function getSchemaBuilder()
{
if ($this->schemaGrammar === null) {
$this->useDefaultSchemaGrammar();
}
return new class($this) extends SQLiteBuilder {
protected function createBlueprint($table, Closure $callback = null)
{
return new class($table, $callback) extends Blueprint {
public function dropForeign($index)
{
return new Fluent();
}
};
}
};
}
};
});
}
} |
Adding a little detail, remember to inject the classes needed on top:
|
Reopen? |
Note when using PHPUnit ^9 you need to register the connection before calling protected function setUp(): void
{
$this->hotfixSqlite();
parent::setUp();
} This confused me for a while so hopefully it helps someone! |
So full version for your base TestCase class: public function __construct(?string $name = null, array $data = [], string $dataName = '')
{
$this->hotfixSqlite();
parent::__construct($name, $data, $dataName);
}
/**
* Fix for: BadMethodCallException : SQLite doesn't support dropping foreign keys (you would need to re-create the table).
*/
public function hotfixSqlite()
{
\Illuminate\Database\Connection::resolverFor('sqlite', function ($connection, $database, $prefix, $config) {
return new class($connection, $database, $prefix, $config) extends \Illuminate\Database\SQLiteConnection {
public function getSchemaBuilder()
{
if ($this->schemaGrammar === null) {
$this->useDefaultSchemaGrammar();
}
return new class($this) extends \Illuminate\Database\Schema\SQLiteBuilder {
protected function createBlueprint($table, \Closure $callback = null)
{
return new class($table, $callback) extends \Illuminate\Database\Schema\Blueprint {
public function dropForeign($index)
{
return new \Illuminate\Support\Fluent();
}
};
}
};
}
};
});
} |
I got this error when tried the solution. |
Maybe change E.g. backslash ('\') before 'Closure'. |
I tried the edited TestCase, now it raises this... |
this looks like another problem...you must have solved the one above successfully. if not the case, you need to provide more info |
I don't want to spam on a closed issue, but it works perfectly if I run tests on an empty MySQL database. I think it's related to the fix because I'm dropping a foreign key to replace it with another one on the same column, and as the deletion is faked, creation fails. I tried mocking the |
This works for
|
Any suggestion for
That really executes the drop of the column? |
maybe the easiest way to resolve this, is by ignoring dropForeign when not if (\DB::getDriverName() !== 'sqlite') {
$table->dropForeign('index_name');
} |
I have used your code improvement, but I'm still with some problems: I have one migration that changes the name of the column, name to first_name for example, and when I try to insert this data on the column, the test fails saying that the column doesn't exist.
the new Fluent doesn't return anything |
The hotfix didn't help, but the lunarphp/lunar#485 (comment) did 👍 |
i wonder why this is not part of framework yet. |
what about @taylorotwell ?? |
For those facing the issue I've rewritre the hotfixsql method to drop the table and recreate it without the dropped columns. Hope it could help someone public function hotfixSqlite(): void
{
Connection::resolverFor('sqlite', function ($connection, $database, $prefix, $config) {
return new class($connection, $database, $prefix, $config) extends SQLiteConnection
{
public function getSchemaBuilder(): SQLiteBuilder
{
if ($this->schemaGrammar === null) {
$this->useDefaultSchemaGrammar();
}
return new class($this) extends SQLiteBuilder
{
protected function createBlueprint($table, ?Closure $callback = null): Blueprint
{
return new class($table, $callback) extends Blueprint
{
public function dropForeign($index)
{
return new Fluent;
}
public function dropColumn($columns)
{
// Get the columns of the original table
$originalColumns = Schema::getColumnListing($this->table);
// Remove the column you want to drop from the original columns
$newColumns = array_diff($originalColumns, (array) $columns);
// Get the table's column details
$columnDetails = DB::select('PRAGMA TABLE_INFO('.$this->table.')');
// Create a new temporary table without the column you want to drop.
Schema::create('temp_'.$this->table, function (Blueprint $table) use ($newColumns, $columnDetails) {
foreach ($newColumns as $column) {
// Get the type of the column
$type = Schema::getConnection()->getSchemaBuilder()->getColumnType($this->table, $column);
// Check if the column is nullable
$columnDetail = collect($columnDetails)->firstWhere('name', $column);
$isNullable = ! ($columnDetail->notnull == 1);
// Recreate the column with the same type
$newColumn = match ($type) {
'integer' => $column === 'id' ? $table->increments($column) : $table->integer($column),
'boolean' => $table->boolean($column),
'date' => $table->date($column),
'text' => $table->text($column),
'float' => $table->float($column),
'double' => $table->double($column),
'decimal' => $table->decimal($column),
'timestamp' => $table->timestamp($column),
'time' => $table->time($column),
'binary' => $table->binary($column),
default => $table->string($column),
};
// If the column is nullable, call the nullable method
if ($isNullable) {
$newColumn->nullable();
}
}
});
Schema::dropIfExists($this->table);
Schema::rename('temp_'.$this->table, $this->table);
return new Fluent;
}
public function dropMorphs($name, $indexName = null)
{
// Define the columns to drop
$columnsToDrop = [$name.'_type', $name.'_id'];
// Call the dropColumn method with the columns to drop
$this->dropColumn($columnsToDrop);
return new Fluent;
}
public function renameColumn($from, $to)
{
return new Fluent;
}
};
}
};
}
};
});
}
``` ` |
Fixed on Laravel 11.15 via #51373 |
Description:
When using Laravel, using in-memory Sqlite for tests while using MySQL/Postgres for dev/prod requests is quite common.
Laravel 5.7 contains an undocumented change which throws an exception when trying to drop foreign keys in Sqlite: #24052
When a testsuite runs migrations, it produces the message:
SQLite doesn't support dropping foreign keys (you would need to re-create the table).
While this is technically correct, this strict behavior also removes the possibility to silently ignore foreign keys in Sqlite and use the same migrations with multiple drivers.
Could the checks in #24052 be reverted, or be made configurable?
If not, I think it should at least be added to the 5.7 upgrade docs to set expectations. Rewriting hundreds of migrations is quite impactful.
The text was updated successfully, but these errors were encountered: