Skip to content

[11.x] Add support for modifying generated columns #50329

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

Merged
merged 12 commits into from
Mar 4, 2024
Merged
8 changes: 8 additions & 0 deletions src/Illuminate/Database/Query/Processors/MySqlProcessor.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,14 @@ public function processColumns($results)
'default' => $result->default,
'auto_increment' => $result->extra === 'auto_increment',
'comment' => $result->comment ?: null,
'generation' => $result->expression ? [
'type' => match ($result->extra) {
'STORED GENERATED' => 'stored',
'VIRTUAL GENERATED' => 'virtual',
default => null,
},
'expression' => $result->expression,
] : null,
];
}, $results);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,9 +97,16 @@ public function processColumns($results)
'type' => $result->type,
'collation' => $result->collation,
'nullable' => (bool) $result->nullable,
'default' => $autoincrement ? null : $result->default,
'default' => $result->generated ? null : $result->default,
'auto_increment' => $autoincrement,
'comment' => $result->comment,
'generation' => $result->generated ? [
'type' => match ($result->generated) {
's' => 'stored',
default => null,
},
'expression' => $result->default,
] : null,
];
}, $results);
}
Expand Down
16 changes: 16 additions & 0 deletions src/Illuminate/Database/Query/Processors/SQLiteProcessor.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,14 @@ public function processColumns($results, $sql = '')
$matches
) === 1 ? strtolower($matches[1]) : null;

$isGenerated = in_array($result->extra, [2, 3]);

$expression = $isGenerated && preg_match(
'/\b'.preg_quote($result->name).'\b[^,]+\s+as\s+\(((?:[^()]+|\((?:[^()]+|\([^()]*\))*\))*)\)/i',
$sql,
$matches
) === 1 ? $matches[1] : null;

return [
'name' => $result->name,
'type_name' => strtok($type, '(') ?: '',
Expand All @@ -35,6 +43,14 @@ public function processColumns($results, $sql = '')
'default' => $result->default,
'auto_increment' => $hasPrimaryKey && $result->primary && $type === 'integer',
'comment' => null,
'generation' => $isGenerated ? [
'type' => match ((int) $result->extra) {
3 => 'stored',
2 => 'virtual',
default => null,
},
'expression' => $expression,
] : null,
];
}, $results);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,10 @@ public function processColumns($results)
'default' => $result->default,
'auto_increment' => (bool) $result->autoincrement,
'comment' => $result->comment,
'generation' => $result->expression ? [
'type' => $result->persisted ? 'stored' : 'virtual',
'expression' => $result->expression,
] : null,
];
}, $results);
}
Expand Down
7 changes: 6 additions & 1 deletion src/Illuminate/Database/Schema/Grammars/MySqlGrammar.php
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,8 @@ public function compileColumns($database, $table)
return sprintf(
'select column_name as `name`, data_type as `type_name`, column_type as `type`, '
.'collation_name as `collation`, is_nullable as `nullable`, '
.'column_default as `default`, column_comment as `comment`, extra as `extra` '
.'column_default as `default`, column_comment as `comment`, '
.'generation_expression as `expression`, extra as `extra` '
.'from information_schema.columns where table_schema = %s and table_name = %s '
.'order by ordinal_position asc',
$this->quoteString($database),
Expand Down Expand Up @@ -343,6 +344,10 @@ public function compileRenameColumn(Blueprint $blueprint, Fluent $command, Conne
'autoIncrement' => $column['auto_increment'],
'collation' => $column['collation'],
'comment' => $column['comment'],
'virtualAs' => ! is_null($column['generation']) && $column['generation']['type'] === 'virtual'
? $column['generation']['expression'] : null,
'storedAs' => ! is_null($column['generation']) && $column['generation']['type'] === 'stored'
? $column['generation']['expression'] : null,
]));

return sprintf('alter table %s change %s %s %s',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ public function compileColumns($schema, $table)
.'(select tc.collcollate from pg_catalog.pg_collation tc where tc.oid = a.attcollation) as collation, '
.'not a.attnotnull as nullable, '
.'(select pg_get_expr(adbin, adrelid) from pg_attrdef where c.oid = pg_attrdef.adrelid and pg_attrdef.adnum = a.attnum) as default, '
.'a.attgenerated as generated, '
.'col_description(c.oid, a.attnum) as comment '
.'from pg_attribute a, pg_class c, pg_type t, pg_namespace n '
.'where c.relname = %s and n.nspname = %s and a.attnum > 0 and a.attrelid = c.oid and a.atttypid = t.oid and n.oid = c.relnamespace '
Expand Down
18 changes: 15 additions & 3 deletions src/Illuminate/Database/Schema/Grammars/SQLiteGrammar.php
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ public function compileViews()
public function compileColumns($table)
{
return sprintf(
'select name, type, not "notnull" as "nullable", dflt_value as "default", pk as "primary" '
'select name, type, not "notnull" as "nullable", dflt_value as "default", pk as "primary", hidden as "extra" '
.'from pragma_table_xinfo(%s) order by cid asc',
$this->wrap(str_replace('.', '__', $table))
);
Expand Down Expand Up @@ -250,14 +250,22 @@ public function compileChange(Blueprint $blueprint, Fluent $command, Connection

if ($column instanceof Fluent) {
$name = $this->wrap($column);
$columnNames[] = $name;
$autoIncrementColumn = $column->autoIncrement ? $column->name : $autoIncrementColumn;

if (is_null($column->virtualAs) && is_null($column->virtualAsJson) &&
is_null($column->storedAs) && is_null($column->storedAsJson)) {
$columnNames[] = $name;
}

return $this->addModifiers($name.' '.$this->getType($column), $blueprint, $column);
} else {
$name = $this->wrap($column['name']);
$columnNames[] = $name;
$autoIncrementColumn = $column['auto_increment'] ? $column['name'] : $autoIncrementColumn;
$isGenerated = ! is_null($column['generation']);

if (! $isGenerated) {
$columnNames[] = $name;
}

return $this->addModifiers($name.' '.$column['type'], $blueprint,
new ColumnDefinition([
Expand All @@ -268,6 +276,10 @@ public function compileChange(Blueprint $blueprint, Fluent $command, Connection
'autoIncrement' => $column['auto_increment'],
'collation' => $column['collation'],
'comment' => $column['comment'],
'virtualAs' => $isGenerated && $column['generation']['type'] === 'virtual'
? $column['generation']['expression'] : null,
'storedAs' => $isGenerated && $column['generation']['type'] === 'stored'
? $column['generation']['expression'] : null,
])
);
}
Expand Down
2 changes: 2 additions & 0 deletions src/Illuminate/Database/Schema/Grammars/SqlServerGrammar.php
Original file line number Diff line number Diff line change
Expand Up @@ -107,13 +107,15 @@ public function compileColumns($schema, $table)
.'col.max_length as length, col.precision as precision, col.scale as places, '
.'col.is_nullable as nullable, def.definition as [default], '
.'col.is_identity as autoincrement, col.collation_name as collation, '
.'com.definition as [expression], is_persisted as [persisted], '
.'cast(prop.value as nvarchar(max)) as comment '
.'from sys.columns as col '
.'join sys.types as type on col.user_type_id = type.user_type_id '
.'join sys.objects as obj on col.object_id = obj.object_id '
.'join sys.schemas as scm on obj.schema_id = scm.schema_id '
.'left join sys.default_constraints def on col.default_object_id = def.object_id and col.object_id = def.parent_object_id '
."left join sys.extended_properties as prop on obj.object_id = prop.major_id and col.column_id = prop.minor_id and prop.name = 'MS_Description' "
.'left join sys.computed_columns as com on col.column_id = com.column_id '
."where obj.type in ('U', 'V') and obj.name = %s and scm.name = %s "
.'order by col.column_id',
$this->quoteString($table),
Expand Down
6 changes: 3 additions & 3 deletions tests/Database/DatabaseMariaDbProcessorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ public function testProcessColumns()
['name' => 'email', 'type_name' => 'varchar', 'type' => 'varchar(100)', 'collation' => 'collate', 'nullable' => 'YES', 'default' => 'NULL', 'extra' => 'on update CURRENT_TIMESTAMP', 'comment' => 'NULL'],
];
$expected = [
['name' => 'id', 'type_name' => 'bigint', 'type' => 'bigint', 'collation' => 'collate', 'nullable' => true, 'default' => '', 'auto_increment' => true, 'comment' => 'bar'],
['name' => 'name', 'type_name' => 'varchar', 'type' => 'varchar(100)', 'collation' => 'collate', 'nullable' => false, 'default' => 'foo', 'auto_increment' => false, 'comment' => ''],
['name' => 'email', 'type_name' => 'varchar', 'type' => 'varchar(100)', 'collation' => 'collate', 'nullable' => true, 'default' => 'NULL', 'auto_increment' => false, 'comment' => 'NULL'],
['name' => 'id', 'type_name' => 'bigint', 'type' => 'bigint', 'collation' => 'collate', 'nullable' => true, 'default' => '', 'auto_increment' => true, 'comment' => 'bar', 'generation' => null],
['name' => 'name', 'type_name' => 'varchar', 'type' => 'varchar(100)', 'collation' => 'collate', 'nullable' => false, 'default' => 'foo', 'auto_increment' => false, 'comment' => '', 'generation' => null],
['name' => 'email', 'type_name' => 'varchar', 'type' => 'varchar(100)', 'collation' => 'collate', 'nullable' => true, 'default' => 'NULL', 'auto_increment' => false, 'comment' => 'NULL', 'generation' => null],
];
$this->assertEquals($expected, $processor->processColumns($listing));

Expand Down
6 changes: 3 additions & 3 deletions tests/Database/DatabaseMySqlProcessorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ public function testProcessColumns()
['name' => 'email', 'type_name' => 'varchar', 'type' => 'varchar(100)', 'collation' => 'collate', 'nullable' => 'YES', 'default' => 'NULL', 'extra' => 'on update CURRENT_TIMESTAMP', 'comment' => 'NULL'],
];
$expected = [
['name' => 'id', 'type_name' => 'bigint', 'type' => 'bigint', 'collation' => 'collate', 'nullable' => true, 'default' => '', 'auto_increment' => true, 'comment' => 'bar'],
['name' => 'name', 'type_name' => 'varchar', 'type' => 'varchar(100)', 'collation' => 'collate', 'nullable' => false, 'default' => 'foo', 'auto_increment' => false, 'comment' => ''],
['name' => 'email', 'type_name' => 'varchar', 'type' => 'varchar(100)', 'collation' => 'collate', 'nullable' => true, 'default' => 'NULL', 'auto_increment' => false, 'comment' => 'NULL'],
['name' => 'id', 'type_name' => 'bigint', 'type' => 'bigint', 'collation' => 'collate', 'nullable' => true, 'default' => '', 'auto_increment' => true, 'comment' => 'bar', 'generation' => null],
['name' => 'name', 'type_name' => 'varchar', 'type' => 'varchar(100)', 'collation' => 'collate', 'nullable' => false, 'default' => 'foo', 'auto_increment' => false, 'comment' => null, 'generation' => null],
['name' => 'email', 'type_name' => 'varchar', 'type' => 'varchar(100)', 'collation' => 'collate', 'nullable' => true, 'default' => 'NULL', 'auto_increment' => false, 'comment' => 'NULL', 'generation' => null],
];
$this->assertEquals($expected, $processor->processColumns($listing));

Expand Down
8 changes: 4 additions & 4 deletions tests/Database/DatabasePostgresProcessorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ public function testProcessColumns()
['name' => 'birth_date', 'type_name' => 'timestamp', 'type' => 'timestamp(6) without time zone', 'collation' => '', 'nullable' => false, 'default' => '', 'comment' => ''],
];
$expected = [
['name' => 'id', 'type_name' => 'int4', 'type' => 'integer', 'collation' => '', 'nullable' => true, 'default' => null, 'auto_increment' => true, 'comment' => ''],
['name' => 'name', 'type_name' => 'varchar', 'type' => 'character varying(100)', 'collation' => 'collate', 'nullable' => false, 'default' => '', 'auto_increment' => false, 'comment' => 'foo'],
['name' => 'balance', 'type_name' => 'numeric', 'type' => 'numeric(8,2)', 'collation' => '', 'nullable' => true, 'default' => '4', 'auto_increment' => false, 'comment' => 'NULL'],
['name' => 'birth_date', 'type_name' => 'timestamp', 'type' => 'timestamp(6) without time zone', 'collation' => '', 'nullable' => false, 'default' => '', 'auto_increment' => false, 'comment' => ''],
['name' => 'id', 'type_name' => 'int4', 'type' => 'integer', 'collation' => '', 'nullable' => true, 'default' => "nextval('employee_id_seq'::regclass)", 'auto_increment' => true, 'comment' => '', 'generation' => null],
['name' => 'name', 'type_name' => 'varchar', 'type' => 'character varying(100)', 'collation' => 'collate', 'nullable' => false, 'default' => '', 'auto_increment' => false, 'comment' => 'foo', 'generation' => null],
['name' => 'balance', 'type_name' => 'numeric', 'type' => 'numeric(8,2)', 'collation' => '', 'nullable' => true, 'default' => '4', 'auto_increment' => false, 'comment' => 'NULL', 'generation' => null],
['name' => 'birth_date', 'type_name' => 'timestamp', 'type' => 'timestamp(6) without time zone', 'collation' => '', 'nullable' => false, 'default' => '', 'auto_increment' => false, 'comment' => '', 'generation' => null],
];

$this->assertEquals($expected, $processor->processColumns($listing));
Expand Down
6 changes: 3 additions & 3 deletions tests/Database/DatabaseSQLiteProcessorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ public function testProcessColumns()
['name' => 'is_active', 'type' => 'tinyint(1)', 'nullable' => '0', 'default' => '1', 'primary' => '0'],
];
$expected = [
['name' => 'id', 'type_name' => 'integer', 'type' => 'integer', 'collation' => null, 'nullable' => false, 'default' => '', 'auto_increment' => true, 'comment' => null],
['name' => 'name', 'type_name' => 'varchar', 'type' => 'varchar', 'collation' => null, 'nullable' => true, 'default' => 'foo', 'auto_increment' => false, 'comment' => null],
['name' => 'is_active', 'type_name' => 'tinyint', 'type' => 'tinyint(1)', 'collation' => null, 'nullable' => false, 'default' => '1', 'auto_increment' => false, 'comment' => null],
['name' => 'id', 'type_name' => 'integer', 'type' => 'integer', 'collation' => null, 'nullable' => false, 'default' => '', 'auto_increment' => true, 'comment' => null, 'generation' => null],
['name' => 'name', 'type_name' => 'varchar', 'type' => 'varchar', 'collation' => null, 'nullable' => true, 'default' => 'foo', 'auto_increment' => false, 'comment' => null, 'generation' => null],
['name' => 'is_active', 'type_name' => 'tinyint', 'type' => 'tinyint(1)', 'collation' => null, 'nullable' => false, 'default' => '1', 'auto_increment' => false, 'comment' => null, 'generation' => null],
];

$this->assertEquals($expected, $processor->processColumns($listing));
Expand Down
6 changes: 6 additions & 0 deletions tests/Database/DatabaseSchemaBlueprintTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,7 @@ public function testNativeRenameColumnOnMysql57()
$blueprint = new Blueprint('users', function ($table) {
$table->renameColumn('name', 'title');
$table->renameColumn('id', 'key');
$table->renameColumn('generated', 'new_generated');
});

$connection = m::mock(Connection::class);
Expand All @@ -197,11 +198,13 @@ public function testNativeRenameColumnOnMysql57()
$connection->shouldReceive('getSchemaBuilder->getColumns')->andReturn([
['name' => 'name', 'type' => 'varchar(255)', 'type_name' => 'varchar', 'nullable' => true, 'collation' => 'utf8mb4_unicode_ci', 'default' => 'foo', 'comment' => null, 'auto_increment' => false],
['name' => 'id', 'type' => 'bigint unsigned', 'type_name' => 'bigint', 'nullable' => false, 'collation' => null, 'default' => null, 'comment' => 'lorem ipsum', 'auto_increment' => true],
['name' => 'generated', 'type' => 'int', 'type_name' => 'int', 'nullable' => false, 'collation' => null, 'default' => null, 'comment' => null, 'auto_increment' => false, 'generation' => ['type' => 'stored', 'expression' => 'expression']],
]);

$this->assertEquals([
"alter table `users` change `name` `title` varchar(255) collate 'utf8mb4_unicode_ci' null default 'foo'",
"alter table `users` change `id` `key` bigint unsigned not null auto_increment comment 'lorem ipsum'",
'alter table `users` change `generated` `new_generated` int as (expression) stored not null',
], $blueprint->toSql($connection, new MySqlGrammar));
}

Expand All @@ -210,6 +213,7 @@ public function testNativeRenameColumnOnLegacyMariaDB()
$blueprint = new Blueprint('users', function ($table) {
$table->renameColumn('name', 'title');
$table->renameColumn('id', 'key');
$table->renameColumn('generated', 'new_generated');
});

$connection = m::mock(Connection::class);
Expand All @@ -218,11 +222,13 @@ public function testNativeRenameColumnOnLegacyMariaDB()
$connection->shouldReceive('getSchemaBuilder->getColumns')->andReturn([
['name' => 'name', 'type' => 'varchar(255)', 'type_name' => 'varchar', 'nullable' => true, 'collation' => 'utf8mb4_unicode_ci', 'default' => 'foo', 'comment' => null, 'auto_increment' => false],
['name' => 'id', 'type' => 'bigint unsigned', 'type_name' => 'bigint', 'nullable' => false, 'collation' => null, 'default' => null, 'comment' => 'lorem ipsum', 'auto_increment' => true],
['name' => 'generated', 'type' => 'int', 'type_name' => 'int', 'nullable' => false, 'collation' => null, 'default' => null, 'comment' => null, 'auto_increment' => false, 'generation' => ['type' => 'stored', 'expression' => 'expression']],
]);

$this->assertEquals([
"alter table `users` change `name` `title` varchar(255) collate 'utf8mb4_unicode_ci' null default 'foo'",
"alter table `users` change `id` `key` bigint unsigned not null auto_increment comment 'lorem ipsum'",
'alter table `users` change `generated` `new_generated` int as (expression) stored not null',
], $blueprint->toSql($connection, new MySqlGrammar));
}

Expand Down
Loading