Skip to content

Commit

Permalink
[10.x] Improvements for artisan migrate --pretend command 🚀 (larave…
Browse files Browse the repository at this point in the history
…l#48768)

* 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 <[email protected]>
  • Loading branch information
NickSdot and taylorotwell authored Oct 26, 2023
1 parent a02ef13 commit 4e43ca0
Show file tree
Hide file tree
Showing 7 changed files with 281 additions and 0 deletions.
25 changes: 25 additions & 0 deletions src/Illuminate/Database/Connection.php
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand Down Expand Up @@ -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');
}
Expand Down
1 change: 1 addition & 0 deletions src/Illuminate/Support/Facades/DB.php
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
133 changes: 133 additions & 0 deletions tests/Integration/Migration/MigratorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;

class CreatePeopleIsDynamicTable extends Migration
{
public function up()
{
Schema::create('people', function (Blueprint $table) {
$table->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' => '[email protected]', 'name' => 'Jane Doe', 'password' => 'secret'],
['email' => '[email protected]', 'name' => 'John Doe', 'password' => 'secret'],
]);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;

class CreatePeopleNonDynamicTable extends Migration
{
public function up()
{
Schema::create('people', function (Blueprint $table) {
$table->increments('id');
$table->string('name');
$table->string('email')->unique();
$table->string('password');
$table->rememberToken();
$table->timestamps();
});

DB::table('people')->insert([
['email' => '[email protected]', 'name' => 'Jane Doe', 'password' => 'secret'],
['email' => '[email protected]', 'name' => 'John Doe', 'password' => 'secret'],
]);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;

class DynamicContentIsShown extends Migration
{
public function up()
{
Schema::create('blogs', function (Blueprint $table) {
$table->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",
]);
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;

class DynamicContentNotShown extends Migration
{
public function up()
{
Schema::create('blogs', function (Blueprint $table) {
$table->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",
]);
});
}
}

0 comments on commit 4e43ca0

Please sign in to comment.