diff --git a/composer.json b/composer.json index 970c573849f..596ec02eec8 100644 --- a/composer.json +++ b/composer.json @@ -40,7 +40,7 @@ "doctrine/coding-standard": "12.0.0", "fig/log-test": "^1", "jetbrains/phpstorm-stubs": "2023.2", - "phpstan/phpstan": "1.11.10", + "phpstan/phpstan": "1.12.0", "phpstan/phpstan-phpunit": "1.4.0", "phpstan/phpstan-strict-rules": "^1.6", "phpunit/phpunit": "10.5.30", diff --git a/src/Cache/ArrayResult.php b/src/Cache/ArrayResult.php index e929d7f74de..16878c37778 100644 --- a/src/Cache/ArrayResult.php +++ b/src/Cache/ArrayResult.php @@ -88,11 +88,8 @@ public function columnCount(): int public function getColumnName(int $index): string { - if ($this->data === [] || $index > count($this->data[0])) { - throw InvalidColumnIndex::new($index); - } - - return array_keys($this->data[0])[$index]; + return array_keys($this->data[0] ?? [])[$index] + ?? throw InvalidColumnIndex::new($index); } public function free(): void diff --git a/src/Driver/AbstractMySQLDriver.php b/src/Driver/AbstractMySQLDriver.php index b265cc9b230..2db47844974 100644 --- a/src/Driver/AbstractMySQLDriver.php +++ b/src/Driver/AbstractMySQLDriver.php @@ -73,7 +73,7 @@ private function getMariaDbMysqlVersionNumber(string $versionString): string '/^(?:5\.5\.5-)?(mariadb-)?(?P\d+)\.(?P\d+)\.(?P\d+)/i', $versionString, $versionParts, - ) === 0 + ) !== 1 ) { throw InvalidPlatformVersion::new( $versionString, diff --git a/src/Driver/PDO/Result.php b/src/Driver/PDO/Result.php index 807409bead8..5016fb4a5dd 100644 --- a/src/Driver/PDO/Result.php +++ b/src/Driver/PDO/Result.php @@ -9,6 +9,7 @@ use PDO; use PDOException; use PDOStatement; +use ValueError; final class Result implements ResultInterface { @@ -78,15 +79,17 @@ public function getColumnName(int $index): string { try { $meta = $this->statement->getColumnMeta($index); - - if ($meta === false) { - throw InvalidColumnIndex::new($index); - } - - return $meta['name']; + } catch (ValueError $exception) { + throw InvalidColumnIndex::new($index, $exception); } catch (PDOException $exception) { throw Exception::new($exception); } + + if ($meta === false) { + throw InvalidColumnIndex::new($index); + } + + return $meta['name']; } public function free(): void diff --git a/src/Exception/InvalidColumnIndex.php b/src/Exception/InvalidColumnIndex.php index 43c9a0f5918..27342dad8f4 100644 --- a/src/Exception/InvalidColumnIndex.php +++ b/src/Exception/InvalidColumnIndex.php @@ -6,14 +6,15 @@ use Doctrine\DBAL\Exception; use LogicException; +use Throwable; use function sprintf; /** @psalm-immutable */ final class InvalidColumnIndex extends LogicException implements Exception { - public static function new(int $index): self + public static function new(int $index, ?Throwable $previous = null): self { - return new self(sprintf('Invalid column index "%s".', $index)); + return new self(sprintf('Invalid column index "%s".', $index), previous: $previous); } } diff --git a/src/Schema/SQLiteSchemaManager.php b/src/Schema/SQLiteSchemaManager.php index 8e3a0a11723..86f2cbe99aa 100644 --- a/src/Schema/SQLiteSchemaManager.php +++ b/src/Schema/SQLiteSchemaManager.php @@ -221,19 +221,22 @@ protected function _getPortableTableColumnList(string $table, string $database, */ protected function _getPortableTableColumnDefinition(array $tableColumn): Column { - preg_match('/^([^()]*)\\s*(\\(((\\d+)(,\\s*(\\d+))?)\\))?/', $tableColumn['type'], $matches); + $matchResult = preg_match('/^([^()]*)\\s*(\\(((\\d+)(,\\s*(\\d+))?)\\))?/', $tableColumn['type'], $matches); + assert($matchResult === 1); $dbType = trim(strtolower($matches[1])); - $length = $precision = $unsigned = null; + $length = $precision = null; $fixed = $unsigned = false; $scale = 0; - if (count($matches) >= 6) { - $precision = (int) $matches[4]; - $scale = (int) $matches[6]; - } elseif (count($matches) >= 4) { - $length = (int) $matches[4]; + if (isset($matches[4])) { + if (isset($matches[6])) { + $precision = (int) $matches[4]; + $scale = (int) $matches[6]; + } else { + $length = (int) $matches[4]; + } } if (str_contains($dbType, ' unsigned')) { diff --git a/tests/Cache/ArrayStatementTest.php b/tests/Cache/ArrayStatementTest.php index 361e8663429..19ec81cea77 100644 --- a/tests/Cache/ArrayStatementTest.php +++ b/tests/Cache/ArrayStatementTest.php @@ -5,6 +5,8 @@ namespace Doctrine\DBAL\Tests\Cache; use Doctrine\DBAL\Cache\ArrayResult; +use Doctrine\DBAL\Exception\InvalidColumnIndex; +use PHPUnit\Framework\Attributes\TestWith; use PHPUnit\Framework\TestCase; use function array_values; @@ -49,6 +51,16 @@ public function testColumnNames(): void self::assertSame('active', $statement->getColumnName(1)); } + #[TestWith([2])] + #[TestWith([-1])] + public function testColumnNameWithInvalidIndex(int $index): void + { + $statement = $this->createTestArrayStatement(); + $this->expectException(InvalidColumnIndex::class); + + $statement->getColumnName($index); + } + public function testRowCount(): void { $statement = $this->createTestArrayStatement(); diff --git a/tests/Connection/ExpandArrayParametersTest.php b/tests/Connection/ExpandArrayParametersTest.php index 0ffcd6be024..f9c00f7dd2d 100644 --- a/tests/Connection/ExpandArrayParametersTest.php +++ b/tests/Connection/ExpandArrayParametersTest.php @@ -32,325 +32,353 @@ class ExpandArrayParametersTest extends TestCase */ public static function dataExpandListParameters(): iterable { - return [ - 'Positional: Very simple with one needle' => [ - 'SELECT * FROM Foo WHERE foo IN (?)', - [[1, 2, 3]], - [ArrayParameterType::INTEGER], - 'SELECT * FROM Foo WHERE foo IN (?, ?, ?)', - [1, 2, 3], - [ParameterType::INTEGER, ParameterType::INTEGER, ParameterType::INTEGER], - ], - 'Positional: One non-list before d one after list-needle' => [ - 'SELECT * FROM Foo WHERE foo = ? AND bar IN (?)', - ['string', [1, 2, 3]], - [ParameterType::STRING, ArrayParameterType::INTEGER], - 'SELECT * FROM Foo WHERE foo = ? AND bar IN (?, ?, ?)', - ['string', 1, 2, 3], - [ParameterType::STRING, ParameterType::INTEGER, ParameterType::INTEGER, ParameterType::INTEGER], - ], - 'Positional: One non-list after list-needle' => [ - 'SELECT * FROM Foo WHERE bar IN (?) AND baz = ?', - [[1, 2, 3], 'foo'], - [ArrayParameterType::INTEGER, ParameterType::STRING], - 'SELECT * FROM Foo WHERE bar IN (?, ?, ?) AND baz = ?', - [1, 2, 3, 'foo'], - [ParameterType::INTEGER, ParameterType::INTEGER, ParameterType::INTEGER, ParameterType::STRING], - ], - 'Positional: One non-list before and one after list-needle' => [ - 'SELECT * FROM Foo WHERE foo = ? AND bar IN (?) AND baz = ?', - [1, [1, 2, 3], 4], - [ParameterType::INTEGER, ArrayParameterType::INTEGER, ParameterType::INTEGER], - 'SELECT * FROM Foo WHERE foo = ? AND bar IN (?, ?, ?) AND baz = ?', - [1, 1, 2, 3, 4], - [ - ParameterType::INTEGER, - ParameterType::INTEGER, - ParameterType::INTEGER, - ParameterType::INTEGER, - ParameterType::INTEGER, - ], - ], - 'Positional: Two lists' => [ - 'SELECT * FROM Foo WHERE foo IN (?, ?)', - [[1, 2, 3], [4, 5]], - [ArrayParameterType::INTEGER, ArrayParameterType::INTEGER], - 'SELECT * FROM Foo WHERE foo IN (?, ?, ?, ?, ?)', - [1, 2, 3, 4, 5], - [ - ParameterType::INTEGER, - ParameterType::INTEGER, - ParameterType::INTEGER, - ParameterType::INTEGER, - ParameterType::INTEGER, - ], - ], - 'Positional: Empty "integer" array (DDC-1978)' => [ - 'SELECT * FROM Foo WHERE foo IN (?)', - [[]], - [ArrayParameterType::INTEGER], - 'SELECT * FROM Foo WHERE foo IN (NULL)', - [], - [], - ], - 'Positional: Empty "str" array (DDC-1978)' => [ - 'SELECT * FROM Foo WHERE foo IN (?)', - [[]], - [ArrayParameterType::STRING], - 'SELECT * FROM Foo WHERE foo IN (NULL)', - [], - [], - ], - 'Positional: explicit keys for params and types' => [ - 'SELECT * FROM Foo WHERE foo = ? AND bar = ? AND baz = ?', - [1 => 'bar', 2 => 'baz', 0 => 1], - [2 => ParameterType::STRING, 1 => ParameterType::STRING], - 'SELECT * FROM Foo WHERE foo = ? AND bar = ? AND baz = ?', - [1 => 'bar', 0 => 1, 2 => 'baz'], - [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 (?) 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 (?, ?) ' . - 'AND bay IN (?, ?)', - [1, 2, 3, 'bar1', 'bar2', true, 'bax1', 'bax2', hex2bin('DEADBEEF'), hex2bin('C0DEF00D')], - [ - ParameterType::INTEGER, - ParameterType::INTEGER, - ParameterType::INTEGER, - ParameterType::STRING, - ParameterType::STRING, - ParameterType::BOOLEAN, - ParameterType::ASCII, - ParameterType::ASCII, - ParameterType::BINARY, - ParameterType::BINARY, - ], - ], - 'Named: Very simple with param int' => [ - 'SELECT * FROM Foo WHERE foo = :foo', - ['foo' => 1], - ['foo' => ParameterType::INTEGER], - 'SELECT * FROM Foo WHERE foo = ?', - [1], - [ParameterType::INTEGER], - ], - 'Named: Very simple with param int and string' => [ - 'SELECT * FROM Foo WHERE foo = :foo AND bar = :bar', - ['bar' => 'Some String','foo' => 1], - ['foo' => ParameterType::INTEGER, 'bar' => ParameterType::STRING], - 'SELECT * FROM Foo WHERE foo = ? AND bar = ?', - [1,'Some String'], - [ParameterType::INTEGER, ParameterType::STRING], - ], - 'Named: Very simple with one needle' => [ - 'SELECT * FROM Foo WHERE foo IN (:foo)', - ['foo' => [1, 2, 3]], - ['foo' => ArrayParameterType::INTEGER], - 'SELECT * FROM Foo WHERE foo IN (?, ?, ?)', - [1, 2, 3], - [ParameterType::INTEGER, ParameterType::INTEGER, ParameterType::INTEGER], - ], - 'Named: One non-list before d one after list-needle' => [ - 'SELECT * FROM Foo WHERE foo = :foo AND bar IN (:bar)', - ['foo' => 'string', 'bar' => [1, 2, 3]], - ['foo' => ParameterType::STRING, 'bar' => ArrayParameterType::INTEGER], - 'SELECT * FROM Foo WHERE foo = ? AND bar IN (?, ?, ?)', - ['string', 1, 2, 3], - [ParameterType::STRING, ParameterType::INTEGER, ParameterType::INTEGER, ParameterType::INTEGER], - ], - 'Named: One non-list after list-needle' => [ - 'SELECT * FROM Foo WHERE bar IN (:bar) AND baz = :baz', - ['bar' => [1, 2, 3], 'baz' => 'foo'], - ['bar' => ArrayParameterType::INTEGER, 'baz' => ParameterType::STRING], - 'SELECT * FROM Foo WHERE bar IN (?, ?, ?) AND baz = ?', - [1, 2, 3, 'foo'], - [ParameterType::INTEGER, ParameterType::INTEGER, ParameterType::INTEGER, ParameterType::STRING], - ], - 'Named: One non-list before and one after list-needle' => [ - 'SELECT * FROM Foo WHERE foo = :foo AND bar IN (:bar) AND baz = :baz', - ['bar' => [1, 2, 3],'foo' => 1, 'baz' => 4], - [ - 'bar' => ArrayParameterType::INTEGER, - 'foo' => ParameterType::INTEGER, - 'baz' => ParameterType::INTEGER, - ], - 'SELECT * FROM Foo WHERE foo = ? AND bar IN (?, ?, ?) AND baz = ?', - [1, 1, 2, 3, 4], - [ - ParameterType::INTEGER, - ParameterType::INTEGER, - ParameterType::INTEGER, - ParameterType::INTEGER, - ParameterType::INTEGER, - ], - ], - 'Named: Two lists' => [ - 'SELECT * FROM Foo WHERE foo IN (:a, :b)', - ['b' => [4, 5],'a' => [1, 2, 3]], - ['a' => ArrayParameterType::INTEGER, 'b' => ArrayParameterType::INTEGER], - 'SELECT * FROM Foo WHERE foo IN (?, ?, ?, ?, ?)', - [1, 2, 3, 4, 5], - [ - ParameterType::INTEGER, - ParameterType::INTEGER, - ParameterType::INTEGER, - ParameterType::INTEGER, - ParameterType::INTEGER, - ], - ], - 'Named: With the same name arg type string' => [ - 'SELECT * FROM Foo WHERE foo <> :arg AND bar = :arg', - ['arg' => 'Some String'], - ['arg' => ParameterType::STRING], - 'SELECT * FROM Foo WHERE foo <> ? AND bar = ?', - ['Some String','Some String'], - [ParameterType::STRING,ParameterType::STRING], - ], - 'Named: With the same name arg' => [ - 'SELECT * FROM Foo WHERE foo IN (:arg) AND NOT bar IN (:arg)', - ['arg' => [1, 2, 3]], - ['arg' => ArrayParameterType::INTEGER], - 'SELECT * FROM Foo WHERE foo IN (?, ?, ?) AND NOT bar IN (?, ?, ?)', - [1, 2, 3, 1, 2, 3], - [ - ParameterType::INTEGER, - ParameterType::INTEGER, - ParameterType::INTEGER, - ParameterType::INTEGER, - ParameterType::INTEGER, - ParameterType::INTEGER, - ], - ], - 'Named: Same name, other name in between (DBAL-299)' => [ - 'SELECT * FROM Foo WHERE (:foo = 2) AND (:bar = 3) AND (:foo = 2)', - ['foo' => 2,'bar' => 3], - ['foo' => ParameterType::INTEGER,'bar' => ParameterType::INTEGER], - 'SELECT * FROM Foo WHERE (? = 2) AND (? = 3) AND (? = 2)', - [2, 3, 2], - [ParameterType::INTEGER, ParameterType::INTEGER, ParameterType::INTEGER], - ], - 'Named: Empty "integer" array (DDC-1978)' => [ - 'SELECT * FROM Foo WHERE foo IN (:foo)', - ['foo' => []], - ['foo' => ArrayParameterType::INTEGER], - 'SELECT * FROM Foo WHERE foo IN (NULL)', - [], - [], - ], - 'Named: Two empty "str" array (DDC-1978)' => [ - 'SELECT * FROM Foo WHERE foo IN (:foo) OR bar IN (:bar)', - ['foo' => [], 'bar' => []], - ['foo' => ArrayParameterType::STRING, 'bar' => ArrayParameterType::STRING], - 'SELECT * FROM Foo WHERE foo IN (NULL) OR bar IN (NULL)', - [], - [], + yield 'Positional: Very simple with one needle' => [ + 'SELECT * FROM Foo WHERE foo IN (?)', + [[1, 2, 3]], + [ArrayParameterType::INTEGER], + 'SELECT * FROM Foo WHERE foo IN (?, ?, ?)', + [1, 2, 3], + [ParameterType::INTEGER, ParameterType::INTEGER, ParameterType::INTEGER], + ]; + + yield 'Positional: One non-list before d one after list-needle' => [ + 'SELECT * FROM Foo WHERE foo = ? AND bar IN (?)', + ['string', [1, 2, 3]], + [ParameterType::STRING, ArrayParameterType::INTEGER], + 'SELECT * FROM Foo WHERE foo = ? AND bar IN (?, ?, ?)', + ['string', 1, 2, 3], + [ParameterType::STRING, ParameterType::INTEGER, ParameterType::INTEGER, ParameterType::INTEGER], + ]; + + yield 'Positional: One non-list after list-needle' => [ + 'SELECT * FROM Foo WHERE bar IN (?) AND baz = ?', + [[1, 2, 3], 'foo'], + [ArrayParameterType::INTEGER, ParameterType::STRING], + 'SELECT * FROM Foo WHERE bar IN (?, ?, ?) AND baz = ?', + [1, 2, 3, 'foo'], + [ParameterType::INTEGER, ParameterType::INTEGER, ParameterType::INTEGER, ParameterType::STRING], + ]; + + yield 'Positional: One non-list before and one after list-needle' => [ + 'SELECT * FROM Foo WHERE foo = ? AND bar IN (?) AND baz = ?', + [1, [1, 2, 3], 4], + [ParameterType::INTEGER, ArrayParameterType::INTEGER, ParameterType::INTEGER], + 'SELECT * FROM Foo WHERE foo = ? AND bar IN (?, ?, ?) AND baz = ?', + [1, 1, 2, 3, 4], + [ + ParameterType::INTEGER, + ParameterType::INTEGER, + ParameterType::INTEGER, + ParameterType::INTEGER, + ParameterType::INTEGER, ], + ]; + + yield 'Positional: Two lists' => [ + 'SELECT * FROM Foo WHERE foo IN (?, ?)', + [[1, 2, 3], [4, 5]], + [ArrayParameterType::INTEGER, ArrayParameterType::INTEGER], + 'SELECT * FROM Foo WHERE foo IN (?, ?, ?, ?, ?)', + [1, 2, 3, 4, 5], [ - 'SELECT * FROM Foo WHERE foo IN (:foo) OR bar IN (:bar)', - ['foo' => [], 'bar' => []], - ['foo' => ArrayParameterType::ASCII, 'bar' => ArrayParameterType::ASCII], - 'SELECT * FROM Foo WHERE foo IN (NULL) OR bar IN (NULL)', - [], - [], + ParameterType::INTEGER, + ParameterType::INTEGER, + ParameterType::INTEGER, + ParameterType::INTEGER, + ParameterType::INTEGER, ], + ]; + + yield 'Positional: Empty "integer" array (DDC-1978)' => [ + 'SELECT * FROM Foo WHERE foo IN (?)', + [[]], + [ArrayParameterType::INTEGER], + 'SELECT * FROM Foo WHERE foo IN (NULL)', + [], + [], + ]; + + yield 'Positional: Empty "str" array (DDC-1978)' => [ + 'SELECT * FROM Foo WHERE foo IN (?)', + [[]], + [ArrayParameterType::STRING], + 'SELECT * FROM Foo WHERE foo IN (NULL)', + [], + [], + ]; + + yield 'Positional: explicit keys for params and types' => [ + 'SELECT * FROM Foo WHERE foo = ? AND bar = ? AND baz = ?', + [1 => 'bar', 2 => 'baz', 0 => 1], + [2 => ParameterType::STRING, 1 => ParameterType::STRING], + 'SELECT * FROM Foo WHERE foo = ? AND bar = ? AND baz = ?', + [1 => 'bar', 0 => 1, 2 => 'baz'], + [1 => ParameterType::STRING, 2 => ParameterType::STRING], + ]; + + yield 'Positional: explicit keys for array params and array types' => [ + 'SELECT * FROM Foo WHERE foo IN (?) AND bar IN (?) AND baz = ? AND bax IN (?) AND bay IN (?)', [ - 'SELECT * FROM Foo WHERE foo IN (:foo) OR bar = :bar OR baz = :baz', - ['foo' => [1, 2], 'bar' => 'bar', 'baz' => 'baz'], - ['foo' => ArrayParameterType::INTEGER, 'baz' => 'string'], - 'SELECT * FROM Foo WHERE foo IN (?, ?) OR bar = ? OR baz = ?', - [1, 2, 'bar', 'baz'], - [ - 0 => ParameterType::INTEGER, - 1 => ParameterType::INTEGER, - 3 => 'string', - ], + 1 => ['bar1', 'bar2'], + 2 => true, + 0 => [1, 2, 3], + ['bax1', 'bax2'], + 4 => [hex2bin('DEADBEEF'), hex2bin('C0DEF00D')], ], [ - 'SELECT * FROM Foo WHERE foo IN (:foo) OR bar = :bar', - ['foo' => [1, 2], 'bar' => 'bar'], - ['foo' => ArrayParameterType::INTEGER], - 'SELECT * FROM Foo WHERE foo IN (?, ?) OR bar = ?', - [1, 2, 'bar'], - [ParameterType::INTEGER, ParameterType::INTEGER], + 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 (?, ?) ' . + 'AND bay IN (?, ?)', + [1, 2, 3, 'bar1', 'bar2', true, 'bax1', 'bax2', hex2bin('DEADBEEF'), hex2bin('C0DEF00D')], + [ + ParameterType::INTEGER, + ParameterType::INTEGER, + ParameterType::INTEGER, + ParameterType::STRING, + ParameterType::STRING, + ParameterType::BOOLEAN, + ParameterType::ASCII, + ParameterType::ASCII, + ParameterType::BINARY, + ParameterType::BINARY, ], - 'Named parameters and partially implicit types' => [ - 'SELECT * FROM Foo WHERE foo = :foo OR bar = :bar', - ['foo' => 'foo', 'bar' => 'bar'], - ['foo' => ParameterType::INTEGER], - 'SELECT * FROM Foo WHERE foo = ? OR bar = ?', - ['foo', 'bar'], - [ParameterType::INTEGER], + ]; + + yield 'Named: Very simple with param int' => [ + 'SELECT * FROM Foo WHERE foo = :foo', + ['foo' => 1], + ['foo' => ParameterType::INTEGER], + 'SELECT * FROM Foo WHERE foo = ?', + [1], + [ParameterType::INTEGER], + ]; + + yield 'Named: Very simple with param int and string' => [ + 'SELECT * FROM Foo WHERE foo = :foo AND bar = :bar', + ['bar' => 'Some String','foo' => 1], + ['foo' => ParameterType::INTEGER, 'bar' => ParameterType::STRING], + 'SELECT * FROM Foo WHERE foo = ? AND bar = ?', + [1,'Some String'], + [ParameterType::INTEGER, ParameterType::STRING], + ]; + + yield 'Named: Very simple with one needle' => [ + 'SELECT * FROM Foo WHERE foo IN (:foo)', + ['foo' => [1, 2, 3]], + ['foo' => ArrayParameterType::INTEGER], + 'SELECT * FROM Foo WHERE foo IN (?, ?, ?)', + [1, 2, 3], + [ParameterType::INTEGER, ParameterType::INTEGER, ParameterType::INTEGER], + ]; + + yield 'Named: One non-list before d one after list-needle' => [ + 'SELECT * FROM Foo WHERE foo = :foo AND bar IN (:bar)', + ['foo' => 'string', 'bar' => [1, 2, 3]], + ['foo' => ParameterType::STRING, 'bar' => ArrayParameterType::INTEGER], + 'SELECT * FROM Foo WHERE foo = ? AND bar IN (?, ?, ?)', + ['string', 1, 2, 3], + [ParameterType::STRING, ParameterType::INTEGER, ParameterType::INTEGER, ParameterType::INTEGER], + ]; + + yield 'Named: One non-list after list-needle' => [ + 'SELECT * FROM Foo WHERE bar IN (:bar) AND baz = :baz', + ['bar' => [1, 2, 3], 'baz' => 'foo'], + ['bar' => ArrayParameterType::INTEGER, 'baz' => ParameterType::STRING], + 'SELECT * FROM Foo WHERE bar IN (?, ?, ?) AND baz = ?', + [1, 2, 3, 'foo'], + [ParameterType::INTEGER, ParameterType::INTEGER, ParameterType::INTEGER, ParameterType::STRING], + ]; + + yield 'Named: One non-list before and one after list-needle' => [ + 'SELECT * FROM Foo WHERE foo = :foo AND bar IN (:bar) AND baz = :baz', + ['bar' => [1, 2, 3],'foo' => 1, 'baz' => 4], + [ + 'bar' => ArrayParameterType::INTEGER, + 'foo' => ParameterType::INTEGER, + 'baz' => ParameterType::INTEGER, ], - 'Named parameters and explicit types' => [ - 'SELECT * FROM Foo WHERE foo = :foo OR bar = :bar', - ['foo' => 'foo', 'bar' => 'bar'], - ['foo' => ParameterType::INTEGER, 'bar' => ParameterType::INTEGER], - 'SELECT * FROM Foo WHERE foo = ? OR bar = ?', - ['foo', 'bar'], - [ParameterType::INTEGER, ParameterType::INTEGER], + 'SELECT * FROM Foo WHERE foo = ? AND bar IN (?, ?, ?) AND baz = ?', + [1, 1, 2, 3, 4], + [ + ParameterType::INTEGER, + ParameterType::INTEGER, + ParameterType::INTEGER, + ParameterType::INTEGER, + ParameterType::INTEGER, ], - 'Null valued parameters (DBAL-522)' => [ - 'INSERT INTO Foo (foo, bar) values (:foo, :bar)', - ['foo' => 1, 'bar' => null], - ['foo' => ParameterType::INTEGER, 'bar' => ParameterType::NULL], - 'INSERT INTO Foo (foo, bar) values (?, ?)', - [1, null], - [ParameterType::INTEGER, ParameterType::NULL], + ]; + + yield 'Named: Two lists' => [ + 'SELECT * FROM Foo WHERE foo IN (:a, :b)', + ['b' => [4, 5],'a' => [1, 2, 3]], + ['a' => ArrayParameterType::INTEGER, 'b' => ArrayParameterType::INTEGER], + 'SELECT * FROM Foo WHERE foo IN (?, ?, ?, ?, ?)', + [1, 2, 3, 4, 5], + [ + ParameterType::INTEGER, + ParameterType::INTEGER, + ParameterType::INTEGER, + ParameterType::INTEGER, + ParameterType::INTEGER, ], + ]; + + yield 'Named: With the same name arg type string' => [ + 'SELECT * FROM Foo WHERE foo <> :arg AND bar = :arg', + ['arg' => 'Some String'], + ['arg' => ParameterType::STRING], + 'SELECT * FROM Foo WHERE foo <> ? AND bar = ?', + ['Some String','Some String'], + [ParameterType::STRING,ParameterType::STRING], + ]; + + yield 'Named: With the same name arg' => [ + 'SELECT * FROM Foo WHERE foo IN (:arg) AND NOT bar IN (:arg)', + ['arg' => [1, 2, 3]], + ['arg' => ArrayParameterType::INTEGER], + 'SELECT * FROM Foo WHERE foo IN (?, ?, ?) AND NOT bar IN (?, ?, ?)', + [1, 2, 3, 1, 2, 3], [ - 'INSERT INTO Foo (foo, bar) values (?, ?)', - [1, null], - [ParameterType::INTEGER, ParameterType::NULL], - 'INSERT INTO Foo (foo, bar) values (?, ?)', - [1, null], - [ParameterType::INTEGER, ParameterType::NULL], + ParameterType::INTEGER, + ParameterType::INTEGER, + ParameterType::INTEGER, + ParameterType::INTEGER, + ParameterType::INTEGER, + ParameterType::INTEGER, ], - 'Escaped single quotes SQL- and C-Style (DBAL-1205)' => [ - "SELECT * FROM Foo WHERE foo = :foo||''':not_a_param''\\'' OR bar = ''':not_a_param''\\'':bar", - ['foo' => 1, 'bar' => 2], - ['foo' => ParameterType::INTEGER, 'bar' => ParameterType::INTEGER], - 'SELECT * FROM Foo WHERE foo = ?||\'\'\':not_a_param\'\'\\\'\' OR bar = \'\'\':not_a_param\'\'\\\'\'?', - [1, 2], - [ParameterType::INTEGER, ParameterType::INTEGER], + ]; + + yield 'Named: Same name, other name in between (DBAL-299)' => [ + 'SELECT * FROM Foo WHERE (:foo = 2) AND (:bar = 3) AND (:foo = 2)', + ['foo' => 2,'bar' => 3], + ['foo' => ParameterType::INTEGER,'bar' => ParameterType::INTEGER], + 'SELECT * FROM Foo WHERE (? = 2) AND (? = 3) AND (? = 2)', + [2, 3, 2], + [ParameterType::INTEGER, ParameterType::INTEGER, ParameterType::INTEGER], + ]; + + yield 'Named: Empty "integer" array (DDC-1978)' => [ + 'SELECT * FROM Foo WHERE foo IN (:foo)', + ['foo' => []], + ['foo' => ArrayParameterType::INTEGER], + 'SELECT * FROM Foo WHERE foo IN (NULL)', + [], + [], + ]; + + yield 'Named: Two empty "str" array (DDC-1978)' => [ + 'SELECT * FROM Foo WHERE foo IN (:foo) OR bar IN (:bar)', + ['foo' => [], 'bar' => []], + ['foo' => ArrayParameterType::STRING, 'bar' => ArrayParameterType::STRING], + 'SELECT * FROM Foo WHERE foo IN (NULL) OR bar IN (NULL)', + [], + [], + ]; + + yield [ + 'SELECT * FROM Foo WHERE foo IN (:foo) OR bar IN (:bar)', + ['foo' => [], 'bar' => []], + ['foo' => ArrayParameterType::ASCII, 'bar' => ArrayParameterType::ASCII], + 'SELECT * FROM Foo WHERE foo IN (NULL) OR bar IN (NULL)', + [], + [], + ]; + + yield [ + 'SELECT * FROM Foo WHERE foo IN (:foo) OR bar = :bar OR baz = :baz', + ['foo' => [1, 2], 'bar' => 'bar', 'baz' => 'baz'], + ['foo' => ArrayParameterType::INTEGER, 'baz' => 'string'], + 'SELECT * FROM Foo WHERE foo IN (?, ?) OR bar = ? OR baz = ?', + [1, 2, 'bar', 'baz'], + [ + 0 => ParameterType::INTEGER, + 1 => ParameterType::INTEGER, + 3 => 'string', ], + ]; + + yield [ + 'SELECT * FROM Foo WHERE foo IN (:foo) OR bar = :bar', + ['foo' => [1, 2], 'bar' => 'bar'], + ['foo' => ArrayParameterType::INTEGER], + 'SELECT * FROM Foo WHERE foo IN (?, ?) OR bar = ?', + [1, 2, 'bar'], + [ParameterType::INTEGER, ParameterType::INTEGER], + ]; + + yield 'Named parameters and partially implicit types' => [ + 'SELECT * FROM Foo WHERE foo = :foo OR bar = :bar', + ['foo' => 'foo', 'bar' => 'bar'], + ['foo' => ParameterType::INTEGER], + 'SELECT * FROM Foo WHERE foo = ? OR bar = ?', + ['foo', 'bar'], + [ParameterType::INTEGER], + ]; + + yield 'Named parameters and explicit types' => [ + 'SELECT * FROM Foo WHERE foo = :foo OR bar = :bar', + ['foo' => 'foo', 'bar' => 'bar'], + ['foo' => ParameterType::INTEGER, 'bar' => ParameterType::INTEGER], + 'SELECT * FROM Foo WHERE foo = ? OR bar = ?', + ['foo', 'bar'], + [ParameterType::INTEGER, ParameterType::INTEGER], + ]; + + yield 'Null valued parameters (DBAL-522)' => [ + 'INSERT INTO Foo (foo, bar) values (:foo, :bar)', + ['foo' => 1, 'bar' => null], + ['foo' => ParameterType::INTEGER, 'bar' => ParameterType::NULL], + 'INSERT INTO Foo (foo, bar) values (?, ?)', + [1, null], + [ParameterType::INTEGER, ParameterType::NULL], + ]; + + yield [ + 'INSERT INTO Foo (foo, bar) values (?, ?)', + [1, null], + [ParameterType::INTEGER, ParameterType::NULL], + 'INSERT INTO Foo (foo, bar) values (?, ?)', + [1, null], + [ParameterType::INTEGER, ParameterType::NULL], + ]; + + yield 'Escaped single quotes SQL- and C-Style (DBAL-1205)' => [ + "SELECT * FROM Foo WHERE foo = :foo||''':not_a_param''\\'' OR bar = ''':not_a_param''\\'':bar", + ['foo' => 1, 'bar' => 2], + ['foo' => ParameterType::INTEGER, 'bar' => ParameterType::INTEGER], + 'SELECT * FROM Foo WHERE foo = ?||\'\'\':not_a_param\'\'\\\'\' OR bar = \'\'\':not_a_param\'\'\\\'\'?', + [1, 2], + [ParameterType::INTEGER, ParameterType::INTEGER], + ]; + + yield [ + 'SELECT NULL FROM dummy WHERE ? IN (?)', + ['foo', ['bar', 'baz']], + [1 => ArrayParameterType::STRING], + 'SELECT NULL FROM dummy WHERE ? IN (?, ?)', + ['foo', 'bar', 'baz'], + [1 => ParameterType::STRING, ParameterType::STRING], + ]; + + yield 'Named: Binary array with explicit types' => [ + 'SELECT * FROM Foo WHERE foo IN (:foo) OR bar IN (:bar)', [ - 'SELECT NULL FROM dummy WHERE ? IN (?)', - ['foo', ['bar', 'baz']], - [1 => ArrayParameterType::STRING], - 'SELECT NULL FROM dummy WHERE ? IN (?, ?)', - ['foo', 'bar', 'baz'], - [1 => ParameterType::STRING, ParameterType::STRING], + 'foo' => [hex2bin('DEADBEEF'), hex2bin('C0DEF00D')], + 'bar' => [hex2bin('DEADBEEF'), hex2bin('C0DEF00D')], ], - '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, - ], + ['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, ], ]; } @@ -378,7 +406,7 @@ public function testExpandListParameters( } /** - * @return list, * array @@ -386,27 +414,28 @@ public function testExpandListParameters( */ public static function missingNamedParameterProvider(): iterable { - return [ - [ - 'SELECT * FROM foo WHERE bar = :param', - ['other' => 'val'], - [], - ], - [ - 'SELECT * FROM foo WHERE bar = :param', - [], - [], - ], - [ - 'SELECT * FROM foo WHERE bar = :param', - [], - ['bar' => ArrayParameterType::INTEGER], - ], - [ - 'SELECT * FROM foo WHERE bar = :param', - ['bar' => 'value'], - ['bar' => ArrayParameterType::INTEGER], - ], + yield [ + 'SELECT * FROM foo WHERE bar = :param', + ['other' => 'val'], + [], + ]; + + yield [ + 'SELECT * FROM foo WHERE bar = :param', + [], + [], + ]; + + yield [ + 'SELECT * FROM foo WHERE bar = :param', + [], + ['bar' => ArrayParameterType::INTEGER], + ]; + + yield [ + 'SELECT * FROM foo WHERE bar = :param', + ['bar' => 'value'], + ['bar' => ArrayParameterType::INTEGER], ]; } @@ -434,15 +463,14 @@ public function testMissingPositionalParameter(string $query, array $params): vo /** @return iterable}> */ public static function missingPositionalParameterProvider(): iterable { - return [ - 'No parameters' => [ - 'SELECT * FROM foo WHERE bar = ?', - [], - ], - 'Too few parameters' => [ - 'SELECT * FROM foo WHERE bar = ? AND baz = ?', - [1], - ], + yield 'No parameters' => [ + 'SELECT * FROM foo WHERE bar = ?', + [], + ]; + + yield 'Too few parameters' => [ + 'SELECT * FROM foo WHERE bar = ? AND baz = ?', + [1], ]; } diff --git a/tests/Functional/ResultMetadataTest.php b/tests/Functional/ResultMetadataTest.php index 4d6747fa9c7..e0b9f6f90a4 100644 --- a/tests/Functional/ResultMetadataTest.php +++ b/tests/Functional/ResultMetadataTest.php @@ -7,6 +7,7 @@ use Doctrine\DBAL\Exception\InvalidColumnIndex; use Doctrine\DBAL\Schema\Table; use Doctrine\DBAL\Tests\FunctionalTestCase; +use PHPUnit\Framework\Attributes\TestWith; use function strtolower; @@ -36,7 +37,9 @@ public function testColumnNameWithResults(): void self::assertEquals('alternate_name', strtolower($result->getColumnName(1))); } - public function testColumnNameWithInvalidIndex(): void + #[TestWith([2])] + #[TestWith([-1])] + public function testColumnNameWithInvalidIndex(int $index): void { $sql = 'SELECT test_int, test_int AS alternate_name FROM result_metadata_table'; @@ -47,7 +50,7 @@ public function testColumnNameWithInvalidIndex(): void $this->expectException(InvalidColumnIndex::class); - $result->getColumnName(2); + $result->getColumnName($index); } public function testColumnNameWithoutResults(): void