Skip to content

Merge 4.3.x up into 5.0.x #6606

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

Merged
merged 28 commits into from
Nov 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
976f3f3
Cover how `transactional()` behaves in different auto commit modes (#…
simPod Oct 31, 2024
ec90463
Fix incorrect `transactional()` handling when DB auto-rolled back the…
simPod Oct 12, 2024
c90982d
Distinguish between Appveyor and github-action
greg0ire Nov 2, 2024
2161a70
Merge pull request #6580 from greg0ire/name-upload
greg0ire Nov 3, 2024
dd4601c
Merge pull request #6545 from simPod/df-c_v3
greg0ire Nov 4, 2024
bb23c9d
Merge pull request #6584 from doctrine/3.9.x
greg0ire Nov 4, 2024
f57a8ff
Merge commit '2161a70e7' into 4.2.x
greg0ire Nov 4, 2024
de96e05
savepoint support is required in v4 for transaction nesting
simPod Nov 5, 2024
6061e27
Fix incorrect `transactional()` handling when DB auto-rolled back the…
simPod Oct 12, 2024
9326373
Merge pull request #9 from simPod/adapt-42
greg0ire Nov 5, 2024
5ddafce
Merge pull request #6546 from simPod/df-c_v4
greg0ire Nov 5, 2024
dae8c84
Merge pull request #6585 from greg0ire/4.2.x
greg0ire Nov 5, 2024
cfb5d3c
Merge remote-tracking branch 'origin/3.9.x' into 4.2.x
greg0ire Nov 5, 2024
9a3d08d
Merge pull request #6587 from greg0ire/4.2.x
greg0ire Nov 5, 2024
9b77907
Fix dropping the PK on a PostgreSQL table with quoted name
morozov Nov 14, 2024
727bd6b
Merge pull request #6599 from morozov/postgresql-quoted-drop-pk
morozov Nov 14, 2024
42d052e
Enable Windows Update service on AppVeyor
morozov Nov 14, 2024
ab9d446
Disable Choco download progress
morozov Nov 14, 2024
395269a
Fix parsing quoted table name on SQLite
morozov Nov 14, 2024
836b5f8
Fix renaming to quoted names on SQL Server
morozov Nov 10, 2024
1300542
Generalize the logic of sp_rename execution
morozov Nov 15, 2024
232fd06
Merge pull request #6601 from morozov/appveyor-wuauserv
greg0ire Nov 15, 2024
579c4dd
Merge pull request #6602 from morozov/sqlserver-rename-to-quoted
morozov Nov 15, 2024
34ea36f
Merge pull request #6600 from morozov/sqlite-quoted-table-comment
morozov Nov 15, 2024
e6986fe
Merge branch '3.9.x' into 3.10.x
morozov Nov 15, 2024
1a30708
Merge branch '3.10.x' into 4.2.x
morozov Nov 15, 2024
e68422d
Merge branch '4.2.x' into 4.3.x
morozov Nov 15, 2024
57fc66f
Merge branch '4.3.x' into 5.0.x
morozov Nov 15, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@

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
);

Check warning on line 949 in src/Connection.php

View check run for this annotation

Codecov / codecov/patch

src/Connection.php#L943-L949

Added lines #L943 - L949 were not covered by tests

throw $t;

Check warning on line 951 in src/Connection.php

View check run for this annotation

Codecov / codecov/patch

src/Connection.php#L951

Added line #L951 was not covered by tests
} finally {
if ($shouldRollback) {
$this->rollBack();

Check warning on line 954 in src/Connection.php

View check run for this annotation

Codecov / codecov/patch

src/Connection.php#L954

Added line #L954 was not covered by tests
}
}

return $res;
}

/**
Expand Down Expand Up @@ -1010,17 +1034,26 @@

$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) {

Check warning on line 1041 in src/Connection.php

View check run for this annotation

Codecov / codecov/patch

src/Connection.php#L1041

Added line #L1041 was not covered by tests
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 @@
12545 => new ConnectionException($exception, $query),
1400 => new NotNullConstraintViolationException($exception, $query),
1918 => new DatabaseDoesNotExist($exception, $query),
2091 => (function () use ($exception, $query) {

Check warning on line 51 in src/Driver/API/OCI/ExceptionConverter.php

View check run for this annotation

Codecov / codecov/patch

src/Driver/API/OCI/ExceptionConverter.php#L51

Added line #L51 was not covered by tests
//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);

Check warning on line 54 in src/Driver/API/OCI/ExceptionConverter.php

View check run for this annotation

Codecov / codecov/patch

src/Driver/API/OCI/ExceptionConverter.php#L54

Added line #L54 was not covered by tests
assert(count($lines) >= 2);

[, $causeError] = $lines;

Check warning on line 57 in src/Driver/API/OCI/ExceptionConverter.php

View check run for this annotation

Codecov / codecov/patch

src/Driver/API/OCI/ExceptionConverter.php#L57

Added line #L57 was not covered by tests

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

Check warning on line 60 in src/Driver/API/OCI/ExceptionConverter.php

View check run for this annotation

Codecov / codecov/patch

src/Driver/API/OCI/ExceptionConverter.php#L59-L60

Added lines #L59 - L60 were not covered by tests

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

Check warning on line 64 in src/Driver/API/OCI/ExceptionConverter.php

View check run for this annotation

Codecov / codecov/patch

src/Driver/API/OCI/ExceptionConverter.php#L62-L64

Added lines #L62 - L64 were not covered by tests
} else {
$why = $this->convert(new Error($causeError, $sqlState, $code, $exception), $query);

Check warning on line 66 in src/Driver/API/OCI/ExceptionConverter.php

View check run for this annotation

Codecov / codecov/patch

src/Driver/API/OCI/ExceptionConverter.php#L66

Added line #L66 was not covered by tests
}

return new TransactionRolledBack($why, $query);
})(),

Check warning on line 70 in src/Driver/API/OCI/ExceptionConverter.php

View check run for this annotation

Codecov / codecov/patch

src/Driver/API/OCI/ExceptionConverter.php#L69-L70

Added lines #L69 - L70 were not covered by tests
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 commit(): void
{
if (! oci_commit($this->connection)) {
if (! @oci_commit($this->connection)) {

Check warning on line 98 in src/Driver/OCI8/Connection.php

View check run for this annotation

Codecov / codecov/patch

src/Driver/OCI8/Connection.php#L98

Added line #L98 was not covered by tests
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 @@

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 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) . '

Check warning on line 378 in src/Schema/SQLiteSchemaManager.php

View check run for this annotation

Codecov / codecov/patch

src/Schema/SQLiteSchemaManager.php#L378

Added line #L378 was not covered by tests
( # 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 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 @@
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