Skip to content

Commit

Permalink
Support VARCHAR binding #4263
Browse files Browse the repository at this point in the history
  • Loading branch information
gjdanis committed Sep 17, 2020
1 parent c70832a commit c6b5fdb
Show file tree
Hide file tree
Showing 16 changed files with 272 additions and 5 deletions.
10 changes: 10 additions & 0 deletions docs/en/reference/types.rst
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,13 @@ or ``null`` if no data is present.
This can lead to type inconsistencies when reverse engineering the
type from the database.

ascii_string
++++++++++++

Similar to the ``string`` type but for binding non-unicode data. This type
should be used with database vendors where a binding type mismatch
can trigger an implicit cast and lead to performance problems.

text
++++

Expand Down Expand Up @@ -583,6 +590,9 @@ Please also notice the mapping specific footnotes for additional information.
| | | | +----------------------------------------------------------+
| | | | | ``NCHAR(n)`` [4]_ |
+-------------------+---------------+--------------------------+---------+----------------------------------------------------------+
| **ascii_string** | ``string`` | **SQL Server** | | ``VARCHAR(n)`` |
| | | | | ``CHAR(n)`` |
+-------------------+---------------+--------------------------+---------+----------------------------------------------------------+
| **text** | ``string`` | **MySQL** | *all* | ``TINYTEXT`` [16]_ |
| | | | +----------------------------------------------------------+
| | | | | ``TEXT`` [17]_ |
Expand Down
1 change: 1 addition & 0 deletions src/Driver/Mysqli/Statement.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ final class Statement implements StatementInterface
{
/** @var string[] */
protected static $_paramTypeMap = [
ParameterType::ASCII => 's',
ParameterType::STRING => 's',
ParameterType::BINARY => 's',
ParameterType::BOOLEAN => 'i',
Expand Down
19 changes: 14 additions & 5 deletions src/Driver/PDO/SQLSrv/Statement.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,20 @@ public function __construct(PDOStatement $statement)
*/
public function bindParam($param, &$variable, $type = ParameterType::STRING, $length = null, $driverOptions = null)
{
if (
($type === ParameterType::LARGE_OBJECT || $type === ParameterType::BINARY)
&& $driverOptions === null
) {
$driverOptions = PDO::SQLSRV_ENCODING_BINARY;
switch ($type) {
case ParameterType::LARGE_OBJECT:
case ParameterType::BINARY:
if ($driverOptions === null) {
$driverOptions = PDO::SQLSRV_ENCODING_BINARY;
}

break;

case ParameterType::ASCII:
$type = PDO::PARAM_STR;
$length = 0;
$driverOptions = PDO::SQLSRV_ENCODING_SYSTEM;
break;
}

return $this->statement->bindParam($param, $variable, $type, $length, $driverOptions);
Expand Down
1 change: 1 addition & 0 deletions src/Driver/PDO/Statement.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ final class Statement implements StatementInterface
ParameterType::NULL => PDO::PARAM_NULL,
ParameterType::INTEGER => PDO::PARAM_INT,
ParameterType::STRING => PDO::PARAM_STR,
ParameterType::ASCII => PDO::PARAM_STR,
ParameterType::BINARY => PDO::PARAM_LOB,
ParameterType::LARGE_OBJECT => PDO::PARAM_LOB,
ParameterType::BOOLEAN => PDO::PARAM_BOOL,
Expand Down
8 changes: 8 additions & 0 deletions src/Driver/SQLSrv/Statement.php
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,14 @@ private function prepare()
];
break;

case ParameterType::ASCII:
$params[$column - 1] = [
&$variable,
SQLSRV_PARAM_IN,
SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR),
];
break;

default:
$params[$column - 1] =& $variable;
break;
Expand Down
5 changes: 5 additions & 0 deletions src/ParameterType.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@ final class ParameterType
*/
public const BINARY = 16;

/**
* Represents an ASCII string data type
*/
public const ASCII = 17;

/**
* This class cannot be instantiated.
*
Expand Down
13 changes: 13 additions & 0 deletions src/Platforms/AbstractPlatform.php
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,19 @@ private function initializeAllDoctrineTypeMappings()
}
}

/**
* Returns the SQL snippet used to declare a column that can
* store characters in the ASCII character set
*
* @param mixed[] $column
*
* @return string
*/
public function getAsciiStringTypeDeclarationSQL(array $column)
{
return $this->getVarcharTypeDeclarationSQL($column);
}

/**
* Returns the SQL snippet used to declare a VARCHAR column type.
*
Expand Down
14 changes: 14 additions & 0 deletions src/Platforms/SQLServer2012Platform.php
Original file line number Diff line number Diff line change
Expand Up @@ -1247,6 +1247,20 @@ public function getDateTimeTzTypeDeclarationSQL(array $column)
return 'DATETIMEOFFSET(6)';
}

/**
* {@inheritDoc}
*/
public function getAsciiStringTypeDeclarationSQL(array $column)
{
$length = $column['length'] ?? null;

if (! isset($column['fixed'])) {
return sprintf('VARCHAR(%d)', $length);
}

return sprintf('CHAR(%d)', $length);
}

/**
* {@inheritDoc}
*/
Expand Down
32 changes: 32 additions & 0 deletions src/Types/AsciiStringType.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

declare(strict_types=1);

namespace Doctrine\DBAL\Types;

use Doctrine\DBAL\ParameterType;
use Doctrine\DBAL\Platforms\AbstractPlatform;

class AsciiStringType extends StringType
{
/**
* {@inheritdoc}
*/
public function getSQLDeclaration(array $column, AbstractPlatform $platform)
{
return $platform->getAsciiStringTypeDeclarationSQL($column);
}

/**
* {@inheritdoc}
*/
public function getBindingType()
{
return ParameterType::ASCII;
}

public function getName(): string
{
return Types::ASCII_STRING;
}
}
1 change: 1 addition & 0 deletions src/Types/Type.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ abstract class Type
*/
private const BUILTIN_TYPES_MAP = [
Types::ARRAY => ArrayType::class,
Types::ASCII_STRING => AsciiStringType::class,
Types::BIGINT => BigIntType::class,
Types::BINARY => BinaryType::class,
Types::BLOB => BlobType::class,
Expand Down
1 change: 1 addition & 0 deletions src/Types/Types.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
final class Types
{
public const ARRAY = 'array';
public const ASCII_STRING = 'ascii_string';
public const BIGINT = 'bigint';
public const BINARY = 'binary';
public const BLOB = 'blob';
Expand Down
26 changes: 26 additions & 0 deletions tests/Functional/ParameterTypes/AsciiTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

declare(strict_types=1);

namespace Doctrine\DBAL\Tests\Functional\ParameterTypes;

use Doctrine\DBAL\Driver\AbstractSQLServerDriver;
use Doctrine\DBAL\ParameterType;
use Doctrine\DBAL\Tests\FunctionalTestCase;

class AsciiTest extends FunctionalTestCase
{
public function testAsciiBinding(): void
{
if (! $this->connection->getDriver() instanceof AbstractSQLServerDriver) {
self::markTestSkipped('Driver does not support ascii string binding');
}

$statement = $this->connection->prepare('SELECT sql_variant_property(?, \'BaseType\')');

$statement->bindValue(1, 'test', ParameterType::ASCII);
$results = $statement->execute()->fetchOne();

self::assertEquals('varchar', $results);
}
}
70 changes: 70 additions & 0 deletions tests/Functional/Types/AsciiStringTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
<?php

declare(strict_types=1);

namespace Doctrine\DBAL\Tests\Functional\Types;

use Doctrine\DBAL\ParameterType;
use Doctrine\DBAL\Schema\Table;
use Doctrine\DBAL\Tests\FunctionalTestCase;

class AsciiStringTest extends FunctionalTestCase
{
protected function setUp(): void
{
parent::setUp();

$table = new Table('ascii_table');
$table->addColumn('id', 'ascii_string', [
'length' => 3,
'fixed' => true,
]);

$table->addColumn('val', 'ascii_string', ['length' => 4]);
$table->setPrimaryKey(['id']);

$sm = $this->connection->getSchemaManager();
$sm->dropAndCreateTable($table);
}

public function testInsertAndSelect(): void
{
$id1 = 'id1';
$id2 = 'id2';

$value1 = 'val1';
$value2 = 'val2';

$this->insert($id1, $value1);
$this->insert($id2, $value2);

self::assertSame($value1, $this->select($id1));
self::assertSame($value2, $this->select($id2));
}

private function insert(string $id, string $value): void
{
$result = $this->connection->insert('ascii_table', [
'id' => $id,
'val' => $value,
], [
ParameterType::ASCII,
ParameterType::ASCII,
]);

self::assertSame(1, $result);
}

private function select(string $id): string
{
$value = $this->connection->fetchOne(
'SELECT val FROM ascii_table WHERE id = ?',
[$id],
[ParameterType::ASCII]
);

self::assertIsString($value);

return $value;
}
}
22 changes: 22 additions & 0 deletions tests/Platforms/AbstractPlatformTestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -1438,6 +1438,28 @@ public function testZeroOffsetWithoutLimitIsIgnored(): void
$this->platform->modifyLimitQuery($query, null, 0)
);
}

/**
* @param array<string, mixed> $column
*
* @dataProvider asciiStringSqlDeclarationDataProvider
*/
public function testAsciiSQLDeclaration(string $expectedSql, array $column): void
{
$declarationSql = $this->platform->getAsciiStringTypeDeclarationSQL($column);
self::assertEquals($expectedSql, $declarationSql);
}

/**
* @return array<int, array{string, array<string, mixed>}>
*/
public function asciiStringSqlDeclarationDataProvider(): array
{
return [
['VARCHAR(12)', ['length' => 12]],
['CHAR(12)', ['length' => 12, 'fixed' => true]],
];
}
}

interface GetCreateTableSqlDispatchEventListener
Expand Down
11 changes: 11 additions & 0 deletions tests/Platforms/OraclePlatformTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -976,4 +976,15 @@ public function testQuotesDatabaseNameInListTableColumnsSQL(): void
$this->platform->getListTableColumnsSQL('foo_table', "Foo'Bar\\")
);
}

/**
* @return array<int, array{string, array<string, mixed>}>
*/
public function asciiStringSqlDeclarationDataProvider(): array
{
return [
['VARCHAR2(12)', ['length' => 12]],
['CHAR(12)', ['length' => 12, 'fixed' => true]],
];
}
}
43 changes: 43 additions & 0 deletions tests/Types/AsciiStringTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?php

declare(strict_types=1);

namespace Doctrine\DBAL\Tests\Types;

use Doctrine\DBAL\ParameterType;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Types\AsciiStringType;
use PHPUnit\Framework\TestCase;

class AsciiStringTest extends TestCase
{
/** @var AsciiStringType */
private $type;

protected function setUp(): void
{
$this->type = new AsciiStringType();
}

public function testReturnCorrectBindingType(): void
{
self::assertEquals($this->type->getBindingType(), ParameterType::ASCII);
}

public function testDelegateToPlatformForSqlDeclaration(): void
{
$columnDefinitions = [
[['length' => 12, 'fixed' => true]],
[['length' => 14]],
];

foreach ($columnDefinitions as $column) {
$platform = $this->createMock(AbstractPlatform::class);
$platform->expects(self::once())
->method('getAsciiStringTypeDeclarationSQL')
->with($column);

$this->type->getSQLDeclaration($column, $platform);
}
}
}

0 comments on commit c6b5fdb

Please sign in to comment.