From 4e43ca0c8433f69a18c983ccd827a4e324e9facf Mon Sep 17 00:00:00 2001 From: NickSdot <32384907+NickSdot@users.noreply.github.com> Date: Thu, 26 Oct 2023 23:21:57 +0800 Subject: [PATCH] =?UTF-8?q?[10.x]=20Improvements=20for=20`artisan=20migrat?= =?UTF-8?q?e=20--pretend`=20command=20=F0=9F=9A=80=20(#48768)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Allow to exclude DB::select*() statements to be excluded from 'pretend' mode * Ensured that we are null safe * Reconsidered naming * Nah, let's keep things simple and people self-responsible * Cleanup * Naming * Changed return type to mixed * Added tests * Cleanup * Added bindings to output and improved tests * Style fixes * formatting --------- Co-authored-by: Taylor Otwell --- src/Illuminate/Database/Connection.php | 25 ++++ src/Illuminate/Support/Facades/DB.php | 1 + tests/Integration/Migration/MigratorTest.php | 133 ++++++++++++++++++ ..._000000_create_people_is_dynamic_table.php | 27 ++++ ...000000_create_people_non_dynamic_table.php | 26 ++++ ..._10_17_000000_dynamic_content_is_shown.php | 37 +++++ ...10_17_000000_dynamic_content_not_shown.php | 32 +++++ 7 files changed, 281 insertions(+) create mode 100644 tests/Integration/Migration/pretending/2014_10_12_000000_create_people_is_dynamic_table.php create mode 100644 tests/Integration/Migration/pretending/2014_10_12_000000_create_people_non_dynamic_table.php create mode 100644 tests/Integration/Migration/pretending/2023_10_17_000000_dynamic_content_is_shown.php create mode 100644 tests/Integration/Migration/pretending/2023_10_17_000000_dynamic_content_not_shown.php diff --git a/src/Illuminate/Database/Connection.php b/src/Illuminate/Database/Connection.php index eb14815b87b9..24583f4a4079 100755 --- a/src/Illuminate/Database/Connection.php +++ b/src/Illuminate/Database/Connection.php @@ -655,6 +655,27 @@ public function pretend(Closure $callback) }); } + /** + * Execute the given callback without "pretending". + * + * @param \Closure $callback + * @return mixed + */ + public function withoutPretending(Closure $callback) + { + if (! $this->pretending) { + return $callback(); + } + + $this->pretending = false; + + $result = $callback(); + + $this->pretending = true; + + return $result; + } + /** * Execute the given callback in "dry run" mode. * @@ -829,6 +850,10 @@ public function logQuery($query, $bindings, $time = null) $this->event(new QueryExecuted($query, $bindings, $time, $this)); + $query = $this->pretending === true + ? $this->queryGrammar?->substituteBindingsIntoRawSql($query, $bindings) ?? $query + : $query; + if ($this->loggingQueries) { $this->queryLog[] = compact('query', 'bindings', 'time'); } diff --git a/src/Illuminate/Support/Facades/DB.php b/src/Illuminate/Support/Facades/DB.php index 474772583694..d9de5c9a4581 100755 --- a/src/Illuminate/Support/Facades/DB.php +++ b/src/Illuminate/Support/Facades/DB.php @@ -42,6 +42,7 @@ * @method static int affectingStatement(string $query, array $bindings = []) * @method static bool unprepared(string $query) * @method static array pretend(\Closure $callback) + * @method static mixed withoutPretending(\Closure $callback) * @method static void bindValues(\PDOStatement $statement, array $bindings) * @method static array prepareBindings(array $bindings) * @method static void logQuery(string $query, array $bindings, float|null $time = null) diff --git a/tests/Integration/Migration/MigratorTest.php b/tests/Integration/Migration/MigratorTest.php index f4210789ada5..a57e469c4bcc 100644 --- a/tests/Integration/Migration/MigratorTest.php +++ b/tests/Integration/Migration/MigratorTest.php @@ -2,7 +2,9 @@ namespace Illuminate\Tests\Integration\Migration; +use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\DB; +use Illuminate\Support\Facades\Schema; use Mockery as m; use Orchestra\Testbench\TestCase; use Symfony\Component\Console\Output\OutputInterface; @@ -98,6 +100,137 @@ public function testPretendMigrate() $this->assertFalse(DB::getSchemaBuilder()->hasTable('people')); } + public function testIgnorePretendModeForCallbackData() + { + // Create two tables with different columns so that we can query it later + // with the new method DB::withoutPretending(). + + Schema::create('table_1', function (Blueprint $table) { + $table->increments('id'); + $table->string('column_1'); + }); + + Schema::create('table_2', function (Blueprint $table) { + $table->increments('id'); + $table->string('column_2')->default('default_value'); + }); + + // From here on we simulate to be in pretend mode. This normally is done by + // running the migration with the option --pretend. + + DB::pretend(function () { + // Returns an empty array because we are in pretend mode. + $tablesEmpty = DB::select("SELECT name FROM sqlite_master WHERE type='table'"); + + $this->assertTrue([] === $tablesEmpty); + + // Returns an array with two tables because we ignore pretend mode. + $tablesList = DB::withoutPretending(function (): array { + return DB::select("SELECT name FROM sqlite_master WHERE type='table'"); + }); + + $this->assertTrue([] !== $tablesList); + + // The following would not be possible in pretend mode, if the + // method DB::withoutPretending() would not exists, + // because nothing is executed in pretend mode. + foreach ($tablesList as $table) { + if (in_array($table->name, ['sqlite_sequence', 'migrations'])) { + continue; + } + + $columnsEmpty = DB::select("PRAGMA table_info($table->name)"); + + $this->assertTrue([] === $columnsEmpty); + + $columnsList = DB::withoutPretending(function () use ($table): array { + return DB::select("PRAGMA table_info($table->name)"); + }); + + $this->assertTrue([] !== $columnsList); + $this->assertCount(2, $columnsList); + + // Confirm that we are still in pretend mode. This column should + // not be added. We query the table columns again to ensure the + // count is still two. + DB::statement("ALTER TABLE $table->name ADD COLUMN column_3 varchar(255) DEFAULT 'default_value' NOT NULL"); + + $columnsList = DB::withoutPretending(function () use ($table): array { + return DB::select("PRAGMA table_info($table->name)"); + }); + + $this->assertCount(2, $columnsList); + } + }); + + Schema::dropIfExists('table_1'); + Schema::dropIfExists('table_2'); + } + + public function testIgnorePretendModeForCallbackOutputDynamicContentIsShown() + { + // Persist data to table we can work with. + $this->expectInfo('Running migrations.'); + $this->expectTask('2014_10_12_000000_create_people_is_dynamic_table', 'DONE'); + + $this->output->shouldReceive('writeln')->once(); + + $this->subject->run([__DIR__.'/pretending/2014_10_12_000000_create_people_is_dynamic_table.php'], ['pretend' => false]); + + $this->assertTrue(DB::getSchemaBuilder()->hasTable('people')); + + // Test the actual functionality. + $this->expectInfo('Running migrations.'); + $this->expectTwoColumnDetail('DynamicContentIsShown'); + $this->expectBulletList([ + 'create table "blogs" ("id" integer primary key autoincrement not null, "url" varchar, "name" varchar)', + 'insert into "blogs" ("url") values (\'www.janedoe.com\'), (\'www.johndoe.com\')', + 'ALTER TABLE \'pseudo_table_name\' MODIFY \'column_name\' VARCHAR(191)', + 'select * from "people"', + 'insert into "blogs" ("id", "name") values (1, \'Jane Doe Blog\')', + 'insert into "blogs" ("id", "name") values (2, \'John Doe Blog\')', + ]); + + $this->output->shouldReceive('writeln')->once(); + + $this->subject->run([__DIR__.'/pretending/2023_10_17_000000_dynamic_content_is_shown.php'], ['pretend' => true]); + + $this->assertFalse(DB::getSchemaBuilder()->hasTable('blogs')); + + Schema::dropIfExists('people'); + } + + public function testIgnorePretendModeForCallbackOutputDynamicContentNotShown() + { + // Persist data to table we can work with. + $this->expectInfo('Running migrations.'); + $this->expectTask('2014_10_12_000000_create_people_non_dynamic_table', 'DONE'); + + $this->output->shouldReceive('writeln')->once(); + + $this->subject->run([__DIR__.'/pretending/2014_10_12_000000_create_people_non_dynamic_table.php'], ['pretend' => false]); + + $this->assertTrue(DB::getSchemaBuilder()->hasTable('people')); + + // Test the actual functionality. + $this->expectInfo('Running migrations.'); + $this->expectTwoColumnDetail('DynamicContentNotShown'); + $this->expectBulletList([ + 'create table "blogs" ("id" integer primary key autoincrement not null, "url" varchar, "name" varchar)', + 'insert into "blogs" ("url") values (\'www.janedoe.com\'), (\'www.johndoe.com\')', + 'ALTER TABLE \'pseudo_table_name\' MODIFY \'column_name\' VARCHAR(191)', + 'select * from "people"', + ]); + + $this->output->shouldReceive('writeln')->once(); + + $this->subject->run([__DIR__.'/pretending/2023_10_17_000000_dynamic_content_not_shown.php'], ['pretend' => true]); + + $this->assertFalse(DB::getSchemaBuilder()->hasTable('blogs')); + + Schema::dropIfExists('people'); + } + protected function expectInfo($message): void { $this->output->shouldReceive('writeln')->once()->with(m::on( diff --git a/tests/Integration/Migration/pretending/2014_10_12_000000_create_people_is_dynamic_table.php b/tests/Integration/Migration/pretending/2014_10_12_000000_create_people_is_dynamic_table.php new file mode 100644 index 000000000000..c8fdadaa4c9e --- /dev/null +++ b/tests/Integration/Migration/pretending/2014_10_12_000000_create_people_is_dynamic_table.php @@ -0,0 +1,27 @@ +increments('id'); + $table->string('blog_id')->nullable(); + $table->string('name'); + $table->string('email')->unique(); + $table->string('password'); + $table->rememberToken(); + $table->timestamps(); + }); + + DB::table('people')->insert([ + ['email' => 'jane@example.com', 'name' => 'Jane Doe', 'password' => 'secret'], + ['email' => 'john@example.com', 'name' => 'John Doe', 'password' => 'secret'], + ]); + } +} diff --git a/tests/Integration/Migration/pretending/2014_10_12_000000_create_people_non_dynamic_table.php b/tests/Integration/Migration/pretending/2014_10_12_000000_create_people_non_dynamic_table.php new file mode 100644 index 000000000000..91341141484b --- /dev/null +++ b/tests/Integration/Migration/pretending/2014_10_12_000000_create_people_non_dynamic_table.php @@ -0,0 +1,26 @@ +increments('id'); + $table->string('name'); + $table->string('email')->unique(); + $table->string('password'); + $table->rememberToken(); + $table->timestamps(); + }); + + DB::table('people')->insert([ + ['email' => 'jane@example.com', 'name' => 'Jane Doe', 'password' => 'secret'], + ['email' => 'john@example.com', 'name' => 'John Doe', 'password' => 'secret'], + ]); + } +} diff --git a/tests/Integration/Migration/pretending/2023_10_17_000000_dynamic_content_is_shown.php b/tests/Integration/Migration/pretending/2023_10_17_000000_dynamic_content_is_shown.php new file mode 100644 index 000000000000..f5304710f422 --- /dev/null +++ b/tests/Integration/Migration/pretending/2023_10_17_000000_dynamic_content_is_shown.php @@ -0,0 +1,37 @@ +increments('id'); + $table->string('url')->nullable(); + $table->string('name')->nullable(); + }); + + DB::table('blogs')->insert([ + ['url' => 'www.janedoe.com'], + ['url' => 'www.johndoe.com'], + ]); + + DB::statement("ALTER TABLE 'pseudo_table_name' MODIFY 'column_name' VARCHAR(191)"); + + /** @var \Illuminate\Support\Collection $tablesList */ + $tablesList = DB::withoutPretending(function () { + return DB::table('people')->get(); + }); + + $tablesList->each(function ($person, $key) { + DB::table('blogs')->where('blog_id', '=', $person->blog_id)->insert([ + 'id' => $key + 1, + 'name' => "{$person->name} Blog", + ]); + }); + } +} diff --git a/tests/Integration/Migration/pretending/2023_10_17_000000_dynamic_content_not_shown.php b/tests/Integration/Migration/pretending/2023_10_17_000000_dynamic_content_not_shown.php new file mode 100644 index 000000000000..78fcb97aead4 --- /dev/null +++ b/tests/Integration/Migration/pretending/2023_10_17_000000_dynamic_content_not_shown.php @@ -0,0 +1,32 @@ +increments('id'); + $table->string('url')->nullable(); + $table->string('name')->nullable(); + }); + + DB::table('blogs')->insert([ + ['url' => 'www.janedoe.com'], + ['url' => 'www.johndoe.com'], + ]); + + DB::statement("ALTER TABLE 'pseudo_table_name' MODIFY 'column_name' VARCHAR(191)"); + + DB::table('people')->get()->each(function ($person, $key) { + DB::table('blogs')->where('blog_id', '=', $person->blog_id)->insert([ + 'id' => $key + 1, + 'name' => "{$person->name} Blog", + ]); + }); + } +}