22
22
23
23
class PostgresAdapter extends PdoAdapter
24
24
{
25
+ public const GENERATED_ALWAYS = 'ALWAYS ' ;
26
+ public const GENERATED_BY_DEFAULT = 'BY DEFAULT ' ;
27
+
25
28
/**
26
29
* @var string[]
27
30
*/
@@ -44,6 +47,13 @@ class PostgresAdapter extends PdoAdapter
44
47
*/
45
48
protected $ columnsWithComments = [];
46
49
50
+ /**
51
+ * Use identity columns if available (Postgres >= 10.0)
52
+ *
53
+ * @var bool
54
+ */
55
+ protected $ useIdentity ;
56
+
47
57
/**
48
58
* {@inheritDoc}
49
59
*
@@ -61,7 +71,6 @@ public function connect(): void
61
71
}
62
72
63
73
$ options = $ this ->getOptions ();
64
-
65
74
$ dsn = 'pgsql:dbname= ' . $ options ['name ' ];
66
75
67
76
if (isset ($ options ['host ' ])) {
@@ -77,7 +86,8 @@ public function connect(): void
77
86
78
87
// use custom data fetch mode
79
88
if (!empty ($ options ['fetch_mode ' ])) {
80
- $ driverOptions [PDO ::ATTR_DEFAULT_FETCH_MODE ] = constant ('\PDO::FETCH_ ' . strtoupper ($ options ['fetch_mode ' ]));
89
+ $ driverOptions [PDO ::ATTR_DEFAULT_FETCH_MODE ] =
90
+ constant ('\PDO::FETCH_ ' . strtoupper ($ options ['fetch_mode ' ]));
81
91
}
82
92
83
93
// pass \PDO::ATTR_PERSISTENT to driver options instead of useless setting it after instantiation
@@ -99,6 +109,8 @@ public function connect(): void
99
109
);
100
110
}
101
111
112
+ $ this ->useIdentity = (float )$ db ->getAttribute (PDO ::ATTR_SERVER_VERSION ) >= 10 ;
113
+
102
114
$ this ->setConnection ($ db );
103
115
}
104
116
}
@@ -231,7 +243,11 @@ public function createTable(Table $table, array $columns = [], array $indexes =
231
243
232
244
$ this ->columnsWithComments = [];
233
245
foreach ($ columns as $ column ) {
234
- $ sql .= $ this ->quoteColumnName ($ column ->getName ()) . ' ' . $ this ->getColumnSqlDefinition ($ column ) . ', ' ;
246
+ $ sql .= $ this ->quoteColumnName ($ column ->getName ()) . ' ' . $ this ->getColumnSqlDefinition ($ column );
247
+ if ($ this ->useIdentity && $ column ->getIdentity () && $ column ->getGenerated () !== null ) {
248
+ $ sql .= sprintf (' GENERATED %s AS IDENTITY ' , $ column ->getGenerated ());
249
+ }
250
+ $ sql .= ', ' ;
235
251
236
252
// set column comments, if needed
237
253
if ($ column ->getComment ()) {
@@ -401,14 +417,15 @@ public function getColumns(string $tableName): array
401
417
'SELECT column_name, data_type, udt_name, is_identity, is_nullable,
402
418
column_default, character_maximum_length, numeric_precision, numeric_scale,
403
419
datetime_precision
420
+ %s
404
421
FROM information_schema.columns
405
422
WHERE table_schema = %s AND table_name = %s
406
423
ORDER BY ordinal_position ' ,
424
+ $ this ->useIdentity ? ', identity_generation ' : '' ,
407
425
$ this ->getConnection ()->quote ($ parts ['schema ' ]),
408
426
$ this ->getConnection ()->quote ($ parts ['table ' ])
409
427
);
410
428
$ columnsInfo = $ this ->fetchAll ($ sql );
411
-
412
429
foreach ($ columnsInfo as $ columnInfo ) {
413
430
$ isUserDefined = strtoupper (trim ($ columnInfo ['data_type ' ])) === 'USER-DEFINED ' ;
414
431
@@ -426,20 +443,28 @@ public function getColumns(string $tableName): array
426
443
} else {
427
444
$ columnDefault = Literal::from ($ columnInfo ['column_default ' ]);
428
445
}
429
- } elseif (preg_match ('/^\D[a-z_\d]*\(.*\)$/ ' , $ columnInfo ['column_default ' ])) {
446
+ } elseif (
447
+ $ columnInfo ['column_default ' ] !== null &&
448
+ preg_match ('/^\D[a-z_\d]*\(.*\)$/ ' , $ columnInfo ['column_default ' ])
449
+ ) {
430
450
$ columnDefault = Literal::from ($ columnInfo ['column_default ' ]);
431
451
} else {
432
452
$ columnDefault = $ columnInfo ['column_default ' ];
433
453
}
434
454
435
455
$ column = new Column ();
456
+
436
457
$ column ->setName ($ columnInfo ['column_name ' ])
437
458
->setType ($ columnType )
438
459
->setNull ($ columnInfo ['is_nullable ' ] === 'YES ' )
439
460
->setDefault ($ columnDefault )
440
461
->setIdentity ($ columnInfo ['is_identity ' ] === 'YES ' )
441
462
->setScale ($ columnInfo ['numeric_scale ' ]);
442
463
464
+ if ($ this ->useIdentity ) {
465
+ $ column ->setGenerated ($ columnInfo ['identity_generation ' ]);
466
+ }
467
+
443
468
if (preg_match ('/\bwith time zone$/ ' , $ columnInfo ['data_type ' ])) {
444
469
$ column ->setTimezone (true );
445
470
}
@@ -492,9 +517,11 @@ protected function getAddColumnInstructions(Table $table, Column $column): Alter
492
517
{
493
518
$ instructions = new AlterInstructions ();
494
519
$ instructions ->addAlter (sprintf (
495
- 'ADD %s %s ' ,
520
+ 'ADD %s %s %s ' ,
496
521
$ this ->quoteColumnName ($ column ->getName ()),
497
- $ this ->getColumnSqlDefinition ($ column )
522
+ $ this ->getColumnSqlDefinition ($ column ),
523
+ $ column ->isIdentity () && $ column ->getGenerated () !== null && $ this ->useIdentity ?
524
+ sprintf ('GENERATED %s AS IDENTITY ' , $ column ->getGenerated ()) : ''
498
525
));
499
526
500
527
if ($ column ->getComment ()) {
@@ -509,8 +536,11 @@ protected function getAddColumnInstructions(Table $table, Column $column): Alter
509
536
*
510
537
* @throws \InvalidArgumentException
511
538
*/
512
- protected function getRenameColumnInstructions (string $ tableName , string $ columnName , string $ newColumnName ): AlterInstructions
513
- {
539
+ protected function getRenameColumnInstructions (
540
+ string $ tableName ,
541
+ string $ columnName ,
542
+ string $ newColumnName
543
+ ): AlterInstructions {
514
544
$ parts = $ this ->getSchemaName ($ tableName );
515
545
$ sql = sprintf (
516
546
'SELECT CASE WHEN COUNT(*) > 0 THEN 1 ELSE 0 END AS column_exists
@@ -542,8 +572,11 @@ protected function getRenameColumnInstructions(string $tableName, string $column
542
572
/**
543
573
* @inheritDoc
544
574
*/
545
- protected function getChangeColumnInstructions (string $ tableName , string $ columnName , Column $ newColumn ): AlterInstructions
546
- {
575
+ protected function getChangeColumnInstructions (
576
+ string $ tableName ,
577
+ string $ columnName ,
578
+ Column $ newColumn
579
+ ): AlterInstructions {
547
580
$ quotedColumnName = $ this ->quoteColumnName ($ columnName );
548
581
$ instructions = new AlterInstructions ();
549
582
if ($ newColumn ->getType () === 'boolean ' ) {
@@ -581,13 +614,33 @@ protected function getChangeColumnInstructions(string $tableName, string $column
581
614
}
582
615
$ instructions ->addAlter ($ sql );
583
616
617
+ $ column = $ this ->getColumn ($ tableName , $ columnName );
618
+
619
+ if ($ this ->useIdentity ) {
620
+ // process identity
621
+ $ sql = sprintf (
622
+ 'ALTER COLUMN %s ' ,
623
+ $ quotedColumnName
624
+ );
625
+ if ($ newColumn ->isIdentity () && $ newColumn ->getGenerated () !== null ) {
626
+ if ($ column ->isIdentity ()) {
627
+ $ sql .= sprintf (' SET GENERATED %s ' , $ newColumn ->getGenerated ());
628
+ } else {
629
+ $ sql .= sprintf (' ADD GENERATED %s AS IDENTITY ' , $ newColumn ->getGenerated ());
630
+ }
631
+ } else {
632
+ $ sql .= ' DROP IDENTITY IF EXISTS ' ;
633
+ }
634
+ $ instructions ->addAlter ($ sql );
635
+ }
636
+
584
637
// process null
585
638
$ sql = sprintf (
586
639
'ALTER COLUMN %s ' ,
587
640
$ quotedColumnName
588
641
);
589
642
590
- if ($ newColumn ->isNull ()) {
643
+ if (! $ newColumn -> getIdentity () && ! $ column -> getIdentity () && $ newColumn ->isNull ()) {
591
644
$ sql .= ' DROP NOT NULL ' ;
592
645
} else {
593
646
$ sql .= ' SET NOT NULL ' ;
@@ -601,7 +654,7 @@ protected function getChangeColumnInstructions(string $tableName, string $column
601
654
$ quotedColumnName ,
602
655
$ this ->getDefaultValueDefinition ($ newColumn ->getDefault (), $ newColumn ->getType ())
603
656
));
604
- } else {
657
+ } elseif (! $ newColumn -> getIdentity ()) {
605
658
//drop default
606
659
$ instructions ->addAlter (sprintf (
607
660
'ALTER COLUMN %s DROP DEFAULT ' ,
@@ -627,6 +680,23 @@ protected function getChangeColumnInstructions(string $tableName, string $column
627
680
return $ instructions ;
628
681
}
629
682
683
+ /**
684
+ * @param string $tableName Table name
685
+ * @param string $columnName Column name
686
+ * @return ?\Phinx\Db\Table\Column
687
+ */
688
+ protected function getColumn (string $ tableName , string $ columnName ): ?Column
689
+ {
690
+ $ columns = $ this ->getColumns ($ tableName );
691
+ foreach ($ columns as $ column ) {
692
+ if ($ column ->getName () === $ columnName ) {
693
+ return $ column ;
694
+ }
695
+ }
696
+
697
+ return null ;
698
+ }
699
+
630
700
/**
631
701
* @inheritDoc
632
702
*/
@@ -1028,7 +1098,7 @@ public function getSqlType($type, ?int $limit = null): array
1028
1098
return ['name ' => $ type ];
1029
1099
}
1030
1100
// Return array type
1031
- throw new UnsupportedColumnTypeException ('Column type " ' . $ type . '" is not supported by Postgresql. ' );
1101
+ throw new UnsupportedColumnTypeException ('Column type ` ' . $ type . '` is not supported by Postgresql. ' );
1032
1102
}
1033
1103
}
1034
1104
@@ -1099,7 +1169,9 @@ public function getPhinxType(string $sqlType): string
1099
1169
case 'macaddr ' :
1100
1170
return static ::PHINX_TYPE_MACADDR ;
1101
1171
default :
1102
- throw new UnsupportedColumnTypeException ('Column type " ' . $ sqlType . '" is not supported by Postgresql. ' );
1172
+ throw new UnsupportedColumnTypeException (
1173
+ 'Column type ` ' . $ sqlType . '` is not supported by Postgresql. '
1174
+ );
1103
1175
}
1104
1176
}
1105
1177
@@ -1163,7 +1235,8 @@ protected function getDefaultValueDefinition($default, ?string $columnType = nul
1163
1235
protected function getColumnSqlDefinition (Column $ column ): string
1164
1236
{
1165
1237
$ buffer = [];
1166
- if ($ column ->isIdentity ()) {
1238
+
1239
+ if ($ column ->isIdentity () && (!$ this ->useIdentity || $ column ->getGenerated () === null )) {
1167
1240
if ($ column ->getType () === 'smallinteger ' ) {
1168
1241
$ buffer [] = 'SMALLSERIAL ' ;
1169
1242
} elseif ($ column ->getType () === 'biginteger ' ) {
@@ -1305,7 +1378,9 @@ protected function getForeignKeySqlDefinition(ForeignKey $foreignKey, string $ta
1305
1378
{
1306
1379
$ parts = $ this ->getSchemaName ($ tableName );
1307
1380
1308
- $ constraintName = $ foreignKey ->getConstraint () ?: ($ parts ['table ' ] . '_ ' . implode ('_ ' , $ foreignKey ->getColumns ()) . '_fkey ' );
1381
+ $ constraintName = $ foreignKey ->getConstraint () ?: (
1382
+ $ parts ['table ' ] . '_ ' . implode ('_ ' , $ foreignKey ->getColumns ()) . '_fkey '
1383
+ );
1309
1384
$ def = ' CONSTRAINT ' . $ this ->quoteColumnName ($ constraintName ) .
1310
1385
' FOREIGN KEY (" ' . implode ('", " ' , $ foreignKey ->getColumns ()) . '") ' .
1311
1386
" REFERENCES {$ this ->quoteTableName ($ foreignKey ->getReferencedTable ()->getName ())} ( \"" .
0 commit comments