From 85b7ca8414f9486c37b0a5f87d81fb99aad17ddc Mon Sep 17 00:00:00 2001 From: Talgat Hairullov Date: Tue, 3 Sep 2024 17:32:15 +0400 Subject: [PATCH] [11.x] SQLite Error: "General error: 1 no such table" after adding a foreign key when using a table prefix. (#52578) * Added removal of the table prefix when defining a foreign key. * Code style fixed. --- .../Database/Schema/BlueprintState.php | 17 +- .../DatabaseSQLiteSchemaGrammarTest.php | 161 ++++++++++++++++++ 2 files changed, 177 insertions(+), 1 deletion(-) diff --git a/src/Illuminate/Database/Schema/BlueprintState.php b/src/Illuminate/Database/Schema/BlueprintState.php index d617f58fac41..1fd5eeb5dc1c 100644 --- a/src/Illuminate/Database/Schema/BlueprintState.php +++ b/src/Illuminate/Database/Schema/BlueprintState.php @@ -105,7 +105,7 @@ public function __construct(Blueprint $blueprint, Connection $connection, Gramma $this->foreignKeys = collect($schema->getForeignKeys($table))->map(fn ($foreignKey) => new ForeignKeyDefinition([ 'columns' => $foreignKey['columns'], - 'on' => $foreignKey['foreign_table'], + 'on' => $this->withoutTablePrefix($foreignKey['foreign_table']), 'references' => $foreignKey['foreign_columns'], 'onUpdate' => $foreignKey['on_update'], 'onDelete' => $foreignKey['on_delete'], @@ -251,4 +251,19 @@ public function update(Fluent $command) break; } } + + /** + * Remove the table prefix from a table name, if it exists. + * + * @param string $table + * @return string + */ + protected function withoutTablePrefix(string $table) + { + $prefix = $this->connection->getTablePrefix(); + + return str_starts_with($table, $prefix) + ? substr($table, strlen($prefix)) + : $table; + } } diff --git a/tests/Database/DatabaseSQLiteSchemaGrammarTest.php b/tests/Database/DatabaseSQLiteSchemaGrammarTest.php index 6454a9d82c60..c7f61d32bfd0 100755 --- a/tests/Database/DatabaseSQLiteSchemaGrammarTest.php +++ b/tests/Database/DatabaseSQLiteSchemaGrammarTest.php @@ -7,8 +7,12 @@ use Illuminate\Database\Query\Expression; use Illuminate\Database\Query\Processors\SQLiteProcessor; use Illuminate\Database\Schema\Blueprint; +use Illuminate\Database\Schema\BlueprintState; +use Illuminate\Database\Schema\ColumnDefinition; use Illuminate\Database\Schema\ForeignIdColumnDefinition; +use Illuminate\Database\Schema\ForeignKeyDefinition; use Illuminate\Database\Schema\Grammars\SQLiteGrammar; +use Illuminate\Database\Schema\IndexDefinition; use Illuminate\Database\Schema\SQLiteBuilder; use Mockery as m; use PHPUnit\Framework\TestCase; @@ -998,6 +1002,163 @@ public function testDroppingColumnsWorks() $this->assertEquals(['alter table "users" drop column "name"'], $blueprint->toSql($this->getConnection(), $this->getGrammar())); } + public function testBlueprintInitialState() + { + $db = new Manager; + + $db->addConnection([ + 'driver' => 'sqlite', + 'database' => ':memory:', + 'prefix' => 'prefix_', + ]); + + $connection = $db->getConnection(); + $grammar = new SQLiteGrammar(); + $grammar->setConnection($connection); + $grammar->setTablePrefix($connection->getTablePrefix()); + + $schema = $connection->getSchemaBuilder(); + + $schema->create('users', function (Blueprint $table) { + $table->string('name'); + $table->string('email'); + }); + + $schema->create('posts', function (Blueprint $table) { + $table->increments('id'); + $table->foreignId('user_id')->constrained('users')->cascadeOnDelete(); + $table->integer('parent_id')->nullable()->index('parent_id'); + $table->string('status')->default('A'); + $table->boolean('is_active')->virtualAs('"status" = \'A\''); + $table->boolean('has_parent')->storedAs('"parent_id" IS NOT NULL'); + $table->string('title')->collation('RTRIM'); + }); + + $blueprint = new Blueprint('posts', null, $connection->getTablePrefix()); + $state = new BlueprintState($blueprint, $connection, $grammar); + + $this->assertCount(7, $state->getColumns()); + $this->assertCount(1, $state->getIndexes()); + $this->assertCount(1, $state->getForeignKeys()); + $this->assertSame(['id'], $state->getPrimaryKey()->get('columns')); + $this->assertSame( + [ + [ + 'name' => 'id', + 'type' => 'integer', + 'full_type_definition' => 'integer', + 'nullable' => false, + 'default' => null, + 'autoIncrement' => true, + 'collation' => null, + 'comment' => null, + 'virtualAs' => null, + 'storedAs' => null, + ], + [ + 'name' => 'user_id', + 'type' => 'integer', + 'full_type_definition' => 'integer', + 'nullable' => false, + 'default' => null, + 'autoIncrement' => false, + 'collation' => null, + 'comment' => null, + 'virtualAs' => null, + 'storedAs' => null, + ], + [ + 'name' => 'parent_id', + 'type' => 'integer', + 'full_type_definition' => 'integer', + 'nullable' => true, + 'default' => null, + 'autoIncrement' => false, + 'collation' => null, + 'comment' => null, + 'virtualAs' => null, + 'storedAs' => null, + ], + [ + 'name' => 'status', + 'type' => 'varchar', + 'full_type_definition' => 'varchar', + 'nullable' => false, + 'default' => "'A'", + 'autoIncrement' => false, + 'collation' => null, + 'comment' => null, + 'virtualAs' => null, + 'storedAs' => null, + ], + [ + 'name' => 'is_active', + 'type' => 'tinyint', + 'full_type_definition' => 'tinyint(1)', + 'nullable' => true, + 'default' => null, + 'autoIncrement' => false, + 'collation' => null, + 'comment' => null, + 'virtualAs' => '"status" = \'A\'', + 'storedAs' => null, + ], + [ + 'name' => 'has_parent', + 'type' => 'tinyint', + 'full_type_definition' => 'tinyint(1)', + 'nullable' => true, + 'default' => null, + 'autoIncrement' => false, + 'collation' => null, + 'comment' => null, + 'virtualAs' => null, + 'storedAs' => '"parent_id" IS NOT NULL', + ], + [ + 'name' => 'title', + 'type' => 'varchar', + 'full_type_definition' => 'varchar', + 'nullable' => false, + 'default' => null, + 'autoIncrement' => false, + 'collation' => 'rtrim', + 'comment' => null, + 'virtualAs' => null, + 'storedAs' => null, + ], + ], + array_map( + fn (ColumnDefinition $definition) => array_replace($definition->toArray(), [ + 'default' => $definition->value('default') ? $definition->value('default')->getValue($grammar) : $definition->value('default'), + ]), + $state->getColumns() + ) + ); + $this->assertSame( + [ + [ + 'name' => 'index', + 'index' => 'parent_id', + 'columns' => ['parent_id'], + ], + ], + array_map(fn (IndexDefinition $definition) => $definition->toArray(), $state->getIndexes()) + ); + $this->assertSame( + [ + [ + 'columns' => ['user_id'], + 'on' => 'users', + 'references' => ['id'], + 'onUpdate' => 'no action', + 'onDelete' => 'cascade', + ], + ], + array_map(fn (ForeignKeyDefinition $definition) => $definition->toArray(), $state->getForeignKeys()) + ); + } + protected function getConnection() { $connection = m::mock(Connection::class);