From 0fc4965f052b19fbd572afa69c0882bbcc0f9dc2 Mon Sep 17 00:00:00 2001 From: Jonas De Kegel Date: Tue, 29 Jan 2019 14:58:42 +0100 Subject: [PATCH 1/4] Add computed support to SQL Server schema grammar [SQL Server also supports computed columns (Azure & 2016+)](https://docs.microsoft.com/en-us/sql/relational-databases/tables/specify-computed-columns-in-a-table), but as a type; not a modifier. Add virtual column type Document persisted (SQL Server) column modifier --- src/Illuminate/Database/Schema/Blueprint.php | 12 +++++++ .../Database/Schema/ColumnDefinition.php | 1 + .../Schema/Grammars/SqlServerGrammar.php | 32 +++++++++++++++++-- 3 files changed, 43 insertions(+), 2 deletions(-) diff --git a/src/Illuminate/Database/Schema/Blueprint.php b/src/Illuminate/Database/Schema/Blueprint.php index ff58904aae7b..d446bc812ad7 100755 --- a/src/Illuminate/Database/Schema/Blueprint.php +++ b/src/Illuminate/Database/Schema/Blueprint.php @@ -822,6 +822,18 @@ public function unsignedDecimal($column, $total = 8, $places = 2) ]); } + /** + * Create a new generated virtual column on the table. + * + * @param string $column + * @param string $formula + * @return \Illuminate\Database\Schema\ColumnDefinition + */ + public function virtual($column, $formula) + { + return $this->addColumn('virtual', $column, compact('formula')); + } + /** * Create a new boolean column on the table. * diff --git a/src/Illuminate/Database/Schema/ColumnDefinition.php b/src/Illuminate/Database/Schema/ColumnDefinition.php index 1752099f31d2..2c36e7387989 100644 --- a/src/Illuminate/Database/Schema/ColumnDefinition.php +++ b/src/Illuminate/Database/Schema/ColumnDefinition.php @@ -24,6 +24,7 @@ * @method ColumnDefinition unsigned() Set the INTEGER column as UNSIGNED (MySQL) * @method ColumnDefinition useCurrent() Set the TIMESTAMP column to use CURRENT_TIMESTAMP as default value * @method ColumnDefinition virtualAs(string $expression) Create a virtual generated column (MySQL) + * @method ColumnDefinition persisted() Mark the virtual generated column as persistent (SQL Server) */ class ColumnDefinition extends Fluent { diff --git a/src/Illuminate/Database/Schema/Grammars/SqlServerGrammar.php b/src/Illuminate/Database/Schema/Grammars/SqlServerGrammar.php index 38f55fcf55ad..a9f4dddcac35 100755 --- a/src/Illuminate/Database/Schema/Grammars/SqlServerGrammar.php +++ b/src/Illuminate/Database/Schema/Grammars/SqlServerGrammar.php @@ -19,7 +19,7 @@ class SqlServerGrammar extends Grammar * * @var array */ - protected $modifiers = ['Increment', 'Collate', 'Nullable', 'Default']; + protected $modifiers = ['Increment', 'Collate', 'Nullable', 'Default', 'Persisted']; /** * The columns available as serials. @@ -733,6 +733,18 @@ public function typeMultiPolygon(Fluent $column) return 'geography'; } + + /** + * Create the column definition for a generated virtual column type. + * + * @param \Illuminate\Support\Fluent $column + * @return string|null + */ + protected function typeVirtual(Fluent $column) + { + return " as ({$column->formula})"; + } + /** * Get the SQL for a collation column modifier. * @@ -756,7 +768,9 @@ protected function modifyCollate(Blueprint $blueprint, Fluent $column) */ protected function modifyNullable(Blueprint $blueprint, Fluent $column) { - return $column->nullable ? ' null' : ' not null'; + if($column->type !== 'virtual') { + return $column->nullable ? ' null' : ' not null'; + } } /** @@ -787,6 +801,20 @@ protected function modifyIncrement(Blueprint $blueprint, Fluent $column) } } + /** + * Get the SQL for a generated stored column modifier. + * + * @param \Illuminate\Database\Schema\Blueprint $blueprint + * @param \Illuminate\Support\Fluent $column + * @return string|null + */ + protected function modifyPersisted(Blueprint $blueprint, Fluent $column) + { + if ($column->persisted) { + return ' persisted'; + } + } + /** * Wrap a table in keyword identifiers. * From 5138a5ccce405751988934eacc627bb4f3cc7bed Mon Sep 17 00:00:00 2001 From: Jonas De Kegel Date: Tue, 29 Jan 2019 15:13:23 +0100 Subject: [PATCH 2/4] Add tests for SQL Server computed support --- tests/Database/DatabaseSqlServerSchemaGrammarTest.php | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/Database/DatabaseSqlServerSchemaGrammarTest.php b/tests/Database/DatabaseSqlServerSchemaGrammarTest.php index aa16f7bb78d1..0c9650b07f18 100755 --- a/tests/Database/DatabaseSqlServerSchemaGrammarTest.php +++ b/tests/Database/DatabaseSqlServerSchemaGrammarTest.php @@ -770,6 +770,17 @@ public function testAddingMultiPolygon() $this->assertEquals('alter table "geo" add "coordinates" geography not null', $statements[0]); } + public function testAddingGeneratedColumn() + { + $blueprint = new Blueprint('products'); + $blueprint->integer('price'); + $blueprint->virtual('discounted_virtual', 'price - 5'); + $blueprint->virtual('discounted_stored', 'price - 5')->persisted(); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $this->assertCount(1, $statements); + $this->assertEquals('alter table "products" add "price" int not null, add "discounted_virtual" as (price - 5), add "discounted_stored" as (price - 5) persisted', $statements[0]); + } + public function testGrammarsAreMacroable() { // compileReplace macro. From 66ce68daee564393555990db23102d47cafc04c7 Mon Sep 17 00:00:00 2001 From: Jonas De Kegel Date: Tue, 29 Jan 2019 15:20:37 +0100 Subject: [PATCH 3/4] Codestyle fixes --- src/Illuminate/Database/Schema/Grammars/SqlServerGrammar.php | 5 ++--- tests/Database/DatabaseSqlServerSchemaGrammarTest.php | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/Illuminate/Database/Schema/Grammars/SqlServerGrammar.php b/src/Illuminate/Database/Schema/Grammars/SqlServerGrammar.php index a9f4dddcac35..92af8c1ffd99 100755 --- a/src/Illuminate/Database/Schema/Grammars/SqlServerGrammar.php +++ b/src/Illuminate/Database/Schema/Grammars/SqlServerGrammar.php @@ -733,7 +733,6 @@ public function typeMultiPolygon(Fluent $column) return 'geography'; } - /** * Create the column definition for a generated virtual column type. * @@ -742,7 +741,7 @@ public function typeMultiPolygon(Fluent $column) */ protected function typeVirtual(Fluent $column) { - return " as ({$column->formula})"; + return "as ({$column->formula})"; } /** @@ -768,7 +767,7 @@ protected function modifyCollate(Blueprint $blueprint, Fluent $column) */ protected function modifyNullable(Blueprint $blueprint, Fluent $column) { - if($column->type !== 'virtual') { + if ($column->type !== 'virtual') { return $column->nullable ? ' null' : ' not null'; } } diff --git a/tests/Database/DatabaseSqlServerSchemaGrammarTest.php b/tests/Database/DatabaseSqlServerSchemaGrammarTest.php index 0c9650b07f18..9ae97203d6fb 100755 --- a/tests/Database/DatabaseSqlServerSchemaGrammarTest.php +++ b/tests/Database/DatabaseSqlServerSchemaGrammarTest.php @@ -778,7 +778,7 @@ public function testAddingGeneratedColumn() $blueprint->virtual('discounted_stored', 'price - 5')->persisted(); $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); $this->assertCount(1, $statements); - $this->assertEquals('alter table "products" add "price" int not null, add "discounted_virtual" as (price - 5), add "discounted_stored" as (price - 5) persisted', $statements[0]); + $this->assertEquals('alter table "products" add "price" int not null, "discounted_virtual" as (price - 5), "discounted_stored" as (price - 5) persisted', $statements[0]); } public function testGrammarsAreMacroable() From 5443dcac0bd1faef44272519d5d6e4988934c505 Mon Sep 17 00:00:00 2001 From: "De Kegel Jonas (ERE)" Date: Wed, 30 Jan 2019 09:34:50 +0100 Subject: [PATCH 4/4] Rename to computed() & $expression, throw exceptions when not supported --- src/Illuminate/Database/Schema/Blueprint.php | 8 ++++---- src/Illuminate/Database/Schema/ColumnDefinition.php | 2 +- src/Illuminate/Database/Schema/Grammars/Grammar.php | 13 +++++++++++++ .../Database/Schema/Grammars/MySqlGrammar.php | 13 +++++++++++++ .../Database/Schema/Grammars/SqlServerGrammar.php | 8 ++++---- .../Database/DatabaseSqlServerSchemaGrammarTest.php | 4 ++-- 6 files changed, 37 insertions(+), 11 deletions(-) diff --git a/src/Illuminate/Database/Schema/Blueprint.php b/src/Illuminate/Database/Schema/Blueprint.php index d446bc812ad7..355c1cea1d68 100755 --- a/src/Illuminate/Database/Schema/Blueprint.php +++ b/src/Illuminate/Database/Schema/Blueprint.php @@ -823,15 +823,15 @@ public function unsignedDecimal($column, $total = 8, $places = 2) } /** - * Create a new generated virtual column on the table. + * Create a new generated computed column on the table. * * @param string $column - * @param string $formula + * @param string $expression * @return \Illuminate\Database\Schema\ColumnDefinition */ - public function virtual($column, $formula) + public function computed($column, $expression) { - return $this->addColumn('virtual', $column, compact('formula')); + return $this->addColumn('computed', $column, compact('expression')); } /** diff --git a/src/Illuminate/Database/Schema/ColumnDefinition.php b/src/Illuminate/Database/Schema/ColumnDefinition.php index 2c36e7387989..25166a1e3e5f 100644 --- a/src/Illuminate/Database/Schema/ColumnDefinition.php +++ b/src/Illuminate/Database/Schema/ColumnDefinition.php @@ -24,7 +24,7 @@ * @method ColumnDefinition unsigned() Set the INTEGER column as UNSIGNED (MySQL) * @method ColumnDefinition useCurrent() Set the TIMESTAMP column to use CURRENT_TIMESTAMP as default value * @method ColumnDefinition virtualAs(string $expression) Create a virtual generated column (MySQL) - * @method ColumnDefinition persisted() Mark the virtual generated column as persistent (SQL Server) + * @method ColumnDefinition persisted() Mark the computed generated column as persistent (SQL Server) */ class ColumnDefinition extends Fluent { diff --git a/src/Illuminate/Database/Schema/Grammars/Grammar.php b/src/Illuminate/Database/Schema/Grammars/Grammar.php index 1ea4dabc8977..f163c999de2f 100755 --- a/src/Illuminate/Database/Schema/Grammars/Grammar.php +++ b/src/Illuminate/Database/Schema/Grammars/Grammar.php @@ -2,6 +2,7 @@ namespace Illuminate\Database\Schema\Grammars; +use RuntimeException; use Illuminate\Support\Fluent; use Doctrine\DBAL\Schema\TableDiff; use Illuminate\Database\Connection; @@ -190,6 +191,18 @@ public function prefixArray($prefix, array $values) }, $values); } + /** + * Create the column definition for a generated computed column type. + * + * @param \Illuminate\Support\Fluent $column + * + * @throws \RuntimeException + */ + protected function typeComputed(Fluent $column) + { + throw new RuntimeException('The database driver in use does not support the computed type.'); + } + /** * Wrap a table in keyword identifiers. * diff --git a/src/Illuminate/Database/Schema/Grammars/MySqlGrammar.php b/src/Illuminate/Database/Schema/Grammars/MySqlGrammar.php index 2e5ec247ddfc..482281c87d69 100755 --- a/src/Illuminate/Database/Schema/Grammars/MySqlGrammar.php +++ b/src/Illuminate/Database/Schema/Grammars/MySqlGrammar.php @@ -2,6 +2,7 @@ namespace Illuminate\Database\Schema\Grammars; +use RuntimeException; use Illuminate\Support\Fluent; use Illuminate\Database\Connection; use Illuminate\Database\Schema\Blueprint; @@ -833,6 +834,18 @@ public function typeMultiPolygon(Fluent $column) return 'multipolygon'; } + /** + * Create the column definition for a generated computed column type. + * + * @param \Illuminate\Support\Fluent $column + * + * @throws \RuntimeException + */ + protected function typeComputed(Fluent $column) + { + throw new RuntimeException('The database driver in use requires a type, see virtualAs/storedAs modifiers.'); + } + /** * Get the SQL for a generated virtual column modifier. * diff --git a/src/Illuminate/Database/Schema/Grammars/SqlServerGrammar.php b/src/Illuminate/Database/Schema/Grammars/SqlServerGrammar.php index 92af8c1ffd99..27d8773db329 100755 --- a/src/Illuminate/Database/Schema/Grammars/SqlServerGrammar.php +++ b/src/Illuminate/Database/Schema/Grammars/SqlServerGrammar.php @@ -734,14 +734,14 @@ public function typeMultiPolygon(Fluent $column) } /** - * Create the column definition for a generated virtual column type. + * Create the column definition for a generated computed column type. * * @param \Illuminate\Support\Fluent $column * @return string|null */ - protected function typeVirtual(Fluent $column) + protected function typeComputed(Fluent $column) { - return "as ({$column->formula})"; + return "as ({$column->expression})"; } /** @@ -767,7 +767,7 @@ protected function modifyCollate(Blueprint $blueprint, Fluent $column) */ protected function modifyNullable(Blueprint $blueprint, Fluent $column) { - if ($column->type !== 'virtual') { + if ($column->type !== 'computed') { return $column->nullable ? ' null' : ' not null'; } } diff --git a/tests/Database/DatabaseSqlServerSchemaGrammarTest.php b/tests/Database/DatabaseSqlServerSchemaGrammarTest.php index 9ae97203d6fb..5c0a09e160bd 100755 --- a/tests/Database/DatabaseSqlServerSchemaGrammarTest.php +++ b/tests/Database/DatabaseSqlServerSchemaGrammarTest.php @@ -774,8 +774,8 @@ public function testAddingGeneratedColumn() { $blueprint = new Blueprint('products'); $blueprint->integer('price'); - $blueprint->virtual('discounted_virtual', 'price - 5'); - $blueprint->virtual('discounted_stored', 'price - 5')->persisted(); + $blueprint->computed('discounted_virtual', 'price - 5'); + $blueprint->computed('discounted_stored', 'price - 5')->persisted(); $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); $this->assertCount(1, $statements); $this->assertEquals('alter table "products" add "price" int not null, "discounted_virtual" as (price - 5), "discounted_stored" as (price - 5) persisted', $statements[0]);