mirror of
https://gitlab.com/signalytic/client-external/streamline/streamline-emr.git
synced 2026-09-13 03:31:31 +00:00
resolved conflicts
This commit is contained in:
@@ -0,0 +1,50 @@
|
||||
# Doctrine DBAL
|
||||
|
||||
| [5.0-dev][5.0] | [4.3-dev][4.3] | [4.2][4.2] | [3.9][3.9] |
|
||||
|:---------------------------------------------------:|:---------------------------------------------------:|:---------------------------------------------------:|:---------------------------------------------------:|
|
||||
| [![GitHub Actions][GA 5.0 image]][GA 5.0] | [![GitHub Actions][GA 4.3 image]][GA 4.3] | [![GitHub Actions][GA 4.2 image]][GA 4.2] | [![GitHub Actions][GA 3.9 image]][GA 3.9] |
|
||||
| [![AppVeyor][AppVeyor 5.0 image]][AppVeyor 5.0] | [![AppVeyor][AppVeyor 4.3 image]][AppVeyor 4.3] | [![AppVeyor][AppVeyor 4.2 image]][AppVeyor 4.2] | [![AppVeyor][AppVeyor 3.9 image]][AppVeyor 3.9] |
|
||||
| [![Code Coverage][Coverage 5.0 image]][CodeCov 5.0] | [![Code Coverage][Coverage 4.3 image]][CodeCov 4.3] | [![Code Coverage][Coverage 4.2 image]][CodeCov 4.2] | [![Code Coverage][Coverage 3.9 image]][CodeCov 3.9] |
|
||||
| N/A | N/A | [![Type Coverage][TypeCov image]][TypeCov] | N/A |
|
||||
|
||||
Powerful ***D***ata***B***ase ***A***bstraction ***L***ayer with many features for database schema introspection and schema management.
|
||||
|
||||
## More resources:
|
||||
|
||||
* [Website](http://www.doctrine-project.org/projects/dbal.html)
|
||||
* [Documentation](http://docs.doctrine-project.org/projects/doctrine-dbal/en/latest/)
|
||||
* [Issue Tracker](https://github.com/doctrine/dbal/issues)
|
||||
|
||||
[Coverage 5.0 image]: https://codecov.io/gh/doctrine/dbal/branch/5.0.x/graph/badge.svg
|
||||
[5.0]: https://github.com/doctrine/dbal/tree/5.0.x
|
||||
[CodeCov 5.0]: https://codecov.io/gh/doctrine/dbal/branch/5.0.x
|
||||
[AppVeyor 5.0]: https://ci.appveyor.com/project/doctrine/dbal/branch/5.0.x
|
||||
[AppVeyor 5.0 image]: https://ci.appveyor.com/api/projects/status/i88kitq8qpbm0vie/branch/5.0.x?svg=true
|
||||
[GA 5.0]: https://github.com/doctrine/dbal/actions?query=workflow%3A%22Continuous+Integration%22+branch%3A5.0.x
|
||||
[GA 5.0 image]: https://github.com/doctrine/dbal/workflows/Continuous%20Integration/badge.svg?branch=5.0.x
|
||||
|
||||
[Coverage 4.3 image]: https://codecov.io/gh/doctrine/dbal/branch/4.3.x/graph/badge.svg
|
||||
[4.3]: https://github.com/doctrine/dbal/tree/4.3.x
|
||||
[CodeCov 4.3]: https://codecov.io/gh/doctrine/dbal/branch/4.3.x
|
||||
[AppVeyor 4.3]: https://ci.appveyor.com/project/doctrine/dbal/branch/4.3.x
|
||||
[AppVeyor 4.3 image]: https://ci.appveyor.com/api/projects/status/i88kitq8qpbm0vie/branch/4.3.x?svg=true
|
||||
[GA 4.3]: https://github.com/doctrine/dbal/actions?query=workflow%3A%22Continuous+Integration%22+branch%3A4.3.x
|
||||
[GA 4.3 image]: https://github.com/doctrine/dbal/workflows/Continuous%20Integration/badge.svg?branch=4.3.x
|
||||
|
||||
[Coverage 4.2 image]: https://codecov.io/gh/doctrine/dbal/branch/4.2.x/graph/badge.svg
|
||||
[4.2]: https://github.com/doctrine/dbal/tree/4.2.x
|
||||
[CodeCov 4.2]: https://codecov.io/gh/doctrine/dbal/branch/4.2.x
|
||||
[AppVeyor 4.2]: https://ci.appveyor.com/project/doctrine/dbal/branch/4.2.x
|
||||
[AppVeyor 4.2 image]: https://ci.appveyor.com/api/projects/status/i88kitq8qpbm0vie/branch/4.2.x?svg=true
|
||||
[GA 4.2]: https://github.com/doctrine/dbal/actions?query=workflow%3A%22Continuous+Integration%22+branch%3A4.2.x
|
||||
[GA 4.2 image]: https://github.com/doctrine/dbal/workflows/Continuous%20Integration/badge.svg?branch=4.2.x
|
||||
[TypeCov]: https://shepherd.dev/github/doctrine/dbal
|
||||
[TypeCov image]: https://shepherd.dev/github/doctrine/dbal/coverage.svg
|
||||
|
||||
[Coverage 3.9 image]: https://codecov.io/gh/doctrine/dbal/branch/3.9.x/graph/badge.svg
|
||||
[3.9]: https://github.com/doctrine/dbal/tree/3.9.x
|
||||
[CodeCov 3.9]: https://codecov.io/gh/doctrine/dbal/branch/3.9.x
|
||||
[AppVeyor 3.9]: https://ci.appveyor.com/project/doctrine/dbal/branch/3.9.x
|
||||
[AppVeyor 3.9 image]: https://ci.appveyor.com/api/projects/status/i88kitq8qpbm0vie/branch/3.9.x?svg=true
|
||||
[GA 3.9]: https://github.com/doctrine/dbal/actions?query=workflow%3A%22Continuous+Integration%22+branch%3A3.9.x
|
||||
[GA 3.9 image]: https://github.com/doctrine/dbal/workflows/Continuous%20Integration/badge.svg?branch=3.9.x
|
||||
@@ -0,0 +1,73 @@
|
||||
{
|
||||
"name": "doctrine/dbal",
|
||||
"type": "library",
|
||||
"description": "Powerful PHP database abstraction layer (DBAL) with many features for database schema introspection and management.",
|
||||
"keywords": [
|
||||
"abstraction",
|
||||
"database",
|
||||
"dbal",
|
||||
"db2",
|
||||
"mariadb",
|
||||
"mssql",
|
||||
"mysql",
|
||||
"pgsql",
|
||||
"postgresql",
|
||||
"oci8",
|
||||
"oracle",
|
||||
"pdo",
|
||||
"queryobject",
|
||||
"sasql",
|
||||
"sql",
|
||||
"sqlite",
|
||||
"sqlserver",
|
||||
"sqlsrv"
|
||||
],
|
||||
"homepage": "https://www.doctrine-project.org/projects/dbal.html",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{"name": "Guilherme Blanco", "email": "guilhermeblanco@gmail.com"},
|
||||
{"name": "Roman Borschel", "email": "roman@code-factory.org"},
|
||||
{"name": "Benjamin Eberlei", "email": "kontakt@beberlei.de"},
|
||||
{"name": "Jonathan Wage", "email": "jonwage@gmail.com"}
|
||||
],
|
||||
"require": {
|
||||
"php": "^7.4 || ^8.0",
|
||||
"composer-runtime-api": "^2",
|
||||
"doctrine/cache": "^1.11|^2.0",
|
||||
"doctrine/deprecations": "^0.5.3|^1",
|
||||
"doctrine/event-manager": "^1|^2",
|
||||
"psr/cache": "^1|^2|^3",
|
||||
"psr/log": "^1|^2|^3"
|
||||
},
|
||||
"require-dev": {
|
||||
"doctrine/coding-standard": "12.0.0",
|
||||
"fig/log-test": "^1",
|
||||
"jetbrains/phpstorm-stubs": "2023.1",
|
||||
"phpstan/phpstan": "1.12.6",
|
||||
"phpstan/phpstan-strict-rules": "^1.6",
|
||||
"phpunit/phpunit": "9.6.20",
|
||||
"psalm/plugin-phpunit": "0.18.4",
|
||||
"slevomat/coding-standard": "8.13.1",
|
||||
"squizlabs/php_codesniffer": "3.10.2",
|
||||
"symfony/cache": "^5.4|^6.0|^7.0",
|
||||
"symfony/console": "^4.4|^5.4|^6.0|^7.0",
|
||||
"vimeo/psalm": "4.30.0"
|
||||
},
|
||||
"suggest": {
|
||||
"symfony/console": "For helpful console commands such as SQL execution and import of files."
|
||||
},
|
||||
"bin": ["bin/doctrine-dbal"],
|
||||
"config": {
|
||||
"sort-packages": true,
|
||||
"allow-plugins": {
|
||||
"dealerdirect/phpcodesniffer-composer-installer": true,
|
||||
"composer/package-versions-deprecated": true
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": { "Doctrine\\DBAL\\": "src" }
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": { "Doctrine\\DBAL\\Tests\\": "tests" }
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
+120
@@ -0,0 +1,120 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\DBAL\Driver\API\MySQL;
|
||||
|
||||
use Doctrine\DBAL\Driver\API\ExceptionConverter as ExceptionConverterInterface;
|
||||
use Doctrine\DBAL\Driver\Exception;
|
||||
use Doctrine\DBAL\Exception\ConnectionException;
|
||||
use Doctrine\DBAL\Exception\ConnectionLost;
|
||||
use Doctrine\DBAL\Exception\DatabaseDoesNotExist;
|
||||
use Doctrine\DBAL\Exception\DeadlockException;
|
||||
use Doctrine\DBAL\Exception\DriverException;
|
||||
use Doctrine\DBAL\Exception\ForeignKeyConstraintViolationException;
|
||||
use Doctrine\DBAL\Exception\InvalidFieldNameException;
|
||||
use Doctrine\DBAL\Exception\LockWaitTimeoutException;
|
||||
use Doctrine\DBAL\Exception\NonUniqueFieldNameException;
|
||||
use Doctrine\DBAL\Exception\NotNullConstraintViolationException;
|
||||
use Doctrine\DBAL\Exception\SyntaxErrorException;
|
||||
use Doctrine\DBAL\Exception\TableExistsException;
|
||||
use Doctrine\DBAL\Exception\TableNotFoundException;
|
||||
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
|
||||
use Doctrine\DBAL\Query;
|
||||
|
||||
/** @internal */
|
||||
final class ExceptionConverter implements ExceptionConverterInterface
|
||||
{
|
||||
/**
|
||||
* @link https://dev.mysql.com/doc/mysql-errors/8.0/en/client-error-reference.html
|
||||
* @link https://dev.mysql.com/doc/mysql-errors/8.0/en/server-error-reference.html
|
||||
*/
|
||||
public function convert(Exception $exception, ?Query $query): DriverException
|
||||
{
|
||||
switch ($exception->getCode()) {
|
||||
case 1008:
|
||||
return new DatabaseDoesNotExist($exception, $query);
|
||||
|
||||
case 1213:
|
||||
return new DeadlockException($exception, $query);
|
||||
|
||||
case 1205:
|
||||
return new LockWaitTimeoutException($exception, $query);
|
||||
|
||||
case 1050:
|
||||
return new TableExistsException($exception, $query);
|
||||
|
||||
case 1051:
|
||||
case 1146:
|
||||
return new TableNotFoundException($exception, $query);
|
||||
|
||||
case 1216:
|
||||
case 1217:
|
||||
case 1451:
|
||||
case 1452:
|
||||
case 1701:
|
||||
return new ForeignKeyConstraintViolationException($exception, $query);
|
||||
|
||||
case 1062:
|
||||
case 1557:
|
||||
case 1569:
|
||||
case 1586:
|
||||
return new UniqueConstraintViolationException($exception, $query);
|
||||
|
||||
case 1054:
|
||||
case 1166:
|
||||
case 1611:
|
||||
return new InvalidFieldNameException($exception, $query);
|
||||
|
||||
case 1052:
|
||||
case 1060:
|
||||
case 1110:
|
||||
return new NonUniqueFieldNameException($exception, $query);
|
||||
|
||||
case 1064:
|
||||
case 1149:
|
||||
case 1287:
|
||||
case 1341:
|
||||
case 1342:
|
||||
case 1343:
|
||||
case 1344:
|
||||
case 1382:
|
||||
case 1479:
|
||||
case 1541:
|
||||
case 1554:
|
||||
case 1626:
|
||||
return new SyntaxErrorException($exception, $query);
|
||||
|
||||
case 1044:
|
||||
case 1045:
|
||||
case 1046:
|
||||
case 1049:
|
||||
case 1095:
|
||||
case 1142:
|
||||
case 1143:
|
||||
case 1227:
|
||||
case 1370:
|
||||
case 1429:
|
||||
case 2002:
|
||||
case 2005:
|
||||
case 2054:
|
||||
return new ConnectionException($exception, $query);
|
||||
|
||||
case 2006:
|
||||
case 4031:
|
||||
return new ConnectionLost($exception, $query);
|
||||
|
||||
case 1048:
|
||||
case 1121:
|
||||
case 1138:
|
||||
case 1171:
|
||||
case 1252:
|
||||
case 1263:
|
||||
case 1364:
|
||||
case 1566:
|
||||
return new NotNullConstraintViolationException($exception, $query);
|
||||
}
|
||||
|
||||
return new DriverException($exception, $query);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
<?php
|
||||
|
||||
namespace Doctrine\DBAL\Driver;
|
||||
|
||||
use Doctrine\DBAL\Connection;
|
||||
use Doctrine\DBAL\Driver\API\ExceptionConverter as ExceptionConverterInterface;
|
||||
use Doctrine\DBAL\Driver\API\IBMDB2\ExceptionConverter;
|
||||
use Doctrine\DBAL\Exception as DBALException;
|
||||
use Doctrine\DBAL\Platforms\AbstractPlatform;
|
||||
use Doctrine\DBAL\Platforms\DB2111Platform;
|
||||
use Doctrine\DBAL\Platforms\DB2Platform;
|
||||
use Doctrine\DBAL\Schema\DB2SchemaManager;
|
||||
use Doctrine\DBAL\VersionAwarePlatformDriver;
|
||||
use Doctrine\Deprecations\Deprecation;
|
||||
|
||||
use function assert;
|
||||
use function preg_match;
|
||||
use function version_compare;
|
||||
|
||||
/**
|
||||
* Abstract base implementation of the {@see Driver} interface for IBM DB2 based drivers.
|
||||
*/
|
||||
abstract class AbstractDB2Driver implements VersionAwarePlatformDriver
|
||||
{
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function getDatabasePlatform()
|
||||
{
|
||||
return new DB2Platform();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @deprecated Use {@link DB2Platform::createSchemaManager()} instead.
|
||||
*/
|
||||
public function getSchemaManager(Connection $conn, AbstractPlatform $platform)
|
||||
{
|
||||
Deprecation::triggerIfCalledFromOutside(
|
||||
'doctrine/dbal',
|
||||
'https://github.com/doctrine/dbal/pull/5458',
|
||||
'AbstractDB2Driver::getSchemaManager() is deprecated.'
|
||||
. ' Use DB2Platform::createSchemaManager() instead.',
|
||||
);
|
||||
|
||||
assert($platform instanceof DB2Platform);
|
||||
|
||||
return new DB2SchemaManager($conn, $platform);
|
||||
}
|
||||
|
||||
public function getExceptionConverter(): ExceptionConverterInterface
|
||||
{
|
||||
return new ExceptionConverter();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function createDatabasePlatformForVersion($version)
|
||||
{
|
||||
if (version_compare($this->getVersionNumber($version), '11.1', '>=')) {
|
||||
return new DB2111Platform();
|
||||
}
|
||||
|
||||
Deprecation::trigger(
|
||||
'doctrine/dbal',
|
||||
'https://github.com/doctrine/dbal/pull/5156',
|
||||
'IBM DB2 < 11.1 support is deprecated and will be removed in DBAL 4.'
|
||||
. ' Consider upgrading to IBM DB2 11.1 or later.',
|
||||
);
|
||||
|
||||
return $this->getDatabasePlatform();
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects IBM DB2 server version
|
||||
*
|
||||
* @param string $versionString Version string as returned by IBM DB2 server, i.e. 'DB2/LINUXX8664 11.5.8.0'
|
||||
*
|
||||
* @throws DBALException
|
||||
*/
|
||||
private function getVersionNumber(string $versionString): string
|
||||
{
|
||||
if (
|
||||
preg_match(
|
||||
'/^(?:[^\s]+\s)?(?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)/i',
|
||||
$versionString,
|
||||
$versionParts,
|
||||
) !== 1
|
||||
) {
|
||||
throw DBALException::invalidPlatformVersionSpecified(
|
||||
$versionString,
|
||||
'^(?:[^\s]+\s)?<major_version>.<minor_version>.<patch_version>',
|
||||
);
|
||||
}
|
||||
|
||||
return $versionParts['major'] . '.' . $versionParts['minor'] . '.' . $versionParts['patch'];
|
||||
}
|
||||
}
|
||||
+231
@@ -0,0 +1,231 @@
|
||||
<?php
|
||||
|
||||
namespace Doctrine\DBAL\Driver;
|
||||
|
||||
use Doctrine\DBAL\Connection;
|
||||
use Doctrine\DBAL\Driver\API\ExceptionConverter;
|
||||
use Doctrine\DBAL\Driver\API\MySQL;
|
||||
use Doctrine\DBAL\Exception;
|
||||
use Doctrine\DBAL\Platforms\AbstractMySQLPlatform;
|
||||
use Doctrine\DBAL\Platforms\AbstractPlatform;
|
||||
use Doctrine\DBAL\Platforms\MariaDb1010Platform;
|
||||
use Doctrine\DBAL\Platforms\MariaDb1027Platform;
|
||||
use Doctrine\DBAL\Platforms\MariaDb1043Platform;
|
||||
use Doctrine\DBAL\Platforms\MariaDb1052Platform;
|
||||
use Doctrine\DBAL\Platforms\MariaDb1060Platform;
|
||||
use Doctrine\DBAL\Platforms\MySQL57Platform;
|
||||
use Doctrine\DBAL\Platforms\MySQL80Platform;
|
||||
use Doctrine\DBAL\Platforms\MySQL84Platform;
|
||||
use Doctrine\DBAL\Platforms\MySQLPlatform;
|
||||
use Doctrine\DBAL\Schema\MySQLSchemaManager;
|
||||
use Doctrine\DBAL\VersionAwarePlatformDriver;
|
||||
use Doctrine\Deprecations\Deprecation;
|
||||
|
||||
use function assert;
|
||||
use function preg_match;
|
||||
use function stripos;
|
||||
use function version_compare;
|
||||
|
||||
/**
|
||||
* Abstract base implementation of the {@see Driver} interface for MySQL based drivers.
|
||||
*/
|
||||
abstract class AbstractMySQLDriver implements VersionAwarePlatformDriver
|
||||
{
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function createDatabasePlatformForVersion($version)
|
||||
{
|
||||
$mariadb = stripos($version, 'mariadb') !== false;
|
||||
|
||||
if ($mariadb) {
|
||||
$mariaDbVersion = $this->getMariaDbMysqlVersionNumber($version);
|
||||
if (version_compare($mariaDbVersion, '10.10.0', '>=')) {
|
||||
return new MariaDb1010Platform();
|
||||
}
|
||||
|
||||
if (version_compare($mariaDbVersion, '10.6.0', '>=')) {
|
||||
return new MariaDb1060Platform();
|
||||
}
|
||||
|
||||
if (version_compare($mariaDbVersion, '10.5.2', '>=')) {
|
||||
return new MariaDb1052Platform();
|
||||
}
|
||||
|
||||
if (version_compare($mariaDbVersion, '10.4.3', '>=')) {
|
||||
return new MariaDb1043Platform();
|
||||
}
|
||||
|
||||
Deprecation::trigger(
|
||||
'doctrine/dbal',
|
||||
'https://github.com/doctrine/dbal/pull/6110',
|
||||
'Support for MariaDB < 10.4 is deprecated and will be removed in DBAL 4.'
|
||||
. ' Consider upgrading to a more recent version of MariaDB.',
|
||||
);
|
||||
|
||||
if (version_compare($mariaDbVersion, '10.2.7', '>=')) {
|
||||
return new MariaDb1027Platform();
|
||||
}
|
||||
} else {
|
||||
$oracleMysqlVersion = $this->getOracleMysqlVersionNumber($version);
|
||||
|
||||
if (version_compare($oracleMysqlVersion, '8.4.0', '>=')) {
|
||||
if (! version_compare($version, '8.4.0', '>=')) {
|
||||
Deprecation::trigger(
|
||||
'doctrine/orm',
|
||||
'https://github.com/doctrine/dbal/pull/5779',
|
||||
'Version detection logic for MySQL will change in DBAL 4. '
|
||||
. 'Please specify the version as the server reports it, e.g. "8.4.0" instead of "8.4".',
|
||||
);
|
||||
}
|
||||
|
||||
return new MySQL84Platform();
|
||||
}
|
||||
|
||||
if (version_compare($oracleMysqlVersion, '8', '>=')) {
|
||||
if (! version_compare($version, '8.0.0', '>=')) {
|
||||
Deprecation::trigger(
|
||||
'doctrine/orm',
|
||||
'https://github.com/doctrine/dbal/pull/5779',
|
||||
'Version detection logic for MySQL will change in DBAL 4. '
|
||||
. 'Please specify the version as the server reports it, e.g. "8.0.31" instead of "8".',
|
||||
);
|
||||
}
|
||||
|
||||
return new MySQL80Platform();
|
||||
}
|
||||
|
||||
if (version_compare($oracleMysqlVersion, '5.7.9', '>=')) {
|
||||
if (! version_compare($version, '5.7.9', '>=')) {
|
||||
Deprecation::trigger(
|
||||
'doctrine/orm',
|
||||
'https://github.com/doctrine/dbal/pull/5779',
|
||||
'Version detection logic for MySQL will change in DBAL 4. '
|
||||
. 'Please specify the version as the server reports it, e.g. "5.7.40" instead of "5.7".',
|
||||
);
|
||||
}
|
||||
|
||||
return new MySQL57Platform();
|
||||
}
|
||||
|
||||
Deprecation::trigger(
|
||||
'doctrine/dbal',
|
||||
'https://github.com/doctrine/dbal/pull/5072',
|
||||
'MySQL 5.6 support is deprecated and will be removed in DBAL 4.'
|
||||
. ' Consider upgrading to MySQL 5.7 or later.',
|
||||
);
|
||||
}
|
||||
|
||||
return $this->getDatabasePlatform();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a normalized 'version number' from the server string
|
||||
* returned by Oracle MySQL servers.
|
||||
*
|
||||
* @param string $versionString Version string returned by the driver, i.e. '5.7.10'
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
private function getOracleMysqlVersionNumber(string $versionString): string
|
||||
{
|
||||
if (
|
||||
preg_match(
|
||||
'/^(?P<major>\d+)(?:\.(?P<minor>\d+)(?:\.(?P<patch>\d+))?)?/',
|
||||
$versionString,
|
||||
$versionParts,
|
||||
) !== 1
|
||||
) {
|
||||
throw Exception::invalidPlatformVersionSpecified(
|
||||
$versionString,
|
||||
'<major_version>.<minor_version>.<patch_version>',
|
||||
);
|
||||
}
|
||||
|
||||
$majorVersion = $versionParts['major'];
|
||||
$minorVersion = $versionParts['minor'] ?? 0;
|
||||
$patchVersion = $versionParts['patch'] ?? null;
|
||||
|
||||
if ($majorVersion === '5' && $minorVersion === '7') {
|
||||
$patchVersion ??= '9';
|
||||
} else {
|
||||
$patchVersion ??= '0';
|
||||
}
|
||||
|
||||
return $majorVersion . '.' . $minorVersion . '.' . $patchVersion;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect MariaDB server version, including hack for some mariadb distributions
|
||||
* that starts with the prefix '5.5.5-'
|
||||
*
|
||||
* @param string $versionString Version string as returned by mariadb server, i.e. '5.5.5-Mariadb-10.0.8-xenial'
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
private function getMariaDbMysqlVersionNumber(string $versionString): string
|
||||
{
|
||||
if (stripos($versionString, 'MariaDB') === 0) {
|
||||
Deprecation::trigger(
|
||||
'doctrine/orm',
|
||||
'https://github.com/doctrine/dbal/pull/5779',
|
||||
'Version detection logic for MySQL will change in DBAL 4. '
|
||||
. 'Please specify the version as the server reports it, '
|
||||
. 'e.g. "10.9.3-MariaDB" instead of "mariadb-10.9".',
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
preg_match(
|
||||
'/^(?:5\.5\.5-)?(mariadb-)?(?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)/i',
|
||||
$versionString,
|
||||
$versionParts,
|
||||
) !== 1
|
||||
) {
|
||||
throw Exception::invalidPlatformVersionSpecified(
|
||||
$versionString,
|
||||
'^(?:5\.5\.5-)?(mariadb-)?<major_version>.<minor_version>.<patch_version>',
|
||||
);
|
||||
}
|
||||
|
||||
return $versionParts['major'] . '.' . $versionParts['minor'] . '.' . $versionParts['patch'];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @return AbstractMySQLPlatform
|
||||
*/
|
||||
public function getDatabasePlatform()
|
||||
{
|
||||
return new MySQLPlatform();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @deprecated Use {@link AbstractMySQLPlatform::createSchemaManager()} instead.
|
||||
*
|
||||
* @return MySQLSchemaManager
|
||||
*/
|
||||
public function getSchemaManager(Connection $conn, AbstractPlatform $platform)
|
||||
{
|
||||
Deprecation::triggerIfCalledFromOutside(
|
||||
'doctrine/dbal',
|
||||
'https://github.com/doctrine/dbal/pull/5458',
|
||||
'AbstractMySQLDriver::getSchemaManager() is deprecated.'
|
||||
. ' Use MySQLPlatform::createSchemaManager() instead.',
|
||||
);
|
||||
|
||||
assert($platform instanceof AbstractMySQLPlatform);
|
||||
|
||||
return new MySQLSchemaManager($conn, $platform);
|
||||
}
|
||||
|
||||
public function getExceptionConverter(): ExceptionConverter
|
||||
{
|
||||
return new MySQL\ExceptionConverter();
|
||||
}
|
||||
}
|
||||
+93
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
|
||||
namespace Doctrine\DBAL\Driver;
|
||||
|
||||
use Doctrine\DBAL\Connection;
|
||||
use Doctrine\DBAL\Driver\API\ExceptionConverter;
|
||||
use Doctrine\DBAL\Driver\API\PostgreSQL;
|
||||
use Doctrine\DBAL\Exception;
|
||||
use Doctrine\DBAL\Platforms\AbstractPlatform;
|
||||
use Doctrine\DBAL\Platforms\PostgreSQL100Platform;
|
||||
use Doctrine\DBAL\Platforms\PostgreSQL120Platform;
|
||||
use Doctrine\DBAL\Platforms\PostgreSQL94Platform;
|
||||
use Doctrine\DBAL\Platforms\PostgreSQLPlatform;
|
||||
use Doctrine\DBAL\Schema\PostgreSQLSchemaManager;
|
||||
use Doctrine\DBAL\VersionAwarePlatformDriver;
|
||||
use Doctrine\Deprecations\Deprecation;
|
||||
|
||||
use function assert;
|
||||
use function preg_match;
|
||||
use function version_compare;
|
||||
|
||||
/**
|
||||
* Abstract base implementation of the {@see Driver} interface for PostgreSQL based drivers.
|
||||
*/
|
||||
abstract class AbstractPostgreSQLDriver implements VersionAwarePlatformDriver
|
||||
{
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function createDatabasePlatformForVersion($version)
|
||||
{
|
||||
if (preg_match('/^(?P<major>\d+)(?:\.(?P<minor>\d+)(?:\.(?P<patch>\d+))?)?/', $version, $versionParts) !== 1) {
|
||||
throw Exception::invalidPlatformVersionSpecified(
|
||||
$version,
|
||||
'<major_version>.<minor_version>.<patch_version>',
|
||||
);
|
||||
}
|
||||
|
||||
$majorVersion = $versionParts['major'];
|
||||
$minorVersion = $versionParts['minor'] ?? 0;
|
||||
$patchVersion = $versionParts['patch'] ?? 0;
|
||||
$version = $majorVersion . '.' . $minorVersion . '.' . $patchVersion;
|
||||
|
||||
if (version_compare($version, '12.0', '>=')) {
|
||||
return new PostgreSQL120Platform();
|
||||
}
|
||||
|
||||
if (version_compare($version, '10.0', '>=')) {
|
||||
return new PostgreSQL100Platform();
|
||||
}
|
||||
|
||||
Deprecation::trigger(
|
||||
'doctrine/dbal',
|
||||
'https://github.com/doctrine/dbal/pull/5060',
|
||||
'PostgreSQL 9 support is deprecated and will be removed in DBAL 4.'
|
||||
. ' Consider upgrading to Postgres 10 or later.',
|
||||
);
|
||||
|
||||
return new PostgreSQL94Platform();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function getDatabasePlatform()
|
||||
{
|
||||
return new PostgreSQL94Platform();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @deprecated Use {@link PostgreSQLPlatform::createSchemaManager()} instead.
|
||||
*/
|
||||
public function getSchemaManager(Connection $conn, AbstractPlatform $platform)
|
||||
{
|
||||
Deprecation::triggerIfCalledFromOutside(
|
||||
'doctrine/dbal',
|
||||
'https://github.com/doctrine/dbal/pull/5458',
|
||||
'AbstractPostgreSQLDriver::getSchemaManager() is deprecated.'
|
||||
. ' Use PostgreSQLPlatform::createSchemaManager() instead.',
|
||||
);
|
||||
|
||||
assert($platform instanceof PostgreSQLPlatform);
|
||||
|
||||
return new PostgreSQLSchemaManager($conn, $platform);
|
||||
}
|
||||
|
||||
public function getExceptionConverter(): ExceptionConverter
|
||||
{
|
||||
return new PostgreSQL\ExceptionConverter();
|
||||
}
|
||||
}
|
||||
Vendored
+38
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
namespace Doctrine\DBAL\Driver\OCI8\Middleware;
|
||||
|
||||
use Doctrine\DBAL\Driver;
|
||||
use Doctrine\DBAL\Driver\Connection;
|
||||
use Doctrine\DBAL\Driver\Middleware;
|
||||
use Doctrine\DBAL\Driver\Middleware\AbstractDriverMiddleware;
|
||||
use SensitiveParameter;
|
||||
|
||||
class InitializeSession implements Middleware
|
||||
{
|
||||
public function wrap(Driver $driver): Driver
|
||||
{
|
||||
return new class ($driver) extends AbstractDriverMiddleware {
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function connect(
|
||||
#[SensitiveParameter]
|
||||
array $params
|
||||
): Connection {
|
||||
$connection = parent::connect($params);
|
||||
|
||||
$connection->exec(
|
||||
'ALTER SESSION SET'
|
||||
. " NLS_DATE_FORMAT = 'YYYY-MM-DD HH24:MI:SS'"
|
||||
. " NLS_TIME_FORMAT = 'HH24:MI:SS'"
|
||||
. " NLS_TIMESTAMP_FORMAT = 'YYYY-MM-DD HH24:MI:SS'"
|
||||
. " NLS_TIMESTAMP_TZ_FORMAT = 'YYYY-MM-DD HH24:MI:SS TZH:TZM'"
|
||||
. " NLS_NUMERIC_CHARACTERS = '.,'",
|
||||
);
|
||||
|
||||
return $connection;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
<?php
|
||||
|
||||
namespace Doctrine\DBAL\Driver\PDO\PgSQL;
|
||||
|
||||
use Doctrine\DBAL\Driver\AbstractPostgreSQLDriver;
|
||||
use Doctrine\DBAL\Driver\PDO\Connection;
|
||||
use Doctrine\DBAL\Driver\PDO\Exception;
|
||||
use Doctrine\Deprecations\Deprecation;
|
||||
use PDO;
|
||||
use PDOException;
|
||||
use SensitiveParameter;
|
||||
|
||||
final class Driver extends AbstractPostgreSQLDriver
|
||||
{
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @return Connection
|
||||
*/
|
||||
public function connect(
|
||||
#[SensitiveParameter]
|
||||
array $params
|
||||
) {
|
||||
$driverOptions = $params['driverOptions'] ?? [];
|
||||
|
||||
if (! empty($params['persistent'])) {
|
||||
$driverOptions[PDO::ATTR_PERSISTENT] = true;
|
||||
}
|
||||
|
||||
$safeParams = $params;
|
||||
unset($safeParams['password'], $safeParams['url']);
|
||||
|
||||
try {
|
||||
$pdo = new PDO(
|
||||
$this->constructPdoDsn($safeParams),
|
||||
$params['user'] ?? '',
|
||||
$params['password'] ?? '',
|
||||
$driverOptions,
|
||||
);
|
||||
} catch (PDOException $exception) {
|
||||
throw Exception::new($exception);
|
||||
}
|
||||
|
||||
if (
|
||||
! isset($driverOptions[PDO::PGSQL_ATTR_DISABLE_PREPARES])
|
||||
|| $driverOptions[PDO::PGSQL_ATTR_DISABLE_PREPARES] === true
|
||||
) {
|
||||
$pdo->setAttribute(PDO::PGSQL_ATTR_DISABLE_PREPARES, true);
|
||||
}
|
||||
|
||||
$connection = new Connection($pdo);
|
||||
|
||||
/* defining client_encoding via SET NAMES to avoid inconsistent DSN support
|
||||
* - passing client_encoding via the 'options' param breaks pgbouncer support
|
||||
*/
|
||||
if (isset($params['charset'])) {
|
||||
$connection->exec('SET NAMES \'' . $params['charset'] . '\'');
|
||||
}
|
||||
|
||||
return $connection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs the Postgres PDO DSN.
|
||||
*
|
||||
* @param array<string, mixed> $params
|
||||
*/
|
||||
private function constructPdoDsn(array $params): string
|
||||
{
|
||||
$dsn = 'pgsql:';
|
||||
|
||||
if (isset($params['host']) && $params['host'] !== '') {
|
||||
$dsn .= 'host=' . $params['host'] . ';';
|
||||
}
|
||||
|
||||
if (isset($params['port']) && $params['port'] !== '') {
|
||||
$dsn .= 'port=' . $params['port'] . ';';
|
||||
}
|
||||
|
||||
if (isset($params['dbname'])) {
|
||||
$dsn .= 'dbname=' . $params['dbname'] . ';';
|
||||
} elseif (isset($params['default_dbname'])) {
|
||||
Deprecation::trigger(
|
||||
'doctrine/dbal',
|
||||
'https://github.com/doctrine/dbal/pull/5705',
|
||||
'The "default_dbname" connection parameter is deprecated. Use "dbname" instead.',
|
||||
);
|
||||
|
||||
$dsn .= 'dbname=' . $params['default_dbname'] . ';';
|
||||
} else {
|
||||
if (isset($params['user']) && $params['user'] !== 'postgres') {
|
||||
Deprecation::trigger(
|
||||
'doctrine/dbal',
|
||||
'https://github.com/doctrine/dbal/pull/5705',
|
||||
'Relying on the DBAL connecting to the "postgres" database by default is deprecated.'
|
||||
. ' Unless you want to have the server determine the default database for the connection,'
|
||||
. ' specify the database name explicitly.',
|
||||
);
|
||||
}
|
||||
|
||||
// Used for temporary connections to allow operations like dropping the database currently connected to.
|
||||
$dsn .= 'dbname=postgres;';
|
||||
}
|
||||
|
||||
if (isset($params['sslmode'])) {
|
||||
$dsn .= 'sslmode=' . $params['sslmode'] . ';';
|
||||
}
|
||||
|
||||
if (isset($params['sslrootcert'])) {
|
||||
$dsn .= 'sslrootcert=' . $params['sslrootcert'] . ';';
|
||||
}
|
||||
|
||||
if (isset($params['sslcert'])) {
|
||||
$dsn .= 'sslcert=' . $params['sslcert'] . ';';
|
||||
}
|
||||
|
||||
if (isset($params['sslkey'])) {
|
||||
$dsn .= 'sslkey=' . $params['sslkey'] . ';';
|
||||
}
|
||||
|
||||
if (isset($params['sslcrl'])) {
|
||||
$dsn .= 'sslcrl=' . $params['sslcrl'] . ';';
|
||||
}
|
||||
|
||||
if (isset($params['application_name'])) {
|
||||
$dsn .= 'application_name=' . $params['application_name'] . ';';
|
||||
}
|
||||
|
||||
if (isset($params['gssencmode'])) {
|
||||
$dsn .= 'gssencmode=' . $params['gssencmode'] . ';';
|
||||
}
|
||||
|
||||
return $dsn;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
|
||||
namespace Doctrine\DBAL\Driver\PgSQL;
|
||||
|
||||
use Doctrine\DBAL\Driver\AbstractPostgreSQLDriver;
|
||||
use ErrorException;
|
||||
use SensitiveParameter;
|
||||
|
||||
use function addslashes;
|
||||
use function array_filter;
|
||||
use function array_keys;
|
||||
use function array_map;
|
||||
use function array_slice;
|
||||
use function array_values;
|
||||
use function func_get_args;
|
||||
use function implode;
|
||||
use function pg_connect;
|
||||
use function restore_error_handler;
|
||||
use function set_error_handler;
|
||||
use function sprintf;
|
||||
|
||||
use const PGSQL_CONNECT_FORCE_NEW;
|
||||
|
||||
final class Driver extends AbstractPostgreSQLDriver
|
||||
{
|
||||
/** {@inheritDoc} */
|
||||
public function connect(
|
||||
#[SensitiveParameter]
|
||||
array $params
|
||||
): Connection {
|
||||
set_error_handler(
|
||||
static function (int $severity, string $message) {
|
||||
throw new ErrorException($message, 0, $severity, ...array_slice(func_get_args(), 2, 2));
|
||||
},
|
||||
);
|
||||
|
||||
try {
|
||||
$connection = pg_connect($this->constructConnectionString($params), PGSQL_CONNECT_FORCE_NEW);
|
||||
} catch (ErrorException $e) {
|
||||
throw new Exception($e->getMessage(), '08006', 0, $e);
|
||||
} finally {
|
||||
restore_error_handler();
|
||||
}
|
||||
|
||||
if ($connection === false) {
|
||||
throw new Exception('Unable to connect to Postgres server.');
|
||||
}
|
||||
|
||||
$driverConnection = new Connection($connection);
|
||||
|
||||
if (isset($params['application_name'])) {
|
||||
$driverConnection->exec('SET application_name = ' . $driverConnection->quote($params['application_name']));
|
||||
}
|
||||
|
||||
return $driverConnection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs the Postgres connection string
|
||||
*
|
||||
* @param array<string, mixed> $params
|
||||
*/
|
||||
private function constructConnectionString(
|
||||
#[SensitiveParameter]
|
||||
array $params
|
||||
): string {
|
||||
$components = array_filter(
|
||||
[
|
||||
'host' => $params['host'] ?? null,
|
||||
'port' => $params['port'] ?? null,
|
||||
'dbname' => $params['dbname'] ?? 'postgres',
|
||||
'user' => $params['user'] ?? null,
|
||||
'password' => $params['password'] ?? null,
|
||||
'sslmode' => $params['sslmode'] ?? null,
|
||||
'gssencmode' => $params['gssencmode'] ?? null,
|
||||
],
|
||||
static fn ($value) => $value !== '' && $value !== null,
|
||||
);
|
||||
|
||||
return implode(' ', array_map(
|
||||
static fn ($value, string $key) => sprintf("%s='%s'", $key, addslashes($value)),
|
||||
array_values($components),
|
||||
array_keys($components),
|
||||
));
|
||||
}
|
||||
}
|
||||
+1497
File diff suppressed because it is too large
Load Diff
+4727
File diff suppressed because it is too large
Load Diff
+13
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace Doctrine\DBAL\Platforms;
|
||||
|
||||
/**
|
||||
* Provides the behavior, features and SQL dialect of the MariaDB 10.2 database platform.
|
||||
*
|
||||
* @deprecated This class will be merged with {@see MariaDBPlatform} in 4.0 because support for MariaDB
|
||||
* releases prior to 10.4.3 will be dropped.
|
||||
*/
|
||||
class MariaDb1027Platform extends MariaDBPlatform
|
||||
{
|
||||
}
|
||||
+131
@@ -0,0 +1,131 @@
|
||||
<?php
|
||||
|
||||
namespace Doctrine\DBAL\Platforms;
|
||||
|
||||
use Doctrine\DBAL\Types\JsonType;
|
||||
use Doctrine\Deprecations\Deprecation;
|
||||
|
||||
use function sprintf;
|
||||
|
||||
/**
|
||||
* Provides the behavior, features and SQL dialect of the MariaDB 10.4 database platform.
|
||||
*
|
||||
* Extend deprecated MariaDb1027Platform to ensure correct functions used in MySQLSchemaManager which
|
||||
* tests for MariaDb1027Platform not MariaDBPlatform.
|
||||
*
|
||||
* @deprecated This class will be merged with {@see MariaDBPlatform} in 4.0 because support for MariaDB
|
||||
* releases prior to 10.4.3 will be dropped.
|
||||
*/
|
||||
class MariaDb1043Platform extends MariaDb1027Platform
|
||||
{
|
||||
/**
|
||||
* Use JSON rather than LONGTEXT for json columns. Since it is not a true native type, do not override
|
||||
* hasNativeJsonType() so the DC2Type comment will still be set.
|
||||
*
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function getJsonTypeDeclarationSQL(array $column): string
|
||||
{
|
||||
return 'JSON';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* From version 10.4.3, MariaDb aliases JSON to LONGTEXT and adds a constraint CHECK (json_valid). Reverse
|
||||
* this process when introspecting tables.
|
||||
*
|
||||
* @see https://mariadb.com/kb/en/information-schema-check_constraints-table/
|
||||
* @see https://mariadb.com/kb/en/json-data-type/
|
||||
* @see https://jira.mariadb.org/browse/MDEV-13916
|
||||
*/
|
||||
public function getListTableColumnsSQL($table, $database = null): string
|
||||
{
|
||||
// @todo 4.0 - call getColumnTypeSQLSnippet() instead
|
||||
[$columnTypeSQL, $joinCheckConstraintSQL] = $this->getColumnTypeSQLSnippets('c', $database);
|
||||
|
||||
return sprintf(
|
||||
<<<SQL
|
||||
SELECT c.COLUMN_NAME AS Field,
|
||||
$columnTypeSQL AS Type,
|
||||
c.IS_NULLABLE AS `Null`,
|
||||
c.COLUMN_KEY AS `Key`,
|
||||
c.COLUMN_DEFAULT AS `Default`,
|
||||
c.EXTRA AS Extra,
|
||||
c.COLUMN_COMMENT AS Comment,
|
||||
c.CHARACTER_SET_NAME AS CharacterSet,
|
||||
c.COLLATION_NAME AS Collation
|
||||
FROM information_schema.COLUMNS c
|
||||
$joinCheckConstraintSQL
|
||||
WHERE c.TABLE_SCHEMA = %s
|
||||
AND c.TABLE_NAME = %s
|
||||
ORDER BY ORDINAL_POSITION ASC;
|
||||
SQL
|
||||
,
|
||||
$this->getDatabaseNameSQL($database),
|
||||
$this->quoteStringLiteral($table),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate SQL snippets to reverse the aliasing of JSON to LONGTEXT.
|
||||
*
|
||||
* MariaDb aliases columns specified as JSON to LONGTEXT and sets a CHECK constraint to ensure the column
|
||||
* is valid json. This function generates the SQL snippets which reverse this aliasing i.e. report a column
|
||||
* as JSON where it was originally specified as such instead of LONGTEXT.
|
||||
*
|
||||
* The CHECK constraints are stored in information_schema.CHECK_CONSTRAINTS so query that table.
|
||||
*/
|
||||
public function getColumnTypeSQLSnippet(string $tableAlias = 'c', ?string $databaseName = null): string
|
||||
{
|
||||
if ($this->getJsonTypeDeclarationSQL([]) !== 'JSON') {
|
||||
return parent::getColumnTypeSQLSnippet($tableAlias, $databaseName);
|
||||
}
|
||||
|
||||
if ($databaseName === null) {
|
||||
Deprecation::trigger(
|
||||
'doctrine/dbal',
|
||||
'https://github.com/doctrine/dbal/pull/6215',
|
||||
'Not passing a database name to methods "getColumnTypeSQLSnippet()", '
|
||||
. '"getColumnTypeSQLSnippets()", and "getListTableColumnsSQL()" of "%s" is deprecated.',
|
||||
self::class,
|
||||
);
|
||||
}
|
||||
|
||||
$subQueryAlias = 'i_' . $tableAlias;
|
||||
|
||||
$databaseName = $this->getDatabaseNameSQL($databaseName);
|
||||
|
||||
// The check for `CONSTRAINT_SCHEMA = $databaseName` is mandatory here to prevent performance issues
|
||||
return <<<SQL
|
||||
IF(
|
||||
$tableAlias.COLUMN_TYPE = 'longtext'
|
||||
AND EXISTS(
|
||||
SELECT * from information_schema.CHECK_CONSTRAINTS $subQueryAlias
|
||||
WHERE $subQueryAlias.CONSTRAINT_SCHEMA = $databaseName
|
||||
AND $subQueryAlias.TABLE_NAME = $tableAlias.TABLE_NAME
|
||||
AND $subQueryAlias.CHECK_CLAUSE = CONCAT(
|
||||
'json_valid(`',
|
||||
$tableAlias.COLUMN_NAME,
|
||||
'`)'
|
||||
)
|
||||
),
|
||||
'json',
|
||||
$tableAlias.COLUMN_TYPE
|
||||
)
|
||||
SQL;
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
public function getColumnDeclarationSQL($name, array $column)
|
||||
{
|
||||
// MariaDb forces column collation to utf8mb4_bin where the column was declared as JSON so ignore
|
||||
// collation and character set for json columns as attempting to set them can cause an error.
|
||||
if ($this->getJsonTypeDeclarationSQL([]) === 'JSON' && ($column['type'] ?? null) instanceof JsonType) {
|
||||
unset($column['collation']);
|
||||
unset($column['charset']);
|
||||
}
|
||||
|
||||
return parent::getColumnDeclarationSQL($name, $column);
|
||||
}
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace Doctrine\DBAL\Platforms;
|
||||
|
||||
use Doctrine\DBAL\Schema\Index;
|
||||
use Doctrine\DBAL\Schema\TableDiff;
|
||||
|
||||
/**
|
||||
* Provides the behavior, features and SQL dialect of the MariaDB 10.5 database platform.
|
||||
*/
|
||||
class MariaDb1052Platform extends MariaDb1043Platform
|
||||
{
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function getPreAlterTableRenameIndexForeignKeySQL(TableDiff $diff)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function getPostAlterTableRenameIndexForeignKeySQL(TableDiff $diff)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function getRenameIndexSQL($oldIndexName, Index $index, $tableName)
|
||||
{
|
||||
return ['ALTER TABLE ' . $tableName . ' RENAME INDEX ' . $oldIndexName . ' TO ' . $index->getQuotedName($this)];
|
||||
}
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace Doctrine\DBAL\Platforms;
|
||||
|
||||
use Doctrine\DBAL\SQL\Builder\SelectSQLBuilder;
|
||||
|
||||
/**
|
||||
* Provides the behavior, features and SQL dialect of the MariaDB 10.6 database platform.
|
||||
*/
|
||||
class MariaDb1060Platform extends MariaDb1052Platform
|
||||
{
|
||||
public function createSelectSQLBuilder(): SelectSQLBuilder
|
||||
{
|
||||
return AbstractPlatform::createSelectSQLBuilder();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
<?php
|
||||
|
||||
namespace Doctrine\DBAL\Platforms;
|
||||
|
||||
use Doctrine\DBAL\Schema\Index;
|
||||
use Doctrine\DBAL\Schema\TableDiff;
|
||||
use Doctrine\DBAL\SQL\Parser;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\Deprecations\Deprecation;
|
||||
|
||||
/**
|
||||
* Provides the behavior, features and SQL dialect of the MySQL 5.7 database platform.
|
||||
*
|
||||
* @deprecated This class will be merged with {@see MySQLPlatform} in 4.0 because support for MySQL
|
||||
* releases prior to 5.7 will be dropped.
|
||||
*/
|
||||
class MySQL57Platform extends MySQLPlatform
|
||||
{
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
public function hasNativeJsonType()
|
||||
{
|
||||
Deprecation::triggerIfCalledFromOutside(
|
||||
'doctrine/dbal',
|
||||
'https://github.com/doctrine/dbal/pull/5509',
|
||||
'%s is deprecated.',
|
||||
__METHOD__,
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function getJsonTypeDeclarationSQL(array $column)
|
||||
{
|
||||
return 'JSON';
|
||||
}
|
||||
|
||||
public function createSQLParser(): Parser
|
||||
{
|
||||
return new Parser(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function getPreAlterTableRenameIndexForeignKeySQL(TableDiff $diff)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function getPostAlterTableRenameIndexForeignKeySQL(TableDiff $diff)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function getRenameIndexSQL($oldIndexName, Index $index, $tableName)
|
||||
{
|
||||
return ['ALTER TABLE ' . $tableName . ' RENAME INDEX ' . $oldIndexName . ' TO ' . $index->getQuotedName($this)];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @deprecated Implement {@see createReservedKeywordsList()} instead.
|
||||
*/
|
||||
protected function getReservedKeywordsClass()
|
||||
{
|
||||
Deprecation::triggerIfCalledFromOutside(
|
||||
'doctrine/dbal',
|
||||
'https://github.com/doctrine/dbal/issues/4510',
|
||||
'MySQL57Platform::getReservedKeywordsClass() is deprecated,'
|
||||
. ' use MySQL57Platform::createReservedKeywordsList() instead.',
|
||||
);
|
||||
|
||||
return Keywords\MySQL57Keywords::class;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function initializeDoctrineTypeMappings()
|
||||
{
|
||||
parent::initializeDoctrineTypeMappings();
|
||||
|
||||
$this->doctrineTypeMapping['json'] = Types::JSON;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace Doctrine\DBAL\Platforms;
|
||||
|
||||
use Doctrine\DBAL\SQL\Builder\SelectSQLBuilder;
|
||||
use Doctrine\Deprecations\Deprecation;
|
||||
|
||||
/**
|
||||
* Provides the behavior, features and SQL dialect of the MySQL 8.0 database platform.
|
||||
*/
|
||||
class MySQL80Platform extends MySQL57Platform
|
||||
{
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @deprecated Implement {@see createReservedKeywordsList()} instead.
|
||||
*/
|
||||
protected function getReservedKeywordsClass()
|
||||
{
|
||||
Deprecation::triggerIfCalledFromOutside(
|
||||
'doctrine/dbal',
|
||||
'https://github.com/doctrine/dbal/issues/4510',
|
||||
'MySQL80Platform::getReservedKeywordsClass() is deprecated,'
|
||||
. ' use MySQL80Platform::createReservedKeywordsList() instead.',
|
||||
);
|
||||
|
||||
return Keywords\MySQL80Keywords::class;
|
||||
}
|
||||
|
||||
public function createSelectSQLBuilder(): SelectSQLBuilder
|
||||
{
|
||||
return AbstractPlatform::createSelectSQLBuilder();
|
||||
}
|
||||
}
|
||||
+1329
File diff suppressed because it is too large
Load Diff
+1420
File diff suppressed because it is too large
Load Diff
+1841
File diff suppressed because it is too large
Load Diff
+1545
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,339 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\DBAL;
|
||||
|
||||
use Doctrine\DBAL\Driver\Exception as DriverException;
|
||||
use Doctrine\DBAL\Driver\Result as DriverResult;
|
||||
use Doctrine\DBAL\Exception\NoKeyValue;
|
||||
use Doctrine\Deprecations\Deprecation;
|
||||
use LogicException;
|
||||
use Traversable;
|
||||
|
||||
use function array_shift;
|
||||
use function func_num_args;
|
||||
|
||||
class Result
|
||||
{
|
||||
private DriverResult $result;
|
||||
private Connection $connection;
|
||||
|
||||
/** @internal The result can be only instantiated by {@see Connection} or {@see Statement}. */
|
||||
public function __construct(DriverResult $result, Connection $connection)
|
||||
{
|
||||
$this->result = $result;
|
||||
$this->connection = $connection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the next row of the result as a numeric array or FALSE if there are no more rows.
|
||||
*
|
||||
* @return list<mixed>|false
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function fetchNumeric()
|
||||
{
|
||||
try {
|
||||
return $this->result->fetchNumeric();
|
||||
} catch (DriverException $e) {
|
||||
throw $this->connection->convertException($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the next row of the result as an associative array or FALSE if there are no more rows.
|
||||
*
|
||||
* @return array<string,mixed>|false
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function fetchAssociative()
|
||||
{
|
||||
try {
|
||||
return $this->result->fetchAssociative();
|
||||
} catch (DriverException $e) {
|
||||
throw $this->connection->convertException($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the first value of the next row of the result or FALSE if there are no more rows.
|
||||
*
|
||||
* @return mixed|false
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function fetchOne()
|
||||
{
|
||||
try {
|
||||
return $this->result->fetchOne();
|
||||
} catch (DriverException $e) {
|
||||
throw $this->connection->convertException($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array containing all of the result rows represented as numeric arrays.
|
||||
*
|
||||
* @return list<list<mixed>>
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function fetchAllNumeric(): array
|
||||
{
|
||||
try {
|
||||
return $this->result->fetchAllNumeric();
|
||||
} catch (DriverException $e) {
|
||||
throw $this->connection->convertException($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array containing all of the result rows represented as associative arrays.
|
||||
*
|
||||
* @return list<array<string,mixed>>
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function fetchAllAssociative(): array
|
||||
{
|
||||
try {
|
||||
return $this->result->fetchAllAssociative();
|
||||
} catch (DriverException $e) {
|
||||
throw $this->connection->convertException($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array containing the values of the first column of the result.
|
||||
*
|
||||
* @return array<mixed,mixed>
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function fetchAllKeyValue(): array
|
||||
{
|
||||
$this->ensureHasKeyValue();
|
||||
|
||||
$data = [];
|
||||
|
||||
foreach ($this->fetchAllNumeric() as [$key, $value]) {
|
||||
$data[$key] = $value;
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an associative array with the keys mapped to the first column and the values being
|
||||
* an associative array representing the rest of the columns and their values.
|
||||
*
|
||||
* @return array<mixed,array<string,mixed>>
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function fetchAllAssociativeIndexed(): array
|
||||
{
|
||||
$data = [];
|
||||
|
||||
foreach ($this->fetchAllAssociative() as $row) {
|
||||
$data[array_shift($row)] = $row;
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<mixed>
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function fetchFirstColumn(): array
|
||||
{
|
||||
try {
|
||||
return $this->result->fetchFirstColumn();
|
||||
} catch (DriverException $e) {
|
||||
throw $this->connection->convertException($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Traversable<int,list<mixed>>
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function iterateNumeric(): Traversable
|
||||
{
|
||||
while (($row = $this->fetchNumeric()) !== false) {
|
||||
yield $row;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Traversable<int,array<string,mixed>>
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function iterateAssociative(): Traversable
|
||||
{
|
||||
while (($row = $this->fetchAssociative()) !== false) {
|
||||
yield $row;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Traversable<mixed, mixed>
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function iterateKeyValue(): Traversable
|
||||
{
|
||||
$this->ensureHasKeyValue();
|
||||
|
||||
foreach ($this->iterateNumeric() as [$key, $value]) {
|
||||
yield $key => $value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an iterator over the result set with the keys mapped to the first column and the values being
|
||||
* an associative array representing the rest of the columns and their values.
|
||||
*
|
||||
* @return Traversable<mixed,array<string,mixed>>
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function iterateAssociativeIndexed(): Traversable
|
||||
{
|
||||
foreach ($this->iterateAssociative() as $row) {
|
||||
yield array_shift($row) => $row;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Traversable<int,mixed>
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function iterateColumn(): Traversable
|
||||
{
|
||||
while (($value = $this->fetchOne()) !== false) {
|
||||
yield $value;
|
||||
}
|
||||
}
|
||||
|
||||
/** @throws Exception */
|
||||
public function rowCount(): int
|
||||
{
|
||||
try {
|
||||
return $this->result->rowCount();
|
||||
} catch (DriverException $e) {
|
||||
throw $this->connection->convertException($e);
|
||||
}
|
||||
}
|
||||
|
||||
/** @throws Exception */
|
||||
public function columnCount(): int
|
||||
{
|
||||
try {
|
||||
return $this->result->columnCount();
|
||||
} catch (DriverException $e) {
|
||||
throw $this->connection->convertException($e);
|
||||
}
|
||||
}
|
||||
|
||||
public function free(): void
|
||||
{
|
||||
$this->result->free();
|
||||
}
|
||||
|
||||
/** @throws Exception */
|
||||
private function ensureHasKeyValue(): void
|
||||
{
|
||||
$columnCount = $this->columnCount();
|
||||
|
||||
if ($columnCount < 2) {
|
||||
throw NoKeyValue::fromColumnCount($columnCount);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* BC layer for a wide-spread use-case of old DBAL APIs
|
||||
*
|
||||
* @deprecated Use {@see fetchNumeric()}, {@see fetchAssociative()} or {@see fetchOne()} instead.
|
||||
*
|
||||
* @psalm-param FetchMode::* $mode
|
||||
*
|
||||
* @return mixed
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function fetch(int $mode = FetchMode::ASSOCIATIVE)
|
||||
{
|
||||
Deprecation::trigger(
|
||||
'doctrine/dbal',
|
||||
'https://github.com/doctrine/dbal/pull/4007',
|
||||
'%s is deprecated, please use fetchNumeric(), fetchAssociative() or fetchOne() instead.',
|
||||
__METHOD__,
|
||||
);
|
||||
|
||||
if (func_num_args() > 1) {
|
||||
throw new LogicException('Only invocations with one argument are still supported by this legacy API.');
|
||||
}
|
||||
|
||||
if ($mode === FetchMode::ASSOCIATIVE) {
|
||||
return $this->fetchAssociative();
|
||||
}
|
||||
|
||||
if ($mode === FetchMode::NUMERIC) {
|
||||
return $this->fetchNumeric();
|
||||
}
|
||||
|
||||
if ($mode === FetchMode::COLUMN) {
|
||||
return $this->fetchOne();
|
||||
}
|
||||
|
||||
throw new LogicException('Only fetch modes declared on Doctrine\DBAL\FetchMode are supported by legacy API.');
|
||||
}
|
||||
|
||||
/**
|
||||
* BC layer for a wide-spread use-case of old DBAL APIs
|
||||
*
|
||||
* @deprecated Use {@see fetchAllNumeric()}, {@see fetchAllAssociative()} or {@see fetchFirstColumn()} instead.
|
||||
*
|
||||
* @psalm-param FetchMode::* $mode
|
||||
*
|
||||
* @return list<mixed>
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function fetchAll(int $mode = FetchMode::ASSOCIATIVE): array
|
||||
{
|
||||
Deprecation::trigger(
|
||||
'doctrine/dbal',
|
||||
'https://github.com/doctrine/dbal/pull/4007',
|
||||
'%s is deprecated, please use fetchAllNumeric(), fetchAllAssociative() or fetchFirstColumn() instead.',
|
||||
__METHOD__,
|
||||
);
|
||||
|
||||
if (func_num_args() > 1) {
|
||||
throw new LogicException('Only invocations with one argument are still supported by this legacy API.');
|
||||
}
|
||||
|
||||
if ($mode === FetchMode::ASSOCIATIVE) {
|
||||
return $this->fetchAllAssociative();
|
||||
}
|
||||
|
||||
if ($mode === FetchMode::NUMERIC) {
|
||||
return $this->fetchAllNumeric();
|
||||
}
|
||||
|
||||
if ($mode === FetchMode::COLUMN) {
|
||||
return $this->fetchFirstColumn();
|
||||
}
|
||||
|
||||
throw new LogicException('Only fetch modes declared on Doctrine\DBAL\FetchMode are supported by legacy API.');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,224 @@
|
||||
<?php
|
||||
|
||||
namespace Doctrine\DBAL\Schema;
|
||||
|
||||
use Doctrine\DBAL\Platforms\AbstractPlatform;
|
||||
use Doctrine\Deprecations\Deprecation;
|
||||
|
||||
use function array_map;
|
||||
use function crc32;
|
||||
use function dechex;
|
||||
use function explode;
|
||||
use function implode;
|
||||
use function str_replace;
|
||||
use function strpos;
|
||||
use function strtolower;
|
||||
use function strtoupper;
|
||||
use function substr;
|
||||
|
||||
/**
|
||||
* The abstract asset allows to reset the name of all assets without publishing this to the public userland.
|
||||
*
|
||||
* This encapsulation hack is necessary to keep a consistent state of the database schema. Say we have a list of tables
|
||||
* array($tableName => Table($tableName)); if you want to rename the table, you have to make sure this does not get
|
||||
* recreated during schema migration.
|
||||
*/
|
||||
abstract class AbstractAsset
|
||||
{
|
||||
/** @var string */
|
||||
protected $_name = '';
|
||||
|
||||
/**
|
||||
* Namespace of the asset. If none isset the default namespace is assumed.
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
protected $_namespace;
|
||||
|
||||
/** @var bool */
|
||||
protected $_quoted = false;
|
||||
|
||||
/**
|
||||
* Sets the name of this asset.
|
||||
*
|
||||
* @param string $name
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function _setName($name)
|
||||
{
|
||||
if ($this->isIdentifierQuoted($name)) {
|
||||
$this->_quoted = true;
|
||||
$name = $this->trimQuotes($name);
|
||||
}
|
||||
|
||||
if (strpos($name, '.') !== false) {
|
||||
$parts = explode('.', $name);
|
||||
$this->_namespace = $parts[0];
|
||||
$name = $parts[1];
|
||||
}
|
||||
|
||||
$this->_name = $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this asset in the default namespace?
|
||||
*
|
||||
* @param string $defaultNamespaceName
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isInDefaultNamespace($defaultNamespaceName)
|
||||
{
|
||||
return $this->_namespace === $defaultNamespaceName || $this->_namespace === null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the namespace name of this asset.
|
||||
*
|
||||
* If NULL is returned this means the default namespace is used.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getNamespaceName()
|
||||
{
|
||||
return $this->_namespace;
|
||||
}
|
||||
|
||||
/**
|
||||
* The shortest name is stripped of the default namespace. All other
|
||||
* namespaced elements are returned as full-qualified names.
|
||||
*
|
||||
* @param string|null $defaultNamespaceName
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getShortestName($defaultNamespaceName)
|
||||
{
|
||||
$shortestName = $this->getName();
|
||||
if ($this->_namespace === $defaultNamespaceName) {
|
||||
$shortestName = $this->_name;
|
||||
}
|
||||
|
||||
return strtolower($shortestName);
|
||||
}
|
||||
|
||||
/**
|
||||
* The normalized name is full-qualified and lower-cased. Lower-casing is
|
||||
* actually wrong, but we have to do it to keep our sanity. If you are
|
||||
* using database objects that only differentiate in the casing (FOO vs
|
||||
* Foo) then you will NOT be able to use Doctrine Schema abstraction.
|
||||
*
|
||||
* Every non-namespaced element is prefixed with the default namespace
|
||||
* name which is passed as argument to this method.
|
||||
*
|
||||
* @deprecated Use {@see getNamespaceName()} and {@see getName()} instead.
|
||||
*
|
||||
* @param string $defaultNamespaceName
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getFullQualifiedName($defaultNamespaceName)
|
||||
{
|
||||
Deprecation::triggerIfCalledFromOutside(
|
||||
'doctrine/dbal',
|
||||
'https://github.com/doctrine/dbal/pull/4814',
|
||||
'AbstractAsset::getFullQualifiedName() is deprecated.'
|
||||
. ' Use AbstractAsset::getNamespaceName() and ::getName() instead.',
|
||||
);
|
||||
|
||||
$name = $this->getName();
|
||||
if ($this->_namespace === null) {
|
||||
$name = $defaultNamespaceName . '.' . $name;
|
||||
}
|
||||
|
||||
return strtolower($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this asset's name is quoted.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isQuoted()
|
||||
{
|
||||
return $this->_quoted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this identifier is quoted.
|
||||
*
|
||||
* @param string $identifier
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function isIdentifierQuoted($identifier)
|
||||
{
|
||||
return isset($identifier[0]) && ($identifier[0] === '`' || $identifier[0] === '"' || $identifier[0] === '[');
|
||||
}
|
||||
|
||||
/**
|
||||
* Trim quotes from the identifier.
|
||||
*
|
||||
* @param string $identifier
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function trimQuotes($identifier)
|
||||
{
|
||||
return str_replace(['`', '"', '[', ']'], '', $identifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of this schema asset.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
if ($this->_namespace !== null) {
|
||||
return $this->_namespace . '.' . $this->_name;
|
||||
}
|
||||
|
||||
return $this->_name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the quoted representation of this asset but only if it was defined with one. Otherwise
|
||||
* return the plain unquoted value as inserted.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getQuotedName(AbstractPlatform $platform)
|
||||
{
|
||||
$keywords = $platform->getReservedKeywordsList();
|
||||
$parts = explode('.', $this->getName());
|
||||
foreach ($parts as $k => $v) {
|
||||
$parts[$k] = $this->_quoted || $keywords->isKeyword($v) ? $platform->quoteIdentifier($v) : $v;
|
||||
}
|
||||
|
||||
return implode('.', $parts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an identifier from a list of column names obeying a certain string length.
|
||||
*
|
||||
* This is especially important for Oracle, since it does not allow identifiers larger than 30 chars,
|
||||
* however building idents automatically for foreign keys, composite keys or such can easily create
|
||||
* very long names.
|
||||
*
|
||||
* @param string[] $columnNames
|
||||
* @param string $prefix
|
||||
* @param int $maxSize
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function _generateIdentifierName($columnNames, $prefix = '', $maxSize = 30)
|
||||
{
|
||||
$hash = implode('', array_map(static function ($column): string {
|
||||
return dechex(crc32($column));
|
||||
}, $columnNames));
|
||||
|
||||
return strtoupper(substr($prefix . '_' . $hash, 0, $maxSize));
|
||||
}
|
||||
}
|
||||
+1808
File diff suppressed because it is too large
Load Diff
+605
@@ -0,0 +1,605 @@
|
||||
<?php
|
||||
|
||||
namespace Doctrine\DBAL\Schema;
|
||||
|
||||
use Doctrine\DBAL\Platforms\AbstractMySQLPlatform;
|
||||
use Doctrine\DBAL\Platforms\MariaDb1027Platform;
|
||||
use Doctrine\DBAL\Platforms\MySQL;
|
||||
use Doctrine\DBAL\Platforms\MySQL\CollationMetadataProvider\CachingCollationMetadataProvider;
|
||||
use Doctrine\DBAL\Platforms\MySQL\CollationMetadataProvider\ConnectionCollationMetadataProvider;
|
||||
use Doctrine\DBAL\Result;
|
||||
use Doctrine\DBAL\Types\Type;
|
||||
use Doctrine\Deprecations\Deprecation;
|
||||
|
||||
use function array_change_key_case;
|
||||
use function array_shift;
|
||||
use function assert;
|
||||
use function explode;
|
||||
use function implode;
|
||||
use function is_string;
|
||||
use function preg_match;
|
||||
use function strpos;
|
||||
use function strtok;
|
||||
use function strtolower;
|
||||
use function strtr;
|
||||
|
||||
use const CASE_LOWER;
|
||||
|
||||
/**
|
||||
* Schema manager for the MySQL RDBMS.
|
||||
*
|
||||
* @extends AbstractSchemaManager<AbstractMySQLPlatform>
|
||||
*/
|
||||
class MySQLSchemaManager extends AbstractSchemaManager
|
||||
{
|
||||
/** @see https://mariadb.com/kb/en/library/string-literals/#escape-sequences */
|
||||
private const MARIADB_ESCAPE_SEQUENCES = [
|
||||
'\\0' => "\0",
|
||||
"\\'" => "'",
|
||||
'\\"' => '"',
|
||||
'\\b' => "\b",
|
||||
'\\n' => "\n",
|
||||
'\\r' => "\r",
|
||||
'\\t' => "\t",
|
||||
'\\Z' => "\x1a",
|
||||
'\\\\' => '\\',
|
||||
'\\%' => '%',
|
||||
'\\_' => '_',
|
||||
|
||||
// Internally, MariaDB escapes single quotes using the standard syntax
|
||||
"''" => "'",
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function listTableNames()
|
||||
{
|
||||
return $this->doListTableNames();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function listTables()
|
||||
{
|
||||
return $this->doListTables();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @deprecated Use {@see introspectTable()} instead.
|
||||
*/
|
||||
public function listTableDetails($name)
|
||||
{
|
||||
Deprecation::triggerIfCalledFromOutside(
|
||||
'doctrine/dbal',
|
||||
'https://github.com/doctrine/dbal/pull/5595',
|
||||
'%s is deprecated. Use introspectTable() instead.',
|
||||
__METHOD__,
|
||||
);
|
||||
|
||||
return $this->doListTableDetails($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function listTableColumns($table, $database = null)
|
||||
{
|
||||
return $this->doListTableColumns($table, $database);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function listTableIndexes($table)
|
||||
{
|
||||
return $this->doListTableIndexes($table);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function listTableForeignKeys($table, $database = null)
|
||||
{
|
||||
return $this->doListTableForeignKeys($table, $database);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function _getPortableViewDefinition($view)
|
||||
{
|
||||
return new View($view['TABLE_NAME'], $view['VIEW_DEFINITION']);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function _getPortableTableDefinition($table)
|
||||
{
|
||||
return array_shift($table);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function _getPortableTableIndexesList($tableIndexes, $tableName = null)
|
||||
{
|
||||
foreach ($tableIndexes as $k => $v) {
|
||||
$v = array_change_key_case($v, CASE_LOWER);
|
||||
if ($v['key_name'] === 'PRIMARY') {
|
||||
$v['primary'] = true;
|
||||
} else {
|
||||
$v['primary'] = false;
|
||||
}
|
||||
|
||||
if (strpos($v['index_type'], 'FULLTEXT') !== false) {
|
||||
$v['flags'] = ['FULLTEXT'];
|
||||
} elseif (strpos($v['index_type'], 'SPATIAL') !== false) {
|
||||
$v['flags'] = ['SPATIAL'];
|
||||
}
|
||||
|
||||
// Ignore prohibited prefix `length` for spatial index
|
||||
if (strpos($v['index_type'], 'SPATIAL') === false) {
|
||||
$v['length'] = isset($v['sub_part']) ? (int) $v['sub_part'] : null;
|
||||
}
|
||||
|
||||
$tableIndexes[$k] = $v;
|
||||
}
|
||||
|
||||
return parent::_getPortableTableIndexesList($tableIndexes, $tableName);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function _getPortableDatabaseDefinition($database)
|
||||
{
|
||||
return $database['Database'];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function _getPortableTableColumnDefinition($tableColumn)
|
||||
{
|
||||
$tableColumn = array_change_key_case($tableColumn, CASE_LOWER);
|
||||
|
||||
$dbType = strtolower($tableColumn['type']);
|
||||
$dbType = strtok($dbType, '(), ');
|
||||
assert(is_string($dbType));
|
||||
|
||||
$length = $tableColumn['length'] ?? strtok('(), ');
|
||||
|
||||
$fixed = null;
|
||||
|
||||
if (! isset($tableColumn['name'])) {
|
||||
$tableColumn['name'] = '';
|
||||
}
|
||||
|
||||
$scale = null;
|
||||
$precision = null;
|
||||
|
||||
$type = $origType = $this->_platform->getDoctrineTypeMapping($dbType);
|
||||
|
||||
// In cases where not connected to a database DESCRIBE $table does not return 'Comment'
|
||||
if (isset($tableColumn['comment'])) {
|
||||
$type = $this->extractDoctrineTypeFromComment($tableColumn['comment'], $type);
|
||||
$tableColumn['comment'] = $this->removeDoctrineTypeFromComment($tableColumn['comment'], $type);
|
||||
}
|
||||
|
||||
switch ($dbType) {
|
||||
case 'char':
|
||||
case 'binary':
|
||||
$fixed = true;
|
||||
break;
|
||||
|
||||
case 'float':
|
||||
case 'double':
|
||||
case 'real':
|
||||
case 'numeric':
|
||||
case 'decimal':
|
||||
if (
|
||||
preg_match(
|
||||
'([A-Za-z]+\(([0-9]+),([0-9]+)\))',
|
||||
$tableColumn['type'],
|
||||
$match,
|
||||
) === 1
|
||||
) {
|
||||
$precision = $match[1];
|
||||
$scale = $match[2];
|
||||
$length = null;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 'tinytext':
|
||||
$length = AbstractMySQLPlatform::LENGTH_LIMIT_TINYTEXT;
|
||||
break;
|
||||
|
||||
case 'text':
|
||||
$length = AbstractMySQLPlatform::LENGTH_LIMIT_TEXT;
|
||||
break;
|
||||
|
||||
case 'mediumtext':
|
||||
$length = AbstractMySQLPlatform::LENGTH_LIMIT_MEDIUMTEXT;
|
||||
break;
|
||||
|
||||
case 'tinyblob':
|
||||
$length = AbstractMySQLPlatform::LENGTH_LIMIT_TINYBLOB;
|
||||
break;
|
||||
|
||||
case 'blob':
|
||||
$length = AbstractMySQLPlatform::LENGTH_LIMIT_BLOB;
|
||||
break;
|
||||
|
||||
case 'mediumblob':
|
||||
$length = AbstractMySQLPlatform::LENGTH_LIMIT_MEDIUMBLOB;
|
||||
break;
|
||||
|
||||
case 'tinyint':
|
||||
case 'smallint':
|
||||
case 'mediumint':
|
||||
case 'int':
|
||||
case 'integer':
|
||||
case 'bigint':
|
||||
case 'year':
|
||||
$length = null;
|
||||
break;
|
||||
}
|
||||
|
||||
if ($this->_platform instanceof MariaDb1027Platform) {
|
||||
$columnDefault = $this->getMariaDb1027ColumnDefault($this->_platform, $tableColumn['default']);
|
||||
} else {
|
||||
$columnDefault = $tableColumn['default'];
|
||||
}
|
||||
|
||||
$options = [
|
||||
'length' => $length !== null ? (int) $length : null,
|
||||
'unsigned' => strpos($tableColumn['type'], 'unsigned') !== false,
|
||||
'fixed' => (bool) $fixed,
|
||||
'default' => $columnDefault,
|
||||
'notnull' => $tableColumn['null'] !== 'YES',
|
||||
'scale' => null,
|
||||
'precision' => null,
|
||||
'autoincrement' => strpos($tableColumn['extra'], 'auto_increment') !== false,
|
||||
'comment' => isset($tableColumn['comment']) && $tableColumn['comment'] !== ''
|
||||
? $tableColumn['comment']
|
||||
: null,
|
||||
];
|
||||
|
||||
if ($scale !== null && $precision !== null) {
|
||||
$options['scale'] = (int) $scale;
|
||||
$options['precision'] = (int) $precision;
|
||||
}
|
||||
|
||||
$column = new Column($tableColumn['field'], Type::getType($type), $options);
|
||||
|
||||
if (isset($tableColumn['characterset'])) {
|
||||
$column->setPlatformOption('charset', $tableColumn['characterset']);
|
||||
}
|
||||
|
||||
if (isset($tableColumn['collation'])) {
|
||||
$column->setPlatformOption('collation', $tableColumn['collation']);
|
||||
}
|
||||
|
||||
if (isset($tableColumn['declarationMismatch'])) {
|
||||
$column->setPlatformOption('declarationMismatch', $tableColumn['declarationMismatch']);
|
||||
}
|
||||
|
||||
// Check underlying database type where doctrine type is inferred from DC2Type comment
|
||||
// and set a flag if it is not as expected.
|
||||
if ($type === 'json' && $origType !== $type && $this->expectedDbType($type, $options) !== $dbType) {
|
||||
$column->setPlatformOption('declarationMismatch', true);
|
||||
}
|
||||
|
||||
return $column;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the database data type for a given doctrine type and column
|
||||
*
|
||||
* Note that for data types that depend on length where length is not part of the column definition
|
||||
* and therefore the $tableColumn['length'] will not be set, for example TEXT (which could be LONGTEXT,
|
||||
* MEDIUMTEXT) or BLOB (LONGBLOB or TINYBLOB), the expectedDbType cannot be inferred exactly, merely
|
||||
* the default type.
|
||||
*
|
||||
* This method is intended to be used to determine underlying database type where doctrine type is
|
||||
* inferred from a DC2Type comment.
|
||||
*
|
||||
* @param mixed[] $tableColumn
|
||||
*/
|
||||
private function expectedDbType(string $type, array $tableColumn): string
|
||||
{
|
||||
$_type = Type::getType($type);
|
||||
$expectedDbType = strtolower($_type->getSQLDeclaration($tableColumn, $this->_platform));
|
||||
$expectedDbType = strtok($expectedDbType, '(), ');
|
||||
|
||||
return $expectedDbType === false ? '' : $expectedDbType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return Doctrine/Mysql-compatible column default values for MariaDB 10.2.7+ servers.
|
||||
*
|
||||
* - Since MariaDb 10.2.7 column defaults stored in information_schema are now quoted
|
||||
* to distinguish them from expressions (see MDEV-10134).
|
||||
* - CURRENT_TIMESTAMP, CURRENT_TIME, CURRENT_DATE are stored in information_schema
|
||||
* as current_timestamp(), currdate(), currtime()
|
||||
* - Quoted 'NULL' is not enforced by Maria, it is technically possible to have
|
||||
* null in some circumstances (see https://jira.mariadb.org/browse/MDEV-14053)
|
||||
* - \' is always stored as '' in information_schema (normalized)
|
||||
*
|
||||
* @link https://mariadb.com/kb/en/library/information-schema-columns-table/
|
||||
* @link https://jira.mariadb.org/browse/MDEV-13132
|
||||
*
|
||||
* @param string|null $columnDefault default value as stored in information_schema for MariaDB >= 10.2.7
|
||||
*/
|
||||
private function getMariaDb1027ColumnDefault(MariaDb1027Platform $platform, ?string $columnDefault): ?string
|
||||
{
|
||||
if ($columnDefault === 'NULL' || $columnDefault === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (preg_match('/^\'(.*)\'$/', $columnDefault, $matches) === 1) {
|
||||
return strtr($matches[1], self::MARIADB_ESCAPE_SEQUENCES);
|
||||
}
|
||||
|
||||
switch ($columnDefault) {
|
||||
case 'current_timestamp()':
|
||||
return $platform->getCurrentTimestampSQL();
|
||||
|
||||
case 'curdate()':
|
||||
return $platform->getCurrentDateSQL();
|
||||
|
||||
case 'curtime()':
|
||||
return $platform->getCurrentTimeSQL();
|
||||
}
|
||||
|
||||
return $columnDefault;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function _getPortableTableForeignKeysList($tableForeignKeys)
|
||||
{
|
||||
$list = [];
|
||||
foreach ($tableForeignKeys as $value) {
|
||||
$value = array_change_key_case($value, CASE_LOWER);
|
||||
if (! isset($list[$value['constraint_name']])) {
|
||||
if (! isset($value['delete_rule']) || $value['delete_rule'] === 'RESTRICT') {
|
||||
$value['delete_rule'] = null;
|
||||
}
|
||||
|
||||
if (! isset($value['update_rule']) || $value['update_rule'] === 'RESTRICT') {
|
||||
$value['update_rule'] = null;
|
||||
}
|
||||
|
||||
$list[$value['constraint_name']] = [
|
||||
'name' => $value['constraint_name'],
|
||||
'local' => [],
|
||||
'foreign' => [],
|
||||
'foreignTable' => $value['referenced_table_name'],
|
||||
'onDelete' => $value['delete_rule'],
|
||||
'onUpdate' => $value['update_rule'],
|
||||
];
|
||||
}
|
||||
|
||||
$list[$value['constraint_name']]['local'][] = $value['column_name'];
|
||||
$list[$value['constraint_name']]['foreign'][] = $value['referenced_column_name'];
|
||||
}
|
||||
|
||||
return parent::_getPortableTableForeignKeysList($list);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function _getPortableTableForeignKeyDefinition($tableForeignKey): ForeignKeyConstraint
|
||||
{
|
||||
return new ForeignKeyConstraint(
|
||||
$tableForeignKey['local'],
|
||||
$tableForeignKey['foreignTable'],
|
||||
$tableForeignKey['foreign'],
|
||||
$tableForeignKey['name'],
|
||||
[
|
||||
'onDelete' => $tableForeignKey['onDelete'],
|
||||
'onUpdate' => $tableForeignKey['onUpdate'],
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
public function createComparator(): Comparator
|
||||
{
|
||||
return new MySQL\Comparator(
|
||||
$this->_platform,
|
||||
new CachingCollationMetadataProvider(
|
||||
new ConnectionCollationMetadataProvider($this->_conn),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
protected function selectTableNames(string $databaseName): Result
|
||||
{
|
||||
$sql = <<<'SQL'
|
||||
SELECT TABLE_NAME
|
||||
FROM information_schema.TABLES
|
||||
WHERE TABLE_SCHEMA = ?
|
||||
AND TABLE_TYPE = 'BASE TABLE'
|
||||
ORDER BY TABLE_NAME
|
||||
SQL;
|
||||
|
||||
return $this->_conn->executeQuery($sql, [$databaseName]);
|
||||
}
|
||||
|
||||
protected function selectTableColumns(string $databaseName, ?string $tableName = null): Result
|
||||
{
|
||||
// @todo 4.0 - call getColumnTypeSQLSnippet() instead
|
||||
[$columnTypeSQL, $joinCheckConstraintSQL] = $this->_platform->getColumnTypeSQLSnippets('c', $databaseName);
|
||||
|
||||
$sql = 'SELECT';
|
||||
|
||||
if ($tableName === null) {
|
||||
$sql .= ' c.TABLE_NAME,';
|
||||
}
|
||||
|
||||
$sql .= <<<SQL
|
||||
c.COLUMN_NAME AS field,
|
||||
$columnTypeSQL AS type,
|
||||
c.IS_NULLABLE AS `null`,
|
||||
c.COLUMN_KEY AS `key`,
|
||||
c.COLUMN_DEFAULT AS `default`,
|
||||
c.EXTRA,
|
||||
c.COLUMN_COMMENT AS comment,
|
||||
c.CHARACTER_SET_NAME AS characterset,
|
||||
c.COLLATION_NAME AS collation
|
||||
FROM information_schema.COLUMNS c
|
||||
INNER JOIN information_schema.TABLES t
|
||||
ON t.TABLE_NAME = c.TABLE_NAME
|
||||
$joinCheckConstraintSQL
|
||||
SQL;
|
||||
|
||||
// The schema name is passed multiple times as a literal in the WHERE clause instead of using a JOIN condition
|
||||
// in order to avoid performance issues on MySQL older than 8.0 and the corresponding MariaDB versions
|
||||
// caused by https://bugs.mysql.com/bug.php?id=81347
|
||||
$conditions = ['c.TABLE_SCHEMA = ?', 't.TABLE_SCHEMA = ?', "t.TABLE_TYPE = 'BASE TABLE'"];
|
||||
$params = [$databaseName, $databaseName];
|
||||
|
||||
if ($tableName !== null) {
|
||||
$conditions[] = 't.TABLE_NAME = ?';
|
||||
$params[] = $tableName;
|
||||
}
|
||||
|
||||
$sql .= ' WHERE ' . implode(' AND ', $conditions) . ' ORDER BY ORDINAL_POSITION';
|
||||
|
||||
return $this->_conn->executeQuery($sql, $params);
|
||||
}
|
||||
|
||||
protected function selectIndexColumns(string $databaseName, ?string $tableName = null): Result
|
||||
{
|
||||
$sql = 'SELECT';
|
||||
|
||||
if ($tableName === null) {
|
||||
$sql .= ' TABLE_NAME,';
|
||||
}
|
||||
|
||||
$sql .= <<<'SQL'
|
||||
NON_UNIQUE AS Non_Unique,
|
||||
INDEX_NAME AS Key_name,
|
||||
COLUMN_NAME AS Column_Name,
|
||||
SUB_PART AS Sub_Part,
|
||||
INDEX_TYPE AS Index_Type
|
||||
FROM information_schema.STATISTICS
|
||||
SQL;
|
||||
|
||||
$conditions = ['TABLE_SCHEMA = ?'];
|
||||
$params = [$databaseName];
|
||||
|
||||
if ($tableName !== null) {
|
||||
$conditions[] = 'TABLE_NAME = ?';
|
||||
$params[] = $tableName;
|
||||
}
|
||||
|
||||
$sql .= ' WHERE ' . implode(' AND ', $conditions) . ' ORDER BY SEQ_IN_INDEX';
|
||||
|
||||
return $this->_conn->executeQuery($sql, $params);
|
||||
}
|
||||
|
||||
protected function selectForeignKeyColumns(string $databaseName, ?string $tableName = null): Result
|
||||
{
|
||||
$sql = 'SELECT DISTINCT';
|
||||
|
||||
if ($tableName === null) {
|
||||
$sql .= ' k.TABLE_NAME,';
|
||||
}
|
||||
|
||||
$sql .= <<<'SQL'
|
||||
k.CONSTRAINT_NAME,
|
||||
k.COLUMN_NAME,
|
||||
k.REFERENCED_TABLE_NAME,
|
||||
k.REFERENCED_COLUMN_NAME,
|
||||
k.ORDINAL_POSITION /*!50116,
|
||||
c.UPDATE_RULE,
|
||||
c.DELETE_RULE */
|
||||
FROM information_schema.key_column_usage k /*!50116
|
||||
INNER JOIN information_schema.referential_constraints c
|
||||
ON c.CONSTRAINT_NAME = k.CONSTRAINT_NAME
|
||||
AND c.TABLE_NAME = k.TABLE_NAME */
|
||||
SQL;
|
||||
|
||||
$conditions = ['k.TABLE_SCHEMA = ?'];
|
||||
$params = [$databaseName];
|
||||
|
||||
if ($tableName !== null) {
|
||||
$conditions[] = 'k.TABLE_NAME = ?';
|
||||
$params[] = $tableName;
|
||||
}
|
||||
|
||||
$conditions[] = 'k.REFERENCED_COLUMN_NAME IS NOT NULL';
|
||||
|
||||
$sql .= ' WHERE ' . implode(' AND ', $conditions)
|
||||
// The schema name is passed multiple times in the WHERE clause instead of using a JOIN condition
|
||||
// in order to avoid performance issues on MySQL older than 8.0 and the corresponding MariaDB versions
|
||||
// caused by https://bugs.mysql.com/bug.php?id=81347.
|
||||
// Use a string literal for the database name since the internal PDO SQL parser
|
||||
// cannot recognize parameter placeholders inside conditional comments
|
||||
. ' /*!50116 AND c.CONSTRAINT_SCHEMA = ' . $this->_conn->quote($databaseName) . ' */'
|
||||
. ' ORDER BY k.ORDINAL_POSITION';
|
||||
|
||||
return $this->_conn->executeQuery($sql, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function fetchTableOptionsByTable(string $databaseName, ?string $tableName = null): array
|
||||
{
|
||||
$sql = $this->_platform->fetchTableOptionsByTable($tableName !== null);
|
||||
|
||||
$params = [$databaseName];
|
||||
if ($tableName !== null) {
|
||||
$params[] = $tableName;
|
||||
}
|
||||
|
||||
/** @var array<string,array<string,mixed>> $metadata */
|
||||
$metadata = $this->_conn->executeQuery($sql, $params)
|
||||
->fetchAllAssociativeIndexed();
|
||||
|
||||
$tableOptions = [];
|
||||
foreach ($metadata as $table => $data) {
|
||||
$data = array_change_key_case($data, CASE_LOWER);
|
||||
|
||||
$tableOptions[$table] = [
|
||||
'engine' => $data['engine'],
|
||||
'collation' => $data['table_collation'],
|
||||
'charset' => $data['character_set_name'],
|
||||
'autoincrement' => $data['auto_increment'],
|
||||
'comment' => $data['table_comment'],
|
||||
'create_options' => $this->parseCreateOptions($data['create_options']),
|
||||
];
|
||||
}
|
||||
|
||||
return $tableOptions;
|
||||
}
|
||||
|
||||
/** @return string[]|true[] */
|
||||
private function parseCreateOptions(?string $string): array
|
||||
{
|
||||
$options = [];
|
||||
|
||||
if ($string === null || $string === '') {
|
||||
return $options;
|
||||
}
|
||||
|
||||
foreach (explode(' ', $string) as $pair) {
|
||||
$parts = explode('=', $pair, 2);
|
||||
|
||||
$options[$parts[0]] = $parts[1] ?? true;
|
||||
}
|
||||
|
||||
return $options;
|
||||
}
|
||||
}
|
||||
+537
@@ -0,0 +1,537 @@
|
||||
<?php
|
||||
|
||||
namespace Doctrine\DBAL\Schema;
|
||||
|
||||
use Doctrine\DBAL\Exception;
|
||||
use Doctrine\DBAL\Platforms\OraclePlatform;
|
||||
use Doctrine\DBAL\Result;
|
||||
use Doctrine\DBAL\Types\Type;
|
||||
use Doctrine\Deprecations\Deprecation;
|
||||
|
||||
use function array_change_key_case;
|
||||
use function array_values;
|
||||
use function implode;
|
||||
use function is_string;
|
||||
use function preg_match;
|
||||
use function str_replace;
|
||||
use function strpos;
|
||||
use function strtolower;
|
||||
use function strtoupper;
|
||||
use function trim;
|
||||
|
||||
use const CASE_LOWER;
|
||||
|
||||
/**
|
||||
* Oracle Schema Manager.
|
||||
*
|
||||
* @extends AbstractSchemaManager<OraclePlatform>
|
||||
*/
|
||||
class OracleSchemaManager extends AbstractSchemaManager
|
||||
{
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function listTableNames()
|
||||
{
|
||||
return $this->doListTableNames();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function listTables()
|
||||
{
|
||||
return $this->doListTables();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @deprecated Use {@see introspectTable()} instead.
|
||||
*/
|
||||
public function listTableDetails($name)
|
||||
{
|
||||
Deprecation::triggerIfCalledFromOutside(
|
||||
'doctrine/dbal',
|
||||
'https://github.com/doctrine/dbal/pull/5595',
|
||||
'%s is deprecated. Use introspectTable() instead.',
|
||||
__METHOD__,
|
||||
);
|
||||
|
||||
return $this->doListTableDetails($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function listTableColumns($table, $database = null)
|
||||
{
|
||||
return $this->doListTableColumns($table, $database);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function listTableIndexes($table)
|
||||
{
|
||||
return $this->doListTableIndexes($table);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function listTableForeignKeys($table, $database = null)
|
||||
{
|
||||
return $this->doListTableForeignKeys($table, $database);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function _getPortableViewDefinition($view)
|
||||
{
|
||||
$view = array_change_key_case($view, CASE_LOWER);
|
||||
|
||||
return new View($this->getQuotedIdentifierName($view['view_name']), $view['text']);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function _getPortableTableDefinition($table)
|
||||
{
|
||||
$table = array_change_key_case($table, CASE_LOWER);
|
||||
|
||||
return $this->getQuotedIdentifierName($table['table_name']);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function _getPortableTableIndexesList($tableIndexes, $tableName = null)
|
||||
{
|
||||
$indexBuffer = [];
|
||||
foreach ($tableIndexes as $tableIndex) {
|
||||
$tableIndex = array_change_key_case($tableIndex, CASE_LOWER);
|
||||
|
||||
$keyName = strtolower($tableIndex['name']);
|
||||
$buffer = [];
|
||||
|
||||
if ($tableIndex['is_primary'] === 'P') {
|
||||
$keyName = 'primary';
|
||||
$buffer['primary'] = true;
|
||||
$buffer['non_unique'] = false;
|
||||
} else {
|
||||
$buffer['primary'] = false;
|
||||
$buffer['non_unique'] = ! $tableIndex['is_unique'];
|
||||
}
|
||||
|
||||
$buffer['key_name'] = $keyName;
|
||||
$buffer['column_name'] = $this->getQuotedIdentifierName($tableIndex['column_name']);
|
||||
$indexBuffer[] = $buffer;
|
||||
}
|
||||
|
||||
return parent::_getPortableTableIndexesList($indexBuffer, $tableName);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function _getPortableTableColumnDefinition($tableColumn)
|
||||
{
|
||||
$tableColumn = array_change_key_case($tableColumn, CASE_LOWER);
|
||||
|
||||
$dbType = strtolower($tableColumn['data_type']);
|
||||
if (strpos($dbType, 'timestamp(') === 0) {
|
||||
if (strpos($dbType, 'with time zone') !== false) {
|
||||
$dbType = 'timestamptz';
|
||||
} else {
|
||||
$dbType = 'timestamp';
|
||||
}
|
||||
}
|
||||
|
||||
$unsigned = $fixed = $precision = $scale = $length = null;
|
||||
|
||||
if (! isset($tableColumn['column_name'])) {
|
||||
$tableColumn['column_name'] = '';
|
||||
}
|
||||
|
||||
// Default values returned from database sometimes have trailing spaces.
|
||||
if (is_string($tableColumn['data_default'])) {
|
||||
$tableColumn['data_default'] = trim($tableColumn['data_default']);
|
||||
}
|
||||
|
||||
if ($tableColumn['data_default'] === '' || $tableColumn['data_default'] === 'NULL') {
|
||||
$tableColumn['data_default'] = null;
|
||||
}
|
||||
|
||||
if ($tableColumn['data_default'] !== null) {
|
||||
// Default values returned from database are represented as literal expressions
|
||||
if (preg_match('/^\'(.*)\'$/s', $tableColumn['data_default'], $matches) === 1) {
|
||||
$tableColumn['data_default'] = str_replace("''", "'", $matches[1]);
|
||||
}
|
||||
}
|
||||
|
||||
if ($tableColumn['data_precision'] !== null) {
|
||||
$precision = (int) $tableColumn['data_precision'];
|
||||
}
|
||||
|
||||
if ($tableColumn['data_scale'] !== null) {
|
||||
$scale = (int) $tableColumn['data_scale'];
|
||||
}
|
||||
|
||||
$type = $this->_platform->getDoctrineTypeMapping($dbType);
|
||||
$type = $this->extractDoctrineTypeFromComment($tableColumn['comments'], $type);
|
||||
$tableColumn['comments'] = $this->removeDoctrineTypeFromComment($tableColumn['comments'], $type);
|
||||
|
||||
switch ($dbType) {
|
||||
case 'number':
|
||||
if ($precision === 20 && $scale === 0) {
|
||||
$type = 'bigint';
|
||||
} elseif ($precision === 5 && $scale === 0) {
|
||||
$type = 'smallint';
|
||||
} elseif ($precision === 1 && $scale === 0) {
|
||||
$type = 'boolean';
|
||||
} elseif ($scale > 0) {
|
||||
$type = 'decimal';
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 'varchar':
|
||||
case 'varchar2':
|
||||
case 'nvarchar2':
|
||||
$length = $tableColumn['char_length'];
|
||||
$fixed = false;
|
||||
break;
|
||||
|
||||
case 'raw':
|
||||
$length = $tableColumn['data_length'];
|
||||
$fixed = true;
|
||||
break;
|
||||
|
||||
case 'char':
|
||||
case 'nchar':
|
||||
$length = $tableColumn['char_length'];
|
||||
$fixed = true;
|
||||
break;
|
||||
}
|
||||
|
||||
$options = [
|
||||
'notnull' => $tableColumn['nullable'] === 'N',
|
||||
'fixed' => (bool) $fixed,
|
||||
'unsigned' => (bool) $unsigned,
|
||||
'default' => $tableColumn['data_default'],
|
||||
'length' => $length,
|
||||
'precision' => $precision,
|
||||
'scale' => $scale,
|
||||
'comment' => isset($tableColumn['comments']) && $tableColumn['comments'] !== ''
|
||||
? $tableColumn['comments']
|
||||
: null,
|
||||
];
|
||||
|
||||
return new Column($this->getQuotedIdentifierName($tableColumn['column_name']), Type::getType($type), $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function _getPortableTableForeignKeysList($tableForeignKeys)
|
||||
{
|
||||
$list = [];
|
||||
foreach ($tableForeignKeys as $value) {
|
||||
$value = array_change_key_case($value, CASE_LOWER);
|
||||
if (! isset($list[$value['constraint_name']])) {
|
||||
if ($value['delete_rule'] === 'NO ACTION') {
|
||||
$value['delete_rule'] = null;
|
||||
}
|
||||
|
||||
$list[$value['constraint_name']] = [
|
||||
'name' => $this->getQuotedIdentifierName($value['constraint_name']),
|
||||
'local' => [],
|
||||
'foreign' => [],
|
||||
'foreignTable' => $value['references_table'],
|
||||
'onDelete' => $value['delete_rule'],
|
||||
];
|
||||
}
|
||||
|
||||
$localColumn = $this->getQuotedIdentifierName($value['local_column']);
|
||||
$foreignColumn = $this->getQuotedIdentifierName($value['foreign_column']);
|
||||
|
||||
$list[$value['constraint_name']]['local'][$value['position']] = $localColumn;
|
||||
$list[$value['constraint_name']]['foreign'][$value['position']] = $foreignColumn;
|
||||
}
|
||||
|
||||
return parent::_getPortableTableForeignKeysList($list);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function _getPortableTableForeignKeyDefinition($tableForeignKey): ForeignKeyConstraint
|
||||
{
|
||||
return new ForeignKeyConstraint(
|
||||
array_values($tableForeignKey['local']),
|
||||
$this->getQuotedIdentifierName($tableForeignKey['foreignTable']),
|
||||
array_values($tableForeignKey['foreign']),
|
||||
$this->getQuotedIdentifierName($tableForeignKey['name']),
|
||||
['onDelete' => $tableForeignKey['onDelete']],
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function _getPortableSequenceDefinition($sequence)
|
||||
{
|
||||
$sequence = array_change_key_case($sequence, CASE_LOWER);
|
||||
|
||||
return new Sequence(
|
||||
$this->getQuotedIdentifierName($sequence['sequence_name']),
|
||||
(int) $sequence['increment_by'],
|
||||
(int) $sequence['min_value'],
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function _getPortableDatabaseDefinition($database)
|
||||
{
|
||||
$database = array_change_key_case($database, CASE_LOWER);
|
||||
|
||||
return $database['username'];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function createDatabase($database)
|
||||
{
|
||||
$statement = $this->_platform->getCreateDatabaseSQL($database);
|
||||
|
||||
$params = $this->_conn->getParams();
|
||||
|
||||
if (isset($params['password'])) {
|
||||
$statement .= ' IDENTIFIED BY ' . $params['password'];
|
||||
}
|
||||
|
||||
$this->_conn->executeStatement($statement);
|
||||
|
||||
$statement = 'GRANT DBA TO ' . $database;
|
||||
$this->_conn->executeStatement($statement);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal The method should be only used from within the OracleSchemaManager class hierarchy.
|
||||
*
|
||||
* @param string $table
|
||||
*
|
||||
* @return bool
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function dropAutoincrement($table)
|
||||
{
|
||||
$sql = $this->_platform->getDropAutoincrementSql($table);
|
||||
foreach ($sql as $query) {
|
||||
$this->_conn->executeStatement($query);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function dropTable($name)
|
||||
{
|
||||
$this->tryMethod('dropAutoincrement', $name);
|
||||
|
||||
parent::dropTable($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the quoted representation of the given identifier name.
|
||||
*
|
||||
* Quotes non-uppercase identifiers explicitly to preserve case
|
||||
* and thus make references to the particular identifier work.
|
||||
*
|
||||
* @param string $identifier The identifier to quote.
|
||||
*/
|
||||
private function getQuotedIdentifierName($identifier): string
|
||||
{
|
||||
if (preg_match('/[a-z]/', $identifier) === 1) {
|
||||
return $this->_platform->quoteIdentifier($identifier);
|
||||
}
|
||||
|
||||
return $identifier;
|
||||
}
|
||||
|
||||
protected function selectTableNames(string $databaseName): Result
|
||||
{
|
||||
$sql = <<<'SQL'
|
||||
SELECT TABLE_NAME
|
||||
FROM ALL_TABLES
|
||||
WHERE OWNER = :OWNER
|
||||
ORDER BY TABLE_NAME
|
||||
SQL;
|
||||
|
||||
return $this->_conn->executeQuery($sql, ['OWNER' => $databaseName]);
|
||||
}
|
||||
|
||||
protected function selectTableColumns(string $databaseName, ?string $tableName = null): Result
|
||||
{
|
||||
$sql = 'SELECT';
|
||||
|
||||
if ($tableName === null) {
|
||||
$sql .= ' C.TABLE_NAME,';
|
||||
}
|
||||
|
||||
$sql .= <<<'SQL'
|
||||
C.COLUMN_NAME,
|
||||
C.DATA_TYPE,
|
||||
C.DATA_DEFAULT,
|
||||
C.DATA_PRECISION,
|
||||
C.DATA_SCALE,
|
||||
C.CHAR_LENGTH,
|
||||
C.DATA_LENGTH,
|
||||
C.NULLABLE,
|
||||
D.COMMENTS
|
||||
FROM ALL_TAB_COLUMNS C
|
||||
INNER JOIN ALL_TABLES T
|
||||
ON T.OWNER = C.OWNER
|
||||
AND T.TABLE_NAME = C.TABLE_NAME
|
||||
LEFT JOIN ALL_COL_COMMENTS D
|
||||
ON D.OWNER = C.OWNER
|
||||
AND D.TABLE_NAME = C.TABLE_NAME
|
||||
AND D.COLUMN_NAME = C.COLUMN_NAME
|
||||
SQL;
|
||||
|
||||
$conditions = ['C.OWNER = :OWNER'];
|
||||
$params = ['OWNER' => $databaseName];
|
||||
|
||||
if ($tableName !== null) {
|
||||
$conditions[] = 'C.TABLE_NAME = :TABLE_NAME';
|
||||
$params['TABLE_NAME'] = $tableName;
|
||||
}
|
||||
|
||||
$sql .= ' WHERE ' . implode(' AND ', $conditions) . ' ORDER BY C.COLUMN_ID';
|
||||
|
||||
return $this->_conn->executeQuery($sql, $params);
|
||||
}
|
||||
|
||||
protected function selectIndexColumns(string $databaseName, ?string $tableName = null): Result
|
||||
{
|
||||
$sql = 'SELECT';
|
||||
|
||||
if ($tableName === null) {
|
||||
$sql .= ' IND_COL.TABLE_NAME,';
|
||||
}
|
||||
|
||||
$sql .= <<<'SQL'
|
||||
IND_COL.INDEX_NAME AS NAME,
|
||||
IND.INDEX_TYPE AS TYPE,
|
||||
DECODE(IND.UNIQUENESS, 'NONUNIQUE', 0, 'UNIQUE', 1) AS IS_UNIQUE,
|
||||
IND_COL.COLUMN_NAME,
|
||||
IND_COL.COLUMN_POSITION AS COLUMN_POS,
|
||||
CON.CONSTRAINT_TYPE AS IS_PRIMARY
|
||||
FROM ALL_IND_COLUMNS IND_COL
|
||||
LEFT JOIN ALL_INDEXES IND
|
||||
ON IND.OWNER = IND_COL.INDEX_OWNER
|
||||
AND IND.INDEX_NAME = IND_COL.INDEX_NAME
|
||||
LEFT JOIN ALL_CONSTRAINTS CON
|
||||
ON CON.OWNER = IND_COL.INDEX_OWNER
|
||||
AND CON.INDEX_NAME = IND_COL.INDEX_NAME
|
||||
SQL;
|
||||
|
||||
$conditions = ['IND_COL.INDEX_OWNER = :OWNER'];
|
||||
$params = ['OWNER' => $databaseName];
|
||||
|
||||
if ($tableName !== null) {
|
||||
$conditions[] = 'IND_COL.TABLE_NAME = :TABLE_NAME';
|
||||
$params['TABLE_NAME'] = $tableName;
|
||||
}
|
||||
|
||||
$sql .= ' WHERE ' . implode(' AND ', $conditions) . ' ORDER BY IND_COL.TABLE_NAME, IND_COL.INDEX_NAME'
|
||||
. ', IND_COL.COLUMN_POSITION';
|
||||
|
||||
return $this->_conn->executeQuery($sql, $params);
|
||||
}
|
||||
|
||||
protected function selectForeignKeyColumns(string $databaseName, ?string $tableName = null): Result
|
||||
{
|
||||
$sql = 'SELECT';
|
||||
|
||||
if ($tableName === null) {
|
||||
$sql .= ' COLS.TABLE_NAME,';
|
||||
}
|
||||
|
||||
$sql .= <<<'SQL'
|
||||
ALC.CONSTRAINT_NAME,
|
||||
ALC.DELETE_RULE,
|
||||
COLS.COLUMN_NAME LOCAL_COLUMN,
|
||||
COLS.POSITION,
|
||||
R_COLS.TABLE_NAME REFERENCES_TABLE,
|
||||
R_COLS.COLUMN_NAME FOREIGN_COLUMN
|
||||
FROM ALL_CONS_COLUMNS COLS
|
||||
LEFT JOIN ALL_CONSTRAINTS ALC ON ALC.OWNER = COLS.OWNER AND ALC.CONSTRAINT_NAME = COLS.CONSTRAINT_NAME
|
||||
LEFT JOIN ALL_CONS_COLUMNS R_COLS ON R_COLS.OWNER = ALC.R_OWNER AND
|
||||
R_COLS.CONSTRAINT_NAME = ALC.R_CONSTRAINT_NAME AND
|
||||
R_COLS.POSITION = COLS.POSITION
|
||||
SQL;
|
||||
|
||||
$conditions = ["ALC.CONSTRAINT_TYPE = 'R'", 'COLS.OWNER = :OWNER'];
|
||||
$params = ['OWNER' => $databaseName];
|
||||
|
||||
if ($tableName !== null) {
|
||||
$conditions[] = 'COLS.TABLE_NAME = :TABLE_NAME';
|
||||
$params['TABLE_NAME'] = $tableName;
|
||||
}
|
||||
|
||||
$sql .= ' WHERE ' . implode(' AND ', $conditions) . ' ORDER BY COLS.TABLE_NAME, COLS.CONSTRAINT_NAME'
|
||||
. ', COLS.POSITION';
|
||||
|
||||
return $this->_conn->executeQuery($sql, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function fetchTableOptionsByTable(string $databaseName, ?string $tableName = null): array
|
||||
{
|
||||
$sql = 'SELECT TABLE_NAME, COMMENTS';
|
||||
|
||||
$conditions = ['OWNER = :OWNER'];
|
||||
$params = ['OWNER' => $databaseName];
|
||||
|
||||
if ($tableName !== null) {
|
||||
$conditions[] = 'TABLE_NAME = :TABLE_NAME';
|
||||
$params['TABLE_NAME'] = $tableName;
|
||||
}
|
||||
|
||||
$sql .= ' FROM ALL_TAB_COMMENTS WHERE ' . implode(' AND ', $conditions);
|
||||
|
||||
/** @var array<string,array<string,mixed>> $metadata */
|
||||
$metadata = $this->_conn->executeQuery($sql, $params)
|
||||
->fetchAllAssociativeIndexed();
|
||||
|
||||
$tableOptions = [];
|
||||
foreach ($metadata as $table => $data) {
|
||||
$data = array_change_key_case($data, CASE_LOWER);
|
||||
|
||||
$tableOptions[$table] = [
|
||||
'comment' => $data['comments'],
|
||||
];
|
||||
}
|
||||
|
||||
return $tableOptions;
|
||||
}
|
||||
|
||||
protected function normalizeName(string $name): string
|
||||
{
|
||||
$identifier = new Identifier($name);
|
||||
|
||||
return $identifier->isQuoted() ? $identifier->getName() : strtoupper($name);
|
||||
}
|
||||
}
|
||||
+769
@@ -0,0 +1,769 @@
|
||||
<?php
|
||||
|
||||
namespace Doctrine\DBAL\Schema;
|
||||
|
||||
use Doctrine\DBAL\Exception;
|
||||
use Doctrine\DBAL\Platforms\PostgreSQLPlatform;
|
||||
use Doctrine\DBAL\Result;
|
||||
use Doctrine\DBAL\Types\JsonType;
|
||||
use Doctrine\DBAL\Types\Type;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\Deprecations\Deprecation;
|
||||
|
||||
use function array_change_key_case;
|
||||
use function array_filter;
|
||||
use function array_map;
|
||||
use function array_merge;
|
||||
use function array_shift;
|
||||
use function assert;
|
||||
use function explode;
|
||||
use function get_class;
|
||||
use function implode;
|
||||
use function in_array;
|
||||
use function preg_match;
|
||||
use function preg_replace;
|
||||
use function sprintf;
|
||||
use function str_replace;
|
||||
use function strpos;
|
||||
use function strtolower;
|
||||
use function trim;
|
||||
|
||||
use const CASE_LOWER;
|
||||
|
||||
/**
|
||||
* PostgreSQL Schema Manager.
|
||||
*
|
||||
* @extends AbstractSchemaManager<PostgreSQLPlatform>
|
||||
*/
|
||||
class PostgreSQLSchemaManager extends AbstractSchemaManager
|
||||
{
|
||||
/** @var string[]|null */
|
||||
private ?array $existingSchemaPaths = null;
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function listTableNames()
|
||||
{
|
||||
return $this->doListTableNames();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function listTables()
|
||||
{
|
||||
return $this->doListTables();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @deprecated Use {@see introspectTable()} instead.
|
||||
*/
|
||||
public function listTableDetails($name)
|
||||
{
|
||||
Deprecation::triggerIfCalledFromOutside(
|
||||
'doctrine/dbal',
|
||||
'https://github.com/doctrine/dbal/pull/5595',
|
||||
'%s is deprecated. Use introspectTable() instead.',
|
||||
__METHOD__,
|
||||
);
|
||||
|
||||
return $this->doListTableDetails($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function listTableColumns($table, $database = null)
|
||||
{
|
||||
return $this->doListTableColumns($table, $database);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function listTableIndexes($table)
|
||||
{
|
||||
return $this->doListTableIndexes($table);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function listTableForeignKeys($table, $database = null)
|
||||
{
|
||||
return $this->doListTableForeignKeys($table, $database);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all the existing schema names.
|
||||
*
|
||||
* @deprecated Use {@see listSchemaNames()} instead.
|
||||
*
|
||||
* @return string[]
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function getSchemaNames()
|
||||
{
|
||||
Deprecation::trigger(
|
||||
'doctrine/dbal',
|
||||
'https://github.com/doctrine/dbal/issues/4503',
|
||||
'PostgreSQLSchemaManager::getSchemaNames() is deprecated,'
|
||||
. ' use PostgreSQLSchemaManager::listSchemaNames() instead.',
|
||||
);
|
||||
|
||||
return $this->listNamespaceNames();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function listSchemaNames(): array
|
||||
{
|
||||
return $this->_conn->fetchFirstColumn(
|
||||
<<<'SQL'
|
||||
SELECT schema_name
|
||||
FROM information_schema.schemata
|
||||
WHERE schema_name NOT LIKE 'pg\_%'
|
||||
AND schema_name != 'information_schema'
|
||||
SQL,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
public function getSchemaSearchPaths()
|
||||
{
|
||||
Deprecation::triggerIfCalledFromOutside(
|
||||
'doctrine/dbal',
|
||||
'https://github.com/doctrine/dbal/pull/4821',
|
||||
'PostgreSQLSchemaManager::getSchemaSearchPaths() is deprecated.',
|
||||
);
|
||||
|
||||
$params = $this->_conn->getParams();
|
||||
|
||||
$searchPaths = $this->_conn->fetchOne('SHOW search_path');
|
||||
assert($searchPaths !== false);
|
||||
|
||||
$schema = explode(',', $searchPaths);
|
||||
|
||||
if (isset($params['user'])) {
|
||||
$schema = str_replace('"$user"', $params['user'], $schema);
|
||||
}
|
||||
|
||||
return array_map('trim', $schema);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets names of all existing schemas in the current users search path.
|
||||
*
|
||||
* This is a PostgreSQL only function.
|
||||
*
|
||||
* @internal The method should be only used from within the PostgreSQLSchemaManager class hierarchy.
|
||||
*
|
||||
* @return string[]
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function getExistingSchemaSearchPaths()
|
||||
{
|
||||
if ($this->existingSchemaPaths === null) {
|
||||
$this->determineExistingSchemaSearchPaths();
|
||||
}
|
||||
|
||||
assert($this->existingSchemaPaths !== null);
|
||||
|
||||
return $this->existingSchemaPaths;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the current schema.
|
||||
*
|
||||
* @return string|null
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function getCurrentSchema()
|
||||
{
|
||||
$schemas = $this->getExistingSchemaSearchPaths();
|
||||
|
||||
return array_shift($schemas);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets or resets the order of the existing schemas in the current search path of the user.
|
||||
*
|
||||
* This is a PostgreSQL only function.
|
||||
*
|
||||
* @internal The method should be only used from within the PostgreSQLSchemaManager class hierarchy.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function determineExistingSchemaSearchPaths()
|
||||
{
|
||||
$names = $this->listSchemaNames();
|
||||
$paths = $this->getSchemaSearchPaths();
|
||||
|
||||
$this->existingSchemaPaths = array_filter($paths, static function ($v) use ($names): bool {
|
||||
return in_array($v, $names, true);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function _getPortableTableForeignKeyDefinition($tableForeignKey)
|
||||
{
|
||||
$onUpdate = null;
|
||||
$onDelete = null;
|
||||
|
||||
if (
|
||||
preg_match(
|
||||
'(ON UPDATE ([a-zA-Z0-9]+( (NULL|ACTION|DEFAULT))?))',
|
||||
$tableForeignKey['condef'],
|
||||
$match,
|
||||
) === 1
|
||||
) {
|
||||
$onUpdate = $match[1];
|
||||
}
|
||||
|
||||
if (
|
||||
preg_match(
|
||||
'(ON DELETE ([a-zA-Z0-9]+( (NULL|ACTION|DEFAULT))?))',
|
||||
$tableForeignKey['condef'],
|
||||
$match,
|
||||
) === 1
|
||||
) {
|
||||
$onDelete = $match[1];
|
||||
}
|
||||
|
||||
$result = preg_match('/FOREIGN KEY \((.+)\) REFERENCES (.+)\((.+)\)/', $tableForeignKey['condef'], $values);
|
||||
assert($result === 1);
|
||||
|
||||
// PostgreSQL returns identifiers that are keywords with quotes, we need them later, don't get
|
||||
// the idea to trim them here.
|
||||
$localColumns = array_map('trim', explode(',', $values[1]));
|
||||
$foreignColumns = array_map('trim', explode(',', $values[3]));
|
||||
$foreignTable = $values[2];
|
||||
|
||||
return new ForeignKeyConstraint(
|
||||
$localColumns,
|
||||
$foreignTable,
|
||||
$foreignColumns,
|
||||
$tableForeignKey['conname'],
|
||||
['onUpdate' => $onUpdate, 'onDelete' => $onDelete],
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function _getPortableViewDefinition($view)
|
||||
{
|
||||
return new View($view['schemaname'] . '.' . $view['viewname'], $view['definition']);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function _getPortableTableDefinition($table)
|
||||
{
|
||||
$currentSchema = $this->getCurrentSchema();
|
||||
|
||||
if ($table['schema_name'] === $currentSchema) {
|
||||
return $table['table_name'];
|
||||
}
|
||||
|
||||
return $table['schema_name'] . '.' . $table['table_name'];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function _getPortableTableIndexesList($tableIndexes, $tableName = null)
|
||||
{
|
||||
$buffer = [];
|
||||
foreach ($tableIndexes as $row) {
|
||||
$colNumbers = array_map('intval', explode(' ', $row['indkey']));
|
||||
$columnNameSql = sprintf(
|
||||
'SELECT attnum, attname FROM pg_attribute WHERE attrelid=%d AND attnum IN (%s) ORDER BY attnum ASC',
|
||||
$row['indrelid'],
|
||||
implode(' ,', $colNumbers),
|
||||
);
|
||||
|
||||
$indexColumns = $this->_conn->fetchAllAssociative($columnNameSql);
|
||||
|
||||
// required for getting the order of the columns right.
|
||||
foreach ($colNumbers as $colNum) {
|
||||
foreach ($indexColumns as $colRow) {
|
||||
if ($colNum !== $colRow['attnum']) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$buffer[] = [
|
||||
'key_name' => $row['relname'],
|
||||
'column_name' => trim($colRow['attname']),
|
||||
'non_unique' => ! $row['indisunique'],
|
||||
'primary' => $row['indisprimary'],
|
||||
'where' => $row['where'],
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return parent::_getPortableTableIndexesList($buffer, $tableName);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function _getPortableDatabaseDefinition($database)
|
||||
{
|
||||
return $database['datname'];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @deprecated Use {@see listSchemaNames()} instead.
|
||||
*/
|
||||
protected function getPortableNamespaceDefinition(array $namespace)
|
||||
{
|
||||
Deprecation::triggerIfCalledFromOutside(
|
||||
'doctrine/dbal',
|
||||
'https://github.com/doctrine/dbal/issues/4503',
|
||||
'PostgreSQLSchemaManager::getPortableNamespaceDefinition() is deprecated,'
|
||||
. ' use PostgreSQLSchemaManager::listSchemaNames() instead.',
|
||||
);
|
||||
|
||||
return $namespace['nspname'];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function _getPortableSequenceDefinition($sequence)
|
||||
{
|
||||
if ($sequence['schemaname'] !== 'public') {
|
||||
$sequenceName = $sequence['schemaname'] . '.' . $sequence['relname'];
|
||||
} else {
|
||||
$sequenceName = $sequence['relname'];
|
||||
}
|
||||
|
||||
return new Sequence($sequenceName, (int) $sequence['increment_by'], (int) $sequence['min_value']);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function _getPortableTableColumnDefinition($tableColumn)
|
||||
{
|
||||
$tableColumn = array_change_key_case($tableColumn, CASE_LOWER);
|
||||
|
||||
if (strtolower($tableColumn['type']) === 'varchar' || strtolower($tableColumn['type']) === 'bpchar') {
|
||||
// get length from varchar definition
|
||||
$length = preg_replace('~.*\(([0-9]*)\).*~', '$1', $tableColumn['complete_type']);
|
||||
$tableColumn['length'] = $length;
|
||||
}
|
||||
|
||||
$matches = [];
|
||||
|
||||
$autoincrement = false;
|
||||
|
||||
if (
|
||||
$tableColumn['default'] !== null
|
||||
&& preg_match("/^nextval\('(.*)'(::.*)?\)$/", $tableColumn['default'], $matches) === 1
|
||||
) {
|
||||
$tableColumn['sequence'] = $matches[1];
|
||||
$tableColumn['default'] = null;
|
||||
$autoincrement = true;
|
||||
}
|
||||
|
||||
if ($tableColumn['default'] !== null) {
|
||||
if (preg_match("/^['(](.*)[')]::/", $tableColumn['default'], $matches) === 1) {
|
||||
$tableColumn['default'] = $matches[1];
|
||||
} elseif (preg_match('/^NULL::/', $tableColumn['default']) === 1) {
|
||||
$tableColumn['default'] = null;
|
||||
}
|
||||
}
|
||||
|
||||
$length = $tableColumn['length'] ?? null;
|
||||
if ($length === '-1' && isset($tableColumn['atttypmod'])) {
|
||||
$length = $tableColumn['atttypmod'] - 4;
|
||||
}
|
||||
|
||||
if ((int) $length <= 0) {
|
||||
$length = null;
|
||||
}
|
||||
|
||||
$fixed = null;
|
||||
|
||||
if (! isset($tableColumn['name'])) {
|
||||
$tableColumn['name'] = '';
|
||||
}
|
||||
|
||||
$precision = null;
|
||||
$scale = null;
|
||||
$jsonb = null;
|
||||
|
||||
$dbType = strtolower($tableColumn['type']);
|
||||
if (
|
||||
$tableColumn['domain_type'] !== null
|
||||
&& $tableColumn['domain_type'] !== ''
|
||||
&& ! $this->_platform->hasDoctrineTypeMappingFor($tableColumn['type'])
|
||||
) {
|
||||
$dbType = strtolower($tableColumn['domain_type']);
|
||||
$tableColumn['complete_type'] = $tableColumn['domain_complete_type'];
|
||||
}
|
||||
|
||||
$type = $this->_platform->getDoctrineTypeMapping($dbType);
|
||||
$type = $this->extractDoctrineTypeFromComment($tableColumn['comment'], $type);
|
||||
$tableColumn['comment'] = $this->removeDoctrineTypeFromComment($tableColumn['comment'], $type);
|
||||
|
||||
switch ($dbType) {
|
||||
case 'smallint':
|
||||
case 'int2':
|
||||
$tableColumn['default'] = $this->fixVersion94NegativeNumericDefaultValue($tableColumn['default']);
|
||||
$length = null;
|
||||
break;
|
||||
|
||||
case 'int':
|
||||
case 'int4':
|
||||
case 'integer':
|
||||
$tableColumn['default'] = $this->fixVersion94NegativeNumericDefaultValue($tableColumn['default']);
|
||||
$length = null;
|
||||
break;
|
||||
|
||||
case 'bigint':
|
||||
case 'int8':
|
||||
$tableColumn['default'] = $this->fixVersion94NegativeNumericDefaultValue($tableColumn['default']);
|
||||
$length = null;
|
||||
break;
|
||||
|
||||
case 'bool':
|
||||
case 'boolean':
|
||||
if ($tableColumn['default'] === 'true') {
|
||||
$tableColumn['default'] = true;
|
||||
}
|
||||
|
||||
if ($tableColumn['default'] === 'false') {
|
||||
$tableColumn['default'] = false;
|
||||
}
|
||||
|
||||
$length = null;
|
||||
break;
|
||||
|
||||
case 'json':
|
||||
case 'text':
|
||||
case '_varchar':
|
||||
case 'varchar':
|
||||
$tableColumn['default'] = $this->parseDefaultExpression($tableColumn['default']);
|
||||
$fixed = false;
|
||||
break;
|
||||
case 'interval':
|
||||
$fixed = false;
|
||||
break;
|
||||
|
||||
case 'char':
|
||||
case 'bpchar':
|
||||
$fixed = true;
|
||||
break;
|
||||
|
||||
case 'float':
|
||||
case 'float4':
|
||||
case 'float8':
|
||||
case 'double':
|
||||
case 'double precision':
|
||||
case 'real':
|
||||
case 'decimal':
|
||||
case 'money':
|
||||
case 'numeric':
|
||||
$tableColumn['default'] = $this->fixVersion94NegativeNumericDefaultValue($tableColumn['default']);
|
||||
|
||||
if (
|
||||
preg_match(
|
||||
'([A-Za-z]+\(([0-9]+),([0-9]+)\))',
|
||||
$tableColumn['complete_type'],
|
||||
$match,
|
||||
) === 1
|
||||
) {
|
||||
$precision = $match[1];
|
||||
$scale = $match[2];
|
||||
$length = null;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 'year':
|
||||
$length = null;
|
||||
break;
|
||||
|
||||
// PostgreSQL 9.4+ only
|
||||
case 'jsonb':
|
||||
$jsonb = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (
|
||||
$tableColumn['default'] !== null && preg_match(
|
||||
"('([^']+)'::)",
|
||||
$tableColumn['default'],
|
||||
$match,
|
||||
) === 1
|
||||
) {
|
||||
$tableColumn['default'] = $match[1];
|
||||
}
|
||||
|
||||
$options = [
|
||||
'length' => $length,
|
||||
'notnull' => (bool) $tableColumn['isnotnull'],
|
||||
'default' => $tableColumn['default'],
|
||||
'precision' => $precision,
|
||||
'scale' => $scale,
|
||||
'fixed' => $fixed,
|
||||
'autoincrement' => $autoincrement,
|
||||
'comment' => isset($tableColumn['comment']) && $tableColumn['comment'] !== ''
|
||||
? $tableColumn['comment']
|
||||
: null,
|
||||
];
|
||||
|
||||
$column = new Column($tableColumn['field'], Type::getType($type), $options);
|
||||
|
||||
if (! empty($tableColumn['collation'])) {
|
||||
$column->setPlatformOption('collation', $tableColumn['collation']);
|
||||
}
|
||||
|
||||
if ($column->getType()->getName() === Types::JSON) {
|
||||
if (! $column->getType() instanceof JsonType) {
|
||||
Deprecation::trigger(
|
||||
'doctrine/dbal',
|
||||
'https://github.com/doctrine/dbal/pull/5049',
|
||||
<<<'DEPRECATION'
|
||||
%s not extending %s while being named %s is deprecated,
|
||||
and will lead to jsonb never to being used in 4.0.,
|
||||
DEPRECATION,
|
||||
get_class($column->getType()),
|
||||
JsonType::class,
|
||||
Types::JSON,
|
||||
);
|
||||
}
|
||||
|
||||
$column->setPlatformOption('jsonb', $jsonb);
|
||||
}
|
||||
|
||||
return $column;
|
||||
}
|
||||
|
||||
/**
|
||||
* PostgreSQL 9.4 puts parentheses around negative numeric default values that need to be stripped eventually.
|
||||
*
|
||||
* @param mixed $defaultValue
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
private function fixVersion94NegativeNumericDefaultValue($defaultValue)
|
||||
{
|
||||
if ($defaultValue !== null && strpos($defaultValue, '(') === 0) {
|
||||
return trim($defaultValue, '()');
|
||||
}
|
||||
|
||||
return $defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a default value expression as given by PostgreSQL
|
||||
*/
|
||||
private function parseDefaultExpression(?string $default): ?string
|
||||
{
|
||||
if ($default === null) {
|
||||
return $default;
|
||||
}
|
||||
|
||||
return str_replace("''", "'", $default);
|
||||
}
|
||||
|
||||
protected function selectTableNames(string $databaseName): Result
|
||||
{
|
||||
$sql = <<<'SQL'
|
||||
SELECT quote_ident(table_name) AS table_name,
|
||||
table_schema AS schema_name
|
||||
FROM information_schema.tables
|
||||
WHERE table_catalog = ?
|
||||
AND table_schema NOT LIKE 'pg\_%'
|
||||
AND table_schema != 'information_schema'
|
||||
AND table_name != 'geometry_columns'
|
||||
AND table_name != 'spatial_ref_sys'
|
||||
AND table_type = 'BASE TABLE'
|
||||
SQL;
|
||||
|
||||
return $this->_conn->executeQuery($sql, [$databaseName]);
|
||||
}
|
||||
|
||||
protected function selectTableColumns(string $databaseName, ?string $tableName = null): Result
|
||||
{
|
||||
$sql = 'SELECT';
|
||||
|
||||
if ($tableName === null) {
|
||||
$sql .= ' c.relname AS table_name, n.nspname AS schema_name,';
|
||||
}
|
||||
|
||||
$sql .= sprintf(<<<'SQL'
|
||||
a.attnum,
|
||||
quote_ident(a.attname) AS field,
|
||||
t.typname AS type,
|
||||
format_type(a.atttypid, a.atttypmod) AS complete_type,
|
||||
(SELECT tc.collcollate FROM pg_catalog.pg_collation tc WHERE tc.oid = a.attcollation) AS collation,
|
||||
(SELECT t1.typname FROM pg_catalog.pg_type t1 WHERE t1.oid = t.typbasetype) AS domain_type,
|
||||
(SELECT format_type(t2.typbasetype, t2.typtypmod) FROM
|
||||
pg_catalog.pg_type t2 WHERE t2.typtype = 'd' AND t2.oid = a.atttypid) AS domain_complete_type,
|
||||
a.attnotnull AS isnotnull,
|
||||
(SELECT 't'
|
||||
FROM pg_index
|
||||
WHERE c.oid = pg_index.indrelid
|
||||
AND pg_index.indkey[0] = a.attnum
|
||||
AND pg_index.indisprimary = 't'
|
||||
) AS pri,
|
||||
(%s) AS default,
|
||||
(SELECT pg_description.description
|
||||
FROM pg_description WHERE pg_description.objoid = c.oid AND a.attnum = pg_description.objsubid
|
||||
) AS comment
|
||||
FROM pg_attribute a
|
||||
INNER JOIN pg_class c
|
||||
ON c.oid = a.attrelid
|
||||
INNER JOIN pg_type t
|
||||
ON t.oid = a.atttypid
|
||||
INNER JOIN pg_namespace n
|
||||
ON n.oid = c.relnamespace
|
||||
LEFT JOIN pg_depend d
|
||||
ON d.objid = c.oid
|
||||
AND d.deptype = 'e'
|
||||
AND d.classid = (SELECT oid FROM pg_class WHERE relname = 'pg_class')
|
||||
SQL, $this->_platform->getDefaultColumnValueSQLSnippet());
|
||||
|
||||
$conditions = array_merge([
|
||||
'a.attnum > 0',
|
||||
"c.relkind = 'r'",
|
||||
'd.refobjid IS NULL',
|
||||
], $this->buildQueryConditions($tableName));
|
||||
|
||||
$sql .= ' WHERE ' . implode(' AND ', $conditions) . ' ORDER BY a.attnum';
|
||||
|
||||
return $this->_conn->executeQuery($sql);
|
||||
}
|
||||
|
||||
protected function selectIndexColumns(string $databaseName, ?string $tableName = null): Result
|
||||
{
|
||||
$sql = 'SELECT';
|
||||
|
||||
if ($tableName === null) {
|
||||
$sql .= ' tc.relname AS table_name, tn.nspname AS schema_name,';
|
||||
}
|
||||
|
||||
$sql .= <<<'SQL'
|
||||
quote_ident(ic.relname) AS relname,
|
||||
i.indisunique,
|
||||
i.indisprimary,
|
||||
i.indkey,
|
||||
i.indrelid,
|
||||
pg_get_expr(indpred, indrelid) AS "where"
|
||||
FROM pg_index i
|
||||
JOIN pg_class AS tc ON tc.oid = i.indrelid
|
||||
JOIN pg_namespace tn ON tn.oid = tc.relnamespace
|
||||
JOIN pg_class AS ic ON ic.oid = i.indexrelid
|
||||
WHERE ic.oid IN (
|
||||
SELECT indexrelid
|
||||
FROM pg_index i, pg_class c, pg_namespace n
|
||||
SQL;
|
||||
|
||||
$conditions = array_merge([
|
||||
'c.oid = i.indrelid',
|
||||
'c.relnamespace = n.oid',
|
||||
], $this->buildQueryConditions($tableName));
|
||||
|
||||
$sql .= ' WHERE ' . implode(' AND ', $conditions) . ')';
|
||||
|
||||
return $this->_conn->executeQuery($sql);
|
||||
}
|
||||
|
||||
protected function selectForeignKeyColumns(string $databaseName, ?string $tableName = null): Result
|
||||
{
|
||||
$sql = 'SELECT';
|
||||
|
||||
if ($tableName === null) {
|
||||
$sql .= ' tc.relname AS table_name, tn.nspname AS schema_name,';
|
||||
}
|
||||
|
||||
$sql .= <<<'SQL'
|
||||
quote_ident(r.conname) as conname,
|
||||
pg_get_constraintdef(r.oid, true) as condef
|
||||
FROM pg_constraint r
|
||||
JOIN pg_class AS tc ON tc.oid = r.conrelid
|
||||
JOIN pg_namespace tn ON tn.oid = tc.relnamespace
|
||||
WHERE r.conrelid IN
|
||||
(
|
||||
SELECT c.oid
|
||||
FROM pg_class c, pg_namespace n
|
||||
SQL;
|
||||
|
||||
$conditions = array_merge(['n.oid = c.relnamespace'], $this->buildQueryConditions($tableName));
|
||||
|
||||
$sql .= ' WHERE ' . implode(' AND ', $conditions) . ") AND r.contype = 'f'";
|
||||
|
||||
return $this->_conn->executeQuery($sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function fetchTableOptionsByTable(string $databaseName, ?string $tableName = null): array
|
||||
{
|
||||
$sql = <<<'SQL'
|
||||
SELECT c.relname,
|
||||
CASE c.relpersistence WHEN 'u' THEN true ELSE false END as unlogged,
|
||||
obj_description(c.oid, 'pg_class') AS comment
|
||||
FROM pg_class c
|
||||
INNER JOIN pg_namespace n
|
||||
ON n.oid = c.relnamespace
|
||||
SQL;
|
||||
|
||||
$conditions = array_merge(["c.relkind = 'r'"], $this->buildQueryConditions($tableName));
|
||||
|
||||
$sql .= ' WHERE ' . implode(' AND ', $conditions);
|
||||
|
||||
return $this->_conn->fetchAllAssociativeIndexed($sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $tableName
|
||||
*
|
||||
* @return list<string>
|
||||
*/
|
||||
private function buildQueryConditions($tableName): array
|
||||
{
|
||||
$conditions = [];
|
||||
|
||||
if ($tableName !== null) {
|
||||
if (strpos($tableName, '.') !== false) {
|
||||
[$schemaName, $tableName] = explode('.', $tableName);
|
||||
$conditions[] = 'n.nspname = ' . $this->_platform->quoteStringLiteral($schemaName);
|
||||
} else {
|
||||
$conditions[] = 'n.nspname = ANY(current_schemas(false))';
|
||||
}
|
||||
|
||||
$identifier = new Identifier($tableName);
|
||||
$conditions[] = 'c.relname = ' . $this->_platform->quoteStringLiteral($identifier->getName());
|
||||
}
|
||||
|
||||
$conditions[] = "n.nspname NOT IN ('pg_catalog', 'information_schema', 'pg_toast')";
|
||||
|
||||
return $conditions;
|
||||
}
|
||||
}
|
||||
+788
@@ -0,0 +1,788 @@
|
||||
<?php
|
||||
|
||||
namespace Doctrine\DBAL\Schema;
|
||||
|
||||
use Doctrine\DBAL\DriverManager;
|
||||
use Doctrine\DBAL\Exception;
|
||||
use Doctrine\DBAL\Platforms\SQLite;
|
||||
use Doctrine\DBAL\Platforms\SqlitePlatform;
|
||||
use Doctrine\DBAL\Result;
|
||||
use Doctrine\DBAL\Types\StringType;
|
||||
use Doctrine\DBAL\Types\TextType;
|
||||
use Doctrine\DBAL\Types\Type;
|
||||
use Doctrine\Deprecations\Deprecation;
|
||||
|
||||
use function array_change_key_case;
|
||||
use function array_map;
|
||||
use function array_merge;
|
||||
use function count;
|
||||
use function explode;
|
||||
use function file_exists;
|
||||
use function implode;
|
||||
use function preg_match;
|
||||
use function preg_match_all;
|
||||
use function preg_quote;
|
||||
use function preg_replace;
|
||||
use function rtrim;
|
||||
use function str_replace;
|
||||
use function strcasecmp;
|
||||
use function strpos;
|
||||
use function strtolower;
|
||||
use function trim;
|
||||
use function unlink;
|
||||
use function usort;
|
||||
|
||||
use const CASE_LOWER;
|
||||
|
||||
/**
|
||||
* Sqlite SchemaManager.
|
||||
*
|
||||
* @extends AbstractSchemaManager<SqlitePlatform>
|
||||
*/
|
||||
class SqliteSchemaManager extends AbstractSchemaManager
|
||||
{
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function listTableNames()
|
||||
{
|
||||
return $this->doListTableNames();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function listTables()
|
||||
{
|
||||
return $this->doListTables();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @deprecated Use {@see introspectTable()} instead.
|
||||
*/
|
||||
public function listTableDetails($name)
|
||||
{
|
||||
Deprecation::triggerIfCalledFromOutside(
|
||||
'doctrine/dbal',
|
||||
'https://github.com/doctrine/dbal/pull/5595',
|
||||
'%s is deprecated. Use introspectTable() instead.',
|
||||
__METHOD__,
|
||||
);
|
||||
|
||||
return $this->doListTableDetails($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function listTableColumns($table, $database = null)
|
||||
{
|
||||
return $this->doListTableColumns($table, $database);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function listTableIndexes($table)
|
||||
{
|
||||
return $this->doListTableIndexes($table);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function fetchForeignKeyColumnsByTable(string $databaseName): array
|
||||
{
|
||||
$columnsByTable = parent::fetchForeignKeyColumnsByTable($databaseName);
|
||||
|
||||
if (count($columnsByTable) > 0) {
|
||||
foreach ($columnsByTable as $table => $columns) {
|
||||
$columnsByTable[$table] = $this->addDetailsToTableForeignKeyColumns($table, $columns);
|
||||
}
|
||||
}
|
||||
|
||||
return $columnsByTable;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @deprecated Delete the database file using the filesystem.
|
||||
*/
|
||||
public function dropDatabase($database)
|
||||
{
|
||||
Deprecation::trigger(
|
||||
'doctrine/dbal',
|
||||
'https://github.com/doctrine/dbal/issues/4963',
|
||||
'SqliteSchemaManager::dropDatabase() is deprecated. Delete the database file using the filesystem.',
|
||||
);
|
||||
|
||||
if (! file_exists($database)) {
|
||||
return;
|
||||
}
|
||||
|
||||
unlink($database);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @deprecated The engine will create the database file automatically.
|
||||
*/
|
||||
public function createDatabase($database)
|
||||
{
|
||||
Deprecation::trigger(
|
||||
'doctrine/dbal',
|
||||
'https://github.com/doctrine/dbal/issues/4963',
|
||||
'SqliteSchemaManager::createDatabase() is deprecated.'
|
||||
. ' The engine will create the database file automatically.',
|
||||
);
|
||||
|
||||
$params = $this->_conn->getParams();
|
||||
|
||||
$params['path'] = $database;
|
||||
unset($params['memory']);
|
||||
|
||||
$conn = DriverManager::getConnection($params);
|
||||
$conn->connect();
|
||||
$conn->close();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function createForeignKey(ForeignKeyConstraint $foreignKey, $table)
|
||||
{
|
||||
if (! $table instanceof Table) {
|
||||
$table = $this->listTableDetails($table);
|
||||
}
|
||||
|
||||
$this->alterTable(new TableDiff($table->getName(), [], [], [], [], [], [], $table, [$foreignKey]));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @deprecated Use {@see dropForeignKey()} and {@see createForeignKey()} instead.
|
||||
*/
|
||||
public function dropAndCreateForeignKey(ForeignKeyConstraint $foreignKey, $table)
|
||||
{
|
||||
Deprecation::trigger(
|
||||
'doctrine/dbal',
|
||||
'https://github.com/doctrine/dbal/pull/4897',
|
||||
'SqliteSchemaManager::dropAndCreateForeignKey() is deprecated.'
|
||||
. ' Use SqliteSchemaManager::dropForeignKey() and SqliteSchemaManager::createForeignKey() instead.',
|
||||
);
|
||||
|
||||
if (! $table instanceof Table) {
|
||||
$table = $this->listTableDetails($table);
|
||||
}
|
||||
|
||||
$this->alterTable(new TableDiff($table->getName(), [], [], [], [], [], [], $table, [], [$foreignKey]));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function dropForeignKey($foreignKey, $table)
|
||||
{
|
||||
if (! $table instanceof Table) {
|
||||
$table = $this->listTableDetails($table);
|
||||
}
|
||||
|
||||
$this->alterTable(new TableDiff($table->getName(), [], [], [], [], [], [], $table, [], [], [$foreignKey]));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function listTableForeignKeys($table, $database = null)
|
||||
{
|
||||
$table = $this->normalizeName($table);
|
||||
|
||||
$columns = $this->selectForeignKeyColumns($database ?? 'main', $table)
|
||||
->fetchAllAssociative();
|
||||
|
||||
if (count($columns) > 0) {
|
||||
$columns = $this->addDetailsToTableForeignKeyColumns($table, $columns);
|
||||
}
|
||||
|
||||
return $this->_getPortableTableForeignKeysList($columns);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function _getPortableTableDefinition($table)
|
||||
{
|
||||
return $table['table_name'];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function _getPortableTableIndexesList($tableIndexes, $tableName = null)
|
||||
{
|
||||
$indexBuffer = [];
|
||||
|
||||
// fetch primary
|
||||
$indexArray = $this->_conn->fetchAllAssociative('SELECT * FROM PRAGMA_TABLE_INFO (?)', [$tableName]);
|
||||
|
||||
usort(
|
||||
$indexArray,
|
||||
/**
|
||||
* @param array<string,mixed> $a
|
||||
* @param array<string,mixed> $b
|
||||
*/
|
||||
static function (array $a, array $b): int {
|
||||
if ($a['pk'] === $b['pk']) {
|
||||
return $a['cid'] - $b['cid'];
|
||||
}
|
||||
|
||||
return $a['pk'] - $b['pk'];
|
||||
},
|
||||
);
|
||||
|
||||
foreach ($indexArray as $indexColumnRow) {
|
||||
if ($indexColumnRow['pk'] === 0 || $indexColumnRow['pk'] === '0') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$indexBuffer[] = [
|
||||
'key_name' => 'primary',
|
||||
'primary' => true,
|
||||
'non_unique' => false,
|
||||
'column_name' => $indexColumnRow['name'],
|
||||
];
|
||||
}
|
||||
|
||||
// fetch regular indexes
|
||||
foreach ($tableIndexes as $tableIndex) {
|
||||
// Ignore indexes with reserved names, e.g. autoindexes
|
||||
if (strpos($tableIndex['name'], 'sqlite_') === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$keyName = $tableIndex['name'];
|
||||
$idx = [];
|
||||
$idx['key_name'] = $keyName;
|
||||
$idx['primary'] = false;
|
||||
$idx['non_unique'] = ! $tableIndex['unique'];
|
||||
|
||||
$indexArray = $this->_conn->fetchAllAssociative('SELECT * FROM PRAGMA_INDEX_INFO (?)', [$keyName]);
|
||||
|
||||
foreach ($indexArray as $indexColumnRow) {
|
||||
$idx['column_name'] = $indexColumnRow['name'];
|
||||
$indexBuffer[] = $idx;
|
||||
}
|
||||
}
|
||||
|
||||
return parent::_getPortableTableIndexesList($indexBuffer, $tableName);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function _getPortableTableColumnList($table, $database, $tableColumns)
|
||||
{
|
||||
$list = parent::_getPortableTableColumnList($table, $database, $tableColumns);
|
||||
|
||||
// find column with autoincrement
|
||||
$autoincrementColumn = null;
|
||||
$autoincrementCount = 0;
|
||||
|
||||
foreach ($tableColumns as $tableColumn) {
|
||||
if ($tableColumn['pk'] === 0 || $tableColumn['pk'] === '0') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$autoincrementCount++;
|
||||
if ($autoincrementColumn !== null || strtolower($tableColumn['type']) !== 'integer') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$autoincrementColumn = $tableColumn['name'];
|
||||
}
|
||||
|
||||
if ($autoincrementCount === 1 && $autoincrementColumn !== null) {
|
||||
foreach ($list as $column) {
|
||||
if ($autoincrementColumn !== $column->getName()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$column->setAutoincrement(true);
|
||||
}
|
||||
}
|
||||
|
||||
// inspect column collation and comments
|
||||
$createSql = $this->getCreateTableSQL($table);
|
||||
|
||||
foreach ($list as $columnName => $column) {
|
||||
$type = $column->getType();
|
||||
|
||||
if ($type instanceof StringType || $type instanceof TextType) {
|
||||
$column->setPlatformOption(
|
||||
'collation',
|
||||
$this->parseColumnCollationFromSQL($columnName, $createSql) ?? 'BINARY',
|
||||
);
|
||||
}
|
||||
|
||||
$comment = $this->parseColumnCommentFromSQL($columnName, $createSql);
|
||||
|
||||
if ($comment === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$type = $this->extractDoctrineTypeFromComment($comment, '');
|
||||
|
||||
if ($type !== '') {
|
||||
$column->setType(Type::getType($type));
|
||||
|
||||
$comment = $this->removeDoctrineTypeFromComment($comment, $type);
|
||||
}
|
||||
|
||||
$column->setComment($comment);
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function _getPortableTableColumnDefinition($tableColumn)
|
||||
{
|
||||
$parts = explode('(', $tableColumn['type']);
|
||||
$tableColumn['type'] = trim($parts[0]);
|
||||
if (isset($parts[1])) {
|
||||
$length = trim($parts[1], ')');
|
||||
$tableColumn['length'] = $length;
|
||||
}
|
||||
|
||||
$dbType = strtolower($tableColumn['type']);
|
||||
$length = $tableColumn['length'] ?? null;
|
||||
$unsigned = false;
|
||||
|
||||
if (strpos($dbType, ' unsigned') !== false) {
|
||||
$dbType = str_replace(' unsigned', '', $dbType);
|
||||
$unsigned = true;
|
||||
}
|
||||
|
||||
$fixed = false;
|
||||
$type = $this->_platform->getDoctrineTypeMapping($dbType);
|
||||
$default = $tableColumn['dflt_value'];
|
||||
if ($default === 'NULL') {
|
||||
$default = null;
|
||||
}
|
||||
|
||||
if ($default !== null) {
|
||||
// SQLite returns the default value as a literal expression, so we need to parse it
|
||||
if (preg_match('/^\'(.*)\'$/s', $default, $matches) === 1) {
|
||||
$default = str_replace("''", "'", $matches[1]);
|
||||
}
|
||||
}
|
||||
|
||||
$notnull = (bool) $tableColumn['notnull'];
|
||||
|
||||
if (! isset($tableColumn['name'])) {
|
||||
$tableColumn['name'] = '';
|
||||
}
|
||||
|
||||
$precision = null;
|
||||
$scale = null;
|
||||
|
||||
switch ($dbType) {
|
||||
case 'char':
|
||||
$fixed = true;
|
||||
break;
|
||||
case 'float':
|
||||
case 'double':
|
||||
case 'real':
|
||||
case 'decimal':
|
||||
case 'numeric':
|
||||
if (isset($tableColumn['length'])) {
|
||||
if (strpos($tableColumn['length'], ',') === false) {
|
||||
$tableColumn['length'] .= ',0';
|
||||
}
|
||||
|
||||
[$precision, $scale] = array_map('trim', explode(',', $tableColumn['length']));
|
||||
}
|
||||
|
||||
$length = null;
|
||||
break;
|
||||
}
|
||||
|
||||
$options = [
|
||||
'length' => $length,
|
||||
'unsigned' => $unsigned,
|
||||
'fixed' => $fixed,
|
||||
'notnull' => $notnull,
|
||||
'default' => $default,
|
||||
'precision' => $precision,
|
||||
'scale' => $scale,
|
||||
];
|
||||
|
||||
return new Column($tableColumn['name'], Type::getType($type), $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function _getPortableViewDefinition($view)
|
||||
{
|
||||
return new View($view['name'], $view['sql']);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function _getPortableTableForeignKeysList($tableForeignKeys)
|
||||
{
|
||||
$list = [];
|
||||
foreach ($tableForeignKeys as $value) {
|
||||
$value = array_change_key_case($value, CASE_LOWER);
|
||||
$id = $value['id'];
|
||||
if (! isset($list[$id])) {
|
||||
if (! isset($value['on_delete']) || $value['on_delete'] === 'RESTRICT') {
|
||||
$value['on_delete'] = null;
|
||||
}
|
||||
|
||||
if (! isset($value['on_update']) || $value['on_update'] === 'RESTRICT') {
|
||||
$value['on_update'] = null;
|
||||
}
|
||||
|
||||
$list[$id] = [
|
||||
'name' => $value['constraint_name'],
|
||||
'local' => [],
|
||||
'foreign' => [],
|
||||
'foreignTable' => $value['table'],
|
||||
'onDelete' => $value['on_delete'],
|
||||
'onUpdate' => $value['on_update'],
|
||||
'deferrable' => $value['deferrable'],
|
||||
'deferred' => $value['deferred'],
|
||||
];
|
||||
}
|
||||
|
||||
$list[$id]['local'][] = $value['from'];
|
||||
|
||||
if ($value['to'] === null) {
|
||||
// Inferring a shorthand form for the foreign key constraint, where the "to" field is empty.
|
||||
// @see https://www.sqlite.org/foreignkeys.html#fk_indexes.
|
||||
$foreignTableIndexes = $this->_getPortableTableIndexesList([], $value['table']);
|
||||
|
||||
if (! isset($foreignTableIndexes['primary'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$list[$id]['foreign'] = [...$list[$id]['foreign'], ...$foreignTableIndexes['primary']->getColumns()];
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$list[$id]['foreign'][] = $value['to'];
|
||||
}
|
||||
|
||||
return parent::_getPortableTableForeignKeysList($list);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function _getPortableTableForeignKeyDefinition($tableForeignKey): ForeignKeyConstraint
|
||||
{
|
||||
return new ForeignKeyConstraint(
|
||||
$tableForeignKey['local'],
|
||||
$tableForeignKey['foreignTable'],
|
||||
$tableForeignKey['foreign'],
|
||||
$tableForeignKey['name'],
|
||||
[
|
||||
'onDelete' => $tableForeignKey['onDelete'],
|
||||
'onUpdate' => $tableForeignKey['onUpdate'],
|
||||
'deferrable' => $tableForeignKey['deferrable'],
|
||||
'deferred' => $tableForeignKey['deferred'],
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
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';
|
||||
|
||||
if (preg_match($pattern, $sql, $match) !== 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $match[1];
|
||||
}
|
||||
|
||||
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)
|
||||
( # Start capture
|
||||
(?:\s*--[^\n]*\n?)+ # Capture anything that starts with whitespaces followed by -- until the end of the line(s)
|
||||
)/ix';
|
||||
|
||||
if (preg_match($pattern, $sql, $match) !== 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$comment = preg_replace('{^\s*--}m', '', rtrim($match[1], "\n"));
|
||||
|
||||
return $comment === '' ? null : $comment;
|
||||
}
|
||||
|
||||
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';
|
||||
|
||||
if (preg_match($pattern, $sql, $match) !== 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$comment = preg_replace('{^\s*--}m', '', rtrim($match[1], "\n"));
|
||||
|
||||
return $comment === '' ? null : $comment;
|
||||
}
|
||||
|
||||
/** @throws Exception */
|
||||
private function getCreateTableSQL(string $table): string
|
||||
{
|
||||
$sql = $this->_conn->fetchOne(
|
||||
<<<'SQL'
|
||||
SELECT sql
|
||||
FROM (
|
||||
SELECT *
|
||||
FROM sqlite_master
|
||||
UNION ALL
|
||||
SELECT *
|
||||
FROM sqlite_temp_master
|
||||
)
|
||||
WHERE type = 'table'
|
||||
AND name = ?
|
||||
SQL
|
||||
,
|
||||
[$table],
|
||||
);
|
||||
|
||||
if ($sql !== false) {
|
||||
return $sql;
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<array<string,mixed>> $columns
|
||||
*
|
||||
* @return list<array<string,mixed>>
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
private function addDetailsToTableForeignKeyColumns(string $table, array $columns): array
|
||||
{
|
||||
$foreignKeyDetails = $this->getForeignKeyDetails($table);
|
||||
$foreignKeyCount = count($foreignKeyDetails);
|
||||
|
||||
foreach ($columns as $i => $column) {
|
||||
// SQLite identifies foreign keys in reverse order of appearance in SQL
|
||||
$columns[$i] = array_merge($column, $foreignKeyDetails[$foreignKeyCount - $column['id'] - 1]);
|
||||
}
|
||||
|
||||
return $columns;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $table
|
||||
*
|
||||
* @return list<array<string, mixed>>
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
private function getForeignKeyDetails($table)
|
||||
{
|
||||
$createSql = $this->getCreateTableSQL($table);
|
||||
|
||||
if (
|
||||
preg_match_all(
|
||||
'#
|
||||
(?:CONSTRAINT\s+(\S+)\s+)?
|
||||
(?:FOREIGN\s+KEY[^)]+\)\s*)?
|
||||
REFERENCES\s+\S+\s*(?:\([^)]+\))?
|
||||
(?:
|
||||
[^,]*?
|
||||
(NOT\s+DEFERRABLE|DEFERRABLE)
|
||||
(?:\s+INITIALLY\s+(DEFERRED|IMMEDIATE))?
|
||||
)?#isx',
|
||||
$createSql,
|
||||
$match,
|
||||
) === 0
|
||||
) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$names = $match[1];
|
||||
$deferrable = $match[2];
|
||||
$deferred = $match[3];
|
||||
$details = [];
|
||||
|
||||
for ($i = 0, $count = count($match[0]); $i < $count; $i++) {
|
||||
$details[] = [
|
||||
'constraint_name' => isset($names[$i]) && $names[$i] !== '' ? $names[$i] : null,
|
||||
'deferrable' => isset($deferrable[$i]) && strcasecmp($deferrable[$i], 'deferrable') === 0,
|
||||
'deferred' => isset($deferred[$i]) && strcasecmp($deferred[$i], 'deferred') === 0,
|
||||
];
|
||||
}
|
||||
|
||||
return $details;
|
||||
}
|
||||
|
||||
public function createComparator(): Comparator
|
||||
{
|
||||
return new SQLite\Comparator($this->_platform);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
public function getSchemaSearchPaths()
|
||||
{
|
||||
Deprecation::triggerIfCalledFromOutside(
|
||||
'doctrine/dbal',
|
||||
'https://github.com/doctrine/dbal/pull/4821',
|
||||
'SqliteSchemaManager::getSchemaSearchPaths() is deprecated.',
|
||||
);
|
||||
|
||||
// SQLite does not support schemas or databases
|
||||
return [];
|
||||
}
|
||||
|
||||
protected function selectTableNames(string $databaseName): Result
|
||||
{
|
||||
$sql = <<<'SQL'
|
||||
SELECT name AS table_name
|
||||
FROM sqlite_master
|
||||
WHERE type = 'table'
|
||||
AND name != 'sqlite_sequence'
|
||||
AND name != 'geometry_columns'
|
||||
AND name != 'spatial_ref_sys'
|
||||
UNION ALL
|
||||
SELECT name
|
||||
FROM sqlite_temp_master
|
||||
WHERE type = 'table'
|
||||
ORDER BY name
|
||||
SQL;
|
||||
|
||||
return $this->_conn->executeQuery($sql);
|
||||
}
|
||||
|
||||
protected function selectTableColumns(string $databaseName, ?string $tableName = null): Result
|
||||
{
|
||||
$sql = <<<'SQL'
|
||||
SELECT t.name AS table_name,
|
||||
c.*
|
||||
FROM sqlite_master t
|
||||
JOIN pragma_table_info(t.name) c
|
||||
SQL;
|
||||
|
||||
$conditions = [
|
||||
"t.type = 'table'",
|
||||
"t.name NOT IN ('geometry_columns', 'spatial_ref_sys', 'sqlite_sequence')",
|
||||
];
|
||||
$params = [];
|
||||
|
||||
if ($tableName !== null) {
|
||||
$conditions[] = 't.name = ?';
|
||||
$params[] = str_replace('.', '__', $tableName);
|
||||
}
|
||||
|
||||
$sql .= ' WHERE ' . implode(' AND ', $conditions) . ' ORDER BY t.name, c.cid';
|
||||
|
||||
return $this->_conn->executeQuery($sql, $params);
|
||||
}
|
||||
|
||||
protected function selectIndexColumns(string $databaseName, ?string $tableName = null): Result
|
||||
{
|
||||
$sql = <<<'SQL'
|
||||
SELECT t.name AS table_name,
|
||||
i.*
|
||||
FROM sqlite_master t
|
||||
JOIN pragma_index_list(t.name) i
|
||||
SQL;
|
||||
|
||||
$conditions = [
|
||||
"t.type = 'table'",
|
||||
"t.name NOT IN ('geometry_columns', 'spatial_ref_sys', 'sqlite_sequence')",
|
||||
];
|
||||
$params = [];
|
||||
|
||||
if ($tableName !== null) {
|
||||
$conditions[] = 't.name = ?';
|
||||
$params[] = str_replace('.', '__', $tableName);
|
||||
}
|
||||
|
||||
$sql .= ' WHERE ' . implode(' AND ', $conditions) . ' ORDER BY t.name, i.seq';
|
||||
|
||||
return $this->_conn->executeQuery($sql, $params);
|
||||
}
|
||||
|
||||
protected function selectForeignKeyColumns(string $databaseName, ?string $tableName = null): Result
|
||||
{
|
||||
$sql = <<<'SQL'
|
||||
SELECT t.name AS table_name,
|
||||
p.*
|
||||
FROM sqlite_master t
|
||||
JOIN pragma_foreign_key_list(t.name) p
|
||||
ON p."seq" != '-1'
|
||||
SQL;
|
||||
|
||||
$conditions = [
|
||||
"t.type = 'table'",
|
||||
"t.name NOT IN ('geometry_columns', 'spatial_ref_sys', 'sqlite_sequence')",
|
||||
];
|
||||
$params = [];
|
||||
|
||||
if ($tableName !== null) {
|
||||
$conditions[] = 't.name = ?';
|
||||
$params[] = str_replace('.', '__', $tableName);
|
||||
}
|
||||
|
||||
$sql .= ' WHERE ' . implode(' AND ', $conditions) . ' ORDER BY t.name, p.id DESC, p.seq';
|
||||
|
||||
return $this->_conn->executeQuery($sql, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function fetchTableOptionsByTable(string $databaseName, ?string $tableName = null): array
|
||||
{
|
||||
if ($tableName === null) {
|
||||
$tables = $this->listTableNames();
|
||||
} else {
|
||||
$tables = [$tableName];
|
||||
}
|
||||
|
||||
$tableOptions = [];
|
||||
foreach ($tables as $table) {
|
||||
$comment = $this->parseTableCommentFromSQL($table, $this->getCreateTableSQL($table));
|
||||
|
||||
if ($comment === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$tableOptions[$table]['comment'] = $comment;
|
||||
}
|
||||
|
||||
return $tableOptions;
|
||||
}
|
||||
}
|
||||
Vendored
+219
@@ -0,0 +1,219 @@
|
||||
<?php
|
||||
|
||||
namespace Doctrine\DBAL\Tools\Console\Command;
|
||||
|
||||
use Doctrine\DBAL\Connection;
|
||||
use Doctrine\DBAL\Exception;
|
||||
use Doctrine\DBAL\Platforms\Keywords\DB2Keywords;
|
||||
use Doctrine\DBAL\Platforms\Keywords\KeywordList;
|
||||
use Doctrine\DBAL\Platforms\Keywords\MariaDb102Keywords;
|
||||
use Doctrine\DBAL\Platforms\Keywords\MySQL57Keywords;
|
||||
use Doctrine\DBAL\Platforms\Keywords\MySQL80Keywords;
|
||||
use Doctrine\DBAL\Platforms\Keywords\MySQL84Keywords;
|
||||
use Doctrine\DBAL\Platforms\Keywords\MySQLKeywords;
|
||||
use Doctrine\DBAL\Platforms\Keywords\OracleKeywords;
|
||||
use Doctrine\DBAL\Platforms\Keywords\PostgreSQL100Keywords;
|
||||
use Doctrine\DBAL\Platforms\Keywords\PostgreSQL94Keywords;
|
||||
use Doctrine\DBAL\Platforms\Keywords\ReservedKeywordsValidator;
|
||||
use Doctrine\DBAL\Platforms\Keywords\SQLiteKeywords;
|
||||
use Doctrine\DBAL\Platforms\Keywords\SQLServer2012Keywords;
|
||||
use Doctrine\DBAL\Tools\Console\ConnectionProvider;
|
||||
use Doctrine\Deprecations\Deprecation;
|
||||
use InvalidArgumentException;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
use function array_keys;
|
||||
use function assert;
|
||||
use function count;
|
||||
use function implode;
|
||||
use function is_array;
|
||||
use function is_string;
|
||||
|
||||
/** @deprecated Use database documentation instead. */
|
||||
class ReservedWordsCommand extends Command
|
||||
{
|
||||
use CommandCompatibility;
|
||||
|
||||
/** @var array<string,KeywordList> */
|
||||
private array $keywordLists;
|
||||
|
||||
private ConnectionProvider $connectionProvider;
|
||||
|
||||
public function __construct(ConnectionProvider $connectionProvider)
|
||||
{
|
||||
Deprecation::triggerIfCalledFromOutside(
|
||||
'doctrine/dbal',
|
||||
'https://github.com/doctrine/dbal/pull/5431',
|
||||
'ReservedWordsCommand is deprecated. Use database documentation instead.',
|
||||
);
|
||||
|
||||
parent::__construct();
|
||||
|
||||
$this->connectionProvider = $connectionProvider;
|
||||
|
||||
$this->keywordLists = [
|
||||
'db2' => new DB2Keywords(),
|
||||
'mariadb102' => new MariaDb102Keywords(),
|
||||
'mysql' => new MySQLKeywords(),
|
||||
'mysql57' => new MySQL57Keywords(),
|
||||
'mysql80' => new MySQL80Keywords(),
|
||||
'mysql84' => new MySQL84Keywords(),
|
||||
'oracle' => new OracleKeywords(),
|
||||
'pgsql' => new PostgreSQL94Keywords(),
|
||||
'pgsql100' => new PostgreSQL100Keywords(),
|
||||
'sqlite' => new SQLiteKeywords(),
|
||||
'sqlserver' => new SQLServer2012Keywords(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Add or replace a keyword list.
|
||||
*/
|
||||
public function setKeywordList(string $name, KeywordList $keywordList): void
|
||||
{
|
||||
$this->keywordLists[$name] = $keywordList;
|
||||
}
|
||||
|
||||
/**
|
||||
* If you want to add or replace a keywords list use this command.
|
||||
*
|
||||
* @param string $name
|
||||
* @param class-string<KeywordList> $class
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setKeywordListClass($name, $class)
|
||||
{
|
||||
Deprecation::trigger(
|
||||
'doctrine/dbal',
|
||||
'https://github.com/doctrine/dbal/issues/4510',
|
||||
'ReservedWordsCommand::setKeywordListClass() is deprecated,'
|
||||
. ' use ReservedWordsCommand::setKeywordList() instead.',
|
||||
);
|
||||
|
||||
$this->keywordLists[$name] = new $class();
|
||||
}
|
||||
|
||||
/** @return void */
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->setName('dbal:reserved-words')
|
||||
->setDescription('Checks if the current database contains identifiers that are reserved.')
|
||||
->setDefinition([
|
||||
new InputOption('connection', null, InputOption::VALUE_REQUIRED, 'The named database connection'),
|
||||
new InputOption(
|
||||
'list',
|
||||
'l',
|
||||
InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY,
|
||||
'Keyword-List name.',
|
||||
),
|
||||
])
|
||||
->setHelp(<<<'EOT'
|
||||
Checks if the current database contains tables and columns
|
||||
with names that are identifiers in this dialect or in other SQL dialects.
|
||||
|
||||
By default all supported platform keywords are checked:
|
||||
|
||||
<info>%command.full_name%</info>
|
||||
|
||||
If you want to check against specific dialects you can
|
||||
pass them to the command:
|
||||
|
||||
<info>%command.full_name% -l mysql -l pgsql</info>
|
||||
|
||||
The following keyword lists are currently shipped with Doctrine:
|
||||
|
||||
* db2
|
||||
* mariadb102
|
||||
* mysql
|
||||
* mysql57
|
||||
* mysql80
|
||||
* mysql84
|
||||
* oracle
|
||||
* pgsql
|
||||
* pgsql100
|
||||
* sqlite
|
||||
* sqlserver
|
||||
EOT);
|
||||
}
|
||||
|
||||
/** @throws Exception */
|
||||
private function doExecute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$output->writeln(
|
||||
'<comment>The <info>dbal:reserved-words</info> command is deprecated.</comment>'
|
||||
. ' Use the documentation on the used database platform(s) instead.',
|
||||
);
|
||||
$output->writeln('');
|
||||
|
||||
$conn = $this->getConnection($input);
|
||||
|
||||
$keywordLists = $input->getOption('list');
|
||||
|
||||
if (is_string($keywordLists)) {
|
||||
$keywordLists = [$keywordLists];
|
||||
} elseif (! is_array($keywordLists)) {
|
||||
$keywordLists = [];
|
||||
}
|
||||
|
||||
if (count($keywordLists) === 0) {
|
||||
$keywordLists = array_keys($this->keywordLists);
|
||||
}
|
||||
|
||||
$keywords = [];
|
||||
foreach ($keywordLists as $keywordList) {
|
||||
if (! isset($this->keywordLists[$keywordList])) {
|
||||
throw new InvalidArgumentException(
|
||||
"There exists no keyword list with name '" . $keywordList . "'. " .
|
||||
'Known lists: ' . implode(', ', array_keys($this->keywordLists)),
|
||||
);
|
||||
}
|
||||
|
||||
$keywords[] = $this->keywordLists[$keywordList];
|
||||
}
|
||||
|
||||
$output->write(
|
||||
'Checking keyword violations for <comment>' . implode(', ', $keywordLists) . '</comment>...',
|
||||
true,
|
||||
);
|
||||
|
||||
$schema = $conn->getSchemaManager()->introspectSchema();
|
||||
$visitor = new ReservedKeywordsValidator($keywords);
|
||||
$schema->visit($visitor);
|
||||
|
||||
$violations = $visitor->getViolations();
|
||||
if (count($violations) !== 0) {
|
||||
$output->write(
|
||||
'There are <error>' . count($violations) . '</error> reserved keyword violations'
|
||||
. ' in your database schema:',
|
||||
true,
|
||||
);
|
||||
|
||||
foreach ($violations as $violation) {
|
||||
$output->write(' - ' . $violation, true);
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
$output->write('No reserved keywords violations have been found!', true);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private function getConnection(InputInterface $input): Connection
|
||||
{
|
||||
$connectionName = $input->getOption('connection');
|
||||
assert(is_string($connectionName) || $connectionName === null);
|
||||
|
||||
if ($connectionName !== null) {
|
||||
return $this->connectionProvider->getConnection($connectionName);
|
||||
}
|
||||
|
||||
return $this->connectionProvider->getDefaultConnection();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
<?php
|
||||
|
||||
namespace Doctrine\DBAL\Types;
|
||||
|
||||
use DateTime;
|
||||
use DateTimeImmutable;
|
||||
use DateTimeInterface;
|
||||
use Doctrine\DBAL\Platforms\AbstractPlatform;
|
||||
use Doctrine\Deprecations\Deprecation;
|
||||
|
||||
use function get_class;
|
||||
|
||||
/**
|
||||
* DateTime type accepting additional information about timezone offsets.
|
||||
*
|
||||
* Caution: Databases are not necessarily experts at storing timezone related
|
||||
* data of dates. First, of not all the supported vendors support storing Timezone data, and some of
|
||||
* them only use the offset to calculate the timestamp in its default timezone (usually UTC) and persist
|
||||
* the value without the offset information. They even don't save the actual timezone names attached
|
||||
* to a DateTime instance (for example "Europe/Berlin" or "America/Montreal") but the current offset
|
||||
* of them related to UTC. That means, depending on daylight saving times or not, you may get different
|
||||
* offsets.
|
||||
*
|
||||
* This datatype makes only sense to use, if your application only needs to accept the timezone offset,
|
||||
* not the actual timezone that uses transitions. Otherwise your DateTime instance
|
||||
* attached with a timezone such as "Europe/Berlin" gets saved into the database with
|
||||
* the offset and re-created from persistence with only the offset, not the original timezone
|
||||
* attached.
|
||||
*/
|
||||
class DateTimeTzType extends Type implements PhpDateTimeMappingType
|
||||
{
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return Types::DATETIMETZ_MUTABLE;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function getSQLDeclaration(array $column, AbstractPlatform $platform)
|
||||
{
|
||||
return $platform->getDateTimeTzTypeDeclarationSQL($column);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @param T $value
|
||||
*
|
||||
* @return (T is null ? null : string)
|
||||
*
|
||||
* @template T
|
||||
*/
|
||||
public function convertToDatabaseValue($value, AbstractPlatform $platform)
|
||||
{
|
||||
if ($value === null) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
if ($value instanceof DateTimeImmutable) {
|
||||
Deprecation::triggerIfCalledFromOutside(
|
||||
'doctrine/dbal',
|
||||
'https://github.com/doctrine/dbal/pull/6017',
|
||||
'Passing an instance of %s is deprecated, use %s::%s() instead.',
|
||||
get_class($value),
|
||||
DateTimeTzImmutableType::class,
|
||||
__FUNCTION__,
|
||||
);
|
||||
}
|
||||
|
||||
if ($value instanceof DateTimeInterface) {
|
||||
return $value->format($platform->getDateTimeTzFormatString());
|
||||
}
|
||||
|
||||
throw ConversionException::conversionFailedInvalidType(
|
||||
$value,
|
||||
$this->getName(),
|
||||
['null', DateTime::class],
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @param T $value
|
||||
*
|
||||
* @return (T is null ? null : DateTimeInterface)
|
||||
*
|
||||
* @template T
|
||||
*/
|
||||
public function convertToPHPValue($value, AbstractPlatform $platform)
|
||||
{
|
||||
if ($value instanceof DateTimeImmutable) {
|
||||
Deprecation::triggerIfCalledFromOutside(
|
||||
'doctrine/dbal',
|
||||
'https://github.com/doctrine/dbal/pull/6017',
|
||||
'Passing an instance of %s is deprecated, use %s::%s() instead.',
|
||||
get_class($value),
|
||||
DateTimeTzImmutableType::class,
|
||||
__FUNCTION__,
|
||||
);
|
||||
}
|
||||
|
||||
if ($value === null || $value instanceof DateTimeInterface) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
$dateTime = DateTime::createFromFormat($platform->getDateTimeTzFormatString(), $value);
|
||||
if ($dateTime !== false) {
|
||||
return $dateTime;
|
||||
}
|
||||
|
||||
throw ConversionException::conversionFailedFormat(
|
||||
$value,
|
||||
$this->getName(),
|
||||
$platform->getDateTimeTzFormatString(),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,218 @@
|
||||
# Doctrine Deprecations
|
||||
|
||||
A small (side-effect free by default) layer on top of
|
||||
`trigger_error(E_USER_DEPRECATED)` or PSR-3 logging.
|
||||
|
||||
- no side-effects by default, making it a perfect fit for libraries that don't know how the error handler works they operate under
|
||||
- options to avoid having to rely on error handlers global state by using PSR-3 logging
|
||||
- deduplicate deprecation messages to avoid excessive triggering and reduce overhead
|
||||
|
||||
We recommend to collect Deprecations using a PSR logger instead of relying on
|
||||
the global error handler.
|
||||
|
||||
## Usage from consumer perspective:
|
||||
|
||||
Enable Doctrine deprecations to be sent to a PSR3 logger:
|
||||
|
||||
```php
|
||||
\Doctrine\Deprecations\Deprecation::enableWithPsrLogger($logger);
|
||||
```
|
||||
|
||||
Enable Doctrine deprecations to be sent as `@trigger_error($message, E_USER_DEPRECATED)`
|
||||
messages by setting the `DOCTRINE_DEPRECATIONS` environment variable to `trigger`.
|
||||
Alternatively, call:
|
||||
|
||||
```php
|
||||
\Doctrine\Deprecations\Deprecation::enableWithTriggerError();
|
||||
```
|
||||
|
||||
If you only want to enable deprecation tracking, without logging or calling `trigger_error`
|
||||
then set the `DOCTRINE_DEPRECATIONS` environment variable to `track`.
|
||||
Alternatively, call:
|
||||
|
||||
```php
|
||||
\Doctrine\Deprecations\Deprecation::enableTrackingDeprecations();
|
||||
```
|
||||
|
||||
Tracking is enabled with all three modes and provides access to all triggered
|
||||
deprecations and their individual count:
|
||||
|
||||
```php
|
||||
$deprecations = \Doctrine\Deprecations\Deprecation::getTriggeredDeprecations();
|
||||
|
||||
foreach ($deprecations as $identifier => $count) {
|
||||
echo $identifier . " was triggered " . $count . " times\n";
|
||||
}
|
||||
```
|
||||
|
||||
### Suppressing Specific Deprecations
|
||||
|
||||
Disable triggering about specific deprecations:
|
||||
|
||||
```php
|
||||
\Doctrine\Deprecations\Deprecation::ignoreDeprecations("https://link/to/deprecations-description-identifier");
|
||||
```
|
||||
|
||||
Disable all deprecations from a package
|
||||
|
||||
```php
|
||||
\Doctrine\Deprecations\Deprecation::ignorePackage("doctrine/orm");
|
||||
```
|
||||
|
||||
### Other Operations
|
||||
|
||||
When used within PHPUnit or other tools that could collect multiple instances of the same deprecations
|
||||
the deduplication can be disabled:
|
||||
|
||||
```php
|
||||
\Doctrine\Deprecations\Deprecation::withoutDeduplication();
|
||||
```
|
||||
|
||||
Disable deprecation tracking again:
|
||||
|
||||
```php
|
||||
\Doctrine\Deprecations\Deprecation::disable();
|
||||
```
|
||||
|
||||
## Usage from a library/producer perspective:
|
||||
|
||||
When you want to unconditionally trigger a deprecation even when called
|
||||
from the library itself then the `trigger` method is the way to go:
|
||||
|
||||
```php
|
||||
\Doctrine\Deprecations\Deprecation::trigger(
|
||||
"doctrine/orm",
|
||||
"https://link/to/deprecations-description",
|
||||
"message"
|
||||
);
|
||||
```
|
||||
|
||||
If variable arguments are provided at the end, they are used with `sprintf` on
|
||||
the message.
|
||||
|
||||
```php
|
||||
\Doctrine\Deprecations\Deprecation::trigger(
|
||||
"doctrine/orm",
|
||||
"https://github.com/doctrine/orm/issue/1234",
|
||||
"message %s %d",
|
||||
"foo",
|
||||
1234
|
||||
);
|
||||
```
|
||||
|
||||
When you want to trigger a deprecation only when it is called by a function
|
||||
outside of the current package, but not trigger when the package itself is the cause,
|
||||
then use:
|
||||
|
||||
```php
|
||||
\Doctrine\Deprecations\Deprecation::triggerIfCalledFromOutside(
|
||||
"doctrine/orm",
|
||||
"https://link/to/deprecations-description",
|
||||
"message"
|
||||
);
|
||||
```
|
||||
|
||||
Based on the issue link each deprecation message is only triggered once per
|
||||
request.
|
||||
|
||||
A limited stacktrace is included in the deprecation message to find the
|
||||
offending location.
|
||||
|
||||
Note: A producer/library should never call `Deprecation::enableWith` methods
|
||||
and leave the decision how to handle deprecations to application and
|
||||
frameworks.
|
||||
|
||||
## Usage in PHPUnit tests
|
||||
|
||||
There is a `VerifyDeprecations` trait that you can use to make assertions on
|
||||
the occurrence of deprecations within a test.
|
||||
|
||||
```php
|
||||
use Doctrine\Deprecations\PHPUnit\VerifyDeprecations;
|
||||
|
||||
class MyTest extends TestCase
|
||||
{
|
||||
use VerifyDeprecations;
|
||||
|
||||
public function testSomethingDeprecation()
|
||||
{
|
||||
$this->expectDeprecationWithIdentifier('https://github.com/doctrine/orm/issue/1234');
|
||||
|
||||
triggerTheCodeWithDeprecation();
|
||||
}
|
||||
|
||||
public function testSomethingDeprecationFixed()
|
||||
{
|
||||
$this->expectNoDeprecationWithIdentifier('https://github.com/doctrine/orm/issue/1234');
|
||||
|
||||
triggerTheCodeWithoutDeprecation();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Displaying deprecations after running a PHPUnit test suite
|
||||
|
||||
It is possible to integrate this library with PHPUnit to display all
|
||||
deprecations triggered during the test suite execution.
|
||||
|
||||
```xml
|
||||
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
|
||||
colors="true"
|
||||
bootstrap="vendor/autoload.php"
|
||||
displayDetailsOnTestsThatTriggerDeprecations="true"
|
||||
failOnDeprecation="true"
|
||||
>
|
||||
<!-- one attribute to display the deprecations, the other to fail the test suite -->
|
||||
|
||||
<php>
|
||||
<!-- ensures native PHP deprecations are used -->
|
||||
<server name="DOCTRINE_DEPRECATIONS" value="trigger"/>
|
||||
</php>
|
||||
|
||||
<!-- ensures the @ operator in @trigger_error is ignored -->
|
||||
<source ignoreSuppressionOfDeprecations="true">
|
||||
<include>
|
||||
<directory>src</directory>
|
||||
</include>
|
||||
</source>
|
||||
</phpunit>
|
||||
```
|
||||
|
||||
Note that you can still trigger Deprecations in your code, provided you use the
|
||||
`#[WithoutErrorHandler]` attribute to disable PHPUnit's error handler for tests
|
||||
that call it. Be wary that this will disable all error handling, meaning it
|
||||
will mask any warnings or errors that would otherwise be caught by PHPUnit.
|
||||
|
||||
At the moment, it is not possible to disable deduplication with an environment
|
||||
variable, but you can use a bootstrap file to achieve that:
|
||||
|
||||
```php
|
||||
// tests/bootstrap.php
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
require dirname(__DIR__) . '/vendor/autoload.php';
|
||||
|
||||
use Doctrine\Deprecations\Deprecation;
|
||||
|
||||
Deprecation::withoutDeduplication();
|
||||
```
|
||||
|
||||
Then, reference that file in your PHPUnit configuration:
|
||||
|
||||
```xml
|
||||
<phpunit …
|
||||
bootstrap="tests/bootstrap.php"
|
||||
…
|
||||
>
|
||||
…
|
||||
</phpunit>
|
||||
```
|
||||
|
||||
## What is a deprecation identifier?
|
||||
|
||||
An identifier for deprecations is just a link to any resource, most often a
|
||||
Github Issue or Pull Request explaining the deprecation and potentially its
|
||||
alternative.
|
||||
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"name": "doctrine/deprecations",
|
||||
"description": "A small layer on top of trigger_error(E_USER_DEPRECATED) or PSR-3 logging with options to disable all deprecations or selectively for packages.",
|
||||
"license": "MIT",
|
||||
"type": "library",
|
||||
"homepage": "https://www.doctrine-project.org/",
|
||||
"require": {
|
||||
"php": "^7.1 || ^8.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"doctrine/coding-standard": "^9 || ^12",
|
||||
"phpstan/phpstan": "1.4.10 || 2.0.3",
|
||||
"phpstan/phpstan-phpunit": "^1.0 || ^2",
|
||||
"phpunit/phpunit": "^7.5 || ^8.5 || ^9.5",
|
||||
"psr/log": "^1 || ^2 || ^3"
|
||||
},
|
||||
"suggest": {
|
||||
"psr/log": "Allows logging deprecations via PSR-3 logger implementation"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Doctrine\\Deprecations\\": "src"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"DeprecationTests\\": "test_fixtures/src",
|
||||
"Doctrine\\Foo\\": "test_fixtures/vendor/doctrine/foo"
|
||||
}
|
||||
},
|
||||
"config": {
|
||||
"allow-plugins": {
|
||||
"dealerdirect/phpcodesniffer-composer-installer": true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
{
|
||||
"name": "doctrine/event-manager",
|
||||
"description": "The Doctrine Event Manager is a simple PHP event system that was built to be used with the various Doctrine projects.",
|
||||
"license": "MIT",
|
||||
"type": "library",
|
||||
"keywords": [
|
||||
"events",
|
||||
"event",
|
||||
"event dispatcher",
|
||||
"event manager",
|
||||
"event system"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Guilherme Blanco",
|
||||
"email": "guilhermeblanco@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Roman Borschel",
|
||||
"email": "roman@code-factory.org"
|
||||
},
|
||||
{
|
||||
"name": "Benjamin Eberlei",
|
||||
"email": "kontakt@beberlei.de"
|
||||
},
|
||||
{
|
||||
"name": "Jonathan Wage",
|
||||
"email": "jonwage@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Johannes Schmitt",
|
||||
"email": "schmittjoh@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Marco Pivetta",
|
||||
"email": "ocramius@gmail.com"
|
||||
}
|
||||
],
|
||||
"homepage": "https://www.doctrine-project.org/projects/event-manager.html",
|
||||
"require": {
|
||||
"php": "^8.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"doctrine/coding-standard": "^12",
|
||||
"phpstan/phpstan": "^1.8.8",
|
||||
"phpunit/phpunit": "^10.5",
|
||||
"vimeo/psalm": "^5.24"
|
||||
},
|
||||
"conflict": {
|
||||
"doctrine/common": "<2.9"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Doctrine\\Common\\": "src"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"Doctrine\\Tests\\Common\\": "tests"
|
||||
}
|
||||
},
|
||||
"config": {
|
||||
"allow-plugins": {
|
||||
"dealerdirect/phpcodesniffer-composer-installer": true
|
||||
},
|
||||
"sort-packages": true
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user