From 82d3ae9d074e754ca4f0b2d6240dfcd2a620a06c Mon Sep 17 00:00:00 2001 From: Paul Rogers Date: Fri, 14 Sep 2018 10:19:15 -0400 Subject: [PATCH 1/2] feat(ptosc-auto-defaults): Option to automatically add default to common column types when absent. --- config/online-migrator.php | 25 +++++++-- src/Strategy/PtOnlineSchemaChange.php | 54 +++++++++++++++++++ tests/PtOnlineSchemaChangeTest.php | 19 +++++++ ...000001_add_columns_not_null_to_test_om.php | 35 ++++++++++++ 4 files changed, 130 insertions(+), 3 deletions(-) create mode 100644 tests/migrations/adds-columns-not-null-no-defaults/0000_00_00_000001_add_columns_not_null_to_test_om.php diff --git a/config/online-migrator.php b/config/online-migrator.php index 5459444..794229f 100644 --- a/config/online-migrator.php +++ b/config/online-migrator.php @@ -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. | */ @@ -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. @@ -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), ]; diff --git a/src/Strategy/PtOnlineSchemaChange.php b/src/Strategy/PtOnlineSchemaChange.php index 96471aa..937c554 100644 --- a/src/Strategy/PtOnlineSchemaChange.php +++ b/src/Strategy/PtOnlineSchemaChange.php @@ -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. * @@ -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). @@ -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. * diff --git a/tests/PtOnlineSchemaChangeTest.php b/tests/PtOnlineSchemaChangeTest.php index 7622dd6..42f41cc 100644 --- a/tests/PtOnlineSchemaChangeTest.php +++ b/tests/PtOnlineSchemaChangeTest.php @@ -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'); @@ -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 @@ -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() { diff --git a/tests/migrations/adds-columns-not-null-no-defaults/0000_00_00_000001_add_columns_not_null_to_test_om.php b/tests/migrations/adds-columns-not-null-no-defaults/0000_00_00_000001_add_columns_not_null_to_test_om.php new file mode 100644 index 0000000..3135159 --- /dev/null +++ b/tests/migrations/adds-columns-not-null-no-defaults/0000_00_00_000001_add_columns_not_null_to_test_om.php @@ -0,0 +1,35 @@ +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'); + }); + } +} From eae2d6cdb57990263a507e2479bf77e7aa7e47a6 Mon Sep 17 00:00:00 2001 From: Paul Rogers Date: Fri, 14 Sep 2018 10:26:26 -0400 Subject: [PATCH 2/2] docs(README.md): Add note about auto defaults to README.md. --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index bba3e03..893f83c 100644 --- a/README.md +++ b/README.md @@ -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