Skip to content

Commit

Permalink
Merge branch '4.3.x' into 5.0.x
Browse files Browse the repository at this point in the history
  • Loading branch information
morozov committed Nov 15, 2024
2 parents 6456ffd + e68422d commit 57fc66f
Show file tree
Hide file tree
Showing 17 changed files with 830 additions and 61 deletions.
13 changes: 7 additions & 6 deletions .appveyor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,15 +40,15 @@ init:

## Install PHP and composer, and run the appropriate composer command
install:
# Upgrade to 0.10.16-beta to benefit from a bugfix for
# https://github.com/chocolatey/choco/issues/1843
- ps: choco upgrade chocolatey --pre
- sc config wuauserv start=auto
- net start wuauserv
- ps: |
# Check if installation is cached
if (!(Test-Path c:\tools\php)) {
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|]','')
choco upgrade chocolatey
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|]','')
# install sqlite
appveyor-retry choco install -y sqlite
appveyor-retry choco install --no-progress -y sqlite
Get-ChildItem -Path c:\tools\php
cd c:\tools\php
Expand Down Expand Up @@ -122,4 +122,5 @@ test_script:
after_test:
- appveyor DownloadFile https://codecov.io/bash -FileName codecov.sh
- bash codecov.sh -f clover.xml
- SET upload_name=appveyor-%db%-%db_version%-%driver%-php-%php%
- bash codecov.sh -f clover.xml -n %upload_name%
1 change: 1 addition & 0 deletions .github/workflows/continuous-integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -652,5 +652,6 @@ jobs:
with:
directory: reports
fail_ci_if_error: true
name: github-action
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
55 changes: 44 additions & 11 deletions src/Connection.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,17 @@
use Doctrine\DBAL\Connection\StaticServerVersionProvider;
use Doctrine\DBAL\Driver\API\ExceptionConverter;
use Doctrine\DBAL\Driver\Connection as DriverConnection;
use Doctrine\DBAL\Driver\Exception as TheDriverException;
use Doctrine\DBAL\Driver\Statement as DriverStatement;
use Doctrine\DBAL\Exception\CommitFailedRollbackOnly;
use Doctrine\DBAL\Exception\ConnectionLost;
use Doctrine\DBAL\Exception\DeadlockException;
use Doctrine\DBAL\Exception\DriverException;
use Doctrine\DBAL\Exception\ForeignKeyConstraintViolationException;
use Doctrine\DBAL\Exception\NoActiveTransaction;
use Doctrine\DBAL\Exception\SavepointsNotSupported;
use Doctrine\DBAL\Exception\TransactionRolledBack;
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Query\Expression\ExpressionBuilder;
use Doctrine\DBAL\Query\QueryBuilder;
Expand Down Expand Up @@ -922,16 +927,35 @@ public function transactional(Closure $func): mixed

try {
$res = $func($this);
$this->commit();

$successful = true;

return $res;
} finally {
if (! $successful) {
$this->rollBack();
}
}

$shouldRollback = true;
try {
$this->commit();

$shouldRollback = false;
} catch (TheDriverException $t) {
$shouldRollback = ! (
$t instanceof TransactionRolledBack
|| $t instanceof UniqueConstraintViolationException
|| $t instanceof ForeignKeyConstraintViolationException
|| $t instanceof DeadlockException
);

throw $t;
} finally {
if ($shouldRollback) {
$this->rollBack();
}
}

return $res;
}

/**
Expand Down Expand Up @@ -1010,17 +1034,26 @@ public function commit(): void

$connection = $this->connect();

if ($this->transactionNestingLevel === 1) {
try {
$connection->commit();
} catch (Driver\Exception $e) {
throw $this->convertException($e);
try {
if ($this->transactionNestingLevel === 1) {
try {
$connection->commit();
} catch (Driver\Exception $e) {
throw $this->convertException($e);
}
} else {
$this->releaseSavepoint($this->_getNestedTransactionSavePointName());
}
} else {
$this->releaseSavepoint($this->_getNestedTransactionSavePointName());
} finally {
$this->updateTransactionStateAfterCommit();
}
}

--$this->transactionNestingLevel;
private function updateTransactionStateAfterCommit(): void
{
if ($this->transactionNestingLevel !== 0) {
--$this->transactionNestingLevel;
}

if ($this->autoCommit !== false || $this->transactionNestingLevel !== 0) {
return;
Expand Down
28 changes: 28 additions & 0 deletions src/Driver/API/OCI/ExceptionConverter.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

use Doctrine\DBAL\Driver\API\ExceptionConverter as ExceptionConverterInterface;
use Doctrine\DBAL\Driver\Exception;
use Doctrine\DBAL\Driver\OCI8\Exception\Error;
use Doctrine\DBAL\Driver\PDO\Exception as DriverPDOException;
use Doctrine\DBAL\Exception\ConnectionException;
use Doctrine\DBAL\Exception\DatabaseDoesNotExist;
use Doctrine\DBAL\Exception\DatabaseObjectNotFoundException;
Expand All @@ -17,9 +19,15 @@
use Doctrine\DBAL\Exception\SyntaxErrorException;
use Doctrine\DBAL\Exception\TableExistsException;
use Doctrine\DBAL\Exception\TableNotFoundException;
use Doctrine\DBAL\Exception\TransactionRolledBack;
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
use Doctrine\DBAL\Query;

use function assert;
use function count;
use function explode;
use function str_replace;

/** @internal */
final class ExceptionConverter implements ExceptionConverterInterface
{
Expand All @@ -40,6 +48,26 @@ public function convert(Exception $exception, ?Query $query): DriverException
12545 => new ConnectionException($exception, $query),
1400 => new NotNullConstraintViolationException($exception, $query),
1918 => new DatabaseDoesNotExist($exception, $query),
2091 => (function () use ($exception, $query) {
//SQLSTATE[HY000]: General error: 2091 OCITransCommit: ORA-02091: transaction rolled back
//ORA-00001: unique constraint (DOCTRINE.GH3423_UNIQUE) violated
$lines = explode("\n", $exception->getMessage(), 2);
assert(count($lines) >= 2);

[, $causeError] = $lines;

[$causeCode] = explode(': ', $causeError, 2);
$code = (int) str_replace('ORA-', '', $causeCode);

$sqlState = $exception->getSQLState();
if ($exception instanceof DriverPDOException) {
$why = $this->convert(new DriverPDOException($causeError, $sqlState, $code, $exception), $query);
} else {
$why = $this->convert(new Error($causeError, $sqlState, $code, $exception), $query);
}

return new TransactionRolledBack($why, $query);
})(),
2289,
2443,
4080 => new DatabaseObjectNotFoundException($exception, $query),
Expand Down
2 changes: 1 addition & 1 deletion src/Driver/OCI8/Connection.php
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ public function beginTransaction(): void

public function commit(): void
{
if (! oci_commit($this->connection)) {
if (! @oci_commit($this->connection)) {
throw Error::new($this->connection);
}

Expand Down
1 change: 1 addition & 0 deletions src/Driver/OCI8/Result.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
use function oci_error;
use function oci_fetch_all;
use function oci_fetch_array;
use function oci_field_name;
use function oci_num_fields;
use function oci_num_rows;

Expand Down
10 changes: 10 additions & 0 deletions src/Exception/TransactionRolledBack.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

declare(strict_types=1);

namespace Doctrine\DBAL\Exception;

/** @psalm-immutable */
class TransactionRolledBack extends DriverException
{
}
8 changes: 7 additions & 1 deletion src/Platforms/PostgreSQLPlatform.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@
use function is_string;
use function sprintf;
use function str_contains;
use function str_ends_with;
use function strtolower;
use function substr;
use function trim;

/**
Expand Down Expand Up @@ -363,7 +365,11 @@ public function getDropForeignKeySQL(string $foreignKey, string $table): string
public function getDropIndexSQL(string $name, string $table): string
{
if ($name === '"primary"') {
$constraintName = $table . '_pkey';
if (str_ends_with($table, '"')) {
$constraintName = substr($table, 0, -1) . '_pkey"';
} else {
$constraintName = $table . '_pkey';
}

return $this->getDropConstraintSQL($constraintName, $table);
}
Expand Down
51 changes: 25 additions & 26 deletions src/Platforms/SQLServerPlatform.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
use Doctrine\DBAL\Types\Types;
use InvalidArgumentException;

use function array_map;
use function array_merge;
use function array_unique;
use function array_values;
Expand Down Expand Up @@ -406,18 +407,20 @@ public function getAlterTableSQL(TableDiff $diff): array
$tableNameSQL = $table->getQuotedName($this);

foreach ($diff->getChangedColumns() as $columnDiff) {
$newColumn = $columnDiff->getNewColumn();
$newColumnName = $newColumn->getQuotedName($this);
$newColumn = $columnDiff->getNewColumn();
$oldColumn = $columnDiff->getOldColumn();
$nameChanged = $columnDiff->hasNameChanged();

$oldColumn = $columnDiff->getOldColumn();
$oldColumnName = $oldColumn->getQuotedName($this);
$nameChanged = $columnDiff->hasNameChanged();

// Column names in SQL server are case insensitive and automatically uppercased on the server.
if ($nameChanged) {
// sp_rename accepts the old name as a qualified name, so it should be quoted.
$oldColumnNameSQL = $oldColumn->getQuotedName($this);

// sp_rename accepts the new name as a literal value, so it cannot be quoted.
$newColumnName = $newColumn->getName();

$sql = array_merge(
$sql,
$this->getRenameColumnSQL($tableNameSQL, $oldColumnName, $newColumnName),
$this->getRenameColumnSQL($tableNameSQL, $oldColumnNameSQL, $newColumnName),
);
}

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

public function getRenameTableSQL(string $oldName, string $newName): string
{
return sprintf(
'sp_rename %s, %s',
$this->quoteStringLiteral($oldName),
$this->quoteStringLiteral($newName),
);
return $this->getRenameSQL($oldName, $newName);
}

/**
Expand Down Expand Up @@ -630,13 +629,7 @@ protected function getDropColumnCommentSQL(string $tableName, string $columnName
*/
protected function getRenameIndexSQL(string $oldIndexName, Index $index, string $tableName): array
{
return [sprintf(
"EXEC sp_rename N'%s.%s', N'%s', N'INDEX'",
$tableName,
$oldIndexName,
$index->getQuotedName($this),
),
];
return [$this->getRenameSQL($tableName . '.' . $oldIndexName, $index->getName(), 'INDEX')];
}

/**
Expand All @@ -650,12 +643,18 @@ protected function getRenameIndexSQL(string $oldIndexName, Index $index, string
*/
protected function getRenameColumnSQL(string $tableName, string $oldColumnName, string $newColumnName): array
{
return [sprintf(
"EXEC sp_rename %s, %s, 'COLUMN'",
$this->quoteStringLiteral($tableName . '.' . $oldColumnName),
$this->quoteStringLiteral($newColumnName),
),
];
return [$this->getRenameSQL($tableName . '.' . $oldColumnName, $newColumnName)];
}

/**
* Returns the SQL statement that will execute sp_rename with the given arguments.
*/
private function getRenameSQL(string ...$arguments): string
{
return 'EXEC sp_rename '
. implode(', ', array_map(function (string $argument): string {
return 'N' . $this->quoteStringLiteral($argument);
}, $arguments));
}

/**
Expand Down
30 changes: 22 additions & 8 deletions src/Schema/SQLiteSchemaManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
use Doctrine\DBAL\Types\Type;

use function array_change_key_case;
use function array_map;
use function array_merge;
use function assert;
use function count;
Expand Down Expand Up @@ -361,9 +362,8 @@ protected function _getPortableTableForeignKeyDefinition(array $tableForeignKey)

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

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

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

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

/**
* Returns a regular expression pattern that matches the given unquoted or quoted identifier.
*/
private function buildIdentifierPattern(string $identifier): string
{
return '(?:' . implode('|', array_map(
static function (string $sql): string {
return '\W' . preg_quote($sql, '/') . '\W';
},
[
$identifier,
$this->platform->quoteSingleIdentifier($identifier),
],
)) . ')';
}

/** @throws Exception */
private function getCreateTableSQL(string $table): string
{
Expand Down
Loading

0 comments on commit 57fc66f

Please sign in to comment.