Skip to content

Commit

Permalink
Add support for identity columns in PostgreSQL 10+
Browse files Browse the repository at this point in the history
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
  • Loading branch information
pmatseykanets committed Oct 12, 2018
1 parent 00af3e3 commit 3cad372
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 6 deletions.
2 changes: 2 additions & 0 deletions src/Illuminate/Database/Schema/ColumnDefinition.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down
44 changes: 38 additions & 6 deletions src/Illuminate/Database/Schema/Grammars/PostgresGrammar.php
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand All @@ -445,7 +477,7 @@ protected function typeLongText(Fluent $column)
*/
protected function typeInteger(Fluent $column)
{
return $column->autoIncrement ? 'serial' : 'integer';
return $this->generatableColumn('integer', $column);
}

/**
Expand All @@ -456,7 +488,7 @@ protected function typeInteger(Fluent $column)
*/
protected function typeBigInteger(Fluent $column)
{
return $column->autoIncrement ? 'bigserial' : 'bigint';
return $this->generatableColumn('bigint', $column);
}

/**
Expand All @@ -467,7 +499,7 @@ protected function typeBigInteger(Fluent $column)
*/
protected function typeMediumInteger(Fluent $column)
{
return $column->autoIncrement ? 'serial' : 'integer';
return $this->generatableColumn('integer', $column);
}

/**
Expand All @@ -478,7 +510,7 @@ protected function typeMediumInteger(Fluent $column)
*/
protected function typeTinyInteger(Fluent $column)
{
return $column->autoIncrement ? 'smallserial' : 'smallint';
return $this->generatableColumn('smallint', $column);
}

/**
Expand All @@ -489,7 +521,7 @@ protected function typeTinyInteger(Fluent $column)
*/
protected function typeSmallInteger(Fluent $column)
{
return $column->autoIncrement ? 'smallserial' : 'smallint';
return $this->generatableColumn('smallint', $column);
}

/**
Expand Down Expand Up @@ -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';
}
}
Expand Down
27 changes: 27 additions & 0 deletions tests/Database/DatabasePostgresSchemaGrammarTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down

0 comments on commit 3cad372

Please sign in to comment.