Skip to content

Commit

Permalink
conflicts with upstream Laravel changes (resolves #85, resolves #87)
Browse files Browse the repository at this point in the history
Co-authored-by: Hafez Divandari <[email protected]>
  • Loading branch information
tpetry and hafezdivandari committed Jul 16, 2024
1 parent d70c72a commit 0074e7e
Show file tree
Hide file tree
Showing 3 changed files with 79 additions and 49 deletions.
39 changes: 18 additions & 21 deletions src/Schema/Grammars/GrammarTable.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace Tpetry\PostgresqlEnhanced\Schema\Grammars;

use Arr;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Fluent;

Expand All @@ -16,36 +17,32 @@ trait GrammarTable
*/
public function compileAdd(Blueprint $blueprint, Fluent $command): array
{
$sql = [];
// In Laravel 11.15.0 the logic was changed that compileAdd is only for one column (the one in the command) of
// the blueprint and not all ones of the blueprint as before.
/** @var \Illuminate\Database\Schema\ColumnDefinition[] $columns */
$columns = isset($command['column']) ? [$command['column']] : $blueprint->getAddedColumns();

foreach (array_reverse($blueprint->getAddedColumns()) as $column) {
$sqlChangeDefault = [];
foreach ($columns as $column) {
$attributes = $column->getAttributes();
if (!\array_key_exists('initial', $attributes)) {
continue;
}

if (\array_key_exists('default', $attributes)) {
$sql[] = sprintf('alter table %s alter column %s set default %s',
$this->wrapTable($blueprint),
$this->wrap($column),
$this->getDefaultValue($column['default'])
);
} else {
$sql[] = sprintf('alter table %s alter column %s drop default',
$this->wrapTable($blueprint),
$this->wrap($column),
);
}

// Transform the column definition to a standard one understood by Laravel:
// - The `initial` modifier is saved to the `default` modifier to set the initial value when creating the column.
// - A SQL query is created to reset the `default` value afterward to NULL or the specified value.
$sqlChangeDefault[] = match (\array_key_exists('default', $attributes)) {
true => "alter table {$this->wrapTable($blueprint)} alter column {$this->wrap($column)} set default {$this->getDefaultValue($column['default'])}",
false => "alter table {$this->wrapTable($blueprint)} alter column {$this->wrap($column)} drop default",
};
$column['default'] = $column['initial'];
}

$sql[] = sprintf('alter table %s %s',
$this->wrapTable($blueprint),
implode(', ', $this->prefixArray('add column', $this->getColumns($blueprint)))
);

return array_reverse($sql);
return [
...Arr::wrap(parent::compileAdd($blueprint, $command)), // Some Laravel versions produce a single string while others an array.
...$sqlChangeDefault,
];
}

/**
Expand Down
50 changes: 22 additions & 28 deletions src/Schema/Grammars/GrammarTypes.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,34 +17,33 @@ trait GrammarTypes
*/
public function compileChange(BaseBlueprint $blueprint, Fluent $command, Connection $connection): array
{
$queries = [];

// The table prefix is accessed differently based on Laravel version. In old version the $prefix was public,
// while with new ones the $blueprint->prefix() method should be used. The issue is solved by invading the
// object and getting the property directly.
$prefix = (fn () => $this->prefix)->call($blueprint);

foreach ($blueprint->getChangedColumns() as $changedColumn) {
$blueprintColumn = new BaseBlueprint($blueprint->getTable(), null, $prefix);
$blueprintColumn->addColumn(
$changedColumn['type'],
$changedColumn['name'],
Arr::except($changedColumn->toArray(), ['compression']),
);

// Remove Compression modifier because Laravel 11 won't work correctly with it (migrator builts incorrectly SQL).
$_modifiers = $this->modifiers;
$this->modifiers = array_filter($this->modifiers, fn ($str) => !\in_array($str, ['Compression']));
$changes = Arr::wrap(parent::compileChange($blueprintColumn, $command, $connection));
$this->modifiers = $_modifiers;

foreach ($changes as $sql) {
// In Laravel 11.15.0 the logic was changed that compileChange is only for one column (the one in the command)
// of the blueprint and not all ones of the blueprint as before.
/** @var \Illuminate\Database\Schema\ColumnDefinition[] $columns */
$columns = isset($command['column']) ? [$command['column']] : $blueprint->getChangedColumns();

$queries = [];
foreach ($columns as $column) {
$modifierCompression = $column['compression'];
$modifierUsing = $column['using'];
unset($column['compression'], $column['using']);

$blueprintColumnExtract = new BaseBlueprint($blueprint->getTable(), null, $prefix);
$blueprintColumnExtract->addColumn($column['type'], $column['name'], $column->toArray());
$blueprintColumnExtractQueries = Arr::wrap(parent::compileChange($blueprint, $command, $connection));

foreach ($blueprintColumnExtractQueries as $sql) {
$regex = Regex::match('/^ALTER table (?P<table>.*?) alter (column )?(?P<column>.*?) type (?P<type>\w+)(?P<modifiers>,.*)?/i', $sql);

if (filled($changedColumn['using']) && $regex->hasMatch()) {
$using = match ($connection->getSchemaGrammar()->isExpression($changedColumn['using'])) {
true => $connection->getSchemaGrammar()->getValue($changedColumn['using']),
false => $changedColumn['using'],
if (filled($modifierUsing) && $regex->hasMatch()) {
$using = match ($connection->getSchemaGrammar()->isExpression($modifierUsing)) {
true => $connection->getSchemaGrammar()->getValue($modifierUsing),
false => $modifierUsing,
};

$queries[] = match (filled($modifiers = $regex->groupOr('modifiers', ''))) {
Expand All @@ -56,13 +55,8 @@ public function compileChange(BaseBlueprint $blueprint, Fluent $command, Connect
}
}

if (filled($changedColumn['compression'])) {
$queries[] = sprintf(
'alter table %s alter %s set compression %s',
$this->wrapTable($blueprint->getTable()),
$this->wrap($changedColumn['name']),
$this->wrap($changedColumn['compression']),
);
if (filled($modifierCompression)) {
$queries[] = "alter table {$this->wrapTable($blueprint)} alter {$this->wrap($column['name'])} set compression {$this->wrap($modifierCompression)}";
}
}

Expand Down
39 changes: 39 additions & 0 deletions tests/CompatibilityTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php

declare(strict_types=1);

namespace Tpetry\PostgresqlEnhanced\Tests;

use DB;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Schema;

class CompatibilityTest extends TestCase
{
// With Laravel 11.15.0 the behaviour of commands within a migration has been changed. The ordering is now important
// which is a BC behaviour break (https://github.com/laravel/framework/pull/51373). So the implementation has been
// changed drastically which has lead to issue (https://github.com/tpetry/laravel-postgresql-enhanced/issues/85).
public function testCompatabilityMigrationOrdering(): void
{
DB::statement('create table test()');
$queries = $this->withQueryLog(function (): void {
Schema::table('test', static function (Blueprint $table): void {
$table->text('column_one');
$table->text('column_two');
});
});

$expected = match (true) {
version_compare(App::version(), '11.15.0', '>=') => [
'alter table "test" add column "column_one" text not null',
'alter table "test" add column "column_two" text not null',
],
default => [
'alter table "test" add column "column_one" text not null, add column "column_two" text not null',
],
};

$this->assertEquals($expected, array_column($queries, 'query'));
}
}

0 comments on commit 0074e7e

Please sign in to comment.