Skip to content

Commit 20a03e6

Browse files
committed
Store binary values natively for MSSQL
1 parent 91b4ba9 commit 20a03e6

6 files changed

+25
-26
lines changed

phpstan.neon.dist

+2-2
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,10 @@ parameters:
2828
# for Doctrine DBAL 2.x, remove the support once Doctrine ORM 2.10 is released
2929
# see https://github.com/doctrine/orm/issues/8526
3030
-
31-
message: '~^(Call to an undefined method Doctrine\\DBAL\\Driver\\Connection::getWrappedConnection\(\)\.|Caught class Doctrine\\DBAL\\DBALException not found\.|Call to static method notSupported\(\) on an unknown class Doctrine\\DBAL\\DBALException\.|Access to an undefined property Doctrine\\DBAL\\Driver\\PDO\\Connection::\$connection\.|Method Atk4\\Data\\Persistence\\Sql\\Expression::execute\(\) should return Doctrine\\DBAL\\Result\|PDOStatement but returns bool\.|Class Doctrine\\DBAL\\Platforms\\MySqlPlatform referenced with incorrect case: Doctrine\\DBAL\\Platforms\\MySQLPlatform\.)$~'
31+
message: '~^(Call to an undefined method Doctrine\\DBAL\\Driver\\Connection::getWrappedConnection\(\)\.|Caught class Doctrine\\DBAL\\DBALException not found\.|Call to static method notSupported\(\) on an unknown class Doctrine\\DBAL\\DBALException\.|Access to an undefined property Doctrine\\DBAL\\Driver\\PDO\\Connection::\$connection\.|Parameter #1 \$dsn of class Doctrine\\DBAL\\Driver\\PDO\\SQLSrv\\Connection constructor expects string, Doctrine\\DBAL\\Driver\\PDO\\Connection given\.|Method Atk4\\Data\\Persistence\\Sql\\Expression::execute\(\) should return Doctrine\\DBAL\\Result\|PDOStatement but returns bool\.|Class Doctrine\\DBAL\\Platforms\\MySqlPlatform referenced with incorrect case: Doctrine\\DBAL\\Platforms\\MySQLPlatform\.)$~'
3232
path: '*'
3333
# count for DBAL 3.x matched in "src/Persistence/GenericPlatform.php" file
34-
count: 11
34+
count: 12
3535

3636
# TODO these rules are generated, this ignores should be fixed in the code
3737
# for src/Schema/TestCase.php

src/Persistence/GenericPlatform.php

+1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ private function createNotSupportedException(): \Exception // DbalException once
2222
\Doctrine\DBAL\DBALException::notSupported('SQL');
2323
\Doctrine\DBAL\DBALException::notSupported('SQL');
2424
\Doctrine\DBAL\DBALException::notSupported('SQL');
25+
\Doctrine\DBAL\DBALException::notSupported('SQL');
2526
}
2627

2728
return \Doctrine\DBAL\DBALException::notSupported('SQL');

src/Persistence/Sql/BinaryTypeCompatibilityTypecastTrait.php

+11-5
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,18 @@ private function binaryTypeValueDecode(string $value): string
4242
throw new Exception('Unexpected binary value crc');
4343
}
4444

45-
return hex2bin($hex);
45+
$res = hex2bin($hex);
46+
if ($this->binaryTypeValueIsEncoded($res)) {
47+
throw new Exception('Unexpected double encoded binary value');
48+
}
49+
50+
return $res;
4651
}
4752

4853
private function binaryTypeIsEncodeNeeded(Type $type): bool
4954
{
50-
// values for PostgreSQL are stored natively, but we need to encode first
51-
// to hold the binary type info for PDO parameter type binding
55+
// binary values for PostgreSQL and MSSQL databases are stored natively, but we need
56+
// to encode first to hold the binary type info for PDO parameter type binding
5257

5358
$platform = $this->getDatabasePlatform();
5459
if ($platform instanceof PostgreSQL94Platform
@@ -78,8 +83,9 @@ public function typecastLoadField(Field $field, $value)
7883
$value = parent::typecastLoadField($field, $value);
7984

8085
if ($value !== null && $this->binaryTypeIsEncodeNeeded($field->getTypeObject())) {
81-
if (!$this->getDatabasePlatform() instanceof PostgreSQL94Platform
82-
|| $this->binaryTypeValueIsEncoded($value)) {
86+
// always decode for Oracle platform to assert the value is really encoded
87+
// for other platforms, binary values are stored natively
88+
if ($this->getDatabasePlatform() instanceof OraclePlatform || $this->binaryTypeValueIsEncoded($value)) {
8389
$value = $this->binaryTypeValueDecode($value);
8490
}
8591
}

src/Persistence/Sql/Expression.php

+3-1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
use Doctrine\DBAL\ParameterType;
1212
use Doctrine\DBAL\Platforms\OraclePlatform;
1313
use Doctrine\DBAL\Platforms\PostgreSQL94Platform;
14+
use Doctrine\DBAL\Platforms\SQLServer2012Platform;
1415
use Doctrine\DBAL\Result as DbalResult;
1516

1617
/**
@@ -530,7 +531,8 @@ public function execute(object $connection = null): object
530531
} elseif (is_string($val)) {
531532
$type = ParameterType::STRING;
532533

533-
if ($this->connection->getDatabasePlatform() instanceof PostgreSQL94Platform) {
534+
if ($this->connection->getDatabasePlatform() instanceof PostgreSQL94Platform
535+
|| $this->connection->getDatabasePlatform() instanceof SQLServer2012Platform) {
534536
$dummySqlPersistence = new SqlPersistence($this->connection);
535537
if (\Closure::bind(fn () => $dummySqlPersistence->binaryTypeValueIsEncoded($val), null, SqlPersistence::class)()) {
536538
$val = \Closure::bind(fn () => $dummySqlPersistence->binaryTypeValueDecode($val), null, SqlPersistence::class)();

src/Persistence/Sql/Mssql/PlatformTrait.php

-15
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,6 @@
66

77
trait PlatformTrait
88
{
9-
// SQL Server database requires explicit conversion when using binary column,
10-
// workaround by using a standard non-binary column with custom encoding/typecast
11-
12-
protected function getBinaryTypeDeclarationSQLSnippet($length, $fixed)
13-
{
14-
return $this->getVarcharTypeDeclarationSQLSnippet($length, $fixed);
15-
}
16-
17-
public function getBlobTypeDeclarationSQL(array $column)
18-
{
19-
return $this->getClobTypeDeclarationSQL($column);
20-
}
21-
229
// remove once https://github.com/doctrine/dbal/pull/4987 is fixed
2310
// and also $this->markDoctrineTypeCommented('text') below
2411
public function getClobTypeDeclarationSQL(array $column)
@@ -32,8 +19,6 @@ protected function initializeCommentedDoctrineTypes()
3219
{
3320
parent::initializeCommentedDoctrineTypes();
3421

35-
$this->markDoctrineTypeCommented('binary');
36-
$this->markDoctrineTypeCommented('blob');
3722
$this->markDoctrineTypeCommented('text');
3823
}
3924

tests/Schema/ModelTest.php

+8-3
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use Atk4\Data\Model;
88
use Atk4\Data\Schema\TestCase;
99
use Doctrine\DBAL\Platforms\OraclePlatform;
10+
use Doctrine\DBAL\Platforms\SQLServer2012Platform;
1011

1112
class ModelTest extends TestCase
1213
{
@@ -186,7 +187,7 @@ private function makePseudoRandomString(bool $isBinary, int $lengthBytes): strin
186187
public function testCharacterTypeFieldLong(string $type, bool $isBinary, int $lengthBytes): void
187188
{
188189
if ($this->getDatabasePlatform() instanceof OraclePlatform) {
189-
$lengthBytes = min($lengthBytes, 500);
190+
$lengthBytes = min($lengthBytes, $type === 'binary' ? 100 : 500);
190191
}
191192

192193
$str = $this->makePseudoRandomString($isBinary, $lengthBytes);
@@ -200,7 +201,11 @@ public function testCharacterTypeFieldLong(string $type, bool $isBinary, int $le
200201

201202
$this->createMigrator($model)->create();
202203

203-
$model->import([['v' => $str . ($isBinary ? "\0" : '.')]]);
204+
$model->import([['v' => $str . (
205+
// MSSQL darabase ignores trailing \0 characters even with binary comparison
206+
// https://dba.stackexchange.com/questions/48660/comparing-binary-0x-and-0x00-turns-out-to-be-equal-on-sql-server
207+
$isBinary ? $this->getDatabasePlatform() instanceof SQLServer2012Platform ? ' ' : "\0" : '.'
208+
)]]);
204209
$model->import([['v' => $str]]);
205210

206211
$model->addCondition('v', $str);
@@ -218,7 +223,7 @@ public function providerCharacterTypeFieldLongData(): array
218223
{
219224
return [
220225
['string', false, 250],
221-
['binary', true, 100],
226+
['binary', true, 250],
222227
['text', false, 256 * 1024],
223228
['blob', true, 256 * 1024],
224229
];

0 commit comments

Comments
 (0)