Skip to content
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

feat(ptosc-auto-defaults): Option to automatically add default to com… #1

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@ class MyColumnWithFkMigration extends Migration
- Adding unique indexes may cause data loss unless tables are manually checked
beforehand [because of how PTOSC works](https://www.percona.com/doc/percona-toolkit/LATEST/pt-online-schema-change.html#id7)
- Adding not-null columns [requires a default](https://www.percona.com/doc/percona-toolkit/LATEST/pt-online-schema-change.html#cmdoption-pt-online-schema-change-alter)
so this package will automatically add default to new columns
- See `ptosc-auto-defaults` in `config/online-migrator.php` for more.
- Renaming a column or dropping a primary key [have additional risks](https://www.percona.com/doc/percona-toolkit/LATEST/pt-online-schema-change.html#id1)
- Foreign key creation should be done separately from column creation or
duplicate indexes may be created with slightly different naming
Expand Down
25 changes: 22 additions & 3 deletions config/online-migrator.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
| When not specified it will use the on-line capabilities; except for DBs
| with 'test' in their name.
|
| '0' bypasses any on-line tools and sends queries unchanged.
| '1' (or any truthy value) forces use of the on-line tools.
| `0` bypasses any on-line tools and sends queries unchanged.
| `1` (or any truthy value) forces use of the on-line tools.
|
*/

Expand All @@ -34,7 +34,7 @@

/*
|--------------------------------------------------------------------------
| Percona Online Schema Change Options
| Percona Online Schema Change - Options
|--------------------------------------------------------------------------
|
| Accepts a comma-separated list of options for pt-online-schema-change.
Expand All @@ -48,4 +48,23 @@
*/

'ptosc-options' => env('PTOSC_OPTIONS'),

/*
|--------------------------------------------------------------------------
| Automatic Defaults For Percona Online Schema Change
|--------------------------------------------------------------------------
|
| Whether or not to automatically add defaults to not-null columns. Doing
| so works around the not-null-default requirement of PTOSC using defaults
| similar to what Mysql would use in non-strict mode.
|
| INTEGER: 0
| DATETIME: '0001-01-01 00:00:00'
|
| Set this to `false` if you prefer your migrations explicitly assign
| defaults as needed.
|
*/

'ptosc-auto-defaults' => env('PTOSC_AUTO_DEFAULTS', true),
];
54 changes: 54 additions & 0 deletions src/Strategy/PtOnlineSchemaChange.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,22 @@

class PtOnlineSchemaChange implements StrategyInterface
{
/** Mimicking non-strict defaults as closely as practical. */
private const AUTO_DEFAULTS = [
'boolean' => 'FALSE',
'char' => "''",
'date' => "'0001-01-01'",
'datetime' => "'0001-01-01 00:00:00'",
'decimal' => '0.0',
'double' => '0.0',
'int' => '0',
'numeric' => '0.0',
// Text cannot have a default unless non-strict mode.
'time' => "'00:00:00'",
'timestamp' => "'1970-01-01 00:00:01'",
'varchar' => "''",
];

/**
* Get query or command, converting "ALTER TABLE " statements to on-line commands/queries.
*
Expand Down Expand Up @@ -57,6 +73,12 @@ public static function getQueryOrCommand(array &$query, Connection $connection)
["default '0'", "default '1'"],
['default 0', 'default 1'], $changes);

if (! empty($alter_parts)
&& config('online-migrator.ptosc-auto-defaults')
) {
$changes = static::getChangesWithAutoDefaults($changes);
}

// TODO: Fix dropping FKs by prefixing constraint name with '_' or
// '__' if already starts with '_' (quirk in PTOSC).

Expand Down Expand Up @@ -95,6 +117,38 @@ public static function getQueryOrCommand(array &$query, Connection $connection)
return $query_or_command_str;
}

private static function getChangesWithAutoDefaults(string $raw_changes) : string
{
$changes = [];

// Cannot do simple comma split because of types like "double(10, 5)".
// CONSIDER: More robust parsing for split.
foreach (preg_split('/,\s+(?!\d+\))/iu', $raw_changes) as $raw_change) {
$change = $raw_change;
// CONSIDER: Detecting column changes and auto-appending default
// when changed from nullable to not-null and doesn't have default.
if (preg_match('/\A\s*ADD\s+(COLUMN\s+)?`?[^`\s+]+`?\s+([^`\s+]+)(.*?\bNOT\s+NULL\b.*)/imu', $change, $add_parts)
&& ! preg_match('/\bDEFAULT\s+[^\s]+\b/imu', $add_parts[3])
) {
$column_type = strtolower(
trim(
preg_replace(
'/(^BIG|^MEDIUM|^SMALL|^TINY|(INT)EGER|\(.*$)/iu',
'\2',
$add_parts[2]
)
)
);
if (isset(static::AUTO_DEFAULTS[$column_type])) {
$change .= ' DEFAULT ' . static::AUTO_DEFAULTS[$column_type];
}
}
$changes[] = $change;
}

return implode(', ', $changes);
}

/**
* Get options from env. since artisan migrate has fixed arguments.
*
Expand Down
19 changes: 19 additions & 0 deletions tests/PtOnlineSchemaChangeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,23 @@ public function test_migrate_addsColumn()
$this->assertEquals('green', $test_row_one->color);
}

public function test_migrate_addsColumnsNotNullWithNoDefaults()
{
$this->loadMigrationsFrom(__DIR__ . '/migrations/adds-columns-not-null-no-defaults');

$test_row_one = \DB::table('test_om')->where('id', 1)->first();
$this->assertNotNull($test_row_one);
$this->assertEquals(false, $test_row_one->boolean_no_default);
$this->assertEquals('0001-01-01', $test_row_one->date_no_default);
$this->assertEquals('0001-01-01 00:00:00', $test_row_one->datetime_no_default);
$this->assertEquals(0.0, $test_row_one->float_no_default);
$this->assertEquals(0, $test_row_one->integer_no_default);
$this->assertEquals('', $test_row_one->string_no_default);
$this->assertEquals('00:00:00', $test_row_one->time_no_default);
$this->assertEquals('1970-01-01 00:00:01', $test_row_one->timestamp_no_default);
$this->assertEquals('', $test_row_one->uuid_no_default);
}

public function test_migrate_addsUnique()
{
$this->loadMigrationsFrom(__DIR__ . '/migrations/adds-unique');
Expand All @@ -39,6 +56,7 @@ public function test_migrate_addsUnique()
\DB::table('test_om')->insert(['name' => 'one']);
}

/* TODO: Move to test class with ptosc-auto-defaults is false or remove
public function test_migrate_addsWithoutDefault()
{
// Known to be unsupported by PTOSC (v3) for the time being, so this
Expand All @@ -48,6 +66,7 @@ public function test_migrate_addsWithoutDefault()
$this->expectExceptionCode(getenv('TRAVIS') ? 255 : 29);
$this->loadMigrationsFrom(__DIR__ . '/migrations/adds-without-default');
}
*/

public function test_migrate_changesType()
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php

class AddColumnsNotNullToTestOm extends \Illuminate\Database\Migrations\Migration
{
public function up()
{
Schema::table('test_om', function (\Illuminate\Database\Schema\Blueprint $table) {
// Excluding explicit defaults to test auto-defaults w/PTSOC.
$table->boolean('boolean_no_default')->nullable(false);
$table->date('date_no_default')->nullable(false);
$table->dateTime('datetime_no_default')->nullable(false);
$table->float('float_no_default')->nullable(false);
$table->integer('integer_no_default')->nullable(false);
$table->string('string_no_default')->nullable(false);
$table->time('time_no_default')->nullable(false);
$table->timestamp('timestamp_no_default')->nullable(false);
$table->uuid('uuid_no_default')->nullable(false);
});
}

public function down()
{
Schema::table('test_om', function (\Illuminate\Database\Schema\Blueprint $table) {
$table->dropColumn('boolean_no_default');
$table->dropColumn('date_no_default');
$table->dropColumn('datetime_no_default');
$table->dropColumn('float_no_default');
$table->dropColumn('integer_no_default');
$table->dropColumn('string_no_default');
$table->dropColumn('time_no_default');
$table->dropColumn('timestamp_no_default');
$table->dropColumn('uuid_no_default');
});
}
}