Skip to content

Commit

Permalink
Ensure the pg_depend relation is for a table object.
Browse files Browse the repository at this point in the history
This fixes a bug where there was a pg_proc which was a dependency of
an extention and also had an identical oid to the table class being
described.  Oids are not guaranteed to be unique because they will
wrap around once they hit the unsigned integer max.  The added
conditional will ensure that the target object of the dependency is a
table.

Fixes: doctrine#5781
  • Loading branch information
Allen Lai committed Feb 17, 2023
1 parent bd54f50 commit 27996ed
Show file tree
Hide file tree
Showing 2 changed files with 74 additions and 0 deletions.
1 change: 1 addition & 0 deletions src/Schema/PostgreSQLSchemaManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -651,6 +651,7 @@ protected function selectTableColumns(string $databaseName, ?string $tableName =
LEFT JOIN pg_depend d
ON d.objid = c.oid
AND d.deptype = 'e'
AND d.classid = (SELECT oid FROM pg_class WHERE relname = 'pg_class')
SQL;

$conditions = array_merge([
Expand Down
73 changes: 73 additions & 0 deletions tests/Functional/Schema/PostgreSQLSchemaManagerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Doctrine\DBAL\Tests\Functional\Schema;

use Doctrine\DBAL\Driver\ServerInfoAwareConnection;
use Doctrine\DBAL\Exception\DatabaseObjectNotFoundException;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Platforms\PostgreSQLPlatform;
Expand All @@ -27,7 +28,10 @@
use function array_unshift;
use function assert;
use function count;
use function preg_match;
use function sprintf;
use function strtolower;
use function version_compare;

class PostgreSQLSchemaManagerTest extends SchemaManagerFunctionalTestCase
{
Expand Down Expand Up @@ -561,6 +565,75 @@ public function testAlterTableAutoIncrementIntToBigInt(
self::assertTrue($tableFinal->getColumn('id')->getAutoincrement());
}

public function testListTableColumnsOidConflictWithNonTableObject(): void
{
$versionParts = [];
$wrappedConnection = $this->connection->getWrappedConnection();
assert($wrappedConnection instanceof ServerInfoAwareConnection);

$matchCount = preg_match('/^(?P<major>\d+)/', $wrappedConnection->getServerVersion(), $versionParts);
assert($matchCount > 0);

$majorVersion = $versionParts['major'];
if (version_compare($majorVersion, '10', '<')) {
self::markTestSkipped('Manually setting the Oid is not supported in 9.4');
}

$table = 'test_list_table_columns_oid_conflicts';
$this->connection->executeStatement(sprintf('CREATE TABLE IF NOT EXISTS %s(id INT NOT NULL)', $table));
$beforeColumns = $this->schemaManager->listTableColumns($table);
$this->assertArrayHasKey('id', $beforeColumns);

$this->connection->executeStatement('CREATE EXTENSION IF NOT EXISTS pg_prewarm');
$originalTableOid = $this->connection->fetchOne(
'SELECT oid FROM pg_class WHERE pg_class.relname = ?',
[$table],
);

$getConflictingOidSql = <<<'SQL'
SELECT objid
FROM pg_depend
JOIN pg_extension as ex on ex.oid = pg_depend.refobjid
WHERE ex.extname = 'pg_prewarm'
ORDER BY objid
LIMIT 1
SQL;
$conflictingOid = $this->connection->fetchOne($getConflictingOidSql);

$this->connection->executeStatement(
'UPDATE pg_attribute SET attrelid = ? WHERE attrelid = ?',
[$conflictingOid, $originalTableOid],
);
$this->connection->executeStatement(
'UPDATE pg_description SET objoid = ? WHERE objoid = ?',
[$conflictingOid, $originalTableOid],
);
$this->connection->executeStatement(
'UPDATE pg_class SET oid = ? WHERE oid = ?',
[$conflictingOid, $originalTableOid],
);

$afterColumns = $this->schemaManager->listTableColumns($table);

// revert to the database to original state prior to asserting result
$this->connection->executeStatement(
'UPDATE pg_attribute SET attrelid = ? WHERE attrelid = ?',
[$originalTableOid, $conflictingOid],
);
$this->connection->executeStatement(
'UPDATE pg_description SET objoid = ? WHERE objoid = ?',
[$originalTableOid, $conflictingOid],
);
$this->connection->executeStatement(
'UPDATE pg_class SET oid = ? WHERE oid = ?',
[$originalTableOid, $conflictingOid],
);
$this->connection->executeStatement(sprintf('DROP TABLE IF EXISTS %s', $table));
$this->connection->executeStatement('DROP EXTENSION IF EXISTS pg_prewarm');

$this->assertArrayHasKey('id', $afterColumns);
}

/** @return iterable<mixed[]> */
public static function autoIncrementTypeMigrations(): iterable
{
Expand Down

0 comments on commit 27996ed

Please sign in to comment.