Skip to content

Commit 5f43767

Browse files
authored
fix doctrine#6198: stop considering generated column definition as being its default value (doctrine#6199)
| Q | A |------------- | ----------- | Type | bug/feature/improvement | Fixed issues | doctrine#6198 #### Summary The `pg_get_expr(adbin, adrelid)` expression in Postgresql's internal table `pg_attrdef` is used to know a column definition for most column, if this returns something, it means this column's default value. So DBAL has been considering has ALWAYS meaning it contains its default. However for generated columns, it contains the value definition and not its default value, so we change the selectTableColumns' query to correctly set the 'default' column to `null` for generated columns It will help setting correctly the column's attributes which in turn will help generate correct schema diff. <!-- Fill in the relevant information below to help triage your pull request. -->
1 parent 1f8b3fb commit 5f43767

6 files changed

+83
-7
lines changed

src/Driver/AbstractPostgreSQLDriver.php

+5
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
use Doctrine\DBAL\Exception;
99
use Doctrine\DBAL\Platforms\AbstractPlatform;
1010
use Doctrine\DBAL\Platforms\PostgreSQL100Platform;
11+
use Doctrine\DBAL\Platforms\PostgreSQL120Platform;
1112
use Doctrine\DBAL\Platforms\PostgreSQL94Platform;
1213
use Doctrine\DBAL\Platforms\PostgreSQLPlatform;
1314
use Doctrine\DBAL\Schema\PostgreSQLSchemaManager;
@@ -40,6 +41,10 @@ public function createDatabasePlatformForVersion($version)
4041
$patchVersion = $versionParts['patch'] ?? 0;
4142
$version = $majorVersion . '.' . $minorVersion . '.' . $patchVersion;
4243

44+
if (version_compare($version, '12.0', '>=')) {
45+
return new PostgreSQL120Platform();
46+
}
47+
4348
if (version_compare($version, '10.0', '>=')) {
4449
return new PostgreSQL100Platform();
4550
}
+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Doctrine\DBAL\Platforms;
6+
7+
/**
8+
* Provides the behavior, features and SQL dialect of the PostgreSQL 12.0 database platform.
9+
*/
10+
class PostgreSQL120Platform extends PostgreSQL100Platform
11+
{
12+
public function getDefaultColumnValueSQLSnippet(): string
13+
{
14+
// in case of GENERATED ALWAYS AS (foobar) STORED column (added in PostgreSQL 12.0)
15+
// PostgreSQL's pg_get_expr(adbin, adrelid) will return the 'foobar' part
16+
// which is not the 'default' value of the column but its 'definition'
17+
// so in that case we force it to NULL as DBAL will use that column only for the
18+
// 'default' value
19+
return <<<'SQL'
20+
SELECT
21+
CASE
22+
WHEN a.attgenerated = 's' THEN NULL
23+
ELSE pg_get_expr(adbin, adrelid)
24+
END
25+
FROM pg_attrdef
26+
WHERE c.oid = pg_attrdef.adrelid
27+
AND pg_attrdef.adnum=a.attnum
28+
SQL;
29+
}
30+
}

src/Platforms/PostgreSQLPlatform.php

+13
Original file line numberDiff line numberDiff line change
@@ -1193,6 +1193,19 @@ public function getTruncateTableSQL($tableName, $cascade = false)
11931193
return $sql;
11941194
}
11951195

1196+
/**
1197+
* Get the snippet used to retrieve the default value for a given column
1198+
*/
1199+
public function getDefaultColumnValueSQLSnippet(): string
1200+
{
1201+
return <<<'SQL'
1202+
SELECT pg_get_expr(adbin, adrelid)
1203+
FROM pg_attrdef
1204+
WHERE c.oid = pg_attrdef.adrelid
1205+
AND pg_attrdef.adnum=a.attnum
1206+
SQL;
1207+
}
1208+
11961209
/**
11971210
* {@inheritDoc}
11981211
*/

src/Schema/PostgreSQLSchemaManager.php

+3-7
Original file line numberDiff line numberDiff line change
@@ -617,7 +617,7 @@ protected function selectTableColumns(string $databaseName, ?string $tableName =
617617
$sql .= ' c.relname AS table_name, n.nspname AS schema_name,';
618618
}
619619

620-
$sql .= <<<'SQL'
620+
$sql .= sprintf(<<<'SQL'
621621
a.attnum,
622622
quote_ident(a.attname) AS field,
623623
t.typname AS type,
@@ -633,11 +633,7 @@ protected function selectTableColumns(string $databaseName, ?string $tableName =
633633
AND pg_index.indkey[0] = a.attnum
634634
AND pg_index.indisprimary = 't'
635635
) AS pri,
636-
(SELECT pg_get_expr(adbin, adrelid)
637-
FROM pg_attrdef
638-
WHERE c.oid = pg_attrdef.adrelid
639-
AND pg_attrdef.adnum=a.attnum
640-
) AS default,
636+
(%s) AS default,
641637
(SELECT pg_description.description
642638
FROM pg_description WHERE pg_description.objoid = c.oid AND a.attnum = pg_description.objsubid
643639
) AS comment
@@ -652,7 +648,7 @@ protected function selectTableColumns(string $databaseName, ?string $tableName =
652648
ON d.objid = c.oid
653649
AND d.deptype = 'e'
654650
AND d.classid = (SELECT oid FROM pg_class WHERE relname = 'pg_class')
655-
SQL;
651+
SQL, $this->_platform->getDefaultColumnValueSQLSnippet());
656652

657653
$conditions = array_merge([
658654
'a.attnum > 0',

tests/Driver/VersionAwarePlatformDriverTest.php

+2
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
use Doctrine\DBAL\Platforms\MySQL84Platform;
1818
use Doctrine\DBAL\Platforms\MySQLPlatform;
1919
use Doctrine\DBAL\Platforms\PostgreSQL100Platform;
20+
use Doctrine\DBAL\Platforms\PostgreSQL120Platform;
2021
use Doctrine\DBAL\Platforms\PostgreSQL94Platform;
2122
use Doctrine\DBAL\VersionAwarePlatformDriver;
2223
use Doctrine\Deprecations\PHPUnit\VerifyDeprecations;
@@ -90,6 +91,7 @@ public static function postgreSQLVersionProvider(): array
9091
['9.4.0', PostgreSQL94Platform::class],
9192
['9.4.1', PostgreSQL94Platform::class],
9293
['10', PostgreSQL100Platform::class],
94+
['12', PostgreSQL120Platform::class],
9395
];
9496
}
9597

tests/Functional/Schema/PostgreSQLSchemaManagerTest.php

+30
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use Doctrine\DBAL\Driver\ServerInfoAwareConnection;
66
use Doctrine\DBAL\Exception\DatabaseObjectNotFoundException;
77
use Doctrine\DBAL\Platforms\AbstractPlatform;
8+
use Doctrine\DBAL\Platforms\PostgreSQL120Platform;
89
use Doctrine\DBAL\Platforms\PostgreSQLPlatform;
910
use Doctrine\DBAL\Schema\AbstractSchemaManager;
1011
use Doctrine\DBAL\Schema\Comparator;
@@ -334,6 +335,35 @@ public function testBooleanDefault(callable $comparatorFactory): void
334335
self::assertFalse($diff);
335336
}
336337

338+
/**
339+
* @param callable(AbstractSchemaManager):Comparator $comparatorFactory
340+
*
341+
* @dataProvider \Doctrine\DBAL\Tests\Functional\Schema\ComparatorTestUtils::comparatorProvider
342+
*/
343+
public function testGeneratedColumn(callable $comparatorFactory): void
344+
{
345+
$wrappedConnection = $this->connection->getWrappedConnection();
346+
if (! $this->connection->getDatabasePlatform() instanceof PostgreSQL120Platform) {
347+
self::markTestSkipped('Generated columns are not supported in Postgres 11 and earlier');
348+
}
349+
350+
$table = new Table('ddc6198_generated_always_as');
351+
$table->addColumn('id', Types::INTEGER);
352+
$table->addColumn(
353+
'idIsOdd',
354+
Types::BOOLEAN,
355+
['columnDefinition' => 'boolean GENERATED ALWAYS AS (id % 2 = 1) STORED', 'notNull' => false],
356+
);
357+
358+
$this->dropAndCreateTable($table);
359+
360+
$databaseTable = $this->schemaManager->introspectTable($table->getName());
361+
362+
$diff = $comparatorFactory($this->schemaManager)->diffTable($table, $databaseTable);
363+
364+
self::assertFalse($diff);
365+
}
366+
337367
/**
338368
* PostgreSQL stores BINARY columns as BLOB
339369
*/

0 commit comments

Comments
 (0)