diff --git a/.github/workflows/coding-standards.yml b/.github/workflows/coding-standards.yml
index b90ffc7b305..249a050542e 100644
--- a/.github/workflows/coding-standards.yml
+++ b/.github/workflows/coding-standards.yml
@@ -25,4 +25,4 @@ on:
jobs:
coding-standards:
name: "Coding Standards"
- uses: "doctrine/.github/.github/workflows/coding-standards.yml@3.0.0"
+ uses: "doctrine/.github/.github/workflows/coding-standards.yml@3.1.0"
diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml
index 616ca0c795c..78b6cf628d4 100644
--- a/.github/workflows/continuous-integration.yml
+++ b/.github/workflows/continuous-integration.yml
@@ -56,7 +56,7 @@ jobs:
steps:
- name: "Checkout"
- uses: "actions/checkout@v3"
+ uses: "actions/checkout@v4"
with:
fetch-depth: 2
@@ -122,7 +122,7 @@ jobs:
steps:
- name: "Checkout"
- uses: "actions/checkout@v3"
+ uses: "actions/checkout@v4"
with:
fetch-depth: 2
@@ -178,7 +178,7 @@ jobs:
steps:
- name: "Checkout"
- uses: "actions/checkout@v3"
+ uses: "actions/checkout@v4"
with:
fetch-depth: 2
@@ -216,15 +216,16 @@ jobs:
postgres-version:
- "10"
- "15"
+ - "16"
extension:
- "pgsql"
- "pdo_pgsql"
include:
- php-version: "8.2"
- postgres-version: "15"
+ postgres-version: "16"
extension: "pgsql"
- php-version: "8.3"
- postgres-version: "15"
+ postgres-version: "16"
extension: "pdo_pgsql"
services:
@@ -241,7 +242,7 @@ jobs:
steps:
- name: "Checkout"
- uses: "actions/checkout@v3"
+ uses: "actions/checkout@v4"
with:
fetch-depth: 2
@@ -310,7 +311,7 @@ jobs:
steps:
- name: "Checkout"
- uses: "actions/checkout@v3"
+ uses: "actions/checkout@v4"
with:
fetch-depth: 2
@@ -381,7 +382,7 @@ jobs:
steps:
- name: "Checkout"
- uses: "actions/checkout@v3"
+ uses: "actions/checkout@v4"
with:
fetch-depth: 2
@@ -451,7 +452,7 @@ jobs:
steps:
- name: "Checkout"
- uses: "actions/checkout@v3"
+ uses: "actions/checkout@v4"
with:
fetch-depth: 2
@@ -511,7 +512,7 @@ jobs:
run: "docker exec ${{ job.services.ibm_db2.id }} su - db2inst1 -c 'db2 -t CONNECT TO doctrine; db2 -t CREATE USER TEMPORARY TABLESPACE doctrine_tbsp PAGESIZE 4 K;'"
- name: "Checkout"
- uses: "actions/checkout@v3"
+ uses: "actions/checkout@v4"
with:
fetch-depth: 2
@@ -557,7 +558,7 @@ jobs:
steps:
- name: "Checkout"
- uses: "actions/checkout@v3"
+ uses: "actions/checkout@v4"
- name: "Install PHP"
uses: "shivammathur/setup-php@v2"
@@ -590,7 +591,7 @@ jobs:
steps:
- name: "Checkout"
- uses: "actions/checkout@v3"
+ uses: "actions/checkout@v4"
with:
fetch-depth: 2
diff --git a/.github/workflows/release-on-milestone-closed.yml b/.github/workflows/release-on-milestone-closed.yml
index c02383e0257..4c1e99c4c7c 100644
--- a/.github/workflows/release-on-milestone-closed.yml
+++ b/.github/workflows/release-on-milestone-closed.yml
@@ -12,7 +12,7 @@ jobs:
steps:
- name: "Checkout"
- uses: "actions/checkout@v3"
+ uses: "actions/checkout@v4"
- name: "Release"
uses: "laminas/automatic-releases@1.24.0"
diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml
index 409a24d2e2e..9b1999f3f88 100644
--- a/.github/workflows/static-analysis.yml
+++ b/.github/workflows/static-analysis.yml
@@ -38,7 +38,7 @@ jobs:
steps:
- name: "Checkout code"
- uses: "actions/checkout@v3"
+ uses: "actions/checkout@v4"
- name: "Install PHP"
uses: "shivammathur/setup-php@v2"
@@ -64,7 +64,7 @@ jobs:
steps:
- name: Checkout code
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
- name: Install PHP
uses: shivammathur/setup-php@v2
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index c2555ebbd46..31b6eff1f10 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,4 +1,6 @@
+This repository has [guidelines specific to testing][testing guidelines], and
Doctrine has [general contributing guidelines][contributor workflow], make
-sure you follow them.
+sure you follow both.
[contributor workflow]: https://www.doctrine-project.org/contribute/index.html
+[testing guidelines]: https://www.doctrine-project.org/projects/doctrine-dbal/en/stable/reference/testing.html
diff --git a/composer.json b/composer.json
index afc25e3a47f..58306ca4c45 100644
--- a/composer.json
+++ b/composer.json
@@ -41,7 +41,7 @@
"doctrine/coding-standard": "12.0.0",
"fig/log-test": "^1",
"jetbrains/phpstorm-stubs": "2023.2",
- "phpstan/phpstan": "1.10.32",
+ "phpstan/phpstan": "1.10.34",
"phpstan/phpstan-phpunit": "1.3.14",
"phpstan/phpstan-strict-rules": "^1.5",
"phpunit/phpunit": "10.2.2",
diff --git a/docs/en/reference/configuration.rst b/docs/en/reference/configuration.rst
index c159fd087b5..7d6bc9662a9 100644
--- a/docs/en/reference/configuration.rst
+++ b/docs/en/reference/configuration.rst
@@ -330,6 +330,7 @@ pdo_sqlsrv / sqlsrv
- ``host`` (string): Hostname of the database to connect to.
- ``port`` (integer): Port of the database to connect to.
- ``dbname`` (string): Name of the database/schema to connect to.
+- ``driverOptions`` (array): Any supported options found on `https://learn.microsoft.com/en-us/sql/connect/php/connection-options`
ibm_db2
^^^^^^^
diff --git a/docs/en/reference/data-retrieval-and-manipulation.rst b/docs/en/reference/data-retrieval-and-manipulation.rst
index 32f85511526..6e011a32092 100644
--- a/docs/en/reference/data-retrieval-and-manipulation.rst
+++ b/docs/en/reference/data-retrieval-and-manipulation.rst
@@ -253,10 +253,12 @@ SQL injection possibilities if not handled carefully.
Doctrine DBAL implements a very powerful parsing process that will make this kind of prepared
statement possible natively in the binding type system.
The parsing necessarily comes with a performance overhead, but only if you really use a list of parameters.
-There are two special binding types that describe a list of integers or strings:
+There are four special binding types that describe a list of integers, regular, ascii or binary strings:
- ``\Doctrine\DBAL\ArrayParameterType::INTEGER``
- ``\Doctrine\DBAL\ArrayParameterType::STRING``
+- ``\Doctrine\DBAL\ArrayParameterType::ASCII``
+- ``\Doctrine\DBAL\ArrayParameterType::BINARY``
Using one of these constants as a type you can activate the SQLParser inside Doctrine that rewrites
the SQL and flattens the specified values into the set of parameters. Consider our previous example:
@@ -538,4 +540,4 @@ given data.
update('user', ['username' => 'jwage'], ['id' => 1]);
- // UPDATE user (username) VALUES (?) WHERE id = ? (jwage, 1)
+ // UPDATE user SET username = ? WHERE id = ? (jwage, 1)
diff --git a/phpstan.neon.dist b/phpstan.neon.dist
index 4230630f7b7..0e2bf6c434c 100644
--- a/phpstan.neon.dist
+++ b/phpstan.neon.dist
@@ -100,6 +100,11 @@ parameters:
# Ignore the possible false return value of db2_num_rows().
- '~^Method Doctrine\\DBAL\\Driver\\IBMDB2\\Connection\:\:exec\(\) should return int but returns int<0, max>\|false\.$~'
- '~^Method Doctrine\\DBAL\\Driver\\IBMDB2\\Result\:\:rowCount\(\) should return int but returns int<0, max>\|false\.$~'
+
+ # TODO
+ -
+ message: '~^Property Doctrine\\DBAL\\Platforms\\AbstractPlatform\:\:\$disableTypeComments is never read, only written\.$~'
+ path: src/Platforms/AbstractPlatform.php
includes:
- vendor/phpstan/phpstan-phpunit/extension.neon
- vendor/phpstan/phpstan-phpunit/rules.neon
diff --git a/psalm.xml.dist b/psalm.xml.dist
index b8299e81ca1..fcc412e3d24 100644
--- a/psalm.xml.dist
+++ b/psalm.xml.dist
@@ -173,6 +173,9 @@
+
+
+
diff --git a/src/ArrayParameterType.php b/src/ArrayParameterType.php
index 6e3877746d2..851d47d1871 100644
--- a/src/ArrayParameterType.php
+++ b/src/ArrayParameterType.php
@@ -21,6 +21,11 @@ enum ArrayParameterType
*/
case ASCII;
+ /**
+ * Represents an array of ascii strings to be expanded by Doctrine SQL parsing.
+ */
+ case BINARY;
+
/** @internal */
public static function toElementParameterType(self $type): ParameterType
{
@@ -28,6 +33,7 @@ public static function toElementParameterType(self $type): ParameterType
self::INTEGER => ParameterType::INTEGER,
self::STRING => ParameterType::STRING,
self::ASCII => ParameterType::ASCII,
+ self::BINARY => ParameterType::BINARY,
};
}
}
diff --git a/src/Configuration.php b/src/Configuration.php
index 826835b3a24..7c50ce83e30 100644
--- a/src/Configuration.php
+++ b/src/Configuration.php
@@ -33,6 +33,14 @@ class Configuration
*/
protected bool $autoCommit = true;
+ /**
+ * Whether type comments should be disabled to provide the same DB schema than
+ * will be obtained with DBAL 4.x. This is useful when relying only on the
+ * platform-aware schema comparison (which does not need those type comments)
+ * rather than the deprecated legacy tooling.
+ */
+ private bool $disableTypeComments = false;
+
private ?SchemaManagerFactory $schemaManagerFactory = null;
public function __construct()
@@ -132,4 +140,17 @@ public function setSchemaManagerFactory(SchemaManagerFactory $schemaManagerFacto
return $this;
}
+
+ public function getDisableTypeComments(): bool
+ {
+ return $this->disableTypeComments;
+ }
+
+ /** @return $this */
+ public function setDisableTypeComments(bool $disableTypeComments): self
+ {
+ $this->disableTypeComments = $disableTypeComments;
+
+ return $this;
+ }
}
diff --git a/src/Connection.php b/src/Connection.php
index bd788c86af4..52bdf9a4a62 100644
--- a/src/Connection.php
+++ b/src/Connection.php
@@ -188,6 +188,7 @@ public function getDatabasePlatform(): AbstractPlatform
}
$this->platform = $this->driver->getDatabasePlatform($versionProvider);
+ $this->platform->setDisableTypeComments($this->_config->getDisableTypeComments());
}
return $this->platform;
diff --git a/src/Platforms/AbstractPlatform.php b/src/Platforms/AbstractPlatform.php
index 8077dd7c9c6..0eb80c83adf 100644
--- a/src/Platforms/AbstractPlatform.php
+++ b/src/Platforms/AbstractPlatform.php
@@ -79,6 +79,14 @@ abstract class AbstractPlatform
*/
protected ?KeywordList $_keywords = null;
+ private bool $disableTypeComments = false;
+
+ /** @internal */
+ final public function setDisableTypeComments(bool $value): void
+ {
+ $this->disableTypeComments = $value;
+ }
+
/**
* Returns the SQL snippet that declares a boolean column.
*
diff --git a/tests/Connection/ExpandArrayParametersTest.php b/tests/Connection/ExpandArrayParametersTest.php
index 27e7c330c72..36868f6d051 100644
--- a/tests/Connection/ExpandArrayParametersTest.php
+++ b/tests/Connection/ExpandArrayParametersTest.php
@@ -14,6 +14,8 @@
use Doctrine\DBAL\Types\Type;
use PHPUnit\Framework\TestCase;
+use function hex2bin;
+
/** @psalm-import-type WrapperParameterTypeArray from Connection */
class ExpandArrayParametersTest extends TestCase
{
@@ -107,16 +109,24 @@ public static function dataExpandListParameters(): iterable
[1 => ParameterType::STRING, 2 => ParameterType::STRING],
],
'Positional: explicit keys for array params and array types' => [
- 'SELECT * FROM Foo WHERE foo IN (?) AND bar IN (?) AND baz = ? AND bax IN (?)',
- [1 => ['bar1', 'bar2'], 2 => true, 0 => [1, 2, 3], ['bax1', 'bax2']],
+ 'SELECT * FROM Foo WHERE foo IN (?) AND bar IN (?) AND baz = ? AND bax IN (?) AND bay IN (?)',
+ [
+ 1 => ['bar1', 'bar2'],
+ 2 => true,
+ 0 => [1, 2, 3],
+ ['bax1', 'bax2'],
+ 4 => [hex2bin('DEADBEEF'), hex2bin('C0DEF00D')],
+ ],
[
+ 4 => ArrayParameterType::BINARY,
3 => ArrayParameterType::ASCII,
2 => ParameterType::BOOLEAN,
1 => ArrayParameterType::STRING,
0 => ArrayParameterType::INTEGER,
],
- 'SELECT * FROM Foo WHERE foo IN (?, ?, ?) AND bar IN (?, ?) AND baz = ? AND bax IN (?, ?)',
- [1, 2, 3, 'bar1', 'bar2', true, 'bax1', 'bax2'],
+ 'SELECT * FROM Foo WHERE foo IN (?, ?, ?) AND bar IN (?, ?) AND baz = ? AND bax IN (?, ?) ' .
+ 'AND bay IN (?, ?)',
+ [1, 2, 3, 'bar1', 'bar2', true, 'bax1', 'bax2', hex2bin('DEADBEEF'), hex2bin('C0DEF00D')],
[
ParameterType::INTEGER,
ParameterType::INTEGER,
@@ -126,6 +136,8 @@ public static function dataExpandListParameters(): iterable
ParameterType::BOOLEAN,
ParameterType::ASCII,
ParameterType::ASCII,
+ ParameterType::BINARY,
+ ParameterType::BINARY,
],
],
'Named: Very simple with param int' => [
@@ -323,6 +335,22 @@ public static function dataExpandListParameters(): iterable
['foo', 'bar', 'baz'],
[1 => ParameterType::STRING, ParameterType::STRING],
],
+ 'Named: Binary array with explicit types' => [
+ 'SELECT * FROM Foo WHERE foo IN (:foo) OR bar IN (:bar)',
+ [
+ 'foo' => [hex2bin('DEADBEEF'), hex2bin('C0DEF00D')],
+ 'bar' => [hex2bin('DEADBEEF'), hex2bin('C0DEF00D')],
+ ],
+ ['foo' => ArrayParameterType::BINARY, 'bar' => ArrayParameterType::BINARY],
+ 'SELECT * FROM Foo WHERE foo IN (?, ?) OR bar IN (?, ?)',
+ [hex2bin('DEADBEEF'), hex2bin('C0DEF00D'), hex2bin('DEADBEEF'), hex2bin('C0DEF00D')],
+ [
+ ParameterType::BINARY,
+ ParameterType::BINARY,
+ ParameterType::BINARY,
+ ParameterType::BINARY,
+ ],
+ ],
];
}
diff --git a/tests/Functional/BinaryDataAccessTest.php b/tests/Functional/BinaryDataAccessTest.php
new file mode 100644
index 00000000000..50be983860f
--- /dev/null
+++ b/tests/Functional/BinaryDataAccessTest.php
@@ -0,0 +1,354 @@
+addColumn('test_int', 'integer');
+ $table->addColumn('test_binary', 'binary', ['notnull' => false, 'length' => 4]);
+ $table->setPrimaryKey(['test_int']);
+
+ $this->dropAndCreateTable($table);
+
+ $this->connection->insert('binary_fetch_table', [
+ 'test_int' => 1,
+ 'test_binary' => hex2bin('C0DEF00D'),
+ ], [
+ 'test_binary' => ParameterType::BINARY,
+ ]);
+ }
+
+ public function testPrepareWithBindValue(): void
+ {
+ $sql = 'SELECT test_int, test_binary FROM binary_fetch_table WHERE test_int = ? AND test_binary = ?';
+ $stmt = $this->connection->prepare($sql);
+
+ $stmt->bindValue(1, 1);
+ $stmt->bindValue(2, hex2bin('C0DEF00D'), ParameterType::BINARY);
+
+ $row = $stmt->executeQuery()->fetchAssociative();
+
+ self::assertIsArray($row);
+ $row = array_change_key_case($row, CASE_LOWER);
+ self::assertEquals(['test_int', 'test_binary'], array_keys($row));
+ self::assertEquals(1, $row['test_int']);
+
+ $binaryResult = $row['test_binary'];
+ if (is_resource($binaryResult)) {
+ $binaryResult = stream_get_contents($binaryResult);
+ }
+
+ self::assertEquals(hex2bin('C0DEF00D'), $binaryResult);
+ }
+
+ public function testPrepareWithFetchAllAssociative(): void
+ {
+ $paramInt = 1;
+ $paramBin = hex2bin('C0DEF00D');
+
+ $sql = 'SELECT test_int, test_binary FROM binary_fetch_table WHERE test_int = ? AND test_binary = ?';
+ $stmt = $this->connection->prepare($sql);
+
+ $stmt->bindValue(1, $paramInt);
+ $stmt->bindValue(2, $paramBin, ParameterType::BINARY);
+
+ $rows = $stmt->executeQuery()->fetchAllAssociative();
+ $rows[0] = array_change_key_case($rows[0], CASE_LOWER);
+
+ self::assertEquals(['test_int', 'test_binary'], array_keys($rows[0]));
+ self::assertEquals(1, $rows[0]['test_int']);
+
+ $binaryResult = $rows[0]['test_binary'];
+ if (is_resource($binaryResult)) {
+ $binaryResult = stream_get_contents($binaryResult);
+ }
+
+ self::assertEquals(hex2bin('C0DEF00D'), $binaryResult);
+ }
+
+ public function testPrepareWithFetchOne(): void
+ {
+ $paramInt = 1;
+ $paramBin = hex2bin('C0DEF00D');
+
+ $sql = 'SELECT test_int FROM binary_fetch_table WHERE test_int = ? AND test_binary = ?';
+ $stmt = $this->connection->prepare($sql);
+
+ $stmt->bindValue(1, $paramInt);
+ $stmt->bindValue(2, $paramBin, ParameterType::BINARY);
+
+ $column = $stmt->executeQuery()->fetchOne();
+ self::assertEquals(1, $column);
+ }
+
+ public function testFetchAllAssociative(): void
+ {
+ $sql = 'SELECT test_int, test_binary FROM binary_fetch_table WHERE test_int = ? AND test_binary = ?';
+ $data = $this->connection->fetchAllAssociative($sql, [1, hex2bin('C0DEF00D')], [1 => ParameterType::BINARY]);
+
+ self::assertCount(1, $data);
+
+ $row = $data[0];
+ self::assertCount(2, $row);
+
+ $row = array_change_key_case($row, CASE_LOWER);
+ self::assertEquals(1, $row['test_int']);
+
+ $binaryResult = $row['test_binary'];
+ if (is_resource($binaryResult)) {
+ $binaryResult = stream_get_contents($binaryResult);
+ }
+
+ self::assertEquals(hex2bin('C0DEF00D'), $binaryResult);
+ }
+
+ public function testFetchAllWithTypes(): void
+ {
+ $sql = 'SELECT test_int, test_binary FROM binary_fetch_table WHERE test_int = ? AND test_binary = ?';
+ $data = $this->connection->fetchAllAssociative(
+ $sql,
+ [1, hex2bin('C0DEF00D')],
+ [ParameterType::STRING, Types::BINARY],
+ );
+
+ self::assertCount(1, $data);
+
+ $row = $data[0];
+ self::assertCount(2, $row);
+
+ $row = array_change_key_case($row, CASE_LOWER);
+ self::assertEquals(1, $row['test_int']);
+
+ $binaryResult = $row['test_binary'];
+ if (is_resource($binaryResult)) {
+ $binaryResult = stream_get_contents($binaryResult);
+ }
+
+ self::assertEquals(hex2bin('C0DEF00D'), $binaryResult);
+ }
+
+ public function testFetchAssociative(): void
+ {
+ $sql = 'SELECT test_int, test_binary FROM binary_fetch_table WHERE test_int = ? AND test_binary = ?';
+ $row = $this->connection->fetchAssociative($sql, [1, hex2bin('C0DEF00D')], [1 => ParameterType::BINARY]);
+
+ self::assertNotFalse($row);
+
+ $row = array_change_key_case($row, CASE_LOWER);
+
+ self::assertEquals(1, $row['test_int']);
+
+ $binaryResult = $row['test_binary'];
+ if (is_resource($binaryResult)) {
+ $binaryResult = stream_get_contents($binaryResult);
+ }
+
+ self::assertEquals(hex2bin('C0DEF00D'), $binaryResult);
+ }
+
+ public function testFetchAssocWithTypes(): void
+ {
+ $sql = 'SELECT test_int, test_binary FROM binary_fetch_table WHERE test_int = ? AND test_binary = ?';
+ $row = $this->connection->fetchAssociative(
+ $sql,
+ [1, hex2bin('C0DEF00D')],
+ [ParameterType::STRING, Types::BINARY],
+ );
+
+ self::assertNotFalse($row);
+
+ $row = array_change_key_case($row, CASE_LOWER);
+
+ self::assertEquals(1, $row['test_int']);
+
+ $binaryResult = $row['test_binary'];
+ if (is_resource($binaryResult)) {
+ $binaryResult = stream_get_contents($binaryResult);
+ }
+
+ self::assertEquals(hex2bin('C0DEF00D'), $binaryResult);
+ }
+
+ public function testFetchArray(): void
+ {
+ $sql = 'SELECT test_int, test_binary FROM binary_fetch_table WHERE test_int = ? AND test_binary = ?';
+ $row = $this->connection->fetchNumeric($sql, [1, hex2bin('C0DEF00D')], [1 => ParameterType::BINARY]);
+ self::assertNotFalse($row);
+
+ self::assertEquals(1, $row[0]);
+
+ $binaryResult = $row[1];
+ if (is_resource($binaryResult)) {
+ $binaryResult = stream_get_contents($binaryResult);
+ }
+
+ self::assertEquals(hex2bin('C0DEF00D'), $binaryResult);
+ }
+
+ public function testFetchArrayWithTypes(): void
+ {
+ $sql = 'SELECT test_int, test_binary FROM binary_fetch_table WHERE test_int = ? AND test_binary = ?';
+ $row = $this->connection->fetchNumeric(
+ $sql,
+ [1, hex2bin('C0DEF00D')],
+ [ParameterType::STRING, Types::BINARY],
+ );
+
+ self::assertNotFalse($row);
+
+ $row = array_change_key_case($row, CASE_LOWER);
+
+ self::assertEquals(1, $row[0]);
+
+ $binaryResult = $row[1];
+ if (is_resource($binaryResult)) {
+ $binaryResult = stream_get_contents($binaryResult);
+ }
+
+ self::assertEquals(hex2bin('C0DEF00D'), $binaryResult);
+ }
+
+ public function testFetchColumn(): void
+ {
+ $sql = 'SELECT test_int FROM binary_fetch_table WHERE test_int = ? AND test_binary = ?';
+ $testInt = $this->connection->fetchOne($sql, [1, hex2bin('C0DEF00D')], [1 => ParameterType::BINARY]);
+
+ self::assertEquals(1, $testInt);
+
+ $sql = 'SELECT test_binary FROM binary_fetch_table WHERE test_int = ? AND test_binary = ?';
+ $testBinary = $this->connection->fetchOne($sql, [1, hex2bin('C0DEF00D')], [1 => ParameterType::BINARY]);
+
+ if (is_resource($testBinary)) {
+ $testBinary = stream_get_contents($testBinary);
+ }
+
+ self::assertEquals(hex2bin('C0DEF00D'), $testBinary);
+ }
+
+ public function testFetchOneWithTypes(): void
+ {
+ $sql = 'SELECT test_binary FROM binary_fetch_table WHERE test_int = ? AND test_binary = ?';
+ $column = $this->connection->fetchOne(
+ $sql,
+ [1, hex2bin('C0DEF00D')],
+ [ParameterType::STRING, Types::BINARY],
+ );
+
+ if (is_resource($column)) {
+ $column = stream_get_contents($column);
+ }
+
+ self::assertIsString($column);
+ self::assertEquals(hex2bin('C0DEF00D'), $column);
+ }
+
+ public function testNativeArrayListSupport(): void
+ {
+ $binaryValues = [
+ hex2bin('A0AEFA'),
+ hex2bin('1F43BA'),
+ hex2bin('8C9D2A'),
+ hex2bin('72E8AA'),
+ hex2bin('5B6F9A'),
+ hex2bin('DAB24A'),
+ hex2bin('3E71CA'),
+ hex2bin('F0D6EA'),
+ hex2bin('6A8B5A'),
+ hex2bin('C582FA'),
+ ];
+
+ for ($i = 100; $i < 110; $i++) {
+ $this->connection->insert('binary_fetch_table', [
+ 'test_int' => $i,
+ 'test_binary' => $binaryValues[$i - 100],
+ ], [
+ 'test_binary' => ParameterType::BINARY,
+ ]);
+ }
+
+ $result = $this->connection->executeQuery(
+ 'SELECT test_int FROM binary_fetch_table WHERE test_int IN (?)',
+ [[100, 101, 102, 103, 104]],
+ [ArrayParameterType::INTEGER],
+ );
+
+ $data = $result->fetchAllNumeric();
+ self::assertCount(5, $data);
+ self::assertEquals([[100], [101], [102], [103], [104]], $data);
+
+ $result = $this->connection->executeQuery(
+ 'SELECT test_int FROM binary_fetch_table WHERE test_binary IN (?)',
+ [
+ [
+ $binaryValues[0],
+ $binaryValues[1],
+ $binaryValues[2],
+ $binaryValues[3],
+ $binaryValues[4],
+ ],
+ ],
+ [ArrayParameterType::BINARY],
+ );
+
+ $data = $result->fetchAllNumeric();
+ self::assertCount(5, $data);
+ self::assertEquals([[100], [101], [102], [103], [104]], $data);
+
+ $result = $this->connection->executeQuery(
+ 'SELECT test_binary FROM binary_fetch_table WHERE test_binary IN (?)',
+ [
+ [
+ $binaryValues[0],
+ $binaryValues[1],
+ $binaryValues[2],
+ $binaryValues[3],
+ $binaryValues[4],
+ ],
+ ],
+ [ArrayParameterType::BINARY],
+ );
+
+ $data = $result->fetchFirstColumn();
+ self::assertCount(5, $data);
+
+ $data = array_map(
+ static fn ($binaryField) => is_resource($binaryField)
+ ? stream_get_contents($binaryField)
+ : $binaryField,
+ $data,
+ );
+
+ self::assertEquals([
+ $binaryValues[0],
+ $binaryValues[1],
+ $binaryValues[2],
+ $binaryValues[3],
+ $binaryValues[4],
+ ], $data);
+ }
+}
diff --git a/tests/Query/QueryBuilderTest.php b/tests/Query/QueryBuilderTest.php
index 0153f7f4820..723371ca805 100644
--- a/tests/Query/QueryBuilderTest.php
+++ b/tests/Query/QueryBuilderTest.php
@@ -16,6 +16,8 @@
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
+use function hex2bin;
+
/** @psalm-import-type WrapperParameterTypeArray from Connection */
class QueryBuilderTest extends TestCase
{
@@ -814,12 +816,17 @@ public function testArrayParameters(): void
$qb->andWhere('name IN (:names)');
$qb->setParameter('names', ['john', 'jane'], ArrayParameterType::STRING);
+ $qb->andWhere('hash IN (:hashes)');
+ $qb->setParameter('hashes', [hex2bin('DEADBEEF'), hex2bin('C0DEF00D')], ArrayParameterType::BINARY);
+
self::assertSame(ArrayParameterType::INTEGER, $qb->getParameterType('ids'));
self::assertSame(ArrayParameterType::STRING, $qb->getParameterType('names'));
+ self::assertSame(ArrayParameterType::BINARY, $qb->getParameterType('hashes'));
self::assertSame([
'ids' => ArrayParameterType::INTEGER,
'names' => ArrayParameterType::STRING,
+ 'hashes' => ArrayParameterType::BINARY,
], $qb->getParameterTypes());
}