Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merge release 3.1.0 into 4.0.x #4612

Merged
merged 22 commits into from Apr 21, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
424bf11
Add Statement::executeQuery and Statement::executeStatement
beberlei Apr 2, 2021
8a502f3
Deprecate Statement::execute()
beberlei Apr 2, 2021
499199f
Improve API by removing nullability
beberlei Apr 5, 2021
5bd5b4b
Add error handler around oci_fetch_array to screen for oracle warnings
amenning Apr 2, 2021
f2413c4
Merge pull request #4581 from amenning/fix-oracle-truncated-fetch
morozov Apr 10, 2021
68a6f8e
Merge pull request #4580 from beberlei/StatementExecuteQuery
beberlei Apr 10, 2021
2d232c1
[GH-4553] Simplify error handling in the OCI8 driver
morozov Apr 10, 2021
fb9ba59
Merge pull request #4599 from morozov/issues/4553
morozov Apr 14, 2021
1372a42
Restore backward compatibility for QueryBuilder::execute()
mdumoulin Apr 9, 2021
f4a801b
Merge pull request #4596 from mdumoulin/fix_issue_4591
morozov Apr 15, 2021
ef629cf
Collect type coverage metrics, add badge to README
morozov Apr 9, 2021
a25bc6a
Replace 2.12 with 2.13 in README
morozov Apr 15, 2021
41d7dd9
Merge branch '2.13.x' into 3.0.x
morozov Apr 17, 2021
275131f
Merge branch '3.0.x' into 3.1.x
morozov Apr 17, 2021
9fc431a
Merge pull request #4604 from morozov/shepherd
morozov Apr 17, 2021
05b7797
Attribute type coverage metric to 3.1.x instead of 2.13.x
morozov Apr 17, 2021
c800380
Merge pull request #4608 from morozov/shepherd
morozov Apr 17, 2021
4dd066d
Merge pull request #4610 from doctrine/2.13.x-merge-up-into-3.0.x_607…
morozov Apr 19, 2021
5ba62e7
Merge branch '3.0.x' into 3.1.x
morozov Apr 19, 2021
8f59014
[GH-4613] Use utf8mb4 instead of utf8 for testing connection charset
morozov Apr 20, 2021
7dc3905
Merge pull request #4614 from morozov/issues/4613
morozov Apr 21, 2021
6d82230
Merge branch '3.1.x' into 4.0.x
morozov Apr 21, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/static-analysis.yml
Expand Up @@ -52,3 +52,4 @@ jobs:
uses: docker://vimeo/psalm-github-actions:4.6.4
with:
composer_require_dev: true
args: --shepherd
25 changes: 14 additions & 11 deletions README.md
@@ -1,10 +1,11 @@
# Doctrine DBAL

| [4.0-dev][4.0] | [3.0][3.0] | [2.12][2.12] |
| [4.0-dev][4.0] | [3.0][3.0] | [2.13][2.13] |
|:----------------:|:----------:|:----------:|
| [![GitHub Actions][GA 4.0 image]][GA 4.0] | [![GitHub Actions][GA 3.0 image]][GA 3.0] | [![GitHub Actions][GA 2.12 image]][GA 2.12] |
| [![AppVeyor][AppVeyor 4.0 image]][AppVeyor 4.0] | [![AppVeyor][AppVeyor 3.0 image]][AppVeyor 3.0] | [![AppVeyor][AppVeyor 2.12 image]][AppVeyor 2.12] |
| [![Code Coverage][Coverage image]][CodeCov 4.0] | [![Code Coverage][Coverage 3.0 image]][CodeCov 3.0] | [![Code Coverage][Coverage 2.12 image]][CodeCov 2.12] |
| [![GitHub Actions][GA 4.0 image]][GA 4.0] | [![GitHub Actions][GA 3.0 image]][GA 3.0] | [![GitHub Actions][GA 2.13 image]][GA 2.13] |
| [![AppVeyor][AppVeyor 4.0 image]][AppVeyor 4.0] | [![AppVeyor][AppVeyor 3.0 image]][AppVeyor 3.0] | [![AppVeyor][AppVeyor 2.13 image]][AppVeyor 2.13] |
| [![Code Coverage][Coverage image]][CodeCov 4.0] | [![Code Coverage][Coverage 3.0 image]][CodeCov 3.0] | [![Code Coverage][Coverage 2.13 image]][CodeCov 2.13] |
| N/A | [![Code Coverage][TypeCov 3.1 image]][TypeCov 3.1] | N/A |

Powerful database abstraction layer with many features for database schema introspection, schema management and PDO abstraction.

Expand All @@ -30,10 +31,12 @@ Powerful database abstraction layer with many features for database schema intro
[GA 3.0]: https://github.com/doctrine/dbal/actions?query=workflow%3A%22Continuous+Integration%22+branch%3A3.0.x
[GA 3.0 image]: https://github.com/doctrine/dbal/workflows/Continuous%20Integration/badge.svg?branch=3.0.x

[Coverage 2.12 image]: https://codecov.io/gh/doctrine/dbal/branch/2.12.x/graph/badge.svg
[2.12]: https://github.com/doctrine/dbal/tree/2.12.x
[CodeCov 2.12]: https://codecov.io/gh/doctrine/dbal/branch/2.12.x
[AppVeyor 2.12]: https://ci.appveyor.com/project/doctrine/dbal/branch/2.12.x
[AppVeyor 2.12 image]: https://ci.appveyor.com/api/projects/status/i88kitq8qpbm0vie/branch/2.12.x?svg=true
[GA 2.12]: https://github.com/doctrine/dbal/actions?query=workflow%3A%22Continuous+Integration%22+branch%3A2.12.x
[GA 2.12 image]: https://github.com/doctrine/dbal/workflows/Continuous%20Integration/badge.svg?branch=2.12.x
[Coverage 2.13 image]: https://codecov.io/gh/doctrine/dbal/branch/2.13.x/graph/badge.svg
[2.13]: https://github.com/doctrine/dbal/tree/2.13.x
[CodeCov 2.13]: https://codecov.io/gh/doctrine/dbal/branch/2.13.x
[AppVeyor 2.13]: https://ci.appveyor.com/project/doctrine/dbal/branch/2.13.x
[AppVeyor 2.13 image]: https://ci.appveyor.com/api/projects/status/i88kitq8qpbm0vie/branch/2.13.x?svg=true
[GA 2.13]: https://github.com/doctrine/dbal/actions?query=workflow%3A%22Continuous+Integration%22+branch%3A2.13.x
[GA 2.13 image]: https://github.com/doctrine/dbal/workflows/Continuous%20Integration/badge.svg?branch=2.13.x
[TypeCov 3.1]: https://shepherd.dev/github/doctrine/dbal
[TypeCov 3.1 image]: https://shepherd.dev/github/doctrine/dbal/coverage.svg
5 changes: 5 additions & 0 deletions psalm.xml.dist
Expand Up @@ -54,6 +54,11 @@
See https://github.com/doctrine/dbal/pull/4317
-->
<file name="tests/Functional/LegacyAPITest.php"/>
<!--
These suppressions should be removed in 4.0.0
-->
<referencedMethod name="Doctrine\DBAL\Query\QueryBuilder::execute"/>
<referencedMethod name="Doctrine\DBAL\Statement::execute"/>
</errorLevel>
</DeprecatedMethod>
<DocblockTypeContradiction>
Expand Down
16 changes: 12 additions & 4 deletions src/Driver/OCI8/Result.php
Expand Up @@ -4,10 +4,13 @@

namespace Doctrine\DBAL\Driver\OCI8;

use Doctrine\DBAL\Driver\Exception;
use Doctrine\DBAL\Driver\FetchUtils;
use Doctrine\DBAL\Driver\OCI8\Exception\Error;
use Doctrine\DBAL\Driver\Result as ResultInterface;

use function oci_cancel;
use function oci_error;
use function oci_fetch_all;
use function oci_fetch_array;
use function oci_num_fields;
Expand Down Expand Up @@ -112,13 +115,18 @@ public function free(): void

/**
* @return mixed|false
*
* @throws Exception
*/
private function fetch(int $mode)
{
return oci_fetch_array(
$this->statement,
$mode | OCI_RETURN_NULLS | OCI_RETURN_LOBS
);
$result = oci_fetch_array($this->statement, $mode | OCI_RETURN_NULLS | OCI_RETURN_LOBS);

if ($result === false && oci_error($this->statement) !== false) {
throw Error::new($this->statement);
}

return $result;
}

/**
Expand Down
41 changes: 41 additions & 0 deletions src/Statement.php
Expand Up @@ -8,6 +8,7 @@
use Doctrine\DBAL\Driver\Statement as DriverStatement;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Types\Type;
use Doctrine\Deprecations\Deprecation;

use function is_string;

Expand Down Expand Up @@ -156,10 +157,18 @@ public function bindParam($param, &$variable, int $type = ParameterType::STRING,
/**
* {@inheritDoc}
*
* @deprecated Statement::execute() is deprecated, use Statement::executeQuery() or executeStatement() instead
*
* @throws Exception
*/
public function execute(?array $params = null): Result
{
Deprecation::triggerIfCalledFromOutside(
'doctrine/dbal',
'https://github.com/doctrine/dbal/pull/4580',
'Statement::execute() is deprecated, use Statement::executeQuery() or Statement::executeStatement() instead'
);

if ($params !== null) {
$this->params = $params;
}
Expand All @@ -179,6 +188,38 @@ public function execute(?array $params = null): Result
}
}

/**
* Executes the statement with the currently bound parameters and return result.
*
* @param mixed[] $params
*
* @throws Exception
*/
public function executeQuery(array $params = []): Result
{
if ($params === []) {
$params = null; // Workaround as long execute() exists and used internally.
}

return $this->execute($params);
}

/**
* Executes the statement with the currently bound parameters and return affected rows.
*
* @param mixed[] $params
*
* @throws Exception
*/
public function executeStatement(array $params = []): int
{
if ($params === []) {
$params = null; // Workaround as long execute() exists and used internally.
}

return $this->execute($params)->rowCount();
}

/**
* Gets the wrapped driver statement.
*/
Expand Down
170 changes: 170 additions & 0 deletions tests/Functional/Driver/OCI8/ResultTest.php
@@ -0,0 +1,170 @@
<?php

declare(strict_types=1);

namespace Doctrine\DBAL\Tests\Functional\Driver\OCI8;

use Doctrine\DBAL\Driver\OCI8\Driver;
use Doctrine\DBAL\Exception\DriverException;
use Doctrine\DBAL\Tests\FunctionalTestCase;
use Doctrine\DBAL\Tests\TestUtil;
use Generator;

use function ini_get;
use function sprintf;

use const E_ALL;
use const E_WARNING;

/**
* @requires extension oci8
*/
class ResultTest extends FunctionalTestCase
{
/**
* Database connection parameters for functional test case
*
* @var array<string,mixed>
*/
private $connectionParams;

protected function setUp(): void
{
parent::setUp();
$this->connectionParams = TestUtil::getConnectionParams();

if ($this->connection->getDriver() instanceof Driver) {
return;
}

self::markTestSkipped('oci8 only test.');
}

protected function tearDown(): void
{
$this->connection->executeQuery('DROP FUNCTION test_oracle_fetch_failure');
$this->connection->executeQuery('DROP TYPE return_numbers');

parent::tearDown();
}

/**
* This test will recreate the case where a data set that is larger than the
* oci8 default prefetch is invalidated on the database after a fetch has begun,
* but before the fetch has completed.
*
* Note that this test requires 2 separate user connections so that the
* pipelined function can be changed mid fetch.
*
* @dataProvider dataProviderForTestTruncatedFetch
*/
public function testTruncatedFetch(
bool $invalidateDataMidFetch
): void {
if ($invalidateDataMidFetch) {
// prevent the PHPUnit error handler from handling the warnings that oci_*() functions may trigger
$this->iniSet('error_reporting', (string) (E_ALL & ~E_WARNING));

$this->expectException(DriverException::class);
$this->expectExceptionCode(4068);
}

// Create a pipelined funtion that returns 10 rows more than the
// oci8 default prefetch
$this->createReturnTypeNeededForPipelinedFunction();
$expectedTotalRowCount = (int) ini_get('oci8.default_prefetch') + 10;
$this->createOrReplacePipelinedFunction($expectedTotalRowCount);

// Create a separate connection from that used to create/update the function
// This must be a different user with permissions to change the given function
$separateConnection = TestUtil::getPrivilegedConnection();

// Query the pipelined function to get initial dataset
$statement = $separateConnection->prepare(sprintf(
'SELECT * FROM TABLE(%s.test_oracle_fetch_failure())',
$this->connectionParams['user']
));
$result = $statement->execute();

// Access the first result to cause the first X rows to be prefetched
// as defined by oci8.default_prefetch (often 100 rows)
$result->fetchOne();

if ($invalidateDataMidFetch) {
// Invalidate the original dataset by changing the pipelined function
// after the initial prefetch that caches locally the first X results
$this->createOrReplacePipelinedFunction($expectedTotalRowCount + 10);
}

while ($result->fetchOne()) {
// Attempt to access all remaining rows from the original fetch
// The rows locally cached from the default prefetch will first be used
// but when the result attempts to get the remaining 10 rows beyond
// the first prefetch, nothing will be returned
//
// PHP oci8 oci_fetch_array will issue a PHP E_WARNING when the 2nd prefetch occurs
// oci_fetch_array(): ORA-04068: existing state of packages has been discarded
// ORA-04061: existing state of function "ROOT.TEST_ORACLE_FETCH_FAILURE" has been invalidated
// ORA-04065: not executed, altered or dropped function "ROOT.TEST_ORACLE_FETCH_FAILURE"
//
// If there was no issue, this should have returned rows totalling 10
// higher than the oci8 default prefetch
continue;
}

self::assertEquals(
$expectedTotalRowCount,
$result->rowCount(),
sprintf(
'Expected to have %s total rows fetched but only found %s rows fetched',
$expectedTotalRowCount,
$result->rowCount()
)
);
}

public function dataProviderForTestTruncatedFetch(): Generator
{
yield 'it should return all rows if no data invalidation occurs'
=> [false];

yield 'it should convert oci8 data invalidation error to DriverException'
=> [true];
}

private function createReturnTypeNeededForPipelinedFunction(): void
{
$this->connection->executeQuery(
'CREATE TYPE return_numbers AS TABLE OF NUMBER(11)'
);
}

/**
* This will create a pipelined function that returns X rows with
* each row returning a single column_value of that row's row number.
* The total number of rows returned is equal to $totalRowCount.
*/
private function createOrReplacePipelinedFunction(int $totalRowCount): void
{
$this->connection->executeQuery(sprintf(
'CREATE OR REPLACE FUNCTION test_oracle_fetch_failure
RETURN return_numbers PIPELINED
AS
v_number_list return_numbers;
BEGIN
SELECT ROWNUM r
BULK COLLECT INTO v_number_list
FROM DUAL
CONNECT BY ROWNUM <= %d;

FOR i IN 1 .. v_number_list.COUNT
LOOP
PIPE ROW (v_number_list(i));
END LOOP;

RETURN;
END;',
$totalRowCount
));
}
}
2 changes: 1 addition & 1 deletion tests/Functional/PrimaryReadReplicaConnectionTest.php
Expand Up @@ -73,7 +73,7 @@ private function createPrimaryReadReplicaConnectionParams(bool $keepReplica = fa
public function testInheritCharsetFromPrimary(): void
{
$charsets = [
'utf8',
'utf8mb4',
'latin1',
];

Expand Down
38 changes: 38 additions & 0 deletions tests/Functional/StatementTest.php
Expand Up @@ -361,4 +361,42 @@ public function testExecWithRedundantParameters(): void

$stmt->execute([null]);
}

public function testExecuteQuery(): void
{
$platform = $this->connection->getDatabasePlatform();
$query = $platform->getDummySelectSQL();
$result = $this->connection->prepare($query)->executeQuery()->fetchOne();

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

public function testExecuteQueryWithParams(): void
{
$this->connection->insert('stmt_test', ['id' => 1]);

$query = 'SELECT id FROM stmt_test WHERE id = ?';
$result = $this->connection->prepare($query)->executeQuery([1])->fetchOne();

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

public function testExecuteStatement(): void
{
$this->connection->insert('stmt_test', ['id' => 1]);

$query = 'UPDATE stmt_test SET name = ? WHERE id = 1';
$stmt = $this->connection->prepare($query);

$stmt->bindValue(1, 'bar');

$result = $stmt->executeStatement();

self::assertEquals(1, $result);

$query = 'UPDATE stmt_test SET name = ? WHERE id = ?';
$result = $this->connection->prepare($query)->executeStatement(['foo', 1]);

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