-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Ensure the pg_depend relation is for a table object #5800
Conversation
Thank you. Would it be possible to write a functional test for this issue? |
@derrabus There are 2 ways I can think to write a functional test for this. Let me know if you have any preference or other ideas.
CREATE TABLE IF NOT EXISTS test(id INT NOT NULL);
CREATE EXTENSION IF NOT EXISTS pg_prewarm;
UPDATE pg_class SET oid = (
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
)
WHERE pg_class.relname='test'; Wrap around script <?php
// touch empty_file.txt
// docker run --rm -p 5432:5432 -e POSTGRES_PASSWORD=Passw0rd -v $(pwd)/empty_file.txt:/tmp/empty_file.txt postgres:14.2-alpine
require 'vendor/autoload.php';
use Doctrine\DBAL\DriverManager;
$connection = DriverManager::getConnection([
'driver' => 'pdo_pgsql',
'host' => 'localhost',
'user' => 'postgres',
'password' => 'Passw0rd',
]);
$connection->executeStatement('CREATE TABLE IF NOT EXISTS oid_wrap (oid_number oid)');
$connection->executeStatement('CREATE EXTENSION IF NOT EXISTS pg_prewarm');
$procOid = $connection->fetchOne("
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"
);
echo sprintf("proc id: %s\n",$procOid);
$connection->executeStatement('CREATE TABLE IF NOT EXISTS abc (id INT NOT NULL)');
$tableOid = $connection->fetchOne("
SELECT oid
FROM pg_class
WHERE relname='abc'
LIMIT 1"
);
echo sprintf("Table id: %s\n", $tableOid);
$maxOidValue = 4294967295;
$totalObjsToInsert = $maxOidValue + $procOid - ($tableOid - $procOid); // 10000 + 16388 - (16391-16388)
echo sprintf("Objs To Insert: %s\n", $totalObjsToInsert);
$start = microtime(true);
while($totalObjsToInsert !== 0)
{
$objsToInsert = 1000000;
if ($totalObjsToInsert < 100000) {
$objsToInsert = $totalObjsToInsert;
}
$connection->executeStatement("INSERT INTO oid_wrap
SELECT lo_import('/tmp/blob.txt')
FROM generate_series(1, $objsToInsert)");
$totalObjsToInsert -= $objsToInsert;
}
$end = microtime(true);
$connection->executeStatement('DROP TABLE IF EXISTS abc');
$connection->executeStatement('CREATE TABLE IF NOT EXISTS abc (id INT NOT NULL)');
$tableOid = $connection->fetchOne("
SELECT oid
FROM pg_class
WHERE relname='abc'
LIMIT 1"
);
echo sprintf("Time to run: %s\n", ($end - $start));
echo sprintf("Final Table id: %s\n", $tableOid); |
Option 1 sounds like it's too slow to run that test in the CI. Can you give option 2 a try? |
c60537d
to
6fb7ab2
Compare
@derrabus I added the functional test you requested. |
6fb7ab2
to
6c8aee7
Compare
Since psql 9.4 doesn't support setting the oid (https://github.com/doctrine/dbal/actions/runs/3380906837/jobs/5879984623#step:6:84) I mark the test as skipped for the 9.4 platform. |
f183fcd
to
6ddef91
Compare
6ddef91
to
0351a09
Compare
Just checking the status on this. Would help us unlock the upgrade to the newest dbal version. |
Have you tested the changes in this PR? If you need this change, please don't query us for a status, but help us ship it. |
@@ -561,6 +564,73 @@ public function testAlterTableAutoIncrementIntToBigInt( | |||
self::assertTrue($tableFinal->getColumn('id')->getAutoincrement()); | |||
} | |||
|
|||
public function testListTableColumnsOidConflictWithNonTableObject(): void | |||
{ | |||
$semanticVersion = $this->connection->fetchOne('SHOW SERVER_VERSION'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Doesn't this work?
$semanticVersion = $this->connection->fetchOne('SHOW SERVER_VERSION'); | |
$semanticVersion = $this->connection->getServerVersion(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
getServerVersion
on the connection object is private. $this->connection->getWrappedConnection()
has a public method, but is deprecated so I went with$this->connection->getNativeConnection()->getAttribute(PDO::ATTR_SERVER_VERSION)
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
getServerVersion
on the connection object is private.
Indeed.
$this->connection->getWrappedConnection()
has a public method, but is deprecated
Let's use that anyway. I'll have to adjust the call in 4.0.x, but that's fine.
so I went with
$this->connection->getNativeConnection()->getAttribute(PDO::ATTR_SERVER_VERSION)
.
This will fail as soon as we merge your change to 3.6.x because we have a Postgres driver there that does not use PDO.
// revert to the original oid | ||
$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)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we need to change thing in the database after we have made our assertion?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since I'm modifying the pg catalog directly, I wanted to put the original values back so that this wouldn't affect downstream tests. I tried wrapping the changes in a transaction but unfortunately that didn't work and pg threw an error when rolling back. Even if it doesn't cause problems now, if it does affect a test in the future I think it will be impossible to debug for whatever poor soul runs into it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, but that means, if the assertion fails, your cleanup won't run?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I moved the assertion after undoing the database changes to account for this.
e97e9f9
to
7fd18a5
Compare
7fd18a5
to
2cd23b6
Compare
FWIW I have had my fork running in production since the 8th based off of 3.5.3 |
$versionParts = []; | ||
$wrappedConnection = $this->connection->getWrappedConnection(); | ||
assert($wrappedConnection instanceof ServerInfoAwareConnection); | ||
assert(preg_match('/^(?P<major>\d+)/', $wrappedConnection->getServerVersion(), $versionParts) > 0); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since we're going to use $versionParts
afterwards, we need to be sure the preg_match()
is actually executed. Since assert()
s can be disabled, you'll have to move that call out of the assert()
.
That being said, you might just use PHP's version_compare()
function to check if we're on Postgres 10 or later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure if I implemented what you wanted. If assert
can be disabled, should I throw an exception if $versionParts['major']
isn't set? That's what I was trying to accomplish with the original assert
call.
I have no doubt that your change works. It's just that I need a proper test to make sure nobody's going to break it in the future. But I think we're close to merging the PR. 🤞🏻 |
2cd23b6
to
27996ed
Compare
$matchCount = preg_match('/^(?P<major>\d+)/', $wrappedConnection->getServerVersion(), $versionParts); | ||
assert($matchCount > 0); | ||
|
||
$majorVersion = $versionParts['major']; | ||
if (version_compare($majorVersion, '10', '<')) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think you understood my last comment about version_compare()
. Have you tried if this works?
$matchCount = preg_match('/^(?P<major>\d+)/', $wrappedConnection->getServerVersion(), $versionParts); | |
assert($matchCount > 0); | |
$majorVersion = $versionParts['major']; | |
if (version_compare($majorVersion, '10', '<')) { | |
if (version_compare($wrappedConnection->getServerVersion(), '10.0', '<')) { |
Using version_compare()
is a bit pointless if we still have to parse the server version.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It worked. Thanks for the clarification.
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
27996ed
to
b35b52c
Compare
Thanks for your time and patience @derrabus |
if (version_compare($wrappedConnection->getServerVersion(), '10.0', '<')) { | ||
self::markTestSkipped('Manually setting the Oid is not supported in 9.4'); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It appears we need to skip that test on Postgres 10 and 11 as well. #5955
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
:( Sorry about that
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No worries, my comment was just FYI. 🙂
This fixes a bug where there was a pg_proc which was a dependency of an extension 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.
Summary
See #5781 for an in depth description of the issue.
Since the issue itself it hard to replicate, I checked the backwards compatibility with #5668 (comment) which was the original cause of the change.