Skip to content

Commit 57fc66f

Browse files
committed
Merge branch '4.3.x' into 5.0.x
2 parents 6456ffd + e68422d commit 57fc66f

17 files changed

+830
-61
lines changed

.appveyor.yml

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -40,15 +40,15 @@ init:
4040

4141
## Install PHP and composer, and run the appropriate composer command
4242
install:
43-
# Upgrade to 0.10.16-beta to benefit from a bugfix for
44-
# https://github.com/chocolatey/choco/issues/1843
45-
- ps: choco upgrade chocolatey --pre
43+
- sc config wuauserv start=auto
44+
- net start wuauserv
4645
- ps: |
4746
# Check if installation is cached
4847
if (!(Test-Path c:\tools\php)) {
49-
appveyor-retry choco install --params '""/InstallDir:C:\tools\php""' --ignore-checksums -y php --version ((choco search php --exact --all-versions -r | select-string -pattern $env:php | sort { [version]($_ -split '\|' | select -last 1) } -Descending | Select-Object -first 1) -replace '[php|]','')
48+
choco upgrade chocolatey
49+
appveyor-retry choco install --no-progress --params '""/InstallDir:C:\tools\php""' --ignore-checksums -y php --version ((choco search php --exact --all-versions -r | select-string -pattern $env:php | sort { [version]($_ -split '\|' | select -last 1) } -Descending | Select-Object -first 1) -replace '[php|]','')
5050
# install sqlite
51-
appveyor-retry choco install -y sqlite
51+
appveyor-retry choco install --no-progress -y sqlite
5252
Get-ChildItem -Path c:\tools\php
5353
cd c:\tools\php
5454
@@ -122,4 +122,5 @@ test_script:
122122
123123
after_test:
124124
- appveyor DownloadFile https://codecov.io/bash -FileName codecov.sh
125-
- bash codecov.sh -f clover.xml
125+
- SET upload_name=appveyor-%db%-%db_version%-%driver%-php-%php%
126+
- bash codecov.sh -f clover.xml -n %upload_name%

.github/workflows/continuous-integration.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -652,5 +652,6 @@ jobs:
652652
with:
653653
directory: reports
654654
fail_ci_if_error: true
655+
name: github-action
655656
env:
656657
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}

src/Connection.php

Lines changed: 44 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,17 @@
1212
use Doctrine\DBAL\Connection\StaticServerVersionProvider;
1313
use Doctrine\DBAL\Driver\API\ExceptionConverter;
1414
use Doctrine\DBAL\Driver\Connection as DriverConnection;
15+
use Doctrine\DBAL\Driver\Exception as TheDriverException;
1516
use Doctrine\DBAL\Driver\Statement as DriverStatement;
1617
use Doctrine\DBAL\Exception\CommitFailedRollbackOnly;
1718
use Doctrine\DBAL\Exception\ConnectionLost;
19+
use Doctrine\DBAL\Exception\DeadlockException;
1820
use Doctrine\DBAL\Exception\DriverException;
21+
use Doctrine\DBAL\Exception\ForeignKeyConstraintViolationException;
1922
use Doctrine\DBAL\Exception\NoActiveTransaction;
2023
use Doctrine\DBAL\Exception\SavepointsNotSupported;
24+
use Doctrine\DBAL\Exception\TransactionRolledBack;
25+
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
2126
use Doctrine\DBAL\Platforms\AbstractPlatform;
2227
use Doctrine\DBAL\Query\Expression\ExpressionBuilder;
2328
use Doctrine\DBAL\Query\QueryBuilder;
@@ -922,16 +927,35 @@ public function transactional(Closure $func): mixed
922927

923928
try {
924929
$res = $func($this);
925-
$this->commit();
926930

927931
$successful = true;
928-
929-
return $res;
930932
} finally {
931933
if (! $successful) {
932934
$this->rollBack();
933935
}
934936
}
937+
938+
$shouldRollback = true;
939+
try {
940+
$this->commit();
941+
942+
$shouldRollback = false;
943+
} catch (TheDriverException $t) {
944+
$shouldRollback = ! (
945+
$t instanceof TransactionRolledBack
946+
|| $t instanceof UniqueConstraintViolationException
947+
|| $t instanceof ForeignKeyConstraintViolationException
948+
|| $t instanceof DeadlockException
949+
);
950+
951+
throw $t;
952+
} finally {
953+
if ($shouldRollback) {
954+
$this->rollBack();
955+
}
956+
}
957+
958+
return $res;
935959
}
936960

937961
/**
@@ -1010,17 +1034,26 @@ public function commit(): void
10101034

10111035
$connection = $this->connect();
10121036

1013-
if ($this->transactionNestingLevel === 1) {
1014-
try {
1015-
$connection->commit();
1016-
} catch (Driver\Exception $e) {
1017-
throw $this->convertException($e);
1037+
try {
1038+
if ($this->transactionNestingLevel === 1) {
1039+
try {
1040+
$connection->commit();
1041+
} catch (Driver\Exception $e) {
1042+
throw $this->convertException($e);
1043+
}
1044+
} else {
1045+
$this->releaseSavepoint($this->_getNestedTransactionSavePointName());
10181046
}
1019-
} else {
1020-
$this->releaseSavepoint($this->_getNestedTransactionSavePointName());
1047+
} finally {
1048+
$this->updateTransactionStateAfterCommit();
10211049
}
1050+
}
10221051

1023-
--$this->transactionNestingLevel;
1052+
private function updateTransactionStateAfterCommit(): void
1053+
{
1054+
if ($this->transactionNestingLevel !== 0) {
1055+
--$this->transactionNestingLevel;
1056+
}
10241057

10251058
if ($this->autoCommit !== false || $this->transactionNestingLevel !== 0) {
10261059
return;

src/Driver/API/OCI/ExceptionConverter.php

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66

77
use Doctrine\DBAL\Driver\API\ExceptionConverter as ExceptionConverterInterface;
88
use Doctrine\DBAL\Driver\Exception;
9+
use Doctrine\DBAL\Driver\OCI8\Exception\Error;
10+
use Doctrine\DBAL\Driver\PDO\Exception as DriverPDOException;
911
use Doctrine\DBAL\Exception\ConnectionException;
1012
use Doctrine\DBAL\Exception\DatabaseDoesNotExist;
1113
use Doctrine\DBAL\Exception\DatabaseObjectNotFoundException;
@@ -17,9 +19,15 @@
1719
use Doctrine\DBAL\Exception\SyntaxErrorException;
1820
use Doctrine\DBAL\Exception\TableExistsException;
1921
use Doctrine\DBAL\Exception\TableNotFoundException;
22+
use Doctrine\DBAL\Exception\TransactionRolledBack;
2023
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
2124
use Doctrine\DBAL\Query;
2225

26+
use function assert;
27+
use function count;
28+
use function explode;
29+
use function str_replace;
30+
2331
/** @internal */
2432
final class ExceptionConverter implements ExceptionConverterInterface
2533
{
@@ -40,6 +48,26 @@ public function convert(Exception $exception, ?Query $query): DriverException
4048
12545 => new ConnectionException($exception, $query),
4149
1400 => new NotNullConstraintViolationException($exception, $query),
4250
1918 => new DatabaseDoesNotExist($exception, $query),
51+
2091 => (function () use ($exception, $query) {
52+
//SQLSTATE[HY000]: General error: 2091 OCITransCommit: ORA-02091: transaction rolled back
53+
//ORA-00001: unique constraint (DOCTRINE.GH3423_UNIQUE) violated
54+
$lines = explode("\n", $exception->getMessage(), 2);
55+
assert(count($lines) >= 2);
56+
57+
[, $causeError] = $lines;
58+
59+
[$causeCode] = explode(': ', $causeError, 2);
60+
$code = (int) str_replace('ORA-', '', $causeCode);
61+
62+
$sqlState = $exception->getSQLState();
63+
if ($exception instanceof DriverPDOException) {
64+
$why = $this->convert(new DriverPDOException($causeError, $sqlState, $code, $exception), $query);
65+
} else {
66+
$why = $this->convert(new Error($causeError, $sqlState, $code, $exception), $query);
67+
}
68+
69+
return new TransactionRolledBack($why, $query);
70+
})(),
4371
2289,
4472
2443,
4573
4080 => new DatabaseObjectNotFoundException($exception, $query),

src/Driver/OCI8/Connection.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ public function beginTransaction(): void
9595

9696
public function commit(): void
9797
{
98-
if (! oci_commit($this->connection)) {
98+
if (! @oci_commit($this->connection)) {
9999
throw Error::new($this->connection);
100100
}
101101

src/Driver/OCI8/Result.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use function oci_error;
1515
use function oci_fetch_all;
1616
use function oci_fetch_array;
17+
use function oci_field_name;
1718
use function oci_num_fields;
1819
use function oci_num_rows;
1920

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Doctrine\DBAL\Exception;
6+
7+
/** @psalm-immutable */
8+
class TransactionRolledBack extends DriverException
9+
{
10+
}

src/Platforms/PostgreSQLPlatform.php

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,9 @@
2929
use function is_string;
3030
use function sprintf;
3131
use function str_contains;
32+
use function str_ends_with;
3233
use function strtolower;
34+
use function substr;
3335
use function trim;
3436

3537
/**
@@ -363,7 +365,11 @@ public function getDropForeignKeySQL(string $foreignKey, string $table): string
363365
public function getDropIndexSQL(string $name, string $table): string
364366
{
365367
if ($name === '"primary"') {
366-
$constraintName = $table . '_pkey';
368+
if (str_ends_with($table, '"')) {
369+
$constraintName = substr($table, 0, -1) . '_pkey"';
370+
} else {
371+
$constraintName = $table . '_pkey';
372+
}
367373

368374
return $this->getDropConstraintSQL($constraintName, $table);
369375
}

src/Platforms/SQLServerPlatform.php

Lines changed: 25 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
use Doctrine\DBAL\Types\Types;
2323
use InvalidArgumentException;
2424

25+
use function array_map;
2526
use function array_merge;
2627
use function array_unique;
2728
use function array_values;
@@ -406,18 +407,20 @@ public function getAlterTableSQL(TableDiff $diff): array
406407
$tableNameSQL = $table->getQuotedName($this);
407408

408409
foreach ($diff->getChangedColumns() as $columnDiff) {
409-
$newColumn = $columnDiff->getNewColumn();
410-
$newColumnName = $newColumn->getQuotedName($this);
410+
$newColumn = $columnDiff->getNewColumn();
411+
$oldColumn = $columnDiff->getOldColumn();
412+
$nameChanged = $columnDiff->hasNameChanged();
411413

412-
$oldColumn = $columnDiff->getOldColumn();
413-
$oldColumnName = $oldColumn->getQuotedName($this);
414-
$nameChanged = $columnDiff->hasNameChanged();
415-
416-
// Column names in SQL server are case insensitive and automatically uppercased on the server.
417414
if ($nameChanged) {
415+
// sp_rename accepts the old name as a qualified name, so it should be quoted.
416+
$oldColumnNameSQL = $oldColumn->getQuotedName($this);
417+
418+
// sp_rename accepts the new name as a literal value, so it cannot be quoted.
419+
$newColumnName = $newColumn->getName();
420+
418421
$sql = array_merge(
419422
$sql,
420-
$this->getRenameColumnSQL($tableNameSQL, $oldColumnName, $newColumnName),
423+
$this->getRenameColumnSQL($tableNameSQL, $oldColumnNameSQL, $newColumnName),
421424
);
422425
}
423426

@@ -492,11 +495,7 @@ public function getAlterTableSQL(TableDiff $diff): array
492495

493496
public function getRenameTableSQL(string $oldName, string $newName): string
494497
{
495-
return sprintf(
496-
'sp_rename %s, %s',
497-
$this->quoteStringLiteral($oldName),
498-
$this->quoteStringLiteral($newName),
499-
);
498+
return $this->getRenameSQL($oldName, $newName);
500499
}
501500

502501
/**
@@ -630,13 +629,7 @@ protected function getDropColumnCommentSQL(string $tableName, string $columnName
630629
*/
631630
protected function getRenameIndexSQL(string $oldIndexName, Index $index, string $tableName): array
632631
{
633-
return [sprintf(
634-
"EXEC sp_rename N'%s.%s', N'%s', N'INDEX'",
635-
$tableName,
636-
$oldIndexName,
637-
$index->getQuotedName($this),
638-
),
639-
];
632+
return [$this->getRenameSQL($tableName . '.' . $oldIndexName, $index->getName(), 'INDEX')];
640633
}
641634

642635
/**
@@ -650,12 +643,18 @@ protected function getRenameIndexSQL(string $oldIndexName, Index $index, string
650643
*/
651644
protected function getRenameColumnSQL(string $tableName, string $oldColumnName, string $newColumnName): array
652645
{
653-
return [sprintf(
654-
"EXEC sp_rename %s, %s, 'COLUMN'",
655-
$this->quoteStringLiteral($tableName . '.' . $oldColumnName),
656-
$this->quoteStringLiteral($newColumnName),
657-
),
658-
];
646+
return [$this->getRenameSQL($tableName . '.' . $oldColumnName, $newColumnName)];
647+
}
648+
649+
/**
650+
* Returns the SQL statement that will execute sp_rename with the given arguments.
651+
*/
652+
private function getRenameSQL(string ...$arguments): string
653+
{
654+
return 'EXEC sp_rename '
655+
. implode(', ', array_map(function (string $argument): string {
656+
return 'N' . $this->quoteStringLiteral($argument);
657+
}, $arguments));
659658
}
660659

661660
/**

src/Schema/SQLiteSchemaManager.php

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
use Doctrine\DBAL\Types\Type;
1414

1515
use function array_change_key_case;
16+
use function array_map;
1617
use function array_merge;
1718
use function assert;
1819
use function count;
@@ -361,9 +362,8 @@ protected function _getPortableTableForeignKeyDefinition(array $tableForeignKey)
361362

362363
private function parseColumnCollationFromSQL(string $column, string $sql): ?string
363364
{
364-
$pattern = '{(?:\W' . preg_quote($column) . '\W|\W'
365-
. preg_quote($this->platform->quoteSingleIdentifier($column))
366-
. '\W)[^,(]+(?:\([^()]+\)[^,]*)?(?:(?:DEFAULT|CHECK)\s*(?:\(.*?\))?[^,]*)*COLLATE\s+["\']?([^\s,"\')]+)}is';
365+
$pattern = '{' . $this->buildIdentifierPattern($column)
366+
. '[^,(]+(?:\([^()]+\)[^,]*)?(?:(?:DEFAULT|CHECK)\s*(?:\(.*?\))?[^,]*)*COLLATE\s+["\']?([^\s,"\')]+)}is';
367367

368368
if (preg_match($pattern, $sql, $match) !== 1) {
369369
return null;
@@ -375,9 +375,7 @@ private function parseColumnCollationFromSQL(string $column, string $sql): ?stri
375375
private function parseTableCommentFromSQL(string $table, string $sql): ?string
376376
{
377377
$pattern = '/\s* # Allow whitespace characters at start of line
378-
CREATE\sTABLE # Match "CREATE TABLE"
379-
(?:\W"' . preg_quote($this->platform->quoteSingleIdentifier($table), '/') . '"\W|\W' . preg_quote($table, '/')
380-
. '\W) # Match table name (quoted and unquoted)
378+
CREATE\sTABLE' . $this->buildIdentifierPattern($table) . '
381379
( # Start capture
382380
(?:\s*--[^\n]*\n?)+ # Capture anything that starts with whitespaces followed by -- until the end of the line(s)
383381
)/ix';
@@ -393,8 +391,8 @@ private function parseTableCommentFromSQL(string $table, string $sql): ?string
393391

394392
private function parseColumnCommentFromSQL(string $column, string $sql): string
395393
{
396-
$pattern = '{[\s(,](?:\W' . preg_quote($this->platform->quoteSingleIdentifier($column))
397-
. '\W|\W' . preg_quote($column) . '\W)(?:\([^)]*?\)|[^,(])*?,?((?:(?!\n))(?:\s*--[^\n]*\n?)+)}i';
394+
$pattern = '{[\s(,]' . $this->buildIdentifierPattern($column)
395+
. '(?:\([^)]*?\)|[^,(])*?,?((?:(?!\n))(?:\s*--[^\n]*\n?)+)}i';
398396

399397
if (preg_match($pattern, $sql, $match) !== 1) {
400398
return '';
@@ -406,6 +404,22 @@ private function parseColumnCommentFromSQL(string $column, string $sql): string
406404
return $comment;
407405
}
408406

407+
/**
408+
* Returns a regular expression pattern that matches the given unquoted or quoted identifier.
409+
*/
410+
private function buildIdentifierPattern(string $identifier): string
411+
{
412+
return '(?:' . implode('|', array_map(
413+
static function (string $sql): string {
414+
return '\W' . preg_quote($sql, '/') . '\W';
415+
},
416+
[
417+
$identifier,
418+
$this->platform->quoteSingleIdentifier($identifier),
419+
],
420+
)) . ')';
421+
}
422+
409423
/** @throws Exception */
410424
private function getCreateTableSQL(string $table): string
411425
{

0 commit comments

Comments
 (0)