From 3cad3720ca557581bcc60f4264d5f19abb2a0633 Mon Sep 17 00:00:00 2001 From: Peter Matseykanets Date: Fri, 12 Oct 2018 00:58:53 -0400 Subject: [PATCH] Add support for identity columns in PostgreSQL 10+ Since PostgreSQL 10 the preferred method of defining auto increment columns is to use SQL standard identity columns instead of SERIAL psedo-types COLUMN TYPE GENERATED { ALWAYS | BY DEFAULT } AS IDENTITY [ ( sequence_options ) ] See https://www.postgresql.org/docs/10/static/sql-createtable.html It fixes issues with SERIAL psudo-types: - CREATE TABLE / LIKE copies default but refers to same sequence - cannot add/drop serialness with ALTER TABLE - dropping default does not drop sequence - need to grant separate privileges to sequence - other slight weirdnesses because serial is some kind of special macro As explained by author of the patch in https://git.postgresql.org/gitweb/?p=postgresql.git;a=commitdiff;h=3217327053638085d24dd4d276e7c1f7ac2c4c6b --- .../Database/Schema/ColumnDefinition.php | 2 + .../Schema/Grammars/PostgresGrammar.php | 44 ++++++++++++++++--- .../DatabasePostgresSchemaGrammarTest.php | 27 ++++++++++++ 3 files changed, 67 insertions(+), 6 deletions(-) diff --git a/src/Illuminate/Database/Schema/ColumnDefinition.php b/src/Illuminate/Database/Schema/ColumnDefinition.php index 75a4487ba994..5210b6172a48 100644 --- a/src/Illuminate/Database/Schema/ColumnDefinition.php +++ b/src/Illuminate/Database/Schema/ColumnDefinition.php @@ -19,6 +19,8 @@ * @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 generatedAs($expression) Create a SQL compliant identity column (PostgreSQL) + * @method ColumnDefinition always() Used as a modifier for generatedAs() (PostgreSQL) */ class ColumnDefinition extends Fluent { diff --git a/src/Illuminate/Database/Schema/Grammars/PostgresGrammar.php b/src/Illuminate/Database/Schema/Grammars/PostgresGrammar.php index 49902a627bea..7c3446279a37 100755 --- a/src/Illuminate/Database/Schema/Grammars/PostgresGrammar.php +++ b/src/Illuminate/Database/Schema/Grammars/PostgresGrammar.php @@ -437,6 +437,38 @@ protected function typeLongText(Fluent $column) return 'text'; } + private function generatableColumn($type, Fluent $column) + { + if (! $column->autoIncrement && $column->generatedAs === null) { + return $type; + } + + if ($column->autoIncrement && $column->generatedAs === null) { + $serial = [ + 'integer' => 'serial', + 'bigint' => 'bigserial', + 'smallint' => 'smallserial', + ]; + + return $serial[$type]; + } + + // Identity column but not a primary key + $always = $column->always ? 'always' : 'by default'; + $options = ''; + + if (! is_bool($column->generatedAs) && ((string) $column->generatedAs !== '')) { + $options = sprintf(' (%s)', $column->generatedAs); + } + + return sprintf( + '%s generated %s as identity%s', + $type, + $always, + $options + ); + } + /** * Create the column definition for an integer type. * @@ -445,7 +477,7 @@ protected function typeLongText(Fluent $column) */ protected function typeInteger(Fluent $column) { - return $column->autoIncrement ? 'serial' : 'integer'; + return $this->generatableColumn('integer', $column); } /** @@ -456,7 +488,7 @@ protected function typeInteger(Fluent $column) */ protected function typeBigInteger(Fluent $column) { - return $column->autoIncrement ? 'bigserial' : 'bigint'; + return $this->generatableColumn('bigint', $column); } /** @@ -467,7 +499,7 @@ protected function typeBigInteger(Fluent $column) */ protected function typeMediumInteger(Fluent $column) { - return $column->autoIncrement ? 'serial' : 'integer'; + return $this->generatableColumn('integer', $column); } /** @@ -478,7 +510,7 @@ protected function typeMediumInteger(Fluent $column) */ protected function typeTinyInteger(Fluent $column) { - return $column->autoIncrement ? 'smallserial' : 'smallint'; + return $this->generatableColumn('smallint', $column); } /** @@ -489,7 +521,7 @@ protected function typeTinyInteger(Fluent $column) */ protected function typeSmallInteger(Fluent $column) { - return $column->autoIncrement ? 'smallserial' : 'smallint'; + return $this->generatableColumn('smallint', $column); } /** @@ -854,7 +886,7 @@ protected function modifyDefault(Blueprint $blueprint, Fluent $column) */ protected function modifyIncrement(Blueprint $blueprint, Fluent $column) { - if (in_array($column->type, $this->serials) && $column->autoIncrement) { + if ((in_array($column->type, $this->serials) || ($column->generatedAs !== null)) && $column->autoIncrement) { return ' primary key'; } } diff --git a/tests/Database/DatabasePostgresSchemaGrammarTest.php b/tests/Database/DatabasePostgresSchemaGrammarTest.php index 373c353b538c..da42ae4e5fea 100755 --- a/tests/Database/DatabasePostgresSchemaGrammarTest.php +++ b/tests/Database/DatabasePostgresSchemaGrammarTest.php @@ -660,6 +660,33 @@ public function testAddingUuid() $this->assertEquals('alter table "users" add column "foo" uuid not null', $statements[0]); } + public function testAddingGeneratedAs() + { + $blueprint = new Blueprint('users'); + $blueprint->increments('foo')->generatedAs(); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $this->assertCount(1, $statements); + $this->assertEquals('alter table "users" add column "foo" integer generated by default as identity primary key not null', $statements[0]); + // With always modifier + $blueprint = new Blueprint('users'); + $blueprint->increments('foo')->generatedAs()->always(); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $this->assertCount(1, $statements); + $this->assertEquals('alter table "users" add column "foo" integer generated always as identity primary key not null', $statements[0]); + // With sequence options + $blueprint = new Blueprint('users'); + $blueprint->increments('foo')->generatedAs('increment by 10 start with 100'); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $this->assertCount(1, $statements); + $this->assertEquals('alter table "users" add column "foo" integer generated by default as identity (increment by 10 start with 100) primary key not null', $statements[0]); + // Not a primary key + $blueprint = new Blueprint('users'); + $blueprint->integer('foo')->generatedAs(); + $statements = $blueprint->toSql($this->getConnection(), $this->getGrammar()); + $this->assertCount(1, $statements); + $this->assertEquals('alter table "users" add column "foo" integer generated by default as identity not null', $statements[0]); + } + public function testAddingIpAddress() { $blueprint = new Blueprint('users');