diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000000..c2555ebbd46 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,4 @@ +Doctrine has [general contributing guidelines][contributor workflow], make +sure you follow them. + +[contributor workflow]: https://www.doctrine-project.org/contribute/index.html diff --git a/UPGRADE.md b/UPGRADE.md index 9eb98d764a3..e8c8997dc67 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -1,5 +1,9 @@ # Upgrade to 2.11 +## Deprecated `Abstraction\Result` + +The usage of the `Doctrine\DBAL\Abstraction\Result` interface is deprecated. In DBAL 3.0, the statement result at the wrapper level will be represented by the `Doctrine\DBAL\Result` class. + ## Deprecated the functionality of dropping client connections when dropping a database The corresponding `getDisallowDatabaseConnectionsSQL()` and `getCloseActiveDatabaseConnectionsSQL` methods diff --git a/docs/en/explanation/implicit-indexes.rst b/docs/en/explanation/implicit-indexes.rst index f68edbe3ffb..9f96e8a34cb 100644 --- a/docs/en/explanation/implicit-indexes.rst +++ b/docs/en/explanation/implicit-indexes.rst @@ -6,13 +6,13 @@ with names such as ``IDX_885DBAFAA76ED395``? In this document, we will distinguish three types of indexes: user-defined indexes - indexes you did ask for + indexes you did ask for DBAL-defined indexes - indexes you did not ask for, created on your behalf by the DBAL + indexes you did not ask for, created on your behalf by the DBAL RDBMS-defined indexes - indexes you did not ask for, created on your behalf by the RDBMS + indexes you did not ask for, created on your behalf by the RDBMS RDBMS-defined indexes can be created by some database platforms when you create a foreign key: they will create an index on the referencing diff --git a/lib/Doctrine/DBAL/Abstraction/Result.php b/lib/Doctrine/DBAL/Abstraction/Result.php index 42ff3419e37..2b1f8e69f3f 100644 --- a/lib/Doctrine/DBAL/Abstraction/Result.php +++ b/lib/Doctrine/DBAL/Abstraction/Result.php @@ -11,6 +11,8 @@ /** * Abstraction-level result statement execution result. Provides additional methods on top * of the driver-level interface. + * + * @deprecated */ interface Result extends DriverResult { diff --git a/lib/Doctrine/DBAL/Connection.php b/lib/Doctrine/DBAL/Connection.php index 82ef5d80170..af803e5db26 100644 --- a/lib/Doctrine/DBAL/Connection.php +++ b/lib/Doctrine/DBAL/Connection.php @@ -541,7 +541,7 @@ public function setFetchMode($fetchMode) * Prepares and executes an SQL query and returns the first row of the result * as an associative array. * - * @deprecated Use fetchAllAssociative() + * @deprecated Use fetchAssociative() * * @param string $sql The query SQL * @param mixed[] $params The query parameters @@ -560,7 +560,7 @@ public function fetchAssoc($sql, array $params = [], array $types = []) * Prepares and executes an SQL query and returns the first row of the result * as a numerically indexed array. * - * @deprecated Use fetchAllNumeric() + * @deprecated Use fetchNumeric() * * @param string $sql The query SQL * @param mixed[] $params The query parameters diff --git a/lib/Doctrine/DBAL/Driver/PDOConnection.php b/lib/Doctrine/DBAL/Driver/PDOConnection.php index 43b8280ef70..8409952cb4e 100644 --- a/lib/Doctrine/DBAL/Driver/PDOConnection.php +++ b/lib/Doctrine/DBAL/Driver/PDOConnection.php @@ -5,6 +5,7 @@ use Doctrine\DBAL\Driver\Connection as ConnectionInterface; use Doctrine\DBAL\Driver\PDO\Exception; use Doctrine\DBAL\Driver\PDO\Statement; +use Doctrine\DBAL\ParameterType; use PDO; use PDOException; use PDOStatement; @@ -101,6 +102,14 @@ public function query() } } + /** + * {@inheritdoc} + */ + public function quote($value, $type = ParameterType::STRING) + { + return parent::quote($value, $type); + } + /** * {@inheritdoc} */ diff --git a/lib/Doctrine/DBAL/Query/QueryBuilder.php b/lib/Doctrine/DBAL/Query/QueryBuilder.php index 6c4aa6ef029..a1306754836 100644 --- a/lib/Doctrine/DBAL/Query/QueryBuilder.php +++ b/lib/Doctrine/DBAL/Query/QueryBuilder.php @@ -8,6 +8,7 @@ use Doctrine\DBAL\Query\Expression\CompositeExpression; use Doctrine\DBAL\Query\Expression\ExpressionBuilder; +use function array_filter; use function array_key_exists; use function array_keys; use function array_unshift; @@ -829,6 +830,7 @@ public function where($predicates) public function andWhere($where) { $args = func_get_args(); + $args = array_filter($args); // https://github.com/doctrine/dbal/issues/4282 $where = $this->getQueryPart('where'); if ($where instanceof CompositeExpression && $where->getType() === CompositeExpression::TYPE_AND) { @@ -862,6 +864,7 @@ public function andWhere($where) public function orWhere($where) { $args = func_get_args(); + $args = array_filter($args); // https://github.com/doctrine/dbal/issues/4282 $where = $this->getQueryPart('where'); if ($where instanceof CompositeExpression && $where->getType() === CompositeExpression::TYPE_OR) { @@ -1010,6 +1013,7 @@ public function having($having) public function andHaving($having) { $args = func_get_args(); + $args = array_filter($args); // https://github.com/doctrine/dbal/issues/4282 $having = $this->getQueryPart('having'); if ($having instanceof CompositeExpression && $having->getType() === CompositeExpression::TYPE_AND) { @@ -1033,6 +1037,7 @@ public function andHaving($having) public function orHaving($having) { $args = func_get_args(); + $args = array_filter($args); // https://github.com/doctrine/dbal/issues/4282 $having = $this->getQueryPart('having'); if ($having instanceof CompositeExpression && $having->getType() === CompositeExpression::TYPE_OR) { diff --git a/lib/Doctrine/DBAL/Statement.php b/lib/Doctrine/DBAL/Statement.php index 0e47e09303c..294866921ec 100644 --- a/lib/Doctrine/DBAL/Statement.php +++ b/lib/Doctrine/DBAL/Statement.php @@ -264,7 +264,15 @@ public function fetch($fetchMode = null, $cursorOrientation = PDO::FETCH_ORI_NEX */ public function fetchAll($fetchMode = null, $fetchArgument = null, $ctorArgs = null) { - return $this->stmt->fetchAll($fetchMode, $fetchArgument, $ctorArgs); + if ($ctorArgs !== null) { + return $this->stmt->fetchAll($fetchMode, $fetchArgument, $ctorArgs); + } + + if ($fetchArgument !== null) { + return $this->stmt->fetchAll($fetchMode, $fetchArgument); + } + + return $this->stmt->fetchAll($fetchMode); } /** diff --git a/lib/Doctrine/DBAL/Version.php b/lib/Doctrine/DBAL/Version.php index cc4069ee9d2..044b85e54aa 100644 --- a/lib/Doctrine/DBAL/Version.php +++ b/lib/Doctrine/DBAL/Version.php @@ -17,7 +17,7 @@ class Version /** * Current Doctrine Version. */ - public const VERSION = '2.11.0'; + public const VERSION = '2.11.1'; /** * Compares a Doctrine version with the current one. diff --git a/phpcs.xml.dist b/phpcs.xml.dist index c82ca439eca..8c6b82da6b0 100644 --- a/phpcs.xml.dist +++ b/phpcs.xml.dist @@ -110,6 +110,11 @@ lib/Doctrine/DBAL/Driver/SQLSrv/SQLSrvStatement.php + + + lib/Doctrine/DBAL/Driver/PDOConnection.php + + lib/Doctrine/DBAL/Driver/ExceptionConverterDriver.php diff --git a/tests/Doctrine/Tests/DBAL/Functional/Driver/PDO/ConnectionTest.php b/tests/Doctrine/Tests/DBAL/Functional/Driver/PDO/ConnectionTest.php index f54cb07b06a..c9f748fbce6 100644 --- a/tests/Doctrine/Tests/DBAL/Functional/Driver/PDO/ConnectionTest.php +++ b/tests/Doctrine/Tests/DBAL/Functional/Driver/PDO/ConnectionTest.php @@ -1,5 +1,7 @@ driverConnection->query('foo'); } + + /** + * This test ensures backward compatibility with DBAL 2.x and should be removed in 3.0. + */ + public function testQuoteInteger(): void + { + self::assertSame("'1'", $this->connection->getWrappedConnection()->quote(1)); + } } diff --git a/tests/Doctrine/Tests/DBAL/Functional/ExceptionTest.php b/tests/Doctrine/Tests/DBAL/Functional/ExceptionTest.php index f5136c66c47..338d3a7aee4 100644 --- a/tests/Doctrine/Tests/DBAL/Functional/ExceptionTest.php +++ b/tests/Doctrine/Tests/DBAL/Functional/ExceptionTest.php @@ -29,6 +29,7 @@ use function version_compare; use const PHP_OS; +use const PHP_OS_FAMILY; class ExceptionTest extends DbalFunctionalTestCase { @@ -303,7 +304,7 @@ public function testConnectionExceptionSqLite(): void } // mode 0 is considered read-only on Windows - $mode = PHP_OS === 'Linux' ? 0444 : 0000; + $mode = PHP_OS_FAMILY === 'Windows' ? 0000 : 0444; $filename = sprintf('%s/%s', sys_get_temp_dir(), 'doctrine_failed_connection_' . $mode . '.db'); diff --git a/tests/Doctrine/Tests/DBAL/Functional/ExternalPDOInstanceTest.php b/tests/Doctrine/Tests/DBAL/Functional/ExternalPDOInstanceTest.php new file mode 100644 index 00000000000..42af9d29ea5 --- /dev/null +++ b/tests/Doctrine/Tests/DBAL/Functional/ExternalPDOInstanceTest.php @@ -0,0 +1,70 @@ +getDriver() instanceof PDOSqliteDriver) { + $this->markTestSkipped('External PDO instance tests are only run on PDO SQLite for now'); + } + + $pdo = new PDO('sqlite::memory:'); + + $this->connection = new Connection(['pdo' => $pdo], new PDOSqliteDriver()); + + $table = new Table('stmt_fetch_all'); + $table->addColumn('a', 'integer'); + $table->addColumn('b', 'integer'); + + $this->connection->getSchemaManager()->createTable($table); + + $this->connection->insert('stmt_fetch_all', [ + 'a' => 1, + 'b' => 2, + ]); + } + + public function testFetchAllWithOneArgument(): void + { + $stmt = $this->connection->prepare('SELECT a, b FROM stmt_fetch_all'); + $stmt->execute(); + + self::assertEquals([[1, 2]], $stmt->fetchAll(FetchMode::NUMERIC)); + } + + public function testFetchAllWithTwoArguments(): void + { + $stmt = $this->connection->prepare('SELECT a, b FROM stmt_fetch_all'); + $stmt->execute(); + + self::assertEquals([2], $stmt->fetchAll(FetchMode::COLUMN, 1)); + } + + public function testFetchAllWithThreeArguments(): void + { + $stmt = $this->connection->prepare('SELECT a, b FROM stmt_fetch_all'); + $stmt->execute(); + + [$obj] = $stmt->fetchAll(FetchMode::CUSTOM_OBJECT, StatementTestModel::class, ['foo', 'bar']); + + $this->assertInstanceOf(StatementTestModel::class, $obj); + + self::assertEquals(1, $obj->a); + self::assertEquals(2, $obj->b); + self::assertEquals('foo', $obj->x); + self::assertEquals('bar', $obj->y); + } +} diff --git a/tests/Doctrine/Tests/DBAL/Functional/StatementTestModel.php b/tests/Doctrine/Tests/DBAL/Functional/StatementTestModel.php new file mode 100644 index 00000000000..16bb6e1e1a3 --- /dev/null +++ b/tests/Doctrine/Tests/DBAL/Functional/StatementTestModel.php @@ -0,0 +1,24 @@ +x = $x; + $this->y = $y; + } + + /** @var int */ + public $a; + + /** @var int */ + public $b; + + /** @var string */ + public $x; + + /** @var string */ + public $y; +} diff --git a/tests/Doctrine/Tests/DBAL/Query/QueryBuilderTest.php b/tests/Doctrine/Tests/DBAL/Query/QueryBuilderTest.php index bc671787877..038097c8910 100644 --- a/tests/Doctrine/Tests/DBAL/Query/QueryBuilderTest.php +++ b/tests/Doctrine/Tests/DBAL/Query/QueryBuilderTest.php @@ -949,4 +949,104 @@ public function testJoinWithNonUniqueAliasThrowsException(): void $qb->getSQL(); } + + public function testAndWhereEmptyStringStartingWithEmptyExpression(): void + { + $qb = new QueryBuilder($this->conn); + + $qb->select('id') + ->from('foo'); + + $qb->andWhere('', 'a = b'); + + self::assertSame('SELECT id FROM foo WHERE a = b', $qb->getSQL()); + } + + public function testAndWhereEmptyStringStartingWithNonEmptyExpression(): void + { + $qb = new QueryBuilder($this->conn); + + $qb->select('id') + ->from('foo') + ->where('a = b'); + + $qb->andWhere('', 'c = d'); + + self::assertSame('SELECT id FROM foo WHERE (a = b) AND (c = d)', $qb->getSQL()); + } + + public function testOrWhereEmptyStringStartingWithEmptyExpression(): void + { + $qb = new QueryBuilder($this->conn); + + $qb->select('id') + ->from('foo'); + + $qb->orWhere('', 'a = b'); + + self::assertSame('SELECT id FROM foo WHERE a = b', $qb->getSQL()); + } + + public function testOrWhereEmptyStringStartingWithNonEmptyExpression(): void + { + $qb = new QueryBuilder($this->conn); + + $qb->select('id') + ->from('foo') + ->where('a = b'); + + $qb->orWhere('', 'c = d'); + + self::assertSame('SELECT id FROM foo WHERE (a = b) OR (c = d)', $qb->getSQL()); + } + + public function testAndHavingEmptyStringStartingWithEmptyExpression(): void + { + $qb = new QueryBuilder($this->conn); + + $qb->select('id') + ->from('foo'); + + $qb->andHaving('', 'a = b'); + + self::assertSame('SELECT id FROM foo HAVING a = b', $qb->getSQL()); + } + + public function testAndHavingEmptyStringStartingWithNonEmptyExpression(): void + { + $qb = new QueryBuilder($this->conn); + + $qb->select('id') + ->from('foo') + ->having('a = b'); + + $qb->andHaving('', 'c = d'); + + self::assertSame('SELECT id FROM foo HAVING (a = b) AND (c = d)', $qb->getSQL()); + } + + public function testOrHavingEmptyStringStartingWithEmptyExpression(): void + { + $qb = new QueryBuilder($this->conn); + + $qb->select('id') + ->from('foo'); + + $qb->orHaving('', 'a = b'); + + self::assertSame('SELECT id FROM foo HAVING a = b', $qb->getSQL()); + } + + public function testOrHavingEmptyStringStartingWithNonEmptyExpression(): void + { + $qb = new QueryBuilder($this->conn); + + $qb->select('id') + ->from('foo') + ->having('a = b'); + + $qb->orHaving('', 'c = d'); + + self::assertSame('SELECT id FROM foo HAVING (a = b) OR (c = d)', $qb->getSQL()); + } }