Skip to content
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

Fix bigint PHP_INT_MIN/PHP_INT_MAX string to int convert #6410

Merged
merged 9 commits into from
Oct 25, 2024
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions psalm.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,12 @@
<file name="tests/Types/DateIntervalTest.php"/>
</errorLevel>
</InaccessibleProperty>
<InvalidCast>
<errorLevel type="suppress">
<!-- See https://github.com/vimeo/psalm/issues/10995 -->
<file name="src/Types/BigIntType.php"/>
</errorLevel>
</InvalidCast>
<InvalidArgument>
<errorLevel type="suppress">
<!-- We're testing with invalid input here. -->
Expand Down Expand Up @@ -185,6 +191,8 @@
</MoreSpecificReturnType>
<NoValue>
<errorLevel type="suppress">
<!-- See https://github.com/vimeo/psalm/issues/10995 -->
<file name="src/Types/BigIntType.php"/>
<!--
This error looks bogus.
-->
Expand Down Expand Up @@ -291,6 +299,8 @@
<!-- Ignore isset() checks in destructors. -->
<file name="src/Driver/PgSQL/Connection.php"/>
<file name="src/Driver/PgSQL/Statement.php"/>
<!-- See https://github.com/vimeo/psalm/issues/10995 -->
<file name="src/Types/BigIntType.php"/>
</errorLevel>
</TypeDoesNotContainType>
<UndefinedClass>
Expand Down
11 changes: 7 additions & 4 deletions src/Types/BigIntType.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,15 +47,18 @@ public function convertToPHPValue(mixed $value, AbstractPlatform $platform): int
return $value;
}

if ($value > PHP_INT_MIN && $value < PHP_INT_MAX) {
return (int) $value;
}
derrabus marked this conversation as resolved.
Show resolved Hide resolved

assert(
is_string($value),
'DBAL assumes values outside of the integer range to be returned as string by the database driver.',
);

if (
($value > PHP_INT_MIN && $value < PHP_INT_MAX)
|| $value === (string) (int) $value
derrabus marked this conversation as resolved.
Show resolved Hide resolved
) {
return (int) $value;
derrabus marked this conversation as resolved.
Show resolved Hide resolved
}

return $value;
}
}
52 changes: 8 additions & 44 deletions tests/Functional/Types/BigIntTypeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@
use Doctrine\DBAL\Types\Types;
use Generator;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\Constraint\IsIdentical;
use PHPUnit\Framework\Constraint\LogicalOr;

use const PHP_INT_MAX;
use const PHP_INT_MIN;
Expand All @@ -30,13 +28,13 @@ public function testSelectBigInt(string $sqlLiteral, int|string|null $expectedVa

$this->connection->executeStatement(<<<SQL
INSERT INTO bigint_type_test (id, my_integer)
VALUES (42, $sqlLiteral)
VALUES (1, $sqlLiteral)
derrabus marked this conversation as resolved.
Show resolved Hide resolved
SQL);

self::assertSame(
$expectedValue,
$this->connection->convertToPHPValue(
$this->connection->fetchOne('SELECT my_integer from bigint_type_test WHERE id = 42'),
$this->connection->fetchOne('SELECT my_integer from bigint_type_test'),
Types::BIGINT,
),
);
Expand All @@ -49,44 +47,10 @@ public static function provideBigIntLiterals(): Generator
yield 'null' => ['null', null];
yield 'positive number' => ['42', 42];
yield 'negative number' => ['-42', -42];

if (PHP_INT_SIZE < 8) {
// The following tests only work on 64bit systems.
return;
}

yield 'large positive number' => ['9223372036854775806', PHP_INT_MAX - 1];
yield 'large negative number' => ['-9223372036854775807', PHP_INT_MIN + 1];
}

#[DataProvider('provideBigIntEdgeLiterals')]
public function testSelectBigIntEdge(int $value): void
{
$table = new Table('bigint_type_test');
$table->addColumn('id', Types::SMALLINT, ['notnull' => true]);
$table->addColumn('my_integer', Types::BIGINT, ['notnull' => false]);
$table->setPrimaryKey(['id']);
$this->dropAndCreateTable($table);

$this->connection->executeStatement(<<<SQL
INSERT INTO bigint_type_test (id, my_integer)
VALUES (42, $value)
SQL);

self::assertThat(
$this->connection->convertToPHPValue(
$this->connection->fetchOne('SELECT my_integer from bigint_type_test WHERE id = 42'),
Types::BIGINT,
),
LogicalOr::fromConstraints(new IsIdentical($value), new IsIdentical((string) $value)),
);
}

/** @return Generator<string, array{int}> */
public static function provideBigIntEdgeLiterals(): Generator
{
yield 'max int' => [PHP_INT_MAX];
yield 'min int' => [PHP_INT_MIN];
yield 'large positive number' => [PHP_INT_SIZE === 4 ? '2147483646' : '9223372036854775806', PHP_INT_MAX - 1];
yield 'large negative number' => [PHP_INT_SIZE === 4 ? '-2147483647' : '-9223372036854775807', PHP_INT_MIN + 1];
yield 'largest positive number' => [PHP_INT_SIZE === 4 ? '2147483647' : '9223372036854775807', PHP_INT_MAX];
yield 'largest negative number' => [PHP_INT_SIZE === 4 ? '-2147483648' : '-9223372036854775808', PHP_INT_MIN];
}

public function testUnsignedBigIntOnMySQL(): void
Expand All @@ -104,13 +68,13 @@ public function testUnsignedBigIntOnMySQL(): void
// Insert (2 ** 64) - 1
$this->connection->executeStatement(<<<'SQL'
INSERT INTO bigint_type_test (id, my_integer)
VALUES (42, 0xFFFFFFFFFFFFFFFF)
VALUES (1, 0xFFFFFFFFFFFFFFFF)
SQL);

self::assertSame(
'18446744073709551615',
$this->connection->convertToPHPValue(
$this->connection->fetchOne('SELECT my_integer from bigint_type_test WHERE id = 42'),
$this->connection->fetchOne('SELECT my_integer from bigint_type_test'),
Types::BIGINT,
),
);
Expand Down