diff --git a/src/Illuminate/Database/Query/Processors/MySqlProcessor.php b/src/Illuminate/Database/Query/Processors/MySqlProcessor.php index 989b7745d218..a8217ca4527d 100644 --- a/src/Illuminate/Database/Query/Processors/MySqlProcessor.php +++ b/src/Illuminate/Database/Query/Processors/MySqlProcessor.php @@ -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); } diff --git a/src/Illuminate/Database/Query/Processors/PostgresProcessor.php b/src/Illuminate/Database/Query/Processors/PostgresProcessor.php index 71c3e862ca37..ad02ce442780 100755 --- a/src/Illuminate/Database/Query/Processors/PostgresProcessor.php +++ b/src/Illuminate/Database/Query/Processors/PostgresProcessor.php @@ -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); } diff --git a/src/Illuminate/Database/Query/Processors/SQLiteProcessor.php b/src/Illuminate/Database/Query/Processors/SQLiteProcessor.php index 63cc84c067da..5ce6ecac48f7 100644 --- a/src/Illuminate/Database/Query/Processors/SQLiteProcessor.php +++ b/src/Illuminate/Database/Query/Processors/SQLiteProcessor.php @@ -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, '(') ?: '', @@ -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); } diff --git a/src/Illuminate/Database/Query/Processors/SqlServerProcessor.php b/src/Illuminate/Database/Query/Processors/SqlServerProcessor.php index 8c632060b025..5fcde7c437c4 100755 --- a/src/Illuminate/Database/Query/Processors/SqlServerProcessor.php +++ b/src/Illuminate/Database/Query/Processors/SqlServerProcessor.php @@ -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); } diff --git a/src/Illuminate/Database/Schema/Grammars/MySqlGrammar.php b/src/Illuminate/Database/Schema/Grammars/MySqlGrammar.php index aec73db6eef4..44ff9c430aa4 100755 --- a/src/Illuminate/Database/Schema/Grammars/MySqlGrammar.php +++ b/src/Illuminate/Database/Schema/Grammars/MySqlGrammar.php @@ -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), @@ -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', diff --git a/src/Illuminate/Database/Schema/Grammars/PostgresGrammar.php b/src/Illuminate/Database/Schema/Grammars/PostgresGrammar.php index aad87542d552..5aa785a88a75 100755 --- a/src/Illuminate/Database/Schema/Grammars/PostgresGrammar.php +++ b/src/Illuminate/Database/Schema/Grammars/PostgresGrammar.php @@ -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 ' diff --git a/src/Illuminate/Database/Schema/Grammars/SQLiteGrammar.php b/src/Illuminate/Database/Schema/Grammars/SQLiteGrammar.php index 5d969c3eaf93..ba03d206e96f 100644 --- a/src/Illuminate/Database/Schema/Grammars/SQLiteGrammar.php +++ b/src/Illuminate/Database/Schema/Grammars/SQLiteGrammar.php @@ -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)) ); @@ -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([ @@ -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, ]) ); } diff --git a/src/Illuminate/Database/Schema/Grammars/SqlServerGrammar.php b/src/Illuminate/Database/Schema/Grammars/SqlServerGrammar.php index b719f127f3dc..cc514e26a6c3 100755 --- a/src/Illuminate/Database/Schema/Grammars/SqlServerGrammar.php +++ b/src/Illuminate/Database/Schema/Grammars/SqlServerGrammar.php @@ -107,6 +107,7 @@ 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 ' @@ -114,6 +115,7 @@ public function compileColumns($schema, $table) .'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), diff --git a/tests/Database/DatabaseMariaDbProcessorTest.php b/tests/Database/DatabaseMariaDbProcessorTest.php index ecd3cae703a8..97c0cc90f479 100644 --- a/tests/Database/DatabaseMariaDbProcessorTest.php +++ b/tests/Database/DatabaseMariaDbProcessorTest.php @@ -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)); diff --git a/tests/Database/DatabaseMySqlProcessorTest.php b/tests/Database/DatabaseMySqlProcessorTest.php index 5ac56b1ce990..9919ff975b34 100644 --- a/tests/Database/DatabaseMySqlProcessorTest.php +++ b/tests/Database/DatabaseMySqlProcessorTest.php @@ -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)); diff --git a/tests/Database/DatabasePostgresProcessorTest.php b/tests/Database/DatabasePostgresProcessorTest.php index 8213fc68aba7..6fca3d3496ed 100644 --- a/tests/Database/DatabasePostgresProcessorTest.php +++ b/tests/Database/DatabasePostgresProcessorTest.php @@ -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)); diff --git a/tests/Database/DatabaseSQLiteProcessorTest.php b/tests/Database/DatabaseSQLiteProcessorTest.php index 4aeb4438f7d7..7511686d40bd 100644 --- a/tests/Database/DatabaseSQLiteProcessorTest.php +++ b/tests/Database/DatabaseSQLiteProcessorTest.php @@ -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)); diff --git a/tests/Database/DatabaseSchemaBlueprintTest.php b/tests/Database/DatabaseSchemaBlueprintTest.php index a9bb895ec0d1..0fc9318d3269 100755 --- a/tests/Database/DatabaseSchemaBlueprintTest.php +++ b/tests/Database/DatabaseSchemaBlueprintTest.php @@ -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); @@ -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)); } @@ -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); @@ -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)); } diff --git a/tests/Integration/Database/SchemaBuilderTest.php b/tests/Integration/Database/SchemaBuilderTest.php index 2abe2ceb8f02..dbf41b88db07 100644 --- a/tests/Integration/Database/SchemaBuilderTest.php +++ b/tests/Integration/Database/SchemaBuilderTest.php @@ -536,6 +536,98 @@ public function testAddingStoredColumnOnSqlite() $this->assertTrue(Schema::hasColumns('test', ['virtual_column', 'stored_column'])); } + public function testModifyingStoredColumnOnSqlite() + { + if ($this->driver !== 'sqlite') { + $this->markTestSkipped('Test requires a SQLite connection.'); + } + + Schema::create('test', function (Blueprint $table) { + $table->integer('price'); + $table->integer('virtual_price')->virtualAs('price - 2'); + $table->integer('stored_price')->storedAs('price - 4'); + $table->integer('virtual_price_changed')->virtualAs('price - 6'); + $table->integer('stored_price_changed')->storedAs('price - 8'); + }); + + DB::table('test')->insert(['price' => 100]); + + Schema::table('test', function (Blueprint $table) { + $table->integer('virtual_price_changed')->virtualAs('price - 5')->change(); + $table->integer('stored_price_changed')->storedAs('price - 7')->change(); + }); + + $this->assertEquals( + ['price' => 100, 'virtual_price' => 98, 'stored_price' => 96, 'virtual_price_changed' => 95, 'stored_price_changed' => 93], + (array) DB::table('test')->first() + ); + + $columns = Schema::getColumns('test'); + + $this->assertTrue(collect($columns)->contains( + fn ($column) => $column['name'] === 'virtual_price' && $column['generation']['type'] === 'virtual' + && $column['generation']['expression'] === 'price - 2' + )); + $this->assertTrue(collect($columns)->contains( + fn ($column) => $column['name'] === 'stored_price' && $column['generation']['type'] === 'stored' + && $column['generation']['expression'] === 'price - 4' + )); + $this->assertTrue(collect($columns)->contains( + fn ($column) => $column['name'] === 'virtual_price_changed' && $column['generation']['type'] === 'virtual' + && $column['generation']['expression'] === 'price - 5' + )); + $this->assertTrue(collect($columns)->contains( + fn ($column) => $column['name'] === 'stored_price_changed' && $column['generation']['type'] === 'stored' + && $column['generation']['expression'] === 'price - 7' + )); + } + + public function testGettingGeneratedColumns() + { + Schema::create('test', function (Blueprint $table) { + $table->integer('price'); + + if ($this->driver === 'sqlsrv') { + $table->computed('virtual_price', 'price - 5'); + $table->computed('stored_price', 'price - 10')->persisted(); + } else { + if ($this->driver !== 'pgsql') { + $table->integer('virtual_price')->virtualAs('price - 5'); + } + $table->integer('stored_price')->storedAs('price - 10'); + } + }); + + $columns = Schema::getColumns('test'); + + $this->assertTrue(collect($columns)->contains( + fn ($column) => $column['name'] === 'price' && is_null($column['generation']) + )); + if ($this->driver !== 'pgsql') { + $this->assertTrue(collect($columns)->contains( + fn ($column) => $column['name'] === 'virtual_price' + && $column['generation']['type'] === 'virtual' + && match ($this->driver) { + 'mysql' => $column['generation']['expression'] === '(`price` - 5)', + 'mariadb' => $column['generation']['expression'] === '`price` - 5', + 'sqlsrv' => $column['generation']['expression'] === '([price]-(5))', + default => $column['generation']['expression'] === 'price - 5', + } + )); + } + $this->assertTrue(collect($columns)->contains( + fn ($column) => $column['name'] === 'stored_price' + && $column['generation']['type'] === 'stored' + && match ($this->driver) { + 'mysql' => $column['generation']['expression'] === '(`price` - 10)', + 'mariadb' => $column['generation']['expression'] === '`price` - 10', + 'sqlsrv' => $column['generation']['expression'] === '([price]-(10))', + 'pgsql' => $column['generation']['expression'] === '(price - 10)', + default => $column['generation']['expression'] === 'price - 10', + } + )); + } + public function testAddingMacros() { Schema::macro('foo', fn () => 'foo');